【JAVA语言学习】第10章 面向对象编程(高级部分)(P374 - P424)

韩顺平 零基础30天学会Java 学习笔记

第10章 面向对象编程(高级部分)(P374 - P424)

10.1 类变量和类方法

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

    类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问

    普通成员变量 = 普通属性 = 非静态属性 = 非静态成员变量 = 实例变量

    实例变量 = 普通变量 = 非静态变量

  • 语法

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

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

  • 访问类变量

    类名.类变量名 [推荐] 或者 对象名.类变量名

    静态变量的访问修饰符的访问权限和范围 和 普通属性是一致的

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

    1. 什么时候需要类变量:当需要让每个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)

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

类方法
  • 类方法又称静态方法

  • 语法

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

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

  • 调用类方法

    类名.类方法名[推荐] 或者 对象名.类方法名

    静态方法的访问修饰符的访问权限和范围 和 普通方法是一致的

  • 类方法经典的使用场景

    当方法中不涉及到任何和对象相关的成员,则可以将对象设计成静态方法(即当做工具来使用),提高开发效率;例如Math类,Array类

    开发自己的工具类时,可以设计静态方法

  • 类方法使用注意事项和细节讨论
    在这里插入图片描述
    在这里插入图片描述

    (1) 静态方法,只能访问静态成员

    (2) 非静态方法,可以访问所有的成员

    (3) 在编写代码时,仍然要遵守访问权限规则

10.2 main 方法语法

public static void main(String[] args) {}

  • 深入理解main方法

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

在这里插入图片描述

特别提示:

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

Eclipse传递main函数参数:

在项目上右击 Run As->Run Configurations…->Arguments->在Program arguments:的文本框中输入你要传入的参数,若有几个参数则在参数间空格就行。然后点击Run按钮。

10.3 代码块

  • 基本介绍

    • 又称初始化块,属于类中的成员,类似于方法。将逻辑语句封装在方法体中,通过{}包围起来。

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

  • 基本语法

    [修饰符] { 代码 };

    • 修饰符可选。要写只能写 static;
    • 代码块分为两类,使用static修饰的叫 静态代码块,反之叫 普通代码块/非静态代码块
    • 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断)
    • ; 可以写,也可以不写
  • 优势

    • 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
    • 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
    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) {
    		super();
    		System.out.println("Movie(String name) 被调用...");
    		this.name = name;
    	}
    	
    	public Movie(String name, double price) {
    		super();
    		this.name = name;
    		this.price = price;
    	}
    	
    	public Movie(String name, double price, String director) {
    		super();
    		this.name = name;
    		this.price = price;
    		this.director = director;
    	}
    }
    
  • 代码块使用注意事项和细节讨论

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

    2. 类什么时候被加载(重要)

      • 创建对象实例时(new)

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

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

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

    1. static代码块是类加载时,执行,只会执行一次
    2. 普通代码块是在创建对象时调用的,创建一次,调用一次
    3. 类加载的3中情况,要记住
    public class CodeBlockDetail01 {
    	
    	public static void main(String[] args) {
    		//类被加载的情况举例
    		//1. 创建对象实例时(new)
    		AA aa = new AA();
    		//2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
    		AA aa2 = new AA();
    		//3. 使用类的静态成员时(静态属性,静态方法)
    		System.out.println(Cat.n1);
    		//static 代码块,是在类加载时,执行的,而且只会执行一次.
    		DD dd = new DD();
    		DD dd1 = new DD();
    		//普通的代码块,在创建对象实例时,会被隐式的调用。
    		// 被创建一次,就会调用一次。
    		// 如果只是使用类的静态成员时,普通代码块并不会执行
    		System.out.println(DD.n1);//8888, 静态模块块一定会执行
    		}
    }
    
    class DD {
    	public static int n1 = 8888;//静态属性
    	//静态代码块
    	static {
    		System.out.println("DD 的静态代码1 被执行...");//
    	}
    	//普通代码块, 在new 对象时,被调用,而且是每创建一个对象,就调用一次
    	//可以这样简单的,理解普通代码块是构造器的补充
    	{
    		System.out.println("DD 的普通代码块...");
    	}
    }
    
    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被执行...");
    	}
    }
    
    1. 创建一个对象时,在一个类中的调用顺序(重点,难点):

      1. 调用静态代码块和静态属性初始化

        两者调用的优先级一样,如果有多个静态代码块和静态属性变量初始化,则按定义的顺序调用

      2. 调用普通代码块和普通属性初始化

        两者调用的优先级一样,如果有多个普通代码块和普通属性变量初始化,则按定义的顺序调用

      3. 调用构造方法

      静态 -> 普通 -> 构造器

      public class CodeBlockDetail02 {
      
      	public static void main(String[] args) {
      		A a = new A();  // (1) A 静态代码块01 (2) getN1 被调用...(3)A 普通代码块01(4)getN2 被调用...(5)A() 构造器被调用
      	}
      }
      
      class A { 
      	
      	 { //普通代码块
      		 System.out.println("A 普通代码块01");
      	 }
      	 
      	 private int n2 = getN2();//普通属性的初始化
      	 
      	 static { //静态代码块
      		 System.out.println("A 静态代码块01");
      	 }
      	 
      	 //静态属性的初始化
      	 private static int n1 = getN1();
      	 
      	 public static int getN1() {
      		 System.out.println("getN1 被调用...");
      		 return 100;
      	 }
      	 
      	 public int getN2() { //普通方法/非静态方法
      		 System.out.println("getN2 被调用...");
      		 return 200;
      	 }
      	 
      	 //无参构造器
      	 public A() {
      		 System.out.println("A() 构造器被调用");
      	 }
      }
      
    2. 构造器的最前面其实隐含了 super() 和 调用普通代码块。

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

      class AAA { //父类Object
      	{
      		System.out.println("AAA 的普通代码块");
      	}
      	
      	public AAA() {
      		//(1)super()
      		//(2)调用本类的普通代码块
      		System.out.println("AAA() 构造器被调用....");
      	}
      }
      // 输出:
      // AAA 的普通代码块
      // AAA() 构造器被调用....
      
    3. 创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:

      1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行);
      2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行);
      3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
      4. 父类的构造器;
      5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
      6. 子类的构造器;

      举例:new BB(); 时,

      (1) 进行类的加载;

      先父类后子类,同时加载类时,还要调用静态相关的属性和方法。

      (2) 创建对象;

      从子类的构造器开始。所以先调用super(),即调用父类的构造器。然后调用普通属性初始化和普通代码块,再调用构造器的其他语句。

    4. 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员。

    package com.codeblock;
    
    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
    	}
    }
    
  • 练习
    在这里插入图片描述
    答案:

    in static block!"
    total = 100
    total = 100
    

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

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

10.4 单例设计模式

  • 什么是设计模式
    在这里插入图片描述

    理解:套路,模板

  • 什么是单例模式

    单例(单个的实例)

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

      在整个软件运行过程中,保证某个类只能有一个实例。

      有时候软件开发有一个核心类很耗费资源,且只需要一个,但就采用单例模式

  • 单例模式分类

    • 饿汉式

      • 步骤

        1)构造器私有化(防止直接new)

        2)类的内部创建对象 (修饰为static是为了能在static方法中调用)

        3)向外暴露一个静态的公共方法(这样就不用在外部创建对象)。 getInstance

    在这里插入图片描述

    • 可能造成创建了对象,但是没有使用

    • 懒汉式

      • 步骤

        1)构造器私有化(防止直接new)

        2)定义一个static静态属性对象

        3)提供一个public的static方法,可以返回一个对象。 getInstance

        在这里插入图片描述

      • 只有当用户使用getInstance时,才返回对象。后面再次调用时,返回之前的对象

    • 两者区别

      在这里插入图片描述

    总结:

    1. 单例模式的两种实现方式 (1)饿汉式 (2) 懒汉式
    2. 饿汉式的问题:在类加载的时候就创建,可能存在资源浪费问题
    3. 懒汉式的问题:线程安全问题
    4. 要独立写出单例模式

10.5 final 关键字

  • 基本介绍

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

    • 当不希望类被继承时,可以用final修饰

    • 当不希望父类的某个方法被子类覆盖或者重写,可以用final修饰

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

    • 当不希望类的某个属性的值被修改,可以用final修饰

      访问修饰符 final 数据类型 属性名 = 值;

    • 当不希望某个局部变量被修改,可以用final修饰

      final 数据类型 属性名 = 值;

    只能赋值一次

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

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

    • final修饰的属性在定义时,必须赋初值,并且以后不能修改。赋值可以在如下位置之一:

      • 定义时
      • 在构造器中
      • 在代码块中

      在这里插入图片描述

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

      • 定义时
      • 在静态代码块中
    • final类不能继承,但是可以实例化对象

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

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

    • final不能修饰构造器

    • final和static往往搭配使用,效率更高,不会导致类加载。底层编译器做了处理

      class BBB {
          public static int num = 10000;
          static {
          	System.out.println("BBB 静态代码块被执行");
          }
      }
      // System.out.println(BBB.num); 
      // 输出 
      // 10000
      
      class BBB {
          public final static int num = 10000;
          static {
          	System.out.println("BBB 静态代码块被执行");
          }
      }
      // System.out.println(BBB.num); 
      // 输出 
      // 10000
      // BBB 静态代码块被执行
      
      static final b=1;
      final int c=1;
      

      这里c和b的区别在于,b存放在静态空间,不会在程序运行时被释放,它永远占着内存直到程序终止,而c在程序用完它而不会再用到它的时候就会被自动释放,不再占用内存。

      当一个常数或字符串我们需要在程序里反复反复使用的时候,我们就可以把它定义为static final,这样内存就不用重复的申请和释放空间。

    • 包装类 (Integer,Double,Float,Boolean等都是final),String也是final类,所以不能被继承。

10.6 抽象类

抽象类介绍

  • 基本介绍

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

    在这里插入图片描述

    一般来说,抽象类会被继承,有其子类来实现抽象方法.

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

      访问修饰符 abstract 类名 { }

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

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

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

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

  • 抽象类的使用细节

    • 抽象类不能被实例化;

    • 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法;

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

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

    • 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等;

    • 抽象方法不能有主体,即不能实现.如图所示1664769191505

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

      所谓实现方法,就是有方法体

    • 抽象方法不能使用private、final(不希望子类重写)和 static修饰,因为这些关键字都是和重写相违背的。

抽象类最佳实践 - 模板设计模式
  • 基本介绍

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

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

    在这里插入图片描述

    多个类有一个相同的方法job,但每个类的具体内容有差异,那么就将其写成抽象方法。同时多个类有一个相同的方法cal,内容一模一样,将其写到父类中

    编写父类,写成抽象类,并编写一个抽象方法;

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

    子类,必须重写分类抽象方法;

    public class AA extends Template {
    
        //计算任务
        //1+....+ 800000
        //存在动态绑定机制,运行类型会先找该方法AA类
        @Override
        public void job() { //实现Template的抽象方法job
    
            long num = 0;
            for (long i = 1; i <= 800000; i++) {
                num += i;
            }
        }
    
    //    public void job2() {
    //        //得到开始的时间
    //        long start = System.currentTimeMillis();
    //        long num = 0;
    //        for (long i = 1; i <= 200000; i++) {
    //            num += i;
    //        }
    //        //得的结束的时间
    //        long end = System.currentTimeMillis();
    //        System.out.println("AA 执行时间 " + (end - start));
    //    }
    }
    
    public class BB extends Template{
    
        public void job() {//这里也去,重写了Template的job方法
    
            long num = 0;
            for (long i = 1; i <= 80000; i++) {
                num *= i;
            }
    
        }
    }
    
    public class TestTemplate {
        public static void main(String[] args) {
    
            AA aa = new AA();//编译类型AA ;运行内存是AA;先找运行类型中的方法
            aa.calculateTime(); //这里还是需要有良好的OOP基础,对多态
    
            BB bb = new BB();
            bb.calculateTime();
        }
    }
    

10.7 接口

接口介绍
  • 基本介绍

    在这里插入图片描述

    如果一个类 implements实现 接口,需要将该接口的所有抽象方法都实现

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

    制定规范
    在这里插入图片描述
    在这里插入图片描述

    视频有详细讲解

    相关博客:接口 - 半路_出家ren - 博客园 (cnblogs.com)

  • 注意事项和细节

    • 接口不能被实例化

      本质是抽象类

    • 接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract修饰图示

      在这里插入图片描述

    • 一个普通类实现接口,就必须将该接口中的所有方法都实现。可以使用alt+enter来解决

    • 抽象类实现接口,可以不用实现接口的方法。

    • 一个类同时可以实现多个接口。

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

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

    • 接口不能继承其它的类,但是可以继承多个别的接口。

      接口和接口之间是继承关系,用extends关键字;接口和类之间是实现关系,用implements关键字。

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

  • 练习

    public class InterfaceExercise01 {
        public static void main(String[] args) {
            B b = new B();//ok
            System.out.println(b.a);  //23
            System.out.println(A.a);  //23
            System.out.println(B.a);  //23
        }
    }
    
    interface A {
        int a = 23; //等价 public static final int a = 23;
    }
    
    class B implements A {  //正确
    }
    
实现接口 vs 继承类
  • 当子类继承了父类,就自动的拥有父类的功能

  • 如果子类需要扩展功能,可以通过实现接口的方式扩展。实现接口这种机制是对java中单继承的一种补充(扩展功能)。

在这里插入图片描述

继承:小猴子悟空继承了猴子,自动拥有猴子的功能

接口:小猴子悟空还希望拥有鸟的飞翔,鱼的游泳(但猴子没有这些功能)

  • 小结

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

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

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

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

接口的多态特性

在这里插入图片描述

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

        //接口的多态体现
        //接口类型的变量 if01 可以指向 实现了IF接口类的对象实例
        IF if01 = new Monster();
        if01 = new Car();

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

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

class AAA {

}
class BBB extends AAA {}
class CCC extends AAA {}
public class InterfacePolyArr {
    public static void main(String[] args) {

        //多态数组 -> 接口类型数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
        /*
        给Usb数组中,存放 Phone  和  相机对象,Phone类还有一个特有的方法call(),
        请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,
        还需要调用Phone 特有方法 call
         */
        for(int i = 0; i < usbs.length; i++) {
            usbs[i].work();//动态绑定..
            //和前面一样,我们仍然需要进行类型的向下转型
            if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
                ((Phone_) usbs[i]).call();
            }
        }

    }
}

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

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

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

interface IH {
    void hi();
}
interface IG extends IH{ }
class Teacher implements IG {
    @Override
    public void hi() {
    }
}
  • 练习

    public class InterfaceExercise02 {
        public static void main(String[] args) {
    
        }
    }
    
    interface A {  // 1min 看看
        int x = 0;
    }  //想到 等价 public static final int x = 0;
    
    class B {
        int x = 1;
    } //普通属性
    
    class C extends B implements A {
        public void pX() {
            //System.out.println(x); //错误,原因不明确x
            //可以明确的指定x
            //访问接口的 x 就使用 A.x
            //访问父类的 x 就使用 super.x
            System.out.println(A.x + " " + super.x);
        }
    
        public static void main(String[] args) {
            new C().pX();
        }
    }
    

10.8 内部类

类的五大成员:属性、方法、构造器、代码块、内部类(难点)

在这里插入图片描述

  • 基本介绍

在这里插入图片描述

注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.

public class InnerClass01 { //外部其他类
    public static void main(String[] args) {

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

    }
}
  • 分类

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

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

  1. 可以直接访问外部类的所有成员,包含私有的;
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final。
  3. 作用域:仅仅在定义它的方法或代码块中。
  4. 局部内部类—访问---->外部类的成员[访问方式:直接访问]。
  5. 外部类—访问---->局部内部类的成员。访问方式:创建对象,再访问(注意:必须在作用域内)。
  6. 外部其他类—不能访问----->局部内部类(因为局部内部类定位是一个局部变量)。
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。解释:外部类名.this表示外部类的一个对象(谁调用就是谁的对象)
/**
 * 演示局部内部类的使用
 */
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);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建Inner02对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }

}
  1. 局部内部类定义在方法中/代码块
  2. 作用域在方法体或者代码块中
  3. 本质仍然是一个类
02 匿名内部类(重要!!!)
  • 基本介绍

    • 本质是类
    • 内部类
    • 该类没有名字(系统分配的),型如外部类$数字,$这个符号表示匿名内部类。
    • 同时还是一个对象

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

    有时候对于某个类只需要使用一次,后面就不再适应了。然后可以使用匿名内部类来简化开发

  • 基本语法

    • 基于接口的内部类
    • 基于类的内部类
    new 类或接口(参数列表) {
    	类体 
    };
    
    /**
     * 演示匿名内部类的使用
     */
    public class AnnoymousInnerClass {
        public static void main(String[] args) {
            Outer04 outer04 = new Outer04();
            outer04.method();
        }
    }
    
    class Outer04 { //外部类
        private int n1 = 10;//属性
        public void method() {//方法
            //基于接口的匿名内部类
            //老韩解读
            //1.需求: 想使用IA接口,并创建对象
            //2.传统方式,是写一个类,实现该接口,并创建对象
            //3.老韩需求是 Tiger/Dog 类只是使用一次,后面再不使用
            //4. 可以使用匿名内部类来简化开发
            //5. tiger的编译类型 ? IA
            //6. tiger的运行类型 ? 就是匿名内部类  Outer04$1
            /*
                我们看底层 会分配 类名 Outer04$1
                class Outer04$1 implements IA {
                    @Override
                    public void cry() {
                        System.out.println("老虎叫唤...");
                    }
                }
             */
            //7. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址
            //   返回给 tiger
            //8. 匿名内部类使用一次,就不能再使用
            IA tiger = new IA() {
                @Override
                public void cry() {
                    System.out.println("老虎叫唤...");
                }
            };
            System.out.println("tiger的运行类型=" + tiger.getClass());
            tiger.cry();
            tiger.cry();
            tiger.cry();
    //        传统
    //        IA tiger = new Tiger();
    //        tiger.cry();
    // ================================================
            //演示基于类的匿名内部类
            //分析
            //1. father编译类型 Father
            //2. father运行类型 Outer04$2
            //3. 底层会创建匿名内部类
            /*
                class Outer04$2 extends Father{
                    @Override
                    public void test() {
                        System.out.println("匿名内部类重写了test方法");
                    }
                }
             */
            //4. 同时也直接返回了 匿名内部类 Outer04$2的对象
            //5. 注意("jack") 参数列表会传递给 构造器
            // Father father = new Father("jack"); 这就是一个对象了
            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();
    }
    

    getClass()就是获得运行类型;

  • 注意事项

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

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

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

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

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

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

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

    public class AnonymousInnerClassDetail {
    
    	public static void main(String[] args) {
    		Outer05 outer05 = new Outer05();
    		outer05.f1();
    	}
    }
    
    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. 有一个铃声接口 Bell,里面有个 ring 方法。
    2. 有一个手机类 Cellphone,具有闹钟功能 alarmClock,参数是 Bell 类型
    3. 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
    public class InnerClassExercise02 {
    	
    	public static void main(String[] args) {
    		CellPhone cellphone = new CellPhone();
    	    // 1.传递的是实现了Bell接口的匿名内部类
            // 2.重写了ring() 方法
            // 3. Bell bell = new Bell() {
            //            @Override
            //            public void ring() {
            //                System.out.println("懒猪起床了");
            //            }
            //        }
    		cellphone.alarmclock(new Bell() {
    			@Override
    			public void ring() {  // 要加public
    				System.out.println("懒猪起床了");
    			}
    		});
    	}
    }
    
    interface Bell {  // 接口
    	void ring();  // 方法
    }
    
    class CellPhone {  // 类
    	public void alarmclock(Bell bell) {  // 形参是Bell接口
    		System.out.println(bell.getClass());// 打印运行类型 InnerClassExercise02$1
            bell.ring();  // 会实现动态绑定,重写回到匿名内部类的ring()方法
    	}
    }
    

    匿名内部类涉及很多知识点

    (1)继承 (2)多态 (3)动态绑定 (4)内部类

03 成员内部类

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

  1. 可以直接访问外部类的所有成员,包含私有的。
  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
  3. 作用域和外部类的其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。
  4. 成员内部类—访问---->外部类成员(比如:属性) [访问方式:直接访问]。
  5. 外部类—访问------>成员内部类(说明) 访问方式:创建成员内部类对象,再访问。
  6. 外部其他类—访问---->成员内部类
  7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
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 = "张三";

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

    //1.注意: 成员内部类,是定义在外部类的成员位置上
    //2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    public class Inner08 {//成员内部类
        private double sal = 99.8;
        private int n1 = 66;
        public void say() {
            //可以直接访问外部类的所有成员,包含私有的
            //如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
            //,可以通过  外部类名.this.属性 来访问外部类的成员
            System.out.println("n1 = " + n1 + " name = " + name + " 外部类的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);
    }
}
04 静态内部类

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

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
  3. 作用域:同其他的成员,为整个类体。
  4. 静态内部类—访问---->外部类(比如:静态属性) [访问方式:直接访问所有静态成员]。
  5. 外部类—访问------>静态内部类 访问方式: 创建对象,再访问。
  6. 外部其他类—访问----->静态内部类。
  7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。
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();

        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. 作用域 :同其他的成员,为整个类体
    static class Inner10 {
        private static String name = "哈哈哈";
        public void say() {
            //如果外部类和静态内部类的成员重名时,静态内部类访问的时,
            //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
            System.out.println(name + " 外部类name= " + 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();
    }
}
小结
  • 内部类有四种:
    • 放在方法或代码块中:局部内部类、匿名内部类
    • 放在外部类的成员位置:成员内部类、静态内部类
  • 重点掌握 匿名内部类
  • 成员内部类,静态内部类 放在外部类的成员位置,本质上就是一个成员
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值