类变量/静态变量/静态属性和类方法/静态方法(static用于修饰 变量、方法、代码块 和 内部类

类变量/静态变量/静态属性

提出问题

提出问题的主要目的就是让大家思考解决之道,从而引出我要讲的知识点.

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

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_构造器

传统方法解决

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_代码块_02

类变量解决
/**
 * @创建人 wdl
 * @创建时间 2024/9/8
 * @描述
 */
public class ChildGame {
    public static void main(String[] args) {
        //定义一个变量 count, 统计有多少小孩加入了游戏
        int count = 0;

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

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

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

        //===========
        //类变量,可以通过类名来访问
        System.out.println("共有" + Child.count + " 小孩加入了游戏...");
        //下面的代码输出什么?
        System.out.println("child1.count=" + child1.count);//3
         System.out.println("child2.count=" + child2.count);//3 System.out.println("child3.count=" + child3.count);//3
    }
}


class Child { //类
     private String name;
    //定义一个变量 count ,是一个类变量(静态变量) static 静态
    //该变量最大的特点就是会被 Child 类的所有的对象实例共享
    public static int count = 0;
//    public int count=0;

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

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

类变量介绍(类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,并且在类加载的时候(即使没有创建对象)就生成了,随着类消亡而销毁)(类变量的内存布局由于jdk版本不同而不同)

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_java_03

定义类变量

访问修饰符 static 数据类型 变量名:`[推荐]`
	static 访问修饰符 数据类型 变量名;

访问类变量

  1. 类名.类变量名 (推荐)
  2. 对象名,类变量名 【静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的

类方法(静态方法(无this),只能访问静态的成员,非静态的方法(隐含着this和super),可以访问静态成员和非静态成员(必须遵守访问权限))(类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区)

定义类方法

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

调用类方法

  1. 类名.类方法名 (推荐)
  2. 对象名.类方法名【静态方法的访问修饰符的访问权限和范围 和 普通方法是一样的

理解 main 方法(在 main()方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静

态成员)
解释main方法的形式
public static void main(String[l args){}

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

【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_开发语言_04


【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_java_05

代码块(代码块调用的顺序优先于构造器)(修饰符 可选,要写的话,也只能写 static)(;号可以写上,也可以省略

代码块VS方法

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

基本语法

[修饰符]{
		代码
	};

说明注意:

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

代码块的好处

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

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


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

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

    


    public Movie(String name) {
        System.out.println("Movie(String name) 被调用...");
        this.name = name;
    }


    public Movie(String name, double price) {


        this.name = name;
        this.price = price;
    }


    public Movie(String name, double price, String director) {


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

代码块使用注意事项和细节讨论(static代码块是类加载时,执行,只会执行一次)(普通代码块是在创建对象时调用的,创建一次,调用一次

类什么时候被加载

  1. 创建对象实例时(new)
  2. 创建子类对象实例,父类也会被加载
  3. 使用类的静态成员时(静态属性,静态方法)(但是static final在一起就不会导致类加载,底层做了优化)

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

  1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)[举例说明1
  2. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
  3. 调用构造方法。

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

/**
 * @创建人 wdl
 * @创建时间 2024/9/9
 * @描述
 */
public class CodeBlockDetail04 {
    public static void main(String[] args) {
        //说明
        //(1) 进行类的加载
        //1.1 先加载 父类 A02 1.2 再加载 B02
        //(2) 创建对象
        //2.1 从子类的构造器开始
        //new B02();//对象
        new C02();
    }
}


class A02 { //父类
    private static int n1 = getVal01();

    static {
        System.out.println("A02 的一个静态代码块..");//(2)
    }

    {
        System.out.println("A02 的第一个普通代码块..");//(5)
    }

    public int n3 = getVal02();//普通属性的初始化

    public static int getVal01() {
        System.out.println("getVal01");//(1)
        return 10;
    }


    public int getVal02() {
        System.out.println("getVal02");//(6)
        return 10;
    }


    public A02() {//构造器
        //隐藏
        //super()
        //普通代码和普通属性的初始化......
        System.out.println("A02 的构造器");//(7)
    }


}


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


    private void m1() {


    }

    private static void m2() {


    }

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

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


class B02 extends A02 { //


    private static int n3 = getVal03();


    static {
        System.out.println("B02 的一个静态代码块..");//(4)
    }

    public int n5 = getVal04();

    {
        System.out.println("B02 的第一个普通代码块..");//(9)
    }


    public static int getVal03() {
        System.out.println("getVal03");//(3)
        return 10;
    }


    public int getVal04() {
        System.out.println("getVal04");//(8)
        return 10;
    }

    //一定要慢慢的去品..
    public B02() {//构造器
        //隐藏了
        //super()
        //普通代码块和普通属性的初始化...
        System.out.println("B02 的构造器");//(10)
        // TODO Auto-generated constructor stub
    }
}
/**
 * @创建人 wdl
 * @创建时间 2024/9/9
 * @描述
 */
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 默认构造函数被调用");//
    }
}

public class HomeWork06 {
    //主方法
    public static void main(String str[]) {
        Test a = new Test();//无参构造器
    }
//        //运行结果, 输出什么内容,并写出. 2min 看看
//        1.静态成员 sam 初始化
//        2.static 块执行
//        3.sam1 成员初始化
//        4.Test 默认构造函数被调用
}

单例设计模式

什么是设计模式

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

什么是单例模式(对某个类只能存在一个对象实例

单例(单个的实例)

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

单例模式应用实例

饿汉式 VS 懒汉式

二者最主要的区别在于创建对象的时机不同:

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

步骤如下:

  1. 构造器私有化=》 防止直接 new
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getlnstance

饿汉式(类加载就创建了对象实例)(线程安全)

public class SingletonEager {
    // 1. 私有化构造器,防止外部实例化
    private SingletonEager() {}

    // 2. 提供唯一的实例,并在类加载时创建
    private static SingletonEager INSTANCE = new SingletonEager();

    // 3. 提供获取实例的静态方法
    public static SingletonEager getInstance() {
        return INSTANCE;
    }

    // 示例方法
    public void showMessage() {
        System.out.println("这是饿汉式单例模式实例!");
    }

    public static void main(String[] args) {
        // 获取单例实例
        SingletonEager instance = SingletonEager.getInstance();
        instance.showMessage();
    }
}

懒汉式(使用时才创建)(线程不安全)

public class SingletonLazy {
    // 1. 私有化构造器,防止外部实例化
    private SingletonLazy() {}

    // 2. 静态实例,开始时为 null,延迟加载
    private static SingletonLazy instance;

    // 3. 提供获取实例的静态方法,带同步关键字以保证线程安全
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    // 示例方法
    public void showMessage() {
        System.out.println("这是懒汉式单例模式实例!");
    }

    public static void main(String[] args) {
        // 获取单例实例
        SingletonLazy instance = SingletonLazy.getInstance();
        instance.showMessage();
    }
}

final 关键字(可以修饰类、属性、方法和局部变量)

final 中文意思:最后的,最终的.
final 可以修饰类、属性、方法和局部变量,在某些情况下,程序员可能有以下需求,就会使用到final:

  1. 不希望类被继承时,可以用final修饰.【案例演示: (访问修饰符) final class A】(注:但是可以实例化对象)(包装类(Integer,Double,Float,Boolean等都是final),String也是final类)
  2. 不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。【案例演示: 访问修饰符 final 返回类型 方法名】(注:但是可以被继承)(如果一个类已经是final类了,就没有必要再将方法修饰成final方法,没有任何意义)(final不能修饰构造方法(即构造器))
  3. 当不希望类的的某个属性的值被修改,可以用final修饰.【案例演示: public final double TAX RATE=0.08】
  • 注:
  • 1.一般用 XX_XX_XX 来命名
  • 2.在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一(涉及到他们的调用顺序)
    – 普通属性
    – 1. 定义时:如 public final double TAX_RATE=0.08;
    – 2. 在代码块中
    – 3. 在构造器中
    – 静态属性 (final 和 static 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化)
    – 1.定义时
    – 2.在静态代码块,不能在构造器中赋值
  1. 当不希望某个局部变量被修改,可以使用final修饰 【案例演示: final double TAX RATE=0.08】
/**
 * @创建人 wdl
 * @创建时间 2024/9/10
 * @描述
 */
public class FinalDetail02 {
    public static void main(String[] args) {
        System.out.println(BBB.num);

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

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

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

静态变量(static)(用于表示类的属性,多个实例共享同一个值)VS常量(final)(用于定义不可变的值)

  • 静态变量:
    在类加载时创建,在整个应用程序的生命周期内共享。其值可以在程序运行时修改,适合表示多个对象共享的可变数据。
  • 常量:
    值在声明时或构造时确定,一旦赋值不能改变。适合用于表示那些不会变化的值,如数学常量或全局配置项。

抽象类(本质还是类,但是不能实例化)(当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类)(abstract 只能修饰类和方法,不能修饰属性和其它的

抽象类的介绍

  1. 用abstract 关键字来修饰一个类时,这个类就叫抽象类访问
修饰符 abstract 类名{
  1. 用abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
  1. 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()

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

  1. 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法,一旦类包含了abstract方法,则这个类必须声明为abstract
  2. 抽象方法不能有主体,即不能实现(空实现也不行 ).如图所示
  3. 【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_构造器_06

  4. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法除非它自己也声明为abstract类。[举例 A类,B类,C类]
  5. 抽象方法不能使用private、final 和 static来修饰,因为这些关键字都是和重写相违背的。

继承+抽象类练习

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


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


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


    public Employee(String name, int id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    //将 work 做成一个抽象方法
    public abstract void work();

    public String getName() {
        return name;
    }


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

class Manager extends Employee {


    private double bonus;

    public Manager(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() + " 工作中...");
    }
}


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


    @Override
    public void work() {
        System.out.println("普通员工 " + getName() + " 工作中...");
    }
}

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

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

抽象类的多态

abstract class Animal {
    abstract void sound(); // 抽象方法
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Cat meows");
    }
}

public class TestPolymorphism {
    public static void main(String[] args) {
        // 抽象类类型引用可以指向子类对象
        Animal myAnimal = new Dog(); // 多态性:Animal 类型的引用指向 Dog 对象
        myAnimal.sound();  // 输出:Dog barks

        myAnimal = new Cat();  // 指向另一个子类对象
        myAnimal.sound();  // 输出:Cat meows
    }
}

接口(不能被实例化)

接口基本介绍

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

语法:

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

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

特别说明:jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现

注意事项和细节

  1. 接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract 修饰 图示:
  2. 【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(高级部分)(2024JavaReview)_构造器_07

  3. 一个普通类实现接口,就必须将该接口的所有方法都实现。,但是 抽象类实现接口,可以不用实现接口的方法
  4. 一个类同时可以实现多个接口
  5. 接口中的属性,只能是final的,而且是 public static final 修饰符。比如:int a=1; 实际上是 public static final int a=1;(必须初始化)
  6. 接口中属性的访问形式: 接口名.属性名
  7. 接口不能继承其它的类,但是可以继承多个别的接口 interface A extends B,C{}
  8. 接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的,

接口的多态

/**
 * @创建人 wdl
 * @创建时间 2024/9/10
 * @描述
 */
public class InterfaceDetail {
    public static void main(String[] args) {
        // 证明接口中的属性是 public static final
        System.out.println(IB.n1);  // 输出:10

        //IB.n1 = 30; // 这行代码会报错,因为 n1 是 final 不能修改

        // 创建实现类的对象
        Cat cat = new Cat();
        cat.say();
        cat.hi();

        Pig pig = new Pig();
        pig.say();
        pig.hi();
    }
}

// 接口定义
interface IA {
    void say(); // 接口中的方法默认是 public abstract 的,不需要修饰符

    void hi();  // 接口方法默认为 public abstract
}

// 普通类实现接口,必须实现接口的所有方法
class Cat implements IA {
    @Override
    public void say() {
        System.out.println("Cat says meow");
    }

    @Override
    public void hi() {
        System.out.println("Cat says hi");
    }
}

// 抽象类实现接口,可以选择不实现接口的所有方法
abstract class Tiger implements IA {
    // Tiger 类没有实现接口方法,因为它是抽象类,可以由子类实现
}

// 证明接口中的属性是 public static final
interface IB {
    // 这相当于 public static final int n1 = 10;
    int n1 = 10;

    void hi();  // 接口方法默认为 public abstract
}

// 接口不能继承类,但可以继承多个其他接口
interface IC {
    void say();
}

// 接口可以多继承
interface ID extends IB, IC {}

// 一个类可以实现多个接口
class Pig implements IB, IC {
    @Override
    public void hi() {
        System.out.println("Pig says hi");
    }

    @Override
    public void say() {
        System.out.println("Pig says oink");
    }
}

// 接口的修饰符只能是 public 或者默认(包级访问权限),不能是 private 或 protected
interface IE {
    // 默认 public abstract void say();
}
/**
 * @创建人 wdl
 * @创建时间 2024/9/10
 * @描述
 */
public class InterfaceDemo {
    public static void main(String[] args) {
        // 接口的多态体现
        IF if01 = new Monster();  // 多态:接口类型变量指向实现类对象
        if01 = new Car();  // 可以指向另一个实现同一接口的对象

        // 继承体现的多态
        AAA a = new BBB();  // 父类类型变量指向子类对象
        a = new CCC();  // 可以指向另一个子类对象

        // 多态数组 -> 接口类型数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();  // 接口实现类对象赋值到接口类型数组
        usbs[1] = new Camera_();

        // 遍历 Usb 数组,如果是 Phone_ 对象,还要调用其特有方法 call()
        for (Usb usb : usbs) {
            usb.work();  // 动态绑定调用对应实现类的 work() 方法
            if (usb instanceof Phone_) {
                ((Phone_) usb).call();  // 向下转型并调用 Phone_ 的特有方法
            }
        }

        // 接口多态传递现象
        IG ig = new Teacher();  // IG 类型变量指向实现类对象
        IH ih = new Teacher();  // IH 是 IG 的父接口,Teacher 也实现了 IH 接口
        ih.hi();  // 调用的是 Teacher 实现的 hi() 方法
    }
}

// 接口和实现类
interface IF {}
class Monster implements IF {}
class Car implements IF {}

// 继承关系类
class AAA {}
class BBB extends AAA {}
class CCC extends AAA {}

// Usb 接口和实现类
interface Usb {
    void work();  // 定义抽象方法
}

class Phone_ implements Usb {
    public void call() {
        System.out.println("手机可以打电话...");
    }

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

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

// 接口多态传递现象的接口和类
interface IH {
    void hi();
}

interface IG extends IH {}

class Teacher implements IG {
    @Override
    public void hi() {
        System.out.println("Teacher is saying hi...");
    }
}

抽象类((单)继承:解决代码的复用性和可维护性,满足 is -a的关系)VS接口((多)实现:设计好各种规范(方法),让其它类去实现这些方法,满足 like-a的关系,更加的灵活)【使用时:父类的实现 优先于 接口的默认方法;访问接口的 x 就使用 A.x,访问父类的 x 就使用 super.x】

对比项

抽象类

接口

默认修饰符

类可以是 public或包级别(默认),方法可以是 publicprotecteddefault

接口和方法默认是 public(方法默认是 public,不能有其他修饰符),方法默认为抽象的(写抽象方法时候可以不写abstract)

字段

可以有字段(成员变量),可以是非 final

只能有 public static final(默认) 的静态常量

方法

可以包含抽象方法,也可以包含普通方法(有方法体)

只能包含抽象方法(Java 8 及之后可以有默认方法和静态方法)

构造方法

可以有构造方法

没有构造方法

实现方式

子类必须实现所有抽象方法,除非子类也是抽象类

实现类必须实现所有接口中的方法

继承关系

抽象类可以继承另一个类或抽象类,并且可以被子类继承

接口不能继承类,但可以继承其他接口

多继承

不支持多继承,一个类只能继承`一个抽象类

支持多继承,一个类可以实现多个接口

多态性

可以表现多态性,通过父类引用指向子类对象

可以表现多态性,通过接口引用指向实现类对象

内部类(本质仍然是一个类)(重点,难点)(类的五大成员:[属性、方法、构造器、代码块、内部类]】)

基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。

类的五大成员

[属性、方法、构造器、代码块、内部类]】

内部类最大的特点

就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

基本语法

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

内部类的分类

定义在外部类局部位置上(比如方法内,代码块)(作用域在方法体或者代码块中)(地位是一个局部变量

1.局部内部类(有类名)
  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
  3. 作用域:仅仅在定义它的方法或代码块中。
  • 局部内部类—访问---->外部类的成员
    [访问方式]: 直接访问(注:如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问)
  • 外部类—访问---->局部内部类的成员
    [访问方式]:创建对象,再访问(注意:必须在作用域内)
  • 外部其他类—不能访问(重要)---->局部内部类(因为 局部内部类地位是一个局部变量)
/**
 * @创建人 wdl
 * @创建时间 2024/9/10
 * @描述
 */

/**
 * 演示局部内部类的使用
 */
public class LocalInnerClass {//

    public static void main(String[] args) {
        //演示一遍
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("outer02 的 hashcode=" + outer02);
    }
}


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 + " 外部类的 n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode=" + Outer02.this);
                System.out.println("Outer02.this hashcode=" + this);
                m2();
            }
        }

        //6. 外部类在方法中,可以创建 Inner02 对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}
2.匿名内部类(没有类名,重点!!!)(既是一个类的定义,同时它本身也是一个对象(因此可以调用匿名内部类的方法))(可以基于接口、抽象类、普通类,进而重写里面的方法和构造对象)(当做实参直接传递,简洁高效见例子3)

作用域:同局部内部类

/**
 * @创建人 wdl
 * @创建时间 2024/9/11
 * @描述
 */
/**
 * 演示匿名内部类的使用
 */
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}

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

    public void method() { // 方法
        // 基于接口的匿名内部类
        // 需求: 想使用 IA 接口, 并创建对象
        // 可以使用匿名内部类来简化开发

        // tiger 的编译类型是 IA
        // tiger 的运行类型是匿名内部类 Outer04$1
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println("tiger 的运行类型=" + tiger.getClass());
        tiger.cry();
        tiger.cry();
        tiger.cry();

        // 基于类的匿名内部类
        // father 的编译类型是 Father
        // father 的运行类型是 Outer04$2
        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("小狗吃骨头...");
            }
        };
        animal.eat();
    }
}

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

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

    public void test() {
        // 方法
    }
}

abstract class Animal {
    // 抽象类
    abstract void eat();
}
/**
 * @创建人 wdl
 * @创建时间 2024/9/11
 * @描述
 */
public class AnonymousInnerClassDetail {

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

class Outer05 {
    private int n1 = 99;

    public void f1() {
        // 创建一个基于类的匿名内部类
        // 不能添加访问修饰符, 因为它的地位就是一个局部变量
        // 作用域 : 仅仅在定义它的方法或代码块中
        Person p = new Person() {
            private int n1 = 88;

            @Override
            public void hi() {
                // 可以直接访问外部类的所有成员,包含私有的
                // 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
                // 默认遵循就近原则,如果想访问外部类的成员,
                // 则可以使用 (外部类名.this.成员)去访问
                System.out.println("匿名内部类重写了 hi 方法 n1=" + n1 + " 外部内的 n1=" + Outer05.this.n1);
                // Outer05.this 就是调用 f1 的对象
                System.out.println("Outer05.this hashcode=" + Outer05.this);
            }
        };
        p.hi(); // 动态绑定, 运行类型是 Outer05$1

        // 也可以直接调用, 匿名内部类本身也是返回对象
        // class 匿名内部类 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);
    }
}
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("这是一副名画 XX...");
    }
}

定义在外部类的成员位置上((地位是一个成员,可以有任意访问修饰符))

1. 成员内部类(没用static修饰)
  1. 可以直接访问外部类的所有成员,包含私有的
  2. 能添加访问修饰符,因为它的地位就是一个成员。
  3. 作用域:和外部类的其他成员一样。
  • 成员内部类—访问---->外部类的成员
    [访问方式]: 直接访问(注:如果外部类和成员内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问)
  • 外部类—访问---->成员内部类的成员
    [访问方式]:创建对象,再访问
  • 外部其他类—访问(重要)---->成员内部类
    –方式一、创建外部类对象,调用外部类的方法,内部类的say方法会在这个方法中调用
    –方式二、通过外部类对象创建内部类对象,调用内部类的say方法(在创建时需要一个外部类的对象,因为它可以访问外部类的成员(包括非静态成员))
    –方式三 、通过外部类中的方法获得内部类对象(有单例设计模式的影子),调用内部类的方法
/**
 * @创建人 wdl
 * @创建时间 2024/9/11
 * @描述
 */
public class MemberInnerClass01 {
    public static void main(String[] args) {
        // 创建外部类对象
        Outer08 outer08 = new Outer08();
        outer08.t1();  // 调用外部类的方法,内部类的say方法会在这个方法中调用

        // 外部其他类使用成员内部类的三种方式
        // 第一种方式:通过外部类对象创建内部类对象
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();  // 调用内部类的say方法

        // 第二种方式:通过外部类中的方法获得内部类对象
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();  // 再次调用内部类的方法
    }
}

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

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

    // 成员内部类
    public class Inner08 {
        private double sal = 99.8;
        private int n1 = 66; // 局部n1,和外部类的n1重名

        public void say() {
            // 可以直接访问外部类的成员,如果成员名重名,通过外部类.this.成员来访问
            System.out.println("内部类 n1 = " + n1 + ", 外部类的 n1 = " + Outer08.this.n1);
            System.out.println("name = " + name);  // 访问外部类的成员
            hi();  // 调用外部类的私有方法
        }
    }

    // 返回一个内部类的实例
    public Inner08 getInner08Instance() {
        return new Inner08();
    }

    // 外部类方法
    public void t1() {
        // 使用成员内部类
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println("内部类的sal = " + inner08.sal);
    }
}
2. 静态内部类(使用static修饰)
  1. 可以直接访问外部类的static成员,包含私有的,但不能直接访问非静态成员
  2. 能添加访问修饰符,因为它的地位就是一个成员。
  3. 作用域:和外部类的其他static成员一样。
  • 静态内部类—访问---->外部类(比如:静态属性)
    [访问方式]: 直接访问所有的静态成员(注:如果外部类和静态内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问)
  • 外部类—访问---->静态内部类的成员
    [访问方式]:对于静态的可以直接类名.属性/方法;对于非静态的创建对象,再访问
  • 外部其他类—访问(重要)---->静态内部类
    –方式一、通过类名直接访问静态内部类( Outer10.Inner10 inner10 = new Outer10.Inner10();
    inner10.say();)
    –方式二 、通过外部类的方法(静态/非静态)返回静态内部类的对象实例(有单例设计模式的影子)
/**
 * @创建人 wdl
 * @创建时间 2024/9/11
 * @描述
 */
public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();

        // 外部其他类 使用静态内部类

        // 方式 1:通过类名直接访问静态内部类
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();

        // 方式 2:通过外部类的方法返回静态内部类的对象实例
        Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("============");
        inner101.say();

        // 方式 3:通过静态方法获取静态内部类的对象实例
        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        System.out.println("************");
        inner10_.say();
    }
}

class Outer10 {
    // 外部类
    private int n1 = 10;  // 非静态成员
    private static String name = "张三";  // 静态成员

    // 静态方法
    private static void cry() {
        System.out.println("Outer10 类的静态方法 cry()");
    }

    // 静态内部类
    static class Inner10 {
        private static String name = "韩顺平教育";  // 静态内部类的静态成员

        public void say() {
            // 如果外部类和静态内部类的成员重名时,遵循就近原则访问
            // 如果想访问外部类的成员,使用 外部类名.成员 来访问
            System.out.println();
            System.out.println(name + ",外部类 name= " + Outer10.name);
            cry();  // 访问外部类的静态方法
        }
    }

    // 外部类方法,访问静态内部类
    public void m1() {
        String name = Inner10.name;

        Inner10 inner10 = new Inner10();  // 创建静态内部类的对象
        inner10.say();
    }

    // 外部类方法,返回静态内部类对象实例
    public Inner10 getInner10() {
        return new Inner10();
    }

    // 静态方法,返回静态内部类对象实例
    public static Inner10 getInner10_() {
        return new Inner10();
    }
}