7.面向对象进阶(1)

文章目录

  • 一、复习
    • 1.1 如何定义类
    • 1.2 如何通过类创建对象
    • 1.3 封装
        • 1.3.1 封装的步骤
        • 1.3.2 封装的步骤实现
    • 1.4 构造方法
      • 1.4.1 构造方法的作用
      • 1.4.2 构造方法的格式
      • 1.4.3 构造方法的应用
    • 1.5 this关键字的作用
      • 1.5.1 this关键字的作用
      • 1.5.2 this关键字的应用
        • 1.5.2.1 用于普通的gettter与setter方法
        • 1.5.2.2 用于构造方法中
  • 第二章 static关键字
    • 2.1 概述
    • 2.2 定义格式和使用
    • 2.3 注意事项和小结
    • 2.4 工具类
  • 第三章 继承
    • 3.1 概述
    • 3.2 继承的特点
    • 3.3 继承的格式
    • 3.4 继承后的特点
      • 3.4.1 成员变量
      • 3.4.2 成员方法与方法重写
        • @Override重写注解
      • 3.4.3 构造方法
    • 3.5 super(...)和this(...)
    • 3.6 继承的特点
    • 3. 7案例小结:
  • 第四章 多态
    • 4.1 多态的形式
    • 4.2 多态的运行
      • 4.2.1 多态的运行特点
      • 4.2.2 多态的使用场景
      • 4.2.3 引用类型转换:
        • instanceof关键字
        • instanceof新特性
        • 代码
    • 4.3 小结
    • 4.4 案例
  • 第五章 抽象类
    • 5.1 abstract使用格式
    • 5.2 抽象类的细节
    • 5.3 案例
  • 第六章 接口
    • 6.1 接口的定义和使用
    • 6.2 定义格式
    • 6.3 接口成员的特点
    • 6.4 接口与类之间的关系
    • 6.5 案例:练习
    • 6.6 JDK8开始接口中新增的方法
      • 6.6.1 新增的方法
        • 案例一:接口的方法不重写
        • 案例二:接口的方法重写
      • 6.6.2 新增的静态方法
      • 6.6.3 JDK9以后新增加的方法
    • 6.7 接口的应用
    • 6.8适配器设计模式
  • 第七章 内部类
    • 7.1 概述
    • 7.2 内部类的分类
    • 7.3 成员内部类
    • 7.4 成员内部类面试题
    • 7.5 静态内部类
    • 7.6 局部内部类
    • 7.7 匿名内部类
      • 案例
      • 案例2

一、复习

1.1 如何定义类

类的定义格式如下:

修饰符 class 类名 {
    // 1.成员变量(属性)
    // 2.成员方法 (行为) 
    // 3.构造方法 (初始化类的对象数据的)
}

例如:

public class Student {
    // 1.成员变量
    public String name ;
    public char sex ; // '男'  '女'
    public int age;
}

1.2 如何通过类创建对象

类名 对象名称 = new 类名();

例如:

Student stu = new Student();

1.3 封装

1.3.1 封装的步骤

1.使用 private 关键字来修饰成员变量。

2.使用public修饰getter和setter方法。

1.3.2 封装的步骤实现

  1. private修饰成员变量
public class Student {
    private String name;
    private int age;
}
  1. public修饰getter和setter方法
public class Student {
    private String name;
    private int age;

    public void setName(String n) {
      	name = n;
    }

    public String getName() {
      	return name;
    }

    public void setAge(int a) {
        if (a > 0 && a <200) {
            age = a;
        } else {
            System.out.println("年龄非法!");
        }
    }

    public int getAge() {
      	return age;
    }
}

1.4 构造方法

1.4.1 构造方法的作用

在创建对象的时候,给成员变量进行初始化。

初始化即赋值的意思。

1.4.2 构造方法的格式

修饰符 类名(形参列表) {
    // 构造体代码,执行代码
}

1.4.3 构造方法的应用

首先定义一个学生类,代码如下:

public class Student {
    // 1.成员变量
    public String name;
    public int age;

    // 2.构造方法
    public Student() {
		System.out.println("无参数构造方法被调用")}
}

接下来通过调用构造方法得到两个学生对象。

public class CreateStu02 {
    public static void main(String[] args) {
        // 创建一个学生对象
        // 类名 变量名称 = new 类名();
        Student s1 = new Student();
        // 使用对象访问成员变量,赋值
        s1.name = "张三";
        s1.age = 20 ;

        // 使用对象访问成员变量 输出值
        System.out.println(s1.name);
        System.out.println(s1.age); 

        Student s2 = new Student();
        // 使用对象访问成员变量 赋值
        s2.name = "李四";
        s2.age = 18 ;
        System.out.println(s2.name);
        System.out.println(s2.age);
    }
}

1.5 this关键字的作用

1.5.1 this关键字的作用

this代表所在类的当前对象的引用(地址值),即代表当前对象。

1.5.2 this关键字的应用

1.5.2.1 用于普通的gettter与setter方法

this出现在实例方法中,谁调用这个方法(哪个对象调用这个方法),this就代表谁(this就代表哪个对象)。

public class Student {
    private String name;
    private int age;

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

    public String getName() {
      	return name;
    }

    public void setAge(int age) {
        if (age > 0 && age < 200) {
            this.age = age;
        } else {
            System.out.println("年龄非法!");
        }
    }

    public int getAge() {
      	return age;
    }
}

1.5.2.2 用于构造方法中

this出现在构造方法中,代表构造方法正在初始化的那个对象。

public class Student {
    private String name;
    private int age;
    
    // 无参数构造方法
    public Student() {} 
    
    // 有参数构造方法
    public Student(String name,int age) {
    	this.name = name;
    	this.age = age; 
    }
}

第二章 static关键字

2.1 概述

Java中成员(变量和方法)等是存在所属性的,Java是通过static关键字来区分的。**static关键字在Java开发非常的重要,对于理解面向对象非常关键。

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。我们上面案例中的成员变量都是没有static修饰的,所以属于每个对象。

7.面向对象进阶(1)_第1张图片

2.2 定义格式和使用

static是静态的意思。 static可以修饰成员变量或者修饰方法。

定义格式

修饰符 static 数据类型 变量名 = 初始值;    

举例

public class Student {
    public static String schoolName = "传智播客"// 属于类,只有一份。
    // .....
     public static void study(){
    	System.out.println("我们都在黑马程序员学习");   
    }    
}

静态成员变量的访问:

格式:类名.静态变量

格式:类名.静态方法

public static void  main(String[] args){
    System.out.println(Student.schoolName); // 传智播客
    Student.schoolName = "黑马程序员";
    System.out.println(Student.schoolName); // 黑马程序员
    Student.study();
}

无static修饰的成员方法属于每个对象的,这个成员方法也叫做实例方法

需要注意的是:实例方法是属于每个对象,必须创建类的对象才可以访问。

格式:对象.实例方法

示例

public class Student {
    // 实例变量
    private String name ;
    // 2.方法:行为
    // 无 static修饰,实例方法。属于每个对象,必须创建对象调用
    public void run(){
        System.out.println("学生可以跑步");
    }
	// 无 static修饰,实例方法
    public  void sleep(){
        System.out.println("学生睡觉");
    }
    public static void study(){
        
    }
}
public static void main(String[] args){
    // 创建对象 
    Student stu = new Student ;
    stu.name = "徐干";
    // Student.sleep();// 报错,必须用对象访问。
    stu.sleep();
    stu.run();
}

2.3 注意事项和小结

static的注意事项

  • 静态方法只能访问静态变量和静态方法
  • 非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
  • 静态方法中是没有this关键字
  • 总结:
    • 静态方法中,只能访问静态。
    • 非静态方法可以访问所有。
    • 静态方法中没有this关键字

小结:

1.当 static 修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法。但是不推荐这种方式去访问。因为静态变量或者静态方法直接通过类名访问即可,完全没有必要用对象去访问。

2.无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。

3.static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。

4.无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。

2.4 工具类

不是用来描述一类事物的,而是帮我们做一些事情的类。

  • 类名见名知意
  • 私有化构造方法
  • 方法定义为静态

工具类:

public class ArrayUtil {

    //私有化构造方法
    //目的:为了不让外界创建他的对象
    private ArrayUtil() {
    }

    //需要定义为静态的,方便调用
    public static String printArr(int[] arr) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < arr.length; i++) {
            //i 索引 arr[i] 元素
            if (i == arr.length - 1) {
                sb.append(arr[i]);
            } else {
                sb.append(arr[i]).append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }


    public static double getAverage(double[] arr) {
        double sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum = sum + arr[i];
        }
        return sum / arr.length;
    }

}

测试类:

public class TestDemo {
    public static void main(String[] args) {
        //测试工具类中的两个方法是否正确

        int[] arr1 = {1, 2, 3, 4, 5};
        //直接就是:类名.f方法
        String str = ArrayUtil.printArr(arr1);
        System.out.println(str);

        double[] arr2 = {1.5, 3.7, 4.9, 5.8, 6.6};
        double avg = ArrayUtil.getAverage(arr2);
        System.out.println(avg);
    }
}

第三章 继承

3.1 概述

1.什么是继承、继承的好处?

继承是面向对象三大特征之一,可以让类跟类之间产生子父的关系。

继承**:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

可以把多个子类中重复的代码抽取到父类中,子类可以直接使用,减少代码冗余,提高代码的复用性

提高代码的复用性(减少代码冗余,相同代码重复利用)。使类与类之间产生了关系。

2.继承的格式?

public class 子类 extends 父类{}

3.继承后子类的特点?

子类可以得到父类的属性和行为,子类可以使用。

子类可以在父类的基础上新增其他功能,子类更强大。继承的好处

7.面向对象进阶(1)_第2张图片

3.2 继承的特点

  • **JAVA只能单继承:**一个类只能继承一个直接父类。
  • JAVA不支持多继承、但是支持多层继承
  • JAVA中所有的类都直接或者间接继承于对象类。
  • Java中所有的类都直接或者间接继承于Object类
  • 子类只能访问父类中非私有的成员

7.面向对象进阶(1)_第3张图片

子类不能继承的内容

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

3.3 继承的格式

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

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}

需要注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。

3.4 继承后的特点

3.4.1 成员变量

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

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

super访问父类成员变量

1.继承中成员变量访问特点:就近原则
先在局部位置找,本类成员位置找,父类成员位置找,逐级往上。

2.如果出现了重名的成员变量怎么办?

system.out.println(name); //从局部位置开始往上找
system.out.println(this.name) ; //从本类成员位置开始往上找
system.out.println(super.name); //从父类成员位置开始往上找
public class Test {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}

class Fu {
    String name = "Fu";
    String hobby = "喝茶";

}

class Zi extends Fu {
    String name = "Zi";
    String game = "吃鸡";

    public void show() {
        //如何打印Zi
        //System.out.println(name);//Zi
        //System.out.println(this.name);//Zi
        //如何打印Fu
        //System.out.println(super.name);//Fu
        //如何打印喝茶
        //System.out.println(hobby);//喝茶
        //System.out.println(this.hobby);//喝茶
        //System.out.println(super.hobby);//喝茶
        //如何打印吃鸡
        System.out.println(game);
        System.out.println(this.game);
    }
}

3.4.2 成员方法与方法重写

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

方法重写注意事项和要求

  • 1.重写方法的名称、形参列表必须与父类中的一致。
  • 2.子类重写父类方法时,访问权限子类必须大于等于父类(暂时了解︰空着不写< protected
  • 3.子类重写父类方法时,返回值类型子类必须小于等于父类
  • 4.建议:重写的方法尽量和父类保持一致。
  • 5.只有被添加到虚方法表中的方法才能被重写

7.面向对象进阶(1)_第4张图片
7.面向对象进阶(1)_第5张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0qgK2CFe-1665577746271)(E:\java\Java基础\图片\7\5.png)]

public class Test {
    public static void main(String[] args) {
        OverseasStudent s = new OverseasStudent();
        s.lunch();
    }
}

class Person {
    public  void eat() {
        System.out.println("吃米饭,吃菜");
    }

    public void drink() {
        System.out.println("喝开水");
    }
}

//留学生
class OverseasStudent extends Person{
    public void lunch(){
        this.eat();//吃意大利面
        this.drink();//喝凉水

        super.eat();//吃米饭,吃菜
        super.drink();//喝开水
    }

    //应用场景:当父类中方法,不能满足子类现在的需求时,我们就需要把这个方法进行重写。
    //注意:子类中重写的方法上面需要加上@override
    @Override
    public  void eat() {
        System.out.println("吃意大利面");
    }

    @Override
    public void drink() {
        System.out.println("喝凉水");
    }

}




class Student extends Person{

    public void lunch(){
        //先在本类中查看eat和drink方法,就会调用子类的,如果没有,就会调用从父类中继承下来的eat和drink方法
        this.eat();
        this.drink();

        //直接调用父类中的eat和drink方法
        super.eat();
        super.drink();
    }

}

@Override重写注解

  • @Override:注解,重写注解校验!
  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
  • 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

3.4.3 构造方法

继承中:构造方法的访问特点

  • 父类中的构造方法不会被子类继承。
  • 子类中所有的构造方法默认先访问父类中的无参构造,再执行自己。

为什么?

  • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
  • 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化。

怎么调用父类构造方法的?

  • 子类构造方法的第一行语句默认都是:super(),不写也存在,且必须在第一行。
  • 如果想调用父类有参构造,必须手动写super进行调用。

7.面向对象进阶(1)_第6张图片

public class Person {
    String name;
    int age;

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

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

public class Student extends Person{

    public Student(){
        //子类构造方法中隐藏的super()去访问父类的无参构造
        super();
        System.out.println("子类的无参构造");
    }


    public Student(String name,int age){
       super(name,age);
    }
}

public class Test {
    public static void main(String[] args) {
        //创建学生对象
        //有参:经过有参构造方法,就不经过
        Student s = new Student("zhangsan",23);
        
        //无参:经过无参构造方法
        Student s = new Student();
        //输出: 父类的无参构造  子类的无参构造

    }
}

代码如下:

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

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

3.5 super(…)和this(…)

super和this的用法格式

super和this完整的用法如下,其中this,super访问成员我们已经接触过了。

this.成员变量    	--    本类的
super.成员变量    	--    父类的

this.成员方法名()  	--    本类的    
super.成员方法名()   --    父类的

接下来我们使用调用构造方法格式:

super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认

7.面向对象进阶(1)_第7张图片

小结

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
  • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
  • super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。
  • super(…)可以调用父类构造方法初始化继承自父类的成员变量的数据。
  • this(…)可以调用本类中的其他构造方法。
public class Student {
    String name;
    int age;
    String school;

    //需求:
    //默认为传智大学

    public Student() {
        //表示调用本类其他构造方法
        //细节:虚拟机就不会再添加super();
        this(null,0,"传智大学");
    }

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

public class Test {
    public static void main(String[] args) {
        Student s = new Student();
    }
}

3.6 继承的特点

  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,作为父类。

3. 7案例小结:

会写一个继承结构下的标准Javabean即可

需求:

​ 猫:属性,姓名,年龄,颜色

​ 狗:属性,姓名,年龄,颜色,吼叫

分享书写技巧:

​ 1.在大脑中要区分谁是父,谁是子

​ 2.把共性写到父类中,独有的东西写在子类中

​ 3.开始编写标准Javabean(从上往下写)

​ 4.在测试类中,创建对象并赋值调用

代码示例:

package com.itheima.test4;

public class Animal {
    //姓名,年龄,颜色
    private String name;
    private int age;
    private String color;


    public Animal() {
    }

    public Animal(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
    //get和set方法
}


public class Cat extends Animal{
    //因为猫类中没有独有的属性。
    //所以此时不需要写私有的成员变量

    //空参
    public Cat() {
    }

    //需要带子类和父类中所有的属性
    public Cat(String name, int age, String color) {
        super(name,age,color);
    }
}


public class Dog extends Animal{
    //Dog :吼叫
    private String wang;

    //构造
    public Dog() {
    }

    //带参构造:带子类加父类所有的属性
    public Dog(String name, int age, String color,String wang) {
        //共性的属性交给父类赋值
        super(name,age,color);
        //独有的属性自己赋值
        this.wang = wang;
    }

    public String getWang() {
        return wang;
    }

    public void setWang(String wang) {
        this.wang = wang;
    }
}

public class Demo {
    public static void main(String[] args) {
        //Animal : 姓名,年龄,颜色
        //Cat :
        //Dog :吼叫

        //创建狗的对象
        Dog d = new Dog("旺财",2,"黑色","嗷呜~~");
        System.out.println(d.getName()+", " + d.getAge() + ", " + d.getColor() + ", " + d.getWang());

        //创建猫的对象
        Cat c = new Cat("中华田园猫",3,"黄色");
        System.out.println(c.getName() + ", " + c.getAge() + ", " + c.getColor());
    }
}


第四章 多态

4.1 多态的形式

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

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

多态体现的格式

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

多态的前提

  • 有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。
  • 有继承/实现关系
  • 有父类引用指向子类对象
  • 有方法的重写

多态的好处:

  • 使用父类型作为参数,可以接受所有子类对象
  • 体现多态的扩展性与便利

7.面向对象进阶(1)_第8张图片

4.2 多态的运行

4.2.1 多态的运行特点

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

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

代码示例:

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

4.2.2 多态的使用场景

父类:
public class Person {
    private String name;
    private int age;

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

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

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

子类2public class Student extends Person{

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

子类3public class Teacher extends Person{

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

测试类:
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();
    }
}

4.2.3 引用类型转换:

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

使用格式:

子类类型 变量名 = (子类类型) 父类变量名;:Aniaml a = new Cat();
   Cat c =(Cat) a;  

instanceof关键字

为了避免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
        }
    }  
}

instanceof新特性

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("没有这个类型,无法转换");
}

代码

public class Test {
    public static void main(String[] args) {
        //创建对象
        Animal a = new Dog();
        //编译看左边,运行看右边
        a.eat();

        //多态的弊端
        //不能调用子类的特有功能
        //报错的原因?
        //当调用成员方法的时候,编译看左边,运行看右边
        //那么在编译的时候会先检查左边的父类中有没有这个方法,如果没有直接报错。
        //a.lookHome();


        //解决方案:
        //变回子类类型就可以了
        //细节:转换的时候不能瞎转,如果转成其他类的类型,就会报错
        //Cat c = (Cat) a;
        //c.catchMouse();

        if(a instanceof Dog){
            Dog d = (Dog) a;
            d.lookHome();
        }else if(a instanceof Cat){
            Cat c = (Cat) a;
            c.catchMouse();
        }else{
            System.out.println("没有这个类型,无法转换");
        }

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

    }
}

class Animal{
    public void eat(){
        System.out.println("动物在吃东西");
    }
}

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

    public void lookHome(){
        System.out.println("狗看家");
    }
}

class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃小鱼干");
    }

    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }
}

4.3 小结

1.多态的优势

方法中,使用父类型作为参数,可以接收所有子类对象

2.多态的弊端是什么?
不能使用子类的特有功能

3.引用数据类型的类型转换,有几种方式?
自动类型转换、强制类型转换

Person p = new Student ();
student s = (student)p;

4.强制类型转换能解决什么问题?

  • 可以转换成真正的子类类型,从而调用子类独有功能。
  • 转换类型与真实对象类型不一致会报错
  • 转换的时候用instanceof关键字进行判断

4.4 案例

需求:根据需求完成代码:
	1.定义狗类
		属性:
			年龄,颜色
		行为:
			eat(String something)(something表示吃的东西)
			看家lookHome方法(无参数)
2.定义猫类
	属性:
		年龄,颜色
	行为:
		eat(String something)方法(something表示吃的东西)
		逮老鼠catchMouse方法(无参数)
3.定义Person//饲养员
	属性:
		姓名,年龄
	行为:
		keepPet(Dog dog,String something)方法
			功能:喂养宠物狗,something表示喂养的东西
	行为:
		keepPet(Cat cat,String something)方法
			功能:喂养宠物猫,something表示喂养的东西
	生成空参有参构造,set和get方法  
4.定义测试类(完成以下打印效果):
	keepPet(Dog dog,String somethind)方法打印内容如下:
		年龄为30岁的老王养了一只黑颜色的2岁的狗
		2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
	keepPet(Cat cat,String somethind)方法打印内容如下:
		年龄为25岁的老李养了一只灰颜色的3岁的猫
		3岁的灰颜色的猫眯着眼睛侧着头吃鱼
5.思考:		
	1.Dog和Cat都是Animal的子类,以上案例中针对不同的动物,定义了不同的keepPet方法,过于繁琐,能否简化,并体会简化后的好处?
	2.Dog和Cat虽然都是Animal的子类,但是都有其特有方法,能否想办法在keepPet中调用特有方法?
//动物类(父类)
public class Animal {
    private int age;
    private String color;


    public Animal() {
    }

    public Animal(int age, String color) {
        this.age = age;
        this.color = color;
    }

    public int getAge() {
        return age;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void eat(String something){
        System.out.println("动物在吃" + something);
    }
}

//猫类(子类)
public class Cat extends Animal {

    public Cat() {
    }

    public Cat(int age, String color) {
        super(age, color);
    }

    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);
    }

    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }

}

//狗类(子类)
public class Dog extends Animal {
    public Dog() {
    }

    public Dog(int age, String color) {
        super(age, color);
    }

    //行为
    //eat(String something)(something表示吃的东西)
    //看家lookHome方法(无参数)
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
    }

    public void lookHome(){
        System.out.println("狗在看家");
    }
}


//饲养员类
public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    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 void keepPet(Dog dog, String something) {
        System.out.println("年龄为" + age + "岁的" + name + "养了一只" + dog.getColor() + "颜色的" + dog.getAge() + "岁的狗");
        dog.eat(something);
    }

    //饲养猫
    public void keepPet(Cat cat, String something) {
        System.out.println("年龄为" + age + "岁的" + name + "养了一只" + cat.getColor() + "颜色的" + cat.getAge() + "岁的猫");
        cat.eat(something);
    }*/


    //想要一个方法,能接收所有的动物,包括猫,包括狗
    //方法的形参:可以写这些类的父类 Animal
    public void keepPet(Animal a, String something) {
        if(a instanceof Dog d){
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "岁的狗");
            d.eat(something);
        }else if(a instanceof Cat c){
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");
            c.eat(something);
        }else{
            System.out.println("没有这种动物");
        }
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        //创建对象并调用方法
       /* Person p1 = new Person("老王",30);
        Dog d = new Dog(2,"黑");
        p1.keepPet(d,"骨头");


        Person p2 = new Person("老李",25);
        Cat c = new Cat(3,"灰");
        p2.keepPet(c,"鱼");*/


        //创建饲养员的对象
        Person p = new Person("老王",30);
        Dog d = new Dog(2,"黑");
        Cat c = new Cat(3,"灰");
        p.keepPet(d,"骨头");
        p.keepPet(c,"鱼");

    }
}

第五章 抽象类

1.抽象类的作用是什么样的?
抽取共性时,无法确定方法体,就把方法定义为抽象的。强制让子类按照某种格式重写。
抽象方法所在的类,必须是抽象类。

2.继承抽象类有哪些要注意?

  • 要么重写抽象类中的所有抽象方法
  • 要么是抽象类

3. 抽象类和抽象方法的注意事项

  • 抽象类不能实例化
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类可以有构造方法
  • 抽象类的子类
    • 要么重写抽象类中的所有抽象方法
    • 要么是抽象类

5.1 abstract使用格式

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

抽象方法

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

定义格式:

修饰符 abstract 返回值类型 方法名 (参数列表)//代码举例:
public abstract void run()

抽象类

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

定义格式:

abstract class 类名字 { 
  
}
//代码举例:
public abstract class Animal {
    public abstract void run()}

抽象类的使用

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

7.面向对象进阶(1)_第9张图片

5.2 抽象类的细节

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

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

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

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

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

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

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

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

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

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

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

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

5.3 案例

public abstract class Person {

    private String name;
    private int age;

    //作用:当创建子类对象的时,给属性进行赋值的。
    public Person() {
    }

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

	//get和set方法
    
    //抽象方法
    public abstract void work();

    public void sleep(){
        System.out.println("睡觉");
    }

}

public abstract class Teacher extends Person{
}


public class Student extends Person{

	//构造方法
    public Student() {
    }

    public Student(String name, int age) {
        super(name, age);  //赋值父类
    }

    @Override
    public void work() {
        System.out.println("学生的工作是学习");
    }
}

public class ATeacher extends Teacher{
    @Override
    public void work() {

    }
}

public class Test {
    public static void main(String[] args) {
        //创建对象
        // 创建抽象类,抽象类不能创建对象
        //Person p = new Person();


        Student s = new Student("zhangsan",23);

        System.out.println(s.getName() + ", " + s.getAge());
    }
}

第六章 接口

6.1 接口的定义和使用

  • 接口用关键字interface来定义

    public interface 接口名{}
    
  • 接口不能实例化(创建对象)

  • 接口和类之间是实现关系,通过implements关键字表示

public class 类名 implements 接口名{}
  • 接口的子类(实现类)
    • 要么重写接口中的所有抽象方法
    • 要么是抽象类

**注意1:**接口和类的实现关系,可以单实现,也可以多实现。

public class 类名 implements 接口名1,接口名2{}

**注意2:**实现类还可以在继承一个类的同时实现多个接口。

public class 类名 extends 父类 implements 接口名1,接口名2{}

6.2 定义格式

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

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

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

}

6.3 接口成员的特点

7.面向对象进阶(1)_第10张图片

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 = "黑马程序员";

}

6.4 接口与类之间的关系

  • 类和类的关系
    继承关系,只能单继承,不能多继承,但是可以多层继承

  • 类和接口的关系

    实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

  • 接口和接口的关系
    继承关系,可以单继承,也可以多继承

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

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

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

6.5 案例:练习

练习编写带有接口和抽象类的标准Javabean类
我们现在有乒乓球运动员和篮球运动员,乒乓球教练和篮球教练。为了出国交流,跟乒乓球相关的人员都需要学习英语。
请用所有知识分析,在这个案例中,哪些是具体类,哪些是抽象类,哪些是接口?

​ 乒乓球运动员:姓名,年龄,学打乒乓球,说英语

​ 篮球运动员:姓名,年龄,学打篮球

​ 乒乓球教练:姓名,年龄,教打乒乓球,说英语

​ 篮球教练:姓名,年龄,教打篮球

方法一:

7.面向对象进阶(1)_第11张图片

方法二:我们采用的

7.面向对象进阶(1)_第12张图片

代码复现:

父类:

//因为现在我不想让外界去直接创建人的对象
//因为直接创建顶层父类人的对象此时是没有意义的
//所以我就把他写为抽象的。
public abstract class Person {
    private String name;
    private int age;

    public Person() {
    }

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

   //set和get方法
}

第二层

接口:

public interface English {
    
    public abstract void speakEnglish();
    //如果implement这个接口后,接口中的方法必须全部重载,要不然或报错
    //public void speak1();  //=public abstract void speak1();
}

javabean类

//运动员类
public abstract class Sporter extends Person{

    public Sporter() {
    }

    public Sporter(String name, int age) {
        super(name, age);
    }

    public abstract void study();
}

//教练类
public abstract class Coach extends Person{

    public Coach() {
    }

    public Coach(String name, int age) {
        super(name, age);
    }

    public abstract void teach();
}


//乒乓球运动员
public class PingPangSporter extends Sporter implements English{

    public PingPangSporter() {
    }

    public PingPangSporter(String name, int age) {
        super(name, age);
    }

    @Override
    public void speakEnglish() {
        System.out.println("乒乓球运动员在说英语");
    }

    @Override
    public void study() {
        System.out.println("乒乓球运动员在学习如何打乒乓球");
    }
}

//乒乓球教练
public class PingPangCoach extends Coach implements English{

    public PingPangCoach() {
    }

    public PingPangCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void teach() {
        System.out.println("乒乓球教练正在教如何打乒乓球");
    }

    @Override
    public void speakEnglish() {
        System.out.println("乒乓球教练在学习说英语");
    }
}

//
public class BasketballCoach extends Coach{

    public BasketballCoach() {
    }

    public BasketballCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void teach() {
        System.out.println("篮球教练正在教如何打篮球");
    }
}


//
public class BasketballSporter extends Sporter{

    public BasketballSporter() {
    }

    public BasketballSporter(String name, int age) {
        super(name, age);
    }

    @Override
    public void study() {
        System.out.println("篮球运动员在学习如何打篮球");
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        //创建运动员或教练的对象

        PingPangSporter pps = new PingPangSporter("刘诗雯",23);
        System.out.println(pps.getName() + ", " + pps.getAge());
        pps.study();
        pps.speakEnglish();

    }
}

6.6 JDK8开始接口中新增的方法

  1. JDK7以前:接口中只能定义抽象方法。
  2. JDK8:接口中可以定义有方法体的方法。(默认、静态)
  3. JDK9:接口中可以定义私有方法。
  4. 私有方法分为两种:普通的私有方法,静态的私有方法

6.6.1 新增的方法

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

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

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

格式: public default 返回值类型 方法名(参数列表){}

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

案例一:接口的方法不重写

//接口A
public interface InterA {
    public abstract void method();

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

//javabean类
public class InterImpl implements InterA{
    @Override
    public void method() {
        System.out.println("实现类重写的抽象方法");
    }
}

//实现类
public class Test {
    public static void main(String[] args) {
        //创建实现类的对象
        InterImpl ii = new InterImpl();
        ii.method();
        ii.show();
    }
}

//输出
实现类重写的抽象方法
InterA接口中的默认方法 ---- show

案例二:接口的方法重写

//接口A
public interface InterA {
    public abstract void method();

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

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

//javabean类
public class InterImpl implements InterA{
    @Override
    public void method() {
        System.out.println("实现类重写的抽象方法");
    }

    //重写的时候去掉default关键字
    @Override
    public void show() {
        System.out.println("重写接口中的默认方法");
    }
}

//实现:测试
public class Test {
    public static void main(String[] args) {
        /*接口中默认方法的定义格式:
            格式:public default 返回值类型 方法名(参数列表) {   }

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

            */


        //创建实现类的对象
        InterImpl ii = new InterImpl();
        ii.method();
        ii.show();
    }
}

//输出
实现类重写的抽象方法
重写接口中的默认方法

6.6.2 新增的静态方法

  • 允许在接口中定义静态方法,需要使用关键字static修饰

  • 接口中静态方法的定义格式:

格式: public static 返回值类型 方法名(参数列表){}

范例: public static void show(){ }
  • 接口中静态方法的注意事项:
    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略

案例

//接口
public interface Inter {

    public abstract void method();

    public static void show(){
        System.out.println("Inter接口中的静态方法");
    }
}

//javabean类
public class InterImpl implements Inter{
    @Override
    public void method() {
        System.out.println("InterImpl重写的抽象方法");
    }

    //不叫重写
    public static void show() {
        System.out.println("InterImpl重写的抽象方法");
    }

}

public class Test {
    public static void main(String[] args) {
        //调用接口中的静态方法
        Inter.show();

        //调用实现类中的静态方法
        InterImpl.show();

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

//输出
Inter接口中的静态方法
InterImpl重写的抽象方法

6.6.3 JDK9以后新增加的方法

public class Test {
    public static void main(String[] args) {
      /*  接口中私有方法的定义格式:

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

        格式2:private static 返回值类型 方法名(参数列表) {   }
        范例2:private static void method() {   }
*/
    }
}
public interface InterA {

    public static void show1(){
        System.out.println("show1方法开始执行了");
        show4();
    }


    public static void show2(){
        System.out.println("show2方法开始执行了");
        show4();
    }


   //普通的私有方法,给默认方法服务的
    private void show3(){
        System.out.println("记录程序在运行过程中的各种细节,这里有100行代码");
    }


    //静态的私有方法,给静态方法服务的
    private static void show4(){
        System.out.println("记录程序在运行过程中的各种细节,这里有100行代码");
    }


}

6.7 接口的应用

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

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

6.8适配器设计模式

  • 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
  • 简单理解:设计模式就是各种套路。
  • 适配器设计模式:解决接口与接口实现类之间的矛盾问题

1.当一个接口中抽象方法过多,但是我只要使用其中一部分的时候,就可以适配器设计模式

2.书写步骤:

  • 编写中间类XXXAdapter,实现对应的接口
  • 对接口中的抽象方法进行空实现
  • 让真正的实现类继承中间类,并重写需要用的方法
  • 为了避免其他类创建适配器类的对象中间的适配器类用abstract进行修饰
//接口
public interface Inter {
    public abstract void method1();
    public abstract void method2();
    public abstract void method3();
    public abstract void method4();
    public abstract void method5();
    public abstract void method6();
    public abstract void method7();
    public abstract void method8();
    public abstract void method9();
    public abstract void method10();
}

//中间类
public abstract class InterAdapter implements Inter{
    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public void method3() {

    }

    @Override
    public void method4() {

    }

    @Override
    public void method5() {

    }

    @Override
    public void method6() {

    }

    @Override
    public void method7() {

    }

    @Override
    public void method8() {

    }

    @Override
    public void method9() {

    }

    @Override
    public void method10() {

    }
}

实现类

public class InterImpl extends InterAdapter{
  //我需要用到那个方法,就重写哪个方法就可以了

    @Override
    public void method5() {
        System.out.println("只要用第五个方法");
    }
}

第七章 内部类

**类的五大成员:**属性、方法、构造方法、代码块、内部类

7.1 概述

7.面向对象进阶(1)_第13张图片
7.面向对象进阶(1)_第14张图片

public class Car {
    String carName;
    int carAge;
    String carColor;


    public void show(Car this){
        //是打印调用者车的名字:宾利
        System.out.println(this.carName);
        //调用内部类
        Engine e = new Engine();
        System.out.println(e.engineName);
    }
    
	//内部类
    class Engine{
        String engineName;
        int engineAge;

        public void show(){
            System.out.println(engineName);
            System.out.println(carName);
        }
    }
}

7.2 内部类的分类

按定义的位置来分

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

7.3 成员内部类

1.成员内部类的代码如何书写

2.如何创建成员内部类的对象

3.成员内部类如何获取外部类的成员变量

成员内部类:

  • 写在成员位置的,属于外部类的成员。

  • 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等在成员内部类里面,

  • JDK16之前不能定义静态变量,JDK 16开始才可以定义静态变量。

    public class car {外部类
        string carName;
        int carAge;
        int carcolor;
        //成员内部类
        class Engine{
    		string engineName;
            int engineAge;
        }
    }
    

成员内部类特点

  • 无static修饰的内部类,属于外部类对象的。
  • 宿主:外部类对象。

内部类的使用格式

 外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类

获取成员内部类对象的两种方式

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

外部类.内部类 变量 = 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());

    }
}

成员内部类的细节

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

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

详解:

​ 内部类被private修饰,外界无法直接获取内部类的对象,只能通过3.3节中的方式二获取内部类的对象

​ 被其他权限修饰符修饰的内部类一般用3.3节中的方式一直接获取内部类的对象

​ 内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。

​ 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上

7.4 成员内部类面试题

请在?地方向上相应代码,以达到输出的内容

注意:内部类访问外部类对象的格式是:外部类名.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
        }
    }
}

7.5 静态内部类

静态内部类特点

  • 静态内部类是一种特殊的成员内部类。

  • 有static修饰,属于外部类本身的。

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

  • 拓展1:静态内部类可以直接访问外部类的静态成员。

  • 拓展2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。

  • 拓展3:静态内部类中没有银行的Outer.this。

内部类的使用格式

外部类.内部类。

静态内部类对象的创建格式

外部类.内部类  变量 = 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();
    }
}

7.6 局部内部类

  • 局部内部类 :定义在方法中的类。
局部内部类
        1.将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
        2.外界是无法直接使用局部内部类,需要在方法内部创建对象并使用。
        3.该类可以直接访问外部类的成员,也可以访问方法内的局部变量。

定义格式:

class 外部类名 {
	数据类型 变量名;
	
	修饰符 返回值类型 方法名(参数列表) {
		// …
		class 内部类 {
			// 成员变量
			// 成员方法
		}
	}
}
public class Outer {
    int b = 20;
    public void show(){
        int a = 10;

        //局部内部类
        class Inner{
            String name;
            int age;

            public void method1(){
                System.out.println(a);
                System.out.println(b);
                System.out.println("局部内部类中的method1方法");
            }

            public static void method2(){
                System.out.println("局部内部类中的method2静态方法");
            }
        }

        //创建局部内部类的对象
        Inner i = new Inner();
        System.out.println(i.name);
        System.out.println(i.age);
        i.method1();
        Inner.method2();
    }
}
public class Test {
    public static void main(String[] args) {
        /*
        局部内部类
        1.将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
        2.外界是无法直接使用局部内部类,需要在方法内部创建对象并使用。
        3.该类可以直接访问外部类的成员,也可以访问方法内的局部变量。*/


        //调用show方法,让代码执行
        Outer o = new Outer();
        o.show();

    }
}

7.7 匿名内部类

1.什么是匿名内部类?
隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置。

2.格式的细节
包含了继承或实现,方法重写,创建对象。
整体就是一个类的子类对象或者接口的实现类对象

3.使用场景
当方法的参数是接口或者类时,
以接口为例,可以传递这个接口的实现类对象,
如果实现类只要使用一次,就可以用匿名内部类简化代码。

7.面向对象进阶(1)_第15张图片

案例

//接口
public interface Swim {
    public abstract void swimming();
}
//实现1
public class Demo07 {
    public static void main(String[] args) {
        new Swim() {
            @Override
            public void swimming() {
                System.out.println("自由泳...");
            }
        }.swimming();
    }
}

//输出
自由泳...
//实现2
public class Demo07 {
    public static void main(String[] args) {
        // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
        Swim s2 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蛙泳...");
            }
        };

        s2.swimming();
    }
}

//输出
蛙泳...

案例2

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

你可能感兴趣的:(2023java,java,jvm,spring)