学习笔记:黑马程序员Java-中级篇(第二部分)

  1. 含义:是一种对象的描述
  2. 定义:类的组成是由成员变量成员方法两部分组成

类的组成是由属性行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)
  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

10.2方法

笔记小结:

  1. 含义:方法(method)是程序中最小的执行单元
  2. 方法的定义和调用
  • 无参数方法
public static void 方法名 (   ) {
	// 方法体;
}
// 注意,方法必须先定义,后调用,否则程序将报错

  • 带参数方法
public static void 方法名 (参数1) {
	方法体;
}
// 注意,方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错 

  • 带返回值方法
public static 数据类型 方法名 ( 参数 ) { 
	return 数据 ;
}
// 注意,方法定义时return后面的返回值与方法定义上的数据类型要匹配

  1. 方法的注意事项
  • 方法不能嵌套定义
  • 定义方法时,需要明确方法返回值类型、明确方法参数个数
  1. 方法重载
  • 含义:一个类中多个方法,它们具有相方法名参数列表不同(个数不同、类型不同或顺序不同)
  • 格式:
public class MethodDemo {
	public static void fn(int a) {
    	//方法体
    }
    public static int fn(double a) {
    	//方法体
    }
}

  1. 构造方法
  • 含义:它是一种特殊的方法,用于创建并初始化对象
  • 格式:
class Student {
    private String name;
    private int age;

    //构造方法
    public Student() {
        System.out.println("无参构造方法");
    }
}

  1. 标准类

① 类名需要见名知意

② 成员变量使用private修饰

③ 提供至少两个构造方法

④ get和set方法

⑤ 如果还有其他行为,也需要写上

10.2.1概述

方法(method)是程序中最小的执行单元

注意:

  • 方法必须先创建才可以使用,该过程成为方法定义
  • 方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为方法调用
10.2.3定义和调用
10.2.3.1无参数方法定义和调用

基本用例

/\*格式:
 public static void 方法名 ( ) {
 // 方法体;
} \*/

public static void method (    ) {
    // 方法体;
}

// 调用
/\*格式:
 方法名(); \*/
method();

注意:

​ 方法必须先定义,后调用,否则程序将报错

10.2.3.2带参数方法定义和调用

基本用例

/\* 格式:
public static void 方法名 (参数1) {
 方法体;
}

public static void 方法名 (参数1, 参数2, 参数3...) {
 方法体;
} \*/

public static void isEvenNumber(int number){
    ...
}
public static void getMax(int num1, int num2){
    ...
}

// 调用

/\* 格式:
方法名(参数);

方法名(参数1,参数2); \*/
isEvenNumber(10);

getMax(10,20);

说明:

​ 参数是由数据类型和变量名组成 - 数据类型 变量名 例如:int a

注意:

  • 方法定义时,参数中的数据类型与变量名都不能缺少,缺少任意一个程序将报错
  • 方法定义时,多个参数之间使用逗号( ,)分隔
  • 方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错

形参和实参

  1. 形参:方法定义中的参数

​ 等同于变量定义格式,例如:int number

  1. 实参:方法调用中的参数

​ 等同于使用变量或常量,例如: 10 number

10.2.3.3带返回值方法的定义和调用

基本用例

/\*格式:public static 数据类型 方法名 ( 参数 ) { 
 return 数据 ;
} \*/
public static boolean isEvenNumber( int number ) {           
	return true ;
}
public static int getMax( int a, int b ) {
	return  100 ;
}

// 调用

/\* 方法名 ( 参数 ) ;
 数据类型 变量名 = 方法名 ( 参数 ) ; \*/
isEvenNumber ( 5 ) ;
boolean  flag =  isEvenNumber ( 5 ); 

注意:

  • 方法定义时return后面的返回值与方法定义上的数据类型要匹配,否则程序将报错
  • 方法的返回值通常会使用变量接收,否则该返回值将无意义
10.2.4方法的注意事项
10.2.4.1方法不能嵌套定义
  • 示例代码:
public class MethodDemo {
    public static void main(String[] args) {

    }

    public static void methodOne() {
		public static void methodTwo() {
       		// 这里会引发编译错误!!!
    	}
    }
}

10.2.4.2void表示无返回值

可以省略return,也可以单独的书写return,后面不加数据

  • 示例代码:
public class MethodDemo {
    public static void main(String[] args) {

    }
    public static void methodTwo() {
        //return 100; 编译错误,因为没有具体返回值类型
        return;	
        //System.out.println(100); return语句后面不能跟数据或代码
    }
}

10.2.4.3方法的通用格式

格式:

public static 返回值类型 方法名(参数) {
   方法体; 
   return 数据 ;
}

说明:

  • public static 修饰符,目前先记住这个格式

  • 返回值类型

    • 方法操作完毕之后返回的数据的数据类型
    • 如果方法操作完毕,没有数据返回,这里写void,而且方法体中一般不写return
  • 方法名 调用方法时候使用的标识

  • 参数 由数据类型和变量名组成,多个参数之间用逗号隔开

  • 方法体 完成功能的代码块

  • return 如果方法操作完毕,有数据返回,用于把数据返回给调用者

  • 定义方法时,要做到两个明确

    • 明确返回值类型:主要是明确方法操作完毕之后是否有数据返回,如果没有,写void;如果有,写对应的数据类型
    • 明确参数:主要是明确参数的类型和数量
  • 调用方法时的注意:

    • void类型的方法,直接调用即可
    • 非void类型的方法,推荐用变量接收调用
10.2.5方法重载
10.2.5.1概述

​ 方法重载指同一个类中定义的多个方法之间的关系

满足下列条件的多个方法相互构成重载

  • 多个方法在同一个类中
  • 多个方法具有相同的方法名
  • 多个方法的参数不相同,类型不同或者数量不同

注意:

  • 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
  • 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载
10.2.5.2基本用例
  • 正确范例:
public class MethodDemo {
	public static void fn(int a) {
    	//方法体
    }
    public static int fn(double a) {
    	//方法体
    }
}

public class MethodDemo {
	public static float fn(int a) {
    	//方法体
    }
    public static int fn(int a , int b) {
    	//方法体
    }
}

  • 错误范例:
public class MethodDemo {
	public static void fn(int a) {
    	//方法体
    }
    public static int fn(int a) { 	/\*错误原因:重载与返回值无关\*/
    	//方法体
    }
}

public class MethodDemo01 {
    public static void fn(int a) {
        //方法体
    }
} 
public class MethodDemo02 {
    public static int fn(double a) { /\*错误原因:这是两个类的两个fn方法\*/
        //方法体
    }
}

10.2.6构造方法
10.2.6.1概述

构造方法是一种特殊的方法

  • 作用:创建对象 Student stu = new Student();
  • 功能:主要是完成对象数据的初始化
10.2.6.2基本用例
/\* 格式:
 public class 类名{
 修饰符 类名( 参数 ) {
 }
} \*/

class Student {
    private String name;
    private int age;

    //构造方法
    public Student() {
        System.out.println("无参构造方法");
    }

    public void show() {
        System.out.println(name + "," + age);
    }
}
/\*
 测试类
 \*/
public class StudentDemo {
    public static void main(String[] args) {
        //创建对象
        Student s = new Student();
        s.show();
    }
}

10.2.6.3注意事项
  • 构造方法的创建

如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法

  • 构造方法的重载

如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

  • 推荐的使用方式

无论是否使用,都手工书写无参数构造方法

  • 重要功能

可以使用带参构造,为成员变量进行初始化

  • 示例代码
/\*
 学生类
 \*/
class Student {
    private String name;
    private int age;

    public Student() {}

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

    public Student(int age) {
        this.age = age;
    }

    public Student(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public void show() {
        System.out.println(name + "," + age);
    }
}
/\*
 测试类
 \*/
public class StudentDemo {
    public static void main(String[] args) {
        //创建对象
        Student s1 = new Student();
        s1.show();

        //public Student(String name)
        Student s2 = new Student("林青霞");
        s2.show();

        //public Student(int age)
        Student s3 = new Student(30);
        s3.show();

        //public Student(String name,int age)
        Student s4 = new Student("林青霞",30);
        s4.show();
    }
}

10.2.6.4标准类制作

① 类名需要见名知意

② 成员变量使用private修饰

③ 提供至少两个构造方法

  • 无参构造方法
  • 带全部参数的构造方法

④ get和set方法

​ 提供每一个成员变量对应的setXxx()/getXxx()

⑤ 如果还有其他行为,也需要写上

10.3对象

笔记小结:

  1. 含义:对象是指一个具体的实例化的实体,就是一种客观存在的事物
  2. 使用:
格式:类名 对象名 = new 类名();
// 例如
Phone p = new Phone();

  1. 内存图:
  • 单个对象内存图
    1. new出来的对象会存在堆内存
    2. 执行的方法会存在栈内存
  • 多个对象内存图
    1. 多个对象在堆内存中,都有不同的内存划分。其中,成员变量存储在各自的内存区域中
    2. 成员方法中,多个对象共用的一份栈内存
10.3.1概述

对象就是一种客观存在的事物,客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。

    • 类的理解
      • 类是对现实生活中一类具有共同属性和行为的事物的抽象
      • 类是对象的数据类型,类是具有相同属性行为的一组对象的集合
      • 简单理解:类就是对现实事物的一种描述
    • 类的组成
      • 属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
      • 行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
  • 类和对象的关系
    • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
    • 对象:是能够看得到摸的着的真实存在的实体
    • 简单理解:类是对事物的一种描述,对象则为具体存在的事物
10.3.2基本用例
/\*格式:
 创建对象
 类名 对象名 = new 类名();\*/

/\*格式:
 使用成员变量
 格式:对象名.变量名
 使用成员方法
 格式:对象名.方法名()
 \*/
public class PhoneDemo {
    public static void main(String[] args) {
        //创建对象
        Phone p = new Phone();

        //使用成员变量
        System.out.println(p.brand);
        System.out.println(p.price);

        p.brand = "小米";
        p.price = 2999;

        System.out.println(p.brand);
        System.out.println(p.price);

        //使用成员方法
        p.call();
        p.sendMessage();
    }
}

10.3.3对象内存图(重点)
10.3.3.1单个对象内存图
  • 成员变量使用过程

image-20230813145155388

  • 成员方法调用过程

image-20230813145205253

10.3.3.2多个对象内存图
  • 成员变量使用过程

image-20230813145212598

  • 成员方法调用过程

image-20230813145218840

  • 总结:

多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份堆内存空间

10.4封装

笔记小结:

  1. 含义:指隐藏对象的内部实现细节,并将其暴露出来可以使用的公共接口
  2. 作用:
  • 隐藏实现细节
  • 简化编程
  • 提高代码复用性
  • 接口隔离
10.4.1概述

​ 在 Java 中,封装是面向对象编程中的一种重要的概念,它指的是将类的属性和方法保护起来,以避免外部程序直接访问和修改它们,从而提高了类的安全性和可维护性。封装的实现可以通过访问控制修饰符(public、private、protected)来实现。

10.4.2基本用例
public class Person {
    private String name;
    private int age;
    private double height;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }
}


说明:

​ 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问

10.5继承

笔记小结:

  1. 概述:
  • 含义:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为

  • 优点:

    1. 代码重用
    2. 可以实现多态性
    3. 代码可维护性高
    4. 代码可扩展性强
    5. 代码复用性增强
  • 格式;

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}
// 子类 extends 父类

  1. 注意事项:子类不能继承的内容
  • 构造方法:非私有(不能),私有(不能)
  • 成员变量非私有(能),私有(能)
  • 成员方法虚方法表(能),否则(不能)
  • 注意:子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量
  1. 继承后的特点:
  • 成员变量:

    1. 成员变量不重名不影响
    2. 成员变量重名:子类会优先访问自己对象中的成员变量
    3. 要想在子类访问父类成员变量可通过super关键字
    // 例如
    super.school
    
    
  • 成员方法:

    1. 成员方法不重名不影响
    2. 成员方法重名:子类会优先访问自己对象中的成员方法
    3. 方法重写:
      • 含义:声明不变,重新实现
      • @Override:注解,重写注解校验
      • 注意:
        1. 必须是子类与父类的关系
        2. 权限大于等于父类权限
        3. 返回值类型、函数参数列表需要一样
  • 构造方法:

    • 特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法
    • 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写
    public Student() {
        //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
    
    
  1. 特点:
  • Java只支持单继承,不支持多继承
  • 一个类可以有多个子类
  • 继承之间可以形成多层继承
10.5.1概述
10.5.1.1定义

​ 继承就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。从而提高代码的复用性(减少代码冗余,相同代码重复利用)。使类与类之间产生了关系。

10.5.1.2子类不能继承的内容

说明:

  • 子类中不能继承构造方法,不能继承非虚方法表里的方法
  • 虚方法表就算非 static、final、private修饰的方法

image-20230303195211274

说明:

​ Java的继承,并不是一层一层往上寻找父方法,而是先判断虚方法表中有无此方法,方便直接调用

注意

​ 值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量

10.5.2基本用例

说明:

​ 类继承用法,基础使用

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}

注意:

​ Java是单继承的,一个类只能继承一个直接父类

10.5.4继承后的特点—成员变量
10.5.4.1成员变量不重名

如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:

class Fu {
	// Fu中的成员变量
	int num = 5;
}
class Zi extends Fu {
	// Zi中的成员变量
	int num2 = 6;
  
	// Zi中的成员方法
	public void show() {
		// 访问父类中的num
		System.out.println("Fu num="+num); // 继承而来,所以直接访问。
		// 访问子类中的num2
		System.out.println("Zi num2="+num2);
	}
}
class Demo04 {
	public static void main(String[] args) {
        // 创建子类对象
		Zi z = new Zi(); 
      	// 调用子类中的show方法
		z.show();  
	}
}

演示结果:
Fu num = 5
Zi num2 = 6

10.5.4.2成员变量重名

如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:

class Fu1 {
	// Fu中的成员变量。
	int num = 5;
}
class Zi1 extends Fu1 {
	// Zi中的成员变量
	int num = 6;
  
	public void show() {
		// 访问父类中的num
		System.out.print9ln("Fu num=" + num);
		// 访问子类中的num
		System.out.println("Zi num=" + num);
	}
}
class Demo04 {
	public static void main(String[] args) {
      	// 创建子类对象
		Zi1 z = new Zi1(); 
      	// 调用子类中的show方法
		z1.show(); 
	}
}
演示结果:
Fu num = 6
Zi num = 6

说明:

  • 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量如何解决呢?我们可以使用super关键字。
  • 例如
class Fu {
	// Fu中的成员变量。
	int num = 5;
}

class Zi extends Fu {
	// Zi中的成员变量
	int num = 6;

	public void show() {
        int num = 1;

        // 访问方法中的num
        System.out.println("method num=" + num);  //method num=1
        // 访问子类中的num
        System.out.println("Zi num=" + this.num); // Zi num=6
        // 访问父类中的num
        System.out.println("Fu num=" + super.num); // Fu num=5
	}
}

class Demo04 {
	public static void main(String[] args) {
      	// 创建子类对象
		Zi1 z = new Zi1(); 
      	// 调用子类中的show方法
		z1.show(); 
	}
}

需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。

10.5.5继承后的特点—成员方法
10.5.5.1成员方法不重名

​ 如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:

class Fu {
	public void show() {
		System.out.println("Fu类中的show方法执行");
	}
}
class Zi extends Fu {
	public void show2() {
		System.out.println("Zi类中的show2方法执行");
	}
}
public  class Demo05 {
	public static void main(String[] args) {
		Zi z = new Zi();
     	//子类中没有show方法,但是可以找到父类方法去执行
		z.show(); 
		z.show2();
	}
}

10.5.5.2成员方法重名

如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。

代码如下:

class Fu {
	public void show() {
		System.out.println("Fu show");
	}
}
class Zi extends Fu {
	//子类重写了父类的show方法
	public void show() {
		System.out.println("Zi show");
	}
}
public class ExtendsDemo05{
	public static void main(String[] args) {
		Zi z = new Zi();
     	// 子类中有show方法,只执行重写后的show方法
		z.show();  // Zi show
	}
}

10.5.7方法重写
10.5.7.1概念

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

10.5.7.2使用场景与案例

​ 发生在子父类之间的关系。
​ 子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方 法。

10.5.7.3@Override重写注解
  • @Override:注解,重写注解校验!
  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
  • 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!
10.5.7.4注意事项
  1. 方法重写是发生在子父类之间的关系
  2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
  3. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
10.5.8继承后的特点—构造方法

笔记小结

概述:子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。

10.5.8.1概述

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子
  3. 子类重写父类方法时,访问权限子类必须大于等于父类
  4. 子类重写父类方法时,返回值类型子类必须小于等于父类
  5. 建议:重写的方法尽量和父类保持一致。

继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

10.5.8.2案例-人际关系

按如下需求定义类:

  1. 人类
    成员变量: 姓名,年龄
    成员方法: 吃饭
  2. 学生类
    成员变量: 姓名,年龄,成绩
    成员方法: 吃饭

代码如下:

class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("父类无参");
    }

    // getter/setter省略
}

class Student extends Person {
    private double score;

    public Student() {
        //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
    
     public Student(double score) {
        //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
        this.score = score;    
        System.out.println("子类有参");
     }

}

public class Demo07 {
    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println("----------");
        Student s2 = new Student(99.9);
    }
}

输出结果:
父类无参
子类无参
----------
父类无参
子类有参

小结

  • 子类构造方法执行的时候,都会在第一行默认先调用父类无参数构造方法一次。
  • 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。
10.5.9继承的特点
  1. Java只支持单继承,不支持多继承。
// 一个类只能有一个父类,不可以有多个父类。
class A {}
class B {}
class C1 extends A {} // ok
// class C2 extends A, B {} // error

  1. 一个类可以有多个子类。
// A可以有多个子类
class A {}
class C1 extends A {}
class C2 extends  A {}

  1. 可以多层继承。
class A {}
class C1 extends A {}
class D extends C1 {}

说明:

​ 顶层父类是Object类。所有的类默认继承Object,作为父类。

10.6多态

笔记小结:

  1. 概述:
  • 定义:指同一个方法或者同一个类,在不同的情况下具有不同表现形式
  • 注意:满足多态的前提是有继承或者实现关系
  1. 格式:父new子
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

// 多态前提,有继承关系,子类对象是可以赋值给父类类型的变量.

  1. 使用场景
  • 父new子
  • 父new接口
  1. 特点
  • 调用成员变量时:编译看左边,运行看左边
  • 调用成员方法时:编译看左边,运行看右边
  1. 内存图

image-20230813145339502
6. 多态优点:

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护
  • 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利
  1. 多态缺点:无法直接访问子类特有的成员,也就是多态的写法无法访问子类独有功能,也就是说,父类不能调用子类的特有方法
  2. 引用类型转换:
  • 含义:将一个对象的引用从一个类型转换为另一个类型

  • 向上转型:将一个子类对象的引用转换为其超类接口类型的引用

    • 例如
    父类类型  变量名 = new 子类类型();
    Animal a = new Cat();
    
    
  • 向下转型:将一个超类或接口类型的引用转换为一个子类对象的引用

    • 例如
    子类类型 变量名 = (子类类型) 父类变量名;
    Aniaml a = new Cat();
    Cat c =(Cat) a;  
    
    
  1. instanceof关键字
  • 含义:用于测试一个对象是否是一个类的实例,或者是该类的子类或接口的实例。简单来说就是类型校验
变量名 instanceof 数据类型 
/\*
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。\*/

10.6.1概述
10.6.1.1定义

多态是指同一行为,具有多个不同表现形式。

多态是继封装、继承之后,面向对象的第三大特性。

多态是出现在继承或者实现关系中的。

10.6.1.2前提【重点】
  1. 继承或者实现关系
  2. 方法的重写【意义体现:不重写,无意义】
  3. 父类引用指向子类对象【格式体现】

说明:

​ 父类类型:指类对象继承类类型,或者实现父接口类型。

10.6.1.3格式
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

10.6.1.4使用场景

​ 如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。

image-20230811093510641

有了多态之后,方法的形参就可以定义为共同的父类Person。

注意:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

image-20230811094030529

10.6.2基本用例

步骤一:创建父类

  • 创建Person实体类
public class Person {
    private String name;
    private int age;

    空参构造
    带全部参数的构造
    get和set方法

    public void show(){
        System.out.println(name + ", " + age);
    }
}

步骤二:创建子类

1.创建Administrator实体类,并继承Person

public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}

2.创建Student实体类,并继承Person

public class Student extends Person{

    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}

3.创建Teacher实体类,并继承Person

public class Teacher extends Person{

    @Override
    public void show() {
        System.out.println("老师的信息为:" + getName() + ", " + getAge());
    }
}

步骤三:演示

  • 创建Test用于演示register方法

注意:

register方法的参数需要为Person父类,这样才能在参数中传入子类对象

public class Test {
    public static void main(String[] args) {
        //创建三个对象,并调用register方法

        Student s = new Student();
        s.setName("张三");
        s.setAge(18);


        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);

        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);



        register(s);
        register(t);
        register(admin);


    }



    //这个方法既能接收老师,又能接收学生,还能接收管理员
    //只能把参数写成这三个类型的父类
    public static void register(Person p){
        p.show();
    }
}

10.6.3运行特点
  • 调用成员变量时:编译看左边,运行看左边
  • 调用成员方法时:编译看左边,运行看右边

调用成员变量时:编译看左边,运行看左边

说明:

  • 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果编译成功,如果没有编译失败。
  • 运行看左边:Java运行代码的时候,若父类与子类都存在相同名字的变量,那么则调用左边,也就是父类中的变量

调用成员方法时:编译看左边,运行看右边

说明:

  • 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果编译成功,如果没有编译失败。
  • 运行看右边:Java运行代码的时候,若父类与子类都存在相同名字的方法,那么则运行右边,也就是子类中的方法

代码示例:

Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

说明:

  • 成员变量:在子类的对象中,会把父类的成员变量也继承下的。父: name子: name
  • 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖
10.6.4调用成员的内存图解(重点)

详细链接:https://www.bilibili.com/video/BV17F411T7Ao?p=130image-20230303205114437

结论:

  • 调用成员变量的特点:编译看左边,运行也看左边
  • 调用成员方法的特点:编译看左边,运行看右边
10.6.5多态的优势
  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护
Person p =new Student ();
p.work();//业务逻辑发生改变时,后续代码无需修改

  • 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
ArrayList list = new ArrayList();
list.add(student);
list.add("ok");
System.out.println(list);

说明:

​ 当不指定ArrayList的泛型时,添加的数据类型将不受到限制

10.6.6多态的弊端

​ 我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

class Animal{
    public  void eat(){
        System.out.println("动物吃东西!")
    }
}
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
   
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
        a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
    }
}

10.6.7引用类型转换
10.6.7.1概述

​ 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

回顾基本数据类型转换

  • 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
  • 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14

​ 多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。

10.6.7.2向上转型(自动转换)

向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。

  • 使用格式:
父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

**原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。**所以子类范围小可以直接自动转型给父类类型的变量。

10.6.7.3向下转型(强制转换)

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

基本用例

/\* 格式:
子类类型 变量名 = (子类类型) 父类变量名; \*/
Aniaml a = new Cat();
Cat c =(Cat) a;  

10.6.7.4案例-转型演示

​ 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

步骤一:定义父类

  • 定义Animal作为父类
abstract class Animal {  
    abstract void eat();  
}  

步骤二:定义子类

1.创建Cat实体类,并继承Animal

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

2.创建Dog实体类,并继承Animal

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

步骤三:演示

public class Test {
    public static void main(String[] args) {
        // 向上转型 
        Animal a = new Cat();  
        a.eat(); 				// 调用的是 Cat 的 eat

        // 向下转型 
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
    }  
}

10.6.7.5转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

public class Test {
    public static void main(String[] args) {
        // 向上转型 
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型 
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}

​ 这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

10.6.7.6instanceof关键字(重点)

​ 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。

所以,转换前,我们最好先做一个判断,代码如下:

public class Test {
    public static void main(String[] args) {
        // 向上转型 
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型 
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}

10.6.7.7instanceof新特性

​ JDK14的时候提出了新特性,把判断和强转合并成了一行

//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
    d.lookHome();
}else if(a instanceof Cat c){
    c.catchMouse();
}else{
    System.out.println("没有这个类型,无法转换");
}

11.抽象类

笔记小结:

  1. 概述:
  • 定义:没有方法体的方法称为抽象方法,包含抽象方法的类就是抽象类
  • 抽象方法:没有方法体的方法
// 抽象方法
public abstract void abstractMethod();

  • 抽象类:包含抽象方法的类
public abstract class AbstractClass {
    // 抽象方法
    public abstract void abstractMethod();
}

  • 注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
  1. abstract介绍
  • 使用
// 被继承

  • 特征:

    1. 抽象类得到了拥有抽象方法的能力,也就是说自己的一套规范
    2. 抽象类失去了创建对象的能力,也就是说不能创建对象
  • 细节:

    • 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
    • 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
    • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
    • 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错
    • 抽象类存在的意义是为了被子类继承
  • 意义:抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义

  • 抽象类和接口区别,主要在于构造函数、成员变量、继承关系(单继承,多实现)

11.1概述

11.1.1定义

​ Java抽象类(Abstract Class)是一种特殊的类,它不能被实例化,只能被继承。抽象类用于定义一些基本行为,而具体的行为由其子类来实现。

11.1.2抽象方法

没有方法体的方法,也就是没有"{ }"的方法

11.1.3抽象类

包含抽象方法的类

11.1abstract关键字

11.1.1abstract含义

​ abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类

11.1.2抽象方法

​ 使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

基本用例:

/\* 格式:
修饰符 abstract 返回值类型 方法名 (参数列表);\*/
public abstract void run();

11.1.3抽象类

​ 如果一个类包含抽象方法,那么该类必须是抽象类。注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。

/\* 格式:
abstract class 类名字 { 
 
} \*/
public abstract class Animal {
    public abstract void run();
}

11.2基本用例

要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。

// 父类,抽象类
abstract class Employee {
	private String id;
	private String name;
	private double salary;
	
	public Employee() {
	}
	
	public Employee(String id, String name, double salary) {
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	
	// 抽象方法
	// 抽象方法必须要放在抽象类中
	abstract public void work();
}

// 定义一个子类继承抽象类
class Manager extends Employee {
	public Manager() {
	}
	public Manager(String id, String name, double salary) {
		super(id, name, salary);
	}
	// 2.重写父类的抽象方法
	@Override
	public void work() {
		System.out.println("管理其他人");
	}
}

// 定义一个子类继承抽象类
class Cook extends Employee {
	public Cook() {
	}
	public Cook(String id, String name, double salary) {
		super(id, name, salary);
	}
	@Override
	public void work() {
		System.out.println("厨师炒菜多加点盐...");
	}
}

// 测试类
public class Demo10 {
	public static void main(String[] args) {
		// 创建抽象类,抽象类不能创建对象
		// 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象
// Employee e = new Employee();
// e.work();
		
		// 3.创建子类
		Manager m = new Manager();
		m.work();
		
		Cook c = new Cook("ap002", "库克", 1);
		c.work();
	}
}

说明:

​ 此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

11.3特征

抽象类的特征总结起来可以说是 有得有失

有得:抽象类得到了拥有抽象方法的能力。

有失:抽象类失去了创建对象的能力。

其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。

11.4细节

不需要背,只要当idea报错之后,知道如何修改即可。

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  1. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  1. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  1. 抽象类存在的意义是为了被子类继承

理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

11.5意义

​ 抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写

11.6.抽象类和接口的区别

抽象类接口
方法实现可以有实现的方法和非抽象方法只有方法签名,无方法实现
构造函数可以构造函数法定义构造函数
成员变量可以成员变量只能定义常量,成员变量
继承关系子类只能继承一个抽象类类可以实现多个接
功能实现提供对类的部分实现定义契约和行为规范

12.接口

笔记小结:

  1. 概述
  • 含义:接口是一种规范或契约,它只定义了方法签名、常量以及嵌套类型的声明,没有方法实现或属性
  • 格式:
//接口的定义格式:
interface 接口名称{
    // 抽象方法
}
// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”

  • 特点:

    1. 抽象方法:会自动加上public abstract修饰
    2. 常量:会自动加上 public static final修饰
  1. 基本实现
  • 实现方式:使用 implements关键字
  • 格式:
/\*\*接口的实现:
 在Java中接口是被实现的,实现接口的类称为实现类。
 实现类的格式:\*/
class 类名 implements 接口1,接口2,接口3...{
}

  • 要求:接口体现的是一种规范,接口对实现类是一种强制性的约束。需要强制重写或定义为抽象类
  1. 接口与接口的多继承
  • 含义:一个接口可以继承另一个或多个接口,这被称为接口的多继承
  • 格式:
public interface SportMan extends Law , Abc {
    void run();
}

  • 补充:接口和类之间的关系

    1. 类和类是继承关系,只能单继承,不能多继承
    2. 类与接口是实现关系,可以单实现,还可以多实现
    3. 接口与接口是继承关系,可以单继承,还可以多继承
  1. JDK中接口新增:
  • JDK7以前:接口中只能定义抽象方法。
  • JDK8以后:新增默认方法(default method
格式:public default 返回值类型 方法名(参数列表) {   }
// 例如
public default void show(){
    System.out.println("InterA接口中的默认方法 ---- show");
}

  • 特点:

    1. 默认方法不是抽象方法,所以不强制被重写
    2. 解决接口升级的问题
  • 注意:

    1. 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    2. public可以省略,static不能省略
  • JDK9以后:新增private修饰符

  • 格式:

- 格式 : private返回值类型方法名(参数列表){}
- 范例: private void show() { }

- 格式: private static返回值类型方法名(参数列表){}
- 范例: private static void method(){ }

  1. 接口的多态
  • 含义:当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态
  1. 接口的细节
  • 实现类可以同时继承A类也可以实现接口,不过需要实现所有方法
  • 实现类可以同时继承抽象类也可以实现接口,不过需要重写所有方法
  • 实现类实现了两个接口,且两个接口存在相同抽象方法,此时只需重写一次
  • 当实现了接口,子类实现类中的方法跟父类方法同名是,看需求选择重写
  • 做空重写:只需要重写实现类中的部分接口,可先创建类进行重写,在将此类继承,实现部分

12.1概述

12.1.1定义

​ 我们已经学完了抽象类,抽象类中可以用抽象方法,也可以有普通方法,构造方法,成员变量等。那么什么是接口呢?接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的

12.1.2格式
//接口的定义格式:
interface 接口名称{
    // 抽象方法
}

// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”

12.1.3特点
在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量

12.1.3.1抽象方法

注意:接口中的抽象方法默认会自动加上public abstract修饰,程序员无需自己手写!!
按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。

12.1.3.2常量

​ 在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。

12.2基本用例

public interface InterF {
    // 抽象方法!
    // public abstract void run();
    void run();

    // public abstract String getName();
    String getName();

    // public abstract int add(int a , int b);
    int add(int a , int b);


    // 它的最终写法是:
    // public static final int AGE = 12 ;
    int AGE  = 12; //常量
    String SCHOOL\_NAME = "黑马程序员";

}

12.3基本的实现

12.3.1概述

​ 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字

12.3.2格式
/\*\*接口的实现:
 在Java中接口是被实现的,实现接口的类称为实现类。
 实现类的格式:\*/
class 类名 implements 接口1,接口2,接口3...{

}

12.3.3要求和意义
  1. 必须重写实现的全部接口中所有抽象方法。
  2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
  3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
12.3.4案例-基本实现

假如我们定义一个运动员的接口(规范),代码如下:

/\*\*
 接口:接口体现的是规范。
 \* \*/
public interface SportMan {
    void run(); // 抽象方法,跑步。
    void law(); // 抽象方法,遵守法律。
    String compittion(String project);  // 抽象方法,比赛。
}

接下来定义一个乒乓球运动员类,实现接口,实现接口的实现类代码如下:

package com.itheima._03接口的实现;
/\*\*
 \* 接口的实现:
 \* 在Java中接口是被实现的,实现接口的类称为实现类。
 \* 实现类的格式:
 \* class 类名 implements 接口1,接口2,接口3...{
 \*
 \*
 \* }
 \* \*/
public class PingPongMan  implements SportMan {
    @Override
    public void run() {
        System.out.println("乒乓球运动员稍微跑一下!!");
    }

    @Override
    public void law() {
        System.out.println("乒乓球运动员守法!");
    }

    @Override
    public String compittion(String project) {
        return "参加"+project+"得金牌!";
    }
}

测试代码

public class TestMain {
    public static void main(String[] args) {
        // 创建实现类对象。
        PingPongMan zjk = new PingPongMan();
        zjk.run();
        zjk.law();
        System.out.println(zjk.compittion("全球乒乓球比赛"));

    }
}

12.3.5案例-多实现

类与接口之间的关系是多实现的,一个类可以同时实现多个接口。

首先我们先定义两个接口,代码如下:

/\*\* 法律规范:接口\*/
public interface Law {
    void rule();
}

/\*\* 这一个运动员的规范:接口\*/
public interface SportMan {
    void run();
}


然后定义一个实现类:

/\*\*
 \* Java中接口是可以被多实现的:
 \* 一个类可以实现多个接口: Law, SportMan
 \*
 \* \*/
public class JumpMan implements Law ,SportMan {
    @Override
    public void rule() {
        System.out.println("尊长守法");
    }

    @Override
    public void run() {
        System.out.println("训练跑步!");
    }
}

说明:

​ 从上面可以看出类与接口之间是可以多实现的,我们可以理解成实现多个规范,这是合理的

12.3接口与接口的多继承

​ Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口

注意:

  • 类与接口是实现关系
  • 接口与接口是继承关系

接口继承接口就是把其他接口的抽象方法与本接口进行了合并。

案例演示:

public interface Abc {
    void go();
    void test();
}

/\*\* 法律规范:接口\*/
public interface Law {
    void rule();
    void test();
}

 \*
 \*  总结:
 \*     接口与类之间是多实现的。
 \*     接口与接口之间是多继承的。
 \* \*/
public interface SportMan extends Law , Abc {
    void run();
}

12.4JDK8以后接口中新增的方法

  • 允许在接口中定义默认方法,需要使用关键字default修饰

作用:解决接口升级的问题

  • 接口中默认方法的定义格式:

    • 格式: public default返回值类型方法名(参数列表){}
    • 范例:public default void show(){ }
  • 接口中默认方法的注意事项:

    • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
    • public可以省略,default不能省略
    • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写

示例:

public interface InterA {
     /\*接口中默认方法的定义格式:
 格式:public default 返回值类型 方法名(参数列表) { }

 接口中默认方法的注意事项:
 1.默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
 2.public可以省略,default不能省略
 3.如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写\*/


    public abstract void method();

    public default void show(){
        System.out.println("InterA接口中的默认方法 ---- show");
    }
}


接口中,静态的关键字可以不能省略。

接口中,静态的关键字修饰可以又方法体

接口中,静态的关键字是为了解决接口升级的问题

  • 允许在接口中定义定义静态方法,需要用static修饰
  • 接口中静态方法的定义格式:
    • 格式: public static返回值类型方法名(参数列表){}
    • 范例:public static void show(){ }
  • 接口中静态方法的注意事项:
    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略

示例:

在接口中,被static修饰的方法,不能被重写,可以直接调用

重写(子类把从父类继承下来的虚方法表里面的方法进行覆盖了,这才叫重写。)

12.5JDK9新增的方法

基本用例

/\*格式:
private返回值类型方法名(参数列表){} \*/
private void show() { }

/\*格式:
 private static返回值类型方法名(参数列表){} \*/
private static void method(){ }

12.6接口的应用

12.6.1接口的灵活使用

​ 接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了

image-20230304192545514

若想让某种javaBean实现某种功能,则实现某种接口即可

12.6.2接口的多态

​ 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态

image-20230304193304751

说明:

​ 如果一个方法中,当参数为接口时,那么在调用方法时就可传递这个接口的所有实现类对象

基本用例:

public class Main {
    public static void main(String[] args) {
        test(new IHomeServiceImpl());
    }
    private static void test(IHomeService IHomeService){
        IHomeServiceImpl home1= (IHomeServiceImpl) IHomeService;
        home1.test();
    }

说明:

​ IHomeService为接口,那么当这个接口需要什么对象时,new相应对象即可拿到这个接口对应的实现类对象

12.7适配器设计模式

请查看接口的细节 第5个

12.8接口的细节

关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

​ 只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  1. 实现类能不能继承A类的时候,同时实现其他接口呢?
  • 继承的父类,就好比是亲爸爸一样
  • 实现的接口,就好比是干爹一样
  • 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
  1. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写

  1. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
  • 处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
  • 处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
  1. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?(重点)
  1. 可以在接口跟实现类中间,新建一个中间类(适配器类)
  2. 让这个适配器类去实现接口,对接口里面的所有的方法做空重写
  3. 让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
  4. 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

13.枚举

笔记小结:

  1. 含义:它是一种特殊的数据类型,用于定义一组固定的常量
  2. 普通枚举常量
enum Weekday {
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

  1. 数的枚举常量
enum DayOfWeek {
    MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);
    private int value;
    DayOfWeek(int value) {
        this.value = value;
    }
    public int getValue() {
        return this.value;


  1. 实现接口枚举常量
public enum BasicOperation implements Operation {
    PLUS("+") {
        public int apply(int x, int y) { return x + y; }
    }

    private final String symbol;

    BasicOperation(String symbol) {
             this.symbol = symbol;
    }
}

  1. 匿名内部类的方式
enum Operation {
    PLUS {
        public int apply(int x, int y) {
            return x + y;
        }
    }
    public abstract int apply(int x, int y);
}

13.1概述

​ Java中枚举是一种特殊的数据类型,用于定义一组固定的常量。枚举类型定义了一个枚举集合,可以在其中定义枚举常量,并且可以通过名称来访问它们。枚举在Java中是一个独立的类,可以包含属性、方法和构造函数等元素

​ 枚举常量通常用于表示一组有限的可能取值,例如一周中的星期几、一年中的季节、颜色等等。使用枚举类型可以提高代码的可读性、可维护性和可扩展性

13.2基本用例

步骤一:定义枚举

  • 创建枚举Weekday
enum Weekday {
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

说明:

​ 常量的定义用作为分隔符进行分割

步骤二:演示

// 调用
Weekday today = Weekday.MONDAY;

说明:

​ 调用的方式类跟静态常量的调用方法相同

13.3枚举常量

/\* 格式
enum 枚举名 {
 枚举常量1,枚举常量2,……
} \*/
enum Weekday {
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

// 使用
Weekday today = Weekday.MONDAY;
System.out.println(day); // 输出 MONDAY

13.4带有参数的枚举常量

/\* 格式 
enum 枚举名 {
 枚举常量1(值),枚举常量2(值),……
} \*/
enum DayOfWeek {
    MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);
    private int value;
    DayOfWeek(int value) {
        this.value = value;
    }
    public int getValue() {
        return this.value;
    }
}
// 使用
DayOfWeek day = DayOfWeek.MONDAY;
int value = day.getValue();
System.out.println("Today is " + day + ", value is " + value);// 输出Today is MONDAY, value is 1

13.5实现接口的枚举常量

/\* 格式 
public interface Operation {
 int apply(int x, int y);
} \*/

public enum BasicOperation implements Operation {
    PLUS("+") {
        public int apply(int x, int y) { return x + y; }
    },
    MINUS("-") {
        public int apply(int x, int y) { return x - y; }
    },
    TIMES("\*") {
        public int apply(int x, int y) { return x \* y; }
    },
    DIVIDE("/") {
        public int apply(int x, int y) { return x / y; }
    };
    
    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override public String toString() {
        return symbol;
    }
}

// 使用
int result = BasicOperation.PLUS.apply(1, 2);  // 3

13.6匿名内部类的方式

// 例如
enum Operation {
    PLUS {
        public int apply(int x, int y) {
            return x + y;
        }
    },
    MINUS {
        public int apply(int x, int y) {
            return x - y;
        }
    },
    TIMES {
        public int apply(int x, int y) {
            return x \* y;
        }
    },
    DIVIDE {
        public int apply(int x, int y) {
            return x / y;
        }
    };

    public abstract int apply(int x, int y);
}

// 使用
int result = Operation.PLUS.apply(1, 2) // 返回值为3

14.常用类

笔记小结:

  • StringStringBuilderStringJoiner类,见各个小节
  • 字符串原理:请参见字符串原理小节内容。
  • 注意:不断的使用String s = “常量”进行字符串拼接会消耗大量的堆内存空间,建议使用StringJoiner类进行字符串的拼接

14.1String类

笔记小结:

  1. 概述:Java 程序中所有的双引号字符串都是 String 类的对象。String对象不需要导包
  2. 特点:
  • 不可变性:频繁修改字符串的操作会导致内存占用和性能问题,因为频繁创建String对象并进行重新赋值
  • 线程安全性:多个线程可以同时访问同一个String对象,而不会出现线程安全问题
  • 存储在常量池中:Java中有一个字符串常量池,用于存储字符串常量
  1. 构造方法
  2. String()
  3. String(char[] chs)
  4. String(byte[] bys)
  5. String s = “常量”
  6. 构造方法创建对象与直接赋值区别
  7. 创建对象:不存在复用,对象每new一次就会在内存中开辟一个新的空间
  8. 直接赋值:存在复用,若对象的值是常量池中已存在的,则JVM不会在堆内存中开辟新的空间
  9. 字符串比较
  10. == 的作用:基本数据类型比较引用数据类型比较地址值
  11. equals方法的作用:比较字符串内容是否相同
  12. equalsIgnoreCase方法的作用:比较字符串内容是否相同,不区分大小
  13. 案例:
  14. 手机号屏蔽:字符.substring()
  15. 敏感词替换:字符串.replace()
14.1.1概述

​ String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包

14.1.2特点
  • 不可变性:String对象一旦创建,其内容不可变。这意味着当对一个String对象进行修改时,实际上是创建了一个新的String对象,并将原对象的内容复制到新对象中。因此,频繁修改字符串的操作会导致内存占用和性能问题
  • 线程安全性:由于String对象不可变,所以多个线程可以同时访问同一个String对象,而不会出现线程安全问题
  • 存储在常量池中:Java中有一个字符串常量池,用于存储字符串常量。当创建一个字符串时,如果该字符串已经存在于常量池中,则返回常量池中的字符串对象,否则创建一个新的对象并添加到常量池中
  • 支持操作符+和+=:可以通过+和+=操作符将两个字符串连接起来,形成一个新的字符串。但是需要注意的是,每次使用这些操作符都会创建一个新的String对象
  • 支持Unicode编码:String类中的字符编码采用Unicode编码,这使得String可以支持多种语言和字符集
  • 支持常用的字符串操作:String类提供了许多常用的字符串操作,例如字符串比较、查找、替换、切割、大小写转换等
14.1.3构造方法
  • 常用的构造方法
方法名说明
public String()创建一个空白字符串对象,不含有任何内容
public String(char[] chs)根据字符数组的内容,来创建字符串对象
public String(byte[] bys)根据字节数组的内容,来创建字符串对象
String s = “abc”;直接赋值的方式创建字符串对象,内容就是abc
  • 示例代码
public class StringDemo01 {
    public static void main(String[] args) {
        //public String():创建一个空白字符串对象,不含有任何内容
        String s1 = new String();
        System.out.println("s1:" + s1);

        //public String(char[] chs):根据字符数组的内容,来创建字符串对象
        char[] chs = {'a', 'b', 'c'};
        String s2 = new String(chs);
        System.out.println("s2:" + s2);

        //public String(byte[] bys):根据字节数组的内容,来创建字符串对象
        byte[] bys = {97, 98, 99};
        String s3 = new String(bys);
        System.out.println("s3:" + s3);

        //String s = “abc”: 直接赋值的方式创建字符串对象,内容就是abc
        String s4 = "abc";
        System.out.println("s4:" + s4);
    }
}

说明:

  • 字符数组常用在更换 某个字符,此时需要构造方法
  • 字节数组常用在把字节信息进行转换,此时需要构造方法
14.1.4构造和直接赋值方式创建对象区别
  • 通过构造方法创建

​ 通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同

image-20230303094222966

说明:

​ 不存在复用,因为每次new出来的对象会在内存中开辟一个新的空间

  • 直接赋值方式创建

​ 以“ ”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护

image-20230303093958531

说明:

​ 当使用双引号直接赋值时,系统会检查该字符串在串池中是否存在。若不存在,则创建新的、若存在则复用

14.1.5字符串的比较
14.1.5.1==号的作用
  • 比较基本数据类型:比较的是具体的值
  • 比较引用数据类型:比较的是对象地址值
14.1.5.2equals方法的作用
  • 方法介绍
public boolean equals(String s)     比较两个字符串内容是否相同、区分大小写

  • 基本用例
public class StringDemo02 {
    public static void main(String[] args) {
        //构造方法的方式得到对象
        char[] chs = {'a', 'b', 'c'};
        String s1 = new String(chs);
        String s2 = new String(chs);

        //直接赋值的方式得到对象
        String s3 = "abc";
        String s4 = "abc";

        //比较字符串对象地址是否相同
        System.out.println(s1 == s2); // false
        System.out.println(s1 == s3); // false
        System.out.println(s3 == s4); // true
        System.out.println("--------");

        //比较字符串内容是否相同
        System.out.println(s1.equals(s2)); // true
        System.out.println(s1.equals(s3)); // true
        System.out.println(s3.equals(s4)); // true
    }
}

14.1.5.3equalsIgnoreCase方法的作用

​ equalsIgnoreCase 是 Java 中的一个字符串方法,用于比较两个字符串是否相等,但忽略它们的大小写。它与 equals 方法类似,但不考虑大小写的区别。

方法介绍:

public boolean equalsIgnoreCase(String anotherString)

14.1.5案例—手机号屏蔽

说明:

​ 需求:以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽

代码实现:

public class Test8手机号屏蔽 {
    public static void main(String[] args) {
        /\*以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽
 最终效果为:131\*\*\*\*9468\*/

        //1.键盘录入一个手机号码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入手机号码");
        String phoneNumber = sc.next();//13112349408

        //2.截取手机号码中的前三位
        String star = phoneNumber.substring(0, 3);

        //3.截取手机号码中的最后四位
        //此时我用substring方法,是用1个参数的,还是两个参数的?1个参数的会更好
        //因为现在我要截取到最后,所以建议使用1个参数的。
        String end = phoneNumber.substring(7);

        //4.拼接
        String result = star + "\*\*\*\*" + end;

        System.out.println(result);

    }
}


补充:在 Java 中,substring 是字符串类中的一个方法,用于截取一个字符串的子串

这个方法有两种不同的用法:

  1. 截取从指定位置开始到字符串末尾的子串:
public String substring(int beginIndex)

其中,beginIndex 表示截取子串的起始位置。返回从 beginIndex 开始到字符串末尾的子串。

例如:

rustCopy codeString str = "Hello, world!";
String substr = str.substring(7);  // 截取 "world!"
System.out.println(substr);

输出结果为:

world!

  1. 截取从指定位置开始到指定位置结束的子串:
public String substring(int beginIndex, int endIndex)

其中,beginIndexendIndex 分别表示截取子串的起始位置和结束位置。返回从 beginIndex 开始到 endIndex - 1 结束的子串。

例如:

rustCopy codeString str = "Hello, world!";
String substr = str.substring(7, 12);  // 截取 "world"
System.out.println(substr);

输出结果为:

world

​ 需要注意的是,substring 方法返回的是一个新的字符串,而不是在原字符串上进行修改。如果 beginIndexendIndex 超出了字符串的范围,会抛出 IndexOutOfBoundsException 异常。

14.1.6案例—敏感词替换

需求1:键盘录入一个 字符串,如果字符串中包含(TMD),则使用***替换

public class Test9敏感词替换 {
    public static void main(String[] args) {
        //1.定义一个变量表示骂人的话
        String talk = "后裔你玩什么啊,TMD";


        //2.把这句话中的敏感词进行替换
        String result = talk.replace("TMD", "\*\*\*");

        //3.打印
        System.out.println(talk);
        System.out.println(result);
    }
}


需求2:如果要替换的敏感词比较多怎么办?

public class Test10多个敏感词替换 {
    public static void main(String[] args) {
        //实际开发中,敏感词会有很多很多

        //1.先键盘录入要说的话
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要说的话");
        String talk = sc.next();//后裔你玩什么啊,TMD,GDX,ctmd,ZZ

        //2.定义一个数组用来存多个敏感词
        String[] arr = {"TMD","GDX","ctmd","ZZ","lj","FW","nt"};

        //3.把说的话中所有的敏感词都替换为\*\*\*

        for (int i = 0; i < arr.length; i++) {
            //i 索引
            //arr[i] 元素 --- 敏感词
            talk = talk.replace(arr[i],"\*\*\*");
        }

        //4.打印结果
        System.out.println(talk);//后裔你玩什么啊,\*\*\*,\*\*\*,\*\*\*,\*\*\*

    }
}


补充:在 Java 中,replace 是字符串类中的一个方法,用于替换一个字符串中的某些字符或子串。****

这个方法有两种不同的用法:

  1. 用新的字符串替换掉所有的旧字符串:
 public String replace(CharSequence target, CharSequence replacement)

其中,target 表示要被替换的旧字符串,replacement 表示用于替换的新字符串。返回一个新的字符串,其中所有的 target 都被替换成了 replacement

例如:

 rustCopy codeString str = "Hello, world!";
String newStr = str.replace("o", "0");  // 替换所有的 "o" 为 "0"
System.out.println(newStr);

输出结果为:

 Hell0, w0rld!

  1. 用新的字符串替换掉某个位置开始的一段子串:
 public String replace(int startIndex, int endIndex, String newStr)

其中,startIndexendIndex 分别表示要被替换的子串的起始位置和结束位置(不包括 endIndex 所在的字符),newStr 表示用于替换的新字符串。返回一个新的字符串,其中从 startIndex 开始到 endIndex - 1 结束的子串都被替换成了 newStr

例如:

 rustCopy codeString str = "Hello, world!";
String newStr = str.replace(7, 12, "JAVA");  // 替换 "world" 为 "JAVA"
System.out.println(newStr);

输出结果为:

 Hello, JAVA!

需要注意的是,replace 方法返回的是一个新的字符串,而不是在原字符串上进行修改。如果 startIndexendIndex 超出了字符串的范围,会抛出 IndexOutOfBoundsException 异常。

14.2StringBuilder类

笔记小结:

  1. 概述:可以看成是一个容器,创建之后里面的内容是可变的
  2. 常用方法:append()、length()、reverse()、toString()
  3. 链式编程:
  • 含义:对StringBuilder对象进行方法连续操作
  • 格式:
StringBuilder sb = new StringBuilder();
sb.append("aaa").append("bbb").append("ccc").append("ddd");



14.2.1概述

​ StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的。当我们在拼接字符串和反转字符串的时候会使用到

14.2.2成员方法

image-20230303101857878

14.2.3基本用例-基本使用
public class StringBuilderDemo3 {
    public static void main(String[] args) {
        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");

        //2.添加元素
        /\*sb.append(1);
 sb.append(2.3);
 sb.append(true);\*/

        //反转
        sb.reverse();

        //获取长度
        int len = sb.length();
        System.out.println(len);


        //打印
        //普及:
        //因为StringBuilder是Java已经写好的类
        //java在底层对他做了一些特殊处理。
        //打印对象不是地址值而是属性值。
        System.out.println(sb);
    }
}

14.2.4链式编程
public class StringBuilderDemo4 {
    public static void main(String[] args) {
        //1.创建对象
        StringBuilder sb = new StringBuilder();

        //2.添加字符串
        sb.append("aaa").append("bbb").append("ccc").append("ddd");

        System.out.println(sb);//aaabbbcccddd

        //3.再把StringBuilder变回字符串
        String str = sb.toString();
        System.out.println(str);//aaabbbcccddd

    }
}

注意:

需要用tostring将它变为字符串,因为此时为StringBuilder容器而不是字符串

14.3StringJoiner类

笔记小结:

  1. 概述:可以看成是一个容器,创建之后里面的内容是可变的
  2. 构造方法:
  • StringJoiner(间隔符号)
  • StringJoiner(间隔符号,开始符号,结束符号)
  1. 常用方法:
  • add()
  • length()
  • toString()
14.3.1概述
  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的
14.3.2构造方法

image-20230303102934390

14.3.3成员方法

image-20230303103013492

14.3.4基本用例-基本使用
//1.创建一个对象,并指定中间的间隔符号
StringJoiner sj = new StringJoiner("---");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
//3.打印结果
System.out.println(sj);//aaa---bbb---ccc

//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]

14.4字符串原理(重点)

说明:

详细视频讲解:https://www.bilibili.com/video/BV17F411T7Ao/?p=107

14.4.1扩展底层原理1:字符串存储的内存原理
  • 直接赋值会复用字符串常量池中的
  • new出来不会复用,而是开辟一个新的空间
14.4.2扩展底层原理2:==号比较的到底是什么?
  • 基本数据类型比较数据值
  • 引用数据类型比较地址值
14.4.3扩展底层原理3:字符串拼接的底层原理
  • 如果没有变量参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串。
  • 如果有变量参与,每一行拼接的代码,都会在内存中创建新的字符串,浪费内存。

常量拼接

image-20230303103749967

变量拼接

image-20230303103923437

image-20230303104322048

说明:

​ 会非常的消耗堆内存,一个加号会在堆中创建两个对象

image-20230303104701222

说明:JDK8之后将字符串拼接进行了优化处理

  • 在 JDK8 中,Java 对字符串拼接进行了优化处理。在之前的版本中,字符串拼接通常会使用 + 运算符,这会创建大量的临时字符串对象,导致内存的浪费和垃圾回收的频繁发生,降低程序的性能。
  • 为了解决这个问题,JDK8 中引入了新的字符串拼接方式,即使用 StringBuilder 类或 StringJoiner 类进行拼接。这些类在内部使用可变长度的字符数组来存储字符串,可以避免创建大量的临时字符串对象,提高了程序的性能和效率。

例如,以下代码使用 StringBuilder 类进行字符串拼接:

String name = "John";
int age = 30;
String city = "New York";

String message = new StringBuilder("My name is ")
.append(name)
.append(", I am ")
.append(age)
.append(" years old, and I live in ")
.append(city)
.toString();

System.out.println(message);

输出结果为:

My name is John, I am 30 years old, and I live in New York

​ 需要注意的是,这种优化并不意味着在所有情况下都应该使用 StringBuilderStringJoiner 类进行字符串拼接。在一些简单的情况下,直接使用 + 运算符可能更加方便和易读。开发者应该根据具体情况选择合适的方式进行字符串拼接。

14.4.4扩展底层原理4:StringBuilder提高效率原理图
  • 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存

image-20230303105957054

说明:

StringBuilder是一个内容可变的容器

image-20230303105922348

14.4.5扩展底层原理5:StringBuilder源码分析
  • 默认创建一个长度为16的字节数组
  • 添加的内容长度小于16,直接存
  • 添加的内容大于16会扩容(原来的容量*2+2)
  • 如果扩容之后还不够,以实际长度为准

image-20230303110350838

说明:

StrinBuilder会创建默认为16的字节数组

image-20230303110618625

image-20230303110556080

15.内部类

笔记小结:

  1. 概述
  • 定义:定义在另一个类中的类
  • 作用:实现更好的封装性,内部的事务脱离外部的事务无法使用
  1. 分类:
  2. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
  3. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,static修饰的内部类)
  4. 局部内部类,类定义在方法内
  5. 匿名内部类没有名字的内部类,可以在方法中,也可以在类中方法外。
  6. 成员内部类:请见成员内部类小节
  7. 静态内部类:请见成员内部类小节
  8. 局部内部类:请见成员内部类小节
  9. 匿名内部类:请见成员内部类小节。只使用一次类时,可以使用匿名内部类

15.1概述

15.1.1定义

​ 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。

​ Java内部类是定义在另一个类中的类,它可以访问包含它的外部类的所有成员和方法,包括私有成员和方法。

15.1.2作用

一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用

  1. 人里面有一颗心脏。
  2. 汽车内部有一个发动机。
  3. 为了实现更好的封装性。

15.2分类

  1. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
  2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,static修饰的内部类)
  3. 局部内部类,类定义在方法内
  4. 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。

15.3成员内部类

笔记小结:

  1. 特点:无static修饰的内部类,属于外部类对象
  2. 使用:
// 前提需要new对象 
外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类

  1. 获取内部类对象方式
  • 外部直接创建成员内部类的对象
  外部类.内部类 变量 = new 外部类().new 内部类();

  • 在外部类中定义一个方法提供内部类的对象
  public class Outer {
    String name;
    private class Inner{
        static int a = 10;
    }
    public Inner getInstance(){
        return new Inner();
    }
}

  1. 细节:
  • 成员内部类可以被一些修饰符所修饰,例如: private,默认,protected等
  • 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量
  • 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值
  1. 内存图:

  2. 方法区中的字节码文件,会加载外部内字节码文件和内部类的字节码文件,它是两个独立的字节码文件

image-20230306085103657
2. 当new Inner()时会在堆内存中记录隐藏的this,而这个this是外部类的地址
3. 补充:a,就近原则、this.a,是内部类中的a、Outer.this.a,是外部类中的a

15.3.1特点
  • 无static修饰的内部类,属于外部类对象的。
  • 宿主:外部类对象。
15.3.2使用格式
 // 前提是new对象
外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类

15.3.3获取成员内部类对象

方式一:外部直接创建成员内部类的对象

/\* 格式:
 外部类.内部类 变量 = new 外部类().new 内部类();\*/
public class Test {
    public static void main(String[] args) {
        // 宿主:外部类对象。
       // Outer out = new Outer();
        // 创建内部类对象。
        Outer.Inner oi = new Outer().new Inner();
        oi.method();
    }
}

class Outer {
    // 成员内部类,属于外部类对象的。
    // 拓展:成员内部类不能定义静态成员。
    public class Inner{
        // 这里面的东西与类是完全一样的。
        public void method(){
            System.out.println("内部类中的方法被调用了");
        }
    }
}

方式二:在外部类中定义一个方法提供内部类的对象

public class Outer {
    String name;
    private class Inner{
        static int a = 10;
    }
    public Inner getInstance(){
        return new Inner();
    }
}

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.getInstance());


    }
}

说明:

  • 当成员内部类被private修饰时。在外部类编写方法,对外提供内部类对象
  • 当成员内部类被非私有修饰时,直接创建对象。Outer.Inner oi = new Outer().new Inner();
15.3.4细节

编写成员内部类的注意点:

  1. 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
  2. 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
  3. 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值。(请参见"成员内部类"的内存图)

详解:

  • ​ 内部类被private修饰,外界无法直接获取内部类的对象,只能通过“成员内部类”中的方式二获取内部类的对象
  • 被其他权限修饰符修饰的内部类一般“成员内部类”中的方式一直接获取内部类的对象
  • ​ 内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。
  • ​ 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。
15.3.5内存图(重点)

image-20230811093523242

说明:

  • 方法区中的字节码文件,会加载外部内字节码文件和内部类的字节码文件,它是两个独立的字节码文件

image-20230306085103657

  • 当new Inner()时会在堆内存中记录隐藏的this,而这个this是外部类的地址
  • 补充:a,就近原则、this.a,是内部类中的a、Outer.this.a,是外部类中的a
15.3.6案例–面试题

注意:

​ 内部类访问外部类对象的格式是:外部类名.this

public class Test {
    public static void main(String[] args) {
        Outer.inner oi = new Outer().new inner();
        oi.method();
    }
}

class Outer {	// 外部类
    private int a = 30;

    // 在成员位置定义一个类
    class inner {
        private int a = 20;

        public void method() {
            int a = 10;
            System.out.println(???);	// 10 答案:a
            System.out.println(???);	// 20 答案:this.a
            System.out.println(???);	// 30 答案:Outer.this.a
        }
    }
}

说明:

​ 外部类成员变量和内部类成员变量重名时,在内部类如何访问?使用System.out.println(outer.this.变量名);

15.4静态内部类

笔记小结:

  1. 特点:有static修饰,属于外部类本身的
  2. 使用:
外部类名.内部类名.方法名();  

  1. 获取内部类对象方式
外部类.内部类  变量 = new  外部类.内部类构造器;

15.4.1特点
  • 静态内部类是一种特殊的成员内部类。
  • 有static修饰,属于外部类本身的。

总结:

​ 静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类

补充:

  • 静态内部类可以直接访问外部类的静态成员
  • 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
  • 静态内部类中没有隐含的Outer.this
15.4.2使用格式
外部类名.内部类名.方法名();  

15.4.3获取成员内部类对象
/\* 格式:
 外部类.内部类 变量 = new 外部类.内部类构造器; \*/
// 外部类:Outer01
class Outer01{
    private static  String sc_name = "黑马程序";
    // 内部类: Inner01
    public static class Inner01{
        // 这里面的东西与类是完全一样的。
        private String name;
        public Inner01(String name) {
            this.name = name;
        }
        public void showName(){
            System.out.println(this.name);
            // 拓展:静态内部类可以直接访问外部类的静态成员。
            System.out.println(sc_name);
        }
    }
}

public class InnerClassDemo01 {
    public static void main(String[] args) {
        // 创建静态内部类对象。
        // 外部类.内部类 变量 = new 外部类.内部类构造器;
        Outer01.Inner01 in  = new Outer01.Inner01("张三");
        in.showName();
    }
}

说明:

​ 注意区分成员内部类的创建方式 Outer.inner oi = new Outer().new inner();

15.5局部内部类

笔记小结:

  1. 特点:定义在方法中的类
  2. 使用格式
 class 外部类名 {
	数据类型 变量名;

	修饰符 返回值类型 方法名(参数列表) {
    		// …
		class 内部类 {
			// 成员变量
			// 成员方法
		}
	}
}

15.5.1特点

局部内部类 :定义在方法中的类

15.5.2使用格式
class 外部类名 {
	数据类型 变量名;
	
	修饰符 返回值类型 方法名(参数列表) {
		// …
		class 内部类 {
			// 成员变量
			// 成员方法
		}
	}
}

15.6匿名内部类(重点)

笔记小结:

  1. 特点:
  • 定义一个没有名字的内部类
  • 这个类实现了父类,或者父类接口
  • 匿名内部类会创建这个没有名字的类的对象
  1. 使用格式:new 接口 或者 new 父类
new 父类名或者接口名(){
  // 方法重写
  @Override 
  public void method() {
      // 执行语句
  }
};

  1. 使用方式
  2. new 父类或方法后 直接.调用
  new Swim() {
    @Override
    public void swimming() {
        System.out.println("自由泳...");
    }
}.swimming();

  1. new 父类或方法后 直接赋值
  // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
    @Override
    public void swimming() {
        System.out.println("蛙泳...");
    }
};
s2.swimming();

  1. 意义:定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码

匿名内部类 :是内部类的简化写法。他是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。

15.6.1特点
  • 定义一个没有名字的内部类
  • 这个类实现了父类,或者父类接口
  • 匿名内部类会创建这个没有名字的类的对象
15.6.2使用格式

前提:

​ 匿名内部类必须继承一个父类或者实现一个父接口

new 父类名或者接口名(){
    // 方法重写
    @Override 
    public void method() {
        // 执行语句
    }
};

注意:

使用前提,要么继承父类,要么实现接口

15.6.3获取匿名内部类

概述:

1.直接new接口

new Swim() {
    @Override
    public void swimming() {
        System.out.println("自由泳...");
    }
}.swimming();

2.重写子类方法

// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
    @Override
    public void swimming() {
        System.out.println("蛙泳...");
    }
};

基本用例:

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 使用匿名内部类
		new Swim() {
			@Override
			public void swimming() {
				System.out.println("自由泳...");
			}
		}.swimming();

        // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
        Swim s2 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蛙泳...");
            }
        };

        s2.swimming();
        s2.swimming();
    }
}

15.6.4应用场景

之前我们使用接口时,似乎得做如下几步操作:

  1. 定义子类
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法
interface Swim {
    public abstract void swimming();
}

// 1. 定义接口的实现类
class Student implements Swim {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
}

public class Test {
    public static void main(String[] args) {
        // 3. 创建实现类对象
        Student s = new Student();
        // 4. 调用方法
        s.swimming();
    }
}

现在:通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 普通方式传入对象
        // 创建实现类对象
        Student s = new Student();
        
        goSwimming(s);
        // 匿名内部类使用场景:作为方法参数传递
        Swim s3 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蝶泳...");
            }
        };
        // 传入匿名内部类
        goSwimming(s3);

        // 完美方案: 一步到位
        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("大学生, 蛙泳...");
            }
        });

        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("小学生, 自由泳...");
            }
        });
    }

    // 定义一个方法,模拟请一些人去游泳
    public static void goSwimming(Swim s) {
        s.swimming();
    }
}

15.6.5意义

​ 实际上,如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。

16.包装类

笔记小结:

最后

文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值