day02——面向对象高级

YangeIT大约 23 分钟JavaSE进阶多态final继承

day02——面向对象高级

学习目标

  • 继承 🍐✏️❤️
    • 方法重写✏️🍐
    • 子类中访问成员的特点 ✏️🍐
    • 子类中访问构造器的特点 ✏️🍐
    • this.和super.,this()和super()的区别 ✏️🍐
  • 多态 🍐✏️❤️
    • 多态概述 🍐
    • 多态的好处 🍐
    • 类型转换 🍐 ✏️
  • final关键字🍐✏️
  • 抽象类🍐✏️

1. 多态

接下来,我们学习面向对象三大特征的的最后一个特征——多态。

1.1 多态概述 🍐 ✏️

多态概述

  1. 什么是多态?

多态是在继承或实现情况下的一种现象,表现为:对象多态、行为多态

比如:Teacher和Student都是People的子类,代码可以写成下面的样子 1664278943905

1664278943905
1664278943905
  1. 多态的前提

    1. 有继承/实现关系;
    2. 存在父类引用子类对象;
    3. 存在方法重写。
  2. 多态的一个注意事项

    • 多态是对象、行为的多态,Java中的属性 (成员变量)不谈多态。

代码操作

定义一个People父类

public class People {
    String name = "父类";
    public void run(){
        System.out.println("人会跑");
    }
}

定义一个学生类,继承People类

public class Student extends People{
    String name = "学生";
    @Override
    public void run() {
        System.out.println("学生跑的飞快~~~");
    }

    public void study(){
        System.out.println("学生好好学习~~~");
    }
}

定义一个老师类,继承People类

public class Teacher extends People{
    String name = "老师";

    @Override
    public void run() {
        System.out.println("老师跑的比较慢~~~");
    }

    public void teach(){
        System.out.println("老师教授Java~~~");
    }
}

定义一个测试类,认识多态

public class Test {
    public static void main(String[] args) {
        // 目标:认识多态
        People p1 = new Teacher();
        System.out.println(p1.name);
        p1.run();

        People p2 = new Student();
        System.out.println(p2.name);
        p2.run();
    }
}

总结

课堂作业

  1. 多态的前提是什么 ?
  2. 导入上述演示代码,体验多态的前提有哪些?🎤

1.2 多态的好处 🍐

多态的好处

各位同学,刚才我们认识了什么是多态。那么多态的写法有什么好处呢?

在多态形式下,右边的代码是解耦合的,更便于扩展和维护。

  1. 怎么理解这句话中的解耦尼?

比如刚开始p1指向Student对象,run方法执行的就是Student对象的业务;假如p1指向Teacher对象 ,run方法执行的自然是Teacher对象的业务。

People p1 = new Student();
p1.run();

People p1 = new Teacher();
p1.run();

  1. 怎么理解这句话中的更便于扩展和维护尼?
public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握使用多态的好处
		Teacher t = new Teacher();
		go(t);

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

    //参数People p既可以接收Student对象,也能接收Teacher对象。
    public static void go(People p){
        System.out.println("开始------------------------");
        p.run();
        System.out.println("结束------------------------");
    }
}

这样go方法是不是拓展性更强!!

问题

多态下不能使用子类的独有功能。

People类中 没有teach()方法

总结

课堂作业

  1. 多态的好处有哪些?下图中p1对象和p2对象能否调用teach()方法?为什么?🎤 image

1.3 类型转换 ✏️ 🍐

类型转换

虽然多态形式下有一些好处,但是也有一些弊端 👇

多态形式下,不能调用子类特有的方法,比如在Teacher类中多了一个teach方法,在Student类中多了一个study方法,这两个方法在多态形式下是 不能直接调用 的。

1665018661860
1665018661860

解决方案:

多态形式不能直接调用 子类特有方法,但是转型后是可以调用 的。这里所说的转型就是把父类变量转换为子类类型

格式如下:

 子类 变量名 = (子类)父类变量;

如果类型转换错了,就会出现类型转换异常ClassCastException ,比如把Teacher类型转换成了Student类型.

1665019335142
1665019335142

完整格式如下:

 子类 变量名 = (子类)父类变量;

//如果p接收的是子类对象
if(父类变量p instanceof 子类){
    //则可以将p转换为子类类型
    子类 变量名 = (子类)父类变量;
}
1665018905475
1665018905475

关于多态转型问题,我们最终记住一句话:原本是什么类型,才能还原成什么类型

完整代码:

注意:Teacher,Student,People类的代码在上面的案例中

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握使用多态的好处,以及多态下的类型转换问题。
//        People p1 = new Student();
//        p1.run();

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

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

    public static void go(People p){
        System.out.println("开始------------------------");
        
        p.run();

        //instanceof可以用来判断 对象是属于哪个类的实例
        if(p instanceof Student){
            Student s = (Student) p;
            s.study();
        }else if(p instanceof Teacher){
            Teacher t = (Teacher) p;
            t.teach();
        }
        System.out.println("结束------------------------");
    }
}

总结

课堂作业

  1. 多态形式下有一些好处,但是也有一些弊端 ,这里说的弊端指的是哪些?🎤
  2. 关于多态转型,只需要记住哪句话?怎么避免在转换过程中不会报错!🎤

2. final关键字

各位同学,接下来我们学习一个在面向对象编程中偶尔会用到的一个关键字叫final,也是为后面学习抽象类和接口做准备的。

2.1 final修饰符的特点

final关键字

  • final 关键字是最终的意思,可以修饰(类、方法、变量)
  • 修饰类:该类被称为最终类,特点是不能被继承了。
  • 修饰方法:该方法被称为最终方法,特点是不能被重写了。
  • 修饰变量:该变量只能被赋值一次。

代码操作

  • 接下来我们分别演示一下,先看final修饰类的特点
1665020107661
1665020107661
// final的作用:修饰类,类不能被继承了
final class A{}
class B extends A{}

总结

final 关键字是最终的意思,可以修饰(类、方法、变量) 修饰类:该类被称为最终类,特点是不能被继承了。 修饰方法:该方法被称为最终方法,特点是不能被重写了。 修饰变量:该变量只能被赋值一次。

课堂作业

  1. final修饰的方法,子类能重写吗?🎤

2.2 补充知识:常量

常量

刚刚我们学习了final修饰符的特点,在实际运用当中经常使用final来定义常量。先说一下什么是Java中的常量?

  • 被 static final 修饰的成员变量,称之为常量。
  • 通常用于记录系统的配置信息

代码操作

接下来我们用代码来演示一下

public class Constant {
    //常量: 定义一个常量表示学校名称
    //为了方便在其他类中被访问所以一般还会加上public修饰符
    //常量命名规范:建议都采用大写字母命名,多个单词之前有_隔开
    public static final String SCHOOL_NAME = "传智教育";
}
public class FinalDemo2 {
    public static void main(String[] args) {
        //由于常量是static的所以,在使用时直接用类名就可以调用
        System.out.println(Constant.SCHOOL_NAME);
        System.out.println(Constant.SCHOOL_NAME);
        System.out.println(Constant.SCHOOL_NAME);
        System.out.println(Constant.SCHOOL_NAME);
        System.out.println(Constant.SCHOOL_NAME);
        System.out.println(Constant.SCHOOL_NAME);
        System.out.println(Constant.SCHOOL_NAME);
    }
}
  • 关于常量的原理 ,同学们也可以了解一下:在程序编译后,常量会“宏替换 ”,出现常量的地方,全都会被替换为其记住的字面量。把代码反编译后,其实代码是下面的样子
image
image

3.抽象

同学们,接下来我们学习Java中一种特殊的类,叫抽象类。为了让同学们掌握抽象类,会先让同学们认识一下什么是抽象类以及抽象类的特点,再学习一个抽象类的常见应用场景。

3.1 认识抽象类 🍐 ✏️

认识抽象类

  • 在Java中有一个关键字叫abstract ,它就是抽象的意思,它可以修饰也可以修饰方法
-abstract修饰的类,就是抽象类
-abstract修饰的方法,就是抽象方法(不允许有方法体)

代码操作

接下来用代码来演示一下抽象类和抽象方法,观察特性

//abstract修饰类,这个类就是抽象类
public abstract class A{
    //abstract修饰方法,这个方法就是抽象方法
    public abstract void test();
}
  • 类的成员(成员变量、成员方法、构造器),类的成员都可以有。如下面代码
// 抽象类
public abstract class A {
    //成员变量
    private String name;
    static String schoolName;

    //构造方法
    public A(){

    }

    //抽象方法
    public abstract void test();

    //实例方法
    public String getName() {
        return name;
    }

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

总结

课堂作业

  1. 抽象的关键字是什么?可以修饰什么?🎤
  2. 结合下图说说,抽象类的特点
image
image

3.2 抽象类的好处 🍐

抽象类的好处

接下来我们用一个案例来说一下抽象类的应用场景和好处。需求如下图所示

1665028790780
1665028790780

分析需求发现,该案例中猫和狗都有名字这个属性,也都有叫这个行为,所以我们可以将共性的内容抽取成一个父类,Animal类,但是由于猫和狗叫的声音不一样,于是我们在Animal类中将叫的行为写成抽象的。

代码如下:

public abstract class Animal {
    private String name;

    //动物叫的行为:不具体,是抽象的
    public abstract void cry();

    public String getName() {
        return name;
    }

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

接着写一个Animal的子类,Dog类。代码如下

public class Dog extends Animal{
    public void cry(){
        System.out.println(getName() + "汪汪汪的叫~~");
    }
}

然后,再写一个Animal的子类,Cat类。代码如下

public class Cat extends Animal{
    public void cry(){
        System.out.println(getName() + "喵喵喵的叫~~");
    }
}

最后,再写一个测试类,Test类。

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握抽象类的使用场景和好处.
        Animal a = new Dog();
        a.cry();	//这时执行的是Dog类的cry方法
    }
}

再学一招,假设现在系统有需要加一个Pig类,也有叫的行为,这时候也很容易原有功能扩展。只需要让Pig类继承Animal,复写cry方法就行。

public class Pig extends Animal{
    @Override
    public void cry() {
        System.out.println(getName() + "嚯嚯嚯~~~");
    }
}

此时,创建对象时,让Animal接收Pig,就可以执行Pig的cry方法

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握抽象类的使用场景和好处.
        Animal a = new Pig();
        a.cry();	//这时执行的是Pig类的cry方法
    }
}

综上所述,我们总结一下抽象类的使用场景和好处👇

  1. 用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。

  2. 反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。

总结

课堂作业

  1. 运行上述的代码,体会一下抽象类的好处🎤

3.3 模板方法模式

模板方法模式

学习完抽象类的语法之后,接下来,我们学习一种利用抽象类实现的一种设计模式。先解释下一什么是设计模式?设计模式是解决某一类问题的最优方案

设计模式在一些源码中经常会出现,还有以后面试的时候偶尔也会被问到,所以在合适的机会,就会给同学们介绍一下设计模式的知识。

那模板方法设计模式解决什么问题呢?模板方法模式主要解决方法中存在重复代码的问题

代码操作

比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。

1665058597483
1665058597483

怎么解决上面的重复代码问题呢? 我们可以写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法,代码如下:

// 模板方法设计模式
public abstract class C {
    // 模板方法
    public final void sing(){
        System.out.println("唱一首你喜欢的歌:");

        doSing();

        System.out.println("唱完了!");
    }

    public abstract void doSing();
}

然后,写一个A类继承C类,复写doSing()方法,代码如下

public class A extends C{
    @Override
    public void doSing() {
        System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~");
    }
}

接着,再写一个B类继承C类,也复写doSing()方法,代码如下

public class B extends C{
    @Override
    public void doSing() {
        System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~");
    }
}

最后,再写一个测试类Test

public class Test {
    public static void main(String[] args) {
        // 目标:搞清楚模板方法设计模式能解决什么问题,以及怎么写。
        B b = new B();
        b.sing();
    }
}

综上所述:模板方法模式解决了多个子类中有相同代码的问题。具体实现步骤如下

  1. 第1步:定义一个抽象类,把子类中相同的代码写成一个模板方法。
  2. 第2步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。
  3. 第3步:子类继承抽象类,只需要父类抽象方法就可以了。

休息,休息,缓一下!!!
休息,休息,缓一下!!!

4. 接口

相关信息

同学们,接下来我们学习一个比抽象类抽象得更加彻底的一种特殊结构,叫做接口。在学习接口是什么之前,有一些事情需要给大家交代一下:Java已经发展了20多年了,在发展的过程中不同JDK版本的接口也有一些变化,所以我们在学习接口时,先以老版本为基础,学习完老版本接口的特性之后,再顺带着了解一些新版本接口的特性就可以了。

4.1 认识接口

认识接口

我们先来认识一下接口?Java提供了一个关键字interface,用这个关键字来定义接口这种特殊结构。格式如下

public interface 接口名{
    //成员变量(常量)
    //成员方法(抽象方法)
}

按照接口的格式,我们定义一个接口看看

public interface A{
    //这里public static final可以加,可以不加。
    public static final String SCHOOL_NAME = "黑马程序员";
    
    //这里的public abstract可以加,可以不加。
    public abstract void test();
}

写好A接口之后,在写一个测试类,用一下

public class Test{
    public static void main(String[] args){
        //打印A接口中的常量
        System.out.println(A.SCHOOL_NAME);
        
        //接口是不能创建对象的
        A a = new A();//报错了,为什么尼?
    }
}

我们发现定义好接口之后,是不能创建对象的。那接口到底什么使用呢? 需要我注意下面两点: 👇 👇

  • 接口是用来被类实现(implements)的,我们称之为实现类。
  • 一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类

比如,再定义一个B接口,里面有两个方法testb1(),testb2()

public interface B {
    void testb1();
    void testb2();
}

接着,再定义一个C接口,里面有两个方法testc1(), testc2()

public interface C {
    void testc1();
    void testc2();
}

然后,再写一个实现类D,同时实现B接口和C接口,此时就需要复写四个方法,如下代码

// 实现类
public class D implements B, C{
    @Override
    public void testb1() { }

    @Override
    public void testb2() { }

    @Override
    public void testc1() { }

    @Override
    public void testc2() { }
}

最后,定义一个测试类Test

public class Test {
    public static void main(String[] args) {
        // 目标:认识接口。
        System.out.println(A.SCHOOL_NAME);

        // A a = new A();
        D d = new D();
    }
}

总结

课堂作业

  1. 接口关键字是什么? 接口中的方法有什么特性? 接口中的成员变量有什么特点?🎤
  2. 接口有什么好处?

4.2 接口的案例

接口的案例

各位同学,关于接口的特点以及接口的好处我们都已经学习完了。接下来我们做一个案例,先来看一下案例需求.

1665102202635
1665102202635

代码操作

首先我们写一个学生类,用来描述学生的相关信息

public class Student {
    private String name;
    private char sex;
    private double score;

    public Student() {
    }

    public Student(String name, char sex, double score) {
        this.name = name;
        this.sex = sex;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

总结

课堂作业

  1. 结合上述业务,思考为什么使用接口设计?🎤

4.4 接口JDK8的新特性和其他细节

前言

各位同学,对于接口最常见的特性我们都学习完了。随着JDK版本的升级,在JDK8版本以后接口中能够定义的成员也做了一些更新,从JDK8开始,接口中新增的三种方法形式。

我们看一下这三种方法分别有什么特点?

public interface A {
    /**
     * 1、默认方法:必须使用default修饰,默认会被public修饰
     * 实例方法:对象的方法,必须使用实现类的对象来访问。
     */
    default void test1(){
        System.out.println("===默认方法==");
        test2();
    }

    /**
     * 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)
     *   实例方法:对象的方法。
     */
    private void test2(){
        System.out.println("===私有方法==");
    }

    /**
     * 3、静态方法:必须使用static修饰,默认会被public修饰
     */
     static void test3(){
        System.out.println("==静态方法==");
     }

     void test4();
     void test5();
     default void test6(){

     }
}

接下来我们写一个B类,实现A接口。B类作为A接口的实现类,只需要重写抽象方法就尅了,对于默认方法不需要子类重写。代码如下:

public class B implements A{
    @Override
    public void test4() {

    }

    @Override
    public void test5() {

    }
}

最后,写一个测试类,观察接口中的三种方法,是如何调用的

public class Test {
    public static void main(String[] args) {
        // 目标:掌握接口新增的三种方法形式
        B b = new B();
        b.test1();	//默认方法使用对象调用
        // b.test2();	//A接口中的私有方法,B类调用不了
        A.test3();	//静态方法,使用接口名调用
    }
}

综上所述:JDK8对接口新增的特性,有利于对程序进行扩展。

课后作业

🚩 1. 重点完成上述的课堂作业

  1. 晚自习第一节课的前30分钟,总结完毕之后,每个同学先必须梳理今日知识点 (记得写不知道的,以及感恩三件事);整理好的笔记可以发给组长,组长交给班长,意在培养大家总结的能力)

  2. 晚自习第一节课的后30分钟开始练习(记住:程序员是代码堆起来的):

    • 先要把今天的所有案例或者课堂练习,如果没练完的,练完他
    • 完成讲师发送在班级群中的作业,提交位置关注群里
    • 不懂要多问,问同学,问老师,问百度,都可以!! 独自解决分析问题,解决问题的能力-----编程思维!!
  3. 剩余的时间:预习第二天的知识,预习的时候一定要注意:

  • 预习不是学习,不要死看第二天的视频(很容易出现看了白看,为了看视频而看视频)
  • 预习看第二天的笔记,把笔记中标注重要的知识,可以找到预习视频,先看一遍,如果不懂的 ,记住做好标注。