Java从入门到精通 - 面向对象高级(一)

发布于:2025-06-13 ⋅ 阅读:(26) ⋅ 点赞:(0)

一、面向对象高级

此笔记参考黑马教程,仅学习使用,如有侵权,联系必删

文章目录

1. static(静态)

1.1 static 修饰成员变量

1.1.1 static
  • 叫静态,可以修饰成员变量、成员方法

成员变量按照有无 static 修饰,分为两种:

  • 类变量:有 static 修饰,属于类,在计算机里只有一份,会被类的全部对象共享
  • 实例变量(对象的变量):无 static 修饰,属于每个对象的
public class Student {
	// 类变量
	static String name;
	
	// 实例变量(对象的变量)
	int age;
}

![](https://i-blog.csdnimg.cn/direct/97874ce1679f49558b8cae2fc878e5ca.png#pic_center =)

  • 把学生类看成一张学生表,可以创建出学生对象 s1 和 s2,age 没有 static 修饰,所以是对象的变量,也就是每个学生对象中都有一个自己的 age。
  • 而成员变量 name 有 static 修饰,就是类变量,就是说它只属于当前这个类自己持有,不属于对象,也就是说无论我们用这个类创建出了多少个学生对象,这些学生对象中都不会持有这个类变量 name

类变量访问方法

类名.类变量(推荐)
对象.类变量(不推荐)

实例变量访问方法

对象.实例变量
1.1.2 成员变量的执行原理

总结
  1. static 是什么?
  • 叫静态,可以修饰成员变量、成员方法
  1. static 修饰的成员变量叫什么?怎么使用?有啥特点?
  • 类变量(静态成员变量)
类名.类变量(推荐)
对象.类变量(不推荐)
  • 属于类,与类一起加载一次,在内存中只有一份,会被类的所有对象共享
  1. 无 static 修饰的成员变量叫什么?怎么使用?有啥特点?
  • 实例变量(对象变量)
对象.实例变量
  • 属于对象,每个对象中都有一份

1.2 static 修饰成员变量的应用场景

类变量的应用场景
  • 在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住
案例:
  • 系统启动后,要求用户类可以记住自己创建了多少个用户对象了

  • User.java

package Advanced.a_static.d1_staticdemo;

public class User {
    // 类变量
    public static int number; // public是让它对外完全公开和暴露的

    public User() {
//        User.number++;
        // 注意:在同一个类中,访问自己类的类变量,才可以省略类名不写
        number++;
    }
}

  • Test2.java
package Advanced.a_static.d1_staticdemo;

public class Test2 {
    public static void main(String[] args) {
        // 目标:通过案例理解类变量的应用场景
        User u1 = new User();
        User u2 = new User();
        User u3 = new User();
        User u4 = new User();

        System.out.println(User.number); // 4
    }
}

总结
  1. 成员变量有几种?各自在什么情况下定义?
  • 类变量:数据只需要一份,且需要被共享时(访问,修改)
  • 实例变量:每个对象都要有一份,数据各不同(如:name、score、age)
  1. 访问自己类中的类变量,是否可以省略类名不写?
  • 可以的
  • 注意:在某个类中访问其他类里的类变量,必须带类名访问

1.3 static 修饰成员方法

1.3.1 成员方法的分类
  • 类方法:有 static 修饰的成员方法,属于类
public static void printHelloWorld() {
	System.out.println("Hello World!");
	System.out.println("Hello World!");
}
类名.类方法(推荐)
对象名.类方法(不推荐)
  • 实例方法:无 static 修饰的成员方法,属于对象
public void printPass() {
	...
}
对象.实例方法
1.3.2 成员方法的执行原理

![](https://i-blog.csdnimg.cn/direct/259054213b87496fb0edc327c6fbff22.png#pic_center =500)


代码演示
  • Student.java
package Advanced.a_static.d2_static_method;

public class Student {
    double score;

    // 类方法
    public static void printHelloWorld() {
        System.out.println("Hello World!");
        System.out.println("Hello World!");
    }

    // 实例方法(对象的方法)
    public void printPass() {
        System.out.println("成绩:" + (score >= 60 ? "及格" : "不及格"));
    }
}

  • Test.java
package Advanced.a_static.d2_static_method;

public class Test {
    public static void main(String[] args) {
        // 目标:掌握有无static修饰方法的用法
        // 1. 类方法的用法
        // 类名.类方法(推荐)
        Student.printHelloWorld();

        // 对象.类方法(不推荐)
        Student s = new Student();
        s.printHelloWorld();

        // 2. 实例方法的用法
        // 对象.实例方法
        s.printPass();
    }
}

总结
  1. static 修饰的成员方法叫什么?如何使用?
  • 类方法(静态方法)
  • 属于类,可以直接用类名访问,也可以用对象访问
类名.类方法(推荐)
对象名.类方法(不推荐)
  1. 无 static 修饰的成员方法叫什么?如何使用?
  • 实例方法(对象的方法)
  • 属于对象,只能用对象访问
对象.实例方法

补充知识:搞懂 mian 方法

public class Test {
	public static void main(String[] args) {
		...
	}
}
  1. main 方法是啥方法?
  • 类方法
    • 因为有 static
  1. main 方法咋就能直接跑起来?
  • 我们用 Java 命令执行这个 Test 程序的时候,虚拟机其实会用这个 Test 类直接去点这个 main 方法来触发这个 main 方法的执行
  • 为什么可以用类名直接去点这个 main 方法执行呢?因为 main 方法也是类方法
  1. 参数 String[] args 是什么?
  • 当我们来执行这个 main 方法的时候实际上我们可以送一些数据给这个参数接收的,这个参数它是一个 String 类型的数组,它可以接一些数据,然后让你这个 main 方法里面的程序来使用这些数据
  1. 怎么在执行 main 方法的时候把数据传给这个参数(String[] args)呢?
  • 在调用 java 命令执行时,直接跟在文件名后面

1.4 static 修饰成员方法的应用场景

1.4.1 类方法的常见应用场景
  • 类方法最常见的应用场景是做工具类
1.4.2 工具类是什么?
  • 工具类中的方法都是一些类方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的
1.4.3 使用类方法来设计工具类有啥好处?
  • 提高了代码复用;调用方便,提高了开发效率
public class XxxxUtil {
	public static void xxx() {
		...
	}
	public static boolean xxxx(String email) {
	...
	}
	public static String xxxxx(int n) {
	...
	}
	...
}
案例

需求:

  • 某系统的登录界面需要开发并展示四位随机的验证码,系统的注册界面需要开发并展示六位的随机验证码

这两套程序的代码几乎一模一样,存在大量重复代码,这时我们就可以使用类方法设计一个工具类让这两个程序来调用,这样就优化好这个结构了

代码实现
  • MyUtil.java
package Advanced.a_static.d3_util;

import java.util.Random;

public class MyUtil {
    
    private MyUtil() {
    }

    public static String createCode(int n) {
        // 1. 定义2个变量是,一个是记住最终产生的随机验证码,一个是记住可能用到的全部字符
        String code = "";
        String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

        Random r = new Random();

        // 2. 开始定义一个循环产生每位随机数字符
        for (int i = 0; i < 4; i++) {
            // 3. 随机一个字符范围内的索引
            int index = r.nextInt(data.length());
            // 4. 根据索引去全部字符中提取该字符
            code += data.charAt(index); // code = code + 字符
        }
        return code;
    }
}

  • 为什么工具类中的方法要用类方法,而不是实例方法?

    • 实例方法需要创建对象来调用,此时对象只是为了调用方法,对象占内存,这样会浪费内存
多学一招
  • 工具类没有创建对象的需求,建议将工具类的构造器进行私有
总结
  1. 类方法有啥应用场景?
  • 可以用来设计工具类
  1. 工具类是什么,有什么好处?
  • 工具类中的方法都是类方法,每个类方法都是用来完成一个功能的
  • 提高了代码的复用性;调用方便,提高了开发效率
  1. 为什么工具类要用类方法,而不是实例方法?
  • 实例方法需要创建对象来调用,会浪费内存
  1. 工具类定义时有什么要求?
  • 工具类不需要创建对象,建议将工具类的构造器私有化

1.5 static 的注意事项

  • 类方法中可以直接访问类的成员,不可以直接访问实例成员
  • 实例方法既可以直接访问类成员,也可以直接访问实例成员
  • 实例方法中可以出现 this 关键字,类方法中不可以出现 this 关键字的
代码演示
package Advanced.a_static.d4_static_attention;

public class Student {
    static String schoolName; // 类变量
    double score; // 实例变量

    // 1. 类方法中可以直接访问类的成员,不可以直接访问实例成员
    public static void printHelloWorld() {
        // 注意:同一个类中,访问类成员可以省略类名不写
        schoolName = "Feng";
        Student.printHelloWorld2();

//        System.out.println(score); // 会报错
//        printPass(); // 会报错

//        System.out.println(this); // 会报错
    }

    // 类方法
    public static void printHelloWorld2() {

    }

    // 2. 实例方法中既可以直接访问类成员,也可以直接访问实例成员
    // 实例方法
    // 3. 实例方法中可以出现this关键字,类方法中不可以出现this关键字
    public void printPass() {
        schoolName = "Feng2";
        printHelloWorld2();

        System.out.println(score);
        printPass2();

        System.out.println(this); // 哪个对象调用它的实例方法 this就会指向哪个对象
    }

    // 实例方法
    public void printPass2() {

    }
}


1.6 static 的应用知识:代码块

1.6.1 代码块概述
  • 代码块是类的的5大成分之一(成员变量、构造器、方法、代码块、内部类)

代码块分为两种:

  • 静态代码块:

    • 格式:static {}
    • 特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
    • 作用:完成类的初识化,例如:对类变量的初始化赋值
  • 实例代码块:

    • 格式:{}
    • 特点:每次创建对象时,执行实例代码块,并在构造器前执行
    • 作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例对象变量进行初始化赋值
代码演示
  • Student.java
package Advanced.a_static.d5_block;

public class Student {
    static int number = 80;
    static String schoolName;

    // 静态代码块
    static {
        System.out.println("静态代码块执行了");
        schoolName = "Feng";
    }


    // 实例代码块
    {
        System.out.println("实例代码块执行了~~~");
        System.out.println("有人创建了对象" + this);
    }

    public Student() {
        System.out.println("无参数构造器执行了~~~");
    }

    public Student(String name) {
        System.out.println("有参数构造器执行了~~~");
    }
}

  • Test.java
package Advanced.a_static.d5_block;

public class Test {
    public static void main(String[] args) {
        // 目标:认识两种代码块,了解他们的特点和基本作用
        System.out.println(Student.number); // 静态代码块执行了 80
        System.out.println(Student.number); // 80
        System.out.println(Student.number); // 80

        System.out.println(Student.schoolName); // Feng

        System.out.println("-----------------------");
        Student s1 = new Student(); // 实例代码块执行了~~~ 有人创建了对象Advanced.a_static.d5_block.Student@b4c966a 无参数构造器执行了~~~
        Student s2 = new Student("张三"); // 实例代码块执行了~~~ 有人创建了对象Advanced.a_static.d5_block.Student@1d81eb93 有参数构造器执行了~~~
    }
}


1.7 static 的应用知识:单例设计模块

1.7.1 什么是设计模式(Design pattern)?
  • 一个问题通常有 n 种解法,其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式
  • 设计模式有20多种,对应20多种软件开发中会遇到的问题
1.7.1.2 单例设计模式(饿汉式单例)
  • 确保一个类只有一个对象

写法

  • 把类的构造器私有
  • 定义一个类变量记住类的一个对象
  • 定义一个类方法,返回对象

代码实现

  • A.java
package Advanced.a_static.d6_singleInstance;

public class A {
    // 2.定义一个类变量记住类的对象
    private static A a = new A();

    // 1. 必须私有类的构造器
    // 使得外面不能创建对象
    private A() {

    }

    // 3. 定义一个类方法返回类的对象
    public static A getObject() {
        return a;
    }
}

  • Test1.java
package Advanced.a_static.d6_singleInstance;

public class Test1 {
    public static void main(String[] args) {
        // 目标:掌握单例设计模式的写法
        A a1 = A.getObject();
        A a2 = A.getObject();
        System.out.println(a1); // Advanced.a_static.d6_singleInstance.A@b4c966a
        System.out.println(a2); // Advanced.a_static.d6_singleInstance.A@b4c966a
    }
}

1.7.1.3 单例模式的应用场景和好处
  • Runtime 类
    • 程序的运行环境,Java 程序执行的时候只有一套运行环境,因此 Runtime 它也只需要一个对象就可以代表你那个唯一的运行环境
  • 任务管理器
    • 无论启动多少次任务管理器,其窗口对象始终只有一个
1.7.1.4 单例设计模式的实现方式很多

总结
  1. 什么是设计模式,设计模式主要学什么?单例模式解决了上面问题
  • 设计模式就是具体问题的最优解决方案
  • 解决了什么问题?怎么写?
  • 确保一个类只有一个对象
  1. 单例怎么写?饿汉式单例的特点是什么?
  • 把类的构造器私有;定义一个类变量存储类的一个对象;提供一个类方法返回对象
  • 在获取类对象时,对象已经创建好了
  1. 单例有啥应用场景,有啥好处?
  • 任务管理器对象、获取运行时对象
  • 在这些业务场景下,使用单例模式,可以避免浪费内存

1.7.2 懒汉式单例设计模式
  • 拿对象时,才开始创建对象

写法

  • 把类的构造器私有
  • 定义一个类变量用于存储对象
  • 提供一个类方法,保证返回的是同一个对象

代码实现

  • B.java
package Advanced.a_static.d6_singleInstance;

public class B {
    // 2. 定义一个类变量,用于存储这个类的一个对象
    private static B b;

    // 1. 把类的构造器私有
    private B() {

    }

    // 3. 定义一个类方法,这个方法要保证第一次调用时才创建一个对象,后面调用时都会用这同一个对象返回
    public static B getInstance() {
        if (b == null) {
            System.out.println("第一次创建对象~~~");
            b = new B();
        }
        return b;
    }
}

  • Test2.java
package Advanced.a_static.d6_singleInstance;

/**
 * 目标:掌握懒汉式单例的写法
 */

public class Test2 {
    public static void main(String[] args) {
        B b1 = B.getInstance(); // 第一次拿对象
        B b2 = B.getInstance();

        System.out.println(b1 == b2); // true
    }
}


2. 面向对象三大特征之二:继承

2.1 继承的快速入门

2.1.1 认识继承、特点
2.1.1.1 什么是继承?
  • Java 中提供了一个关键字 extends,用这个关键字,可以让一个类和另一个类建立起父子关系
public class B extends A {
    
}
  • A 类称之为父类(基类或超类)
  • B 类称之为子类(派生类)

2.1.1.2 继承的特点
  • 子类能继承父类的非私有成员(成员变量、成员方法)

继承后对象的创建

  • 子类的对象是由子类、父类共同完成的
2.1.1.3 继承的执行原理

  • 子类对象实际上是由子父类这两张设计图共同创建出来的
代码实现
  • A.java
package Advanced.b_extends.d7_extends;

// 父类
public class A {
    // 公开成员
    public int i;

    public void print1() {
        System.out.println("===print1===");
    }

    // 私有成员
    private int j;

    private void print2() {
        System.out.println("===print2===");
    }
}

  • B.java
package Advanced.b_extends.d7_extends;

// 子类
public class B extends A {
    // 子类是可以继承父类的非私有成员
    public void print3() {
        System.out.println(i);
        print1();

//        System.out.println(j); // 报错
//        print2(); // 报错
    }
}

  • Test.java
package Advanced.b_extends.d7_extends;

public class Test {
    public static void main(String[] args) {
        // 目标:认识继承、掌握继承的特点
        B b = new B();
        System.out.println(b.i); // 0
//        System.out.println(b.j); // 报错

        b.print1();
//        b.print2(); // 报错
        b.print3();
    }
}

总结
  1. 什么是继承?继承后有啥特点?
  • 继承就是用 extends 关键字,让一个类和另一个类建立起一种父子关系
  • 子类可以继承父类非私有的成员
  1. 带继承关系的类,Java 会怎么创建它的对象?对象创建出来后,可以直接访问哪些成员?
  • 带继承关系的类,Java 会用类和其父类,这多张设计图来一起创建类的对象
  • 对象能直接访问什么成员,是由子父类这多张设计图共同决定的,这多张设计图对外暴露了什么成员,对象就可以访问什么成员

2.1.2 继承的好处、应用场景
2.1.2.1 使用继承有啥好处?
  • 减少重复代码的编写

需求:
Feng 的员工管理系统种
需要处理讲师、咨询师的数据
讲师的数据有:姓名、具备的技能
咨询师的数据有:姓名、解答问题的总人数

代码实现

  • People.java
package Advanced.b_extends.d8_extends_application;

public class People {
    private String name;

    public String getName() {
        return name;
    }

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

  • Teacher.java
package Advanced.b_extends.d8_extends_application;

public class Teacher extends People {
    private String skill;

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    public void printInfo() {
        System.out.println(getName() + "具备的技能" + skill);
    }
}

  • Test.java
package Advanced.b_extends.d8_extends_application;

public class Test {
    public static void main(String[] args) {
        // 目标:搞清楚继承的好处
        Teacher t = new Teacher();
        t.setName("张三");
        t.setSkill("Java、Spring");
        System.out.println(t.getName()); // 张三
        System.out.println(t.getSkill()); // Java、Spring
        t.printInfo(); // 张三具备的技能Java、Spring
    }
}

总结
  1. 使用继承有啥好处?
  • 减少了重复代码的编写,提高了代码的复用性

2.2 继承相关的注意事项

2.2.1 权限修饰符
2.2.1.1 什么是权限修饰符?
  • 就是用来限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围
2.2.1.2 权限修饰符有几种?各自的作用是什么?
修饰符 在本类中 同一个包下的其他类里 任意包下的子类里 任意包下的任意类里
private
缺省
protected
public

private < 缺省 < protected < public

代码演示
  • d9_modifer.Fu.java
package Advanced.b_extends.d9_modifer;

public class Fu {
    // 1. 私有:只能在本类中访问
    private void privateMethod(){
        System.out.println("===private===");
    }

    // 2. 缺省:本类,同一个包下的类
    void method(){
        System.out.println("===缺省===");
    }

    // 3. protected:本类,同一个包下的类,任意包下的子类
    protected void protectedMethod(){
        System.out.println("===protected===");
    }

    // 4. public:本类,同一个包下的类,任意包下的子类,任意包下的任意类
    public void publicMethod(){
        System.out.println("===public===");
    }

    public void test(){
        privateMethod();
        method();
        privateMethod();
        publicMethod();
    }
}

  • d9_modifer.Demo.java
package Advanced.b_extends.d9_modifer;

public class Demo {
    public static void main(String[] args) {
        // 目标:掌握不同权限修饰符的作用
        Fu f = new Fu();
//        f.privateMethod(); // 报错
        f.method();
        f.protectedMethod();
        f.publicMethod();
    }
}

  • d10_modifer.Zi.java
package Advanced.b_extends.d10_modifer;

import Advanced.b_extends.d9_modifer.Fu;

public class Zi extends Fu {
    public void test() {
//        privateMethod(); // 报错
//        method(); // 报错
        protectedMethod();
        publicMethod();
    }
}

  • d10_modifer.Demo2.java
package Advanced.b_extends.d10_modifer;

import Advanced.b_extends.d9_modifer.Fu;

public class Demo2 {
    public static void main(String[] args) {
        Fu f = new Fu();
//        f.privateMethod(); // 报错
//        f.method(); // 报错
//        f.protectedMethod(); // 报错
        f.publicMethod();

        Zi zi = new Zi();
//        zi.protectedMethod(); // 报错
    }
}


2.2.2 单继承、Object 类
2.2.2.1 单继承

Java 是单继承的,Java 中的类不支持多继承,但是支持多层继承

单继承:指的是一个类只能继承一个直接父类

2.2.2.2 为何 Java 中的类不支持多继承

反证法:

  • 假设 Java 中的类支持多继承,现在有两个类,一个 A 中有一个 method 方法里面打印 “aaa”,一个 B 中有一个 method 方法里面打印 “bbb”
  • 再定义一个 C 类同时继承 A 和 B,那么 C 调用 method 方法时就不知道调用谁的方法了
2.2.2.3 Object 类
  • Object 类是 Java 所有类的祖宗类。我们写的任意一个类,其实都是 Object 的子类或者子孙类
代码演示
package Advanced.b_extends.d11_extends_feature;

public class Test {
    public static void main(String[] args) {
        // 目标:掌握继承的两个注意事项
        // 1. Java是单继承的:一个类只能继承一个直接父类;Java中的类不支持多继承,但支持多层继承
        // 2. Object类是Java中所有类的祖宗
        A a = new A();

        B b = new B();
    }
}

class A {
} // extends Object{}

class B extends A {
}

//class C extends B, A{} // 报错

class D extends B {
}

总结
  1. 继承相关的两个注意事项?
  • Java 是单继承的:一个类只能继承一个直接父类;Java 中的类不支持多继承,但是支持多层继承

  • Object 类是 Java 中所有类的祖宗


2.2.3 方法重写
2.2.3.1 认识方法重写

什么是方法重写?

  • 当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写
  • 注意:重写后,方法的访问,Java 会遵循就近原则

方法重写的其他注意事项

  • 重写小技巧:使用 Override 注解,他可以指定 Java 编译器,检查我们方法重写的格式是否正确,代码可读性也会更好
  • 子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限(public > protected > 缺省
  • 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
  • 私有方法、静态方法不能被重写,如果重写会报错的

代码演示

  • A.java
package Advanced.b_extends.d12_extends_override;

public class A {
    public void print1() {
        System.out.println("111");
    }

    public void print2(int a, int b) {
        System.out.println("111111");
    }
}

  • B.java
package Advanced.b_extends.d12_extends_override;

public class B extends A {
    // 方法重写
    @Override // 安全,可读性好
    public void print1() {
        System.out.println("666");
    }

    // 方法重写
    @Override
    public void print2(int a, int b) {
        System.out.println("666666");
    }
}

  • Test.java
package Advanced.b_extends.d12_extends_override;

public class Test {
    public static void main(String[] args) {
        // 目标:认识方法重写,掌握方法重写的常见应用场景
        B b = new B();
        b.print1(); // 666
        b.print2(2, 3); // 666666
    }
}

2.2.3.2 方法重写的应用场景
  • 子类重写 Object 类的 toString() 方法,以便返回对象的内容

代码演示

  • Student.java
package Advanced.b_extends.d12_extends_override;

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

    public Student() {
    }

    public Student(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;
    }

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

  • Test.java
package Advanced.b_extends.d12_extends_override;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        // 目标:认识方法重写,掌握方法重写的常见应用场景
        B b = new B();
        b.print1(); // 666
        b.print2(2, 3); // 666666

        System.out.println("----------------------------");

        Student s = new Student("张三", 19);
        System.out.println(s.toString()); // Advanced.b_extends.d12_extends_override.Student@4e50df2e   Student{name=张三,age=19}
        System.out.println(s); // Advanced.b_extends.d12_extends_override.Student@4e50df2e   Student{name=张三,age=19}

        ArrayList list = new ArrayList();
        list.add("java");
        System.out.println(list); // [java]   说明ArrayList类中把Object类里面的toString方法进行了重写
    }
}

总结
  1. 方法重写是什么?
  • 子类写了一个方法名称、形参列表与父类某个方法一样的方法去覆盖父类的该方法
  1. 重写方法有哪些注意事项?
  • 建议加上:@Override 注解,可以校验重写是否正确,同时可读性好
  • 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限
  • 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
  • 私有方法、静态方法不能被重写
  1. 方法重写有啥应用场景?
  • 当子类觉得父类的方法不好用,或者不满足自己的需求时,就可以用方法重写

2.2.4 子类中访问其他成员的特点
  1. 在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则
  • 先子类局部范围找
  • 然后子类成员范围找
  • 然后父类成员范围找,如果父类范围还没有找到则报错
  1. 如果父类中,出现了重名的成员,会优先使用子类的,如果此时一定要在子类中使用父类的怎么办?
  • 可以通过 super 关键字,指定访问父类的成员:super.父类成员变量/父类成员方法

代码演示

  • F.java
package Advanced.b_extends.d13_extends_visit;

public class F {
    String name = "父类名字";

    public void print1() {
        System.out.println("===父类的print1方法执行===");
    }
}

  • Z.java
package Advanced.b_extends.d13_extends_visit;

public class Z extends F {
    String name = "子类名称";

    public void showName() {
        String name = "局部名称";
        System.out.println(name);
        System.out.println(this.name);
        System.out.println(super.name);
    }

    @Override
    public void print1() {
        System.out.println("===子类的print1方法执行了===");
    }

    public void showMethod() {
        print1(); // 子类的
        super.print1(); // 父类的
    }
}

  • Test.java
package Advanced.b_extends.d13_extends_visit;

public class Test {
    public static void main(String[] args) {
        // 目标:掌握子类中访问其他成员的特点:就近原则
        Z z = new Z();
        z.showName(); // 局部名称 子类名称 父类名字
        z.showMethod(); // ===子类的print1方法执行了=== ===父类的print1方法执行===
    }
}


2.2.5 子类构造器的特点
2.2.5.1 认识子类构造器的特点

子类构造器的特点:

  • 子类的全部构造器,都会先调用父类的构造器,再执行自己

子类构造器是如何实现调用父类构造器的:

  • 默认情况下,子类全部构造器的第一行代码都是 super(); (写不写都有),它会调用父类的无参数构造器
  • 如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写 super(...); ,指定去调用父类的有参数构造器
代码演示
package Advanced.b_extends.d14_extends_constructor;

class F {
//    public F() {
//        System.out.println("===父类F的无参数构造器执行了===");
//    }

    public F(String name, int age) {

    }
}

class Z extends F {
    public Z() {
//        super(); // 默认存在的
        super("张三",17);
        System.out.println("===子类Z的无参数构造器执行了===");
    }

    public Z(String name) {
//        super(); // 默认存在的
        super("张三",17);
        System.out.println("===子类Z的有参数构造器执行了===");
    }
}

public class Test {
    public static void main(String[] args) {
        // 目标:先认识子类构造器的特点,再掌握这个特点的常见应用场景
        Z z = new Z(); // ===父类F的无参数构造器执行了=== ===子类Z的无参数构造器执行了===
        Z z2 = new Z("张三"); // ===父类F的无参数构造器执行了=== ===子类Z的有参数构造器执行了===
    }
}

2.2.5.1 常见的应用场景

搞清楚子类构造器为什么要调用父类的构造器,有啥应用场景?

  • 在继承情况下,由于处理对象数据的构造器拆到了多个类里面去了,所以对象要通过调用这多个构造器才能把对象的数据处理完整

运行过程

  • 子类构造器可以通过父类构造器,把对象中包含父类这部分的数据先初始化赋值,再回来把对象里包含子类这部分的数据也进行初始化赋值
代码演示
package Advanced.b_extends.d14_extends_constructor;

public class Test2 {
    public static void main(String[] args) {
        // 目标:搞清楚子类构造器为什么要调用父类的构造器,有啥应用场景
        Teacher t = new Teacher("李四", 36, "Java");
        System.out.println(t.getName()); // 李四
        System.out.println(t.getAge()); // 36
        System.out.println(t.getSkill()); // Java
    }
}

class Teacher extends People {
    private String skill;

    public Teacher(String name, int age, String skill) {
        super(name, age);
        this.skill = skill;
    }

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
}

class People {
    private String name;
    private int age;

    public People() {
    }

    public People(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;
    }
}

补充知识:this(…) 调用兄弟构造器
  • 任意类的构造器中,是可以通过 this(...) 去调用该类的其他构造器的

this(…) 和 super(…) 使用时的注意事项:

  • this(…)、super(…) 都只能放在构造器的第一行,因此,有了 this(…) 就不能写 super(…) 了,反之亦然

代码演示

package Advanced.b_extends.d14_extends_constructor;

public class Test3 {
    public static void main(String[] args) {
        // 目标:掌握在类的构造器中,通过this(...)调用兄弟构造器的作用
        Student s1 = new Student("李四", 26, "家里蹲大学");

        // 需求:如果学生没有填写学校,那么学校默认就是Feng
        Student s2 = new Student("张三", 28);
        System.out.println(s2.getName()); // 张三
        System.out.println(s2.getAge()); // 28
        System.out.println(s2.getSchoolName()); // Feng
    }
}

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

    public Student() {
    }

    public Student(String name, int age) {
        this(name, age, "Feng");
    }

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

    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 String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }
}


2.2.6 注意事项小结
  1. 子类构造器有啥特点?
  • 子类中的全部构造器,都必须先调用父类的构造器,再执行自己
  1. super(…) 调用父类有参数构造器的常见场景是什么?
  • 为对象中包含父类这部分的成员变量进行赋值
  1. this(…) 的作用是什么?
  • 在构造器中调用本类的其他构造器
  1. this(…) 和 super(…) 的使用需要注意什么?
  • 都必须放在构造器的第一行

3. 面向对象的三大特征之三:多态

3.1 认识多态

3.1.1 什么是多态?
  • 多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态
3.1.2 多态的具体代码实现
People p1 = new Student();
p1.run();

People p2 = new Teacher();
p2.run();
3.1.3 多态的前提
  • 继承/实现关系;存在父类引用子类对象;存在方法重写
3.1.4 多态的一个注意事项
  • 多态是对象、行为的多态,Java 中的属性(成员变量)不谈多态
代码演示
  • People.java
package Advanced.c_polymorphism.d1_polymorphism;

// 父类
public class People {
    public String name = "父类People的名称";

    public void run() {
        System.out.println("人可以跑~~~");
    }
}

  • Student.java
package Advanced.c_polymorphism.d1_polymorphism;

public class Student extends People {
    public String name = "子类Student的名称";

    @Override
    public void run() {
        System.out.println("学生跑得贼快~~~");
    }
}

  • Teacher.java
package Advanced.c_polymorphism.d1_polymorphism;

public class Teacher extends People {
    public String name = "子类Teacher的名称";

    @Override
    public void run() {
        System.out.println("老师跑的气喘吁吁~~~");
    }
}

  • Test.java
package Advanced.c_polymorphism.d1_polymorphism;

public class Test {
    public static void main(String[] args) {
        // 目标:认识多态:对象多态、行为多态
        // 1. 对象多态
        People p1 = new Teacher();
        // 行为多态:识别技巧:编译看左边,运行看右边
        p1.run(); // 老师跑的气喘吁吁~~~
        // 注意:对于变量:编译看左边,运行看左边
        System.out.println(p1.name); // 父类People的名称

        People p2 = new Student();
        // 行为多态:识别技巧:编译看左边,运行看右边
        p2.run(); // 学生跑得贼快~~~
        System.out.println(p2.name); // 父类People的名称
    }
}


3.2 使用多态的好处

3.2.1 使用多态的好处
  • 在多态形势下,右边对象是解耦合的,更便于扩展和维护

    • 解耦合:比如说我们在开发系统时,希望把这个系统中的每个模块拆分成一个一个的服务,可以随时的对接,这样更容易扩展和维护,这就是解耦合
  • 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利

3.2.2 多态下会产生的一个问题,怎么解决?
  • 多态下不能使用子类的独有功能
代码演示
  • People.java
package Advanced.c_polymorphism.d2_polymorphism;

// 父类
public class People {
    public String name = "父类People的名称";

    public void run() {
        System.out.println("人可以跑~~~");
    }
}

  • Student.java
package Advanced.c_polymorphism.d2_polymorphism;

public class Student extends People {
    public String name = "子类Student的名称";

    @Override
    public void run() {
        System.out.println("学生跑得贼快~~~");
    }

    public void test() {
        System.out.println("学生需要考试~~~");
    }
}

  • Teacher.java
package Advanced.c_polymorphism.d2_polymorphism;

public class Teacher extends People {
    public String name = "子类Teacher的名称";

    @Override
    public void run() {
        System.out.println("老师跑的气喘吁吁~~~");
    }

    public void test() {
        System.out.println("老师需要教知识~~~");
    }
}

  • Test.java
package Advanced.c_polymorphism.d2_polymorphism;

public class Test {
    public static void main(String[] args) {
        // 目标:理解多态的好处
        // 好处1:可以实现解耦合,右边对象可以随时切换,后续业务随之改变
        People p1 = new Student();
        p1.run();
//        p1.test(); // 多态下存在的问题,无法直接调用子类的独有功能

        Student s = new Student();
        go(s);

        Teacher t = new Teacher();
        go(s);
    }

    // 好处2:可以使用父类类型的变量作为形参,可以接收一切子类对象
    public static void go(People p) {

    }
}

总结
  1. 使用多态有什么好处?存在什么问题?
  • 可以解耦合,扩展性更强;使用父类类型的变量作为方法的形参时,可以接收一切子类对象
  • 多态下不能直接调用子类的独有方法

3.3 多态下的类型转换问题

3.3.1 类型转换
  • 自动类型转换:父类 变量名 = new 子类();

    • eg:People p = new Teacher();
  • 强制类型转换:子类 变量名 = (子类) 父类变量

    • eg:Teacher t = (Teacher) p;
3.3.2 强制类型转换的一个注意事项
  • 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
  • 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来
People p = new Teahcer();

Student s = (Student) p; // java.lang.ClassCastException
3.3.3 强转前,Java 建议:
  • 使用 instanceof 关键字,判断当前对象的真实类型,再进行强转
p instanceof Student
代码演示
  • People.java
package Advanced.c_polymorphism.d2_polymorphism;

// 父类
public class People {
    public String name = "父类People的名称";

    public void run() {
        System.out.println("人可以跑~~~");
    }
}

  • Student.java
package Advanced.c_polymorphism.d2_polymorphism;

public class Student extends People {
    public String name = "子类Student的名称";

    @Override
    public void run() {
        System.out.println("学生跑得贼快~~~");
    }

    public void test() {
        System.out.println("学生需要考试~~~");
    }
}

  • Teacher.java
package Advanced.c_polymorphism.d2_polymorphism;

public class Teacher extends People {
    public String name = "子类Teacher的名称";

    @Override
    public void run() {
        System.out.println("老师跑的气喘吁吁~~~");
    }

    public void teach() {
        System.out.println("老师需要教知识~~~");
    }
}

  • Test.java
package Advanced.c_polymorphism.d2_polymorphism;

public class Test {
    public static void main(String[] args) {
        // 目标:理解多态的好处
        // 好处1:可以实现解耦合,右边对象可以随时切换,后续业务随之改变
        People p1 = new Student();
        p1.run();
//        p1.test(); // 多态下存在的问题,无法直接调用子类的独有功能

        // 强制类型转换
        Student s1 = (Student) p1;
        s1.test(); // 学生需要考试~~~

        // 强制类型转换可能存在的问题,编译阶段有继续或者实现关系就可以强制转换,但是运行时可能出现类型转换异常
//        Teacher t1 = (Teacher) p1; // 运行时出现了:ClassCastException

        if (p1 instanceof Student) {
            Student s2 = (Student) p1;
            s2.test(); // 学生需要考试~~~
        } else {
            Teacher t2 = (Teacher) p1;
            t2.teach();
        }

        System.out.println("------------------------------");
        // 好处2:可以使用父类类型的变量作为形参,可以接收一切子类对象
        Student s = new Student();
        go(s); // 学生跑得贼快~~~ 学生需要考试~~~

        Teacher t = new Teacher();
        go(t); // 老师跑的气喘吁吁~~~ 老师需要教知识~~~
    }

    // 好处2:可以使用父类类型的变量作为形参,可以接收一切子类对象
    public static void go(People p) {
        p.run();
        if (p instanceof Student) {
            Student s2 = (Student) p;
            s2.test();
        } else if (p instanceof Teacher) {
            Teacher t2 = (Teacher) p;
            t2.teach();
        }
    }
}

总结
  1. 类型转换有几种形式?能解决什么问题?
  • 自动类型转换、强制类型转换
  • 可以把对象转换成其真正的类型,从而解决了多态下不能调用子类独有方法的问题
  1. 强制类型转换需要注意什么?
  • 存在继承/实现时,就可以进行强制类型转换,编译阶段不会报错
  • 但是,运行时,如果发现对象的真是类型与强转后的类型不同会报错(ClassCastException)
  1. 强制类型转换前,Java 建议我们做什么事情?
  • 使用 instanceof 判断当前对象的真是类型:对象 instanceof 类型