下面的基础学习, java进阶, java高级均来自菜鸟教程

java基础学习

组织结构method–class–file–package

java里的四个层次: method/property👉 class(object)👉 .java👉 package

  • 每一个后者, 都能拥有多个前者.
  • class在磁盘, 实例化后, object在内存.
  • 一个package内所有、任何class都能互相访问.

    如果做了import, 那么当前类也能访问包外的类了.

  • 一个.java文件只能有一个public class, 其余的class都是private的.

    一个文件也叫编译单元, 只能有一个公共接口(用public类来表现).
    强制规定public类名==文件名, 因此在引用其它文件的类时无需显式声明。在编译时,编译器会根据类名去寻找同名文件。

  • method中的特例, main方法: public static void main(String[] args){...}
    main是 Java 程序的入口点,只有作为应用程序的启动类才需要定义它.
    因此, 它在后三个层次里都不是必须的.

class

内部类

内部类可以随意使用外部类的成员变量(包括私有)而不用生成外部类的对象,这也是内部类的唯一优点.
程序编译过后会产生两个 .class 文件,分别是 Out.class 和 Out$In.class.
内部类对象的生成: Out.In in = new Out().new In()

数据类型

分类

  • 基本数据类型(primitive type)(仅8个):

    • 原始类型:boolean,char,byte,short,int,long,float,double。
    • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double。

    原始类型变成包装类型后, 就可以当引用数据类型使用了.

  • 引用数据类型(reference type): 剩下的都是.
    String数组接口Lambda表达式.

字符串类型

java的字符串相关的类有三种:

  • String: 被 final 修饰的,他的长度是不可变的. 就算调用 String 的 concat 方法,那也是把字符串拼接起来并重新创建一个对象
  • StringBuffer: 长度可变的, 线程安全的,但是效率低. (基本没有适用场景)
  • StringBuilder: 长度可变的, 线程不安全, 但效率高. (最常用)

从 jdk1.5 开始,把所有用加号连接的 string 运算都隐式的改写成 stringbuilder,也就是说,用加号拼接字符串已经没有任何性能损失了。
但是, 用循环拼接字符串的时候,还是老老实实的用 stringbuilder 吧。

其他要点

  • 自动类型转换(低级转高级): byte, short, char👉 int👉 long👉 float👉 double.
  • 不能对boolean类型进行类型转换。
  • 不能把对象类型转换成不相关类的对象。
  • 在把容量大的类型转换为容量小的类型时必须使用强制类型转换
  • 使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析.

字符串

  • 其它类型👉 字符串的方法:
    • 调用类的串转换方法:X.toString();
    • 自动转换:X+””;
    • 使用String的方法:String.valueOf(X);
  • 字符串👉 其他类型的方法:
    • 先转换成相应的封装器实例,再调用对应的方法转换成其它类型, 如new Float("32.1").doubleValue()Double.valueOf("32.1").doubleValue()
    • 静态parseXXX方法, 如Double.parseDouble("32.1")
  • 任何字符类型字符串相加,结果都是拼接。

变量

方法参数变量的值传递方式有两种:

  • 值传递:在方法调用时,传递的是实际参数的值的副本。所以参数变量的修改不会影响原始值。Java 中的基本数据类型都采用值传递。
  • 引用传递:在方法调用时,传递的是实际参数的引用(即内存地址)。所以参数变量的修改影响原始值。Java 中的对象类型采用引用传递。

静态变量在内存中只有一份拷贝,被所有实例共享。因此多线程时需要考虑其线程安全性。

修饰符

4个访问修饰符:

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
修饰符 当前类 同一包内 子孙类(同一包) 子孙类(不同包) 其他包
public Y Y Y Y Y
protected Y Y Y Y/N(说明 N
default Y Y Y N N
private Y N N N N

protected 需要从以下两个点来分析说明:

  • 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
  • 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。

运算符

下表中, 上→下的优先级高→低.

类别 操作符 关联性
后缀 () [] . (点操作符) 左到右
一元 expr++ expr– 从左到右
一元 ++expr –expr + - ~ ! 从右到左
乘性 * /% 左到右
加性 + - 左到右
移位 >> >>>  << 左到右
关系 > >= < <= 左到右
相等 ==  != 左到右
按位与 左到右
按位异或 ^ 左到右
按位或 | 左到右
逻辑与 && 左到右
逻辑或 | | 左到右
条件 ?: 从右到左
赋值 = + = - = * = / =%= >> = << =&= ^ = | = 从右到左
逗号 左到右

数组

内置方法

数组的声明和创建(初始化):

  • double[] myList = new double[size]; myList[0] = 3.14; ...
  • double[] myList = {1.9, 2.9, 3.4, 3.5};
  • 直接在函数参数里创建: public void method1(new int[]{3, 1, 2, 6, 4, 2}) {...}

多维数组:

  • 固定行列: int[][] myArray = new int[2][3];
  • 不固定: int[][] myArray = new int[2][]; myArray[0] = new int[3]; myArray[1] = new int[2];

Array类

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。

正则表达式

java的特殊语法: 两个 \\ 代表其他语言中的一个 \ (转义字符)。

java.util.regex 包,它包含了 Pattern 和 Matcher 类,用于处理正则表达式的匹配操作。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexMatches
{
public static void main( String[] args ){
// 按指定模式在字符串查找
String line = "This order was placed for QT3000! OK?";
String pattern = "(\\D*)(\\d+)(.*)";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 现在创建 matcher 对象
Matcher m = r.matcher(line);
if (m.find( )) {
System.out.println("Found value: " + m.group(0) );
System.out.println("Found value: " + m.group(1) );
System.out.println("Found value: " + m.group(2) );
System.out.println("Found value: " + m.group(3) );
} else {
System.out.println("NO MATCH");
}
}
}

异常处理

分类

  • 检查性异常:在编译时编译器强制要求处理的异常。如果一个方法可能抛出检查型异常,那么该方法必须声明抛出该异常,或者在其内部捕获并处理该异常。
  • 运行时异常:在运行时JVM虚拟机自动抛出的异常,编译器不要求强制处理这些异常。通常表示编程错误或逻辑错误。如数组下标越界 (ArrayIndexOutOfBoundsException)。

语法

  • 一般地, try块后, 应至少有catchfinally块之一.
  • 而在try-with-resources语句中, 把资源声明写在try后面, 可以确保在代码块结束时自动关闭资源(如文件、数据库连接等), 也就是隐式拥有了finally的功能, 因此可以只有try块.
    public static void main(String[] args) throws IOException{
    try (Scanner scanner = new Scanner(new File("test.txt"));
    PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
    while (scanner.hasNext()) {
    writer.print(scanner.nextLine()+ "\n");
    }
    }
    }

java进阶–面向对象

继承

  • java中只有单继承, 没有多继承(多个父亲)
  • 关键字:
    • extends: 继承父类, 如public class Penguin extends Animal{ ... }.
    • implements: 类似多继承, 仅限于子类继承多个父接口. 如public class C implements A,B { ... }.
  • 构造器: 子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。
    • 显式: 在子类构造器第一句写上: super(参数列表).
    • 隐式: 如果子类构造器没有super, 则默认调用父类无参构造器.

重写与重载

  • 重写(Override): 外壳不变, 核心重写.
    • 不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
  • 重载(Overload): 在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

多态

C++回顾: 虚函数的概念用以支持多态. 即在基类定义虚函数, 在派生类中重写该函数, 然后通过基类指针或引用调用虚函数时,实际调用的是派生类中重写的版本。

在Java中,

  • 多态就是同一个接口,使用不同的实例而执行不同操作.

  • 多态存在的三个必要条件

    • 继承
    • 重写
    • 父类引用指向子类对象: Parent p = new Child();
  • Java普通函数都相当于 C++ 的虚函数,动态绑定是Java的默认行为。 如果想避免这种特性, final 关键字可变成非虚函数。

  • Java的多态和C++类似: 子类重写了父类的函数, 那么在运行时, 即使子类对象的引用变量的类型是父类, JVM调用的仍然是子类重写后的函数.

  • 多态的实现方式:

    • 重写
    • 接口
    • 抽象类和抽象方法

抽象类

抽象类是用abstract修饰的类, 不能被实例化, 只能被继承. 抽象类中可以有抽象方法, 也可以有非抽象方法.

在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象方法: 抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。如public abstract double computePay();
声明抽象方法的后果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

另外, 构造方法类方法(用 static 修饰)不能声明为抽象方法。

封装

封装(Encapsulation): 将抽象性函式接口的实现细节部分包装、隐藏起来。
步骤:

  1. 将类的属性私有化(private),
  2. 提供公共的(public)方法来获取(get)和设置(set)该属性的值。

接口

概念: 是一个抽象类型,是抽象方法的集合,以interface来声明, 隐式抽象的, 因此不必写abstract.

对比:

  • 接口与类相似点:
    • 一个接口可以有多个方法。
    • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
    • 接口的字节码文件保存在 .class 结尾的文件中。
    • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
  • 接口与类的区别:
    • 接口不能用于实例化对象。
    • 接口没有构造方法。
    • 接口中所有的方法必须是抽象方法(隐式默认为public abstract, 因此不必写),且没有方法体.
      • Java 1.8 之后, 可以有方法体了, 但其声明要用default关键字修饰.
      • Java 1.9 之后, 可以有private方法.
    • 接口可以有变量, 但均被隐式指定为public static final, 因此不必写。
    • 接口不是被类继承了,而是要被类实现。
    • 接口支持多继承(多个爸爸)。
  • 接口与抽象类的区别
    • 接口中的方法不能有方法体
    • 接口中不能含有静态代码块以及静态方法(static)
      • Java 1.8 之后, 可以有了.
    • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

语法:

// 接口定义
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
// 接口实现
[可见度] class 类名 [extends 其他的类] implements 接口名称[, 其他接口名称, ...] {
// 接口中所有的方法都必须实现
}

重写接口的方法时的注意事项:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

标记接口:
标记接口是没有任何方法和属性的接口. 目的:

  • 建立一个公共的父接口。
  • 向一个类添加数据类型: 这个接口名就是所有派生对象被定义时的数据类型名.

枚举

是特殊的, 一般用户只需定义一组常量. 这些常量默认为public static final的.

语法:

// 枚举定义
public enum Color{ RED, GREEN, BLUE;
// 构造函数, 只能私有
private Color() { ... }
// 其他方法: 可以是普通的, 也可以是抽象的
}
// 枚举使用
Color c = Color.RED;
// 枚举遍历
for (Color myVar : Color.values()) { ... }

枚举类的方法:
它默认继承了java.lang.Enum类, 并实现了java.lang.Serializablejava.lang.Comparable两个接口. 因此有以下方法:

  • values(): 返回枚举类中所有的值.
  • valueOf(): 将普通字符串转换成枚举实例.
  • ordinal(): 获取枚举变量在枚举类中的索引位置.
  • compareTo(): 比较两个枚举变量的顺序.
  • toString(): 返回枚举变量名.

包机制,用于区别类名的命名空间。可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的

// 包定义: 必须放在源文件的第一行; 通常全小写
package pkg1[.pkg2[.pkg3...]];
// 包引用
import package1[.package2...].(classname|*);

注意,使用通配符 * 导入整个包时,只会导入包中的类,而不会导入包中的子包.
import 声明必须在包声明之后,类声明之前。

反射

反射(Reflection)是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。

反射 API

Java 的反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:

  • java.lang.Class:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。
  • java.lang.reflect.Field:表示类的字段(属性)。提供了访问和修改字段的能力。
  • java.lang.reflect.Method:表示类的方法。提供了调用方法的能力。
  • java.lang.reflect.Constructor:表示类的构造函数。提供了创建对象的能力。

工作流程

  1. 获取类的Class 对象. ( 每个类在JVM中都有一个对应的 Class 对象.)

    // 方式1. 通过类字面量
    Class<?> clazz = MyClass.class;
    // 方式2. 通过对象的getClass()方法
    MyClass obj = new MyClass();
    Class<?> clazz = obj.getClass();
    // 方式3. 通过Class.forName()方法
    Class<?> clazz = Class.forName("com.example.MyClass");
  2. 获取成员信息:通过 Class 对象,可以获取类的字段、方法、构造函数等信息。

        // 先获取构造函数, 创建新对象
    Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
    Object clzObj = constructor.newInstance("John", 30);
    // 获取字段
    Field field = clazz.getDeclaredField("fieldName");
    field.setAccessible(true); // 如果字段是私有的,需要设置为可访问
    Object value = field.get(clzObj); // 获取字段的值
    // 获取方法
    Method method = clazz.getMethod("methodName", String.class);
    // 获取所有接口
    Class<?>[] interfaces = clazz.getInterfaces();
    // 获取父类
    Class<?> superclass = clazz.getSuperclass();

    3. 操作成员:通过反射 API 可以读取和修改字段的值、调用方法以及创建对象。
    ```java
    // 修改字段
    field.set(clzObj, "new value");
    // 调用方法
    method.invoke(clzObj, "arg1", "arg2");
    // 调用构造函数
    Object newObj = constructor.newInstance("arg1", "arg2");

    ## java高级

    ### 数据结构

    在`java.util`包中, 有常用的如: 数组, 列表, 集合等.
    ```java
    // 数组
    int[] arr = new int[5];
    // 列表--数组列表, 链表(双向)
    List<String> list = new ArrayList<>();
    List<String> list = new LinkedList<>();
    // 集合--哈希表, 红黑树
    Set<String> set = new HashSet<>();
    Set<String> set = new TreeSet<>();
    // 映射--哈希表, 红黑树
    Map<String, Integer> map = new HashMap<>();
    Map<String, Integer> map = new TreeMap<>();
    // 栈
    Stack<String> stack = new Stack<>();
    // 队列
    Queue<String> queue = new LinkedList<>();
    // 堆
    PriorityQueue<String> queue = new PriorityQueue<>();
    PriorityQueue<String> queue = new PriorityQueue<>(Collections.reverseOrder());
    // 树. 图. ...

java.util中的其他数据结构:

  • 枚举接口: 是传统接口, 已被迭代器取代.
  • 位集合:
    BitSet bits1 = new BitSet(16);
    for(int i=0; i<16; i++) {
    if ((i % 2) == 0) bits1.set(i);}
    // bits1打印结果: {0, 2, 4, 6, 8, 10, 12, 14}
  • 向量: 向量(Vector)类和传统数组非常相似,但是Vector的大小能根据需要动态的变化。
  • 字典: 弃用. 推荐使用 Map 接口及其实现类.
  • 哈希表: Hashtable不再推荐, HashMap 和 ConcurrentHashMap 更常被推荐.
  • 属性: Properties类非常常用, 继承于Hashtable. 表示一个持久的属性集. 常用于处理配置文件、属性文件和国际化资源时.
    Properties prop = new Properties();
    // 加载配置文件
    prop.load(new FileInputStream("config.properties"));
    // 添加
    prop.put("name", "John");
    // 获取--单个属性值
    String name = prop.getProperty("name");
    // 获取--所有属性值
    Set keys = prop.keySet();
    Iterator it = keys.iterator();
    while (it.hasNext()) {
    String key = (String) it.next(); // print...
    }
    // 设置属性值
    prop.setProperty("name", "John");
    // 保存属性到文件
    prop.store(new FileOutputStream("config.properties"), "Configuration");

集合框架

java集合框架

集合框架是用来代表和操纵集合的统一架构。它包含了:

  • 接口: 是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。
  • 类: 是集合接口的具体实现。例如:ArrayList、LinkedList、HashSet、HashMap等。
  • 算法: 是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序. 存在于java.util.Collections类中.
    如排序算法: Collections.sort(mylist)

注意: 任何没有使用泛型的对象加入集合类后,自动转变为Object类型,所以在取出的时候,需要进行强制类型转换。

框架内的工具–迭代器

迭代器是一种设计模式,属于轻量级对象.

  • 作用: 可以逐个访问集合中的元素,而不需要使用传统的 for 循环或索引。这种方式更加简洁和灵活,并且适用于各种类型的集合。
  • 使用: next(), hasNext(), remove().
    import java.util.Iterator;
    // 准备一个集合
    List<String> list = new ArrayList<>(); ...;
    // 创建迭代器
    Iterator<String> it = list.iterator();
    // 迭代器遍历
    while (it.hasNext()) {
    String str = it.next(); // 打印...
    if(str.equals("abc")) {
    it.remove(); // 删除
    }
    }

框架内的工具–比较器

比较器是用来定义对象的排序规则的接口. 它包含两个方法:compare()equals()

Object类

Object 是所有类的父类, 如果一个类声明时没有任何父类, 则默认继承了 Object 类.

泛型

  • 泛型本质: 把数据类型参数化.
  • 类型参数: 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前.
  • 类型参数的上限: (Upper Bound)即类型参数可以接受的类型范围, 用extends语法。如下面的例子2
  • 泛型标记符:
    • E - Element (在集合中使用,因为集合中存放的是元素)
    • T - Type(Java 类)
    • K - Key(键)
    • V - Value(值)
    • N - Number(数值类型)
    • ?- 表示不确定的 Java 类型, 即类型通配符

泛型方法

例子1–允许数组的元素是任何数据类型:

public class Test{
// 定义泛型方法
public static <E> void printArray(E[] inputArray) {
for(E element : inputArray) {...}
}
// 调用泛型方法
public static void main(String args[]) {
printArray( obj );
// 这里的 obj 可以是任意数据类型的数组.
}
}

例子2–定义一个泛型函数, 实现任意类型的对象作比较. 下面的<T extends Comparable<T>>表示 T 必须是实现了 Comparable<T> 接口的类.

public class Test {
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z)
{
T max = x;
if ( y.compareTo( max ) > 0 ){max = y;}
if ( z.compareTo( max ) > 0 ){max = z;}
return max;
}
}

泛型类

public class Box<T> {   
private T t;
public void add(T t) {this.t = t;}
public T get() {return t;}
}

java序列化

序列化是一种将对象转换为字节流的过程,以便在网络上传输或存储在文件中。反序列化则是将字节流转换回对象的过程。

如何让一个对象可序列化:

  • 在定义它的类时必须实现java.io.Serializable 接口. 这个接口内没有任何方法, 需要被继承者实现.
  • 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
    // 1.定义一个类, 它必须实现 java.io.Serializable 接口
    import java.io.Serializable;
    public class MyClass implements Serializable {
    public transient int SSN; // 表面该属性不可被序列化
    public int number;
    public void mailCheck(){...}
    }
    // 2.序列化对象.
    // 这里`ObjectOutputStream`是高层次的数据流,它们包含反序列化和序列化对象的方法。
    MyClass obj = new MyClass();
    try {
    FileOutputStream fileOut = new FileOutputStream("object.ser");
    ObjectOutputStream out = new ObjectOutputStream(fileOut);
    out.writeObject(obj);
    out.close();
    fileOut.close();
    } catch (IOException e) {
    e.printStackTrace();
    }

    // 3.反序列化对象
    MyClass obj = null;
    try {
    FileInputStream fileIn = new FileInputStream("object.ser");
    ObjectInputStream in = new ObjectInputStream(fileIn);
    obj = (MyClass) in.readObject(); //默认返回值为`Object`,因此需要将它转换成合适的数据类型。
    in.close();
    fileIn.close();
    } catch (IOException e) {
    e.printStackTrace();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }

java网络编程

两台机器利用套接字建立TCP连接的步骤:

  • 服务器实例化 ServerSocket 对象,表示通过服务器上的端口通信。
  • 服务器调用套接字对象的accept() 方法,它会一直等待直到客户端连接到服务器上给定的端口。
  • 客户端实例化 Socket 对象,指定服务器名称和端口号来请求连接
  • 客户端的Socket 类的构造函数如果成功连接到指定的服务器和端口号,则创建 Socket 对象与服务器通信。
  • 服务器accept() 方法等来了连接, 会返回一个新的 socket 引用,该 socket 连接到客户端的 socket。
  • 连接建立后,通过使用 I/O 流进行双向通信,即每一个socket都有一个输出流和一个输入流。

辨析两个Socket类:

  • ServerSocket: 服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
  • Socket: java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。C/S两端都有一个Socket对象, 其中,
    • 客户端要获取一个 Socket 对象是通过类的实例化.
    • 服务器要获取一个 Socket 对象是通过accept()方法的返回值.

项目学习–前后端分离的博客系统

资料:

下面是自己全程学习的简要笔记

项目搭建

项目涉及前台, 后台两套系统, 为此在该项目(父模块)中设计三个子模块: 公共子模块(可被其他子模块复用), 前台子模块, 后台子模块.

在依赖管理方面:

  • 父模块: 有<dependencyManagement>标签, 其中所有依赖均被锁定版本号, 但不实际安装依赖.
  • 子模块: <dependency>中不指定版本号.

Static Badge Static Badge Static Badge Static Badge
Copyright © 2023-2024 Raymond H., All Rights Reserved.