10面向对象编程(高级)

面向对象编程(高级)

1. 类变量和类方法(static)

1.1 类变量基本介绍

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

  2. 不管 static 变量在哪里,共识:(1) static 变量是同一个类所有对象共享;(2) static 类变量,在类加载的时候就生成了,所以即使没有创建对象实例也可以访问(类名.类变量名)

  3. 定义类变量语法:

    访问修饰符 static 数据类型 变量名;(推荐)

    static 访问修饰符 数据类型 变量名;

  4. 访问类变量:

    类名.类变量名(推荐) 或者 对象名.类变量名

  5. 类变量的访问也要遵守访问权限规则。

1.2 类变量的使用细节

  1. 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。比如:定义学生类,每个学生的住宿费是统一的,可以定义为类变量。

  2. 类变量与实例变量(普通属性)区别:类变量是该类的所有对象共享的,而实例变量是每个对象独享的。

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

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

  5. 实例变量不能通过类名.类变量名方式访问。

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

       public static void main(String[] args) {
            System.out.println(A.n1);   //错误
            System.out.println(A.n2);		//正确
        }
    
    class A{
        public int n1 = 100;		// 实例变量/普通变量/非静态变量
        public static int n2 = 200;	//类变量/静态变量
    }
    
  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。[][案例演示]

1.3 类方法基本介绍

  1. 类方法也叫静态方法。定义类方法的语句:

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

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

  2. 类方法的调用:

    类名.类方法名 or 对象名.类方法名

    注意:类方法的调用也要满足访问权限

  3. 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。比如:将一个冒泡排序方法设置成静态方法,以后需要排序时直接调用 类名.冒泡排序方法名,就不用了重新实例化一个对象

1.4 类方法的使用细节

特别说明:类方法 = 静态方法; 类变量 = 静态变量

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

  2. 类方法可以通过类名调用,也可以通过对象名调用;普通方法只能通过对象名调用。比如:

    //类 A 有静态方法 say(),非静态方法 talk()
    A.say();	//正确
    new A().say();	//正确
    new A().talk();	//正确
    
  3. ==类方法中不允许使用和对象有关的关键字,比如 this 和 super 。==普通方法(成员方法)可以。

  4. 静态方法,只能访问静态的成员;普通方法,既可以访问静态成员,又可以访问非静态成员(必须遵守访问权限)

1.5 课堂练习

在这里插入图片描述

在这里插入图片描述

对题2:main 中的第一个 println 语句中没有实例化对象,没有调用构造器。第二句 Person p1 = new Person(); 实例化了一个对象,调用构造器,total++ 被执行,故 total 变成 1。

在这里插入图片描述

对题 3:setTotalPerson() 是静态方法,不能有关键字 this 或者 super,故 this.total = total;语句错误。main 中的语句 new Person();相当于实例化了一个对象,会执行构造器,但是没有返回给对象的引用,所以 total++ 被执行。


2. main

2.1 main 语法说明

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

  1. main 方法是 java 虚拟机调用

  2. public:java 虚拟机需要调用类的 main() 方法,所以该方法的访问权限必须是 public

  3. static:java 虚拟机在执行 main() 方法时不必创建对象,所以该方法必须是 static

  4. String[] args:该方法接收String类型的数组参数,从命令行输入,该数组中保存执行 java 命令时传递给所运行的类的参数,演示接收参数如下:

在这里插入图片描述

在这里插入图片描述

  1. java 执行的程序 参数1 参数2 参数3
    在这里插入图片描述

2.2 main 特别说明

  1. 在 main() 方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性。

  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静

态成员

  1. 如何在 IDEA 中给 args 传值。 (2.1 中的 4 是在 DOS 命令中传递的值)

在这里插入图片描述

在这里插入图片描述

设置好后再次执行,结果如下:

在这里插入图片描述


3. 代码块

3.1 代码块基本介绍

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

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

基本语法:

[修饰符]{
	代码
};

1、修饰符可选,要写的话,也只能写 static

2、代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,叫普通代码块/非静态代码块。

3、逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)

4、;号可以写上,也可以省略。

3.2 代码块用法

public class CodeBlock {
    public static void main(String[] args) {
        Movie movie = new Movie("你好,李焕英");
        System.out.println("=======================");
        Movie movie1 = new Movie("唐探3", 100, "陈思诚");
    }
}

class Movie{
    private String name;
    private double price;
    private String director;

    //代码块
    {
        System.out.println("电影开始了......");
        System.out.println("小朋友坐端正......");
    };

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

    public Movie(String name, double price, String director) {
        //System.out.println("电影开始了......");
        //System.out.println("小朋友坐端正......");
        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)被调用......
*/

3.3 代码块的使用细节

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

  2. 类什么时候被加载:

    ①、创建对象实例时,即 new 时

    //main方法中
    AA aa = new AA();	//初始化时,先加载类的信息,此时会加载静态代码块的信息,只加载一次
    
    class AA { 
    	//静态代码块
    	static {
    		System.out.println("AA 的静态代码 1 被执行...");
    	} 
    }
    
    //输出:
    /*
    AA 的静态代码 1 被执行...
    */
    

    ②、创建子类对象实例,父类也会被加载

    //main方法中
    AA aa = new AA();	//初始化时,先加载父类的信息即BB,再加载子类的信息。
    
    class BB { 
    	//静态代码块 
    	static {
    		System.out.println("BB 的静态代码 1 被执行...");
    	} 
    }
    
    class AA extends BB { 
    	//静态代码块
    	static {
    		System.out.println("AA 的静态代码 1 被执行...");
    	} 
    }
    
    //输出:
    /*
    BB 的静态代码 1 被执行...
    AA 的静态代码 1 被执行...
    */
    

    ③、使用类的静态成员时(静态属性,静态方法)

    //main 方法中
    System.out.println(Cat.age);	//因为用到了 Cat.age 所以会加载 Cat 类
    
    class Cat{
        public static int age = 999;	//访问权限为 public 其他类才能直接访问
        
        //静态代码块
        static {
            System.out.println("Cat的静态代码块被执行...");
        };
    }
    //输出
    /*
    Cat的静态代码块被执行...
    999
    */
    
  3. 普通的代码块,在创建对象实例(new)时,会被隐式的调用。被创建一次,就会调用一次。(参见 3.2 中,两个对象都调用了普通代码块)

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

    public class CodeBlock {
        public static void main(String[] args) {
            System.out.println(DD.n1);
        }
    }
    
    class DD {
        public static int n1 = 8888;//静态属性
    
        // 静态代码块
        static {
            System.out.println("DD 的静态代码 1 被执行...");//
        }
    
        //普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一次
        //可以这样简单的理解: 普通代码块是构造器的补充
        {
            System.out.println("DD 的普通代码块...");
        }
    }
    //输出:
    //DD 的静态代码 1 被执行...
    //8888
    
  4. 创建一个对象时,在一个类调用顺序是

    ①、先调用静态代码块和静态属性初始化。(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态属性初始化,则按照定义的顺序调用)

    ②、再调用普通代码块和普通属性的初始化。(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个,则按照定义的顺序调用)

    ③、最后调用构造器

    总结:为什么先 静态——普通——构造器,因为静态是与类加载有关的,所以最先执行;普通是与对象初始化开辟空间有关的,所以实例化对象时第二执行;构造器是根据赋值 new A(参数) 来给对象的数据空间赋值的,所以最后执行。(注意:看下一条——第5条,这里分析有问题,普通也是包含在构造器中,只是在构造器前面执行的)

    //main方法中
    A a = new A();//按照 静态——普通——构造器 的过程进行
    
    class A {
        private int n2 = getN2();
        private static int n1 = getN1();//这里的getN1()必须是静态方法
    
        public A() {
            System.out.println("A的构造器被调用...(顺序 5)");
        }
    
        {
            System.out.println("A的普通代码块被调用...(顺序 4)");
        }
    
        public int getN2() {
            System.out.println("getN2()被调用(普通属性n2)...(顺序 3)");
            return 99;
        }
    
        static {
            System.out.println("A的静态代码块被调用...(顺序 2 )");
        }
    
        public static int getN1() {
            System.out.println("getN1()被调用(静态变量n1)...(顺序 1)");
            return 100;
        }
    }
    //输出是按照后面顺序 1 2 3 4 5 输出的
    
  5. 构造器 的最前面其实隐含了 super() 与 调用普通代码块执行和普通属性初始化。(注意:此时已经完成了静态相关的部分,因为构造器最后执行嘛,因此静态相关代码是优于 构造器和普通代码块执行的):

    class A{
    	public A(){	//构造器
            //1.super();
            //2.调用本类的普通代码块,初始化本类的普通属性(优先级一样)
    		System.out.println("OK"); //实际上在这一句上面还有两句隐藏的:先是super(); 再是调用普通代码块和普通属性初始化
                						//故super比普通代码块先执行(super和this应该在构造器第一行,还是有道理)
    	}
    }
    
  6. 当创建一个子类时,其代码块、属性和构造器的调用顺序为:父静——子静——父普——父构——子普——子构(十分重要!!!)

    其中父静表示父类的静态代码块和静态属性(优先级一样,按定义顺序执行);父普表示父类的普通代码块和普通属性(优先级一样,按定义顺序执行);父构表示父类的构造方法。子静、子普、子构同理。

    ①、先明白一个通识:Cat cat = new Cat(); 即创建一个对象实例时,先加载类信息(先父类,再子类),再创建对象(开辟空间),加载类信息与静态相关,创建对象与构造器有关(构造器中第一句为父类的 super,第二句为普通代码块执行和普通属性初始化,优先级一样)

    ②、上面顺序:父静、子静是跟类加载相关的,父普、父构、子普、子构是跟对象创建相关的

  7. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员

3.4 课堂练习

  1. 下面代码输出什么:
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!
100
100
*/

​ 考点:①、类名.静态属性名 访问静态属性;②、加载类信息时,静态代码块和静态属性初始化都会被执行,并且只执行一次

  1. 下面代码输出什么:

    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 默认构造函数被调用");
        }
    }
    
    //主方法main
            Test a = new Test();
        
    /*1. 静态成员 sam 初始化 
    2. static 块执行 
    3. sam1 成员初始化 
    4. Test 默认构造函数被调用*/
    

    分析:创建一个对象时,先加载类信息(执行静态的东西),再执行构造器(先是普通属性的初始化 和 普通代码块的调用(此时无父类),然后是构造器本身)


4. 单例设计模式

4.1 设计模式

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

4.2 单例设计模式

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

4.3 应用

演示饿汉式和懒汉式单例模式的实现。

  1. 构造器私有化,这是为了防止直接 new
  2. 类的内部直接创建对象(该对象是 static)
  3. 在类中向外暴露一个 static 的公共方法——getlnstance() 用来返回一个对象实例。
  4. main 中使用 类名.getlnstance() 来获得一个对象实例

饿汉式:类加载的时候对象已经被创建好了,即没有使用的时候已经创建了对象实例。饿汉式可能造成创建了对象但是没有使用。代码如下:

//main中
public static void main(String[] args) {
        GirlFriend gf = GirlFriend.getInstance();//通过调用公开函数来得到一个对象实例,如果再次调用getInstance()还是原对象,从而保证单例
        System.out.println(gf);
    	System.out.println(GirlFriend.n1);	//注释掉上上面那一句,还是会输出“构造器被调用”,因为此时会加载类
    										//而加载类的时候就会创建静态的对象(因为有new)
    }

class GirlFriend{
    private String name;
    public static int n1 = 100;	//为什么是static因为main函数是static,所以要想直接调用n1,n1必须为static
    private static GirlFriend instance = new GirlFriend("二狗");//static 和 new是为了在加载类的时候就把该对象创建好(饿汉式精髓)
    
	//返回该类的对象,用于在main中得到一个实例化的对象,因为要用到静态属性instance 所以次函数也必须为静态的
    public static GirlFriend getInstance(){	
        return instance;
    }

    private GirlFriend(String name) {	//构造器私有化,只能类内部创建对象
        System.out.println("构造器被调用");
        this.name = name;
    }

    @Override	//重写便于输出
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

懒汉式:只有当用户使用 getInstance() 这个方法的时候才创建对象实例,后面如果再次调用 getInstance() ,只会返回上一次创建的对象。代码如下:

public static void main(String[] args) {
        GirlFriend gf = GirlFriend.getInstance();//执行这一句的时候静态的instance为空才会执行构造器创建对象,
        						//如果不执行getInstance()就不会创建,再次调用也只会返回第一次调用时创建的那个对象实例
        System.out.println(gf);
        System.out.println(GirlFriend.n1);//注释掉上面两行,不会输出“构造器被调用”,因为定义静态属性的时候没有用new
    }
}

class GirlFriend{
    private String name;
    public static int n1 = 100;	
    private static GirlFriend instance;//这里没有new所以加载类的时候不会直接创建对象实例

    public static GirlFriend getInstance(){//调用函数时,先判断instance属性有没有被创建,如果没有被创建才重新创建
        if(instance == null){	
            instance = new GirlFriend("二狗");
        }
        return instance;
    }

    private GirlFriend(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

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

4.4 饿汉式 VS 懒汉式

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

  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善一把)

  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。

  4. 在我们 javaSE 标准类中,java.lang.Runtime 就是经典的单例模式。源码如下,可以看出是饿汉式:

在这里插入图片描述


5. final 关键字

5.1 final 基本介绍

final 可以修饰类、属性、方法和局部变量。

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

  1. 不希望类被继承时,可以用 final 修饰。如:final class A{…}
  2. 不希望父类的某个方法被子类覆盖/重写(override)时,可以用 final 关键字修饰。如:访问修饰符 final 返回类型 方法名(){…}
  3. 不希望类的的某个属性的值被修改,可以用final修饰。【案例演示:public final double TAX_ RATE=0.08】
  4. 不希望某个局部变量被修改,可以使用final修饰【案例演示: final double TAX RATE=0.08 】

5.2 final 使用细节

  1. final 修饰的属性又叫常量,一般用 XX_XX_XX(大写) 来命名

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

①、定义时:如 public final double TAX_RATE = 0.08;

②、在构造器中

③、在代码块中。

class AA{
    public final int A_NUM = 100;   //1. 定义时直接赋值

    public final int B_NUM; //2.构造器中赋值
    public AA() {
        B_NUM = 200;
    }

    public final int C_NUM; //3.代码块中赋值
    {
        C_NUM = 300;
    }
}
//即实例化对象时(因为上面的3种赋值都是在实例化对象完成之前),即满足实例化后该对象的常量属性必须是已知的(值确定的)
  1. 如果 final 修饰的属性是静态的,则初始化的位置只能是

    ①、定义时

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

    class AA{
        public static final int A_NUM = 100;  //1.定义时赋值
        
        public static final int B_NUM;  //2.静态代码块中赋值
        static {
            B_NUM = 200;
        }
    }
    
  2. final类不能继承,但是可以实例化对象。

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

  4. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成 final 方法(因此他已经没法被继承了,方法当然也不能被重写)。

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

  6. final 和 static 往往搭配使用,效率更高,不会导致类加载(底层编译器做了优化处理),如下:

    public static void main(String[] args) {
            System.out.println(AA.NUM);	//只会输出 99 ,不会输出“静态代码块被加载...”
        }
    
    class AA{
        public final static int NUM = 99;	//如果删除了final,则会输出:“静态代码块被加载...” 和 99
        static {
            System.out.println("静态代码块被加载...");
        }
    }
    
  7. 包装类(Integer,Double,Float,Boolean 等都是 final ),String 也是 final 类(不允许他们被继承)。

在这里插入图片描述

5.3 课堂练习

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

6. 抽象类(abstract)

6.1 抽象类基本介绍

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

  1. 用 abstract 关键字来修饰一个类时,这个类就叫抽象类,语法如下:

    访问修饰符 abstract 类名 {…};

  2. 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法,语法如下:

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

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

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

6.2 抽象类使用细节

  1. 抽象类不能被实例化,如 new A();错误的

  2. 抽象类不一定要包含 abstract 方法。

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

  4. abstract 只能修饰类和方法,不能修饰属性和其他的

  5. 抽象类可以有任意成员(抽象类的本质还是类),比如非抽象方法、构造器、静态属性等等

  6. 抽象方法不能有主体,即 {…}

  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类

  8. 抽象方法不能使用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的,如下:

    abstract class H{
        private abstract void hi();//错误的
        public final abstract void hi1();//错误的,因为final修饰了方法后,该方法不允许被重写。而abstract本身就是要让子类去重写的
        public static abstract void hi2();//错误的
    }
    

6.3 课堂练习

在这里插入图片描述
在这里插入图片描述

public class Abstract01 {
    public static void main(String[] args) {
		Manager Em1 = new Manager("二狗", "929751708", 50000, 80000);
        Em1.work();
    }
}

class Manager extends Employee{
    private double bonus;

    public Manager(String name, String id, double salary, double bonus) {//构造器
        super(name, id, salary);
        this.bonus = bonus;
    }

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

abstract class Employee{
    private String name;
    private String id;
    private double salary;

    public Employee(String name, String id, double salary) {//构造器
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    
    //省略get和set方法

    public abstract void work();	//抽象方法
}

7. 模板设计模式

7.1 说明

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

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

7.2 例子

需求:

  1. 有多个类,完成不同的任务 job
  2. 要求统计得到各自完成任务的时间
  3. 请编程实现

想法:

  1. 先用最容易想到的方法(每个类中用 System.currentTimeMillis() ),如下:

    class AA{
        public void job(){
            long start = System.currentTimeMillis();
            long num = 0;
            for (int i = 0; i < 100000; i++) {
                num += i;
            }
            long end = System.currentTimeMillis();
            System.out.println("AA 执行的时间为:" + (end - start));
        }
    }
    //每一个类中有三句都是重复的:
    /*
    long start = System.currentTimeMillis();
    ...
    long end = System.currentTimeMillis();
    System.out.println("AA 执行的时间为:" + (end - start));
    由此分析我们可以在每一个类中创建一个 calculateTime()的函数,把每一个类中job()方法放到中间,即
    public void calculateTime(){
        long start = System.currentTimeMillis();
        job();
        long end = System.currentTimeMillis();
        System.out.println("AA 执行的时间为:" + (end - start));
    }
    虽然简化了一点但是还是有点复杂,因此考虑到抽象类,如2
    */
    
  2. 分析问题,提出使用模板设计模式

public class Abstract02 {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();//这里也是动态绑定机制,即OOP机制

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

abstract  class Template {
    public abstract void job();//抽象方法

    public void calculateTime(){
        long start = System.currentTimeMillis();
        job();  //动态绑定机制,哪个对象调用就跟哪个对象绑定 ,执行到job方法这里是跟对象绑定的,所以回去对象中找该方法
        //main中对象aa调用,跟aa绑定,bb调用,跟bb绑定
        long end = System.currentTimeMillis();
        System.out.println("任务执行的时间:"  + (end - start));
    }
}

class AA extends Template{
    @Override
    public void job() {
        long num = 0;
        for (int i = 0; i < 100000; i++) {
            num += i;
        }
    }
}

class BB extends Template{
    @Override
    public void job() {
        long num = 0;
        for (int i = 0; i < 800000; i++) {
            num += i;
        }
    }
}

8. 接口(Interface)

8.1 接口的基本介绍

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

语法:

interface 接口名{
	//接口的属性
	//接口的方法(1.抽象方法;2.默认实现方法;3.静态方法)
}
class 类名 implements 接口{
	//类的属性;
	//类的方法;
	//必须实现的接口的抽象方法;
}
  1. 在 JDK 7.0 前接口里面的所有方法都没有方法体,即都是抽象方法。

  2. 在 JDK 8.0 后接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。

    //接口
    public interface Interface01 {
        public int n1 = 10; //接口的属性
        
        public void hi();   //接口的抽象方法(此处可以省略abstract关键字)
        
        public static void ok(){//接口的方法声明为静态可以写实体
            System.out.println("ok()......");
        }
        
        default public void cry(){//接口的方法声明为default(默认)可以写实体
            System.out.println("cry()......");
        }
        
        public void say(){//此句错误 ,没有static或者default修饰,默认为abstract,所以不能有方法体
            System.out.println("123.....");
        }
    }
    
    //实现上面接口的类
    class A implements Interface01{//如果一个类implements实现接口,需要将该接口的所有抽象方法都实现
        @Override
        public void hi() {
            System.out.println("hi()......");
        }
    }
    

8.2 接口的使用细节

  1. 接口不能被实例化。抽象类和接口一样 ,都不能被实例化

  2. 接口中的所有方法是 public 方法;接口中抽象方法可以不用 abstract 修饰

    interface IA{
    	void say();	//这句话写完整就是:public abstract void say();
    }
    
  3. 一个普通类实现接口,就必须将该接口的所有方法(除static修饰的方法外)都实现(可以使用 Alt + enter 或者 Alt + Insert 来实现)

  4. 抽象类实现接口,可以不用实现接口中的抽象方法

  5. 一个类同时可以实现多个接口

    interface IB{}
    interface IA{}
    
    class AA implements IB,IC{
    	//实现接口的方法
    }
    
  6. 接口中的属性,只能是 final 的,而且是public static final修饰的(但是 public static final 可以省略)

    public class Test {
        public static void main(String[] args) {
            System.out.println(IA.a);//直接通过 类名.属性名 访问,证明了静态static
            IA.a = 3;//这句话会报错,说明不能修改 IA.a 的值,证明了final
        }
    }
    
    interface IA{
        int a = 1;	//实际上是:public static final int a = 1;
    }
    
  7. 接口中属性的访问形式:接口名.属性名

  8. 接口不能继承类,但是接口可以继承接口(接口与接口的关系可以是继承 extends,接口与类的关系只能是实现 implements)。如果接口 A 继承了接口B ,类 C 实现了接口 A,那么 C 也实现了 父类接口 B。

  9. 接口的修饰符只能是 public 和 默认,这一点跟类的修饰符是一样的

8.3 课堂练习

在这里插入图片描述

语句:class B implements A 也表示 B 继承了 A 的属性

8.4 实现接口 VS 继承类

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

接口与继承解决的问题不同:

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

接口比继承更加灵活

接口在一定程度上实现代码解耦(即:接口规范性 + 动态绑定)

public class Test {
    public static void main(String[] args) {
        littleMonkey wukong = new littleMonkey("wukong");
        wukong.climing();   //通过继承父类可以直接调用父类的方法
        wukong.swimming();  //通过实现接口,来扩展方法
        wukong.fly();
    }
}

class Monkey{
    private String name;

    public Monkey(String name) {
        this.name = name;
    }

    public void climing(){
        System.out.println(getName() + " 学会了爬树......");
    }

    public String getName() {
        return name;
    }
}

interface Fishable{
    void swimming();
}

interface Birdable{
    void fly();
}

class littleMonkey extends Monkey implements Fishable,Birdable{
    public littleMonkey(String name) {
        super(name);
    }

    @Override
    public void swimming() {
        System.out.println(getName() + " 学会了游泳......");
    }

    @Override
    public void fly() {
        System.out.println(getName() + " 学会了飞翔......");
    }
}

8.5 接口的多态

参考笔记9中的多态的应用。接口的多态主要体现在三个地方:1. 多态参数;2. 多态数组;3. 接口的多态传递

  1. 多态参数。接口引用可以指向实现了接口的类的对象。

    //例子1
    public class Test {
        public static void main(String[] args) {
            //接口类型的变量 ia 可以指向实现了 IA 接口的类的对象实例 
            IA ia = new Car();
            ia = new Monster();
            
            //对比继承的多态,父类类型的变量 a 可以指向继承AAA的子类的对象实例
            AA a = new BB();
            a = new CC();
        }
    }
    interface IA{}
    class Monster implements IA{}
    class Car implements IA{}
    
    class AA{}
    class BB extends AA{}
    class CC extends AA{}
    
    //例子2
    public class All {
        public static void main(String[] args) {
            //创建手机,相机对象,两者都实现了USB
            Phone phone = new Phone();
            Camera camera = new Camera();
            //创建计算机
            Computer computer = new Computer();
            //接口引用可以指向实现了接口的类的对象
            computer.work(phone);//把手机接入到计算机(public void work(USB usb) 所以类似于向上转型)
            computer.work(camera);//把相机接入到计算机
    
        }
    }
    
    interface USB{
    	void start();
        void stop();
    }
    
    class Phone implements USB {//Phone 实现了 USB接口
        @Override
        public void start() {
            System.out.println("Phone start......");
        }
    
        @Override
        public void stop() {
            System.out.println("Phone stop......");
        }
    }
    
    class Camera implements USB {//Camera 实现了 USB接口
        @Override
        public void start() {
            System.out.println("Camera start......");
        }
    
        @Override
        public void stop() {
            System.out.println("Camera stop......");
        }
    }
    
    class Computer {
        //1.USB usb形参是接口类型 USB
        public void work(USB usb){//usb可以是 Phone和Camera对象的实例,因为他们实现了接口
            usb.start();
            usb.stop();
        }
    }
    
  2. 多态数组。演示一个案例:给USB 数组中存放 Phone 和 Camera 对象,Phone 类还有一个特有的方法 call(),请遍历 USB 数组,如果是 Phone 对象,除了调用 USB 接口定义的方法外,还需要调用 Phone 特有方法 call。

    public class Test {
        public static void main(String[] args) {
            USB[] usbs = new USB[2];
            usbs[0] = new Phone();
            usbs[1] = new Camera();
            for (int i = 0; i < usbs.length; i++) {
                usbs[i].work();//遵从动态绑定机制
                //和讲多态那里一样,需要进行类型的向下转型
                if(usbs[i] instanceof Phone){//判断他的运行类型是Phone
                    ((Phone)usbs[i]).call();//将编译类型转换为Phone才能调用Phone中的特有方法
                }
            }
        }
    }
    
    interface USB{
        void work();
    }
    
    class Phone implements USB{
        @Override
        public void work() {
            System.out.println("Phone working......");
        }
    
        public void call(){
            System.out.println("Phone can call......");
        }
    }
    
    class Camera implements USB{
        @Override
        public void work() {
            System.out.println("Camera working......");
        }
    }
    
  3. 接口的多态传递现象。接口存在多态传递现象。

    public class Test {
        public static void main(String[] args) {
            IB ib = new AA();
            IA ia = new AA();//只有接口IB继承了接口IA才能这么用
        }
    }
    
    interface IA{
        void hi();
    }
    interface IB extends IA{}   //接口IB继承接口IA
    
    class AA implements IB{
        @Override
        public void hi() {
            
        }
    }
    

8.4 课堂练习

//看看程序中的错误,并改正
public class Test {
}
interface A{
    int x = 0;//想到等价于:public final static int x = 0;
}

class B{
    int x = 1;
}

class C extends B implements A{
    public void  pX(){
        System.out.println(x);//错误,原因不明确x是谁
    }

    public static void main(String[] args) {
        new C().pX();
    }
}
/*
*如果要访问接口A中的x,用:A.x;
*如果要访问父类B中的x,用:super().x
*/

在这里插入图片描述


9. 内部类

9.1 内部类的基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。类的五大成员:属性、方法、构造器、代码块、内部类内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

基本语法:

class Outer{//外部类
	class Inner{//内部类
	}
}

class Other{//外部其他类
}
class Outer{//外部类
    private int a;//1.属性
    
    public void m(){//2.方法
        System.out.println("成员方法...");
    }

    public Outer(int a) {//3.构造器
        this.a = a;
    }
    
    {//4.代码块
        System.out.println("代码块...");
    }
    
    class Inner{//5.内部类
        
    }
}

9.2 内部类的分类

定义在外部类的局部位置上(比如方法内):

  1. 局部内部类(有类名)
  2. 匿名内部类(没有类名,十分重要!!!!!!!!!!!!!)

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

  1. 成员内部类(没有 static 修饰)
  2. 静态内部类(使用 static 修饰)

9.3 局部内部类

说明:局部内部类是定义在外部类的局部位置,比如定义在方法 or 代码块中,并且有类名

  1. 可以直接访问外部类的所有成员,包括私有的
  2. 不能添加访问修饰符,因为它(局部内部类)的地位就是一个局部变量。局部变量是不能用访问修饰符。但是可以使用 final 修饰,因为局部变量也可以使用 final。
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 局部内部类访问外部类的成员——直接访问;
  5. 外部类访问局部内部类的成员——创建对象,再访问(注意必须在作用域内)
  6. 外部其它类不能访问局部内部类,因为局部内部类地位是一个局部变量
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果局部内部类想访问外部类的成员,则可以使用(外部类名.this.成员 )去访问

记住:①、局部内部类定义在方法/代码块中。②、作用域在方法体或者代码块中。③、本质仍然是一个类

//
public class InnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.m1();//输出800 100
        System.out.println("outer hashCode = " + outer);
    }
}

class Outer{//外部类
    private int n1 = 100;
    private void m2(){//私有方法
        System.out.println("Outer m2()......");
    }
    public void m1(){
        //1.局部内部类是定义在外部类的局部位置,通常在方法
        //3.不能添加访问修饰符,但是可以使用final修饰
        //4.作用域:仅仅在定义它的方法或代码块中
        final class Inner{
            private int n1 = 800;//定义一个跟外部类重名的属性,证明第7条
            public void f1(){
                //2.局部内部类可以直接访问外部类的所有成员,包括私有
                //7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用Outer.this.n1
                //其中 哪个对象调用了m1(), Outer.this就是哪一个对象
                System.out.println("内部类的 n1 = " + n1 + "外部类的 n1 = " + Outer.this.n1);
                System.out.println("Outer.this hashCode = " + Outer.this);
                m2();
            }
        }
        //5.外部类在方法中,可以创建Inner对象,然后调用方法即可
        Inner inner = new Inner();
        inner.f1();
    }

}

9.4 匿名内部类

匿名内部类的使用:①、本质是类;②、内部类;③、该类没有名字(实际上底层创建了,只是一闪而过);④、同时还是一个对象

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

匿名内部类的使用细节:

  1. 匿名内部类的基本语法:
new 类或接口(参数列表){
	类体
};
public class InnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.method();
    }
}

class Outer {//外部类
    private int n1 = 10;//属性

    public void method() {//方法
        //基于接口的匿名内部类
        //tiger的编译类型?IA
        //tiger的运行类型?即匿名内部类 XXXX => Outer$1
        /*
        *   我们看底层:会分配 类名:Outer$1(即外部类类名+$1)
        *     class XXXX implements IA{
                @Override						//只要重写了方法,就是一个新的东西了
                public void cry() {
                    System.out.println("老虎叫唤......");
                };
              }
         */
        //JDK 底层在创建了匿名内部类 Outer$1 后,立马就创建了 Outer$1 实例(对象),并把地址返回给了 tiger
        //匿名内部类使用一次就不能再使用了,但是返回的实例化对象tiger可以重复使用
        //其实也相当于匿名内部类执行了IA接口:class Outer$1 implements IA{}
        //注意:接口不能被实例化,单独一个 IA tiger = new IA();是会报错的,所以下面这种确实是创建了一个新类
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤......");
            }
        };
        System.out.println("tiger 的运行类型 = " + tiger.getClass());//getClass()获得运行类型
        tiger.cry();

        //基于类的匿名内部类
        //1.father的编译类型?Father
        //2.father的运行类型?Outer$2
        //3.底层会创建匿名内部类
        /*
        class Outer$1 extends Father{
            @Override						//对于类,匿名内部类中,只要重写了原来类的方法,就是一个新类了,并且继承原类
            public void test() {
                System.out.println("匿名内部类重写了 test 方法");
            }
        }
         */
        //4.同时也直接返回了匿名内部类 Outer$2的对象
        //5.注意("jack")参数列表会传递给构造器
        //其实也相当于匿名内部类继承了Father类:class Outer$2 extends Father{}
        //如果是Father father = new Father("jack");那么father的运行类型还是Father类,并且没有创建匿名内部类
        Father father = new Father("jack") {
            @Override
            public void test() {//可以不用重写这个方法
                System.out.println("匿名内部类重写了 test 方法");
            }
        };
        System.out.println("father对象的运行类型 = " + father.getClass());
        father.test();

        //基于抽象类的匿名内部类
        Animal animal = new Animal() {
            @Override
            void eat() {//因为此方法是抽象方法,所以必须重写
                System.out.println("小狗吃骨头......");
            }
        };
        System.out.println("animal对象的运行类型 = " + animal.getClass());//Outer$3
        animal.eat();
    }
}

interface IA {//接口
    public void cry();
}

class Father {//类
    public Father(String name) {//构造器
    }
    public void test() {//方法
    }
}

abstract class Animal{//抽象类
    abstract void eat();
}
  1. 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
public class InnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f1();//输出:匿名内部类重写了 hi()方法
    }
}

class Outer{
    private int n1 = 99;
    public void f1(){
        //创建一个基于类的匿名内部类
        Person person = new Person() {//person匿名内部类,运行类型是Outer$1
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi()方法");
            }
        };
        person.hi();//动态绑定:编译类型是Person,运行类型是Outer$1

        //也可以直接调用,匿名内部类本身也是返回对象
        //相当于class 匿名内部类 extends Person
        new Person(){//新的匿名内部类,运行类型Outer$2
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi()方法......");
            }

            @Override
            public void ok(String str) {
                super.ok(str);//匿名内部类的父类是Person(对super的解释)
            }
        }.ok("jack");//输出:Person ok() jack
    }
}

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

    public void ok(String str){
        System.out.println("Person ok() " + str);
    }
}
/*
匿名内部类重写了 hi()方法
Person ok() jack
*/
  1. 可以直接访问外部类的所有成员,包括私有成员
  2. 不能添加访问修饰符,因为匿名内部类的地位就是局部变量(同局部内部类)
  3. 作用域:仅仅在定义它的方法中或者代码块中
  4. 匿名内部类访问外部成员——直接访问
  5. 外部其它类不能访问匿名内部类
  6. 如果外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员 )去访问

9.5 匿名内部类的最佳实践

匿名内部类当做实参直接传递,简洁高效

public class InnerClass{
    public static void main(String[] args) {
        f1(new IA() {
            @Override
            public void show() {
                System.out.println("这是一幅名画...");
            }
        });
        //此句传递的实参是一个匿名内部类返回的对象(即 f1(括号内的所有) ),这个匿名内部类的编译类型:IA,运行类型:InnerClass$1
        //并且IA$1重写了show方法
    }
    //静态方法,形参是接口类型
    public static void f1(IA  ia){
        ia.show();
    }
}

interface IA{
    void show();
}

在这里插入图片描述

public class InnerClass{
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
		
        /*
        1.传递的是一个实现了Bell接口的匿名内部类,类名为InnerClass$1
        2.该匿名内部类重写了ring
        3.Bell bell = new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        }
        即public void alarmclock(Bell bell)中的bell形参接受的实参是上面那一块
        bell的编译类型是Bell,运行类型是InnerClass$1
        */
        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();//动态绑定
    }
}

9.6 成员内部类

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

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

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

  3. 作用域:和外部类的其他成员 一样,为整个类体(与前面两个不同)

  4. 成员内部类访问外部类成员——直接访问

  5. 外部类访问成员内部类——创建对象,再访问

  6. 其他外部类访问成员内部类——两种方式,如下代码

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

    public class InnerClass02 {
        public static void main(String[] args) {
            Outer outer = new Outer();
            outer.t1();//66 张三 10
            
            //6.外部其他类使用成员内部类的两种方式
            //第一种方式: outer.new Inner(); 其中outer是外部类的实例化对象,相当于把 new Inner() 当做是outer的成员
            Outer.Inner inner = outer.new Inner();
            inner.say();//66 张三 10
            
            //第二种方式,在外部类中编写一个方法,返回一个Inner对象
           	//注意两种方式的编译类型都是:Outer.Inner
            Outer.Inner innerinstance = outer.getInnerInstance();
            innerinstance.say();
        }
    }
    
    class Outer{
        private int n1 = 10;
        public String name = "张三";
    
        //1.注意成员内部类,是定义在外部内的成员位置上
        //2.可以添加任何访问修饰符(public、private...),因为它的地位是一个成员
        public class Inner{
            private int n1 = 66;
            public void say(){
                //可以直接访问外部内的所有成员,包括私有的
                System.out.println("n1= " + n1 + " name = " + name + "外部类的n1 = " + Outer.this.n1);
            }
        }
        
        //返回一个Inner()实例
        public Inner getInnerInstance(){
            return new Inner();
        }
    
        //写调用方法
        public void t1(){
            Inner inner = new Inner();
            inner.say();//外部类访问成员内部类的方法:创建对象,再访问
        }
    }
    

9.7 静态内部类

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

  1. 可以直接访问外部类的所有静态成员,包含私有的,但**不能直接访问非静态成员**
  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
  3. 作用域:同其他的成员,为整个类体
  4. 静态内部类访问外部类——直接访问所有静态成员
  5. 外部内访问静态内部类——创建对象,再访问
  6. 外部其他类访问静态内部类——两种方式
  7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问(这里因为静态内部类只能访问外部类的静态成员,而静态成员的方式是通过 类名.静态成员 的方式里访问的,不用this)
public class InnerClass02 {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.m1();

        //6.外部其他类使用静态内部类
        //方式1:因为静态内部类是可以直接通过类名访问的(前提是满足访问权限)
        Outer.Inner inner = new Outer.Inner();
        inner.say();

        //方式2:写一个方法,可以返回静态内部类的对象实例
        Outer.Inner inner1 = outer.getInner();//这是对象.getInner(),这个getInner()是一个非静态方法
        inner1.say();

        Outer.Inner inner_ = Outer.getInner_();//直接通过 外部类名.(静态)返回内部类方法
        inner_.say();
    }
}

class Outer{
    private int n1 = 10;
    public static String name = "张三";
    private static void cry(){};

    //Inner就是静态内部类
    //1.方在外部类的成员位置
    //2.使用static修饰
    //4.可以任意添加访问修饰符(public、prvate...)
    public static class Inner {
        private static String name = "张二娃";
        public void say(){
            //3.可以直接访问外部类的所有 静态 成员,包括私有的,但是不能访问非静态成员
            //因为这里外部类的name是静态的,所以直接用 Outer.name访问
            System.out.println("静态内部类的name = " + name + " 外部类的name = " + Outer.name);
            cry();
        }
    }

    //5.作用域:同其他成员,为整个类体
    public void m1(){
        Inner inner = new Inner();
        inner.say();
    }

    //返回内部类对象实例
    public Inner getInner(){
        return new Inner();
    }

    public static Inner getInner_(){
        return new Inner();
    }
}

9.8 课堂练习

在这里插入图片描述

输出为:5,5
ate),因为它的地位就是一个成员
3. 作用域:同其他的成员,为整个类体
4. 静态内部类访问外部类——直接访问所有静态成员
5. 外部内访问静态内部类——创建对象,再访问
6. 外部其他类访问静态内部类——两种方式
7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问(这里因为静态内部类只能访问外部类的静态成员,而静态成员的方式是通过 类名.静态成员 的方式里访问的,不用this)

public class InnerClass02 {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.m1();

        //6.外部其他类使用静态内部类
        //方式1:因为静态内部类是可以直接通过类名访问的(前提是满足访问权限)
        Outer.Inner inner = new Outer.Inner();
        inner.say();

        //方式2:写一个方法,可以返回静态内部类的对象实例
        Outer.Inner inner1 = outer.getInner();//这是对象.getInner(),这个getInner()是一个非静态方法
        inner1.say();

        Outer.Inner inner_ = Outer.getInner_();//直接通过 外部类名.(静态)返回内部类方法
        inner_.say();
    }
}

class Outer{
    private int n1 = 10;
    public static String name = "张三";
    private static void cry(){};

    //Inner就是静态内部类
    //1.方在外部类的成员位置
    //2.使用static修饰
    //4.可以任意添加访问修饰符(public、prvate...)
    public static class Inner {
        private static String name = "张二娃";
        public void say(){
            //3.可以直接访问外部类的所有 静态 成员,包括私有的,但是不能访问非静态成员
            //因为这里外部类的name是静态的,所以直接用 Outer.name访问
            System.out.println("静态内部类的name = " + name + " 外部类的name = " + Outer.name);
            cry();
        }
    }

    //5.作用域:同其他成员,为整个类体
    public void m1(){
        Inner inner = new Inner();
        inner.say();
    }

    //返回内部类对象实例
    public Inner getInner(){
        return new Inner();
    }

    public static Inner getInner_(){
        return new Inner();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值