• 8. Java面向对象编程(三)


    1. 类变量和类方法

    1.1 为什么需要类变量 / 静态变量 / static变量

    看一个问题:有一群小孩在玩堆雪人,不时有新的小孩加入,问:如何知道现在共有多少人在玩?编写程序解决。

    public class ChildGame {
        public static void main(String[] args) {    
            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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出结果:

    白骨精 加入了游戏..
    狐狸精 加入了游戏..
    老鼠精 加入了游戏..
    共有3个小孩加入了游戏...
    
    • 1
    • 2
    • 3
    • 4

    由输出结果看,上面代码虽然按照预期解决了问题,但是存在以下缺陷:

    • count是一个独立于对象的变量,很尴尬。
    • 以后访问 count 很麻烦,没有使用到OOP(面向对象)。

    如果设计一个 int count 表示总人数,我们在创建一个小孩时,就把 count 加 1,并且count 是所有对象共享的就 ok 了,这就要用类变量 / 静态变量来解决。
    类变量 / 静态变量 最大的特点就是会被类的所有的对象实例共享。
    引入类变量 / 静态变量后,上面的问题可以这样解决:

    public class ChildGame {
        public static void main(String[] args) {
            Child child1 = new Child("白骨精");
            child1.join();
            child1.count++;
    
            Child child2 = new Child("狐狸精");
            child2.join();
            child2.count++;
    
            Child child3 = new Child("老鼠精");
            child3.join();
            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;
        public static int count = 0;
        public Child(String name) {
            this.name = name;
        }
        public void join() {
            System.out.println(name + " 加入了游戏..");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    输出结果:

    白骨精 加入了游戏..
    狐狸精 加入了游戏..
    老鼠精 加入了游戏..
    共有3个小孩加入了游戏...
    child1.count=3
    child2.count=3
    child3.count=3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.2 类变量的内存布局

    在这里插入图片描述
    在JDK8之前,类变量的内存布局如红色虚线所示,JDK8之后如绿色实线所示(大概是这样,可能不是很准确)。

    不管类变量在哪里,可以确定的是:
    (1)类变量可以被同一个类的所有对象共享。
    (2)类变量在类加载的时候就生成了。

    1.3 类变量基本介绍

    类变量 / 静态(static)变量 / 静态属性: 是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,任何一个该类的对象去修改它时,修改的也是同一个变量。

    定义语法:
    (1)访问修饰符 static 数据类型 变量名;(推荐)
    (2)static 访问修饰符 数据类型 变量名;

    访问:
    (1)类名 . 类变量名(推荐)
        类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问。
    (2)对象名 . 类变量名

    静态变量访问修饰符的访问权限、范围与普通属性相同。

    public class Test {
    	public static void main(String[] args) {
    		//通过类名.类变量名访问
    		System.out.println(A.name);
    		A a = new A();
    		//通过对象名.类变量名访问
    		System.out.println("a.name=" + a.name);
    	}
    }
    class A {
    	//类变量的访问,必须遵守相关的访问权限
    	//若用private修饰,其他类中就不能直接访问了
    	public static String name = "marry";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.4 类变量使用细节

    • 何时需要用类变量:当需要让某个类的所有对象共享一个变量时,就可以考虑使用类变量(静态变量)。比如:定义学生类,统计所有学生共交多少钱。Student(name,static fee)
    • 类变量与实例变量(普通属性)区别:类变量是该类的所有对象共享的,而实例变量是每个对象独享的,带有static的变量称为类变量或静态变量,否则称为实例变量 / 普通变量 / 非静态变量。
    • 类变量可以通过 “类名 . 类变量名” 或者 “对象名 . 类变量名” 来访问,但推荐前者。前提是满足访问修饰符的访问权限和范围。
    • 实例变量不能通过 “类名 . 类变量名” 的方式访问。
    • 类变量在类加载时就已经初始化,也就是说:只要类加载了,即使没有创建对象,也可以使用类变量。
    • 类变量的生命周期:随类的加载而开始,随类的消亡而销毁,

    1.5 类方法基本介绍

    类方法也叫静态方法。

    定义语法:
    (1)访问修饰符 static 返回类型 方法名(){ }(推荐)
    (2)static 访问修饰符 返回类型 方法名(){ }

    调用:
    (1)类名 . 类方法名(推荐)
    (2)对象名 . 类方法名
    前提:满足访问修饰符的访问权限和范围。

    【例1】学生学费总和。

    public class Test {
    	public static void main(String[] args) {
    		//创建2个学生对象,交学费
    		Stu tom = new Stu("tom");
    		Stu.payFee(100);//tom.payFee(100)也可以
    		Stu mary = new Stu("mary");
    		Stu.payFee(200);//mary.payFee(200)也可以
    		Stu.showFee();//当前总学费300
    	}
    }
    class Stu {
    	private String name;
    	private static double fee = 0;//静态变量,累积学费
    	public Stu(String name) {
    		this.name = name;
    	}
    	//当方法使用了 static 修饰后,该方法就是静态方法
    	//静态方法就可以访问静态属性/变量
    	public static void payFee(double fee) {
    		Stu.fee += fee;
    	}
    	public static void showFee() {
    		System.out.println("总学费有:" + Stu.fee);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 当方法不涉及与对象相关的成员时,可以设计成静态方法。这样,不创建对象就能调用某个方法。在实际开发中,会将通用的方法设计成静态方法,比如:打印一维数组,冒泡排序,完成某个计算任务等。
    • Java工具类,比如:Math类、Array类、Collection类中的方法,很多都是静态方法。
    public class Test {
    	public static void main(String[] args) {
    		System.out.println(MyTools.calSum(10, 30));
    		System.out.println(Math.sqrt(9));
    	}
    }
    class MyTools {
    	public static double calSum(double n1, double n2) {
    		return n1 + n2;
    	}
    	//可以写出很多这样的工具方法... 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.6 类方法的细节

    • 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区。
    • 类方法可以通过类名对象名调用;普通方法和对象有关,只能通过对象名调用。
    • 类方法中不允许使用和对象有关的关键字,比如 this 和 super。普通方法可以。
    • 在本类中,类方法(静态方法)可以直接访问静态成员(变量/方法);非静态方法可以直接访问非静态成员或静态成员;如果类方法(静态方法)要访问非静态成员,就要先创建对象再访问。
    • 在遵循访问权限的前提下,其他类中的方法(静态 / 非静态)都可以直接访问某个类的静态成员;其他类在创建非静态成员所在类的对象后,其中的方法才可以访问该非静态成员(其实就是初学时常用的那种先创建对象再访问成员)。
    • 构造器也是非静态方法,可以访问非静态和静态成员。
    • 静态方法可以被继承但不能被重写。
    class D {
    	private int n1 = 100;
    	private static int n2 = 200;
    	public void say() {
    	}
    	public static void hi() {
    		//静态方法中不能使用this或super
    		//System.out.println(this.n1);
    	}
    	//类方法(静态方法)只能访问静态成员
    	public static void hello() {
    		System.out.println(n2);
    		System.out.println(D.n2);
    		//System.out.println(this.n2);//错误
    		hi();//正确 
    		//say();//错误
    	}
    	//普通成员方法,可以访问访问静态成员和非静态成员
    	public void ok() {
    		//非静态成员
    		System.out.println(n1);
    		say();
    		//静态成员
    		System.out.println(n2);
    		hello();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    //如果类方法(静态方法)非要访问非静态成员,要先创建对象再访问
    class Person { 
    	private static int total = 3;
    	private int id = 5;
    
    	public static void printVar(){
    		System.out.println(total);
    		Person person = new Person(); 
    		System.out.println(person.id);
    	}
    }
    public class Test {
    	public static void main(String[] args) {
    		Person.printVar();//3 5
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    练习:

    【例1】看看下面代码有没有错误,如果有错误,就修改,看看输出什么?
    在这里插入图片描述

    class Person { 
    	private int id;
    	private static int total = 0;
    	public static int getTotalPerson() {
    		//id++;//错误, 静态方法不能访问非静态变量
    		return total;
    	}
    	//构造器也是非静态方法,可以访问非静态和静态成员
    	public Person() {
    		total++; 
    		id = total;
    	}
    }
    public class Test {
    	public static void main(String[] args) {
    		System.out.println(Person.getTotalPerson()); //0
    		Person p1 = new Person();
    		System.out.println(Person.getTotalPerson()); //1
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    【例2】下面代码有没有错误,如果有错误,就修改。修改后,total 等于多少?
    在这里插入图片描述

    class Person { 
    	private int id;
    	private static int total = 0;
    	public static void setTotalPerson(int total){
    		// this.total = total;//错误,static方法不能使用this
    		//不能使用this,又要区分局部变量total和属性total
    		//就可以直接用类名的形式访问
    		Person.total = total;
    	}
    	public Person() {
    		total++;
    		id = total;
    	}
    	public static void printTotal(){
    		System.out.println(total);
    	}
    }
    public class Test {
    	public static void main(String[] args) {
    		Person.setTotalPerson(3);
    		new Person(); 
    		Person.printTotal();//total的值是 4
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2. 理解 main 方法语法

    解释 main 方法的形式:public static void main(String[] args)

    • main 方法由虚拟机调用。
    • 访问权限必须是 public:Java 虚拟机(JVM)需要调用类的 main() 方法。
    • 方法必须是 static:Java 虚拟机在执行 main() 方法时不必创建对象。
    • 返回值是void:Java 虚拟机调用 main() 方法不需要返回值,能执行成功就执行,不能成功就退出或异常终止。
    • 该方法接收 String数组 类型的参数,该数组中保存 “执行Java命令时,传递给所运行的类的参数” 。
      在这里插入图片描述
      在这里插入图片描述
      若要在idea中传参数,按下面步骤操作:
      在这里插入图片描述
      在这里插入图片描述

    main()方法与普通static方法访问本类成员的规则一致:

    • main()方法是static方法,可以直接调用所在类的静态方法或静态属性。
    • main()方法不能直接访问该类中的非静态成员,必须创建该类的对象后,才能通过这个对象去访问类中的非静态成员。
    public class Test {
    	private static String name = "Smith";
    	private int n1 = 10000;
    	public static void hi() {
    		System.out.println("Test 的 hi 方法");
    	}
    	public void cry() {
    		System.out.println("Test 的 cry 方法");
    	}
    	public static void main(String[] args) {
    		System.out.println(name);
    		hi();
    		Test test = new Test();
    		System.out.println(test.n1);
    		test.cry();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出结果:

    Smith
    Test 的 hi 方法
    10000
    Test 的 cry 方法
    
    • 1
    • 2
    • 3
    • 4

    3. 代码块

    3.1 代码块基本介绍

    代码块是什么:

    • 代码化块又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
    • 代码块只有方法体,不是通过对象或类显式调用,而是加载类或创建对象时隐式调用。

    语法:

    [修饰符]{
    	代码
    };
    
    • 1
    • 2
    • 3
    • 修饰符可选,要写的话,也只能写 static。
    • 代码块分为两类,使用static修饰的称为静态代码块,没有static修饰的,称为普通代码块/非静态代码块。
    • 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
    • 最后的 “ ; ”号可以写上,也可以省略。

    【例】下面的三个构造器都有相同的语句,这样代码很啰嗦。可以把相同语句,放入一个代码块中。在创建对象时,不管调用哪个构造器,都会先调用代码块的内容

    public class Test {
    	public static void main(String[] args){
    		Movie movie = new Movie("你好,李焕英");
    		System.out.println("===============");
    		Movie movie2 = new Movie("唐探 3", 100, "陈思诚");
    	}
    }
    class Movie {
    	private String name;
    	private double price;
    	private String director;
    
    	{
    		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, String director) {
    		System.out.println("Movie(String name, double price, String director) 被调用...");
    		this.name = name;
    		this.price = price;
    		this.director = director;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出结果:

    电影屏幕打开...
    广告开始...
    电影正式开始...
    Movie(String name) 被调用...
    ===============
    电影屏幕打开...
    广告开始...
    电影正式开始...
    Movie(String name, double price, String director) 被调用...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.2 代码块细节问题(可以只看3.2.2)

    3.2.1 静态代码块和普通代码块

    • static 代码块也叫静态代码块,作用就是对类进行初始化。
      静态代码块在类加载的时候执行;
      无论为某个类创建多少个对象,该类都只会加载一次;
      所以,为某个类创建多个对象时,静态代码块只执行一次。

      public class Test {
      	public static void main(String[] args){
      		DD dd = new DD();
      		DD dd2 = new DD();
      	}
      }
      class DD {
      	static {//静态代码块
      		System.out.println("DD 的静态代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      输出结果:

      DD 的静态代码块被执行...
      
      • 1
    • 类什么时候被加载 ?
      创建对象实例时(new)

      public class Test {
      	public static void main(String[] args){
      		//1. 创建对象实例时
      		AA aa = new AA();
      	}
      }
      class AA {
      	static {//静态代码块
      		System.out.println("AA 的静态代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      输出结果:

      AA 的静态代码块被执行...
      
      • 1

      创建子类对象实例,父类也会被加载(先加载父类,再加载子类),实际上只要用到子类,就会加载父类。

      public class Test {
      	public static void main(String[] args){
      		//创建子类对象时,父类也会被加载。先加载父类,再加载子类
      		AA aa2 = new AA();
      	}
      }
      class BB {
      	static {//静态代码块
      		System.out.println("BB 的静态代码块被执行...");
      	}
      }
      class AA extends BB {
      	static {//静态代码块
      		System.out.println("AA 的静态代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      输出结果:

      BB 的静态代码块被执行...
      AA 的静态代码块被执行...
      
      • 1
      • 2

      使用类的静态成员时(静态属性 / 方法)
      【例1】

      public class Test {
      	public static void main(String[] args){
      		//使用类的静态成员时(静态属性,静态方法)
      		 System.out.println(Cat.n1);
      	}
      }
      class Cat{
      	public static int n1 = 999;//静态属性
      	static {//静态代码块
      		System.out.println("Cat 的静态代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      输出结果:

      Cat 的静态代码块被执行...
      999
      
      • 1
      • 2

      【例2】

      public class Test {
      	public static void main(String[] args){
      		//使用类的静态成员时(静态属性,静态方法)
      		 System.out.println(Cat.n1);
      	}
      }
      class Animal {
      	static {//静态代码块
      		System.out.println("Animal 的静态代码块被执行...");
      	}
      }
      class Cat extends Animal {
      	public static int n1 = 999;//静态属性
      	static {//静态代码块
      		System.out.println("Cat 的静态代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      输出结果:

      Animal 的静态代码块被执行...
      Cat 的静态代码块被执行...
      999
      
      • 1
      • 2
      • 3
    • 普通的代码块,在创建对象实例时,会被隐式地调用。每创建一次对象,就会调用一次代码块。
      对普通代码块的理解:
      (1) 相当于构造器的补充机制,可以做初始化操作。
      (2) 如果多个构造器中都有重复语句,可以抽取到代码块中,提高重用性。

      public class Test {
      	public static void main(String[] args){
      		 DD dd = new DD();
      		 DD dd2 = new DD();
      	}
      }
      class DD {
      	static {//静态代码块
      		System.out.println("DD 的静态代码块被执行...");
      	}
      	{//普通代码块
      		System.out.println("DD 的普通代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      输出结果:

      DD 的静态代码块被执行...
      DD 的普通代码块被执行...
      DD 的普通代码块被执行...
      
      • 1
      • 2
      • 3
    • 如果只是使用类的静态成员,普通代码块不会执行。
      (普通代码块被调用只与是否创建对象有关,与类加载无关。可以这样简单理解:普通代码块是构造器的补充,构造器被调用时,普通代码块才被调用)

      public class Test {
      	public static void main(String[] args){
      		 System.out.println(DD.n1);
      	}
      }
      class DD {
      	public static int n1 = 8888;
      	static {//静态代码块
      		System.out.println("DD 的静态代码块被执行...");
      	}
      	{//普通代码块
      		System.out.println("DD 的普通代码块被执行...");
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      输出结果:

      DD 的静态代码块被执行...
      8888
      
      • 1
      • 2
    • 小结:
      static代码块是在类加载时调用的,只会调用一次。(类加载的3种情况要记住)
      普通代码块是在创建对象时调用的,每创建一次对象,就调用一次普通代码块。

    3.2.2 创建对象时的调用顺序

    创建一个对象时,在一个类中的调用顺序是:

    ① 调用静态代码块和静态属性初始化(两者优先级相同,若有多个静态代码块 / 属性初始化,则按定义顺序调用)。

    ② 调用普通代码块和普通属性的初始化(两者优先级相同,若有多个普通代码块 / 属性初始化,则按定义顺序调用)。

    ③ 调用构造方法。

    public class Test {
    	public static void main(String[] args){
    		 A a = new A();
    	}
    }
    class A {
    	public A(){
    		System.out.println("构造器 被调用...");
    	}
    	//普通属性的显式初始化在创建对象时、构造器初始化之前,普通代码块的调用也是在创建对象时、构造器执行之前
    	//普通属性n2在前,所以先被初始化,于是getN2()被调用
    	//普通代码块在后,所以后被调用
    	private int n2 = getN2();
    
    	{ //普通代码块
    		System.out.println("普通代码块 被调用...");
    	}
    	public int getN2() {
    		System.out.println("getN2 被调用...");
    		return 100;
    	}
    	//静态变量在类加载时初始化,静态代码块也是在类加载时被调用
    	//但静态变量n1在前,所以先被初始化,于是getN1()被调用
    	//静态代码块在后,所以后被调用
    	private static int n1 = getN1();
    	static { //静态代码块
    		System.out.println("静态代码块 被调用...");
    	}
    	public static int getN1() {
    		System.out.println("getN1 被调用...");
    		return 100;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出结果:

    getN1 被调用...
    静态代码块 被调用...
    getN2 被调用...
    普通代码块 被调用...
    构造器 被调用...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    构造器的最前面其实隐含了 super 和调用普通代码块。静态相关的代码块、属性初始化,在类加载时,就执行完毕,因此优先于构造器和普通代码块执行。
    在这里插入图片描述
    输出结果:

    AAA 的普通代码块...
    AAA() 构造器被调用...
    BBB 的普通代码块...
    BBB() 构造器被调用...
    
    • 1
    • 2
    • 3
    • 4

    创建子类对象时(继承关系),静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
    ① 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    ② 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    ③ 父类的普通代码块和普通属性初始化(优先级一样, 按定义顺序执行)
    ④ 父类的构造方法
    ⑤ 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    ⑥ 子类的构造方法

    【例】
    (1)加载类,由于B02有父类A02:
     所以先加载父类A02:初始化静态属性(getVal01被调用)、调用静态代码块
     再加载子类B02:初始化静态属性(getVal03被调用)、调用静态代码块
    (2)创建B02对象
     进入B02的构造器 ➡ super()进入父类A02的构造器,在父类A02的构造器中:
      super()调用Object类的构造器;
      初始化普通属性(getVal02被调用)、调用普通代码块;
      执行A02构造器中的剩余语句。
     父类构造器执行完毕后,回到子类B02构造器,执行super()后面的语句:
      初始化普通属性(getVal04被调用)、调用普通代码块;
      执行B02构造器中的剩余语句。

    public class Test {
    	public static void main(String[] args){
    		 new B02();
    		//注:()序号为执行顺序
    	}
    }
    class A02 { //父类
    	private static int n1 = getVal01();
    	static {
    		System.out.println("A02 的一个静态代码块..");//(2)
    	}
    	{
    		System.out.println("A02 的第一个普通代码块..");//(5)
    	}
    	public int n3 = getVal02();//普通属性的初始化
    	public static int getVal01() {
    		System.out.println("getVal01");//(1)
    		return 10;
    	}
    	public int getVal02() {
    		System.out.println("getVal02");//(6)
    		return 10;
    	}
    	public A02() {//构造器
    		//下面注释的语句会执行但隐藏了
    		//super()
    		//初始化普通属性、调用普通代码块
    		System.out.println("A02 的构造器");//(7)
    	}
    }
    
    class B02 extends A02 { 
    	private static int n3 = getVal03();
    	static {
    		System.out.println("B02 的一个静态代码块..");//(4)
    	}
    	public int n5 = getVal04();
    
    	{
    		System.out.println("B02 的第一个普通代码块..");//(9)
    	}
    	public static int getVal03() {
    		System.out.println("getVal03");//(3)
    		return 10;
    	}
    	public int getVal04() {
    		System.out.println("getVal04");//(8)
    		return 10;
    	}
    	public B02() {//构造器
    		//下面注释的语句会执行但隐藏了
    		//super()
    		//初始化普通属性、调用普通代码块
    		System.out.println("B02 的构造器");//(10)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    输出结果:

    getVal01
    A02 的一个静态代码块..
    getVal03
    B02 的一个静态代码块..
    A02 的第一个普通代码块..
    getVal02
    A02 的构造器
    getVal04
    B02 的第一个普通代码块..
    B02 的构造器
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在本类中,静态代码块只能直接访问静态成员(静态属性 / 方法),若要访问非静态成员可以先创建对象*;普通代码块可以直接访问任意成员。(静态代码块很多用法与静态方法相同)
    【注】静态代码块在类加载的时候执行,那时候还没有创建对象、没有初始化非静态成员,所以要先创建对象才能访问非静态成员。

    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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.3 代码块练习

    【例】下面的代码输出什么?

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    分析:
    访问 Person.total 前先加载Person类:初始化静态变量、调用静态代码块(输出"in static block!")
    Person类加载完成后,输出Person.total(100)
    再次输出Person.total前,不再加载Person类,直接输出Person.total(100)

    输出结果:

    in static block!
    total = 100
    total = 100
    
    • 1
    • 2
    • 3

    4. 单例设计模式

    4.1 什么是设计模式

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

    4.2 单例模式

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

    单例模式有两种方式: (1)饿汉式 (2)懒汉式

    饿汉式和懒汉式单例模式的实现步骤:
    (1)构造器私有化,防止直接 new
    (2)类的内部创建对象
    (3)向外暴露一个静态的公共方法,getInstance
    (4)代码实现

    4.2.1 饿汉式单例模式

    public class Test {
    	public static void main(String[] args) {
    		System.out.println("n1="+GirlFriend.n1);
    
    		GirlFriend instance = GirlFriend.getInstance();
    		System.out.println(instance);
    		//因为类只加载一次,所以getInstance得到的仍是第一次创建对象前类加载时创建的对象
    		GirlFriend instance2 = GirlFriend.getInstance();
    		System.out.println(instance2);
    		System.out.println(instance == instance2);//true
    	}
    }
    class GirlFriend {
    	public static int n1 = 999;
    	private String name;
    	//为了能够在静态方法getInstance中,直接访问属性(返回gf对象)
    	//需要将其修饰为static
    	//类一加载,就初始化static属性,就创建对象(饿:还没等到要用就自己创建了)
    	private static GirlFriend gf = new GirlFriend("小红红");
    
    	//将构造器私有化,其他类中不能直接调用构造器,从而不能直接创建对象
    	private GirlFriend(String name) {
    		System.out.println("构造器被调用.");
    		this.name = name;
    	}
    	//在其他类中不创建对象就直接调用该方法,返回该类中创建的gf对象
    	//所以方法应该是static的
    	public static GirlFriend getInstance() {
    		return gf;
    	}
    	@Override
    	public String toString() {
    		return "GirlFriend{" +
    		"name='" + name + '\'' +
    		'}';
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    输出结果:

    构造器被调用.
    n1=999
    GirlFriend{name='小红红'}
    GirlFriend{name='小红红'}
    true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    单例模式中的对象通常都是重量级的对象,可能存在创建了但是没有使用的情况。
    若上面代码的GirlFriend类中有public static int n1 = 100,main方法中只有这样一条语句:System.out.println(GirlFriend.n1),则GirlFriend.n1会导致GirlFriend类加载,从而创建了该类的对象,但是之后却没有用到,造成资源浪费。
    于是有了懒汉式单例模式,只有使用时才会创建对象。

    4.2.2 懒汉式单例模式

    public class Test {
    	public static void main(String[] args) {
    		System.out.println("n1="+Cat.n1);
    
    		Cat instance = Cat.getInstance();
    		System.out.println(instance);
    		
    		Cat instance2 = Cat.getInstance();
    		System.out.println(instance2);
    		System.out.println(instance == instance2);//true
    	}
    }
    class Cat {
    	public static int n1 = 999;
    	private String name;
    	//保证静态方法getInstance能够直接访问该静态属性
    	private static Cat cat ; //默认是 null
    	//构造器私有化,保证其他类不能直接利用该构造器创建对象
    	
    	//4.懶汉式,只有当用户使用 getInstance 方法时,才返回cat对象(没有就创建), 
    	//当再次调用时,会返回上次创建的cat对象。从而保证了单例
    	private Cat(String name) {
    		System.out.println("构造器调用...");
    		this.name = name;
    	}
    	//在其他类中不创建对象就直接调用该方法,返回该类中创建的cat对象
    	//所以方法应该是static的
    	public static Cat getInstance() {
    		if(cat == null) {//如果还没有创建cat对象
    			cat = new Cat("小可爱");
    		}
    		return cat;
    	}
    	@Override
    	public String toString() {
    		return "Cat{" +
    		"name='" + name + '\'' +
    		'}';
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    输出结果:

    n1=999
    构造器调用...
    Cat{name='小可爱'}
    Cat{name='小可爱'}
    true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.2.3 饿汉式与懒汉式的区别

    • 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建。

    • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善)
      多个线程同时执行到懒汉式的getInstance方法时,可能出现 ”检查发现对象还未创建,从而都去创建对象“ 的情况(最终保留最后创建的对象)
      在这里插入图片描述

    • 饿汉式存在浪费资源的可能,原因是:如果用不到对象实例,饿汉式创建的对象就浪费了;懒汉式是使用时才创建,就不存在浪费的问题。

    • 在我们JavaSE标准类中,java.lang.Runtime就是经典的单例模式:
      在这里插入图片描述

    5. final 关键字

    5.1 final 基本介绍

    有以下需求时,就会使用到final。

    (1)不希望类被继承
    在这里插入图片描述

    (2)不希望父类的某个方法被子类覆盖/重写
    在这里插入图片描述

    (3)不希望类的某个属性的值被修改(常量)
    在这里插入图片描述

    (4)不希望某个局部变量被修改(常量)
    在这里插入图片描述

    5.2 final 使用细节

    • final修饰的属性又叫常量,一般用 XX_XX_XX 来命名。

    • final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一(多于一种会报错):
      (1)定义时;
      (2)在构造器中;
      (3)在代码块中。

      class AA {
      	public final double TAX_RATE = 0.08;//定义时赋值
      	public final double TAX_RATE2 ;
      	public final double TAX_RATE3 ;
      	public AA() {//构造器中赋值
      		TAX_RATE2 = 1.1;
      	}
      	{//在代码块赋值
      		TAX_RATE3 = 8.8;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 如果final修饰的属性是静态的,则初始化的位置只能是:
      (1)定义时;(2)在静态代码块。
      不能在构造器中赋值,因为静态属性初始化是在类加载阶段,调用构造器是创建对象时,太晚了。

      class AA {
      	public static final double TAX_RATE = 99.9;
      	public static final double TAX_RATE2 ;
      	static {
      		TAX_RATE2 = 3.3;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • final类不能继承,但是可以实例化对象。

      public class Test {
      	public static void main(String[] args) {
      		CC cc = new CC();
      	}
      }
      final class CC {}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承。

      public class Test {
      	public static void main(String[] args) {
      		new EE().cal();//输出"cal()方法"
      
      	}
      }
      class DD{
      	public final void cal(){
      		System.out.println("cal()方法");
      	}	
      }
      class EE extends DD{}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 一般来说,如果某个类已经是 final 类了,就没有必要再将方法修饰成 final 方法。
      (因为 final 方法是为了不让子类重写,而 final 类连子类都没有,所以没必要)

    • final 不能修饰构造方法(即构造器)

    • final 也可以修饰局部变量。

    • final 和 static 往往搭配使用,效率更高。因为不会导致类加载(底层编译器做了优化处理)。final 与 static 顺序可颠倒。

      public class Test {
      	public static void main(String[] args) {
      		System.out.println(BBB.n);
      	}
      }
      class BBB{
      	public final static int n = 10000;
      	static {
      		System.out.println("BBB静态代码块");
      	}	
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      输出结果:

      10000
      
      • 1

      若上面BBB类中的静态变量 n 不用 final 修饰,则会输出:

      BBB静态代码块
      10000
      
      • 1
      • 2
    • 包装类(Integer,Double,Float,Boolean 等都是 final 类),String 也是 final 类。这些类都不能被继承。

    5.3 final 练习

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

    public int addOne(final int x) {//形参可以用final修饰
    	++x; //错误,不能更改x的值
    	return x + 1; //可以,没有更改x的值
    }
    
    • 1
    • 2
    • 3
    • 4

    6. 抽象类

    6.1 为什么需要抽象类

    看一个问题:写一个父类Animal,其中有一个eat方法,子类要重写该方法。但这时,父类如果写了eat方法,可能没有用处,或者不知道方法体里面应该写什么。就像下面代码:

    class Animal {
    	private String name;
    	public Animal(String name) {
    		this.name = name;
    	}
    	public void eat() {
    		System.out.println("这是一个动物,但是不知道吃什么..");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    为了解决上述问题,将 eat 方法设计为抽象(abstract)方法。

    抽象方法就是没有实现的方法,即:没有方法体。
    当一个类中存在抽象方法时,需要将该类声明为 abstract 类。
    一般来说,抽象类会被继承,由子类来实现抽象方法。

    所以,上述代码可以修改为:

    abstract class Animal {
    	private String name;
    	public Animal(String name) {
    		this.name = name;
    	}
    	public abstract void eat();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.2 抽象类的介绍

    • 用 abstract 关键字来修饰一个类时,这个类就叫抽象类。
      访问修饰符 abstract 类名{}
    • 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法。
      访问修饰符 abstract 返回类型 方法名(参数列表); //抽象方法没有方法体,没有大括号
    • 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现
      抽象类()。
    • 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多。

    6.3 抽象类的细节问题

    • 抽象类不能被实例化。
      在这里插入图片描述

    • 抽象类可以没有 abstract 方法。

    • 一旦类包含了 abstract 方法,则这个类必须声明为 abstract。

    • abstract 只能修饰类和方法,不能修饰属性和其它。

    • 抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法(有方法体的方法)、构造器、静态属性等等。
      在这里插入图片描述

    • 抽象方法不能有方法体。

    • 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类。
      在这里插入图片描述

    • 抽象方法不能用 private、final 和 static 修饰,因为这些关键字都与重写相违背。
      private:私有方法不能重写(子类不能直接访问到)
      final:修饰方法的目的就是不允许子类重写
      static:与重写无关,不能与 abstract 一起

    【例】编写一个Employee类,声明为抽象类,包含如下三个属性 name, id, salary.
    提供必要的构造器和抽象方法:work()。 对于Manager类来说,他既是员工,还
    具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和
    Manager类,要求类中提供必要的方法进行属性访问,实现work(), 提示 “经理/普
    通员工+名字+工作中…

    public class Test {
    	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();
    	}
    }
    abstract class Employee {
    	private String name;
    	private int id;
    	private double salary;
    	public Employee(String name, int id, double salary) {
    		this.name = name;
    		this.id = id;
    		this.salary = salary;
    	}
    	//将 work 做成一个抽象方法
    	public abstract void work();
    
    	public String getName() {
    		return name;
    	}
    }
    class Manager extends Employee{
    	private double bonus;
    	public Manager(String name, int id, double salary) {
    		super(name, id, salary);
    	}
    	public void setBonus(double bonus) {
    		this.bonus = bonus;
    	}
    	@Override
    	public void work() {
    		System.out.println("经理 " + getName() + " 工作中...");
    	}
    }
    class CommonEmployee extends Employee{
    	public CommonEmployee(String name, int id, double salary) {
    		super(name, id, salary);
    	}
    	@Override
    	public void work() {
    		System.out.println("普通员工 " + getName() + " 工作中...");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    输出结果:

    经理 jack 工作中...
    普通员工 tom 工作中...
    
    • 1
    • 2

    7. 模板设计模式(抽象类最佳实践)

    7.1 基本介绍

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

    7.2 模板设计模式能解决的问题

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

    【例】有多个类,完成不同的任务job,要求统计得到各自完成任务的时间。

    一般解法:

    public class Test1{
    	public static void main(String[] args) 
    	{
    		AA aa = new AA();
    		aa.calculateTime(); 
    		BB bb = new BB();
    		bb.calculateTime();
    	}
    }
    class AA {
    	public void calculateTime() {
    		//开始的时间
    		long start = System.currentTimeMillis();
    		job(); 
    		//结束的时间
    		long end = System.currentTimeMillis();
    		System.out.println("任务执行时间 " + (end - start));
    	}
    	public void job() { 
    		long num = 0;
    		for (long i = 1; i <= 10000000; i++) {
    			num += i;
    		}
    	}
    }
    class BB {
    	public void calculateTime() {
    		//开始的时间
    		long start = System.currentTimeMillis();
    		job(); 
    		//结束的时间
    		long end = System.currentTimeMillis();
    		System.out.println("任务执行时间 " + (end - start));
    	}
    	public void job() {
    		long num = 0;
    		for (long i = 1; i <= 8000000; i++) {
    			num *= i;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    上述代码中,AA类和BB类有重复的方法,代码冗余度较大,可以考虑将两个类中重复的方法抽取到一个父类(抽象类)中,不同的方法声明为抽象的,然后再去子类AA和BB中实现(重写)抽象方法。

    public class Test {
    	public static void main(String[] args) {
    		AA aa = new AA();
    		aa.calculateTime(); 
    		BB bb = new BB();
    		bb.calculateTime();
    	}
    }
    abstract class Template { //抽象类-模板设计模式
    	public abstract void job();//抽象方法
    	public void calculateTime() {
    		//开始的时间
    		long start = System.currentTimeMillis();
    		//发起者是aa/bb(运行类型),所以会去执行AA/BB类中的job()方法
    		job(); //动态绑定机制
    		//结束的时间
    		long end = System.currentTimeMillis();
    		System.out.println("任务执行时间 " + (end - start));
    	}
    }
    class AA extends Template {
    	@Override
    	public void job() {//实现(重写)Template的抽象方法job
    		long num = 0;
    		for (long i = 1; i <= 10000000; i++) {
    			num += i;
    		}
    	}
    }
    
    class BB extends Template{
    	@Override
    	public void job() {//实现(重写)Template的抽象方法job
    		long num = 0;
    		for (long i = 1; i <= 8000000; i++) {
    			num *= i;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    8. 接口

    8.1 接口快速入门

    看下面例子,直观地体会接口。

    package com.hspedu.interface_;
    public class Interface01 {
        public static void main(String[] args) {
            //Camera 实现了 UsbInterface
            Camera camera = new Camera();
            //Phone 实现了 UsbInterface
            Phone phone = new Phone();
            Computer computer = new Computer();
            computer.work(phone);//把手机接入到计算机
            System.out.println("===============");
            computer.work(camera);//把相机接入到计算机
        }
    }
    class Computer {
        public void work(UsbInterface usbInterface){
            usbInterface.start();
            usbInterface.stop();
        }
    }
    interface UsbInterface{
        //规定接口的相关方法,即规范
        void start();
        void stop();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    package com.hspedu.interface_;
    public class Phone implements UsbInterface{
        @Override
        public void start() {
            System.out.println("手机开始工作");
        }
        @Override
        public void stop() {
            System.out.println("手机停止工作");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    package com.hspedu.interface_;
    public class Camera implements UsbInterface{
        @Override
        public void start() {
            System.out.println("相机开始工作");
        }
        @Override
        public void stop() {
            System.out.println("相机停止工作");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出结果:

    手机开始工作
    手机停止工作
    ===============
    相机开始工作
    相机停止工作
    
    • 1
    • 2
    • 3
    • 4
    • 5

    8.2 基本介绍

    (可以将接口理解为一个特殊的类。类实现接口也可达到代码重用的目的)
    接口就是给出一些没有实现的方法,封装到一起。到某个类要使用的时候,再根据具体情况把这些方法写出来。语法:

    interface 接口名{
    	//属性
    	//抽象方法(Jdk8及以后还有默认实现方法、静态方法)
    }
    
    • 1
    • 2
    • 3
    • 4
    class 类名 implements 接口名{
    	自己的属性;
    	自己的方法;
    	必须实现的接口的抽象方法;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Jdk7.0及以前,接口里的所有方法都没有方法体,即:都是抽象方法。
    Jdk8.0及以后,接口里可以有静态方法,默认方法,即:接口中可以有方法的具体实现。
    在这里插入图片描述

    在接口中,抽象方法可以省略 abstract 关键字。

    如果一个类 implements 接口,就要将该接口中的所有抽象方法都实现。
    在这里插入图片描述

    在这里插入图片描述
    对初学者讲,理解接口的概念不算太难,难的是不知道什么时候使用接口,下面列举几个应用场景:
    (1)现在要制造战斗机、直升机。专家只需把飞机需要的功能 / 规格定下来,然后让别的人具体实现即可。
    (2)一个项目经理管理3个程序员,分别实现 MySql、Oracle、DB2 数据库的 connect、close。项目经理写好接口,程序员根据需求去具体实现接口中的方法。这样就能保证3个程序员写的方法有相同的方法名,便于管理。(下面以MySql、Oracle为例)

    package com.hspedu.interface_;
    public class Test {
        public static void main(String[] args) {
            MySqlDB mySqlDB = new MySqlDB();
            m(mySqlDB);
            OracleDB oracleDB = new OracleDB();
            m(oracleDB);
        }
        public static void m(DBInterface db){
            db.connect();
            db.colse();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    package com.hspedu.interface_;
    public interface DBInterface {
    	//强制MySql、Oracle的连接和关闭方法都叫connect、close
        public void connect();
        public void colse();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    package com.hspedu.interface_;
    public class MySqlDB implements DBInterface{
        @Override
        public void connect() {
            System.out.println("MySql已连接...");
        }
        @Override
        public void colse() {
            System.out.println("MySql已关闭...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    package com.hspedu.interface_;
    public class OracleDB implements DBInterface{
        @Override
        public void connect() {
            System.out.println("Oracle已连接...");
        }
        @Override
        public void colse() {
            System.out.println("Oracle已关闭...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出结果:

    MySql已连接...
    MySql已关闭...
    Oracle已连接...
    Oracle已关闭...
    
    • 1
    • 2
    • 3
    • 4

    8.3 接口使用细节

    • 接口不能被实例化。
      (接口的本意是让类去实现它,然后我们再去创建该类的对象进行其他操作)
      在这里插入图片描述

    • 接口中所有的方法都是 public 方法,接口中的抽象方法可以不用 abstract 修饰。
      接口中的 void aaa() 实际上是 public abstract void aaa(),所以在接口中写 void aaa(){} 是错误的。

    • 一个普通类实现接口,就必须将该接口的所有方法都实现。

    • 抽象类实现接口,可以不用实现接口的方法。
      就像身为抽象类的子类可以不去实现父类(也是抽象类)中的抽象方法一样。

    • 一个类可以同时实现多个接口。
      在这里插入图片描述

    • 接口中的属性,只能是 final 的,而且是 public static final 修饰符。比如 int a=1 实际上是 public static final int a = 1(必须初始化)。

    • 接口中属性的访向形式:接口名.属性名。

    • 接口不能继承其它的类,但是可以继承多个别的接口。(类可以实现多个接口)
      interface A extends B,C{}

    • 接口的修饰符只能是 public 和默认,这点和的修饰符是一样的。

    【例】
    在这里插入图片描述

    8.4 接口与继承的区别与联系

    接口可以看作对单继承机制的补充。

    下面代码中 LittleMonkey 只能拥有父类的功能:

    class Monkey{
        private String name;
        public Monkey(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void climb(){
            System.out.println(name+"会爬树");
        }
    }
    class LittleMonkey extends Monkey {
        public LittleMonkey(String name) {
            super(name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    下面代码 LittleMonkey 除了可以拥有父类的功能,还可以拥有接口中的功能:

    public class ExtendsVsInterface {
        public static void main(String[] args) {
            LittleMonkey wuKong = new LittleMonkey("孙悟空");
            wuKong.climb();
            wuKong.swimming();
            wuKong.flying();
        }
    }
    class Monkey{
        private String name;
        public Monkey(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void climb(){
            System.out.println(name+"会爬树");
        }
    }
    interface Fish{
        void swimming();
    }
    interface Bird{
        void flying();
    }
    class LittleMonkey extends Monkey implements Fish,Bird{
        public LittleMonkey(String name) {
            super(name);
        }
        @Override
        public void swimming() {
            System.out.println(getName()+"通过学习,可以像鱼儿一样游泳");
        }
        @Override
        public void flying() {
            System.out.println(getName()+"通过学习,可以像鸟儿一样飞翔");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    输出结果:

    孙悟空会爬树
    孙悟空通过学习,可以像鱼儿一样游泳
    孙悟空通过学习,可以像鸟儿一样飞翔
    
    • 1
    • 2
    • 3

    小结:当子类继承了父类,就自动的拥有父类的功能。如果子类需要扩展功能,就可以通过实现接口的方式扩展。可以理解为:“实现接口” 是对 Java 单继承机制的一种补充。

    接口和继承解决的问题不同
    继承的价值主要在于:解决代码的复用性和可维护性。
    接口的价值主要在于:设计好各种规范(方法),让其它类去实现这些方法,不同的类实现方式不同。
    接口比继承更加灵活
    接口比继承更加灵话,继承要满足 is-a 的关系(人是一种动物),而接口只需满足like-a的关系(人像鸟一样飞翔)。
    接口在一定程度上实现代码解耦[即:接口规范性+动态绑定机制](集合那里再讲)

    8.5 接口的多态

    接口引用可以指向实现了接口的类的对象。

    • 多态参数
      在前面 8.1 的案例中,Computer 类中的work方法的形参 UsbInterface usbInterface 既可以接收手机对象,又可以接收相机对象。

    • 接收新创建的实现了接口的类的对象

      public class InterfacePolyParameter {
          public static void main(String[] args) {
              IF if1 = new Monster();
              IF if2 = new Car();
          }
      }
      interface IF{}
      class Monster implements IF{}
      class Car implements IF{}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 多态数组
      演示一个案例:给Usb数组中,存放 Phone 和 相机对象,Phone类还有一个特有的方法cal(),请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,还需要调用 Phone 特有方法 call。

      public class InterfacePolyArr {
          public static void main(String[] args) {
              Usb[] usbs = new Usb[3];
              usbs[0] = new Phone();
              usbs[1] = new Pad();
              usbs[2] = new Phone();
              for (int i = 0; i < usbs.length; i++) {
                  System.out.println("--------------");
                  if (usbs[i] instanceof Phone){
                      ((Phone) usbs[i]).call();//向下转型
                  }
                  usbs[i].work();//动态绑定机制
              }
          }
      }
      interface Usb{
          void work();
      }
      class Phone implements Usb{
          @Override
          public void work() {
              System.out.println("手机工作中");
          }
          public void call(){
              System.out.println("手机打电话");
          }
      }
      class Pad implements Usb{
          @Override
          public void work() {
              System.out.println("Pad工作中");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33

    接口存在多态传递现象。

    public class InterfacePolyPass {
        public static void main(String[] args) {
            IG ig = new Teacher();
            IH ih = new Teacher();
        }
    }
    interface IH{}
    interface IG extends IH{}
    class Teacher implements IG{}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上例中,IG继承了IH接口,Teacher类实现了IG接口,就相当于Teacher类也实现了IH接口。所以,若IH接口中有抽象方法,Teacher类也应该去实现。

    练习:看下面代码有没有错误,有就改正。

    package com.hspedu;
    interface A {
        int x = 0;//等价 public static final int x = 0;
    }
    class B {
        int x = 1;
    }
    class C extends B implements A {
        public void pX() {
            //System.out.println(x); //错误,原因不明确 x
            //可以明确的指定 x
            //访问接口的x使用A.x
            //访问父类的x使用super.x
            System.out.println(A.x + " " + super.x);
        }
        public static void main(String[] args) {
            new C().pX();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    9. 内部类

    9.1 基本介绍

    内部类: 一个类的内部又完整地嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。内部类是类的第五大成员。【类的五大成员:属性、方法、构造器、代码块、内部类】

    内部类的特点: 可以直接访问私有属性,并体现类与类之间的包含关系。
    注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类。

    基本语法:

    class Outer{//外部类
    	class Inner{//内部类
    	}
    }
    class Other{//外部的其他类
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    内部类的分类:

    • 定义在外部类局部位置上(比如方法内):
      局部内部类(有类名)
      匿名内部类(没有类名,重点)
    • 定义在外部类的成员位置上:
      成员内部类(不用static修饰)
      静态内部类(使用static修饰)

    9.2 局部内部类

    • 局部内部类是定义在外部类的局部位置(方法或代码块中),并且有类名。

    • 不能添加访向修饰符,因为它的地位就是一个局部变量。局部变量不能使用修饰符。但是可以使用final修饰,因为局部变量也可以使用final。不用final修饰时,本外部类中的其他与该内部类同级的类可以继承该内部类;用final修饰时就不能再被继承。

    • 作用域在定义它的方法或代码块中

    • 本质仍是一个类

    • 局部内部类可以直接访问外部类的成员,包含私有的。

    • 外部类访问局部内部类的成员,要先创建对象(注意作用域),再访问。

    • 外部其他类不能访问局部内部类(因为局部内部类地位是一个局部变量)。

    • 如果外部类和局部内部类的成员重名,默认遵循就近原则,如果想访向外部类的成员,则可以使用(外部类名.this.成员)去访问。

    public class InnerClassTest {
        public static void main(String[] args) {
            Outer outer = new Outer();
            outer.m2();
        }
    }
    class Outer{
        private int n1 = 100;
        private void m1(){
            System.out.println("外部类的m1()被调用");
        }
        public void m2(){
            final class Inner{//地位等同于局部变量
                public void f1(){
                    //Outer.this本质就是外部类的对象, 即调用m2的对象
                    //如果外部类中的n1是静态的,可以直接用Outer.n1访问
                    System.out.println("内部类的n1=" + n1 + " 外部类的n1=" + Outer.this.n1);
                    m1();//内部类直接访问外部类中的成员
                }
            }
            //class inner02 extends inner{}
            //外部类先在内部类作用域内创建内部类的对象,再访问内部类的成员
            Inner inner = new Inner();
            inner.f1();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    代码执行流程:在这里插入图片描述

    9.3 匿名内部类

    匿名内部类: 匿名内部类定义在外部类的局部位置(比如方法中),并且没有类名。它的本质是一个类,同时还是一个对象。

    **匿名内部类的基本语法: **

    new 类或接口(参数列表){
    	类体
    };
    
    • 1
    • 2
    • 3

    9.3.1 基于接口的匿名内部类

    package com.hspedu.innerclass;
    public class AnonymousInnerClass {
        public static void main(String[] args) {
            Outer02 outer02 = new Outer02();
            outer02.method();
        }
    }
    interface IA{
        void cry();
    }
    class Outer02 {
        public void method(){
            //想使用IA接口,并创建对象
            //传统方式是写一个类实现该接口,并创建对象
            //但是,如果该类只使用一次,就可以用匿名内部类简化
    
            //tiger的编译类型是IA,运行类型是匿名内部类
            //IA tiger = new IA(){...}使jdk底层 先创建匿名内部类Outer04$1(外部类名+$+编号,编号是挨着排的,)
            //然后马上创建了Outer04$1对象,并且把地址返回给tiger
            //jdk底层创建的类:
            //class Outer02$1 implements IA{
            //    @Override
            //    public void cry() {
            //        System.out.println("老虎在叫");
            //    }
            //}
            //匿名内部类使用一次,就不能再使用
            IA tiger = new IA(){
                @Override
                public void cry() {
                    System.out.println("老虎在叫");
                }
            };
            System.out.println(tiger.getClass());//获取运行类型(获取系统分配的匿名内部类名)
            tiger.cry();//动态绑定机制
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    输出结果:

    老虎在叫
    class com.hspedu.innerclass.Outer02$1
    
    • 1
    • 2

    9.3.2 基于类的匿名内部类

    package com.hspedu.innerclass;
    public class AnonymousInnerClass {
        public static void main(String[] args) {
            Outer02 outer02 = new Outer02();
            outer02.method();
        }
    }
    class Father{
        public Father(String name) {//构造器
            System.out.println("接收到 name=" + name);
        }
        public void test() {//方法
        }
    }
    abstract class Animal{
        abstract void eat();
    }
    class Outer02 {
        public void method(){
            //father的编译类型是Father,运行类型是匿名内部类Outer02$1
            //jdk底层 先创建匿名内部类Outer04$1,然后马上创建了Outer04$1对象,并且把地址返回给father
            //class Outer04$1 extends Father{
            //    @Override
            //    public void test() {
            //        System.out.println("匿名内部类重写了 test 方法");
            //    }
            //}
            注意("jack")参数列表会传递给Father类的构造器
            Father father = new Father("jack"){
                @Override
                public void test() {
                    System.out.println("匿名内部类重写了test方法");
                }
            };
            System.out.println("father 对象的运行类型=" + father.getClass());//Outer04$1
            father.test();//动态绑定机制
    
            System.out.println("===============================================");
            //基于抽象类的匿名内部类 必须实现父类的抽象方法
            Animal animal = new Animal(){
                @Override
                void eat() {
                    System.out.println("小狗吃骨头");
                }
            };
            System.out.println("animal 对象的运行类型=" + animal.getClass());
            animal.eat();//动态绑定机制
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    输出结果:

    接收到 name=jack
    father 对象的运行类型=class com.hspedu.innerclass.Outer02$1
    匿名内部类重写了test方法
    ===============================================
    animal 对象的运行类型=class com.hspedu.innerclass.Outer02$2
    小狗吃骨头
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    9.3.3 匿名内部类细节

    • 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象。从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法。

      package com.hspedu.innerclass;
      public class AnonymousInnerClass {
          public static void main(String[] args) {
              Outer02 outer02 = new Outer02();
              outer02.method();
          }
      }
      class Outer02{
          public void method(){
              Person person = new Person(){
                  @Override
                  public void ok(String name) {
                      System.out.println("子类(匿名内部类1)重写了父类的ok方法 "+name);
                  }
              };
              person.ok("jack");
              //也可以这样调用
              new Person(){
                  @Override
                  public void ok(String name) {
                      System.out.println("子类(匿名内部类2)重写了父类的ok方法 "+name);
                  }
              }.ok("tom");//直接调用
          }
      }
      class Person{
          public void ok(String name) {
              System.out.println("父类的ok方法 "+name);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

      输出结果:

      子类(匿名内部类1)重写了父类的ok方法 jack
      子类(匿名内部类2)重写了父类的ok方法 tom
      
      • 1
      • 2
    • 匿名内部类可以直接访问外部类的所有成员,包含私有的。

      package com.hspedu.innerclass;
      public class AnonymousInnerClass {
          public static void main(String[] args) {
              Outer02 outer02 = new Outer02();
              outer02.method();
          }
      }
      class Outer02{
          private int n1 = 100;
          public void method(){
              //类继承Person,并创建了该类对象,用父类Person的引用指向子类的对象
              Person person = new Person(){
                  @Override
                  public void hi() {
                      //匿名内部类直接访问外部类的所有成员,包含私有的
                      System.out.println("n1 "+n1);
                  }
              };
              person.hi();
          }
      }
      class Person{
          public void hi() {
              System.out.println("父类的hi方法");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
    • 不能添加访问修饰符,因为它的地位就是一个局部变量。

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

    • 匿名内部类只能使用一次

    • 匿名内部类可以直接访问外部类成员。

    • 外部其他类不能访问匿名内部类,因为匿名内部类的地位是一个局部变量。

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

      package com.hspedu.innerclass;
      public class AnonymousInnerClass {
          public static void main(String[] args) {
              Outer02 outer02 = new Outer02();
              outer02.method();
              System.out.println("outer02的运行类型:"+outer02.getClass());
          }
      }
      class Outer02{
          private int n1 = 100;
          public void method(){
              //类继承Person,并创建了该类对象,用父类Person的引用指向子类的对象
              Person person = new Person(){
                  private int n1 = 200;
                  @Override
                  public void hi() {
                      //Outer.this本质就是外部类的对象, 即调用method的对象
                      //如果外部类中的n1是静态的,可以直接用Outer.n1访问
                      System.out.println("内部类中的n1="+n1+" 外部类中的n1="+Outer02.this.n1);
                      System.out.println("Outer02.this的运行类型:"+Outer02.this.getClass());
                  }
              };
              person.hi();
          }
      }
      class Person{
          public void hi() {
              System.out.println("父类的hi方法");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

      输出结果:

      内部类中的n1=200 外部类中的n1=100
      Outer02.this的运行类型:class com.hspedu.innerclass.Outer02
      outer02的运行类型:class com.hspedu.innerclass.Outer02
      
      • 1
      • 2
      • 3

    9.3.4 匿名内部类做实参直接传递

    传统方法:类实现接口 ➡ 创建对象 ➡ 传参

     public class Test {
    	public static void main(String[] args) {
    		//传统方法
    		f1(new Picture());//创建对象和传参
    	}
    	//因为不知道匿名内部类的类名
    	//所以只能用IL类型的形参接收
    	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");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    匿名内部类做实参直接传递:

    public class Test {
    	public static void main(String[] args) {
    		//当做实参直接传递,简洁高效
    		f1(new IL() {
    			@Override
    			public void show() {
    				System.out.println("一副名画~~");
    			}
    		});
    	}
    	//因为不知道匿名内部类的类名
    	//所以只能用IL类型的形参接收
    	public static void f1(IL il) {
    		il.show();
    	}
    }
    
    interface IL {
    	void show();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    public class Test {
    	public static void main(String[] args) {
    		CellPhone cellPhone = new CellPhone();
    		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.ring();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    输出结果:

    懒猪起床了
    小伙伴上课了
    
    • 1
    • 2

    9.4 成员内部类

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

    • 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。(前面学习的普通类只能用 public 和 默认 来修饰)
    • 作用域和外部类的其他成员一样,为整个类体。
    • 成员内部类可以直接访问外部类的成员,包含私有的。
    • 外部类访问成员内部类中的成员:先创建对象,再访问。
    public class Test {
    	public static void main(String[] args) {
    		Outer outer = new Outer();
    		outer.t1();
    	}
    }
    
    class Outer{
    	private int n1 = 10;
    	public String name = "张三";
    	private void hi() {
    		System.out.println("hi()方法...");
    	}
    	public class Inner{
    		private double sal = 99.8;
    		public void say(){
    			//可以直接访问外部类的所有成员,包括私有的
    			System.out.println("n1="+n1+" name="+name);
    			hi();
    		}
    	}
    	public void t1(){
    		//外部类访问成员内部类中的成员:先创建对象,再访问
    		Inner inner = new Inner();
    		inner.say();
    		//可以访问内部类的私有成员,因为在同一个类中
    		System.out.println("sal="+inner.sal);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 外部其他类访问成员内部类中的成员。
    public class Test {
    	public static void main(String[] args) {
    		//外部其他类,使用成员内部类的2种方式(记住就行)
    		Outer outer = new Outer();
    		//第1种方式:相当于把new Inner()当成outer的成员
    		Outer.Inner inner = outer.new Inner();
    		inner.say();
    		System.out.println("========================");
    		// 第2种方式:在外部类编写一个方法,返回Inner对象
    		Outer.Inner innerInstance = outer.getInnerInstance();
    		innerInstance.say();
    	}
    }
    
    class Outer{
    	private int n1 = 10;
    	public String name = "张三";
    	private void hi() {
    		System.out.println("hi()方法...");
    	}
    	public class Inner{
    		private double sal = 99.8;
    		public void say(){
    			//可以直接访问外部类的所有成员,包括私有的
    			System.out.println("n1="+n1+" name="+name);
    			hi();
    		}
    	}
    	//返回一个 Inner对象
    	public Inner getInnerInstance(){
    		return new Inner();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 如果外部类和内部类的成员重名,在内部类访问时遵循就近原则,若想访向外部类的成员,可以使用 “外部类名.this.成员” 去访问。(与前面相同)

    9.5 静态内部类

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

    • 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。(前面学习的普通类只能用 public 和 默认 来修饰)
    • 作用域和外部类的其他成员一样,为整个类体。
    • 静态内部类可以直接访向外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
    • 外部类访问静态内部类中的成员:先创建对象,再访问。
    public class Test {
    	public static void main(String[] args) {
    		Outer outer = new Outer();
    		outer.m();
    	}
    }
    
    class Outer{
    	private int n1 = 10;
    	public static String name = "张三";
    	private static void cry() {
    		System.out.println("cry()方法...");
    	}
    	static class Inner{
    		public void say(){
    			//可以直接访问外部类的所有静态成员,包括私有的
    			System.out.println("name="+name);
    			cry();
    		}
    	}
    	public void m(){
    		//外部类访问静态内部类中的成员:先创建对象,再访问
    		Inner inner = new Inner();
    		inner.say();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 外部其他类访问静态内部类中的成员。
    public class Test {
    	public static void main(String[] args) {
    		//外部其他类 使静态内部类
    		//方式1
    		//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
    		Outer.Inner inner = new Outer.Inner();
    		inner.say();
    		System.out.println("============");
    		//方式2
    		//编写一个方法,可以返回静态内部类的对象实例
    		Outer.Inner inner2 = new Outer().getInner();
    		inner2.say();
    		System.out.println("============");
    		//方式2改进 返回对象的方法改成静态的
    		Outer.Inner inner3 = Outer.getInner_();
    		inner3.say();
    	}
    }
    
    class Outer{
    	private int n1 = 10;
    	public static String name = "张三";
    	private static void cry() {
    		System.out.println("cry()方法...");
    	}
    	static class Inner{
    		public void say(){
    			//可以直接访问外部类的所有静态成员,包括私有的
    			System.out.println("name="+name);
    			cry();
    		}
    	}
    	public Inner getInner() {//(方法2用)返回静态内部类的对象
    		return new Inner();
    	}
    
    	public static Inner getInner_() {//(方法2改进用)
    		return new Inner();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    输出结果:

    name=张三
    cry()方法...
    ============
    name=张三
    cry()方法...
    ============
    name=张三
    cry()方法...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 如果外部类和静态内部类的静态成员重名,在静态内部类中访问时,遵循就近原则,若想访问外部类的静态成员,则可以使用 “外部类名.成员” 去访问。
  • 相关阅读:
    DockerFile常用保留字指令及知识点合集
    idea找不到或无法加载主类
    LNK2001 __GSHandlerCheck【error】
    html+css+js制作LOL官网,web前端大作业(3个页面+模拟登录+链接)
    webpack(二)webpack介绍与基础配置
    fsync
    Linux —用户和组
    忘记Docker的NextCloud密码
    C++ Qt开发:Charts折线图绘制详解
    工作任务“杂乱难”?这个小工具帮你轻松搞定!
  • 原文地址:https://blog.csdn.net/qq_44378854/article/details/125982081