Java面向对象编程(高级部分)

类变量和类方法(静态变量和静态方法)

类变量(静态变量)

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

传统的方法来解决

思路:

  1. 在main方法中定义一个变量count
  2. 当一个小孩加入游戏后,count++,最后count就记录有多少个小孩玩游戏
package com.zanedu.static_;

public class ChildGame {
    public static void main(String[] args) {

        //定义变量count,统计有多少小孩加入了游戏

        Child child1 = new Child("白骨精");
        child1.join();
        count++;
        

        Child child2 = new Child("狐狸精");
        child2.join();
        count++;
        

        Child child3 = new Child("老鼠精");
        child3.join();
     	count++;
    
    }
}
class Child {
    private String name;

    //定义一个变量count,是一个类变量(静态变量) static 静态
    //Java8存储在静态区(方法区),后面的版本存储在堆中
    //默认只会用一次
    //该变量的最大的特点就是会被Child类的所有的实例对象共享

    public Child(String name) {
        this.name = name;
    }
    public void join() {
        System.out.println(name + "加入了游戏");
    }
}

问题分析:

  1. count是一个独立于对象
  2. 以后我们访问count很麻烦,没有使用到OOP
  3. 因此,我们引出类变量/静态变量
类变量快速入门
  • 思考:如果,设计一个int count 表示总人数,我们在创建一个小孩时,就把count加1,并且count是所有对象共享的ok了,我们使用类变量来解决
package com.zanedu.static_;

public class ChildGame {
    public static void main(String[] args) {

        //的定义变量count,统计有多少小孩加入了游戏

        Child child1 = new Child("白骨精");
        child1.join();
//        count++;
        child1.count++;

        Child child2 = new Child("狐狸精");
        child2.join();
//        count++;
        child2.count++;

        Child child3 = new Child("老鼠精");
        child3.join();
//        count++;
        child3.count++;

        //类变量可以通过类名来访问
        System.out.println("共有" + Child.count + "个小孩加入了游戏");
        System.out.println("child1.count=" + child1.count);
        System.out.println("child2.count=" + child2.count);
        System.out.println("child3.count=" + child3.count);

    }
}
class Child {
    private String name;

    //定义一个变量count,是一个类变量(静态变量) static 静态
    //Java8存储在静态区(方法区),后面的版本存储在堆中
    //默认只会用一次
    //该变量的最大的特点就是会被Child类的所有的实例对象共享
    public static int count = 0;

    public Child(String name) {
        this.name = name;
    }
    public void join() {
        System.out.println(name + "加入了游戏");
    }
}

img

类变量内存布局
  • 问题:静态变量放在哪里?

有些书说在方法区,和JDK版本有关,但是记住一点:

static变量是对象共享,不管static变量在哪里

共识:

  1. static 变量是同一个类所有对象共享
  2. static 类变量,在类加载的时候就生成了
类变量的基本介绍

什么是类变量

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

如何定义类变量

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

如何访问类变量

  • 类名.类对象名(推荐)
  • 对象名.类变量名
  • 注意:静态变量的访问修饰符的访问权限和范围和普通属性是一样的

img

img

类变量使用注意事项和细节讨论
  1. 什么时候需要用类变量

当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student(name, static fee)

  1. 类变量与实例变量(普通属性)区别

类变量是该类的所有对象共享的,而实例变量是每个对象独享的

  1. 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  2. 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但Java设计者推荐我们使用类名.类变量名方式访问(前提:满足访问修饰符的访问权限和范围)
  3. 实例变量不能通过类名.类变量名的方式访问
package com.zanedu.static_;

public class StaticDetail {
    public static void main(String[] args) {
        B b = new B();
        System.out.println(B.n1);//error
        System.out.println(B.n2);

    }
}

class B {
    public int n1 = 100;
    public static int n2 = 200;
}

img

  1. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
package com.zanedu.static_;

public class StaticDetail {
    public static void main(String[] args) {

        //静态变量是类加载的时候,就创建了,所以我们没有创建对象实例
        //也可以通过类名.类变量名来访问
        System.out.println(C.address);
    }
}

class C {
    public static String address = "北京";
}

img

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

类方法(静态方法)

类方法的基本介绍
  • 类方法也叫静态方法

类方法的语法形式**:**

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

类方法的调用

  • 使用方式:

    • 1. 类名.类方法名
    • 2. 对象名.类方法名
    • 前提:满足访问修饰符的访问权限和范围
类方法经典的使用场景
  • 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率

比如:工具类中的方法utils、Math类、Arrays类、Collections集合类

  • 小结:

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

img

类方法使用注意事项和细节讨论
  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区

类方法中无this的参数

普通方法中隐含着this的参数

  1. 类方法可以通过类名调用,也可以通过对象名调用
  2. 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
package com.zanedu.static_;

public class StaticMethodDetail {
    public static void main(String[] args) {
        D.hi();
        //非静态方法,不能通过类名调用
        D.say();//error
    }
}

class D {
    
    public void say() { //非静态方法

    }

    public static void hi() { //静态方法,类方法
        //类方法中不允许使用和对象有关的关键字
        //比如this和super,普通方法(成员方法)可以
        //        this.n1;
        //        super.n1;
        //        return;
        //        System.out.println("1");
    }
}

img

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

img

img

  1. 类方法(静态方法)中只能访问静态变量或静态方法
package com.zanedu.static_;

public class StaticMethodDetail {
    public static void main(String[] args) {
        D.hi();
        //非静态方法,不能通过类名调用
//        D.say();//error
        new D().say();//ok
    }
}

class D {
    public int n1 = 100;

    private static int n2 = 200;

    public void say() { //非静态方法

    }

    public static void hi() { //静态方法,类方法
        //类方法中不允许使用和对象有关的关键字
        //比如this和super,普通方法(成员方法)可以
//        this.n1;
//        super.n1;
//        return;
//        System.out.println("1");
    }

    //类方法(静态方法)中 只能访问 静态变量或静态方法
    //口诀:静态方法只能访问静态方法或变量
    public static void hello() {
        System.out.println(n2);
        System.out.println(D.n2);
//        System.out.println(this.n2);//error
        hi();
//        say();//error
    }
}

img

img

  1. 普通成员方法,既可以访问非静态成员,也可以访问静态成员
package com.zanedu.static_;

public class StaticMethodDetail {
    public static void main(String[] args) {
        D.hi();
        //非静态方法,不能通过类名调用
//        D.say();//error
        new D().say();//ok
    }
}

class D {
    public int n1 = 100;

    private static int n2 = 200;

    public void say() { //非静态方法

    }

    public static void hi() { //静态方法,类方法
        //类方法中不允许使用和对象有关的关键字
        //比如this和super,普通方法(成员方法)可以
//        this.n1;
//        super.n1;
//        return;
//        System.out.println("1");
    }

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

img

  • 小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(但必须遵守访问权限)

补充:static修饰的方法不能被重写

理解main方法语法

深入理解main方法

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

  1. main方法是Java虚拟机调用的
  2. Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  4. 该方法接受String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
  5. Java 执行的程序 参数1 参数2 参数3
  • args就是在执行程序的时候传进去的

  • 将三个字符串打包成一个数组,然后传入进去

img

img

特别提示:

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

public class Main01 {

    //静态的变量/属性
    private static String name = "zanedu";
    
    //静态方法
    public static void hi() {
        System.out.println("Main01 的hi方法");
    }
    
    public static void main(String[] args) {
        //可以直接使用name
        //静态方法可以访问本类的静态成员
        System.out.println(name);
        hi();
    }
}

img

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

public class Main01 {

    //静态的变量/属性
    private static String name = "zanedu";
    //非静态的变量/属性
    private int n1 = 10000;

    //静态方法
    public static void hi() {
        System.out.println("Main01 的hi方法");
    }

    //非静态方法
    public void ok() {
        System.out.println("Main01 的ok方法");
    }

    public static void main(String[] args) {
        //可以直接使用name
        //静态方法可以访问本类的静态成员
        System.out.println(name);
        hi();

        //静态方法main,不能访问本类的非静态方法
//        System.out.println(n1);
//        ok();

        //静态方法main 要访问本类的非静态成员,需要先创建对象,再调用即可
        Main01 main01 = new Main01();
        System.out.println(main01.n1);
        main01.ok();
    }
}
  • 不能直接访问该类中的非静态成员

img

img

  • 必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

img

  • 补充:在idea里传递参数

img

代码块

基本介绍

  • 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来
  • 注意:但和方法不同,它没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或****创建对象时隐式调用

基本语法

[修饰符]{

代码

};

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

代码块的好处和案例演示

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

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

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

    //构造器 - 重载
    //解读
    //(1)下面的三个构造器都有相同的语句
    //(2)这样代码看起来比较冗余
    //(3)我们可以把相同的语句放到一个代码块中,即可
    //(4)这样当我们不管调用哪个构造器创建对象,都会先调用代码块的内容
    //(5)代码块调用的顺序优先于构造器
    static {
        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) {
        System.out.println("Movie(String name, double price)构造器被调用....");
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {
        System.out.println("Movie(String name, double price, String director)构造器被调用...");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

img

img

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

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

img

  1. 类什么时候被加载(重点)

    1. 创建对象实例时(new)
    2. 创建子类对象实例,父类也会被加载
    3. 使用类的静态成员时(静态属性、静态方法)
package com.zanedu.codeblock_;

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 创建对象实例时(new)
//        AA aa = new AA();

        //2. 创建子类对象实例,父类也会被加载,而且父类先被加载,子类再被加载
//        AA aa2 = new AA();

        //3. 使用类的静态成员时(静态属性、静态方法)
//        System.out.println(Cat.n1);
    }
}

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

class Cat extends Animal {

    public static int n1 = 999;//静态属性

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

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

class AA extends BB {
    //静态代码块
    static {
        System.out.println("AA 的静态代码块1被执行....");
    }
}
  • 创建对象实例时(new)

img

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

img

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

img

img

  1. **普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次,就会被调用一次。**如果只是使用类的静态成员,那么普通代码块并不会执行
package com.zanedu.codeblock_;

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //static代码块,是在类加载时,执行的,并且只会执行一次
//        DD dd = new DD();
//        DD dd1 = new DD();

        //普通的代码块,在创建对象实例时,会被隐式的调用
        //被创建一次,就会调用一次
        //如果只是使用类的静态成员时,普通代码块并不会执行
        System.out.println(DD.n1);//888,静态代码块一定会执行
    }
}

class DD {
    public static int n1 = 888;//静态属性
    //静态代码块
    static {
        System.out.println("DD 的静态代码块1被执行....");
    }
    //普通代码块,在new对象时,被调用,而且是每创建一个对象,就调用一次
    //类加载跟普通代码块没有任何关系
    //普通代码块是构造器的补充
    {
        System.out.println("DD 的普通代码块....");
    }
}
  • static代码块,是在类加载时执行的,并且只会执行一次

img

  • 普通代码块,创建对象一次就会执行一次

img

img

小结:

  1. static代码块是类加载时,执行,并且只会执行一次

  2. 普通代码块是在创建对象时调用的,创建一次,就会调用一次

  3. 类加载的3种情况,重点

  4. 创建一个对象时,在一个类调用顺序是:(重点、难点)

    1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
    2. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
    3. 调用构造方法(即构造器)【构造器的优先级是最低的】
package com.zanedu.codeblock_;

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}

class A {
    //静态属性的初始化
    private static int n1 = getN1();

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

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

img

    1. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
package com.zanedu.codeblock_;

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}

class A {
    private int n2 = getN2();//普通属性的初始化
    { //普通代码块
        System.out.println("A 的普通代码块01");
    }

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

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

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

    public int getN2() { //普通方法
        System.out.println("getN2被调用");
        return 200;
    }
}

img

    1. 调用构造方法(即构造器)
package com.zanedu.codeblock_;

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
    }
}

class A {
    //无参构造器
    public A() {
        System.out.println("A() 无参构造器被调用");
    }

    private int n2 = getN2();//普通属性的初始化
    { //普通代码块
        System.out.println("A 的普通代码块01");
    }

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

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

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

    public int getN2() { //普通方法
        System.out.println("getN2被调用");
        return 200;
    }
}

img

img

  • 注意:不管你构造器定义在前在后,都是最后调用
  1. **构造器的最前面其实****隐含了super()调用普通代码块,**静态相关的代码块,属性初始化,在类加载时,就执行完毕了,因此是优先于构造器和普通代码块执行的

class A {

public A() { //构造器

//这里有隐藏的执行要求 - 不显示

//(1)super();

//(2)调用普通代码块

System.out.println(“OK”);

}

}

package com.zanedu.codeblock_;

public class CodeBlockDetail03 {
    public static void main(String[] args) {
        new BBB();
    }
}

class AAA { //父类Object
    {
        System.out.println("AAA 的普通代码块");
    }
    public AAA() {
        //(1)super();
        //(2)调用本类的普通代码块
        System.out.println("AAA() 构造器被调用...");
    }
}
class BBB extends AAA {
    {
        System.out.println("BBB 的普通代码块");
    }
    public BBB() {
        //(1)super();
        //(2)调用本类的普通代码块
        System.out.println("BBB() 构造器被调用");
    }
}
  • AAA没有普通代码块

img

  • AAA有普通代码块

img

  1. 创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:

  2. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)

  3. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)

  4. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

  5. 父类的构造方法(即构造器)

  6. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

  7. 子类的构造方法(即构造器)

package com.zanedu.codeblock_;

public class CodeBlockDetail04 {
    public static void main(String[] args) {
        //说明
        //(1)进行类的加载
        //1.1 先加载父类AAAA
        //1.2 再加载子类BBBB
        //(2)创建对象
        //2.1 从子类的构造器开始
        new BBBB();//对象
        /**
         * getVal01方法被调用
         * AAAA的一个静态代码块....
         * getVal03方法被调用
         * BBBB的一个静态代码块....
         * AAAA的一个普通代码块....
         * getVal02方法被调用
         * AAAA 的构造器
         * BBBB的一个普通代码块....
         * getVal04方法被调用
         * BBBB 的构造器
         */
    }
}

class AAAA {
    private static int n1 = getVal01();
    static {
        System.out.println("AAAA的一个静态代码块....");
    }
    {
        System.out.println("AAAA的一个普通代码块....");
    }
    private int n3 = getVal02();
    public static int getVal01() {
        System.out.println("getVal01方法被调用");
        return 10;
    }

    public int getVal02() {
        System.out.println("getVal02方法被调用");
        return 10;
    }

    public AAAA() {
        //隐藏了
        //super()
        //普通代码块....
        System.out.println("AAAA 的构造器");
    }
}

class BBBB extends AAAA{
    private static int n2 = getVal03();
    static {
        System.out.println("BBBB的一个静态代码块....");
    }
    {
        System.out.println("BBBB的一个普通代码块....");
    }
    private int n3 = getVal04();
    public static int getVal03() {
        System.out.println("getVal03方法被调用");
        return 10;
    }

    public int getVal04() {
        System.out.println("getVal04方法被调用");
        return 10;
    }

    public BBBB() {
        //隐藏了
        //super();
        //普通代码块
        System.out.println("BBBB 的构造器");
    }
}

img

  1. 静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
package com.zanedu.codeblock_;

public class CodeBlockDetail04 {
    public static void main(String[] args) {
        new C02();
    }
}

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

    private void m1() {

    }
    private static void m2() {

    }
    static {
        //静态代码块,只能调用静态成员
//        System.out.println(n1);//error
        System.out.println(n2);//ok
//        m1();//error
        m2();
    }
    {
        //普通代码块,可以使用任意成员
        System.out.println(n1);//ok
        System.out.println(n2);//ok
        m1();//ok
        m2();//ok
    }
}
  • 静态代码块不能调用非静态成员

img

img

单例设计模式

什么是设计模式

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

什么是单例模式

单例(单个的实例)

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

单例模式应用实例

步骤:

  1. 构造器私有化 ==> 防止直接new
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法 getInstance
  • 饿汉式
package com.zanedu.single_;

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

        System.out.println(girlFriend == girlFriend1);//true

        //System.out.println(GirlFriend.n1);
    }
}

//有一个类,GirlFriend
//只能有一个女朋友
class GirlFriend {
    private String name;

    public static int n1 = 100;

    //为了能够在静态方法中,返回 gf 对象,需要将其修饰为static
    //对象,通常是重量级对象,饿汉式可能造成了创建了对象,但是没有使用
    private static GirlFriend gf = new GirlFriend("小红");
    //如何保证我们只能创建一个GirlFriend对象
    //步骤:【单例模式 - 饿汉式】 - 还没用到这个对象,但是这个对象已经创建好了
    //1. 将构造器私有化
    //2. 在类的内部直接创建(该对象是static)
    //3. 提供一个公共的静态方法,返回gf对象
    private GirlFriend(String name) {
        this.name = name;
    }
    public static GirlFriend getInstance() {
        return gf;
    }

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

img

  • 懒汉式
package com.zanedu.single_;

/**
 * 演示懒汉式的单例模式
 */
public class SingleTon02 {
    public static void main(String[] args) {
//        System.out.println(Cat.n1);
        Cat cat = Cat.getInstance();
        System.out.println(cat);

        //再次调用getInstance()
        Cat cat1 = Cat.getInstance();
        System.out.println(cat1);

        System.out.println(cat == cat1);//true
    }
}

//希望在程序运行过程中,只能创建一个Cat对象
//使用单例模式
class Cat {
    private String name;
    public static int n1 = 999;
    private static Cat cat; //默认是null

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

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

img

饿汉式 VS 懒汉式

  1. 二者最主要的区别在于创建对象的时机不同,饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
  3. 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
  4. 在外面JavaSE标准类中,java.lang.Runtime就是经典的单例模式

final关键字

基本介绍

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

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

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

public class Final01 {
    public static void main(String[] args) {
    	E e = new E();
       	e.TAX_RATE = 0.09;
    }
}

//如果我们要求A类不能被其他类继承,
//可以使用final修饰A类即可
final class A {

}

class B extends A {

}
class C {
   //如果我们要求hi方法不能被子类重写
   //可以用final修饰 hi方法
   public final void hi() {

   }
}
class D extends C {
   @Override
   public void hi() {
       System.out.println("重写了C类的hi方法");
   }
}

class E {
    public final double TAX_RATE = 0.08;
}

// 当不希望某个局部变量被修改,可以使用final修饰
class F {
   public void cry() {
       //这时,NUM也被称为局部常量
       final double NUM = 0.01;
       NUM = 0.9;
       System.out.println("NUM=" + NUM);
   }
}
  • 要求A类不能被其他类继承

img

  • 父类方法不能被子类重写

img

  • 不希望类的某个属性的值被修改

img

  • 不希望局部变量被修改

img

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

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

img

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

    1. 定义时:如public final double TAX_RATE = 0.08;
    2. 在构造器中
    3. 在代码块中
package com.zanedu.final_;

public class FinalDetail01 {
    public static void main(String[] args) {
        CC cc = new CC();

        new EE().cal();
    }
}

class AA {
    /*
    1. 定义时,如public final double TAX_RATE = 0.08;
    2. 在构造器中
    3. 在代码块中
     */
    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;
    }
}
  • 常量必须初始化

img

  • 三种地方的定义

img

  1. 如果final修饰的属性是静态的,则初始化的位置只能是

    1. 定义时
    2. 在静态代码块,不能再构造器中赋值
package com.zanedu.final_;

public class FinalDetail01 {
    public static void main(String[] args) {
        CC cc = new CC();

        new EE().cal();
    }
}

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

//    public BB() {
//        TAX_RATE3 = 8.8;
//    }

    static {
        TAX_RATE2 = 3.3;
    }
}
  • 在类加载的时候就要初始化了,而在构造器中只有创建对象才能调用,因此不能在构造器中赋值

img

  1. final类不能继承,但是可以实例化对象
  2. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
package com.zanedu.final_;

public class FinalDetail01 {
    public static void main(String[] args) {
        CC cc = new CC();

        new EE().cal();
    }
}

//final类不能继承,但是可以实例化对象
final class CC {}
//如果类不是final类,但是含有final方法,则该方法虽然不能被重写,但是可以被继承
//即仍然遵守继承的机制
class DD {
    public final void cal() {
        System.out.println("cal() 方法");
    }
}

class EE extends DD {

}

img

  1. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法了
final class AAA {
    //一般来说,如果一个类已经时final类了,就没有必要再将方法修饰成final方法
//    public final void cry() {}
}
  1. final不能修饰构造方法(即构造器)
  2. final 和 static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
package com.zanedu.final_;

public class FinalDetail02 {
    public static void main(String[] args) {
        System.out.println(BBB.num);

        //包装类,String是final类,不能被继承
//        Double
    }
}

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

img

  1. 包装类(Integer、Double、Float、Boolean等都是final),String也是final类

img

img

抽象类

引入

  • 看一个小问题,看一个程序
package com.zanedu.abstract_;

public class Abstract01 {
    public static void main(String[] args) {

    }
}
abstract class Aniaml {
    private String name;

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

    //思考:这里eat实现了,但是没有什么意义
    //即父类方法不确定性的问题
    //====> 考虑将该方法设计为抽象(abstract)方法
    //所谓抽象方法就是没有实现的方法
    //所谓没有实现就是指,没有方法体
    //当一个类中存在抽象方法时,需要将该类声明为abstract类
    //一般来说,抽象类会被继承,由其子类来实现抽象方法
//    public void eat() {
//        System.out.println("这是一个动物,但是不知道吃什么..");
//    }
    public abstract void eat();
}
  • 小结:当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

img

img

  • 正确形式

img

抽象类快速入门

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

抽象类的基本介绍

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

访问修饰符 abstract 类名 {

}

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

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

img

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

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

  1. 抽象类不能被实例化
package com.zanedu.abstract_;

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

abstract class A {
    
}

img

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

img

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

   }
}

img

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

img

  1. 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等
//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {
    public int n1 = 10;
    public static String name = "zan";
    public void hi() {
        System.out.println("hi");
    }
    public abstract void hello();
    public static void ok() {
        System.out.println("ok");
    }
}

img

  1. 抽象方法不能有主体,即不能实现

img

  1. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
abstract class E {
    public abstract void hi();
}

abstract class F extends E {
	//error
}
class G extends E {
    public void hi() { //相当于G子类实现了父类E的抽象方法,所谓实现方法,就是要有方法体

    }
}

img

img

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

img

  • 不能用final

img

  • 不能用static

img

练习题

img

  • Employee类
package com.zanedu.abstract_;

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;
    }

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

    public int getId() {
        return id;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
  • Manager类
package com.zanedu.abstract_;

public class Manage extends Employee {
    private double bonus;

    public Manage(String name, int id, double salary) {
        super(name, id, salary);
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    @Override
    public void work() {
        System.out.println("经理 " + getName() + "工作中");
    }
}
  • CommonEmployee类
package com.zanedu.abstract_;

public class CommonEmployee extends Employee{
    public CommonEmployee(String name, int id, double salary) {
        super(name, id, salary);
    }

    @Override
    public void work() {
        System.out.println("普通员工" + getName() + "工作中");
    }
}
  • AbstractExercise类
package com.zanedu.abstract_;

public class AbstractExercise {
    public static void main(String[] args) {
        Manage manage = new Manage("jack", 999, 200000);
        manage.setBonus(80000);
        manage.work();

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

img

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

基本介绍

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

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

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

实践

需求:

  1. 有多个类,完成不同的任务job
  2. 要求统计得到各自完成任务的实践
  • Template类
package com.zanedu.abstract_;

abstract public class Template { //抽象类 - 模板设计模式

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

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

}
  • AA类
package com.zanedu.abstract_;

public class AA extends Template{

//    public void calculateTime() {
//        //得到开始的时间
//        long start = System.currentTimeMillis();
//        job();
//        //得到结束的时间
//        long end = System.currentTimeMillis();
//        System.out.println("AA 执行时间=" + (end - start));
//    }

    //计算任务
    //1+...+9000000
    public void job() { //这里也去,重写Template的job方法
        long num = 0;
        for (long i = 1; i <= 9000000; i++) {
            num += i;
        }
    }
}
  • BB类
package com.zanedu.abstract_;

public class BB extends Template{

//    public void calculateTime() {
//        //得到开始的时间
//        long start = System.currentTimeMillis();
//        job();
//        //得到结束的时间
//        long end = System.currentTimeMillis();
//        System.out.println("BB 执行时间=" + (end - start));
//    }

    public void job() { //实现Template的抽象方法job
        long num = 0;
        for (long i = 1; i <= 9000000; i++) {
            num *= i;
        }
    }
}
  • TestTemplate类
package com.zanedu.abstract_;

public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime(); //这里需要良好的OOP基础,多态

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

img

接口

引入+快速入门

  • Interface01类
package com.zanedu.interface_;

public class Interface01 {
    public static void main(String[] args) {
        //创建手机、相机对象
        Camera camera = new Camera();
        Phone phone = new Phone();
        //创建计算机
        Computer computer = new Computer();
        computer.work(phone); //把手机接入到计算机

        computer.work(camera); //把相机接入到计算机
    }
}
  • UsbInterface接口
package com.zanedu.interface_;

public interface UsbInterface { //接口
    //规定接口的相关方法 - 人为规定的,即规范
    public void start();
    public void stop();
}
  • Computer类
package com.zanedu.interface_;

public class Computer {
    //编写一个方法,计算机工作
    //解读:
    /*
    1. UsbInterface usbInterface 形参是接口类型UsbInterface
    2. 看到 接受 实现了 UsbInterface接口的类的对象实例
     */
    public void work(UsbInterface usbInterface) {
        //通过接口来调用方法
        usbInterface.start();
        usbInterface.stop();
    }
}
  • Phone类
package com.zanedu.interface_;

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

    @Override
    public void stop() {
        System.out.println("手机停止工作.....");
    }
}
  • Camera类
package com.zanedu.interface_;

public class Camera implements UsbInterface{ //实现接口,就是把接口的方法实现

    @Override
    public void start() {
        System.out.println("相机开始工作....");
    }

    @Override
    public void stop() {
        System.out.println("相机停止工作....");
    }
}

img

基本介绍

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

语法:

interface 接口名 {

//属性

//抽象方法、默认实现方法、静态方法

}

class 类名 implements 接口 {

自己属性;

自己方法;

必须实现的接口的抽象方法

}

  • 小结:

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

  • 特别说明:

    • JDK7.0前,接口里的所有方法都是抽象方法
    • JDK8.0后,接口可以有静态方法、默认方法
  • AInterface接口

package com.zanedu.interface_;

public interface AInterface {
    //写属性
    public int n1 = 10;
    //写方法
    //在接口中,抽象方法可以省略abstract关键字
    public abstract void hi();
    //在jdk8后,可以有默认实现方法,但是需要使用default关键字修饰
    default public void ok() {
        System.out.println("ok()....");
    }
    //在jdk8后,可以有静态方法
    public static void cry() {
        System.out.println("cry()....");
    }
}
  • 默认实现方法,需要用default关键字修饰

img

  • 可以有静态方法

img

深入讨论

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

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

img

  1. 现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现

img

需求:三个程序员,编写三个类,分别完成对Mysql、Oracle、DB2数据库的连接connect、close

  • DBInterface接口
package com.zanedu.interface_;

public interface DBInterface { //项目经理
    public void connect();//连接方法
    public void close();//关闭连接方法
}
  • OracleDB类 - 实现了DBInterface接口
package com.zanedu.interface_;

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

    @Override
    public void close() {
        System.out.println("关闭oracle");
    }
}
  • MysqlDB类 - 实现了DBInterface接口
package com.zanedu.interface_;

public class MysqlDB implements DBInterface{
    @Override
    public void connect() {
        System.out.println("连接mysql");
    }

    @Override
    public void close() {
        System.out.println("关闭mysql");
    }
}
  • Interface03类 - 主类
package com.zanedu.interface_;

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

    public static void t(DBInterface dbInterface) {
        dbInterface.connect();
        dbInterface.close();
    }
}

img

注意事项和细节

  1. 接口不能被实例化(即不能new对象)
package com.zanedu.interface_;

public class InterfaceDetail01 {
    public static void main(String[] args) {
        new IA();//error
    }
}
//1. 接口不能被实例化
interface IA {

}

img

  1. 接口中所有的方法都是public方法,接口中抽象对象,可以不用abstract修饰

void aaa(); ==> 实际上是 abstract void aaa();

//2. 接口中所有的方法是public方法,可以不写,接口中抽象方法,可以不用abstract修饰
interface IA {
   	public abstract void say();
   	void say1();
    protected void say2();//error
    void hi();
}

img

  • 接口中所有的方法都是public方法

img

  • 访问修饰符是默认如何判断 - 在别的实现类里面修改看一下,是否报错

img

  1. 一个普通类实现接口,就必须将该接口的所有方法都实现
//3. 一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alt + enter来解决
interface IA {
//    public abstract void say();
//    void say1();
    void say(); //修饰符 public protected 默认 private
    void hi();
}

class Cat implements IA { // alt + enter
    //将其注释掉就error
    // @Override
    // public void say() {

    // }

    // @Override
    // public void hi() {

    // }
}

img

  1. 抽象类实现接口,可以不用实现接口的方法
interface IA {
//    public abstract void say();
//    void say1();
    void say(); //修饰符 public protected 默认 private
    void hi();
}
//4. 抽象类去实现接口时,可以不实现接口的方法
abstract class Tiger implements IA {

}

img

  1. 一个类同时可以实现多个接口
package com.zanedu.interface_;

public class InterfaceDetail02 {
    public static void main(String[] args) {
        
    }
}
interface IB {
    void hi();
}
interface IC {
    void say();
}

//一个类可以同时实现多个接口
//pig类 同时 实现两个接口IB IC
class Pig implements IB, IC {

    @Override
    public void hi() {

    }

    @Override
    public void say() {

    }
}

img

  1. 接口中的属性,只能是final,而且是 public static final修饰符。比如:int a = 1; 实际上是****public static final int a = 1; (必须初始化)
package com.zanedu.interface_;

public class InterfaceDetail02 {
    public static void main(String[] args) {
        //证明:接口中的属性,是public、static、final 修饰符
        System.out.println(IB.n1); //可以调用说明n1就是static
//        IB.n1 = 30;//不可以改变说明n1是final的
    }
}
interface IB {
    //接口中的属性,只能是final的,而且是public、static、final 修饰符
    int n1 = 10; //等价于 public static final int n1 = 10;
    void hi();
}
interface IC {
    void say();
}
  • 不能改变说明是final

img

  • 只能是public

img

img

  1. 接口中属性的访问形式:接口名.属性名
  2. 接口不能继承其他的类,但是可以继承多个别的接口

interface A extends B, C { }

interface IB {
    //接口中的属性,只能是final的,而且是public、static、final 修饰符
    int n1 = 10; //等价于 public static final int n1 = 10;
    void hi();
}
interface IC {
    void say();
}

//接口不能继承其他的类,但是可以继承多个别的接口
interface ID extends IB, IC {

}
  1. 接口的访问修饰符,只能是 public 和 默认,这点和类的修饰符是一样的
//接口的修饰符只能是public 和 默认,这点和类的修饰符是一样的】
interface IE {}

实现接口 VS 继承类

继承就比如是小猴子继承父类大猴子的能力,会爬树

接口就比如是小猴子通过学习鱼儿,可以游泳

package com.zanedu.interface_;

public class ExtendsVsInterface {
    public static void main(String[] args) {
        LittleMonkey littleMonkey = new LittleMonkey("悟空");
        littleMonkey.climbing();
        littleMonkey.swiming();
        littleMonkey.flying();
    }
}

//接口
interface Fishable {
    void swiming();
}
interface Birdable {
    void flying();
}


//猴子
class Monkey {
    private String name;

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

    public void climbing() {
        System.out.println(name + "会爬树");
    }

    public String getName() {
        return name;
    }
}
//继承
//小结:当子类继承了父类,就自动拥有了父类的功能
//      如果子类需要扩展功能,可以通过实现接口的方式来扩展
//      可以理解 实现接口 是对Java单继承的补充
class LittleMonkey extends Monkey implements Fishable, Birdable{
    public LittleMonkey(String name) {
        super(name);
    }

    @Override
    public void swiming() {
        System.out.println(getName() + "通过学习,可以像鱼儿一样游泳");
    }

    @Override
    public void flying() {
        System.out.println(getName() + "通过学习,可以像鸟儿一样飞翔");
    }
}

img

img

  • 接口和继承解决的问题不同

继承的价值主要在于:解决代码的复用性和可维护性

接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法,即更加的灵活

  • 接口比继承更加灵活

接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系

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

接口的多态特性

  1. 多态参数

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

package com.zanedu.interface_;

public class InterfacePolyParameter {
    public static void main(String[] args) {
        //接口的多态体现
        //接口类型的变量 if01,可以指向实现了IF接口的类的对象实例
        IF if01 = new Monster();
        if01 = new Car();

        //继承体现的多态
        //父类类型的变量 a 可以指向继承AAAA的子类的对象实例
        AAAA aaaa = new BBBB();
        aaaa = new CCCC();
    }
}

interface IF {}
class Monster implements IF {}
class Car implements IF {}

class AAAA {}

class BBBB extends AAAA {}

class CCCC extends AAAA {}

img

img

  1. 多态数组

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

package com.zanedu.interface_;

public class InterfacePolyArr {
    public static void main(String[] args) {
        //多态数组 -> 接口类型数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new MyCamera();
        usbs[1] = new MyPhone();
        for (int i = 0; i < usbs.length; i++) {
            usbs[i].work(); //动态绑定
            //和前面一样,我们仍然需要进行类型的向下转型 - 为了访问到call方法
            //instanceof判断的是运行类型
            if (usbs[i] instanceof MyPhone) { //判断它的运行类型是 MyPhone
                ((MyPhone) usbs[i]).call();
            }
        }
    }
}
interface Usb {
    void work();
}
class MyPhone implements Usb {
    public void call() {
        System.out.println("手机可以打电话");
    }

    @Override
    public void work() {
        System.out.println("手机工作中");
    }
}
class MyCamera implements Usb {
    @Override
    public void work() {
        System.out.println("相机工作中");
    }
}

img

3, 接口存在多态传递现象

package com.zanedu.interface_;

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

    @Override
    public void hi() {
        ;
    }
}

练习题

package com.zanedu.interface_;

public class InterfaceExercise {


}
interface A1 {
    int x = 1;
}
class B1 {
    int x = 0;
}
class C1 extends B1 implements A1 {
    public void pX() {
        //System.out.println(x); //error 原因不明确x
        //可以明确的指定x
        //访问接口的x就是用A1.x
        //访问父类的x就是用super.x
        System.out.println(A1.x);
        System.out.println(super.x);
    }

    public static void main(String[] args) {
        new C1().pX();
    }
}

img

img

内部类

基本介绍

一个类的内部又完整的嵌套了另一个类结构**,被嵌套的类称为内部类,嵌套的其他类的类称为外部类,是我们类的第五大成员。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系**

  • 基本语法

class Outer { //外部类

class Inner { //内部类

}

}

class Other { //外部其他类

}

内部类的分类(四种)

定义在外部类局部位置上(比如方法内/代码块内):

  1. 局部内部类(有内名)
  2. 匿名内部类(没有类名)

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

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

局部内部类的使用

说明:

  1. 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
package com.zanedu.innerclass;

/**
 * 演示局部内部类的使用
 */
public class LocalInnerClass { //外部其他类
    public static void main(String[] args) {
        
    }
}
class Outer02 { //外部类
    private int n1 = 100;
    public void m1() { //方法
        //1. 局部内部类是定义在外部类的局部位置,通常在方法
        class Inner02 { //局部内部类 (本质仍然是一个类)
            
        }
    }
    
    {//代码块
        class Inner03 {

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

img

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

img

  • 补充:被final修饰的类不能被继承

img

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

img

  1. 局部内部类 – 访问 —> 外部类的成员【访问方式:直接访问】
  2. 外部类 – 访问 —> 局部内部类的成员【访问方式:创建对象再访问(注意:必须在作用域内)】
package com.zanedu.innerclass;

/**
 * 演示局部内部类的使用
 */
public class LocalInnerClass { //外部其他类
    public static void main(String[] args) {
        //演示
        Outer02 outer02 = new Outer02();
        outer02.m1();
    }
}
class Outer02 { //外部类
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    } //私有方法
    public void m1() { //方法
        //1. 局部内部类是定义在外部类的局部位置,通常在方法
        //3. 不能添加访问修饰符,但是可以用final修饰
        //4. 作用域:仅仅在定义它的方法或代码块中
        final class Inner02 { //局部内部类 (本质仍然是一个类)
            //2. 可以直接访问外部类的所有成员,包括私有的
            private int n1 = 800;
            public void f1() {
                //5. 局部内部类可以直接访问外部类的成员,如下:外部类n1 和 m2()
                System.out.println("n1=" + n1);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建Inner02的对象,然后调用方法
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

img

  1. 外部其他类 – 不能访问 —> 局部内部类(因为局部内部类地位是一个局部变量)
  2. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.zanedu.innerclass;

/**
 * 演示局部内部类的使用
 */
public class LocalInnerClass { //外部其他类
    public static void main(String[] args) {
        //演示
        Outer02 outer02 = new Outer02();
        System.out.println("outer02的hashCode=" + outer02.hashCode());
        outer02.m1();
    }
}
class Outer02 { //外部类
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    } //私有方法
    public void m1() { //方法
        //1. 局部内部类是定义在外部类的局部位置,通常在方法
        //3. 不能添加访问修饰符,但是可以用final修饰
        //4. 作用域:仅仅在定义它的方法或代码块中

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

img

img

img

匿名内部类的使用

说明:

  1. 本质还是类
  2. 内部类
  3. 该类没有名字
  4. 同时还是一个对象

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

基本语法:

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

类体

};

  • 接口
package com.zanedu.innerclass;

/**
 * 演示匿名内部类的使用
 */
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}
class Outer04 { //外部类
    private int n1 = 10; //属性
    public void method() { //方法
        //基于接口的匿名内部类
        /*
        解读:
        1. 需求:想使用接口IA,并创建对象
        2. 传统的方式:写一个类,实现该接口,并创建对象
        3. 需求是 Tiger类只是使用一次,以后再不使用
        4. 可以使用匿名内部类来简化开发
        5. tiger的编译类型 IA
        6. tiger的运行类型 就是匿名内部类 XXX ==> Outer04$1

        我们看底层 - 名字看不到,底层会分配 即 类名 + $
        class Outer04$1 implements IA {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        }
         */
        //7. JDK底层在创建匿名内部类 Outer04$1,立马就创建了 Outer04$1实例,并且把地址返回给 tiger
        //8. 匿名内部类使用一次,就不能在使用了 ,但是对象可以反复调用,只不过匿名内部类没了
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println("tiger的运行类型=" + tiger.getClass());
        tiger.cry();
//        IA tiger = new Tiger();
//        tiger.cry();
    }
}
interface IA { //接口
    public void cry();
}
//老方法
//class Tiger implements IA {
//    @Override
//    public void cry() {
//        System.out.println("老虎叫唤...");
//    }
//}
//class Dog implements IA {
//    @Override
//    public void cry() {
//        System.out.println("狗叫唤...");
//    }
//}

img

  • 运行类型用getClass方法获取

img

package com.zanedu.innerclass;

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

        //基于抽象类的匿名内部类
        Animal animal =  new Animal() {
            @Override
            void eat() {
                System.out.println("小狗吃骨头...");
            }
        };
        animal.eat();
    }
}
interface IA { //接口
    public void cry();
}
//老方法
//class Tiger implements IA {
//    @Override
//    public void cry() {
//        System.out.println("老虎叫唤...");
//    }
//}
//class Dog implements IA {
//    @Override
//    public void cry() {
//        System.out.println("狗叫唤...");
//    }
//}

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

    }
}
abstract class Animal {
    abstract void eat();
}

img

  • 基于抽象类的匿名内部类

img

  1. 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征
package com.zanedu.innerclass;

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
        System.out.println("outer05的hashCode=" + outer05.hashCode());

    }
}

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

        //也可以直接调用,因为匿名内部类本身也是返回对象
        // class 匿名内部类 extends Person {}
        new Person() {
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法,哈哈哈...");
            }

            @Override
            public void ok(String str) {
                super.ok(str);
            }
        }.ok("jack");
    }
}

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

img

  1. 可以直接访问外部类的所有成员,包含私有的
class Outer05 {
    private int n1 = 99;
    public void f1() {
        //创建一个基于类的匿名内部类
        Person p = new Person() {
            private int n1 = 88;
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法 n1=" + n1);
            }
        };
        p.hi();//动态绑定,运行类型是 Outer05$1

        //也可以直接调用,因为匿名内部类本身也是返回对象
        // class 匿名内部类 extends Person {}
        new Person() {
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法,哈哈哈...");
            }

            @Override
            public void ok(String str) {
                super.ok(str);
            }
        }.ok("jack");
    }
}

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

img

  1. 不能添加访问修饰符,因为它的地位就是一个局部变量
  2. 作用域:仅仅在定义它的方法或代码块中
  3. 匿名内部类 – 访问 —> 外部类成员【访问方式:直接访问】
  4. 外部其他类 – 不能访问 —> 匿名内部类(因为匿名内部类地位是一个局部变量)
  5. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.zanedu.innerclass;

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
        System.out.println("outer05的hashCode=" + outer05.hashCode());

    }
}

class Outer05 {
    private int n1 = 99;
    public void f1() {
        //创建一个基于类的匿名内部类
        //不能添加访问修饰符,因为它的低位就是一个局部变量
        //作用域:仅仅在定义它的方法或代码块中,并且只会有一次,因为一次使用过后,匿名内部类就消失了
        Person p = new Person() {
            private int n1 = 88;
            @Override
            public void hi() {
                //可以直接访问外部类的所有成员,包括私有的
                /*
                如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
                如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
                 */
                System.out.println("匿名内部类重写了hi方法 n1=" + n1);
                System.out.println("外部类的n1=" + Outer05.this.n1);
                //Outer05.this就是调用f1的对象,谁调用f1,那么就是谁
                System.out.println("Outer05.this hashCode=" + Outer05.this.hashCode());
            }
        };
        p.hi();//动态绑定,运行类型是 Outer05$1

        //也可以直接调用,因为匿名内部类本身也是返回对象
        // class 匿名内部类 extends Person {}
        new Person() {
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法,哈哈哈...");
            }

            @Override
            public void ok(String str) {
                super.ok(str);
            }
        }.ok("jack");
    }
}

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

img

img

img

匿名内部类的最佳实践
  • 当作实参直接传递,简洁高效
package com.zanedu.innerclass;

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

        //传统方式
        f1(new Picture());

    }

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

}
//接口
interface IL {
    void show();
}
//类 -》实现IL ==> 编程领域(硬编码)
class Picture implements IL {

    @Override
    public void show() {
        System.out.println("这是一副名画..");
    }
}
  • 题目
  1. 有一个铃声接口Bell,里面有个ring方法
  2. 有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型
  3. 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
  4. 再传入另一个匿名内部类(对象),打印:小伙伴上课了
package com.zanedu.innerclass;

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

        cellphone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了");
            }
        });
    }
}
interface Bell { //接口
    void ring();//方法
}
class Cellphone { //类
    public void alarmClock(Bell bell) { //形参是Bell接口类型
        System.out.println(bell.getClass());
        bell.ring();//动态绑定
    }
}

成员内部类的使用

  • 说明:成员内部类是定义在外部类的成员位置,并且没有static修饰
  1. 可以直接访问外部类的所有成员,包括私有的
class Outer08 {
    private int n1 = 10;
    public String name = "张三";

    public void hi() {
        System.out.println("hi() 方法");
    }
    class Inner08 { //成员内部类
        public void say() {
            //1. 可以直接访问外部类的所有成员,包括私有的
            System.out.println("n1 = " + n1 + " name = " + name);
        }
    }
    //写方法
    public void t1() {
        //使用成员内部类
        Inner08 inner08 = new Inner08();
        inner08.say();
    }
}

img

  1. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
//1. 注意:成员内部类,是定义在外部类的成员位置上
    //2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    public class Inner08 { //成员内部类
        private double sal = 99.9;
        private int n1 = 66;
        public void say() {
            //1. 可以直接访问外部类的所有成员,包括私有的
            System.out.println("n1 = " + n1 + " name = " + name);
        }
    }

img

  1. 作用域:和外部类的其他成员一样,为整个类体。比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法
  2. 成员内部类 – 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】
class Outer08 {
    private int n1 = 10;
    public String name = "张三";

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

    //1. 注意:成员内部类,是定义在外部类的成员位置上
    //2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    public class Inner08 { //成员内部类
        private double sal = 99.9;
        private int n1 = 66;
        public void say() {
            //1. 可以直接访问外部类的所有成员,包括私有的
            System.out.println("n1 = " + n1 + " name = " + name);
            hi();
        }
    }
    //写方法
    public void t1() {
        //使用成员内部类
        Inner08 inner08 = new Inner08();
        inner08.say();
    }
}

img

  1. 外部类 – 访问 —> 成员内部类【访问方式:创建对象再访问】
class Outer08 {
    private int n1 = 10;
    public String name = "张三";

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

    //1. 注意:成员内部类,是定义在外部类的成员位置上
    //2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    public class Inner08 { //成员内部类
        private double sal = 99.9;
        private int n1 = 66;
        public void say() {
            //1. 可以直接访问外部类的所有成员,包括私有的
            System.out.println("n1 = " + n1 + " name = " + name);
            hi();
        }
    }
    //方法返回一个Inner08实例
    public Inner08 getInner08Instance() {
        return new Inner08();
    }

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

img

  1. 外部其他类 – 访问 —> 成员内部类
package com.zanedu.innerclass;

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();

        //外部其他类,使用成员内部类的三种方式
        //解读
        //第一种方式
        //outer08.new Inner08(); 相当于把new Inner08() 当作 outer08的成员
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();

        //第二种方式 - 在外部类中编写一个方法,可以返回 Inner08对象
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();

    }
}

class Outer08 {
    private int n1 = 10;
    public String name = "张三";

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

    //1. 注意:成员内部类,是定义在外部类的成员位置上
    //2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    public class Inner08 { //成员内部类
        private double sal = 99.9;
        private int n1 = 66;
        public void say() {
            //1. 可以直接访问外部类的所有成员,包括私有的
            System.out.println("n1 = " + n1 + " name = " + name);
            hi();
        }
    }
    //方法返回一个Inner08实例
    public Inner08 getInner08Instance() {
        return new Inner08();
    }

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

img

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

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();

        //外部其他类,使用成员内部类的三种方式
        //解读
        //第一种方式
        //outer08.new Inner08(); 相当于把new Inner08() 当作 outer08的成员
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();

        //第二种方式 - 在外部类中编写一个方法,可以返回 Inner08对象
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();

    }
}

class Outer08 {
    private int n1 = 10;
    public String name = "张三";

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

    //1. 注意:成员内部类,是定义在外部类的成员位置上
    //2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    public class Inner08 { //成员内部类
        private double sal = 99.9;
        private int n1 = 66;
        public void say() {
            //1. 可以直接访问外部类的所有成员,包括私有的
            //如果成员内部类的成员和外部类的成员重名,会遵守就近原则
            // 可以通过外部类名.this.属性 来访问外部类的成员
            System.out.println("n1 = " + n1 + " name = " + name);
            System.out.println("Outer08 n1 = " + Outer08.this.n1);
            hi();
        }
    }
    //方法返回一个Inner08实例
    public Inner08 getInner08Instance() {
        return new Inner08();
    }

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

img

静态内部类的使用

  • 说明:静态内部类是定义在外部类的成员位置,并且有static修饰
  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
  3. 作用域:同其他的成员,为整个类体
class Outer10 { //外部类
    private int n1 = 10;
    private static String name = "张三";

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

    public void m1() {
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
}

img

img

  1. 静态内部类 – 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
  2. 外部类 – 访问 —> 静态内部类【访问方式:创建对象再访问】
private static void cry() {}
    //Inner10 就是一个静态内部类
    //1. 放在外部类的成员位置
    //2. 是用static修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    //4. 可以添加任意访问修饰符(public protected 默认 private),因为它的地位就是一个成员
    //5. 作用域:同其他的成员,为整个类体
    public static class Inner10 {
        private static String name = "zan";
        public void say() {
            System.out.println(name);
            //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
            //如果想访问外部类的成员,则可以使用 (外部类名.成员)去访问
            System.out.println(Outer10.name);
            cry();
        }
    }

img

  1. 外部其他类 --访问 —> 静态内部类
  2. 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
package com.zanedu.innerclass;

public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();

        //外部其他类 使用静态内部类
        //方式一
        //因为静态内部类可以通过类名直接访问(前提是满足访问权限)
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();

        //方式二
        //编写一个方法,可以返回静态内部类的对象实例
        Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("========");
        inner101.say();

        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        System.out.println("*******");
        inner10_.say();
    }
}

class Outer10 { //外部类
    private int n1 = 10;
    private static String name = "张三";

    private static void cry() {}
    //Inner10 就是一个静态内部类
    //1. 放在外部类的成员位置
    //2. 是用static修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    //4. 可以添加任意访问修饰符(public protected 默认 private),因为它的地位就是一个成员
    //5. 作用域:同其他的成员,为整个类体
    public static class Inner10 {
        private static String name = "zan";
        public void say() {
            System.out.println(name);
            //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
            //如果想访问外部类的成员,则可以使用 (外部类名.成员)去访问
            System.out.println(Outer10.name);
            cry();
        }
    }

    public void m1() {
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
    public Inner10 getInner10() {
        return new Inner10();
    }
    public static Inner10 getInner10_() {
        return new Inner10();
    }
}

img

测试题

public class Test { //外部类
    public Test() { //构造器
        Inner s1 = new Inner();
        s1.a = 10;
        Inner s2 = new Inner();
        System.out.println(s2.a);
    }
    class Inner { //内部类,成员内部类
        public int a = 5;
    }

    public static void main(String[] args) {
        Test t = new Test();
        Inner r = t.new Inner(); //5
        System.out.println(r.a); //5
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzzan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值