2024年最新版零基础详细Java知识笔记【面向对象】③

首先,感谢杜老师的讲解,感谢动力节点,真的受益匪浅!特别适合零基础、备考软件设计师证书、就业等,非常适合打基础。

笔记根据老杜《动力节点》视频进行编写,视频地址:动力节点Java零基础视频(上)

笔记有写的不好的地方,恳请在评论区指正批评,谢谢!


第三章 面向对象

3.1 面向对象概述

3.1.1 软件开发方法

软件开发方法:面向过程和面向对象

  1. 面向过程:关注点在实现功能的步骤上。
    • PO:Procedure Oriented。代表语言:C语言
    • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
    • 例如开汽车:启动、踩离合、挂挡、松离合、踩油门、车走了。
    • 再例如装修房子:做水电、刷墙、贴地砖、做柜子和家具、入住。
    • 对于简单的流程是适合使用面向过程的方式进行的。复杂的流程不适合使用面向过程的开发方式。
  2. 面向对象:关注点在实现功能需要哪些对象的参与。
    • OO:Object Oriented 面向对象。包括OOA,OOD,OOP。OOA:Object Oriented Analysis 面向对象分析。OOD:Object Oriented Design 面向对象设计。OOP:Object Oriented Programming 面向对象编程。代表语言:Java、C#、Python等。
    • 人类是以面向对象的方式去认知世界的。所以采用面向对象的思想更加容易处理复杂的问题。
    • 面向对象就是分析出解决这个问题都需要哪些对象的参加,然后让对象与对象之间协作起来形成一个系统。
    • 例如开汽车:汽车对象、司机对象。司机对象有一个驾驶的行为。司机对象驾驶汽车对象。
    • 再例如装修房子:水电工对象,油漆工对象,瓦工对象,木工对象。每个对象都有自己的行为动作。最终完成装修。
    • 面向对象开发方式耦合度低,扩展能力强。例如采用面向过程生产一台电脑,不会分CPU、内存和硬盘,它会按照电脑的工作流程一次成型。采用面向对象生产一台电脑,CPU是一个对象,内存条是一个对象,硬盘是一个对象,如果觉得硬盘容量小,后期是很容易更换的,这就是扩展性。

在这里插入图片描述

面向对象三大特征

  1. 封装(Encapsulation)
  2. 继承(Inheritance)
  3. 多态(Polymorphism

3.1.2 类与对象

    • 现实世界中,事物与事物之间具有共同特征,例如:刘德华和梁朝伟都有姓名、身份证号、身高等状态,都有吃、跑、跳等行为。将这些共同的状态和行为提取出来,形成了一个模板,称为类。
    • 类实际上是人类大脑思考总结的一个模板,类是一个抽象的概念。
    • 状态在程序中对应属性。属性通常用变量来表示。
    • 行为在程序中对应方法。用方法来描述行为动作。
    • 类 = 属性 + 方法
  1. 对象
    • 实际存在的个体。
    • 对象又称为实例(instance)。
    • 通过类这个模板可以实例化n个对象。(通过类可以创造多个对象)
    • 例如通过“明星类”可以创造出“刘德华对象”和“梁朝伟对象”。
    • 明星类中有一个属性姓名:String name;
    • “刘德华对象”和“梁朝伟对象”由于是通过明星类造出来的,所以这两个都有name属性,但是值是不同的。因此这种属性被称为实例变量

在这里插入图片描述

3.2 对象的创建和使用

3.2.1 类的定义

  1. 语法格式
    [修饰符列表] class 类名 {
    // 属性(描述状态)
    // 方法(描述行为动作)
    }
    
  2. 例如:学生类
    public class Student {
    // 姓名
    String name; // 实例变量
    int age;// 年龄
    boolean gender; // 性别
    public void study(){ System.out.println(“正在学习”); } // 实例方法
    }
    
  3. 为什么要定义类
    因为要通过类实例化对象。有了对象,让对象和对象之间协作起来形成系统。
  4. 一个类可以实例化多个java对象。(通过一个类可以造出多个java对象。)
  5. 实例变量是一个对象一份,比如创建3个学生对象,每个学生对象中应该都有name变量。
  6. 实例变量属于成员变量,成员变量如果没有手动赋值,系统会赋默认值。

3.2.2 对象的创建和使用

  1. 对象的创建
    • Student s = new Student();在Java中,使用class定义的类,属于引用数据类型。所以Student属于引用数据类型。类型名为:Student。
    • Student s;表示定义一个变量。数据类型是Student,变量名是s。
  2. 对象的使用
    • 读取属性值:s.name
    • 修改属性值:s.name = "xfanny";
  3. 通过一个类可以实例化多个对象
    Student s1 = new Student();
    Student s2 = new Student();

根据3-2-1的学生类Student,继续编写一个新的文件StudentTest01.java

public class StudentTest01 {
    public static void main(String[] args) {
        // 局部变量
        int i = 10;
        // 通过学生类Student实例化学生对象(通过类创造对象)
        // Student s1; 是什么?s1是变量名。Student是一种数据类型名。属于引用数据类型。
        // s1也是局部变量。和i一样。
        // s1变量中保存的是:堆内存中Student对象的内存地址。
        // s1有一个特殊的称呼:引用
        // 什么是引用?引用的本质上是一个变量,这个变量中保存了java对象的内存地址。
        // 引用和对象要区分开。对象在JVM堆当中。引用是保存对象地址的变量。
        Student s1 = new Student();

        // 访问对象的属性(读变量的值)
        // 访问实例变量的语法:引用.变量名
        // 两种访问方式:第一种读取,第二种修改。
        // 读取:引用.变量名 s1.name; s1.age; s1.gender;
        // 修改:引用.变量名 = 值; s1.name = "jack"; s1.age = 20; s1.gender = true;
        System.out.println("姓名:" + s1.name); // null
        System.out.println("年龄:" + s1.age); // 0
        System.out.println("性别:" + (s1.gender ? "男" : "女"));

        // 修改对象的属性(修改变量的值,给变量重新赋值)
        s1.name = "张三";
        s1.age = 20;
        s1.gender = true;

        System.out.println("姓名:" + s1.name); // 张三
        System.out.println("年龄:" + s1.age); // 20
        System.out.println("性别:" + (s1.gender ? "男" : "女")); // 男

        // 再创建一个新对象
        Student s2 = new Student();

        // 访问对象的属性
        System.out.println("姓名=" + s2.name); // null
        System.out.println("年龄=" + s2.age); // 0
        System.out.println("性别=" + (s2.gender ? "男" : "女"));

        // 修改对象的属性
        s2.name = "李四";
        s2.age = 20;
        s2.gender = false;

        System.out.println("姓名=" + s2.name); // 李四
        System.out.println("年龄=" + s2.age); // 20
        System.out.println("性别=" + (s2.gender ? "男" : "女")); // 女
    }
}

3.2.3 对象的内存分析(对象与引用)

根据StudentTest01进行画图解析内存结构
在这里插入图片描述

  1. 元空间(Metaspace)中存储的是类的元信息,字节码等。元空间是Java8之后引入的。元空间可以看做是JVM规范中方法区的实现。方法区是:JVM规范中的叫法。元空间是:对方法区的实现。
    JVM是一套规范:各个厂家都可以根据这个规范去实现具体的Java虚拟机。
  2. 在ava8之前,方法区的具体实现叫做:永久代。在Java8之后,永久代被丢弃了。永久代是对方法区的实现。
  3. 方法区是规范:永久代是实现(<=java7)。元空间也是实现(>=java8)。
  4. 所有使用运算符new的对象,都存储在堆内存当中。
  5. 虚拟机栈VM Stack中存储:栈帧。当方法被调用的时候,会给该方法分配空间,在VM Stack中压栈,压入一个栈帧。这个栈帧就是该方法的活动空间。每个栈帧中有:局部变量表,操作数栈等。
  6. 局部变量存储在哪里?虚拟机栈的栈帧当中。
  7. 实例变量存储在JVM堆内存的java对象内部。
  8. 对象和引用的区别?
    对象:在堆内存当中,new出来的。
    引用:是一个保存了对象内存地址的变量。
    在Java中,程序员是不能直接操作堆内存的。
    需要借助引用来访问堆内存。语法格式:引用.
  9. JVM中有GC机制,垃圾回收机制,java是自动垃圾回收。自动垃圾回收主要针对的是堆区。

  1. new运算符会在JVM的堆内存中分配空间用来存储实例变量。new分配的空间就是Java对象。
  2. 在JVM中对象创建后会有对应的内存地址,将内存地址赋值给一个变量,这个变量被称为引用。
  3. Java中的GC主要针对的是JVM的堆内存。
  4. 空指针异常是如何发生的?
    • 引用一旦为null,表示引用不再指向对象了。但是通过引用访问name属性,编译可以通过。运行时会出现异常:空指针异常。NullPointerException这个是非常著名的异常。
    • 因为运行的时候会找真正的对象,如果对象不存在了,就会出现空指针异常。
  5. 方法调用时参数是如何传递的?将变量中保存的值复制一份传递过去。
  6. 初次认识this关键字:出现在实例方法中,代表当前对象。“this.”大部分情况下可以省略。this存储在实例方法栈帧的局部变量表的0号槽位上。

3.2.4 方法调用时参数传递问题

  1. 面试题:判断该程序的输出结果

    public class ArgsTest01 {
        public static void main(String[] args) {
            int i = 10;
            // 调用add方法的时候,将i传进去,实际上是怎么传的?将i变量中保存值10复制了一份,传给了add方法。
            add(i);
            System.out.println("main--->" + i); // 10
        }
        public static void add(int i){ // 方法的形参是局部变量。
            i++;
            System.out.println("add--->" + i); // 11
            }
        }
    
  2. 面试题:判断该程序的输出结果

    public class User {
        int age;
    }
    
    public class ArgsTest02 {
        public static void main(String[] args) {
            User u = new User();
            u.age = 10;
            // u是怎么传递过去的。实际上和i原理相同:都是将变量中保存的值传递过去。
            // 只不过这里的u变量中保存的值比较特殊,是一个对象的内存地址。
            add(u);
            System.out.println("main-->" + u.age); // 11
        }
        public static void add(User u) { // u是一个引用。
            u.age++;
            System.out.println("add-->" + u.age); // 11
        }
    }
    

在这里插入图片描述

3.3 封装

  1. 现实世界中封装:
    液晶电视也是一种封装好的电视设备,它将电视所需的各项零部件封装在一个整体的外壳中,提供给用户一个简单而便利的使用接口,让用户可以轻松地切换频道、调节音量、等。液晶电视内部包含了很多复杂的技术,如显示屏、LED背光模块、电路板、扬声器等等,而这些内部结构对于大多数普通用户来说是不可见的,用户只需要通过遥控器就可以完成电视的各种设置和操作,这就是封装的好处。液晶电视的封装不仅提高了用户的便利程度和使用效率,而且还起到了保护设备内部部件的作用,防止灰尘、脏物等干扰。同时,液晶电视外壳材料的选择也能起到防火、防潮、防电等效果,为用户的生活带来更安全的保障。
  2. 什么是封装?
    封装是一种将数据和方法加以包装,使之成为一个独立的实体,并且把它与外部对象隔离开来的机制。具体来说,封装是将一个对象的所有“状态(属性)”以及“行为(方法)”统一封装到一个类中,从而隐藏了对象内部的具体实现细节,向外界提供了有限的访问接口,以实现对对象的保护和隔离。
  3. 封装的好处?
    封装通过限制外部对对象内部的直接访问和修改,保证了数据的安全性,并提高了代码的可维护性和可复用性。
  4. 在代码上如何实现封装?
    属性私有化,对外提供gettersetter方法。
  • 详细例子:
    为了保证User类型对象的age属性的安全,我们需要使用封装机制。实现封装的步骤是什么?

    • 第一步:属性私有化。(什么是私有化?使用 private 进行修饰。)
      属性私有化的作用是:禁止外部程序对该属性进行随意的访问。
      所有被private修饰的,都是私有的,私有的只能在本类中访问。
    • 第二步:对外提供setter和getter方法。
      • 为了保证外部的程序仍然可以访问age属性,因此要对外提供公开的访问入口。
      • 访问一般包括两种:
        • 读:读取属性的值
        • 改:修改属性的值
      • 那么应该对外提供两个方法,一个负责读,一个负责修改。
      • 读方法的格式:getter
        • public int getAge(){}
      • 改方法的格式:setter
        • public void setAge(int age){}
    public class User {
        private int age;
        // 读取age属性的值
        // getter方法是绝对安全的。因为这个方法是读取属性的值,不会涉及修改操作。
        public int getAge(){
            //return this.age;
            return age;
        }
    
        // 修改age属性的值
        // setter方法当中就需要编写拦截过滤代码,来保证属性的安全。
        /*public void setAge(int num){
            //this.age = num;
            if(num < 0 || num > 100) {
                System.out.println("对不起,您的年龄值不合法!");
                return;
            }
            age = num; // 区别
        }*/
    
        // java有就近原则。
        public void setAge(int age){
            if(age < 0 || age > 100) {
                System.out.println("对不起,您的年龄值不合法!");
                return;
            }
            // this. 大部分情况下可以省略。
            // this. 什么时候不能省略?用来区分局部变量和实例变量的时候。
            this.age = age;
        }
    }
    
    public class UserTest {
        public static void main(String[] args) {
            User u = new User();
            // 读
            System.out.println("年龄:" + u.getAge());
            // 改
            u.setAge(-100);
            // 读
            System.out.println("年龄:" + u.getAge());
            u.setAge(50);
            System.out.println("年龄:" + u.getAge()); // 50
        }
    }
    

3.4 构造方法Constructor(构造器)

  1. 构造方法有什么作用?
    • 构造方法的执行分为两个阶段:对象的创建(通过调用构造方法可以完成对象的创建)和对象的初始化(给对象的所有属性赋值就是对象的初始化)。这两个阶段不能颠倒,也不可分割
    • 在Java中,当我们使用关键字new时,就会在内存中创建一个新的对象,虽然对象已经被创建出来了,但还没有被初始化。而初始化则是在执行构造方法体时进行的。
  2. 构造方法如何定义?
    • [修饰符列表] 构造方法名(形参){}
    • 注意:
      • 构造方法名必须和类名一致
      • 构造方法不需要提供返回值类型。
      • 如果提供了返回值类型,那么这个方法就不是构造方法了,就变成普通方法了。
  3. 构造方法如何调用?
    • 使用new运算符来调用
    • 语法:new 构造方法名(实参);
    • 注意:构造方法最终执行结束之后,会自动将创建的对象的内存地址返回。但构造方法体重不需要提供“return 值;”这样的语句。
  4. 关于无参数构造方法:如果一个类没有显示的定义任何构造方法,系统会默认提供一个无参数构造方法,也被称为缺省构造器。一旦显示的定义了构造方法,则缺省构造器将不存在。为了方便对象的创建,建议将缺省构造器显示的定义出来
  5. 构造方法支持重载机制。在java中,一个类中可以定义多个构造方法,而且这些构造方法自动构成了方法的重载(overload)。
  6. 构造方法中给属性赋值了,为什么还需要单独定义set方法给属性赋值呢?
    • 在构造方法中赋值是对象第一次创建时属性赋的值。set方法可以在后期的时候调用,来完成属性值的修改。
  7. 构造方法执行原理。对象的创建和初始化过程梳理:
    • new的时候在堆内存中开辟空间,给所有属性赋默认值,完成对象的创建。(这个过程是在构造方法体执行之前就完成了。)
    • 构造方法体开始执行,标志着开始进行对象初始化。
      • 执行构造代码块进行初始化
      • 执行构造方法体进行初始化
    • 构造方法执行结束,对象初始化完毕。
  8. 构造代码块
    • 语法格式:{}
    • 构造代码块什么时候执行,执行几次?
      • 每一次在new的时候,都会执行一次构造代码块。
      • 构造代码块是在构造方法执行之前执行的。
    • 作用:
      • 如果所有的构造方法在最开始的时候有相同的一部分代码,不妨将这个公共的代码提取到构造代码块当中,这样代码可以得到复用。(语法糖)

3.5 this关键字

  1. this是一个关键字。
  2. this出现在实例方法中,代表当前对象。语法是:this.
  3. this本质上是一个引用,该引用保存当前对象的内存地址。
  4. 通过“this.”可以访问实例变量,可以调用实例方法。
  5. this存储在:栈帧的局部变量表的第0个槽位上。
  6. this. 大部分情况下可以省略,用于区分局部变量和实例变量时不能省略。
  7. this不能出现在静态方法中。
    • 原因:this代表的是当前对象。static的方法中没有当前对象。所以static的方法中不能使用this。
  8. this(实参)”语法:
    • 只能出现在构造方法的第一行。
    • 通过当前构造方法去调用本类中其他的构造方法。
    • 作用是:代码复用。

例子:

public class Student {
    // 属性,实例变量,学生姓名
    String name;
    // 方法:学习的行为(实例方法)
    public void study(){
        // this 本质上是一个引用。
        // this 中保存的也是对象的内存地址。
        // this 中保存的是当前对象的内存地址。
        //System.out.println(this.name + "正在努力的学习!");
        // this. 是可以省略。默认访问的就是当前对象的name。
        System.out.println(name + "正在努力的学习!!!!!!!");
    }
}
public class StudentTest {
    public static void main(String[] args) {
        // 创建学生对象
        Student zhangsan = new Student();
        // zhangsan 是引用。通过“引用.”来访问实例变量。
        zhangsan.name = "张三";
        System.out.println("学生姓名:" + zhangsan.name);
        // 让张三去学习。
        zhangsan.study();
        // 创建一个新的学生对象
        Student lisi = new Student();
        lisi.name = "李四";
        System.out.println("学生的姓名:" + lisi.name);
        // 让李四去学习。
        lisi.study();
    }
}

图3-5-1 this关键字
8.this(实参)代码详细解析:

public class Date {
    private int year; // 年
    private int month; // 月
    private int day; // 日
    
    public Date() {
        /*
        this.year = 1970;
        this.month = 1;
        this.day = 1;
         */
        // 不要这么写,这样会导致再创建一个新的对象。
        //new Date(1970,1,1);

        // 不会创建新对象。只是通过一个构造方法去调用另一个构造方法。
        this(1970,1,1);
        System.out.println("日期创建成功!~");
    }
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
    // getter 和 setter 方法创建......
    
    // 打印日期
   public void display(){
        System.out.println(this.getYear() + "年" + this.getMonth() + "月" + this.getDay() + "日");
    }
}

测试:

public class DateTest {
    public static void main(String[] args) {
        // 创建默认日期对象
        Date d1 = new Date();
        d1.display();
        // 创建指定的日期
        Date d2 = new Date(2008, 8, 8);
        d2.display();
    }
}

3.6 static关键字

  1. static是一个关键字,翻译为:静态的。
  2. static修饰的变量叫做静态变量。当所有对象的某个属性的值是相同的,建议将该属性定义为静态变量,来节省内存的开销。
  3. 静态变量在类加载时初始化,存储在堆中。
  4. static修饰的方法叫做静态方法
  5. 所有静态变量和静态方法,统一使用“类名.”调用,不需要new对象。虽然可以使用“引用.”来调用,但实际运行时和对象无关,所以不建议这样写,因为这样写会给其他人造成疑惑。
  6. 使用“引用.”访问静态相关的,即使引用为null,也不会出现空指针异常。
  7. 静态方法中不能使用this关键字。因此无法直接访问实例变量和调用实例方法。
  8. 静态代码块在类加载时执行,一个类中可以编写多个静态代码块,遵循自上而下的顺序依次执行。
    • 静态代码块
      • 语法格式:static{}
      • 静态代码块在类加载时执行,并且只执行一次。
    • 使用情况:
      • 本质上,静态代码块就是为程序员预留的一个特殊的时间点:类加载时刻
      • 如果你需要再类加载时执行一段程序的话,这段代码就可以写到静态代码块当中。
      • 例如:有这样的一个需求,请在类加载时,记录日志,那么记录日志的代码就可以编写到静态代码块当中。
  9. 静态代码块代表了类加载时刻,如果你有代码需要在此时刻执行,可以将该代码放到静态代码块中。
  10. 什么情况下把成员变量定义为静态变量?
    • 当一个属性是对象级别的,这个属性通常定义为实例变量。(实例变量是一个对象一份。100个对象就应该有100个空间)
    • 当一个属性是类级别的(所有对象都有这个属性,并且这个属性的值是一样的),建议将其定义为静态变量,在内存空间上只有一份。节省内存开销。这种类级别的属性,不需要new对象,直接通过类名访问。
  11. 静态变量存储在哪里?静态变量在什么时候初始化?(什么时候开辟空间)
    • JDK8之后:静态变量存储在堆内存当中。
    • 类加载时初始化。

在这里插入图片描述

3.7 JVM体系结构

3.7.1 JVM规范

  1. JVM对应了一套规范(Java虚拟机规范),它可以有不同的实现
    1. JVM规范是一种抽象的概念,它可以有多种不同的实现。例如:
    2. HotSpot:HotSpot 由 Oracle 公司开发,是目前最常用的虚拟机实现,也是默认的 Java 虚拟机,默认包含在 Oracle JDK 和 OpenJDK 中
    3. JRockit:JRockit 也是由 Oracle 公司开发。它是一款针对生产环境优化的 JVM 实现,能够提供高性能和可伸缩性
    4. IBM JDK:IBM JDK 是 IBM 公司开发的 Java 环境,采用了与 HotSpot 不同的 J9 VM,能够提供更小的内存占用和更迅速的启动时间
    5. Azul Zing:Azul Zing 是针对生产环境优化的虚拟机实现,能够提供高性能和实时处理能力,适合于高负载的企业应用和实时分析等场景
    6. OpenJ9:OpenJ9 是由 IBM 开发的优化的 Java 虚拟机实现,支持高度轻量级、低时延的 GC、优化的 JIT 编译器和用于健康度测试的可观察性仪表板
  2. 下图是从oracle官网上截取的Java虚拟机规范中的一部分。(大家也可以找一下oracle官方文档)
  3. 我们主要研究运行时数据区。运行时数据区包括6部分:
    1. The pc Register(程序计数器)
    2. Java Virtual Machine Stacks(Java虚拟机栈)
    3. Heap(堆)
    4. Method Area(方法区)
    5. Run-Time Constant Pool(运行时常量池)
    6. Native Method Stacks(本地方法栈)

在这里插入图片描述

3.7.2 JVM规范中的运行时数据区

  1. The pc Register(程序计数器):是一块较小的内存空间,此计数器记录的是正在执行的虚拟机字节码指令的地址;
  2. Java Virtual Machine Stacks(Java虚拟机栈):Java虚拟机栈用于存储栈帧。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  3. Heap(堆):是Java虚拟机所管理的最大的一块内存。堆内存用于存放Java对象实例以及数组。堆是垃圾收集器收集垃圾的主要区域。
  4. Method Area(方法区):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  5. Run-Time Constant Pool(运行时常量池):是方法区的一部分,用于存放编译期生成的各种字面量与符号引用。
  6. Native Method Stacks(本地方法栈):在本地方法的执行过程中,会使用到本地方法栈。和 Java 虚拟机栈十分相似。

总结:这些运行时数据区虽然在功能上有所区别,但在整个 Java 虚拟机启动时都需要被创建,并且在虚拟机运行期间始终存在,直到虚拟机停止运行时被销毁。同时,不同的 JVM 实现对运行时数据区的分配和管理方式也可能不同,会对性能和功能产生影响。

JVM体系结构图(该图属于JVM规范,不是具体的实现)

图3-7-2 JVM规范

3.7.3 JVM规范的实现

JVM规范的实现:HotSpot(Oracle JDK/Open JDK内部使用的JVM就是HotSpot)
以下是JDK6的HotSpot

  • 年轻代:刚new出来的对象放在这里。
  • 老年代:经过垃圾回收之后仍然存活的对象。
  • 符号引用:类全名,字段全名,方法全名等。
  • 这个时期的永久代和堆是相邻的,使用连续的物理内存,但是内存空间是隔离的。
  • 永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

JDK6的HotSpot

以下是JDK7的HotSpot,这是一个过渡的版本,该版本相对于JDK6来说,变化如下:

  • 类的静态变量转移到堆中了
  • 字符串常量池转移到堆中了
  • 运行时常量池中的符号引用转移到本地内存了

JDK7的HotSpot

以下是JDK8及更高版本的HotSpot,相对于JDK7来说发生了如下变化:

  • 彻底删除永久代(为了避免OOM错误的发生)
  • 将方法区的实现转移到本地内存
  • 将符号引用重新放回运行时常量池

JDK8的HotSpot

3.8 单例模式

3.8.1 设计模式概述

  1. 什么是设计模式?
    设计模式(Design Pattern)是一套被广泛接受的、经过试验验证的、可反复使用的基于面向对象的软件设计经验总结,它是软件开发人员在软件设计中,对常见问题的解决方案的总结和抽象。设计模式是针对软件开发中常见问题和模式的通用解决方案
  2. 设计模式有哪些?
    • GoF设计模式:《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为四人组(Gang of Four)。
    • 架构设计模式(Architectural Pattern):主要用于软件系统的整体架构设计,包括多层架构、MVC架构、微服务架构、REST架构和大数据架构等。
    • 企业级设计模式(Enterprise Pattern):主要用于企业级应用程序设计,包括基于服务的架构(SOA)、企业集成模式(EIP)、业务流程建模(BPM)和企业规则引擎(BRE)等。
    • 领域驱动设计模式(Domain Driven Design Pattern):主要用于领域建模和开发,包括聚合、实体、值对象、领域事件和领域服务等。
    • 并发设计模式(Concurrency Pattern):主要用于处理并发性问题,包括互斥、线程池、管道、多线程算法和Actor模型等。
    • 数据访问模式(Data Access Pattern):主要用于处理数据访问层次结构,包括数据访问对象(DAO)、仓库模式和活动记录模式等。
  3. GoF设计模式的分类?
    • 创建型:主要解决对象的创建问题
    • 结构型:通过设计和构建对象之间的关系,以达到更好的重用性、扩展性和灵活性
    • 行为型:主要用于处理对象之间的算法和责任分配

3.8.2 单例模式(初级)

单例模式(GoF23种设计模式之一,最简单的设计模式:如何保证某种类型的对象只创建一个)
饿汉式:类加载时就创建对象。

  • 第一步:构造方法私有化
  • 第二步:对外提供一个静态方法,通过这个方法可以获取到Singleton对象
  • 第三步:提供一个静态变量,但是这个变量值为null
public class Singleton {
    private static Singleton instance = new Singleton(); // 在类加载的时候就创建实例
    private Singleton() {}  // 将构造方法设为私有化
    public static Singleton getInstance() {  // 提供一个公有的静态方法,以获取实例
        return instance;
    }
}

懒汉式:第一次调用get方法时才会创建对象。

  • 第一步:构造方法私有化
  • 第二步:对外提供一个静态方法,通过这个方法可以获取到Singleton对象
  • 第三步:提供一个静态变量,但是这个变量值为null
public class Singleton {
    private static Singleton instance; // 声明一个静态的、私有的该类类型的变量,用于存储该类的实例
    private Singleton() {} // 将构造方法设为私有化
    public static Singleton getInstance() { // 提供一个公有的静态方法,以获取实例
        if (instance == null) { // 第一次调用该方法时,才真正创建实例
            instance = new Singleton(); // 创建实例
        }
        return instance;
    }
}

3.9 继承

  1. 面向对象三大特征之一:继承
  2. 继承作用?
    • 基本作用:代码复用
    • 重要作用:有了继承,才有了方法覆盖和多态机制。
  3. 继承在java中如何实现?
    • [修饰符列表] class 类名 extends 父类名{}
    • extends翻译为扩展。表示子类继承父类后,子类是对父类的扩展。
  4. 继承相关的术语:当B类继承A类时
    • A类称为:父类、超类、基类、superclass
    • B类称为:子类、派生类、subclass
  5. Java只支持单继承,一个类只能直接继承一个类。
  6. Java不支持多继承,但支持多重继承(多层继承)。
  7. 子类继承父类后,除私有的不支持继承、构造方法不支持继承。其它的全部会继承。
  8. 一个类没有显示继承任何类时,默认继承java.lang.Object类。

图3-9 继承

3.10 方法覆盖

方法覆盖/override/方法重写/overwrite

  1. 什么情况下考虑使用方法覆盖?
    • 当从父类中继承过来的方法无法满足当前子类的业务需求时。
  2. 发生方法覆盖的条件?
    • 条件1:具有继承关系的父子类之间
    • 条件2:相同的返回值类型,相同的方法名,相同的形式参数列表
    • 访问权限不能变低,可以变高。
    • 抛出异常不能变多,可以变少。
    • 返回值类型可以是父类方法返回值类型的子类。
  3. 方法覆盖的小细节:
    • 当子类将父类方法覆盖之后,将来子类对象调用方法的时候,一定会执行重写之后的方法。
    • 在java语言中,有一个注解,这个注解可以在编译阶段检查这个方法是否重写了父类的方法。
      • @Override注解是JDK5引入,用来标注方法,被标注的方法必须是重写父类的方法,如果不是重写的方法,编译器会报错。
      • Override注解只在编译阶段有用,和运行期无关。
    • 如果返回值类型是引用数据类型,那么这个返回值类型可以是原类型的子类型。
    • 私有方法不能继承,所以不能覆盖。
    • 构造方法不能继承,所以不能覆盖。
    • 静态方法不存在方法覆盖,方法覆盖针对的是实例方法。
    • 方法覆盖说的实例方法,和实例变量无关。

3.11 多态

3.11.1 多态的基础语法

  1. 什么是向上转型和向下转型?
    • java允许具有继承关系的父子类型之间的类型转换。
    • 向上转型(upcasting):子–>父 (可以等同看作自动类型转换)
      • 子类型的对象可以赋值给一个父类型的引用。 (可以等同看作强制类型转换)
    • 向下转型(downcasting):父–>子
      • 父类型的引用可以转换为子类型的引用。但是需要加强制类型转换符。
    • 无论是向上转型还是向下转型,前提条件是:两种类型之间必须存在继承关系。这样编译器才能编译通过。

图3-11 多态

  1. 什么是多态?
    • 父类型引用指向子类对象。Animal a = new Cat(); a.move();
    • 程序分为编译阶段和运行阶段:
      • 编译阶段:编译器只知道a是Animal类型,因此去Animal类中找move()方法,找到之后,绑定成功,编译通过。这个过程通常被称为静态绑定
      • 运行阶段:运行时和JVM堆内存中的真实Java对象有关,所以运行时会自动调用真实对象的move()方法。这个过程通常被称为动态绑定
    • 多态指的是:多种形态,编译阶段一种形态,运行阶段另一种形态,因此叫做多态。
  2. 向下转型我们需要注意什么?
    1. 向下转型时,使用不当,容易发生类型转换异常:ClassCastException。
    2. 在向下转型时,一般建议使用instanceof运算符进行判断来避免ClassCastException的发生。
  3. instanceof运算符的使用
    1. 语法格式:(引用 instanceof 类型)
    2. 执行结果是true或者false
    3. 例如:(a instanceof Cat)
      • 如果结果是true:表示a引用指向的对象是Cat类型的。
      • 如果结果是false:表示a引用指向的对象不是Cat类型的。

3.11.2 软件开发七大原则

  1. 软件开发原则旨在引导软件行业的从业者在代码设计和开发过程中,遵循一些基本原则,以达到高质量、易维护、易扩展、安全性强等目标。软件开发原则与具体的编程语言无关的,属于软件设计方面的知识。
  2. 软件开发七大原则?
    • 开闭原则 (Open-Closed Principle,OCP):一个软件实体应该对扩展开放,对修改关闭。即在不修改原有代码的基础上,通过添加新的代码来扩展功能。(最基本的原则,其它原则都是为这个原则服务的。)
    • 单一职责原则:一个类只负责单一的职责,也就是一个类只有一个引起它变化的原因。
    • 里氏替换原则:子类对象可以替换其基类对象出现的任何地方,并且保证原有程序的正确性。
    • 接口隔离原则:客户端不应该依赖它不需要的接口。
    • 依赖倒置原则:高层模块不应该依赖底层模块,它们都应该依赖于抽象接口。换言之,面向接口编程。
    • 迪米特法则:一个对象应该对其它对象保持最少的了解。即一个类应该对自己需要耦合或调用的类知道得最少。
    • 合成复用原则:尽量使用对象组合和聚合,而不是继承来达到复用的目的。组合和聚合可以在获取外部对象的方法中被调用,是一种运行时关联,而继承则是一种编译时关联。

3.11.3 多态在开发中的作用

  1. 降低程序的耦合度,提高程序的扩展力。
  2. 尽量使用多态,面向抽象编程,不要面向具体编程。

3.12 super关键字

  1. super关键字和this关键字对比来学习。this代表的是当前对象。super代表的是当前对象中的父类型特征。
  2. super不能使用在静态上下文中。
  3. super.”大部分情况下是可以省略的。什么时候不能省略?
    • 当父类和子类中定义了相同的属性(实例变量)或者相同方法(实例方法)时,如果需要在子类中访问父类的属性或方法时,super.不能省略。
  4. this可以单独输出,super不能单独输出。
  5. super(实参); 通过子类的构造方法调用父类的构造方法,目的是为了完成父类型特征的初始化。
  6. 当一个构造方法第一行没有显示的调用“super(实参);”,也没有显示的调用“this(实参)”,系统会自动调用super()。因此一个类中的无参数构造方法建议显示的定义出来。
  7. super(实参); 这个语法只能出现在构造方法第一行
    • 引入这个语法的作用:
      • ①代码复用。
      • ②为了“模拟”现实世界中的要有儿子,得先有父亲。
      • ③通过子类构造方法调用父类构造方法是为了给继承过来的父类型特征初始化。
  8. 在Java语言中只要new对象,Object的无参数构造方法一定会执行。
/**
 * 父类
 */
public class Person {
    String name;
    int age;
    String email;
    String address;
    public Person() {
        super();
    }
    // getter和setter方法...
    // 实例方法
    public void doSome(){
        System.out.println("人类正在做一些事情!");
    }
}
/**
 * 子类
 */
public class Teacher extends Person{
    /**
     * 特有的属性:工资
     */
    double sal;
    String name;
    public Teacher() {
    }
    public Teacher(String name, int age, String email, String address, double sal) {
        super();
        //super.name = name;
        this.name = name;
        this.age = age;
        this.email = email;
        this.address = address;
        this.sal = sal;
    }
    public double getSal() {
        return sal;
    }
    public void setSal(double sal) {
        this.sal = sal;
    }

    public void display() {
        System.out.println("姓名:" + super.name);
        System.out.println("年龄:" + super.age);
        System.out.println("邮箱:" + super.email);
        System.out.println("住址:" + super.address);
        System.out.println("工资:" + this.sal);

        System.out.println("姓名:" + this.name);
        System.out.println("年龄:" + this.age);
        System.out.println("邮箱:" + this.email);
        System.out.println("住址:" + this.address);
        System.out.println("工资:" + this.sal);
    }

    // this和super都不能使用在静态上下文中。
    /*public static void test(){
        System.out.println(this);
        System.out.println(super.name);
    }*/

    @Override
    public void doSome() {
        // 重写的要求:要求在父类方法的执行基础之上额外再添加一些代码。
        System.out.println("do some开始执行了");
        // super. 什么时候不能省略?父中有,子中有相同的,但是想在子类中访问父的,必须添加 super.
        super.doSome();
        System.out.println("do some方法执行结束了");

        // this本身是一个引用。所以可以直接输出。
        System.out.println(this);
        // super本身不是一个引用。super只是代表了当前对象的父类型特征那部分。
        // super 不能够单独的输出。
        //System.out.println(super); // 编译报错。
    }
}
public class Test01 {
    public static void main(String[] args) {
        // 创建Teacher对象
        Teacher t = new Teacher("张三", 20, "zhangsan@123.com", "北京朝阳", 10000.0);
        t.display();
        t.doSome();
    }
}

图3-12 super关键字

3.13 final关键字

  1. final修饰的类不能被继承
  2. final修饰的方法不能被覆盖
  3. final修饰的变量,一旦赋值不能重新赋值
  4. final修饰的实例变量必须在对象初始化时手动赋值。(不允许采用系统默认值) 一般不存在这种情况。
  5. final修饰的实例变量一般和static联合使用:称为常量
    1. 怎么定义常量:public static final 数据类型 常量名 = 常量值;
    2. 常量名的命名规范:全部单词大写,每个单词采用_衔接。
  6. final修饰的引用,一旦指向某个对象后,不能再指向其它对象。但指向的对象内部的数据是可以修改的。

3.14 抽象类

  1. 什么时候考虑将类定义为抽象类?
    • 如果类中有些方法无法实现或者没有意义,可以将方法定义为抽象方法。类定义为抽象类。这样在抽象类中只提供公共代码,具体的实现强行交给子类去做。比如一个Person类有一个问候的方法greet(),但是不同国家的人问候的方式不同,因此greet()方法具体实现应该交给子类。再比如主人喂养宠物的例子中的宠物Pet,Pet中的eat()方法的方法体就是没有意义的。
  2. 抽象类如何定义?
    • abstract class 类名{}
  3. 抽象类有构造方法,但无法实例化。抽象类的构造方法是给子类使用的。
  4. 抽象方法如何定义?
    • abstract 方法返回值类型 方法名(形参);
    • public和abstract关键字的顺序没有要求
  5. 抽象类中不一定有抽象方法,但如果有抽象方法那么类要求必须是抽象类。
  6. 一个非抽象的类继承抽象类,要求必须将抽象方法进行实现/重写。
  7. abstract关键字不能和private,final,static关键字共存。

3.15 接口

3.15.1 接口的基础语法

  1. 接口(interface)在Java中表示一种规范或契约,它定义了一组抽象方法和常量,用来描述一些实现这个接口的类应该具有哪些行为和属性。接口和类一样,也是一种引用数据类型。
  2. 接口怎么定义?[修饰符列表] interface 接口名{}
  3. 抽象类是半抽象的,接口是完全抽象的。接口没有构造方法,也无法实例化。
  4. (JDK8之前的语法规则)接口中只能定义:常量+抽象方法。接口中的常量的static final可以省略。接口中的抽象方法的abstract可以省略。接口中所有的方法和变量都是public修饰的。
  5. 接口和接口之间可以多继承。
  6. 类和接口的关系我们叫做实现(这里的实现也可以等同看做继承)。使用implements关键字进行接口的实现。
  7. 一个非抽象的类实现接口必须将接口中所有的抽象方法全部实现。
  8. 一个类可以实现多个接口。语法是:class 类 implements 接口A,接口B{}
  9. Java8之后,接口中允许出现默认方法和静态方法(JDK8新特性)
    1. 引入默认方式是为了解决接口演变问题:接口可以定义抽象方法,但是不能实现这些方法。所有实现接口的类都必须实现这些抽象方法。这会导致接口升级的问题:当我们向接口添加或删除一个抽象方法时,这会破坏该接口的所有实现,并且所有该接口的用户都必须修改其代码才能适应更改。这就是所谓的"接口演变"问题。
    2. 引入的静态方法只能使用本接口名来访问,无法使用实现类的类名访问。
  10. JDK9之后允许接口中定义私有的实例方法(为默认方法服务的)和私有的静态方法(为静态方法服务的)。
  11. 所有的接口隐式的继承Object。因此接口也可以调用Object类的相关方法。
public Interface MyInterface{
    // JDK9之后允许定义私有的实例方法。(给默认方法服务的。)
    private void privateMethod(){
        System.out.println("privateMethod执行了。");
    }
    // 默认方法
    default void defaultMethod(){
        System.out.println("接口中的默认方法defaultMethod执行了。");
        privateMethod();
    }
    // JDK9之后允许定义私有的静态方法。(给静态方法服务的)
    private static void privateStaticMethod(){
        System.out.println("privateStaticMethod执行了");
    }
    // 静态方法
    static void staticMethod(){
        System.out.println("接口的静态方法执行了");
        privateStaticMethod();
    }   
}
public class MyInterfaceTest {
    public static void main(String[] args) {
    // 使用了接口之后,为了降低程序的耦合度,一定要让接口和多态联合起来使用。
    // 父类型的引用指向子类型的对象
    MyInterface mi = new MyInterfaceImpl();
    // 调用默认方法
    mi.defaultMethod();
    // 调用接口的静态方法
    MyInterface.staticMethod();
    }
}    

3.15.2 接口的作用

  1. 面向接口调用的称为:接口调用者
  2. 面向接口实现的称为:接口实现者
  3. 调用者和实现者通过接口达到了解耦合。也就是说调用者不需要关心具体的实现者,实现者也不需要关心具体的调用者,双方都遵循规范,面向接口进行开发。
  4. 面向抽象编程,面向接口编程,可以降低程序的耦合度,提高程序的扩展力。
  5. 例如定义一个Usb接口,提供read()和write()方法,通过read()方法读,通过write()方法写:
    定义一个电脑类Computer,它是调用者,面向Usb接口来调用。
    Usb接口的实现可以有很多,例如:打印机(Printer),硬盘(HardDrive)。
    public class Computer{
        public void conn(Usb usb){
            usb.read();
            usb.write();
        }
    }
    
  6. 再想想,我们平时去饭店吃饭,这个场景中有没有接口呢?食谱菜单就是接口。顾客是调用者。厨师是实现者。

3.15.3 接口与抽象类如何选择

抽象类和接口虽然在代码角度都能达到同样的效果,但适用场景不同:

  1. 抽象类主要适用于公共代码的提取。当多个类中有共同的属性和方法时,为了达到代码的复用,建议为这几个类提取出来一个父类,在该父类中编写公共的代码。如果有一些方法无法在该类中实现,可以延迟到子类中实现。这样的类就应该使用抽象类。
  2. 接口主要用于功能的扩展。例如有很多类,一些类需要这个方法,另外一些类不需要这个方法时,可以将该方法定义到接口中。需要这个方法的类就去实现这个接口,不需要这个方法的就可以不实现这个接口。接口主要规定的是行为。

练习:

  1. 定义一个动物类Animal,属性包括name,age。方法包括display(),eat()。display()方法可以有具体的实现,显示动物的基本信息。但因为不同的动物会有不同的吃的方式,因此eat()方法应该定义为抽象方法,延迟给子类来实现。
  2. 定义多个子类,例如:XiaoYanZi、Dog、YingWu。分别继承Animal,实现eat()方法。
  3. 不是所有的动物都会飞,其中只有XiaoYanZi和YingWu会飞,请定义一个Flyable接口,接口中定义fly()方法。让XiaoYanZi和YingWu都能飞。
  4. 不是所有的动物都会说话,其中只有YingWu会说话,请定义一个Speakable接口,接口中定义speak()方法。让YingWu会说话。
  5. 编写测试程序,创建各个动物对象,调用display()方法,eat()方法,能飞的动物让它飞,能说话的动物让它说话。
    注意:一个类继承某个类的同时可以实现多个接口:class 类 extends 父类 implements 接口A,接口B{}
    注意:当某种类型向下转型为某个接口类型时,接口类型和该类之间可以没有继承关系,编译器不会报错的。

3.16 类之间的关系

3.16.1 UML

  1. UML(Unified Modeling Language,统一建模语言)是一种用于面向对象软件开发的图形化的建模语言。它由Grady Booch、James Rumbaugh和Ivar Jacobson等三位著名的软件工程师所开发,并于1997年正式发布。UML提供了一套通用的图形化符号和规范,帮助开发人员以图形化的形式表达软件设计和编写的所有关键方面,从而更好地展示软件系统的设计和实现过程。
  2. UML是一种图形化的语言,类似于现实生活中建筑工程师画的建筑图纸,图纸上有特定的符号代表特殊的含义。
  3. UML不是专门为java语言准备的。只要是面向对象的编程语言,开发前的设计,都需要画UML图进行系统设计。(设计模式、软件开发七大原则等同样也不是只为java语言准备的。)
  4. UML图包括:
    • 类图(Class Diagram):描述软件系统中的类、接口、关系和其属性等;
    • 用例图(Use Case Diagram):描述系统的功能需求和用户与系统之间的关系;
    • 序列图(Sequence Diagram):描述对象之间的交互、消息传递和时序约束等;
    • 状态图(Statechart Diagram):描述类或对象的生命周期以及状态之间的转换;
    • 对象图(Object Diagram):表示特定时间的系统状态,并显示其包含的对象及其属性;
    • 协作图(Collaboration Diagram):描述对象之间的协作,表示对象之间相互合作来完成任务的关系;
    • 活动图(Activity Diagram):描述系统的动态行为和流程,包括控制流和对象流;
    • 部署图(Deployment Diagram):描述软件或系统在不同物理设备上部署的情况,包括计算机、网络、中间件、应用程序等。
  5. 常见的UML建模工具有:StarUML,Rational Rose等。

3.16.2 类之间的关系

  1. 泛化关系(is a)
  2. 实现关系(is like a)
  3. 关联关系(has a)
  4. 聚合关系
    聚合关系指的是一个类包含、合成或者拥有另一个类的实例,而这个实例是可以独立存在的。聚合关系是一种弱关联关系,表示整体与部分之间的关系。例如一个教室有多个学生
  5. 组合关系(Composition)
    组合关系是聚合关系的一种特殊情况,表示整体与部分之间的关系更加强烈。组合关系指的是一个类包含、合成或者拥有另一个类的实例,而这个实例只能同时存在于一个整体对象中。如果整体对象被销毁,那么部分对象也会被销毁。例如一个人对应四个肢体。
  6. 依赖关系(Dependency)
    依赖关系是一种临时性的关系,当一个类使用另一个类的功能时,就会产生依赖关系。如果一个类的改变会影响到另一个类的功能,那么这两个类之间就存在依赖关系。依赖关系是一种较弱的关系,可以存在多个依赖于同一个类的对象。例如A类中使用了B类,但是B类作为A类的方法参数或者局部变量等。

图3-16-2 类之间的关系

3.17 访问控制权限

图3-17 访问控制权限

  1. private:私有的,只能在本类中访问。
  2. 缺省:默认的,同一个包下可以访问。
  3. protected:受保护的,子类中可以访问。(受保护的通常就是给子孙用的。)
  4. public:公共的,在任何位置都可以访问。
  5. 类中的属性和方法访问权限共有四种:private、缺省、protected和public。
  6. 类的访问权限只有两种:public和 缺省。
  7. 访问权限控制符不能修饰局部变量。

3.18 Object类

  1. java.lang.Object是所有类的超类。java中所有类都实现了这个类中的方法。
  2. Object类是我们学习JDK类库的第一个类。通过这个类的学习要求掌握会查阅API帮助文档。
  3. 现阶段Object类中需要掌握的方法:
    • toString:将java对象转换成字符串。
      • Object类设计toString()方法的目的是什么?
        • 这个方法的作用是:将java对象转换成字符串的表示形式。
      • Object类中toString()方法的默认实现是怎样的?
        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
        
        • 默认实现是:完整类名 + @ + 十六进制的数字
        • 这个输出结果可以等同看做一个java对象的内存地址。
    • equals:判断两个对象是否相等。
      • Object类设计equals方法的作用是什么?目的是什么?
        • equals方法的作用是:判断两个对象是否相等。
        • equals方法的返回值是true/false
        • true代表两个对象相等。
        • false代表两个对象不相等。
      • Object类中对equals方法的默认实现是怎样的?
        public boolean equals(Object obj) {
            return (this == obj);
        }
        
        • a.equals(b) 表面是a和b的比较。实际上方法体当中是:this和obj的比较。
      • 关于 == 运算符的运算规则:
        • == 永远只有一个运算规则,永远比较的是变量中保存的值之间的比较。
        • 只不过有的时候这个值是基本数据类型。有的时候这个值是对象的内存地址。
        • 如果毕竟两个字符串是否相等,不能使用==,只能使用equals
      • equals方法为什么要重写?
        • 因为Object类中的equals方法在进行比较的时候,比较的是两个java对象的内存地址。
        • 我们希望比较的是对象的内容。只要对象的内容相等,则认为是相同的。
  4. 现阶段Object类中需要了解的方法:
    • hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象。
      • hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。
      • Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。
      • hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象
      • hashCode()方法在Object类中的默认实现:
        • public native int hashCode();
        • 这是一个本地方法,底层调用了C++写的动态链接库程序:xxx.dll
    • finalize:当java对象被回收时,由GC自动调用被回收对象的finalize方法,通常在该方法中完成销毁前的准备。
      • 从Java9开始,这个方法被标记已过时,不建议使用。作为了解。
      • 在Object类中是这样实现的:很显然,这个方法是需要子类重写的。protected void finalize() throws Throwable { }
    • clone:对象的拷贝。(浅拷贝,深拷贝)通常在开发中需要保护原对象数据结构。通常复制一份,生成一个新对象,对新对象进行操作。
      • protected修饰的只能在同一个包下或者子类中访问。
      • 只有实现了Cloneable接口的对象才能被克隆。
      • Object类中的默认实现:
        • protected native Object clone() throws CloneNotSupportedException;
        • 受保护的方法,专门给子类使用的。
        • 本地方法。
        • 底层调用C++程序已经可以完成对象的创建了。
        • 我们现在要解决的问题是:怎么调用这个方法。
      • 怎么解决clone()方法的调用问题?
        • 在子类中重写该clone()方法。
        • 为了保证clone()方法在任何位置都可以调用,建议将其修饰符修改为:public
      • 凡事参加克隆的对象,必须实现一个标志接口:java.lang.Cloneable
        • java中接口包括两大类:
          • 一类是:起到标志的作用,标志型接口。
          • 另一类是:普通接口。

图3-18 浅克隆

3.19 内部类

  1. 什么是内部类?
    定义在一个类中的类。
  2. 什么时候使用内部类?
    • 一个类用到了另外一个类,而这两个类的联系比较密切,但是如果把这两个类定义为独立的类,不但增加了类的数量,也不利于代码的阅读和维护。
    • 内部类可以访问外部类的私有成员,这样可以将相关的类和接口隐藏在外部类的内部,从而提高封装性。
    • 匿名内部类是指没有名字的内部类,通常用于定义一个只使用一次的类,比如在事件处理中。
  3. 内部类包括哪几种?
    1. 静态内部类:和静态变量一个级别
      • 静态内部类如何实例化:OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass();
      • 无法直接访问外部类中实例变量和实例方法。
    2. 实例内部类:和实例变量一个级别
      • 实例内部类如何实例化:OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
      • 可以直接访问外部类中所有的实例变量,实例方法,静态变量,静态方法。
    3. 局部内部类:和局部变量一个级别
      • 局部内部类能不能访问外部类的数据,取决于局部内部类所在的方法。
      • 如果这个方法是静态的:只能访问外部类中静态的。
      • 如果这个方法是实例的:可以都访问。
      • 局部内部类不能使用访问权限修饰符修饰。
      • 局部内部类在访问外部的局部变量时,这个局部变量必须是final的。只不过从JDK8开始。这个final关键字不需要提供了。系统自动提供。
    4. 匿名内部类:特殊的局部内部类,没有名字,只能用一次。

  • 每章一句:“我荒废了时间,时间便把我荒废了。”
  • 恭喜你已阅读完第三章!点个赞证明你已经挑战成功,进入下一关关卡吧第四章数组
  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值