java面向对象

72 阅读36分钟

java面向对象

1、面向对象概述

面向对象程序设计(object-oriented programming,OOP)是当今主流的设计泛型。面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。面向对象是相对面向过程而言,面向对象和面向过程都是一种思想,面向过程强调的是功能行为,面向对象则是将功能封装进对象,强调具备功能的对象,面向对象是基于面向过程的。面向对象的三大特征:封装、继承、多态

2、类与对象

2.1、简单理解

类是对事物的一种描述,是构造对象的模板。对象是事物的具体实现。

2.2、类

2.2.1、类定义格式
[public] class 类名 {
    // 成员变量(可多个)
    [权限修饰符] {基本数据类型|引用数据类型} 变量名;
​
    // 成员常量(可多个)
    [权限修饰符] [static] final {基本数据类型|引用数据类型} 变量名;
​
    // 成员方法(可多个)
    [权限修饰符] 返回值类型 方法名([参数类型 参数名,...]) {
        ...
        [return 返回值;]
    }
​
    // 构造方法(无参,只一个,有参构造不存在时默认会提供,有参构造存在时必须自己写了才能调用)
    public 类名() {
        ...
    }
​
    // 构造方法(有参,可多个)
    public 类名([参数类型 参数名,...]) {
        ...
    }
}
2.2.2、类加载的顺序

顺序:

  1. 父类静态成员初始化: 父类的静态变量(static字段)和静态代码块(static {})按声明顺序执行,且仅执行一次(类首次加载时)。
  2. 子类静态成员初始化: 子类的静态变量和静态代码块按声明顺序执行,同样仅执行一次。
  3. 父类实例成员初始化: 父类的实例变量(非静态字段)和实例代码块({})按声明顺序执行。
  4. 父类构造方法执行: 父类的构造方法被调用(默认调用无参构造,可通过super()指定参数)。
  5. 子类实例成员初始化: 子类的实例变量和实例代码块按声明顺序执行。
  6. 子类构造方法执行: 子类的构造方法被调用。

执行结果分析:

Grandparent: 静态代码块       // 父类静态成员(首次加载类时执行)
Parent: 静态变量初始化        // 子类静态成员(按声明顺序执行)
Child: 静态代码块            // 子类静态成员(按声明顺序执行)
Grandparent: 实例代码块      // 父类实例成员
Grandparent: 构造方法        // 父类构造方法
Parent: 实例代码块           // 子类实例成员(按声明顺序执行)
Parent: 实例变量初始化       // 子类实例成员(按声明顺序执行)
Parent: 构造方法             // 子类构造方法
Child: 实例代码块            // 子类实例成员
Child: 构造方法              // 子类构造方法
​
--- 再次创建对象 ---
Grandparent: 实例代码块      // 静态成员不再执行,仅初始化实例部分
Grandparent: 构造方法
Parent: 实例代码块
Parent: 实例变量初始化
Parent: 构造方法
Child: 实例代码块
Child: 构造方法

总结:

  1. 静态成员仅初始化一次: 静态变量和静态代码块在类首次加载时执行,且只执行一次。
  2. 实例成员每次创建对象时初始化: 每次new对象时,实例变量和实例代码块都会按声明顺序执行。
  3. 父类优先于子类: 无论是静态成员还是实例成员,父类的初始化总是先于子类。
  4. 构造方法依赖于实例初始化: 构造方法执行前,实例变量和实例代码块必须先完成初始化。
  5. 静态初始化顺序: 静态变量和静态代码块按在类中声明的顺序执行(即使变量在代码块后声明,也会按顺序初始化)。

2.3、对象

2.3.1、对象的创建
类名 对象名 = new 类名();
2.3.2、成员调用
对象名.成员变量
对象名.成员方法();
2.3.3、对象的判断(==、equals、instanceof)
  • ==比较引用地址

    • 基本类型:比较值是否相等。
    • 引用类型:比较两个引用是否指向同一个对象实例(内存地址是否相同)。
  • equals()方法:比较内容是否相等

    • 默认行为Object 类的 equals() 方法等同于 ==,比较引用地址。
    • 重写规则:通常需要重写 equals() 方法来比较对象的内容(如 String、Integer 等类已重写)。必须同时重写 hashCode()
  • instanceof运算符:判断对象类型

    • 作用:检查对象是否是某个类或接口的实例(包括子类或实现类)。
    • 语法boolean result = 对象 instanceof (类/抽象类/接口);
  • instanceof新特性:JDK14的时候提出了新特性,把判断和强转合并成了一行

    //新特性
    //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
    //如果不是,则不强转,结果直接是false
    if(a instanceof Dog d){
        d.lookHome();
    }else if(a instanceof Cat c){
        c.catchMouse();
    }else{
        System.out.println("没有这个类型,无法转换");
    }
    
2.3.4、对象的销毁
2.3.4.1、垃圾回收机制

Java的垃圾回收机制是自动进行的,它会在合适的时机自动销毁不再使用的对象。当一个对象不再被引用时,垃圾回收器会将其标记为垃圾对象,并在释放的时候回收其占用的内存空间。

我们可以通过调用System.gc()方法来建议垃圾回收器执行回收操作。但需要注意的是,调用之后并不一定会立即触发垃圾回收,这只是一个建议

2.3.4.2、手动置空

除了依靠垃圾回收机制来销毁对象外,我们还可以手动将对象设置为null来销毁它。当一个对象不再被引用时,我们可以将其引用设置为null,这样垃圾回收器就会将其标记为垃圾对象并在适当的时候回收。

2.3.4.3、使用finalize()方法(不要用)

在Java中,每个对象都有一个finalize()方法,它是Object类的一个方法。finalize()方法在垃圾回收器准备回收对象前被调用,我们可以重写该方法来在对象销毁之前执行一些清理操作。需要注意的是,因为finalize()方法的调用时机不确定,无法确保何时被调用,而且在某些情况下甚至不调用。因此,我们避免使用finalize()方法来进行对象销毁操作。

2.4、成员变量和局部变量的区别

对比维度成员变量(实例变量 / 静态变量)局部变量(方法内 / 代码块内变量)
定义位置类中,方法、构造方法或代码块外部方法、构造方法、代码块或参数列表中
生命周期- 实例变量:随对象创建而存在,随对象销毁而消失- 方法调用时创建,方法结束或代码块执行完毕后销毁
- 静态变量:类加载时创建,类卸载时销毁
内存位置- 实例变量:堆内存(对象所在区域)栈内存(方法栈帧)
- 静态变量:方法区(JDK 7 及以前)或堆内存(JDK 8+)
默认值有默认值(如int为 0,对象null无默认值,必须显式初始化后才能使用
访问修饰符可使用publicprivateprotected等修饰符不能使用访问修饰符
静态修饰符可声明为static(静态变量)或非static(实例变量)不能声明为static
作用域整个类内可见(需根据访问修饰符确定外部可见性)仅限于声明它的方法、构造方法或代码块内

3、封装

3.0、概述

  • 概述:是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
  • 原则:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
  • 好处:通过方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性

3.1、权限修饰符

修饰符同一个类中同一个包中子类、无关类不同包的子类不同包的无关类
private
默认
protected
public

3.2、this关键字

  • 概述:this代表它所在函数所属对象的引用,哪个对象在调用this所在的函数,this就代表哪个对象
  • 应用:当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象

3.3、final关键字

  • final关键字是最终的意思,可以修饰成员方法、成员变量、类、局部变量
  • final 关键字修饰基本数据类型时,其数值不能发生改变。
  • final 关键字修饰引用数据类型时,其地址不能发生改变。
  • final 关键字修饰成员方法时,表示该方法不能被重写。
  • final 关键字修饰 class 类时,表示该类不能被继承。

3.4、package关键字

  • 概述:包就是文件夹,用来管理类文件的

  • 格式:package 包名; (多级包用.分开)

    带包编译:javac –d . 类名.java
    例如:javac -d . com.wxx.demo.HelloWorld.java
    ​
    带包运行:java 包名+类名
    例如:java com.wxx.demo.HelloWorld
    

3.5、import关键字

  • 概述:使用不同包下的类时,使用的时候要写类的全路径,写起来太麻烦了,为了简化带包的操作,Java就提供了导包的功能
  • 普通格式:import 包名;
  • 静态导入:import static 包名.类名.方法名;

3.6、static关键字

由 static 关键字修饰的成员变量、成员常量、成员方法和内部类被称为静态成员变量、静态成员常量、静态成员方法和静态内部类。

  • 修饰成员变量时,将其变为类的静态成员变量,从而实现所有对象对于该变量的共享,可直接使用"类名.变量名"的方式调用。
  • 修饰成员常量时,将其变为类的静态成员常量,从而实现所有对象对于该常量的共享,可直接使用"类名.常量名"的方式调用。
  • 修饰成员方法时,将其变为类的静态成员方法,从而实现所有对象对于该方法的共享,可直接使用"类名.方法名"的方式调用。
  • 修饰内部类时,将其变为类的静态内部类,从而实现所有对象对于该内部类的共享,可直接使用"类名.内部类名"的方式调用。
  • 静态导包用法时,将指定类的方法直接导入到当前类中,从而直接使用"方法名"即可调用指定类的指定方法。
  • 静态代码块用法时,随着类的加载而执行,而且只执行一次,常用于初始化成员变量。

3.7、非静态的成员方法 和 静态成员方法的区别

  • 非静态的成员方法

    • 能访问静态的成员变量
    • 能访问非静态的成员变量
    • 能访问静态的成员方法
    • 能访问非静态的成员方法
  • 静态的成员方法

    • 能访问静态的成员变量
    • 能访问静态的成员方法
  • 注意:静态成员方法只能访问静态成员

3.8、构造方法

  • 概述:构造方法是一种特殊的方法,可以使用带参构造,为成员变量进行初始化

  • 格式:

    public class 类名{
        // [参数]可有可无 
        修饰符 类名([参数]) {
            
        }
    }
    
  • 注意:

    1. 如果没有定义构造方法,系统将给出一个默认的无参数构造方法,如果定义了构造方法,系统将不再提供默认的构造方法
    2. 如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

3.9、封装案例

class Student {
    // 成员变量
    private String name;
    private int age;
​
    // 无参构造方法
    public Student() {}
​
    // 有参构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    // 成员方法
    public void setName(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public int getAge() {
        return age;
    }
}

4、继承

4.0、概述

  • 概述:继承可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法,类只能单继承
  • 格式:class 子类 extends 父类 { }
  • 好处:继承可以让类与类之间产生关系,当子父类关系产生后,子类则可以使用父类中非私有的成员

4.1、super关键字

  • 概述:代表父类存储空间的标识(可以理解为父类对象引用)
  • 应用:子类要访问父类中的同名成员(成员变量和成员方法),用super关键字

4.2、this和super区别

thissuper的概述:
        this:代表本类对象的引用
        super:代表父类对象的引用
    
thissuper的使用:
成员变量:
    this.成员变量 - 访问本类成员变量
    super.成员变量 - 访问父类成员变量
    
成员方法:
    this.成员方法 - 访问本类成员方法
    super.成员方法 - 访问父类成员方法
    
构造方法:
    this(…) - 访问本类构造方法
    super(…) - 访问父类构造方法

4.3、继承中的成员访问特点

4.3.1、继承中变量使用的访问特点
  • 子类局部范围找
  • 子类成员范围找
  • 父类成员范围找
  • 如果都没有就报错(不考虑父亲的父亲…)
4.3.2、继承中成员方法的访问特点
  • 子类成员范围找
  • 父类成员范围找
  • 如果都没有就报错(不考虑父亲的父亲…)
4.3.3、继承中构造方法的访问特点
  • 注意:子类中所有的构造方法默认都会访问父类中无参的构造方法,每一个子类构造方法的第一条语句默认都是:super()
  • 问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
  • 答案:通过使用super关键字去显示的调用父类的带参构造方法

4.4、方法重写

  • 概述:子类出现了和父类中一模一样的方法声明(方法名一样,返回值一样,参数列表也一样)

  • 应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容

  • 注解:Override注解用来检测当前的方法,是否是重写的方法,起到【校验】的作用

  • 注意:

    1. 私有方法不能被重写(父类私有成员子类是不能继承的)
    2. 子类方法访问权限不能更低(public > 默认 > 私有)

4.5、方法重写与方法重载的区别

  • 方法重写:子类对父类允许访问的方法的实现过程进行重新编写就是方法重写。英文名:Override
  • 方法重载:在一个类里面,方法名字相同而参数列表不同的方法就是方法重载。英文名:Overload
区别方法重写方法重载
参数名不能修改不能修改
参数列表不能修改必须修改
返回类型不能修改可以修改
异常可以减少或删除但不能抛出新的可以修改
访问权限不能做更严格的限制可以修改

4.6、继承的注意事项

  • 类只支持单继承,不支持多继承
  • 类支持多层继承(父类可以有子类,子类还可以有它的子类)

5、多态

  • 概述:同一个对象,在不同时刻表现出来的不同形态
  • 前提:要有继承或实现关系、要有方法的重写、要有父类引用指向子类对象
  • 好处:提高程序的扩展性,定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作

5.1、方法多态

  • 方法重载:同一个方法名称可以根据参数的类型或个数不同调用不同的方法体
  • 方法重写:同一个父类的方法可以根据实例化子类的不同也有不同的实现

5.2、多态中的转型

  • 向上转型:父类引用指向子类对象就是向上转型
  • 向下转型:子类型 对象名 = (子类型)父类引用;

6、抽象类

6.1、概述

在做主类共性功能抽取时,有些方法在父类中并没有具体的实现,这个时候就需要抽象类。

6.2、格式

[public] abstract 抽象类名 {
    [权限修饰符] abstract [返回类型] 抽象方法名(参数列表);
}

6.3、语法特点

  • 抽象类和抽象方法必须使用 abstract 关键字修饰
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 抽象类不能实例化,要想实例化,参照多态的方式,通过子类对象实例化,这叫抽象类多态
  • 抽象类的子类,要么重写抽象类中的所有抽象方法,要么子类也是抽象类

6.4、内部特点

  • 成员变量:既可以成员变量,也可以成员常量

  • 成员方法:既可以普通方法,也可以抽象方法

  • 构造方法:既可以无参构造,也可以有参构造

  • 注意事项:与abstract不能共存的关键字

    • final:被final修饰的类不能有子类,而被abstract修饰的类一定是一个父类
    • private:抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写
    • static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义

7、接口

7.1、概述

接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用

7.2、格式

public interface 接口名 {}

7.3、语法特点

  • 接口用关键字interface修饰
  • 类实现接口用implements表示,可以多实现
  • 接口不能实例化,要想实例化,参照多态的方式,通过子类对象实例化,这叫接口多态。
  • 接口的子类,要么重写接口中的所有抽象方法,要么子类也是接口。
  • 抽象类不能继承接口,必须去实现接口。
  • 接口与接口直接可以多继承

7.4、内部特点

  • 成员变量:只能是常量,默认修饰符:public static final

  • 构造方法:没有,因为接口主要是扩展功能的,而没有具体存在

  • 成员方法:只能是抽象方法,默认修饰符:public abstract

  • 默认方法:public default 返回值类型 方法名(参数列表) { } jdk1.8新增

  • 静态方法:public static 返回值类型 方法名(参数列表) { }jdk1.8新增

  • 私有方法:Java 8允许在接口中定义带方法体的 默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9新增私有方法的必然性

    • private 返回值类型 方法名(参数列表) { }
    • private static 返回值类型 方法名(参数列表) { }

7.5、默认方法冲突

如果先在一个接口中将一个方法定义为默认方法,然后又在超类(父类)或者另一个接口中定义了同样的方法,那么同时实现这两个接口的类或既继承了超类又实现了接口的类就会发生冲突。但Java提供相应的规则:

  • 超类优先。如果超类提供了一个具体方法,同名且有相同参数类型的默认方法会被忽略。
  • 接口冲突。如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,子类必须覆盖这个方法来解决冲突。

8、内部类

8.1、概念

内部类就是一个定义在一个类里面的类,里面的类可以理解为(寄生),外部类可以理解成(宿主)。共分为4类。内部类可以访问外部类的成员,包括私有的。

8.2、成员内部类

  • 定义:在成员变量位置定义的内部类。

  • 格式:

    [public] class Outer {
        [public] class Inner { }
    }
    
  • 创建对象:

    //格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
    Outer.Inner in = new Outer().new Inner();
    //同一属性名定义在外部类、内部类、内部类方法的访问
    //方法内定义访问:属性名
    //内部类定义访问:this.属性名
    //外部类定义访问:Outer.this.属性名
    

    image-20230717230334489

8.3、局部内部类

  • 定义:在成员方法、代码块位置定义的内部类
  • 外界是无法直接使用,需要在方法内部创建对象并使用
  • 局部内部类可以直接访问外部类的成员,也可以访问方法内的局部变量

8.4、匿名内部类

  • 定义:本质上是一个没有名字的局部内部类,定义在方法中、代码块中。

  • 本质上是一个继承了该类或者实现了该接口的子类匿名对象

  • 格式:

    new 类|抽象类名|或者接口名(){
        自定义或重写方法;
    }
    

8.5、静态内部类

  • 定义:用static修饰的成员内部类

  • 格式:

    public class Outer{
        //静态内部类
        public static class Inner{
        }
    }
    
  • 创建对象

    //格式:外部类名.内部类名 对象名 = new 外部类.内部类构造器;
     Outer.Inner in = new Outer.Inner();
    
  • 静态内部类加载是在程序调用静态内部类的时候加载的

9、枚举类

[public] enum 枚举名 { 枚举项1,枚举项2,枚举项3,... }
  • 成员变量:既可以成员变量,也可以成员常量
  • 成员方法:既可以普通方法,也可以抽象方法,但是枚举项必须实现抽象方法
  • 既可以无参构造,也可以有参构造,但是修饰符必须都是 private,默认不写也是private
  • 注意事项:枚举类第一行必须是枚举项,多个枚举项之间使用逗号隔开,最后一个枚举项使用分号结束,如果最后一个枚举项后没有其他东西,最后一个分号可以省略,所有枚举类都是Enum的子类,有参构造函数的枚举类的枚举项的用法比较特殊:枚举项("")

枚举类的常用方法:

方法描述示例
values()该方法可以将枚举类型成员以数组的形式返回枚举类型名称.values()
valueOf(String name)该方法可以实现将普通字符串转换为枚举实例枚举类型名称.valueOf()
compareTo()该方法用于比较两个枚举对象在定义时的顺序枚举对象.compareTo()
ordinal()该方法用于获取枚举成员的位置索引枚举对象.ordinal(

10、泛型

10.1、泛型类

[public] class 类名<T> { }

10.2、泛型方法

权限修饰符 <T> 返回值类型 方法名(T t) {
    return 返回值类型;
}

10.3、泛型接口

[public] interface 类名<T> { }

10.4、泛型通配符

类型通配符:<?>
    List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
    
类型通配符上限:<? extends 类型>
    List<? extends Number>:它表示的类型是Number或者其子类型
    
类型通配符下限:<? super 类型>
    List<? super Number>:它表示的类型是Number或者其父类型 

10.5、泛型局限性

  • 不能用八种基本类型实例化类型参数,而是用他们所对应的包装类
  • 无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型( raw type ),原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased ) 类型变量,并替换为限定类型,无限定的变量用Object替换
  • 运行时类型查询只适用于原始类型,试图查询一个对象是否属于某个泛型类型时,倘若使用instanceof会得到一个编译器错误, 如果使用强制类型转换会得到一个警告,正确的做法是调用getClass()方法进行比较原始类型是否一致
  • 不能创建参数化类型的数组,例如:Pair<String>[] table = new Pair<String>[10] ;这是错误的,可以声明通配类型的数组, 然后进行类型转换:Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
  • 不能实例化类型变量,不能使用像new T(...),new T[...] 或T.class这样的表达式
  • 不能在静态域或方法中引用类型变量
  • 不能抛出或捕获泛型类的实例

11、注解

11.1、概述

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、方法、变量、参数和包等都可以被注解。和 Javadoc 不同,Java 注解可以通过反射获取注解内容。在编译器生成类文件时,注解可以被嵌入到字节码中。Java 虚拟机可以保留注解内容,在运行时可以获取到注解内容 。 当然它也支持自定义 Java 注解。

11.2、定义格式

public @interface 注解名 {
    成员类型 value() default "默认值; 
}

11.3、元注解

  • @interface注解:用于定义一个注解

  • @Document注解:表示将此注解包含在 javadoc 中

  • @Inherited注解:表示允许子类继承父类中的注解

  • Retention注解:表示该注解的生命周期

    • RetentionPolicy.SOURCE:源码期间有效
    • RetentionPolicy.CLASS:编译期间有效
    • RetentionPolicy.RUNTIME:运行期间有效
  • @Target注解:表示该注解的标注位置

    • ElementType.ANNOTATION_TYPE:表示该注解可以应用在另一个注解上。
    • ElementType.CONSTRUCTOR:表示该注解可以应用在构造方法上。
    • ElementType.FIELD:表示该注解可以应用在字段或属性上。
    • ElementType.LOCAL_VARIABLE:表示该注解可以应用在局部变量上。
    • ElementType.METHOD:表示该注解可以应用在方法上。
    • ElementType.PACKAGE:表示该注解可以应用在包上。
    • ElementType.PARAMETER:表示该注解可以应用在参数上。
    • ElementType.TYPE:表示该注解可以应用在类、接口(包括注解类型)或枚举声明上。

11.4、参数成员

  • 参数成员只能用public或默认(default)这两个访问权修饰
  • 参数成员只能用八种基本数据类型(byte,short,int,long,float,double,char,boolean)和String、Enum、Class、Annotations等类型以及这些类型的一维数组
  • 参数成员可以加default设置默认值
  • 属性默认值的说明:当一个注解内有多个属性时,如果只有一个属性没有默认值,而且这个属性名为value,其他属性有默认值,那么我直接直接使用注解,里面双引号括起来的内容默认会赋值给value属性。当无默认值的属性名不为value或多个属性无默认值时,则不可以这么使用。

11.5、常见注解

  • @Deprecated 表示当前方法已经过时不建议使用
  • @Override 只能标注方法,表示该方法覆盖父类中的方法。
  • @SuppressWarnings 所标注内容产生的警告,编译器会对这些警告保持静默。
  • @FunctionalInterface 所标注接口代表是一个函数式接口。

12、类加载器

12.1、类加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化

12.2、类初始化过程

  • 类的加载

    • 就是指将class文件读入内存,并为之创建一个java.lang.Class对象
    • 任何类被使用时,系统都会为之建立一个java.lang.Class对象
  • 类的连接

    • 验证阶段:用于检验被加载的类是否有正确的内部结构并和其它类协调一致
    • 准备阶段:负责为类的类变量分配内存并设置默认初始化值
    • 解析阶段:将类的二进制数据中的符号引用替换为直接引用
  • 类的初始化:在该阶段,主要就是对类变量进行初始化

    • 假如类还未被加载和连接,则程序先加载并连接该类
    • 假如该类的直接父类还未被初始化,则先初始化其直接父类
    • 假如类中有初始化语句,则系统依次执行这些初始化语句
    • 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3

12.3、类初始化时机

  • 创建类的实例
  • 调用类的方法
  • 访问类、接口的变量或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类

12.4、类加载器

概述:负责将.class文件加载到内存中并为之生成对应的 java.lang.Class 对象

  • Bootstrap class loader:引导类加载器负责加载<JAVA_HOME>\jre\lib路径下的核心类库,由于安全考虑只加载 包名 java、javax、sun开头的类。打印为null,是因为Bootstrap是C++实现的。

  • Extension ClassLoader:Java语言实现,扩展类加载器负责加载<JAVA_HOME>\jre\lib\ext目录下的类库

  • System class loader:它是系统类加载器也被称为应用程序类加载器 (AppClassLoader),系统类加载器负责加载 classpath环境变量所指定的类库,是用户自定义类的默认类加载器,开发者可用通过 java.lang.ClassLoader.getSystemClassLoader()方法获得此类加载器的实例

  • 自定义类加载器:自定义类加载器是为了加载在jvm三个加载器负责的目录范围之外的类

    package com;
    ​
    import java.io.*;
    ​
    /**
     * @Description: 自定义类加载器
     */
    public class MyClassLoader extends ClassLoader {
    ​
        private String classPath;
    ​
        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }
    ​
        //parent: 指定父加载器, AppClassLoader/ExtClassLoader/Bootstrap
        public MyClassLoader(ClassLoader parent, String classPath) {
            super(parent);
            this.classPath = classPath;
        }
    ​
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            //要求返回的是你要加载的字节码文件的Class对象.
    ​
            //这里都是我们说了算的。
            //步骤:
            //1. 从本地或网络某处读一个输入流到内存中 .
            //2. 将流内容字节数组 封装成Class对象 (直接调ClassLoader的defineClass方法,JVM会帮我们按照.class文件格式创建好的。)
    ​
            //1.
            //处理得到完整路径
            String path = this.classPath + name.replace(".", File.separator) + ".class";
    ​
            //2.读取到内存
            try (FileInputStream fis = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = fis.read(buffer)) != -1) {
                    //用ByteArrayOutputStream暂存一下。
                    baos.write(buffer, 0, len);
                }
                byte[] allByte = baos.toByteArray();
                //将字节数组生成Class对象
                return super.defineClass(name, allByte, 0, allByte.length);
            } catch (IOException e) {
                throw new ClassNotFoundException(name + "加载失败");
            }
        }
    ​
        //测试下
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            //使用自己的类加载器,加载D:\ + com.ali.Hello
            MyClassLoader myClassLoader = new MyClassLoader("d:\");  //
            //加载 全限定名类
            Class<?> clazz = myClassLoader.loadClass("com.wxx.Hello");
    ​
            clazz.newInstance();
    ​
            System.out.println(clazz.getClassLoader()); //out: 使用的类加载器 MyClassLoader@481248
        }
    }
    

注:System 的父加载器为Extension ,Extension 的父加载器为Bootstrap ,三者并无继承关系。

12.5、双亲委派机制

双亲委派机制就是: 每个类加载器都很懒,加载类时都先让父加载器去尝试加载,父加载器加载不了时自己才去加载。顺序大致如下:

  • 首先使用AppClassLoader类加载器尝试加载,AppClassLoader加载器会先检查它的缓存,查看该类是否已经被加载,有则不加载,没有则向上交给ExtClassLoader加载器。
  • ExtClassLoader加载器同样会先检查它的缓存,查看该类是否已经被加载,有则不加载,没有则向上交给Bootstrap加载器。
  • Bootstrap加载器同样会先检查它的缓存,查看该类是否已经被加载。有则不加载,没有则尝试从它负责的目录中加载,
  • Bootstrap加载器加载失败(不在它负责的目录范围)则向下交给ExtClassLoader加载器。
  • ExtClassLoader加载器会从它负责的目录中尝试加载,加载失败则向下交给AppClassLoader加载器
  • AppClassLoader加载器从它负责的classpath尝试加载,加载完成。

双亲委派机制的好处:

  • 避免类的重复加载:当父加载器已经加载该类时,就没有必要子加载器再加载一遍,保证被加载类的唯一性。
  • 同时Java有沙箱安全机制:自定义类的包名以 java.开头被禁止, 防止核心API被篡改,判断逻辑在defineClass方法中。

打破双亲委派机制:

  • 双亲委派机制的实现其实就是在loadClass方法中实现的。

  • 直接调用findClass方法就可以跳过双亲委派机制,这样就可以直接加载,而不用向上委托了。

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            //使用上面自定义的类加载器。
            MyClassLoader myClassLoader1 = new MyClassLoader("d:\");
    ​
            //find方法调用,加载 全限定名类
            Class<?> clazz1 = myClassLoader1.findClass("com.ali.Hello");
            
            System.out.println(clazz1.hashCode()); //out: 26508395
    ​
            System.out.println(clazz1.getClassLoader()); //out: 使用的类加载器 MyClassLoader@481248
    }
    

12.6、ClassLoader抽象类

所有的类加载器(除了Bootstrap)都要继承ClassLoader抽象类。

主要方法:

方法名作用
public Class<?> loadClass(String name)双亲委派机制的实现
protected Class<?> findClass(String name)读取字节码文件到内存并调用defindClass方法生成Class对象
protected final Class<?> defineClass(String name, byte[] b, int off, int len)先判断是否加载过,然后将字节数组解析成Class对象
protected final void resolveClass(Class<?\ c)连接指定的类

12.7、ClassLoader 中的两个方法【应用】

  • 方法介绍

    方法名说明
    public static ClassLoader getSystemClassLoader()获取系统类加载器
    public InputStream getResourceAsStream(String name)加载某一个资源文件
  • 示例代码

    public class ClassLoaderDemo2 {
        public static void main(String[] args) throws IOException {
            //static ClassLoader getSystemClassLoader() 获取系统类加载器
            //InputStream getResourceAsStream(String name)  加载某一个资源文件
    ​
            //获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    ​
            //利用加载器去加载一个指定的文件
            //参数:文件的路径(放在src的根目录下,默认去那里加载)
            //返回值:字节流。
            InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
    ​
            Properties prop = new Properties();
            prop.load(is);
    ​
            System.out.println(prop);
    ​
            is.close();
        }
    }
    

13、反射

13.1、概述

反射是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制。 由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展

13.2、获取字节码对象(Class)

13.2.1、演示准备

Person类:

public class Person {
    public int id;
    private String name;
    int age;
​
    public Person() {
    }
​
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", age=" + age +
                '}';
    }
​
    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
    public void eat(){
        System.out.println("eat....");
    }
    public void eat(String food){
        System.out.println("eat.."+food);
    }
}

Account类:

public class Account {
    private int id;
    private String name;
    private double money;
​
    public Account() {
    }
​
    public Account(int id, String name, double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public double getMoney() {
        return money;
    }
​
    public void setMoney(double money) {
        this.money = money;
    }
}
13.2.2、获取Class对象方式
  • Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  • 类名.class:通过类名的属性class获取。多用于参数的传递
  • 对象.getClass():getClass()方法在Object类中定义着。多用于对象的获取字节码的方式。

方法演示

public class GetClass {
    public static void main(String[] args) throws Exception{
        Class c1 = Class.forName("com.wxx.pojo.Person");
        
        Class c2 = Person.class;
        
        Person person = new Person();
        Class c3 = person.getClass();
        
        System.out.println(c1==c2);//true
        System.out.println(c1==c3);//true
        //证明使用任何类的Class文件获取的类加载器都是同一个
        Account account = new Account();
        System.out.println(person.getClass().getClassLoader() == account.getClass().getClassLoader());//true
​
        System.out.println(c2==account.getClass());//false
    }
}

结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个

13.3、反射获取构造方法(Constructor)

13.3.1、获取Constructor对象方式
方法描述
public Constructor<?>[] getConstructors()返回一个包含Constructor对象的数组,不包含私有构造
public Constructor getConstructor(Class<?>… parameterTypes)返回一个指定的Constructor对象,不包含私有构造
public Constructor<?>[] getDeclaredConstructors()返回一个包含Constructor对象的数组,包含私有构造
public Constructor getConstructor(Class<?>… parameterTypes)返回一个指定的Constructor对象,包含私有构造

方法演示:

public class ReflectConstructor {
    public static void main(String[] args) throws Exception{
        Class personClass = Person.class;
        Constructor constructor1 = personClass.getConstructor(int.class,String.class,int.class);
        Object person = constructor1.newInstance(1,"zhangsan",18);
        System.out.println(person);
​
        Constructor constructor2 = personClass.getConstructor();
        System.out.println(constructor2);
        Object person2 = constructor2.newInstance();
        System.out.println(person2);
        
        //直接反射获取新的实例
        Object person3 = personClass.newInstance();
        System.out.println(person3);
    }
}
13.3.2、Constructor提供的方法
方法描述
public <T extends Annotation> T getAnnotation(类<T> annotationClass)获取指定类型的注解
public Annotation[] getDeclaredAnnotations()返回直接存在于此元素上的注释。 此方法忽略继承的注释。
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)指定的注解类型是否存在于这个构造器上
public Class<T> getDeclaringClass()获取该构造器所属的类的Class对象
public String getName()获取构造器名称(等于它所属的类名)
public Annotation[][] getParameterAnnotations()获取各个参数上的注解,按照声明顺序
public int getParameterCount ()获取参数数量,变长参数列表整体算一个
public Class[]<?> getParameterTypes ()获取各个参数的类对应的Class对象
public boolean isVarArgs ()是否具有变长参数列表
public Class[]<?> getExceptionTypes ()获取异常列表
public T newInstance(Object ... initargs)用于使用Constructor的实例创建相应的对象

13.4、反射获取成员变量(Field)

13.4.1、获取Field对象的方法
方法描述
public Field[] getFields()返回一个包含Field对象的数组,不包含私有变量
public Field getField(String name)返回一个指定的Field对象,不包含私有变量
public Field[] getDeclaredFields()返回一个包含Field对象的数组,包含私有变量
public Field getDeclaredField(String name)返回一个指定的Field对象,包含私有变量

方法演示:

public class ReflectField {
    public static void main(String[] args) throws Exception{
        Class personClass = Person.class;
        // Field[] getFields() :获取所有public修饰的成员变量
        Field[] fields = personClass.getFields();
        for (Field field : fields){
            System.out.println(field);//id
​
        }
        System.out.println("---------------------");
​
        //Field getField(String name)   获取指定名称的 public修饰的成员变量
        Field field1 = personClass.getField("id");
        System.out.println(field1);
        System.out.println("---------------------");
​
        //Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
        Field[] fields1 = personClass.getDeclaredFields();
        for (Field field : fields1){
            System.out.println(field);
        }
        System.out.println("---------------------");
​
        //Field getDeclaredField(String name)
        Field field2 = personClass.getDeclaredField("name");
        field2.setAccessible(true);//暴力反射,访问私有的成员
        System.out.println(field2.getName());
​
    }
}
13.4.2、Field提供的方法
方法描述
public Class<?> getType()返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型
public Type getGenericType()返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型
public boolean isEnumConstant()判断这个属性是否是枚举类
public int getModifiers()以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符
public String getName()获取属性的名字
public Object get(Object obj)返回指定对象obj上此 Field 表示的字段的值
public void set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值
public void setAccessible(boolean flag)获取权限
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null
public Annotation[] getDeclaredAnnotations()返回直接存在于此元素上的所有注释

13.5、获取反射的成员方法(Method)

13.5.1、获取Method对象的方式
方法描述
public Method[] getMethods()返回一个包含Method对象的数组,不包含私有成员方法
public Method getMethod(String name, Class<?>… parameterTypes)返回一个指定的Method对象,不包含私有成员方法
public Method[] getDeclaredMethods()返回一个包含Method对象的数组,包含私有成员方法
public Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回一个指定的Method对象,包含私有成员方法

方法演示:

public class ReflectMethod {
    public static void main(String[] args) throws Exception{
        Class personClass = Person.class;
        Method eat_method = personClass.getMethod("eat");
        Person person = new Person();
        eat_method.invoke(person);
​
        Method eat_method2 = personClass.getMethod("eat",String.class);
        eat_method2.invoke(person,"饭");
​
        //获取所有public修饰的方法
        Method[] methods =personClass.getMethods();
        for (Method method : methods){
            System.out.println(method);
            String name = method.getName();
            System.out.println(name);
        }
        //获取类名
        String classname = personClass.getName();
        System.out.println(classname);
    }
13.5.2、Method提供的方法
方法描述
public String getName()获得方法名
public int getModifiers()获得修饰符
public Class<?> getReturnType()获取返回值类型
public Class<?>[] getParameterTypes()获取参数类型的数组
public Object invoke(Object obj, Object... args)调用method类代表的方法,其中obj是对象名,args是传入method方法的参数
public <T extends Annotation> T getAnnotation(类<T> annotationClass)获取指定类型的注解
public Annotation[] getDeclaredAnnotations()返回直接存在于此元素上的注释。 此方法忽略继承的注释。

13.6、反射获取类上注解(Annotation)

方法描述
public Annotation[] getAnnotations()返回一个包含Annotation对象的数组 包括继承的注解,继承需标明@Inherited
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)返回一个指定的Annotation对象 包括继承的注解,继承需标明@Inherited
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass)返回一个指定的Annotation对象数组 包括继承的注解,继承需标明@Inherited
public Annotation[] getDeclaredAnnotations()返回一个包含Annotation对象的数组 不包括继承的注解
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)返回一个指定的Annotation对象 不包括继承的注解
public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass)返回一个指定的Annotation对象数组 不包括继承的注解

13.7、反射获取类上信息

方法描述
public Package getPackage()获取当前类的包对象
public String getName()获取当前类的类全名
public Class<?>[] getClasses()获取当前类的所有内部类 不包含私有,包含父类声明的
public Class<?>[] getDeclaredClasses()获取当前类的所有内部类 包含私有,不包含父类声明的
public native Class<? super T> getSuperclass();返回直接继承的父类,不包含泛型参数
public Type getGenericSuperclass()返回直接继承的父类,包含泛型参数
public Class<?>[] getInterfaces()返回直接实现的接口,不包含泛型参数
public Type[] getGenericInterfaces()返回直接实现的接口,包含泛型参数

13.8、反射判断数据类型

方法描述
public native boolean isPrimitive();判断是否是基本类型
public native boolean isArray();判断是否是数组类型
public native boolean isInterface();判断是否是接口类型
public boolean isEnum()判断是否是枚举类型
public boolean isAnnotation()判断是否是注解类型
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)判断是否有指定注解
public native boolean isAssignableFrom(Class<?> cls);判断是否为某类父类