Java

Java

Java 概述

环境介绍

  • JVM(java virtual machine)
    • 是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令、管理数据、内存、寄存器,包含在 JDK 中。
    • 对于不同的平台,有不同的虚拟机。
    • Java 虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。
  • JDK(java development kit, java 开发工具包)
    • JDK = JRE + java 开发工具(java, javac, javadoc, javap 等)
    • JDK 是提供给 java 开发人员使用的,其中包含了 java 的开发工具,也包括了 JRE。
  • JRE(java runtime environment, java 运行时环境)
    • JRE = JVM + java 核心类库
    • 如果想要运行一个开发好的 java 程序,计算机只需要安装 JRE 即可。
  • test.java(源文件) –> 编译 javac –> test.class(字节码文件) –> 运行 java –> JVM

注意事项和细节说明

  • java 应用程序的执行入口是 main() 方法,有固定的书写格式: public static void main(String [] args)
  • 一个源文件中最多只能包含一个 public 类(其他类的个数不限制, 也可以将 main 方法写在非 public 类中,然后制定运行非 public 类),同时文件名必须按该类名命名。
  • 编译后,每一个类都对应一个 .class 文件。

变量

数据类型

  • 每一种数据都定义了明确的数据类型,在内存中分配了不同大小的内存空间。

基本数据类型

数值型
整数类型
  • java 没有无符号数
  • byte(1字节)
  • short(2字节)
  • int(4字节): java 的整型常量默认是 int 类型,声明 long 类型常量加 l / L。
  • long(8字节)
浮点类型
  • float(4字节): java 的浮点型常量默认是 double 类型,声明 float 类型常量加 f / F。
  • double(4字节)
字符型
  • char(2字节)
    • 字符类型可以直接存放一个数字(char 的本质是一个整数,在输出时,是 unicode 码对应的字符)
    • 是可以用于运算的,相当于一个整数
字符编码表
  • ASCII: 一个字节表示
  • Unicode: 固定大小的编码,使用两个字节来表示字符,字母和汉字统一都占用两个字节。(兼容 ASCII 码)
  • utf-8: 大小可变的编码(可以使用 1-6 个字节表示一个符号),字母使用 1 字节,汉字使用 3 字节。
  • gbk: 大小可变的编码,字母使用 1 字节,汉字使用 2 字节。
布尔型
  • boolean(1字节): 存放 true / false
    • 不可以 0 或非 0 的整数代替 false 和 true,这点和 C 语言不同。
自动类型转换
  • 赋值或运算时,精度小的类型自动转换为精度大的数据类型。
  • char -> int -> long -> float -> double
  • byte -> short -> int -> long -> float -> double
  • 有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。
  • 把精度大的数据类型赋值给精度小的数据类型时会报错,反之会进行自动类型转换。
    • int a = 1.1; // error
  • (byte, short) 和 char 之间不会相互自动转换。
  • 当把具体数赋给 byte 时,先判断该数是否在 byte 范围内,如果是就可以
    • byte b = 1; // yes
    • int a = 1; byte b = a; // error
  • byte, short, char 三者之间可以运算,在运算时首先转换为 int 类型。(即使只使用 byte 进行运算也会先转为 int)
    • byte a = 1; byte b = a + a; // error
  • boolean 不参与类型转换。
强制类型转换
  • 自动类型转化的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时加上强制转换符 (),可能造成精度降低或溢出。
    • int n = (int)1.9;
基本数据类型和 String 类型的转换
  • 基本数据类型 -> String: 基本类型的值 + “”
    • int a = 100; String s = a + “”;
  • String -> 基本数据类型: 通过基本类型的包装类调用 parseXX 方法
    • String s = “123”; int n = Integer.parseInt(s);
    • 如果格式不正确,就会抛出异常。(编译能过,运行报错)
      • String s = “hello”; int n = Integer.parseInt(s);

引用数据类

类(class)
接口(interface)
数组([])

运算符

算术运算符

加法

  • 当左右两边都是数值类型时,做加法运算。
  • 当左右两边有一方为字符串时,做拼接运算。(从左到右运算)
    • “1” + 1 输出 “11”
    • 100 + 3 + “hello” 输出 “103hello”
    • “hello” + 100 + 3 输出 “helle1003”

除法

  • System.out.println(10 / 4); // 2
  • System.out.println(10.0 / 4); // 2.5
  • System.out.println(10 / 4.0); // 2.5
  • double d = 10 / 4; // 2.0

取模

  • a % b = a - a / b * b;
    • 10 % 3; // 1
    • -10 % 3; // -1
    • 10 % -3; // 1
    • -10 % -3; // -1

赋值运算符

基本赋值运算符

  • int a = 10;

复合赋值运算符

  • a += b;
  • 也会进行类型转换
    1
    2
    3
    byte a = 1;
    a += 2; // 等价于 a = (byte)(a + 2);
    // a = a + 2; // 这样写是错的

关系运算符(比较运算符)

  • ==, !=, <, >, <=, >=, instanceof

逻辑运算符

  • 用于连接多个条件,最终的结果也是一个 boolean 值。
  • &&, ||, !
  • &, |, ^
  • & 和 && 作用相同,| 和 || 作用相同,只不过 && 和 || 第一个条件能得出结果时,后面的条件不会执行;而 & 和 | 会执行所有条件。

位运算符

  • (>>, <<) 算术移位
  • (>>>) 逻辑右移(没有逻辑左移)

三元运算符

基本语法

  • 条件表达式? 表达式1: 表达式2;

控制结构

顺序控制

分支控制(同 C++)

  • switch(表达式)中表达式的返回值必须是: byte, short, int, char, enum, String; case 子句中的值必须是常量或者常量表达式

循环控制

  • for: 同 C++
  • while: 同 C++

break

continue

return

数组、排序和查找

数组

  • 数组可以存放多个同一类型的数据。
  • 数组也是一种数据类型,是引用类型。

初始化

动态初始化1
1
int[] a = new int[3];
动态初始化2(先声明后定义)
1
2
int[] a;
a = new int[3];
静态初始化
1
int[] a = {1, 2, 3};
  • java 中数组元素都是在堆中,不论是静态还是动态初始化。

默认值

  • int/short/byte/long: 0
  • float/double: 0.0
  • char: \u0000
  • boolean: false
  • String: null

赋值

  • 数组在默认情况下是引用传递,赋的值是地址,赋值方式为引用传递(是一个地址)
    1
    2
    int[] arr1 = {1, 2, 3};
    int[] arr2 = arr1; // arr2 的变化会影响 arr1

二维数组

1
2
3
4
int [][] arr = new int[2][3];
int[][] arr = {{1, 2, 3}, {4, 5, 6}};
System.out.println(arr.length); // 2
System.out.println(arr[0].length); // 3

排序

查找

面向对象编程(基础)

类与对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Hello{
public static void main(String [] args) {
Cat cat1 = new Cat();
cat1.name = "yzy";
cat1.age = 2;
cat1.color = "white";
System.out.println(cat1.name);
}
}

class Cat{
String name;
int age;
String color;
}

java 内存结构分析

  • 栈: 一般存放基本数据类型(局部变量)
  • 堆: 存放对象(Cat cat, 数组等)
  • 方法区: 常量池(常量,比如字符串),类加载信息

成员方法

1
2
3
4
5
6
7
8
class Cat{
String name;
int age;
String color;
public void speak() {
System.out.println("miao~");
}
}

成员方法传参机制

  • 对于基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参。
  • 对于引用数据类型,传递的是地址(传递的也是值,但是值是地址),可以通过形参影响实参。

重载 overload

  • 方法名: 必须相同
  • 形参列表: 必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
  • 返回类型: 无要求

可变传参

  • java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
  • 可变参数的实参可以为 0 个或任意多个
  • 可变参数的实参可以为数组
  • 可变参数的本质就是数组
  • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
  • 一个形参列表中只能有一个可变参数
    1
    2
    3
    4
    5
    6
    // 1. int... 表示接受的是可变参数,类型是int,即可以接收多个int(0-多)
    // 2. 使用可变参数时,可以当做数组来使用,即 nums 可以当作数组
    public int sum(int... nums) {
    System.out.println("可变参数的长度:" + nums.length);
    return 0;
    }

作用域

  • 全局变量: 也就是属性,作用域为整个类体。(属性在定义时,可以直接赋值)
  • 全局变量可以不赋值,直接使用,因为有默认值
  • 局部变量必须赋值后,才能使用,因为没有默认值
  • 全局变量和局部变量可以重名,使用时遵循就近原则
  • 全局变量可以加修饰符

构造器(构造函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Hello{
public static void main(String [] args) {
Cat cat1 = new Cat("yzy", 2, "white");
cat1.print();
}
}

class Cat{
String name;
int age;
String color;
public Cat(String _name, int _age, String _color) {
name = _name;
age = _age;
color = _color;
}
public void print() {
System.out.println("name: " + name + " age: " + age + " color: " + color);
}
}
  • 构造器的修饰符可以默认
  • 构造器没有返回值
  • 方法名和类名必须一样
  • 参数列表和成员方法一样的规则
  • 构造器的调用由系统完成

构造器使用细节

  • 一个类可以定义多个不同的构造器,即构造器重载
  • 构造器是完成对象的初始化,并不是创建对象
  • 如果没有定义构造器,系统会给类生成一个默认无参构造器
  • 一旦定义了构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显示的定义一下

对象创建的流程分析

    1. 加载类信息(方法区,只会加载一次)
    1. 在堆中分配空间
    1. 完成对象的初始化
    • 3.1 默认初始化
    • 3.2 显示初始化
    • 3.3 构造器初始化
  • 返回对象的地址

this

1
2
3
4
5
6
7
8
9
10
class Cat{
String name;
int age;
String color;
public Cat(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
}
  • this 关键字可以用来访问本类的属性、方法、构造器
  • this 用于区分当前类的属性和局部变量
  • 访问成员方法的语法: this.方法名(参数列表)
  • 访问构造器语法: this(参数列表); 只能在构造器中使用(即只能在构造器中访问另一个构造器,且必须放在构造器的第一条语句)
  • this 不能在类定义的外部使用,只能在类定义的方法中使用

面向对象编程(中级)

  • 包的本质实际上就是创建不同的文件夹/目录来保存类文件
  • 三大作用
    • 区分相同名字的类
    • 当类很多时,可以很好地管理类
    • 控制访问范围

包基本语法

  • package 包名;
  • package 的作用是声明当前类所在的包,需要放在类的最上面,一个类最多只能有一句 package
  • import 指令放在 package 的下面,在类定义的前面,可以有多句且没有顺序要求

命名

  • com.公司名.项目名.业务模块名

常用的包

  • java.lang.*: lang 包是基本包,默认引入,不需要再引入
  • java.util.*: util 包,系统提供的工具包,工具类。(使用 Scanner)
  • java.net.*: 网络包,网络开发
  • java.awt.*: 做 java 的界面开发,GUI

引入包

  • import java.util.Scanner; // 只会引入 java.util 包下的 Scanner
  • import java.util.*; // 将 java.util 包下的所有类都引入

访问修饰符

  • 用于控制方法和属性的访问权限
  • 只有默认级别和 public 才能修饰类
  • public > protected > 默认 > private

public

  • 对外公开

protected

  • 对子类和同一个包的类公开

默认级别

  • 没有修饰符号,向同一个包的类公开

private

  • 只有类本身可以访问,不对外公开

封装

  • 把抽象出来的数据和对数据的操作封装在一起

继承

1
2
3
4
5
class Father {
}

class Son extends Father {
}
  • 子类继承了父类所有的属性和方法,非私有的属性和方法可以直接访问,但是私有属性不能在子类直接访问,要通过公共的方法去访问
  • 子类必须调用父类的构造器,完成父类的初始化。(super() 默认调用父类的无参构造器)
  • 当创建子类时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不通过
  • 如果希望指定去调用父类的某个构造器,则显式的调用一下: super(父类构造器参数列表)
  • super 在使用时,必须放在构造器的第一行(super 只能在构造器中使用)
  • super() 和 this() 都只能放在构造器的第一行,因此这两个方法不能共存在一个构造器
  • java 所有类都是 Object 类的子类,Object 类是所有类的基类
  • 父类构造器的调用不限于直接父类,将一直往上追溯直到 Object 类
  • 子类最多只能继承一个父类(直接继承),即 java 中是单继承机制
  • 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系

Super

  • 用于访问父类的属性、方法和构造器
  • 不能访问父类的私有属性及私有方法
  • 当子类中有和父类中的属性和方法重名时,为了访问父类的属性和方法,必须通过 super。如果没有重名,使用 super、this、直接访问是一样的效果
  • super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super 去访问爷爷类的成员;如果多个基类(上级类)中都有同名成员,使用 super 访问遵循就近原则

override

  • 子类有一个方法和父类的某个方法的名称、返回类型、参数都一样,那么就说子类的这个方法覆盖了父类的方法
    • 子类的方法的参数、方法名称,要和父类方法的参数、方法名称完全一样
    • 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
    • 子类方法不能缩小父类方法的访问权限,可以扩大父类的访问权限

多态

  • 方法或对象具有多种形态,是建立在封装和继承基础之上的。
  • 一个的编译类型和运行类型可以不一致。
  • 编译类型在定义类型时就确定了,不能改变。
  • 运行类型是可以变换的。
  • 编译类型看定义时 = 号的左边,运行类型看 = 号的右边。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Hello{
    public static void main(String [] args) {
    Father fs = new Son(); // fs 编译类型是 Father, 运行类型是 Son
    fs.say(); // Son!
    fs = new Girl(); // fs 编译类型是 Father, 运行类型是 Gril
    fs.say(); // Gril!
    }
    }

    class Father {
    public void say() {
    System.out.println("Father!");
    }
    }

    class Son extends Father {
    @Override
    public void say() {
    System.out.println("Son!");
    }
    }

    class Girl extends Father {
    @Override
    public void say() {
    System.out.println("Girl!");
    }
    }

向上转型

  • 父类的引用指向了子类的对象
  • 可以调用父类的所有成员(需遵循访问权限),但是不能调用子类的特有成员(因为在编译阶段,能调用哪些成员,是由编译类型决定的),最终的运行效果看子类的具体实现(即调用方法时,按照从子类开始查找方法,然后调用)

向下转型

  • 子类类型 引用名 = (子类类型)父类引用;
  • 只能强转父类的引用,不能强转父类的对象
  • 要求父类的引用必须指向的是当前目标类型的对象
  • 当向下转型后,可以调用子类类型中的所有成员

属性重写

  • 属性没有重写一说,属性的值看编译类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Hello{
    public static void main(String [] args) {
    Father fs = new Son(); // fs 编译类型是 Father, 运行类型是 Son
    System.out.println(fs.cnt); // 1, 成员变量的访问看编译类型
    }
    }

    class Father {
    int cnt = 1;
    }

    class Son extends Father {
    int cnt = 2;
    }

instanceof

  • 用于判断对象的运行类型是否为 xx 类型或 xx 类型的子类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Hello{
    public static void main(String [] args) {
    Son s = new Son();
    System.out.println(s instanceof Son); // true
    System.out.println(s instanceof Father);//true
    Father fs = (Father)s;
    System.out.println(fs instanceof Father);//true
    System.out.println(fs instanceof Father);//true
    }
    }

    class Father {
    int cnt = 1;
    }

    class Son extends Father {
    int cnt = 2;
    }

动态绑定机制

  • 当调用对象方法的时,该方法会和该对象的内存地址 / 运行类型绑定
  • 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Hello{
    public static void main(String [] args) {
    A a = new B();
    System.out.println(a.sum()); // 40
    System.out.println(a.sum1()); // 30
    }
    }

    class A {
    public int i = 10;
    public int sum() {
    return getI() + 10;
    }
    public int sum1() {
    return i + 10;
    }
    public int getI() {
    return i;
    }
    }

    class B extends A {
    public int i = 20;
    public int sum() {
    return i + 20;
    }
    public int sum1() {
    return i + 10;
    }
    public int getI() {
    return i;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Hello{
    public static void main(String [] args) {
    A a = new B();
    System.out.println(a.sum()); // 30
    System.out.println(a.sum1()); // 20
    }
    }

    class A {
    public int i = 10;
    public int sum() {
    return getI() + 10;
    }
    public int sum1() {
    return i + 10;
    }
    public int getI() {
    return i;
    }
    }

    class B extends A {
    public int i = 20;
    // public int sum() {
    // return i + 20;
    // }
    // public int sum1() {
    // return i + 10;
    // }
    public int getI() {
    return i;
    }
    }

多态参数

  • 方法定义的形参类型为父类类型,实参类型允许为子类类型

Object 类详解

== 运算符

  • 既可以判断基本类型,也可以判断引用类型
    • 判断基本类型,判断的是值是否相等
    • 判断引用类型,判断的是地址是否相等,即判断是不是同一个对象

equals 方法

  • Object 类中的方法,只能判断引用类型,默认判断的是地址是否相等,子类中往往会重写该方法,用于判断内容是否相等

hashCode 方法

  • 提高具有哈希结构的容器的效率
  • 两个引用,如果指向同一个对象,则哈希值肯定是一样的
  • 两个引用,如果指向的是不同对象,则哈希值是不一样的(有可能出现地址冲突而导致哈希值相同)
  • 一般是通过将该对象的内部地址转换为一个整数来实现的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Hello{
    public static void main(String [] args) {
    A a1 = new A();
    A a2 = new A();
    A a3 = a1;
    System.out.println(a1.hashCode() == a2.hashCode()); // false
    System.out.println(a1.hashCode() == a3.hashCode()); // true
    }
    }

    class A {
    }

toString 方法

  • 默认返回 全类名 + @ + 哈希值的十六机制
    1
    2
    Object o = new Object();
    System.out.println(o.toString()); // java.lang.Object@7181ae3f
  • 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式
  • 直接输出一个对象时,toString 方法会被默认的调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Hello{
    public static void main(String [] args) {
    A a = new A();
    System.out.println(a); // A{name='yzy'}
    }
    }

    class A {
    String name = "yzy";
    @Override
    public String toString() {
    return "A{" +
    "name='" + name + '\'' +
    '}';
    }
    }

finalize 方法(析构函数)(deprecated)

  • 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用该方法
  • 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作

面向对象编程(高级)

类变量和类方法

类变量(静态类属性)

  • 同一个类所有对象共享
  • 在类加载的时候就生成(没有创建对象实例,也能使用类变量)
  • 生命周期随类的加载开始,随着类的消亡而销毁
  • 访问: 类名.类变量名 / 对象名.类变量名 (满足访问修饰符的访问权限和范围)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Hello{
    public static void main(String [] args) {
    System.out.println(A.cnt);
    A a = new A();
    System.out.println(a.cnt);
    }
    }

    class A {
    public static int cnt = 0;
    }

类方法(静态方法)

  • 访问: 类名.类方法名 / 对象名.类方法名 (满足访问修饰符的访问权限和范围)
  • 当方法中不涉及到任何和对象相关的成员,则可以设计成类方法
  • 和普通方法一样都是随着类的加载而加载,将结构信息存储在方法区
  • 类方法中无 this 参数
  • 类方法只能访问类变量和类方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Hello{
    public static void main(String [] args) {
    A.test();
    A a = new A();
    a.test();
    }
    }

    class A {
    public static void test() {
    System.out.println("A.test()");
    }
    }

理解 main 方法语法

  • main 方法是虚拟机调用的
  • java 虚拟机需要调用类的 main 方法,所以 main 方法的访问权限必须是 public
  • java 虚拟机在执行 main 方法时不必创建对象,所以 main 方法必须是 static
  • main 方法接收 String 类型的数组参数,该数组中保存执行 java 命令时传递给所执行的类的参数
  • java 执行的程序 参数1 参数2 参数3 …

代码块

  • 又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过 {} 包围起来
  • 和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类或创建对象时隐式调用
  • 基本语法
    1
    2
    3
    4
    5
    [修饰符]{
    代码
    };
    修饰符可选,要写的话,只能写 static
    使用 static 修饰的叫静态代码块,不修饰的叫普通代码块
  • static 代码块,作用就是对类进行初始化,随着类的加载而执行,并且只会执行一次;若是普通代码块,每创建一个对象都执行
  • 静态代码块只能调用静态成员(属性和方法),普通代码块可以调用任意成员
  • 使用场景: 如果多个构造器中都有重复语句,可以抽取到代码块中,提高代码的重用
  • 不管调用哪个构造器创建对象,都会先调用代码块的内容
  • 代码块调用顺序优先于构造器
  • 构造器的最前面其实隐含了 super() 和普通代码块的调用(先 super 加载父类)

类加载时机

  • 创建对象实例时
  • 创建子类对象实例时,父类也会被加载
  • 使用类的静态成员(属性或方法)时

创建对象时类的调用顺序

  • 调用静态代码块和静态属性初始化(这俩初始化优先级是一样的,按定义的顺序调用)
  • 调用 super()、普通代码块和普通属性的初始化(这俩优先级也一样)
  • 调用构造方法

创建子类对象时类的调用顺序

  • 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  • 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  • 父类的 super()、普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  • 父类的构造方法
  • 子类的 super()、普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  • 子类的构造方法

单例设计模式

步骤

  • 构造器私有化
  • 类的内部创建对象
  • 向外暴露一个静态的公共方法

饿汉式

  • 未使用对象也会在类加载时创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class SingleTon {
    private static SingleTon tmp = new SingleTon();
    private SingleTon() {

    }
    public static SingleTon getInstance() {
    return tmp;
    }
    }

懒汉式

  • 目前懒汉式存在线程安全问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class SingleTon {
    private static SingleTon tmp;
    private SingleTon() {

    }
    public static SingleTon getInstance() {
    if (tmp == null) {
    tmp = new SingleTon();
    }
    return tmp;
    }
    }

final 关键字

  • 可以修饰类、属性、方法和局部变量
  • final 修饰的属性又叫常量,一般用大写命名
  • final 修饰的属性在定义时必须赋初值,并且以后不能修改,赋值可以加在如下位置之一
    • 定义时
    • 在构造器中
      1
      2
      3
      4
      5
      6
      class A {
      final int CNT;
      public A() {
      CNT = 0;
      }
      }
    • 在代码块中
      1
      2
      3
      4
      5
      6
      class A {
      final int CNT;
      {
      CNT = 0;
      }
      }
  • 如果 final 修饰的属性是静态的,则初始化的位置只能是定义时或者在静态代码块中,不能在构造器中赋值
  • final 类不能继承,但是可以实例化
  • 如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
  • 如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法
  • final 不能修饰构造方法
  • final 和 static 往往搭配使用,效率更高,这样不会导致类加载(单独使用 static 会加载类)
  • 包装类(Integer,Double,Float,Boolean等都是 final 类),String 也是 final 类

使用场景

  • 当不希望类被继承时
    1
    2
    final class A {}
    class B extends A {} // This should cause a compile-time error
  • 当不希望父类的某个方法被子类覆盖 / 重写时
    1
    2
    3
    4
    5
    6
    7
    class A {
    public final void test() {}
    }
    class B extends A {
    @Override
    public void test() {} // compile error
    }
  • 当不希望类的某个属性被修改时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Hello{
    public static void main(String [] args) {
    A a = new A();
    a.cnt = 1; // compile error
    }
    }

    class A {
    public final int CNT = 0;
    }
  • 当不希望某个局部变量被修改时
    1
    2
    3
    4
    5
    6
    class A {
    public void test() {
    final int CNT = 0;
    cnt = 1; // error
    }
    }

抽象类

  • 当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
  • 当一个类中存在抽象方法时,需要将该类声明为 abstract 类
    1
    2
    3
    abstract class A {
    abstract public void test();
    }
  • 抽象类不能被实例化
  • 抽象类不一定要包含 abstract 方法,
  • 一旦类包含了 abstract 方法,则这个类必须声明为 abstract 类
  • abstract 只能修饰类和方法,不能修饰属性和其他的
  • 抽象类可以有任意成员
  • 抽象方法不能有主体,即不能实现
  • 如果一个类继承了抽象类,则该类必须实现抽象类的所有抽象方法,除非该类也声明为 abstract 类
  • 抽象方法不能使用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的

模板设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract class Template {
public abstract void job();
public void calculateTime() {
long start = System.currentTimeMillis();
job();
long end = System.currentTimeMillis();
System.out.println("Time taken: " + (end - start) + " ms");
}
}

class jabA extends Template {
@Override
public void job() {
long sum = 0;
for (int i = 1; i <= 1000000; i++) {
sum += i;
}
System.out.println("Sum: " + sum);
}
}

class jabB extends Template {
@Override
public void job() {
long product = 1;
for (int i = 1; i <= 20; i++) {
product *= i;
}
System.out.println("Product: " + product);
}
}

接口

  • 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface 接口名 {
    // 属性
    // 方法
    }
    class 类名 implements 接口 {
    // 自己属性
    // 自己方法
    // 必须实现的接口的抽象方法
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    interface Ainterface {
    // 属性
    public int n = 0;
    // 方法
    // 在接口中,抽象方法,可以省略abstract关键字
    public void test();
    // 在 jdk8 之后,接口中允许定义默认方法
    default public void test1() {
    System.out.println("test1");
    }
    // 在 jdk8 之后,接口中允许定义静态方法
    static public void test2() {
    System.out.println("test1");
    }
    }

    class A implements Ainterface {
    // 实现接口中的所有抽象方法
    public void test() {
    System.out.println("实现接口中的抽象方法");
    }
    }
  • 接口不能被实例化
  • 接口中所有的方法是 public 方法,接口中的抽象方法,可以不用 abstract 修饰
  • 一个普通类实现接口,就必须将该接口的所有方法都实现
  • 抽象类实现接口,可以不用实现接口的方法
  • 一个类同时可以实现多个接口
  • 接口中的属性,只能是 final 的,而且是 public static final 修饰的
  • 接口中属性的访问形式: 接口名.属性名
  • 接口不能继承其他类,但是可以继承多个别的接口
  • 接口的修饰符只能是 public 和默认

接口 vs 继承

  • 接口是对 java 单继承机制的补充
  • 接口比继承更加灵活,继承是满足 is-a 关系,而接口只需满足 like-a 关系
  • 接口在一定程度上实现代码解耦(接口规范性 + 动态绑定机制)
接口和继承解决的问题不同
  • 继承的价值主要在于: 解决代码的复用性和可维护性
  • 接口的价值主要在于: 设计好各种规范,让其他类去实现这些方法

接口多态特性

  • 接口引用可以指向实现了接口类的对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Hello{
    public static void main(String [] args) {
    // 接口类型的变量可以指向实现了接口的对象实例
    Ainterface ifa = new A();
    Ainterface ifb = new B();
    A ifaa = new A();
    Test.func(ifa); // A
    Test.func(ifb); // B
    Test.func(ifaa);// A
    }
    }

    class Test {
    public static void func(Ainterface inf) {
    inf.test();
    }
    }

    interface Ainterface {
    public void test();
    }

    class A implements Ainterface {
    public void test() {
    System.out.println("A");
    }
    }

    class B implements Ainterface {
    public void test() {
    System.out.println("B");
    }
    }
  • 接口多态数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Hello{
    public static void main(String [] args) {
    Ainterface infs[] = new Ainterface[2];
    infs[0] = new A();
    infs[1] = new B();
    }
    }

    interface Ainterface {
    public void test();
    }

    class A implements Ainterface {
    public void test() {
    System.out.println("A");
    }
    }

    class B implements Ainterface {
    public void test() {
    System.out.println("B");
    }
    }
  • 接口多态传递(接口与接口之间继承)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Hello{
    public static void main(String [] args) {
    Ainterface a = new A();
    Binterface b = new A();
    }
    }

    interface Ainterface {
    public void test();
    }

    interface Binterface extends Ainterface {
    public void test();
    }

    class A implements Binterface {
    public void test() {
    System.out.println("A");
    }
    }

内部类

  • 一个类的内部又完整的嵌套了另一个类结构
  • 内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

定义在外部类局部位置上

局部内部类(有类名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Outer {
private int x = 10;
public void func() {
// 局部内部类是定义在外部类的局部位置,通常是在方法内
// 不能添加访问修饰符,但是可以使用final修饰(不让下面的内部类继承)
// 作用域: 仅仅在定义它的方法或代码块内有效
// 外部其他类不能访问此类
class Inner {
// 如果局部内部类的成员与外部类的成员重名,默认遵循就近原则,如果想访问外部类的成员,可以使用 外部类名.this.成员名
private int x = 20;
public void print() {
// 可以访问外部类的成员,包括私有成员
System.out.println("x = " + Outer.this.x); // 10
System.out.println("x = " + x); // 20
}
}
// 外部类可以创建局部内部类的对象并调用其方法
Inner i = new Inner();
i.print();
}
}
匿名内部类(没有类名)
  • 本质是类,同时还是一个对象(jdk 底层会分配类名)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    interface IA {
    public void test();
    }

    class Father {
    public String name;
    public Father(String name) {
    this.name = name;
    }
    public void test() {
    System.out.println("father test");
    }
    }

    class Outer {
    private int x = 10;
    public void func() {
    // a 的编译类型是 IA,运行类型是匿名内部类
    // 原理是编译器在编译时会生成一个类文件,类名为 Outer$1.class
    IA a = new IA() {
    @Override
    public void test() {
    System.out.println("inner IA test");
    }
    };
    a.test();

    // f 编译类型是 Father,运行类型是匿名内部类
    // 原理同上,编译器会生成一个类文件,类名为 Outer$2.class
    Father f = new Father("yzy") {
    @Override
    public void test() {
    System.out.println("inner Father test");
    }
    };
    f.test();

    // 也可以不使用变量,直接调用匿名内部类的方法
    new Father("yzy") {
    @Override
    public void test() {
    System.out.println("inner Father test");
    }
    }.test();
    }
    }

定义在外部类的成员位置上

成员内部类(没用 static 修饰)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Outer {
private int x = 10;
class Inner {
public void test() {
System.out.println("x = " + x);
}
}
public void func() {
Inner i = new Inner();
i.test();
}
public Inner getInner() {
return new Inner();
}
}
  • 可以添加任意的访问修饰符(因为是外部类成员)
  • 外部其他类也能使用成员内部类
    1
    2
    3
    4
    5
    6
    class Test {
    public void test() {
    Outer.Inner i = new Outer().new Inner();
    i.test();
    }
    }

静态内部类(使用 static 修饰)

  • 可以直接访问外部类的所有静态成员
  • 可以添加任意的访问修饰符

枚举和注解

自定义类实现枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Season {
private String name;
private String desc;
// 1. 将构造方法私有化
// 2. 去掉 set 方法
// 3. 在类内部直接创建固定的对象
// 4. 提供公共的静态常量
public static final Season SPRING = new Season("春天", "春天是万物复苏的季节");
public static final Season SUMMER = new Season("夏天", "夏天是炎热的季节");
public static final Season AUTUMN = new Season("秋天", "秋天是丰收的季节");
public static final Season WINTER = new Season("冬天", "冬天是寒冷的季节");
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
}

enum 关键字实现枚举

  • 当使用 enum 关键字开发一个枚举类时,默认会继承 Enum 类
  • 如果使用无参创建枚举对象,则实参列表和小括号都可以省略
  • 当有多个枚举对象时,使用逗号间隔,最后一个分号结尾
  • 枚举对象必须放在枚举类的行首
  • 使用了 enum 关键字之后,就不能继承其他类,因为 enum 会隐式继承 Enum,而 java 是单继承机制
  • 枚举类和普通类一样可以实现接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    enum Gender {
    BOY, GIRL;
    }

    enum Season {
    SPRING("Spring", "The season of new beginnings"),
    SUMMER("Summer", "The warmest season"),
    AUTUMN("Autumn", "The season of harvest"),
    WINTER("Winter", "The coldest season");
    private String name;
    private String desc;
    private Season(String name, String desc) {
    this.name = name;
    this.desc = desc;
    }
    public String getName() {
    return name;
    }
    public String getDesc() {
    return desc;
    }
    }

Enum 成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Hello{
public static void main(String [] args) {
// name()
System.out.println(Gender.BOY.name()); // BOY
// ordinal() 输出枚举常量的序数
System.out.println(Gender.BOY.ordinal()); // 0
// values() 含有所有枚举常量的数组
Gender[] values = Gender.values();
for (Gender gender : values) {
System.out.println(gender.name());
}
// valueOf() 根据名称获取枚举常量
Gender gender = Gender.valueOf("BOY");
System.out.println(gender.name()); // BOY
// compareTo() 比较两个枚举常量的序数,输出减法
System.out.println(Gender.BOY.compareTo(Gender.GIRL));
}
}

enum Gender {
BOY, GIRL;
}

JDK 内置的基本注解类型

注解的理解

  • 注解也被称为元数据,用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息
  • 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息

基础注解

  • Override: 表示指定重写父类的方法,如果父类没有该方法,则会报错
  • Deprecated: 表示某个程序元素已过时,可以修饰方法、类、字段、包、参数等
  • SuppressWarnings: 抑制编译警告

元注解: 对注解进行注解

  • Retention: 指定注解的范围,三种(SOURCE、CLASS、RUNTIME)
  • Target: 指定注解可以在哪些地方使用
  • Documented: 指定该注解是否会在 javadoc 中体现
  • Inherited: 子类会继承父类注解

异常

常见的异常

  • NullPointerException
  • ArithmeticException
  • ArrayIndexOutOfBoundsException
  • ClassCastException
  • NumberFormatException

异常处理方式

  • 没有显式处理异常,默认是 throws

try-catch-finally

throws

1
2
3
4
5
6
7
class Test {
public void test() throws Exception {
System.out.println("Hello, World!");
}
}

throw new MyException("_");
  • 子类重写父类方法时,对于抛出异常的规定: 所抛出的异常类型要么和父类抛出的异常一样,要么为父类抛出异常的子类

异常处理分类

编译时异常

运行时异常

自定义异常

1
2
3
4
5
class MyException extends Exception {
public MyException(String message) {
super(message);
}
}

throw 和 throws 对比

  • throws: 异常处理的一种方式,在方法声明处使用,后面跟异常类型
  • throw: 手动生成异常对象的关键字,在方法体中使用,后面跟异常对象

常用类

包装类

  • 针对八种基本数据类型相应的引用类型–包装类
基本类型 包装类
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
  • 常用方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Hello{
    public static void main(String [] args) {
    int a = 1;
    Integer integer = a;
    int b = integer;
    System.out.println(b);
    String str1 = integer + "";
    String str2 = integer.toString();
    String str3 = String.valueOf(integer);
    string str4 = "12345";
    Integer c = Integer.parseInt(str4);
    Integer c = new Integer(str4);
    }
    }

String

  • 实现了 Serializable 和 Comparable(可以串行化和比较大小)
  • String 是 final 类
  • 有属性 private final char value[]; 用于字符串内容(final 表示指向的地址不能修改,类似于 C++ 中 char* const)

创建剖析

直接赋值
  • String str = “yzy”;
  • 先从常量池查看是否有 “yzy” 数据空间,如果有,直接指向;如果没有则重新创建,然后指向。最终指向的是常量池的空间地址。
调用构造器
  • String str = new String(“yzy”);
  • 先在堆中创建空间,里面维护了 value 属性,指向常量池的 “yzy” 空间。如果常量池没有 “yzy”,重新创建,如果有,直接通过 value 指向。最终指向的是堆中的空间地址。
常用方法

StringBuffer

  • String 保存的是字符串常量,里面的值不能更改,每次 String 类的更新实际上就是更改地址,效率较低 // private final char value[];
  • StringBuffer 保存的是字符串常量,里面的值可以更改,每次的更新实际上可以更新内容,不用每次更新地址,效率较高

StringBuilder

  • 一般用在单线程

Math

  • 包含用于执行基本数学运算的方法

Arrays

  • 包含了一系列的静态方法,用于管理或操作数组(比如排序和搜索)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    import java.util.Arrays;

    public class Hello{
    public static void main(String [] args) {
    Integer[] integers = {1,2,3};
    System.out.println(Arrays.toString(integers));
    Arrays.sort(integers);
    }
    }

System

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Arrays;

public class Hello{
public static void main(String [] args) {
// 数组拷贝
int[] src = {1,2,3};
int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, src.length);

// 距离 1970.1.1 的毫秒数
System.out.println(System.currentTimeMillis());

// gc
System.gc();

// 退出
System.exit(0);
}
}

BigInteger、BigDecimal

BigInteger

  • 加减乘除需要使用对应的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import java.math.BigInteger;

    public class Hello{
    public static void main(String [] args) {
    BigInteger b1 = new BigInteger("123456789012345678901234567890");
    System.out.println(b1);
    BigInteger b2 = b1.add(new BigInteger("987654321098765432109876543210"));
    BigInteger b3 = b1.multiply(new BigInteger("2"));
    BigInteger b4 = b2.subtract(b3);
    BigInteger b5 = b4.divide(new BigInteger("3"));
    }
    }

BigDecimal

  • 同 BigIntger
  • 使用除法时可能除不尽,可以设置参数保留解读

Date、Calender、LocalDate

Date

  • 精确到毫秒
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import java.util.Date;
    import java.text.SimpleDateFormat;

    public class Hello{
    public static void main(String [] args) {
    Date d1 = new Date();
    System.out.println(d1); // Sat Aug 23 00:01:43 CST 2025
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String formattedDate = sdf.format(d1);
    System.out.println(formattedDate); // 2025-08-23 00:01:43
    }
    }

Calender

1
2
3
4
5
6
7
8
9
import java.util.Calendar;

public class Hello{
public static void main(String [] args) {
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());
System.out.println(calendar.get(Calendar.YEAR));
}
}

LocalDate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class Hello{
public static void main(String [] args) {
LocalDate today = LocalDate.now();
System.out.println(today);
System.out.println(today.getYear());
System.out.println(today.getMonthValue());
System.out.println(today.getDayOfMonth());
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formattedDate = today.format(dtf);
System.out.println(formattedDate);
}
}

集合

集合框架体系

Collection

Collection 公共常用方法

  • add
  • remove
  • contains: 查看元素是否存在
  • size
  • isEmpty
  • clear
  • addAll: 添加一个 Collection
  • containsAll
  • removeAll
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import java.util.ArrayList;

    public class Hello{
    public static void main(String [] args) {
    ArrayList list = new ArrayList();
    list.add("Hello, World!");
    list.add(123); // list.add(new Integer(123));
    list.add(true);
    System.out.println(list);
    list.remove(0); // 删除索引为0的元素
    list.remove(true); // 删除值为 true 的元素
    System.out.println(list);
    System.out.println(list.contains("yzy"));
    System.out.println(list.size());
    System.out.println(list.isEmpty());
    list.clear();
    System.out.println(list);
    ArrayList list2 = new ArrayList();
    list2.add("haha");
    list.addAll(list2);
    System.out.println(list);
    list.removeAll(list2);
    System.out.println(list);
    }
    }

迭代器遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.Iterator;

public class Hello{
public static void main(String [] args) {
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");

Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
}
}
}

增强 for

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.ArrayList;

public class Hello{
public static void main(String [] args) {
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");

for (Object obj : list) {
System.out.println((String)obj);
}
}
}

List

  • List 集合类中元素有序、可重复
  • 每个元素都有其顺序索引
List 接口方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;

public class Hello{
public static void main(String [] args) {
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");
System.out.println(list.get(0));
System.out.println(list.get(1));
list.add(1, "Java");
System.out.println(list.indexOf("Hello"));
System.out.println(list.lastIndexOf("Hello"));
list.remove(0);
list.set(1, "Programming");
System.out.println(list);
}
}
ArrayList
  • 可以放 null 值
  • 底层使用数组实现的
  • 基本等同于 Vector
  • 线程不安全的
  • 维护了一个 Object 类的数组
  • 当创建 ArrayList 对象时,如果使用的是无参构造器,则初始容量为 0,第一次添加,则扩容为 10,若需要再次扩容,则扩容为 1.5 倍
  • 若使用的是指定大小的构造器,则初始容量为指定大小,若在扩容,还是扩 1.5 倍
Vector
  • 底层也是数组
  • 线程安全的
LinkedList
  • 实现了双向链表和双向队列特点
  • 可以添加任意元素,包括 null
  • 线程不安全

Set

  • 无序、没有索引
  • 不允许有重复元素
  • 可以使用迭代器、增强 for
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import java.util.HashSet;

    public class Hello{
    public static void main(String [] args) {
    HashSet set = new HashSet();
    set.add("Hello");
    set.add("World");
    set.add("Hello");
    set.add(null);
    System.out.println(set);
    }
    }
HashSet
  • HashSet 实际上是一个 HashMap
LinkedHashSet
  • LinkedHashSet 实际上是一个 LinkedHashMap,底层维护了一个数组 + 双向链表
  • 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序
TreeSet
  • 使用无参构造器仍然是无序的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import java.util.TreeSet;
    import java.util.Comparator;

    public class Hello{
    public static void main(String [] args) {
    TreeSet ts = new TreeSet(new MyComparator());
    ts.add("B");
    ts.add("A");
    ts.add("C");
    System.out.println(ts);
    }
    }

    class MyComparator implements Comparator {
    public int compare(Object obj1, Object obj2) {
    return -((String)obj1).compareTo((String)obj2);
    }
    }

Map

  • Map 与 Collection 并列存在

常用方法

  • put
  • remove
  • get
  • size
  • isEmpty
  • clear
  • containKey

遍历方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.HashMap;
import java.util.Set;
import java.util.Iterator;

public class Hello{
public static void main(String [] args) {
HashMap map = new HashMap();
map.put("key1", "value1");
map.put("key2", "value2");
System.out.println(map);

// 1
Set keys = map.keySet();
for (Object key : keys) {
System.out.println(key + ": " + map.get(key));
}

// 2
Iterator it = keys.iterator();
while (it.hasNext()) {
Object key = it.next();
System.out.println(key + ": " + map.get(key));
}

// 3
Set entries = map.entrySet();
for (Object entryObj : entries) {
HashMap.Entry entry = (HashMap.Entry) entryObj;
System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 4
Iterator entryIt = entries.iterator();
while (entryIt.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) entryIt.next();
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}

HashMap

  • 底层是数组 + 链表 + 红黑树
  • 线程不安全

HashTable

  • key 和 value 都不能为 null
  • 使用方法基本上和 HashMap 一样
  • 线程安全

LinkedHashMap

TreeMap

Properties

  • 继承自 HashTable 类并且实现了 Map 接口
  • 使用特点和 HashTable 类似
  • 用于从 xxx.propertise 文件中,加载数据到 Properties 类对象

Collections

  • 一个操作 Set、List 和 Map 等集合的工具类
  • 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

泛型

  • 编译时检查添加元素的类型,提高了安全性
  • 减少了类型转化的次数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import java.util.ArrayList;

    public class Hello{
    public static void main(String [] args) {
    ArrayList <Dog> dogs = new ArrayList<Dog>();
    dogs.add(new Dog("Buddy", 3));
    dogs.add(new Dog("Max", 5));
    // dogs.add(new Cat("Whiskers", 2)); // This line will cause a compile-time error
    for (Dog dog : dogs) {
    System.out.println(dog.name + " is " + dog.age + " years old.");
    }
    }
    }

    class Dog {
    String name;
    int age;

    Dog(String name, int age) {
    this.name = name;
    this.age = age;
    }
    }

    class Cat {
    String name;
    int age;

    Cat(String name, int age) {
    this.name = name;
    this.age = age;
    }
    }

泛型语法

  • 给泛型指向数据类型时,要求是引用类型,不能是基本数据类型
    1
    2
    ArrayList<int> list = new ArrayList<int>();     // error
    ArrayList<String> list = new ArrayList<String>(); // yes
  • 在给泛型指定具体类型后,可以传入该类型或者其子类类型
  • 省略语法
    1
    2
    ArrayList<String> list = new ArrayList<>();   // 编译器会自动推断
    ArrayList list = new ArrayList(); // 不指定类型时,默认是 Object

自定义泛型

  • 普通成员可以使用泛型
  • 使用泛型的数组,不能初始化
  • 静态方法中不能使用类的泛型(因为静态是和类相关的,在类加载时,对象还没有创建,所以,如果静态方法或属性使用了泛型,JVM就无法完成初始化),但是能够定义和使用自己的泛型,成为静态泛型方法
  • 泛型类的类型,是在创建对象时确定的
  • 如果在创建对象时,没有指定类型,默认为 Object

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Hello{
public static void main(String [] args) {
Animal<String> dog = new Animal<>("Buddy");
}
}

class Animal<T> {
private T name;

public Animal(T name) {
this.name = name;
}

public T getName() {
return name;
}

public void setName(T name) {
this.name = name;
}
}

泛型接口

1
2
3
interface Animal<T> {

}

泛型方法

  • 修饰符 <T, R…> 返回类型 方法名(参数列表) {}
  • 可以定义在普通类中,也可以定义在泛型类中

泛型继承和通配符

  • 泛型没有继承性
    1
    ArrayList<Object> list = new ArrayList<String>();   // error
  • : 支持任意泛型
  • : 支持 A 类以及 A 类的子类,规定了泛型的上限
  • : 支持 A 类以及 A 类的父类,不限于直接父类,规定了泛型的下限

线程(基础)

线程使用

  • main 线程结束后,其他线程不会被动结束,还会继续执行

继承 Thread 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Hello{
public static void main(String [] args) {
Test t = new Test();
t.start();
}
}

// 当一个类继承了 Thread 类,该类就可以作为一个线程类使用
class Test extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}

实现 Runnable 接口

  • 避免单继承的限制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Hello{
    public static void main(String [] args) {
    Thread t = new Thread(new Test(), "My Runnable");
    t.start();
    }
    }
    class Test implements Runnable {
    public void run() {
    System.out.println("Hello, World!");
    }
    }

线程方法

  • setName: 设置线程名字
  • getName
  • start: 底层会创建新的线程,并调用 run
  • run: run 就是一个简单的方法调用,不会启动新线程
  • setPriority: 设置线程优先级
  • getPriority
  • sleep: 线程的静态方法,使当前线程休眠
  • interrupt: 中断线程,但并没有真正结束线程。一般用于中断正在休眠的线程(唤醒?)
  • yield: 让出 cpu,让其他线程执行(不一定礼让成功,看操作系统)
  • join: 线程的插队,一旦插队成功,则肯定先执行完插入的线程所有的任务

用户线程

  • 也叫工作线程,当线程的任务执行完或通知方式结束

守护线程

  • setDaemon(true): 一般是为工作线程服务的,当所有的工作线程结束,守护线程自动结束

线程生命周期

  • Thead.State
    • NEW: 尚未启动的线程处于此状态
    • RUNNABLE: 在 java 虚拟机中执行的线程处于此状态
      • READY
      • RUNNING
    • BLOCKED: 被阻塞等待监视器锁定的线程处于此状态
    • WAITING: 正在等待另一个线程执行特定动作的线程处于此状态
    • TIMED_WAITING: 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
    • TERMINATED: 已退出的线程处于此状态

Synchronized

同步代码块

1
2
3
synchronized (对象) {   // 得到对象的锁才能操作同步代码
// 需要被同步的代码
}

同步方法

1
2
3
public synchronized void func() {   // 在同一时刻,只能有一个线程执行该方法
// 需要被同步的代码
}

互斥锁

  • 每个对象都对应一个可称为互斥锁的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  • 关键字 synchronized 来与对象的互斥锁联系,当某个对象被 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问
  • 同步方法(非静态的)的所可以是 this,也可以是其他对象(任意对象都可以)
  • 同步方法(静态的)的锁为当前类本身(默认锁对象: 当前类.class)

死锁

IO 流

文件

概念

  • 文件在程序中是以流的形式来操作的
  • 输入流: 内存 –> 文件
  • 输出流: 文件 –> 内存

常用操作

创建文件
  • new File(String pathname) // 根据路径构建一个 File 对象
  • new File(File parent, String child) // 根据父目录文件 + 子路径构建
  • new File(String parent, String child) // 根据父目录 + 子路径构建
  • createNewFile: 创建新文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import java.io.File;

    public class Hello{
    public static void main(String [] args) {
    File f = new File("test.txt");
    try {
    f.createNewFile();
    }
    catch (Exception e) {
    System.out.println(e);
    }
    }
    }
获取文件信息
  • getName
  • getAbsolutePath
  • getParent
  • length
  • exits
  • isFile
  • isDirectory
目录操作和文件删除
  • mkdir: 创建一级目录
  • mkdirs: 创建多级目录
  • delete: 删除空目录或文件j

IO 流原理及流的分类

  • 按操作数据单位不同: 字节流(InputStream, OutpuStream)、字符流(Reader, Writer)
  • 按数据流的流向不同: 输入流、输出流
  • 按流的角色不同: 节点流、处理流 / 包装流

节点流和处理流

输入流

InputStream()

FileInputStream
BufferedInputStream
ObjectInputStream

Reader

FileReader
BufferedReader
InputStreamReader

输出流

OutputStream

FileOutputReader
BufferedOutputReader
ObjectOutputReader

Writer

FileWriter
BufferedWriter
OutputStreamWriter

Properties 类

反射

  • 通过外部文件配置,在不修改源码的情况下来控制程序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import java.lang.reflect.Method;

    public class Hello{
    public static void main(String [] args) {
    String className = "Cat";
    String methodName = "hi";
    try {
    Class<?> cls = Class.forName(className);
    Object obj = cls.getDeclaredConstructor().newInstance();
    Method method = cls.getMethod(methodName);
    method.invoke(obj);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    class Cat {
    public void hi() {
    System.out.println("Meow");
    }
    }

反射机制

  • 反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作对象的属性及方法
  • 加载完类之后,在堆中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。
  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时得到任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

反射相关类

  • java.lang.Class: 代表一个类,Class 对象表示某个类加载后在堆中的对象
  • java.lang.reflect.Method: 代表类的方法
  • java.lang.reflect.Field: 代表类的成员变量
  • java.lang.reflect.Constructor: 代表类的构造方法

Class类

  • Class 也是类,因此也继承 Object 类
  • Class 类对象不是 new 出来的,而是系统创建的
  • 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过 Class 可以完整地得到一个类的完整结构,通过一系列 API
  • Class 对象是存放在堆中的
  • 类的字节码二进制数据是放在方法区的,有的地方称为类的元数据

反射获取类的结构信息

Class

Field

Method

Constructor

访问属性

访问方法

哪些类有 Class 对象

  • 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  • interface
  • 数组
  • enum
  • annotation(注解)
  • 基本数据类型
  • void

类加载

  • 静态加载: 编译时加载相关的类,如果没有则报错,依赖性太强
  • 动态加载: 运行时加载需要的类,如果运行时不用该类则不报错,降低了依赖性

类加载时机

  • 当创建类时 new
  • 当子类被加载时
  • 调用类中的静态成员
  • 反射(动态加载)

类加载流程(线程安全)

  1. java 源码
  2. 字节码文件
  3. 加载: 将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,此过程由类加载器完成
  4. 连接: 将类的二进制数据合并到 JRE 中
    • 验证: 对文件的安全进行校验
    • 准备: 对静态变量进行默认初始化(0, null, false…)并分配内存
    • 解析: 把符号引用转成直接引用
  5. 初始化: JVM 负责对类进行初始化,这里主要指静态成员

类加载后内存布局

  • 方法区: 类的字节码二进制
  • 堆区: 类的 Class 对象

反射调用性能优化

MySQL

安装

1
2
3
4
5
6
7
8
9
sudo pacman -S mysql
sudo mysqld --initialize --user=mysql --basedir=/usr --datadir=/var/lib/mysql
sudo systemctl start mysqld.service
sudo systemctl enable mysqld.service
sudo systemctl status mysqld.service
mysql -uroot -p
alter user 'root'@'localhost' identified by '123456';
quit
mysql -h 主机地址 -P 端口 -u 用户名 -p密码

数据库

创建

1
2
3
CREATE DATABASE test;   # 默认 utf8 和 utf8_general_ci
CREATE DATABASE test CHARACTER SET utf8; # 指定字符集 utf8
CREATE DATABASE test CHARACTER SET utf8 collate utf8_bin; # 指定字符集,校对规则(utf8_bin 区分大小写, utf8_general_ci 不区分大小写)

查看、删除数据库

1
2
3
SHOW DATABASES; # 显示数据库语句
SHOW DATABASE db_name; # 显示数据库创建语句
DROP DATABASE db_test; # 数据库删除语句

备份恢复数据库

备份数据库
1
2
3
4
5
# 在 shell 中执行
# 备份数据库
mysqldump -u 用户名 -p密码 -B 数据库1 数据库2 ... 数据库n > 文件名.sql
# 备份数据库的表
mysqldump -u 用户名 -p密码 数据库 表1 表2 ... 表n > 文件名.sql
恢复数据库
1
2
# 在 mysql 命令行执行
source 文件名.sql

创建

1
2
3
4
create table table_name (
id INT,
name VARCHAR(255)
)

删除

修改

MySQL 数据类型

数值类型

  • 整型
    • tinyint
    • smallint
    • mediumint
    • int
    • bigint
  • 小数类型
    • float
    • double
    • decimal

文本类型(字符串类型)

  • char
  • varchar
  • text
  • longtext

二进制数据类型

  • blob
  • longblob

日期类型

  • data(年月日)
  • time(时分秒)
  • datatime(年月日时分秒)
  • timestamp(时间戳)
  • year

CRUD

Insert

Update

Delete

Select

单表
多表

函数

统计函数

时间日期

字符串函数

数学函数

流程控制

内连接

外连接

约束

not null

primary key

unique

foreign key

check

自增长

索引

事务