【Java系列】:面向对象编程(下)

在这里插入图片描述
本文章总结自韩顺平老师教程,韩顺平Java视频链接:韩顺平Java

⭐️面向对象编程-高级部分⭐️

📚1. 类变量

🍀1.1 类变量-提出问题

在这里插入图片描述
使用现有技术去解决,代码如下:

package com.hj.第十章面向对象编程下.ststic_;

/**
 * @author Baridhu
 */
public class ChildGame {
    public static void main(String[] args) {
        //定义一个变量count,统计有多少加入游戏
        int count=0;
        Child child1 = new Child("白骨精");
        child1.join();
        count++;
        Child child2 = new Child("狐狸精");
        child2.join();
        count++;
        Child child3 = new Child("老鼠精");
        child3.join();
        count++;
        //==============
        System.out.println("共有"+count+"加入了游戏...");
    }
}
class Child{
    private String name;
    public Child(String name){
        this.name=name;
    }
    public void join(){
        System.out.println(name+"加入了游戏..");
    }
}

大家看看上面代码觉得有什么问题?

  1. count是一个独立于对象,并不是Child类的属性;
  2. 以后我们访问count会非常的麻烦,没有使用到OOP;
  3. 因此我们引入类变量(静态变量)

⚡️改进:
如果设计一个count表示总人数,我们在创建一个小孩时,就把 count 加 1,并且 count 是所有对象共享的就 ok 了!,我们使用类变量来解决 ChildGame.java 改进。

package com.hj.第十章面向对象编程下.ststic_;

/**
 * @author Baridhu
 */
public class ChildGame {
    public static void main(String[] args) {
        //定义一个变量count,统计有多少加入游戏
        int count=0;
        Child child1 = new Child("白骨精");
        child1.join();
        //count++;
        child1.count++;
        Child child2 = new Child("狐狸精");
        child2.join();
        //count++;
        child2.count++;
        Child child3 = new Child("老鼠精");
        child3.join();
        //count++;
        child3.count++;
        //==============
        //类变量可以通过类名来访问。
        System.out.println("共有"+Child.count+"人加入了游戏...");
        System.out.println("child1.count=" + child1.count);//3
        System.out.println("child2.count=" + child2.count);//3
        System.out.println("child3.count=" + child3.count);//3
    }
}
class Child{
    private String name;
    //定义一个变量count,是一个类变量(静态变量)static 静态
    //该变量最大的特点就是会被Child类的所有对象实例共享。
    public static int count=0;
    public Child(String name){
        this.name=name;
    }
    public void join(){
        System.out.println(name+"加入了游戏..");
    }
}

🚀运行结果:

白骨精加入了游戏..
狐狸精加入了游戏..
老鼠精加入了游戏..
共有3人加入了游戏...
child1.count=3
child2.count=3
child3.count=3

🍀1.2 类变量内存布局

视频讲解:韩顺平Java_类变量内存剖析
JDK7以上版本,静态域存储于定义类型的Class对象中,Class对象如同堆中其他对象一样,存在于GC堆中。也就是说
☀️JDK7以上版本的静态变量存在于堆中,而不是元空间。

补充:
☀️JDK7以下的静态变量储存在静态域中,静态域储存在方法区中。

在这里插入图片描述

1.类加载时,.class文件被加载到内存中,并构建了一个Class对象,在中。
2. 静态变量经过追踪,发现其和Class对象绑定在一起,由 1 可知其存在于堆中(都说静态变量在方法区,只能说.class文件的信息都在方法区,所以静态变量的信息存在于方法区,静态变量本身并不在)。
3. 它的对象实体(new出来的对象)也存在于堆中(java8)。补充:java8 将 静态变量 、StringTable 都从方法区移动到 堆中,主要是想进行GC,方法区也能GC,但只能full GC,频率会很低。

⭐️总结:不管static变量在哪里,都有以下共识
(1) static类变量是同一个类所有对象共享;
(2) static类变量,在类加载的时候就生成了。

🍀1.3 类变量的介绍和定义

⚡️介绍:
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可看出来。
⚡️定义:

定义语法:
访问修饰符static 数据类型变量名;[推荐]
static访问修饰符数据类型变量名;

🍀1.4 如何访问类变量

类名.类变量名;
或者
对象名.类变量名;【静态变量的访问修饰符的访问权限和范围和普通属性是一样的。】
推荐使用:类名.类变量名;

⚡️示例代码:

public class VisitStatic {
    public static void main(String[] args) {
        //类名.类变量名
        //说明:类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以访问
        System.out.println(A.name);
        A a = new A();
        //通过对象名.类变量名a
        System.out.println(a.name);
    }
}
class A{
    //类变量
    public static String name="BaridHU";
}

大家还要注意一点:类变量的访问,必须遵守相关的访问权限!
比如我将public改为private的话就会报错:
在这里插入图片描述
在这里插入图片描述

🍀1.5 类变量使用注意事项和细节讨论

⚡️1.什么时候需要用类变量?
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。
比如:定义学生类,统计所有学生共交多少钱。

Student (name, static fee)

⚡️2.类变量与实例变量(普通属性)区别:

类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
在这里插入图片描述

⚡️3.加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量。

普通属性/普通成员变量/非静态属性/非静态成员变量/实例变量
private int num = 10;

⚡️4.类变量可以通过类名.类变量名或者对象名.类变量名来访问,但java设计者推荐我们使用类名.类变量名方式访问。前提是满足访问修饰符的访问权限和范围

⚡️5.实例变量不能通过类名.类变量名方式访问。
在这里插入图片描述

在这里插入图片描述

⚡️6.类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。

/**
 * @author Baridhu
 */
public class StaticDetail {
    public static void main(String[] args) {
        //静态变量是类加载的时候,就创建了,所以我们没有创建对象实例
        //也可以通过类名.类变量名来访问
        System.out.println(C.address);
    }
}
class C {
    public static String address = "北京";
}

⚡️7.类变量的生命周期是随类的加载开始,随着类消亡而销毁。

📚2. 类方法

🍀1.1 类方法基本介绍

类方法也叫静态方法。

⚡️形式如下:

访问修饰符 static数据返回类型 方法名(){}【推荐】
static 访问修饰符 数据返回类型 方法名(){}

🍀1.2 类方法的调用

使用方式:类名.类方法名或者对象名.类方法名
前提是满足访问修饰符的访问权限和范围。

🍀1.3 类方法应用案例

定义学生类,统计所有学生共交多少钱。
使用对象.静态方法

public class StaticMethod {
    public static void main(String[] args) {
        Stu tom=new Stu("tom");
        tom.payFee(100);
        Stu mary=new Stu("tom");
        mary.payFee(200);

        //输出当前收到的总学费
        Stu.showFee();
    }
}
class Stu{
    private String name;//456swd
    //定义一个静态变量,来累计学生的学费
    private static double fee=0;

    public Stu(String name) {
        this.name = name;
    }
    //说明
    //1. 当方法使用了 static 修饰后,该方法就是静态方法
    //2. 静态方法就可以访问静态属性/变量
    public static void payFee(double fee){
        Stu.fee += fee;//累积到
    }
    public static void showFee(){
        System.out.println("总学费有:"+Stu.fee);
    }
}

使用类名.静态方法

Stu tom=new Stu("tom");
Stu.payFee(100);
Stu mary=new Stu("tom");
Stu.payFee(200);
//输出当前收到的总学费
Stu.showFee();

⚡️运行结果:

总学费有:300.0

🍀1.4 类方法经典的使用场景

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
比如:工具类中的方法utils
Math类Arrays类Collections集合类

//如果我们希望不创建实例,也可以调用某个方法(即当做工具来使用)
//这时,把方法做成静态方法时非常合适
System.out.println("9 开平方的结果是=" + Math.sqrt(9));

⚡️小结

在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等。

开发自己的工具类时,可以将方法做成静态的,方便调用。

public class StaticMethod {
    public static void main(String[] args) {
        System.out.println(MyTools.calSum(10,20));
    }
}
//开发自己的工具类时,可以将方法做成静态的,方便调用
class MyTools {
    //求出两个数的和
    public static double calSum(double n1, double n2) {
        return n1 + n2;
    }
    //可以写出很多这样的工具方法...
}

🍀1.5 类方法使用注意事项和细节讨论

1.类方法普通方法都是随着类加载而加载将结构信息存储在方法区
⚡️注意:
类方法中无this的参数
普通方法中隐含着this的参数

2.类方法可以通过类名调用,也可以通过对象名调用。

3.普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用

public class StaticMethodDetail {
    public static void main(String[] args) {
        D.hi();//ok
        //非静态方法,不能通过类名调用
        //D.say();, 错误,需要先创建对象,再调用
        new D().say();//可以
    }
}
class D {
    public void say() {//非静态方法,普通方法
    }
    public static void hi() {//静态方法,类方法 
    }
}

4.类方法中不允许使用和对象有关的关键字,比如thissuper。普通方法(成员方法)可以

使用下面代码会报错:

class D {
	private int n1 = 100;
	
    public void say() {//非静态方法,普通方法
    }
    
    public static void hi() {/静态方法,类方法
    //类方法中不允许使用和对象有关的关键字,
    //比如 this 和 super。普通方法(成员方法)可以。
    System.out.println(this.n1);
    }
}

在这里插入图片描述

5.类方法(静态方法)中只能访问静态变量或静态方法。

//类方法(静态方法)中 只能访问 静态变量 或静态方法
//口诀:静态方法只能访问静态成员.
public static void hello() {
    System.out.println(n2);
    System.out.println(D.n2);
    //System.out.println(this.n2);不能使用
    hi();//OK
    //say();//错误
}

6.普通成员方法,既可以访问非静态成员,也可以访问静态成员。
⚡️小结:
静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)

//普通成员方法,既可以访问 非静态成员,也可以访问静态成员
//小结: 非静态方法可以访问 静态成员和非静态成员
public void ok() {
    //非静态成员
    System.out.println(n1);
    say();
    //静态成员
    System.out.println(n2);
    hello();
}

还可以看看:Java关键字之static

🍀1.6 练习

视频讲解韩顺平Java_类成员课堂练习
⚡️T1:请判断下面代码的输出结果:
在这里插入图片描述
⚡️答案:
在这里插入图片描述
⚡️T2:看看下面代码有没有错误,如果有错误,就修改,看看输出什么?
在这里插入图片描述
⚡️答案:
类方法(静态方法)中只能访问静态变量或静态方法,所以将i++注销。
在这里插入图片描述
⚡️T3:看看下面代码有没有错误,如果有错误,就修改,看看 total 等于多少 ?
在这里插入图片描述
⚡️答案:
在这里插入图片描述

🍀小结:
(1) 静态方法,只能访问静态成员
(2) 非静态方法,可以访问所有的成员
(3) 在编写代码时,仍然要遵守访问权限规则

📚3. 理解 main 方法语法

🍀3.1 深入理解main()方法

解释main方法的形式:public static void main(Stringll args){}

  1. main方法虚拟机调用
  2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. java虚拟机在执行main()方法不必创建对象,所以该方法必须是static
  4. 该方法接收String类型数组参数,该数组中保存执行java命令时传递给所运行的类的参数。
public class hello {
   public static void main(String[] args) {
       //args是如何传入的
       //遍历演示
       for(int i = 0; i < args.length; i++){
           System.out.println("第"+ (i + 1) + "个参数=" + args);
       }
   }
}

在这里插入图片描述

  1. java 执行的程序会传入参数

在这里插入图片描述

🍀3.2 特别提示

  1. main()方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性。
public class Main01 {
    //静态的变量/属性
    private static String name = "BaridHU";
    //静态方法
    public static void hi() {
        System.out.println("Main01 的 hi 方法");
    }
    public static void main(String[] args) {
        //可以直接使用 name
        //1. 静态方法 main 可以访问本类的静态成员
        System.out.println("name=" + name);
        hi();

    }
}
name=BaridHU
Main01 的 hi 方法
  1. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
public class Main01 {
    //非静态的变量/属性
    private int n1 = 10000;
    //非静态方法
    public void cry() {
        System.out.println("Main01 的 cry 方法");
    }
    public static void main(String[] args) {
        //1. 静态方法 main 不可以访问本类的非静态成员
        //System.out.println("n1=" + n1);//错误
        //cry();
        //2. 静态方法 main 要访问本类的非静态成员,需要先创建对象 , 再调用即可
        Main01 main01 = new Main01();
        System.out.println(main01.n1);//ok
        main01.cry();
    }
}
10000
Main01 的 cry 方法

拓展:怎么在idea中给main方法传入参数?

public class hello {
    public static void main(String[] args) {
        //args是如何传入的
        //遍历演示
        for(int i = 0; i < args.length; i++){
            System.out.println("第"+ (i + 1) + "个参数=" + args);
        }
    }
}

在这里插入图片描述

📚4. 代码块

🍀4.1 基本介绍

代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。

🍀4.2 基本语法

[修饰符]{
代码
};

⚡️说明注意:

  1. 修饰符可选,要写的话,也只能写static
  2. 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块
  3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;可以写上,也可以省略

🍀4.3 代码块的好处和案例演示

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
  2. 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性

⚡️代码块的快速入门

public class CodeBlock01 {
    public static void main(String[] args) {
        new Movie("你好,李焕英!");
        System.out.println("===============");
        Movie movie2 = new Movie("唐探 3", 100,"陈思诚");
    }
}
class Movie{
    private String name;
    private double price;
    private String director;

    //三个构造器==》重载
    //解读
    //(1) 下面的三个构造器都有相同的语句
    //(2) 这样代码看起来比较冗余
    //(3) 这时我们可以把相同的语句,放入到一个代码块中,即可
    //(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
    //(5) 代码块调用的顺序优先于构造器...
    {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始...");
        System.out.println("电影正是开始...");
    }
    public Movie(String name){
        System.out.println("Movie(String name)被调用");
        this.name=name;
    }

    public Movie(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {
        System.out.println("Movie(String name, double price, String director)——)");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

⚡️运行结果:

电影屏幕打开...
广告开始...
电影正是开始...
Movie(String name)被调用
===============
电影屏幕打开...
广告开始...
电影正是开始...
Movie(String name, double price, String director)——)

🍀4.4 代码块使用注意事项和细节讨论

⚡️细节一: static代码块也叫静态代码块作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行

⚡️案例演示:
🚀static 代码块是在类加载时执行的,而且只会执行一次

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        DD dd = new DD();
        DD dd1 = new DD();
    }
}
class DD {
    //静态代码块
    static {
        System.out.println("DD 的静态代码 1 被执行...");//
    }
}
DD 的静态代码 1 被执行...

⚡️细节二: 类什么时候被加载[重要!要背!]

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

⚡️案例演示:
🚀1.创建对象实例时(new)

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 创建对象实例时(new)
        AA aa = new AA();
    }
}
class AA{
    //静态代码块
    static{
        System.out.println("AA的静态代码块被执行...");
    }
}
AA的静态代码块被执行...

🚀2.创建子类对象实例,父类也会被加载,而且,父类先被加载,子类后被加载

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 创建对象实例时(new)
        //AA aa = new AA();
        //2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
        AA aa2 = new AA();
    }
}
class BB {
    //静态代码块
    static{
        System.out.println("BB的静态代码块被执行...");
    }
}
class AA extends BB{
    //静态代码块
    static{
        System.out.println("AA的静态代码块被执行...");
    }
}
BB的静态代码块被执行...
AA的静态代码块被执行...

🚀3.使用类的静态成员时(静态属性,静态方法)

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 创建对象实例时(new)
        //AA aa = new AA();
        //2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
        //AA aa2 = new AA();
        //3. 使用类的静态成员时(静态属性,静态方法)
        System.out.println(Cat.n1);
    }
}
class Cat{
    public static int n1 = 999;//静态属性

    //静态代码块
    static {
        System.out.println("Cat 的静态代码 1 被执行...");//
    }
}
Cat 的静态代码 1 被执行...
999

静态代码块的优先级大于静态变量

拓展:大家思考一个问题:如果Cat有一个父类Animal,当Cat的静态变量n1被调用时,Animal类有没有被加载?

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        System.out.print(Cat.n1);
    }
}
class Animal {
    //静态代码块
    static {
        System.out.println("Animal 的静态代码 1 被执行...");//
    }
}
class Cat extends Animal {
    public static int n1 = 999;//静态属性
    //静态代码块
    static {
        System.out.println("Cat 的静态代码 1 被执行...");//
    }
}

答案:会
可以看一下运行结果!

Animal 的静态代码 1 被执行...
Cat 的静态代码 1 被执行...
999

⚡️细节三: 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。

⚡️案例演示:
🚀普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //普通的代码块,在创建对象实例时,会被隐式的调用。
        //被创建一次,就会调用一次。
        DD dd = new DD();
        DD dd1 = new DD();
    }
}
class DD {
    //静态代码块
    static {
        System.out.println("DD 的静态代码 1 被执行...");//
    }
    //普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一次
    //可以这样简单的,理解 普通代码块是构造器的补充
    {
        System.out.println("DD 的普通代码块...");
    }
}
DD 的静态代码 1 被执行...
DD 的普通代码块...
DD 的普通代码块...

🚀如果只是使用类的静态成员时,普通代码块并不会执行

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        // 如果只是使用类的静态成员时,普通代码块并不会执行
        System.out.println(DD.n1);//8888, 静态模块块一定会执行
    }
}
class DD {
    public static int n1 = 8888;//静态属性
    //静态代码块
    static {
        System.out.println("DD 的静态代码 1 被执行...");//
    }
    //普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一次
    //和类加载没有关系,可以这样简单的,理解 普通代码块是构造器的补充
    {
        System.out.println("DD 的普通代码块...");
    }
}
DD 的静态代码 1 被执行...
8888

⚡️小结:
1. static代码块是类加载时,执行,只会执行一次
2. 普通代码块是在创建对象时调用的,创建一次,调用一次
3. 类加载的3种情况,需要记住(细节二)

⚡️细节四: 创建一个对象时,在一个类调用顺序是:(重点,难点);

  • 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)-见案例演示1
  • 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)见案例演示2
  • 调用构造方法(构造器的优先级是最低的)-见案例演示3.

⚡️案例演示:
🚀1. 调用静态代码块和静态属性初始化

代码一:

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}
class A{
    //静态属性初始值
    private static int n1 = getN1();

    static {//静态属性代码块

        System.out.println("A 静态代码块01");
    }

    public static int getN1(){
        System.out.println("getN1被调用");
        return 100;
    }
}

☀️运行结果:

getN1被调用
A 静态代码块01

代码二(将静态方法和静态属性换个位置):

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}
class A{
    static {//静态属性代码块
        System.out.println("A 静态代码块01");
    }

    //静态属性初始值
    private static int n1 = getN1();

    public static int getN1(){
        System.out.println("getN1被调用");
        return 100;
    }
}

☀️运行结果:

A 静态代码块01
getN1被调用

可以看到两段代码的运行结果是顺序不一样的

代码一:
getN1被调用
A 静态代码块01
代码二:
A 静态代码块01
getN1被调用

⚡️说明:
静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用

⚡️案例演示:
🚀2. 调用普通代码块和普通属性的初始化

代码一:

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}
class A {
    { //普通代码块
        System.out.println("A 普通代码块 01");
    }
    private int n2 = getN2();//普通属性的初始化
    static { //静态代码块
        System.out.println("A 静态代码块 01");
    }
    //静态属性的初始化
    private static int n1 = getN1();
    public static int getN1() {
        System.out.println("getN1 被调用...");
        return 100;
    }
    public int getN2() { //普通方法/非静态方法
        System.out.println("getN2 被调用...");
        return 200;
    }
}

☀️运行结果::

A 静态代码块 01
getN1 被调用...
A 普通代码块 01
getN2 被调用...

代码二(调换普通代码块和普通属性的顺序):

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}
class A {
    private int n2 = getN2();//普通属性的初始化
    
    { //普通代码块
        System.out.println("A 普通代码块 01");
    }
    
    static { //静态代码块
        System.out.println("A 静态代码块 01");
    }
    
    //静态属性的初始化
    private static int n1 = getN1();
    public static int getN1() {
        System.out.println("getN1 被调用...");
        return 100;
    }
    public int getN2() { //普通方法/非静态方法
        System.out.println("getN2 被调用...");
        return 200;
    }
}

☀️运行结果:

A 静态代码块 01
getN1 被调用...
getN2 被调用...
A 普通代码块 01

⚡️说明:
普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用

⚡️案例演示:
调用构造方法

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}
class A {
    { //普通代码块
        System.out.println("A 普通代码块 01");
    }
    private int n2 = getN2();//普通属性的初始化
    static { //静态代码块
        System.out.println("A 静态代码块 01");
    }
    //静态属性的初始化
    private static int n1 = getN1();
    public static int getN1() {
        System.out.println("getN1 被调用...");
        return 100;
    }
    public int getN2() { //普通方法/非静态方法
        System.out.println("getN2 被调用...");
        return 200;
    }
    //无参构造器
    public A() {
        System.out.println("A() 构造器被调用");
    }
}

☀️运行结果:

A 静态代码块 01
getN1 被调用...
A 普通代码块 01
getN2 被调用...
A() 构造器被调用

⭐️注意⭐️

静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的

上面说的细节四一定要理解,要弄懂,要把顺序搞清楚,这是非常重要的!

⚡️细节五: 构造器的最前面其实隐含了super()和调用普通代码块

class A {
	public A( /构造器
		//这里有隐藏的执行要求
		//(1) super)://这个知识点,在前面讲解继承的时候总结过
		//(2)调用普通代码块的
		System.out.println("ok");
	}	
}

⚡️案例演示:

public class CodeBlockDetail03 {
    public static void main(String[] args) {
        new BBB();//(1)AAA 的普通代码块(2)AAA() 构造器被调用(3)BBB 的普通代码块(4)BBB() 构造器被调用
    }
}
class AAA { //父类 Object
    {
        System.out.println("AAA 的普通代码块");
    }
    public AAA() {
    //(1)super()
    //(2)调用本类的普通代码块
        System.out.println("AAA() 构造器被调用....");
    }
}
class BBB extends AAA {
    {
        System.out.println("BBB 的普通代码块...");
    }
    public BBB() {
    //(1)super()
    //(2)调用本类的普通代码块
        System.out.println("BBB() 构造器被调用....");
    }
}

☀️运行结果:

AAA 的普通代码块
AAA() 构造器被调用....
BBB 的普通代码块...
BBB() 构造器被调用....

⭐️总结⭐️

(父静>子静)>(父普>父构)>(子普>子构)

接下来是最为重量级的选手,大家打起精神来哦!
⚡️细节六: 我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:

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

☀️注意:面试可能会问到这样的问题。
⚡️细节七:静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调
用任意成员。

⚡️案例演示:

class C02 {
    private int n1 = 100;
    private static int n2 = 200;

    private void m1() {
    }

    private static void m2() {
    }

    static {
        //静态代码块,只能调用静态成员
        //System.out.println(n1);错误
        System.out.println(n2);//ok
        //m1();//错误
        m2();
    }

    {
        //普通代码块,可以使用任意成员
        System.out.println(n1);
        System.out.println(n2);//ok
        m1();
        m2();
    }
}

学习这些知识虽然比较麻烦,但是将来我们工作以后就会相对轻松了。

🍀4.5练习题

⚡️T1:
下面的代码输出什么?

class Person {
    public static int total;//静态变量
    static {//静态代码块
        total = 100;
        System.out.println("in static block!");
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println("total = "+ Person.total); 
        System.out.println("total = "+ Person.total); 
    }
}

☀️答案:

in static block!
total = 100
total = 100

⚡️T2:
重点题:下面的代码输出什么?

public class Exercise{
    //主方法
    public static void main(String str[])
    {
        Test a=new Test();//无参构造器
    }
}
class Sample
{
    Sample(String s)
    {
        System.out.println(s);
    }
    Sample()
    {
        System.out.println("Sample 默认构造函数被调用");
    }
}
    //====
class Test{
    Sample sam1=new Sample("sam1 成员初始化");//
    static Sample sam=new Sample("静态成员 sam 初始化 ");//
    static{
        System.out.println("static 块执行");//
        if(sam==null)System.out.println("sam is null");
    }
    Test()//构造器
    {
        System.out.println("Test 默认构造函数被调用");//
    }
}

⚡️讲解:
① 在主方法中,创建一个Test对象时会先进行类的加载

//主方法
    public static void main(String str[])
    {
        Test a=new Test();//无参构造器
    }

我们可以看到在Test类中首先进行的就是静态成员的初始化

    static Sample sam=new Sample("静态成员 sam 初始化 ");

这时又会创建一个Sample类的对象,并且调用有参构造器 Sample(String s)

Sample(String s)
    {
        System.out.println(s);
    }

所以首先输出的是:静态成员 sam 初始化
②静态属性初始化完以后就是进行静态代码块的初始化

static{
        System.out.println("static 块执行");//
        if(sam==null)System.out.println("sam is null");
    }

这是会输出:static 块执行,由于sam已经不为空了,所以不会输出sam is null

③加载完有关静态的以后会加载构造器Test()

 Test()//构造器
    {
        System.out.println("Test 默认构造函数被调用");
    }

由于在构造器中隐含有super()和普通代码块或普通属性初始化的调用,所以会先调用普通属性初始化:

 Sample sam1=new Sample("sam1 成员初始化");

这时就会输出语句:sam1 成员初始化
最后再输出语句:Test 默认构造函数被调用

☀️运行结果:

静态成员 sam 初始化 
static 块执行
sam1 成员初始化
Test 默认构造函数被调用

📚5. 单例设计模式

🍀5.1 什么是设计模式

①静态方法和属性的经典使用
②设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索

在这里插入图片描述

🍀5.2 什么是单例模式

单例(单个的实例)
①所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
②单例模式有两种方式:

  1. 饿汉式
  2. 懒汉式

🍀5.3 单例模式应用实例

演示饿汉式和懒汉式单例模式的实现。步骤如下:

  1. 构造器私有化(目的是防止用户直接new一个对象)
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getlnstance
  4. 代码实现

⚡️饿汉式演示

在日常生活中我们一般都只能有一个女朋友(除了我这种单生狗和另外一些海王),但在下面的代码中你有了两个女朋友:小红和小白(挺美好是吧),但这时你必须甩掉一个使得你从此摆脱渣男的称号,你要怎么做呢?记得用代码实现!

public class SingleTon01 {
    public static void main(String[] args) {
        GirlFriend xh = new GirlFriend("小红");
        GirlFriend xb = new GirlFriend("小白");
    }
}
//有一个类,GirlFriend
//只能有一个女朋友
class GirlFriend{

    private String name;

    //如何保障我们只能创建一个GirlFriend对象
    public GirlFriend(String name) {
        this.name = name;
    }
}

☀️步骤:
① 将构造器私有化

 private GirlFriend(String name) {
        this.name = name;
    }

在这里插入图片描述
但这时又有麻烦了,如果只这样处理一个女朋友都没有了,所以我们接下来要做点什么来挽回小红或小白的欢心。

② 在类的内部直接创建对象(该对象是 static)

class GirlFriend{

    private String name;
    
    private static GirlFriend gf = new GirlFriend("小红红");
    //如何保障我们只能创建一个GirlFriend对象
    //步骤
    //1. 将构造器私有化
    //2. 在类的内部直接创建对象(该对象是 static)
    private GirlFriend(String name) {
        this.name = name;
    }
}

这时你发现原来小红才是自己的真爱,所以你将小红作为自己终生为之付出的女朋友。

③ 提供一个公共的static方法,返回gf对象

class GirlFriend{

    private String name;

    //为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static
    private static GirlFriend gf = new GirlFriend("小红红");
    //如何保障我们只能创建一个GirlFriend对象
    //步骤
    //1. 将构造器私有化
    //2. 在类的内部直接创建对象(该对象是 static)
    //3. 提供一个公共的static方法,返回gf对象
    private GirlFriend(String name) {
        this.name = name;
    }

    public static GirlFriend getInstance() {
        return gf;
    }
}

为了得到小红我们写一个static方法,用来返回gf对象。
这时我们通过方法可以获取对象:

public class SingleTon01 {
    public static void main(String[] args) {
//        GirlFriend xh = new GirlFriend("小红");
//        GirlFriend xb = new GirlFriend("小白");
        //通过方法可以获取对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
        System.out.println(instance == instance2);//T
    }
}

☀️运行结果:

GirlFriend{name='小红红'}
GirlFriend{name='小红红'}
true

最后通过努力终于又追回了小红。
⚡️说明:上面代码为饿汉式,饿汉式:类加载的时候就实例化,并且创建单例对象。

为什么叫饿汉式呢?
因为在类加载的时候就实例化,并且创建单例对象,比如说我们在GirlFriend类中增加一个静态变量n1

public static int n1 = 100;

当我们在主方法中使用以下语句时就已经进行了类加载并且实例化了对象,创建单例对象。

System.out.println(GirlFriend.n1);

此时的gf对象已经创建,但是还没有调用。

public class SingleTon01 {
    public static void main(String[] args) {
    	System.out.println(GirlFriend.n1);
    }
}
//有一个类,GirlFriend
//只能有一个女朋友
class GirlFriend{

    private String name;

    public static int n1 = 100;

    //为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static
    private static GirlFriend gf = new GirlFriend("小红红");
    //如何保障我们只能创建一个GirlFriend对象
    //步骤
    //1. 将构造器私有化
    //2. 在类的内部直接创建对象(该对象是 static)
    //3. 提供一个公共的static方法,返回gf对象
    private GirlFriend(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

    public static GirlFriend getInstance() {
        return gf;
    }

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

☀️运行结果:

构造器被调用
100

⚡️饿汉式的弊端饿汉式的对象,通常是重量级的对象, 饿汉式可能造成创建了对象,但是没有使用!

⚡️懒汉式演示

☀️步骤:
① 将构造器私有化

class Cat {
    private String name;
    //步骤
    //1.将构造器私有化
    private Cat(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }
   
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

② 定义一个static 静态属性对象:private static Cat cat ;

class Cat {
    private String name;
    private static Cat cat ; //默认是 null
    //步驟
    //1.将构造器私有化
    //2.定义一个 static 静态属性对象
    private Cat(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

③ 提供一个 publicstatic 方法,可以返回一个 Cat 对象

class Cat {
    private String name;
    private static Cat cat ; //默认是 null
    //步驟
    //1.将构造器私有化
    //2.定义一个 static 静态属性对象
    //3.提供一个 public 的 static 方法,可以返回一个 Cat 对象
    private Cat(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }
    public static Cat getInstance() {
        if(cat == null) {//如果还没有创建 cat 对象
            cat = new Cat("小可爱");
        }
        return cat;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

现在我们就可以看看懒汉式和饿汉式有什么区别了。

☀️懒汉式,只有当用户使用 getInstance 时,才返回 cat 对象, 后面再次调用时,会返回上次创建的 cat 对象,从而保证了单例
☀️看下面代码:

public class SingleTon02 {
    public static void main(String[] args) {
        System.out.println(Cat.n1);
    }
}
    //希望在程序运行过程中,只能创建一个 Cat 对象
    //使用单例模式
class Cat {
    private String name;
    public static int n1 = 999;
    private static Cat cat ; //默认是 null
    //步驟
    //1.将构造器私有化
    //2.定义一个 static 静态属性对象
    //3.提供一个public 的 static 方法,可以返回一个 Cat 对象
    //4.懒汉式,只有当用户使用 getInstance 时,才返回 cat 对象, 后面再次调用时,会返回上次创建的 cat 对象
    // 从而保证了单例
    private Cat(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }
    public static Cat getInstance() {
        if(cat == null) {//如果还没有创建 cat 对象
            cat = new Cat("小可爱");
        }
        return cat;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

☀️运行结果:

999

这时就没有再调用Cat类的构造器了。
那如何调用呢?

public class SingleTon02 {
    public static void main(String[] args) {
        Cat instance = Cat.getInstance();
        System.out.println(instance);
        //再次调用 getInstance
        Cat instance2 = Cat.getInstance();
        System.out.println(instance2);
        System.out.println(instance == instance2);//T
    }
}
    //希望在程序运行过程中,只能创建一个 Cat 对象
    //使用单例模式
class Cat {
    private String name;

    private static Cat cat ; //默认是 null
    //步驟
    //1.将构造器私有化
    //2.定义一个 static 静态属性对象
    //3.提供一個个public 的 static 方法,可以返回一个 Cat 对象
    //4.懒汉式,只有当用户使用 getInstance 时,才返回 cat 对象, 后面再次调用时,会返回上次创建的 cat 对象
    // 从而保证了单例
    private Cat(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }
    public static Cat getInstance() {
        if(cat == null) {//如果还没有创建 cat 对象
            cat = new Cat("小可爱");
        }
        return cat;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

☀️运行结果

构造器调用...
Cat{name='小可爱'}
Cat{name='小可爱'}
true

🍀5.4 饿汉式 VS 懒汉式

① 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善一把)
饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式

📚6. final 关键字

🍀6.1 基本介绍

➢ final中文意思:最后的,最终的
➢ final可以修饰类、属性、方法和局部变量

在某些情况下,程序员可能有以下需求,就会使用到final:

  1. 当不希望类被继承时,可以用final修饰。
  2. 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
  3. 当不希望类的的某个属性的值被修改,可以用final修饰。
  4. 当不希望某个局部变量被修改,可以使用final修饰

🍀6.2 final 使用注意事项和细节讨论

细节一final修饰的属性又叫常量,一般用XX _XX_XX来命名

细节二:final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
①定义时

class AA {
    /*
    1. 定义时:如 public final double TAX_RATE=0.08;
    */
    public final double TAX_RATE = 0.08;//1.定义时赋值
}

②在构造器中

class AA {
    /*
    2. 在构造器中
    */
    public final double TAX_RATE2 ;

    public AA() {//构造器中赋值
        TAX_RATE2 = 1.1;
    }
}

③在代码块中。

class AA {
    /*
    3. 在代码块中
    */
    public final double TAX_RATE3 ;
    {//在代码块赋值
        TAX_RATE3 = 8.8;
    }
}

细节三如果final修饰的属性是静态的,则初始化的位置只能是
①定义时

class BB {
    /*
    如果 final 修饰的属性是静态的,则初始化的位置只能是
    1 定义时
    */
    public static final double TAX_RATE = 99.9;
    }
}

②在静态代码块不能在构造器中赋值。

class BB {
    /*
    如果 final 修饰的属性是静态的,则初始化的位置只能是
    1 定义时 
    2 在静态代码块 不能在构造器中赋值。
    */
    public static final double TAX_RATE2 ;
    static {
        TAX_RATE2 = 3.3;
    }
}

细节四final类不能继承,但是可以实例化对象。

public class FinalDetail01 {
    public static void main(String[] args) {
        CC cc = new CC();
    }
}
//final 类不能继承,但是可以实例化对象
final class CC { }

细节五如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。

public class FinalDetail01 {
    public static void main(String[] args) {
        new EE().cal();
    }
}
//如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
//即,仍然遵守继承的机制.
class DD {
    public final void cal() {
        System.out.println("cal()方法");
    }
}
class EE extends DD { }

细节六一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。

final class AAA{
//一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法
//public final void cry() {}
}

细节七final不能修饰构造方法(即构造器)

细节八final和static往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。
📚演示:
如果仅仅将num定义为static,在主方法中调用时则会有类加载。看下面代码:

public class FinalDetail02 {
    public static void main(String[] args) {
        System.out.println(BBB.num);
        //包装类,String 是 final 类,不能被继承
    }
}
//final 和 static 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理
class BBB {
    public static int num = 10000;
    static {
        System.out.println("BBB 静态代码块被执行");
    }
}

📚运行结果:

BBB 静态代码块被执行
10000

而final和static往往搭配使用,效率更高,不会导致类加载

public class FinalDetail02 {
    public static void main(String[] args) {
        System.out.println(BBB.num);
        //包装类,String 是 final 类,不能被继承
    }
}
//final 和 static 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理
class BBB {
    public final static int num = 10000;
    static {
        System.out.println("BBB 静态代码块被执行");
    }
}

📚运行结果:

10000

细节九包装类(Integer,Double,Float,Boolean等都是final),String也是final类。

🍀6.3 练习题

T1:请编写一个程序,能够计算圆形的面积。要求圆周率为3.14

public class FinalExercise01 {
    public static void main(String[] args) {
        Circle circle = new Circle(5.0);
        System.out.println("面积=" + circle.calArea());
    }
}
class Circle {
    private double radius;
    private final double PI;// = 3.14;
    //构造器
    public Circle(double radius) {
        this.radius = radius;
    //PI = 3.14;
    }
    {
        PI = 3.14;
    }
    public double calArea() {
        return PI * radius * radius;
    }
}

T2:下面的代码是否有误,为什么?

public int addOne(final int x) { 
	++x; 
	return x + 1;
}

答案:

public int addOne(final int x) { //下面的代码是否有误,为什么? 1min
    //++x; //错误,原因是不能修改 final x 的值
    return x + 1; //这里是可以. }
}

📚7. 抽象类

🍀7.1 先看一个问题

在这里插入图片描述

简单介绍:

当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

public class Abstract01 {
    public static void main(String[] args) {
    }
}
abstract class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
    //思考:这里 eat 这里你实现了,其实没有什么意义
    //即: 父类方法不确定性的问题
    //===> 考虑将该方法设计为抽象(abstract)方法
    //===> 所谓抽象方法就是没有实现的方法
    //===> 所谓没有实现就是指,没有方法体
    //===> 当一个类中存在抽象方法时,需要将该类声明为 abstract 类
    //===> 一般来说,抽象类会被继承,有其子类来实现抽象方法. 
    // public void eat() {
    //      System.out.println("这是一个动物,但是不知道吃什么..");
    // }
    public

🍀7.2 解决之道-抽象类快速入门

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract 来修饰该类就是抽象类。

我们看看如何把Animal做成抽象类,并让子类Cat类实现。

abstract class Animal{
	String name;
	int age;
	abstract public void cry();
}

🍀7.3 抽象类的介绍

abstract关键字来修饰一个类时,这个类就叫抽象类访问修饰符

abstract class 类名{
}

abstract关键字来修饰一个方法时,这个方法就是抽象方法

访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体

抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类方法

抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多

🍀7.4 抽象类使用的注意事项和细节讨论

细节一:抽象类不能被实例化

public class AbstractDetail01 {
    public static void main(String[] args) {
        //抽象类,不能被实例化
        new A();
    }
}

abstract class A {
}

在这里插入图片描述

细节二:抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法

//抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法
//,还可以有实现的方法。
abstract class A {
    public void hi() {
        System.out.println("hi");
    }
}

细节三:一旦类包含了abstract方法,则这个类必须声明为abstract

//一旦类包含了 abstract 方法,则这个类必须声明为 abstract
abstract class B {
    public abstract void hi();
}

在这里插入图片描述

细节四:abstract只能修饰类和方法,不能修饰属性和其它的。

//abstract 只能修饰类和方法,不能修饰属性和其它的
class C {
   public abstract int n1 = 1;
}

在这里插入图片描述
细节五:抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等

//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {
    public int n1 = 10;
    public static String name = "hj";
    public void hi() {
        System.out.println("hi");
    }
    public abstract void hello();
    public static void ok() {
        System.out.println("ok");
    }
}

细节六:抽象方法不能有主体,即不能实现.如图所示
在这里插入图片描述

细节七:如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为`abstract类。

//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类
abstract class E {
    public abstract void hi();
}
abstract class F extends E {
}
class G extends E {
    @Override
    public void hi() { //这里相等于 G 子类实现了父类 E 的抽象方法,所谓实现方法,就是有方法体

    }
}

细节八:抽象方法不能使用private、finalstatic来修饰,因为这些关键字都是和重写相违背的。

//抽象方法不能使用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的
abstract class H {
    public abstract void hi();//抽象方法
}

🍀7.5 课堂练习题

⚡️T1:
思考: abstract final class A能编译通过吗, why?
错误,final是不能继承
⚡️T2:
思考: abstract public static void test2();能编译通过吗, why?
错误,static关键字和方法重写无关
⚡️T3:
思考: abstract private void test3();能编译通过吗, why?
错误,private的方法不能重写
⚡️T4:
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary.提供必要的构造器和抽象方法: work()。对于Manage类来说,他既是员工,还具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示"经理/普通员工名字工作中…"。
Employee类

abstract public class Employee {
    private String name;
    private int id;
    private double salsry;

    public Employee(String name, int id, double salsry) {
        this.name = name;
        this.id = id;
        this.salsry = salsry;
    }
    //将 work 做成一个抽象方法
    abstract public void work();

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getSalsry() {
        return salsry;
    }

    public void setSalsry(double salsry) {
        this.salsry = salsry;
    }

}

Manager类

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, int id, double salsry) {
        super(name, id, salsry);
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
    @Override
    public void work(){
        System.out.println("经理" + getName() + "工作中");
    }
}

CommonEmployee类

public class CommonEmployee extends Employee {
    public CommonEmployee(String name, int id, double salsry) {
        super(name, id, salsry);
    }
    @Override
    public void work(){
        System.out.println("普通员工:" + getName() + "工作中...");
    }
}

Main类

public class Main {
    public static void main(String[] args) {
        Manager jack = new Manager("Jack", 999, 50000);
        jack.setBonus(8000);
        jack.work();

        CommonEmployee tom = new CommonEmployee("tom", 888, 20000);
        tom.work();
    }
}

📚8. 抽象类最佳实践-模板设计模式

🍀8.1 基本介绍

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

🍀8.2 问题引入

需求
有多个类,完成不同的任务job,要求统计得到各自完成任务的时间,请编程实现。
1.先用最容易想到的方法
2.分析问题,提出使用模板设计模式

⚡️1.先用最容易想到的方法

AA类:统计完成1加到500000的时间

package com.hj.第十章面向对象编程下.抽象类.抽象类最佳实践模板设计模式;

/**
 * @author Baridhu
 */

public class AA {
     public void job() {
         //得到开始的时间
         long start = System.currentTimeMillis();
         long num = 0;
         for (long i = 1; i <= 500000; i++) {
         num += i;
         }
         //得的结束的时间
         long end = System.currentTimeMillis();
         System.out.println("AA 执行时间 " + (end - start));
     }
}

BB类:统计完成1加到100000的时间

package com.hj.第十章面向对象编程下.抽象类.抽象类最佳实践模板设计模式;

/**
 * @author Baridhu
 */
public class BB {
    public void job() {
        //得到开始的时间
        long start = System.currentTimeMillis();
        long num = 0;
        for (long i = 1; i <= 100000; i++) {
            num += i;
        }
        //得的结束的时间
        long end = System.currentTimeMillis();
        System.out.println("BB 执行时间 " + (end - start));
    }
}

实现类:

package com.hj.第十章面向对象编程下.抽象类.抽象类最佳实践模板设计模式;

/**
 * @author Baridhu
 */
public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.job();

        BB bb = new BB();
        bb.job();
    }
}

运行结果:

AA 执行时间 3
AA 执行时间 2

我们分析上面的代码:
可以看到AA类和BB类都包含一个共同的代码段

 long start = System.currentTimeMillis();
//得的结束的时间
long end = System.currentTimeMillis();
System.out.println(".. 执行时间 " + (end - start));

于是我们可以把这段代码单独拉出来做成一个方法calculateTime()
AA类:

public class AA {
     public void calculateTime() {//实现方法,调用 job 方法
        //得到开始的时间
        long start = System.currentTimeMillis();
        job(); //动态绑定机制
        //得的结束的时间
        long end = System.currentTimeMillis();
        System.out.println("AA 任务执行时间 " + (end - start));
     }
     public void job() {
         long num = 0;
         for (long i = 1; i <= 500000; i++) {
            num += i;
         }
     }
}

BB类:

public class BB {
    public void calculateTime() {//实现方法,调用 job 方法
        //得到开始的时间
        long start = System.currentTimeMillis();
        job(); //动态绑定机制
        //得的结束的时间
        long end = System.currentTimeMillis();
        System.out.println("BB 任务执行时间 " + (end - start));
    }
    public void job() {
        long num = 0;
        for (long i = 1; i <= 100000; i++) {
            num += i;
        }
    }
}

实现类:

public class TestTemplate {
    public static void main(String[] args) {
        AA aa=new AA();
        aa.calculateTime();

        BB bb=new BB();
        bb.calculateTime();
    }
}

但是还是很麻烦,比如说我们还想要加一个类CC,这是我们依旧还要写两个方法😢。于是我们可以这么想

设计一个抽象类(Template),能完成如下功能:

  1. 编写方法calculateTime(),可以计算某段代码的耗时时间
  2. 编写抽象方法job()
  3. 编写一个子类Sub,继承抽象类Template,并实现job方法
  4. 编写一个测试类TestTemplate,看看是否好用。

⚡️代码如下:

abstract public class Template { //抽象类-模板设计模式
    public abstract void job();//抽象方法
    public void calculateTime() {//实现方法,调用 job 方法
        //得到开始的时间
        long start = System.currentTimeMillis();
        job(); //动态绑定机制
        //得的结束的时间
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间 " + (end - start));
    }
}

AA类继承Template:

public class AA extends Template{
     public void job() {
         long num = 0;
         for (long i = 1; i <= 500000; i++) {
            num += i;
         }
     }
}

BB类继承Template:

public class BB extends Template {
    public void job() {
        long num = 0;
        for (long i = 1; i <= 100000; i++) {
            num += i;
        }
    }
}

实现类:

public class TestTemplate {
    public static void main(String[] args) {
        AA aa=new AA();
        aa.calculateTime();

        BB bb=new BB();
        bb.calculateTime();
    }
}

🍀8.3 总结

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
⚡️模板设计模式能解决的问题

  1. 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  2. 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式.

📚9. 接口

🍀9.1 为什么有接口

请大家先看一张图:
在这里插入图片描述

USB插槽就是现实中的接口。
你可以把手机,相机,u盘都插在USB插槽上,而不用担心那个插槽是专门插哪个的,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等

🍀9.2 接口快速入门

这样的设计需求在java编程/php/.net/go中也是会大量存在的,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序来模拟一下。

🔌设计一个接口UsbInterface

package com.hj.第十章面向对象编程下.接口;

/**
 * @author Baridhu
 */
public interface UsbInterface { //接口
    //规定接口的相关方法,老师规定的.即规范...
    public void start();
    public void stop();
}

📷Camera 实现 UsbInterface

public class Camera implements UsbInterface{//实现接口,就是把接口方法实现
    @Override
    public void start() {
        System.out.println("相机开始工作...");
    }
    @Override
    public void stop() {
        System.out.println("相机停止工作....");
    }
}

📱Phone实现 UsbInterface

//Phone 类 实现 UsbInterface
//解读 1. 即 Phone 类需要实现 UsbInterface 接口 规定/声明的方法
public class Phone implements UsbInterface {
    @Override
    public void start() {
        System.out.println("手机开始工作...");
    }
    @Override
    public void stop() {
        System.out.println("手机停止工作.....");
    }
}

💻Computer 类:

package com.hj.第十章面向对象编程下.接口;

/**
 * @author Baridhu
 */
public class Computer {
    //编写一个方法,计算机工作
    public void work(UsbInterface Usb) {
        //通过接口,来调用方法
        Usb.start();
        Usb.stop();
    }

}

🍀9.3 基本介绍

➢ 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来

📙语法:

interface接口名{
	属性
	抽象方法
}
class 类名 implements 接口{
	自己属性;
	自己方法;
	必须实现的接口的抽象方法
}

💡小结:

接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【jdk7.0】。接口体现了程序设计的多态和高内聚低偶合的设计思想。

🔥特别说明:

Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
在这里插入图片描述

🍀9.4 深入讨论

对初学者讲.理解接口的概念不算太难,难的是不知道什么时候使用接口,下面我例举几个应用场景:

  1. 说现在要制造战斗机,武装直升机.专家只需把飞机需要的功能/规格定下来即可,然后让别的人具体实现就可。

  2. 现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。(1.项目质量2.项目进度3.项目奖)参加工作
    在这里插入图片描述

👍举例实现
DBInterface接口

package com.hj.第十章面向对象编程下.接口.简单案例;

/**
 * @author Baridhu
 */
public interface DBInterface { //项目经理
    public void connect();//连接方法
    public void close();//关闭连接
}

MysqlDB实现DBInterface

package com.hj.第十章面向对象编程下.接口.简单案例;

/**
 * @author Baridhu
 */
public class MysqlDB implements DBInterface {
    @Override
    public void connect() {
        System.out.println("连接 mysql");
    }
    @Override
    public void close() {
        System.out.println("关闭 mysql");
    }
}

OracleDB 实现 DBInterface

//B 程序员连接 Oracle
public class OracleDB implements DBInterface{
    @Override
    public void connect() {
        System.out.println("连接 oracle");
    }
    @Override
    public void close() {
        System.out.println("关闭 oracle");
    }
}

实现类:

public class Interface03 {
    public static void main(String[] args) {
        MysqlDB mysqlDB = new MysqlDB();
        t(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        t(oracleDB);
    }
    public static void t(DBInterface db) {
        db.connect();
        db.close();
    }
}

🍀9.5 注意事项和细节

  1. 接口不能被实例化
  2. 接口中所有的方法是public方法,接口中抽象方法,可以不用abstract修饰
    图示:
    在这里插入图片描述
  3. 一个普通类实现接口,就必须将该接口的所有方法都实现。
  4. 抽象类实现接口,可以不用实现接口的方法。
//4.抽象类去实现接口时,可以不实现接口的抽象方法
interface IA {
    void say();//修饰符 public protected 默认 private
    void hi();
}
abstract class Tiger implements IA {
}
  1. 一个类同时可以实现多个接口
interface IB {
    //接口中的属性,只能是 final 的,而且是 public static final 修饰符
    int n1 = 10; //等价 public static final int n1 = 10;
    void hi();
}
interface IC {
    void say();
}
//一个类同时可以实现多个接口
class Pig implements IB,IC {
    @Override
    public void hi() {
    }
    @Override
    public void say() {
    }
}
  1. 接口中的属性,只能是final的,而且是 public static final修饰符。比如:int a=1;实际上是 public static final int a=1;(必须初始化)
interface IB {
    //接口中的属性,只能是 final 的,而且是 public static final 修饰符
    int n1 = 10; //等价 public static final int n1 = 10;
    void hi();
}
  1. 接口中属性的访问形式:接口名.属性名
public class InterfaceDetail02 {
    public static void main(String[] args) {
        //接口中的属性,是 public static final
        System.out.println(IB.n1);//说明 n1 就是 static
        //IB.n1 = 30; 说明 n1 是 final
    }
}
interface IB {
    //接口中的属性,只能是 final 的,而且是 public static final 修饰符
    int n1 = 10; //等价 public static final int n1 = 10;
    void hi();
}
  1. 接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C{}
  1. 接口的修饰符只能是public默认这点和类的修饰符是一样的
public interface UsbInterface{}
interface IE{}

🍀9.6 课堂练习

T1:判断下面代码输出

public interface A{
    int a=23;
}
class B implements A{}
public class C {
    public static void main(String[] args) {
        B b = new B();
        System.out.println(b.a);
        System.out.println(A.a);
        System.out.println(B.a);

    }
}

答案:
在这里插入图片描述

🍀9.7 继承类和接口的区别

大家看到现在,可能会对实现接口和继承类比较迷茫了,网上也有很多帖子在讨论这个问题,那么他们究竟有什么区别呢?

🚀比如说我们现在有一个小猴,它是继承自上面的老猴的(红色箭头),但是它想要学会飞行和游泳,这时它就需要像小鸟和小鱼去学习怎样去飞行和游泳,这是我们所说的实现,学习就是去实现(灰色箭头)。
在这里插入图片描述

💡代码实现:

public class ExtendsVsInterface {
    public static void main(String[] args) {
        LittleMonkey wuKong = new LittleMonkey("悟空");
        wuKong.climbing();
        wuKong.swimming();
        wuKong.flying();
    }
}
//猴子
class Monkey {
    private String name;
    public Monkey(String name) {
        this.name = name;
    }
    public void climbing() {
        System.out.println(name + " 会爬树...");
    }
    public String getName() {
        return name;
    }
}
//接口
interface Fishable {
    void swimming();
}
interface Birdable {
    void flying();
}
    //继承
//小结: 当子类继承了父类,就自动的拥有父类的功能
// 如果子类需要扩展功能,可以通过实现接口的方式扩展. 
// 可以理解 实现接口 是 对 java 单继承机制的一种补充.

class LittleMonkey extends Monkey implements Fishable,Birdable {
    public LittleMonkey(String name) {
        super(name);
    }
    @Override
    public void swimming() {
        System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
    }
    @Override
    public void flying() {
        System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
    }
}

💡再次说明:
➢接口和继承解决的问题不同

继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活

➢接口比继承更加灵活

接口比继承更加灵活,继承是满足is - a的关系(什么是一个什么),而接口只需满足 like - a(什么像一个什么)的关系。

➢接口在一定程度上实现代码解耦[即:接口规范性+动态绑定机制]

🍀9.8 接口的多态特性

➢多态参数(前面案例体现)
在前面的Usb接口案例,UsbInterface usb,既可以接收手机对象,又可以接收相机对象,就体现了接口多态(接口引用可以指向实现了接口的类的对象)

public class InterfacePolyParameter {
    public static void main(String[] args) {
        //接口的多态体现
        //接口类型的变量 if01 可以指向 实现了 IF 接口类的对象实例
        IF if01 = new Monster();
        if01 = new Car();
        //继承体现的多态
        //父类类型的变量 a 可以指向 继承 AAA 的子类的对象实例
        AAA a = new BBB();
        a = new CCC();
    }
}
interface IF {}
class Monster implements IF{}
class Car implements IF{}
class AAA {
}
class BBB extends AAA {}
class CCC extends AAA {}

➢多态数组
演示一个案例:
给Usb数组中,存放 Phone 和相机对象,Phone类还有一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,还需要调用Phone特有方法call.

public class InterfacePolyArr {
    public static void main(String[] args) {
        //多态数组 -> 接口类型数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
        /*
        给 Usb 数组中,存放 Phone 和 相机对象,Phone 类还有一个特有的方法 call(),
        请遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外,
        还需要调用 Phone 特有方法 call
        */
        for(int i = 0; i < usbs.length; i++) {
            usbs[i].work();//动态绑定.. //和前面一样,我们仍然需要进行类型的向下转型
            if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
                ((Phone_) usbs[i]).call();
            }
        }
    }
}
interface Usb{
    void work();
}
class Phone_ implements Usb {
    public void call() {
        System.out.println("手机可以打电话...");
    }
    @Override
    public void work() {
        System.out.println("手机工作中...");
    }
}
class Camera_ implements Usb {
    @Override
    public void work() {
        System.out.println("相机工作中...");
    }
}

➢ 接口存在多态传递现象

/**
 * 演示多态传递现象
 */
public class InterfacePolyPass {
    public static void main(String[] args) {
        //接口类型的变量可以指向,实现了该接口的类的对象实例
        IG ig = new Teacher();
        //如果 IG 继承了 IH 接口,而 Teacher 类实现了 IG 接口
        //那么,实际上就相当于 Teacher 类也实现了 IH 接口. //这就是所谓的 接口多态传递现象.
        IH ih = new Teacher();
    }
}
interface IH {
    void hi();
}
interface IG extends IH{ }
class Teacher implements IG {
    @Override
    public void hi() {
    }
}

📚10. 内部类(非常重要)

➢如果定义类在局部位置(方法中/代码块) :(1) 局部内部类 (2) 匿名内部类
➢定义在成员位置 (1) 成员内部类 (2) 静态内部类

🍀10.1 基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
⭐注意:
内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类。

💡思考:
类的五大成员是哪些?(面试题)
[属性、方法、构造器、代码块、内部类]

🍀10.2 基本语法

class Outer{/外部类
	class Inner{//内部类
	}
}
class Other{//外部其他类
}

🍀10.3 快速入门案例

public class InnerClass01 { //外部其他类
    public static void main(String[] args) {
    }
}
class Outer { //外部类
    private int n1 = 100;//属性
    public Outer(int n1) {//构造器
        this.n1 = n1;
    }
    public void m1() {//方法
        System.out.println("m1()");
    }
    {//代码块
        System.out.println("代码块...");
    }
    class Inner { //内部类, 在 Outer 类的内部
    }
}

🍀10.4 内部类的分类

➢定义在外部类局部位置上(比如方法内)
① 局部内部类(有类名)
② 匿名内部类(没有类名,重点!!!)

➢定义在外部类的成员位置上:
① 成员内部类(没用static修饰)
② 静态内部类(使用static修饰)

🍀10.5 局部内部类的使用

💡说明:

局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

可以直接访问外部类的所有成员,包含私有的

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法
        //局部内部类是定义在外部类的局部位置,通常在方法
        class Inner02 {
            //可以直接访问外部类的所有成员方法,包括私有的
            public void f1() {
                System.out.println("n1=" + n1);
                m2();
            }
        }
    }
}

不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法
        //1. 局部内部类是定义在外部类的局部位置,通常在方法
        //3. 不能添加访问修饰符,但是可以使用final修饰,因为局部变量也可以使用final
        final class Inner02 {
            //2. 可以直接访问外部类的所有成员方法,包括私有的
            public void f1() {
                System.out.println("n1=" + n1);
                m2();
            }
        }
    }
}

💡注:将局部内部类声明为final后不能再被继承

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法
        //1. 局部内部类是定义在外部类的局部位置,通常在方法
        //3. 不能添加访问修饰符,但是可以使用final修饰,因为局部变量也可以使用final
        class Inner02 {
            //2. 可以直接访问外部类的所有成员方法,包括私有的
            public void f1() {
                System.out.println("n1=" + n1);
                m2();
            }
        }
        
        class Inner03 extends Inner02 {
            
        }
    }
}

在这里插入图片描述

③ 作用域:仅仅在定义它的方法代码块中。

局部内部类Inner02的作用范围仅在方法m1()中,局部内部类也可在代码块内定义。

局部内部类—访问---->外部类的成员[访问方式:直接访问]

final class Inner02 {
            // 可以直接访问外部类的所有成员方法,包括私有的
            public void f1() {
                System.out.println("n1=" + n1);
                m2();
            }
        }

外部类—访问---->局部内部类的成员

访问方式:创建对象,再访问(注意:必须在作用域内)
⭐记住:
(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类

public class LocalInnerClass {//
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
    }
}

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法
        //1. 局部内部类是定义在外部类的局部位置,通常在方法
        //3. 不能添加访问修饰符,但是可以使用final修饰,因为局部变量也可以使用final
        //4. 作用域:仅仅在定义它的方法或代码块中。
        final class Inner02 {
            //2. 可以直接访问外部类的所有成员方法,包括私有的
            public void f1() {
                System.out.println("n1=" + n1);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建 Inner02 对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

💻运行结果:

n1=100

外部其他类—不能访问----->局部内部类(因为局部内部类地位是一个局部变量

⑦如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问:
System.out.println( "外部类的n2=" + 外部类名.this.n2 );

💻如果外部类和局部内部类的成员重名时,默认遵循就近原则

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法      
        final class Inner02 {
            private int n1 = 800;
            public void f1() {
                //7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则
                System.out.println("n1=" + n1);
                m2();
            }
        }
    }
}

💻运行结果:

n1=800

💻如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

public class LocalInnerClass {//
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
    }
}

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法
        final class Inner02 {
            private int n1 = 800;
            public void f1() {
                //7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
                // 使用 外部类名.this.成员)去访问
                // Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1, Outer02.this 就是哪个对象
                System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建 Inner02 对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

💡注释:

Outer02.this本质就是外部类的对象, 即哪个对象调用了m1, Outer02.this就是哪个对象

Outer02 outer02 = new Outer02();
outer02.m1();

此时outer02调用了方法m1,所以Outer02.this是指向outer02这个对象。

public class LocalInnerClass {//
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("Outer02.this hashcode=" + outer02);
    }
}

class Outer02 {//外部类
    private int n1 = 100;
    private void m2(){}//私有方法
    public void m1(){//方法
        final class Inner02 {
            private int n1 = 800;
            public void f1() {
                //7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
                // 使用 外部类名.this.成员)去访问
                // Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1, Outer02.this就是哪个对象
                System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode=" + Outer02.this);
                m2();
            }
        }
    }
}

💻运行结果:

n1=800 外部类的 n1=100
Outer02.this hashcode=com.hj.第十章面向对象编程下.内部类.Outer02@1b6d3586
Outer02.this hashcode=com.hj.第十章面向对象编程下.内部类.Outer02@1b6d3586

可以看到是同一个对象。

🍀10.6 匿名内部类的使用(重要!!!)

①本质是类
②内部类
③该类没有名字
④同时还是一个对象

🚀说明:

匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

🔥1. 匿名内部类的基本语法

new 类或接口(参数列表){
		类体
};

① 基于接口的匿名内部类:
📒分析:
1.需求: 想使用 IA 接口,并创建对象
2.传统方式,是写一个类,实现该接口,并创建对象

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}
class Outer04 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        //基于接口的匿名内部类
        //解读
        //1.需求: 想使用 IA 接口,并创建对象
        //2.传统方式,是写一个类,实现该接口,并创建对象        
        IA tiger=new Tiger();
        tiger.cry();
    }
}
interface IA {//接口
    public void cry();
}
class Tiger implements IA {
    @Override
    public void cry() {
        System.out.println("老虎叫唤...");
    }
}

💻运行结果:

老虎叫唤...

🐶再弄一个Dog类

class Dog implements IA {
    @Override
    public void cry() {
        System.out.println("狗狗叫唤...");
    }
}

3.需求是 Tiger/Dog 类只是使用一次,后面再不使用
4. 可以使用匿名内部类来简化开发

这时老虎类和小狗类我都不要了

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}
class Outer04 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        //基于接口的匿名内部类
        //解读
        //1.需求: 想使用 IA 接口,并创建对象
        //2.传统方式,是写一个类,实现该接口,并创建对象
        //3.老韩需求是 Tiger/Dog 类只是使用一次,后面再不使用
        //4. 可以使用匿名内部类来简化开发
        IA tiger = new IA(){
          @Override
          public void cry(){
              System.out.println("老虎叫唤...");
          }
        };
    }
}
interface IA {//接口
    public void cry();
}

这时我们就要分析tiger对象的编译类型和运行类型。

大家告诉我:
tiger 的编译类型是什么?
tiger 的运行类型是什么?

❤答案:
tiger 的编译类型是IA
tiger 的运行类型是匿名内部类 Outer04$1

为什么?

我们看底层 会分配 类名 Outer04$1
class Outer04$1 implements IA {
	@Override
	public void cry() {
		System.out.println("老虎叫唤...");
	}
}

怎么获得匿名内部类的名称呢?
System.out.println("tiger 的运行类型=" + tiger.getClass());

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}
class Outer04 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        //基于接口的匿名内部类
        //解读
        //1.需求: 想使用 IA 接口,并创建对象
        //2.传统方式,是写一个类,实现该接口,并创建对象
        //3.老韩需求是 Tiger/Dog 类只是使用一次,后面再不使用
        //4. 可以使用匿名内部类来简化开发
        //5. tiger 的编译类型 ? IA
		//6. tiger 的运行类型 ? 就是匿名内部类 Outer04$1
        /*
            我们看底层 会分配 类名 Outer04$1
            class Outer04$1 implements IA {
            @Override
            public void cry() {
            System.out.println("老虎叫唤...");
            }
            }
          */
        //7. jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址
		// 返回给 tiger
        IA tiger = new IA(){
          @Override
          public void cry(){
              System.out.println("老虎叫唤...");
          }
        };
        System.out.println("tiger 的运行类型=" + tiger.getClass());
        tiger.cry();
    }
}
interface IA {//接口
    public void cry();
}

💻运行结果:

tiger 的运行类型=class com.hj.第十章面向对象编程下.内部类.匿名内部类.Outer04$1
老虎叫唤...

❤说明:

jdk 底层在创建匿名内部类 Outer04$1,马上就创建了 Outer04$1 实例,并且把地址 返回给 tiger

5.匿名内部类使用一次,就不能再使用
❤说明:

匿名内部类只能使用一次,但是对象还是可以一直使用的,比如说上面代码中的tiger对象它依旧可以继续使用。

tiger.cry();
tiger.cry();
tiger.cry();

💻运行结果:

老虎叫唤...
老虎叫唤...
老虎叫唤...

⭐总结:

通过以上的分析我们可以看到,如果我们想基于接口去创建一个类但是这个类我们只想用一次,当我们使用传统方式去创建类的话会非常的麻烦。所以Java开发者设计了匿名内部类来解决这样的问题。

②基于类的匿名内部类:

⭐分析

1. father 编译类型 Father
2. father 运行类型 Outer04$2
3. 底层会创建匿名内部类

 class Outer04$2 extends Father{
     @Override
     public void test() {
         System.out.println("匿名内部类重写了 test 方法");
     }
 }

4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
5. 注意(“jack”) 参数列表会传递给 构造器

public class AnonymousInnerClass01 {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.method();
    }
}
class Outer { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        //演示基于类的匿名内部类
        //分析
        //1. father 编译类型 Father
        //2. father 运行类型 Outer04$2
        //3. 底层会创建匿名内部类
            /*
            class Outer04$2 extends Father{
                @Override
                public void test() {
                    System.out.println("匿名内部类重写了 test 方法");
                }
            }
            */
        //4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
        //5. 注意("jack") 参数列表会传递给 构造器
        Father father = new Father("jack"){
            @Override
            public void test() {
                System.out.println("匿名内部类重写了 test 方法");
            }
        };
        System.out.println("father 对象的运行类型=" + father.getClass());//Outer04$2
        father.test();
    }
}

class Father {//类
    public Father(String name) {//构造器
        System.out.println("接收到 name=" + name);
    }
    public void test() {//方法
    }
}

💻运行结果:

接收到 name=jack
father 对象的运行类型=class com.hj.第十章面向对象编程下.内部类.匿名内部类.Outer$1
匿名内部类重写了 test 方法

③基于类的匿名内部类:
分析参考上面

public class AnonymousInnerClass02 {
    public static void main(String[] args) {
        Outer01 outer = new Outer01();
        outer.method();
    }
}
class Outer01 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        //基于抽象类的匿名内部类
        Animal animal = new Animal(){
            @Override
            void eat() {
                System.out.println("小狗吃骨头...");
            }
        };
        animal.eat();
    }
}
abstract class Animal { //抽象类
    abstract void eat();
}

🔥2. 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。

匿名内部类是一个类的定义

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
//外部其他类---不能访问----->匿名内部类
        System.out.println("main outer05 hashcode=" + outer05);
    }
}
class Outer05 {
    private int n1 = 99;
    public void f1() {
        //创建一个基于类的匿名内部类
        //不能添加访问修饰符,因为它的地位就是一个局部变量
        //作用域 : 仅仅在定义它的方法或代码块中
        Person p = new Person(){
            private int n1 = 88;
            @Override
            public void hi() {
                //可以直接访问外部类的所有成员,包含私有的
                //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
                //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
                System.out.println("匿名内部类重写了 hi 方法 n1=" + n1 +
                        " 外部内的 n1=" + Outer05.this.n1 );
                //Outer05.this 就是调用 f1 的 对象
                System.out.println("Outer05.this hashcode=" + Outer05.this);
            }
        };
        p.hi();//动态绑定, 运行类型是 Outer05$1
    }
}
class Person {//类
    public void hi() {
        System.out.println("Person hi()");
    }
    public void ok(String str) {
        System.out.println("Person ok() " + str);
    }
}
//抽象类/接口...

匿名内部类它本身也是一个对象

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
//外部其他类---不能访问----->匿名内部类
        System.out.println("main outer05 hashcode=" + outer05);
    }
}

class Outer05 {
    private int n1 = 99;

    public void f1() {
        //创建一个基于类的匿名内部类
        //不能添加访问修饰符,因为它的地位就是一个局部变量
        //作用域 : 仅仅在定义它的方法或代码块中
        Person p = new Person() {
            private int n1 = 88;
            @Override
            public void hi() {
	        //也可以直接调用, 匿名内部类本身也是返回对象
	        // class 匿名内部类 extends Person {}
	        new Person() {
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi 方法,哈哈...");
            }

            @Override
            public void ok(String str) {
                super.ok(str);
                System.out.println("Hi!Jack!");
            }
        }.ok("jack");
    }
}

class Person {//类

    public void hi() {
        System.out.println("Person hi()");
    }

    public void ok(String str) {
        System.out.println("Person ok() " + str);
    }
}
//抽象类/接口...

💻运行结果:

Person ok() jack
Hi!Jack!

下面代码调用匿名内部类的ok方法

 Person p = new Person() {}.ok("jack");
@Override
public void ok(String str) {
    super.ok(str);
    System.out.println("Hi!Jack!");
}

通过super.ok(str);传给Person类,然后输出Hi!Jack!

🔥3.可以直接访问外部类的所有成员,包含私有的(前面有写)

🔥4. 不能添加访问修饰符,因为它的地位就是一个局部变量。

🔥5. 作用域:仅仅在定义它的方法或代码块中。

🔥6. 匿名内部类—访问---->外部类成员[访问方式:直接访问]

🔥7. 外部其他类—不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)

🔥8. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

🍀10.7 匿名内部类的最佳实践

1. 当做实参直接传递,简洁高效。

public class InnerClassExercise01 {
    public static void main(String[] args) {
        //当做实参直接传递,简洁高效。
        f1(new IL() {
            @Override
            public void show() {
                System.out.println("这是一副名画 ...");
            }
        });
    }
    //静态方法
    public static void f1(IL il){
        il.show();
    }
}
//接口
interface IL {
    void show();
}

💻运行结果:

这是一副名画 ...

⭐说明:

il.show();

调用了匿名内部类的show方法

2. 若用传统方式去实现

public class InnerClassExercise01 {
    public static void main(String[] args) {
		//传统方法
        f1(new Picture());
    }

    //静态方法,形参是接口类型
    public static void f1(IL il) {
        il.show();
    }
}

//接口
interface IL {
    void show();
}

//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
    @Override
    public void show() {
        System.out.println("这是一副名画 XX...");
    }
}

💻运行结果:

这是一副名画 XX...

🍀10.8 匿名内部类课堂练习

①有一个铃声接口Bell,里面有个ring方法。
②有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型
③测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了。
④再传入另一个匿名内部类(对象),打印:小伙伴上课了

public class InnerClassExercise02 {
    public static void main(String[] args) {
        /*
        1.有一个铃声接口 Bell,里面有个 ring 方法。(右图)
        2.有一个手机类 Cellphone,具有闹钟功能 alarmClock,参数是 Bell 类型(右图)
        3.测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
        4.再传入另一个匿名内部类(对象),打印:小伙伴上课了
        */
        CellPhone cellPhone = new CellPhone();
        //解读
        //1. 传递的是实现了 Bell 接口的匿名内部类 InnerClassExercise02$1
        //2. 重写了 ring
        //3. Bell bell = new Bell() {
        // @Override
        // public void ring() {
        // System.out.println("懒猪起床了");
        // }
        // }
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        });
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了");
            }
        });
        
    }
}
interface Bell{ //接口
    void ring();//方法
}
class CellPhone{//类
    public void alarmClock(Bell bell){//形参是 Bell 接口类型
        System.out.println(bell.getClass());
        bell.ring();//动态绑定
    }
}

💻运行结果:

class com.hj.第十章面向对象编程下.内部类.匿名内部类.练习.InnerClassExercise02$1
懒猪起床了
class com.hj.第十章面向对象编程下.内部类.匿名内部类.练习.InnerClassExercise02$2
小伙伴上课了

⭐总结:

匿名内部类涉及的内容:(1)继承,(2)多态,(3)动态绑定,(4)内部类.

🍀10.9 成员内部类的使用

♗说明:

成员内部类是定义在外部类的成员位置,并且没有static修饰。

①可以直接访问外部类的所有成员,包含私有的

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();
    }
}
class Outer08 { //外部类
    private int n1 = 10;
    public String name = "张三";
    //1.注意: 成员内部类,是定义在外部内的成员位置上
    public class Inner08 {//成员内部类
        public void say() {
            //可以直接访问外部类的所有成员,包含私有的
            System.out.println("n1 = " + n1 + " name = " + name );
        }
    }
    //写方法
    public void t1(){
        //使用成员内部类
        //创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08=new Inner08();
        inner08.say();
    }
}

💻运行结果:

n1 = 10 name = 张三

②可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。

③作用域 :
和外部类的其他成员一样,作用域为整个类体,比如前面案例:在外部类的成员方法中创建成员内部类对象,再调用方法.

④成员内部类—访问---->外部类成员(比如:属性)[访问方式:直接访问]

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();
    }
}
class Outer08 { //外部类
    private int n1 = 10;
    public String name = "张三";
    private void hi() {
        System.out.println("hi()方法...");
    }
    //1.注意: 成员内部类,是定义在外部内的成员位置上
    //2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    public class Inner08 {//成员内部类
        public void say() {
            //可以直接访问外部类的所有成员,包含私有的
            System.out.println("n1 = " + n1 + " name = " + name );
            hi();
        }
    }
    public void t1(){
        Inner08 inner08=new Inner08();
        inner08.say();
    }
}

💻运行结果:

n1 = 10 name = 张三
hi()方法...

⑤外部类—访问------>成员内部类(说明)访问方式:创建对象,再访问

public void t1(){
    //使用成员内部类
    //创建成员内部类的对象,然后使用相关的方法
    Inner08 inner08=new Inner08();
    inner08.say();
}

⑥外部其他类—访问---->成员内部类

 // 第一种方式
 // outer08.new Inner08(); 相当于把 new Inner08()当做是 outer08 成员
 // 这就是一个语法,不要特别的纠结.
 Outer08.Inner08 inner08 = outer08.new Inner08();
 inner08.say();
 // 第二方式 在外部类中,编写一个方法,可以返回 Inner08 对象
 Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
 inner08Instance.say();
class Outer08 { //外部类
    private int n1 = 10;
    public String name = "张三";
    private void hi() {
        System.out.println("hi()方法...");
    }
    //1.注意: 成员内部类,是定义在外部内的成员位置上
    //2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    public class Inner08 {//成员内部类
        public void say() {
            //可以直接访问外部类的所有成员,包含私有的
            System.out.println("n1 = " + n1 + " name = " + name );
            hi();
        }
    }
    public void t1(){
        //使用成员内部类
        //创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08=new Inner08();
        inner08.say();
    }
    //方法,返回一个 Inner08 实例
    public Inner08 getInner08Instance(){
        return new Inner08();
    }
}
//第三种方式:可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println(" 外部类的 n1=" + Outer08.this.n1);

🍀10.10 静态内部类的使用

♗说明:

静态内部类是定义在外部类的成员位置,并且有static修饰

①可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

class Outer10{
    private int n1=10;
    private static String name = "张三";
    //Inner10是静态内部类
    //1. 放在外部类的成员位置
    //2.使用 static 修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    static class Inner10{
        public void say(){
            System.out.println(name);
        }
    }
}

②可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。

③作用域:同其他的成员,为整个类体

public class StaticInnerclass {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();
    }
}
class Outer10{
    private int n1=10;
    private static String name = "张三";
    //Inner10是静态内部类
    //1. 放在外部类的成员位置
    //2.使用 static 修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    //4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
	//5. 作用域 :同其他的成员,为整个类体
    static class Inner10{
        public void say(){
            System.out.println(name);
        }
    }
    public void m1() { 
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
}

④静态内部类—访问---->外部类(比如:静态属性)[访问方式:直接访问所有静态成员]

class Outer10{
    private int n1=10;
    private static String name = "张三";
    //Inner10是静态内部类
    //1. 放在外部类的成员位置
    //2.使用 static 修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    static class Inner10{
        public void say(){
            System.out.println(name);
        }
    }
}

⑤外部类—访问------>静态内部类访问方式:创建对象,再访问

    public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
        Inner10 inner10 = new Inner10();
        inner10.say();
    }

⑥外部其他类—访问----->静态内部类

public class StaticInnerclass {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();
        //外部其他类 使用静态内部类
        //方式 1
        //因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
        //方式 2
        //编写一个方法,可以返回静态内部类的对象实例.
        Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("============");
        inner101.say();
    }
}
class Outer10{
    private int n1=10;
    private static String name = "张三";

    public Inner10 getInner10() {
        return new Inner10();
    }

    static class Inner10{
        public void say(){
            System.out.println(name);
        }
    }
    public void m1() { 
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
}

⑦如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问

class Outer10{
    private int n1=10;
    private static String name = "张三";
    static class Inner10{
        private static String name = "Baridhu";
        public void say() {
            //如果外部类和静态内部类的成员重名时,静态内部类访问的时,
            //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
            System.out.println(name + " 外部类 name= " + Outer10.name);
        }
    }

💻运行结果:

Baridhu 外部类 name= 张三

🍀10.11 测试题

视频讲解:韩顺平Java
判断下面代码的输出值

public class Test {
    public Test() {//构造器
        Inner s1 = new Inner();
        s1.a = 10;
        Inner s2 = new Inner();
        System.out.println(s2.a);
    }
    class Inner { 
        public int a = 5;
    }
    public static void main(String[] args) {
        Test t = new Test();
        Inner r = t.new Inner();
        System.out.println(r.a);
    }
}

结果:

5
5
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值