面向对象高级二(多态)

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

什么是多态?
  • 多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。

多态的具体代码体现

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

对于方法编译类型看左边,运行类型看右边。(行为多态)

对于变量编译看左边,运行看左边。

多态的前提

  • 有继承/实现关系,存在父类引用子类对象;存在方法重写。
使用多态的好处
  • 在多态形式下,右边对象是解耦合的,更便于拓展和维护。
  • 定义方法时,使用父类类型的形参,可以接受一切子类对象,拓展性更强、更便利。
public static void main(String args){
    //好处1:可以实现解耦合,右边对象可以随时切换,后续业务随机改变。
    People p1 = new Student();
    p1.run();
    
    // 好处2:可以使用父类类型的变量作为形参,可以接受一切的子类对象
    Student s = new Student0);
    go(s);
    
    Teacher t = new Teacher();
    go(t);
}
public static void go(People p){	}

多态会产生一个问题:

  • 多态下不能使用子类的独有功能。
多态下的类型转换问题

类型转换

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

强制类型转换的注意事项

  • 存在继承/实现关系就可以在编译阶段进行强制类型转换, 编译阶段不会报错。
  • 运行时,发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误。

强转前,Java建议:

  • 使用instanceof关键字,判断当前对象的真实类型,再进行强转。

    eg:p instanceof Student

public static void main(String args){
    //好处1:可以实现解耦合,右边对象可以随时切换,后续业务随机改变。
    People p1 = new Student();
    p1.run();
    
    // 好处2:可以使用父类类型的变量作为形参,可以接受一切的子类对象
    Student s = new Student0);
    go(s);
    
    Teacher t = new Teacher();
    go(t);
}
public static void go(People p){
    if(p instanceof Student){
        Student s = (Student) p;
        s.test();
    }else if(p instanceof Teacher){
        Teacher t = (Teacher) p;
        p.teach();
    }
}

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

final

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

变量:

  1. 局部变量
  2. 成员变量
    1. 静态成员变量
    2. 实例成员变量

常量: static final修饰的成员变量,建议名称全部大写,多个单词下划线连接。

final修饰变量的注意事项

  • final修饰基本类型的变量,变量存储得数据不能被改变。
  • final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。
常量
  • 使用static final修饰的成员变量——常量;
  • 作用:通常用于记录系统的配置信息。
public class Constant{
    public static final String SCHOOL_NALE = "XX教育";
}

注意:常量名的命名规范:建议使用大写英文单词,多个单词使用下划线连接。

使用常量记录系统配置信息的优势、执行原理

  • 代码可读性更好,可维护性也更好。
  • 程序编译后,常量会被“宏替换”:出现倡廉的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的。

抽象类

  • 在java中有个关键字:abstract,他就是抽象得意思,可以用它修饰类,成员方法。
  • abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法。
修饰符 abstract class 类名{
    修饰符 abstract 返回值类型 方法名称(形参列表);
}

public abstract class A{
    //抽象方法:必须是abstract修饰,只有方法签名,不能有方法主体。
    public abstract void test();
}

抽象类的注意事项、特点

  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
  • 类该有的成员(成员变量、方法、构造器)抽象类都可以有。
  • 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
  • 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
抽象类的场景和好处
  • 父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现,我们设计这样的抽象类,就是为了更好的支持多态。
抽象类的常见应用场景:模板方法设计模式

模板方法设计模式解决了什么问题:解决方法中存在重复代码的问题。

eg:

public class A{
    //...
    public void sing(){
        //代码一样
        
        //代码不同-----
        
        //代码一样
    }
}

public class B{
    //...
    public void sing(){
        //代码一样
        
        //代码不同------
        
        //代码一样
    }
}

模板方法设计模式的写法:

  1. 定义一个抽象类
  2. 在里面定义两个方法
    1. 一个是模板方法:把相同代码放里面去(这个方法建议使用final修饰)。
    2. 一个是抽象方法:具体实现交给子类完成。

为什么用final修饰模板方法:

  1. 模板方法是给对象直接使用的,不能被子类重写。
  2. 一旦子类重写了模板方法,模板方法就失效了。

接口

认识接口

java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构:接口。

接口中没有构造器,没有代码块这些东西。接口也不能创建对象。

接口中定义的成员变量默认是常量,接口中定义的方法默认是抽象方法。

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

注意:接口不能创建对象;接口是用来被实现(implements)的,实现接口的类称为实现类。

修饰符 class 实现类 implements 接口1,接口2,接口3,……{
    
}
  • 一个类可以实现多个接口(接口可以理解成干爹),实现类可以实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。
接口的好处(重点)
  • 弥补了类单继承的不足,一个类同时可以实现多个接口。
  • 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。

小结:

  1. 使用接口有什么好处?
  • 可以解决类单继承问题,通过接口,我们可以让一个类有一个亲爹的同事,还可以找多个干爹去拓展自己的功能。
  1. 为什么我们要通过接口,也就是去找干爹,来拓展自己的功能?
  • 因为通过接口去找干爹,别人通过implements的接口,就可以显性的知道你是谁,从而可以放心的把你当作谁来用了。
  1. 使用接口的第二个好处是什么?
  • 一个类我们说可以实现多个接口,同样,一个接口也可以被多个类实现的。这样做的好处是我们的程序就可以面向接口编程,这样我们程序员可以很方便的灵活切换各种业务的实现。
接口的应用案例:班级学生信息管理模块的开发
需求:
设计一个班级学生的信息管理模块:学生数据有:姓名、性别、成绩
功能1:要求打印出全班学生的信息。功能2:要求打印出全班学生的平均成绩。

# 注意!以上功能的业务实现是有多套方案的,比如:

第一套方案:能打印出班级全部学生的信息;能打印班级全部学生的平均分。
第二套方案:能打印出班级全部学生的信息(包含男女人数);能打印班级全部学生的平均分(要求去掉最高分、最低分)。

# 要求:系统可以支持灵活的切换这些实现方案。

编写思路:

首先我们要创建学生类(Student),使用班级管理类(ClassManger)管理学生,在班级管理类中有两个方法,一个用来输出学生信息,一个用来输出平均成绩。关于输出有两种实现形式,但是都是这两个方法,所以我们先创建一个学生操作接口(StudentOperator)其中有printInfo()和printAverageScore()让继承类来实现,然后创建两种实现方式–两个类(StudentOperatorImpl1)(StudentOperatorImpl2)。最后我们使用Test来实现整体的功能。

Test.java

public class Test {
    public static void main(String[] args) {
        ClassManger classManger = new ClassManger();
        classManger.printInfo();
        classManger.printAverageScore();
    }
}

Student.java

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

ClassManger.java

import java.util.ArrayList;

public class ClassManger {
    private ArrayList<Student> students = new ArrayList<>();
    //private StudentOperatorImpl1 studentOperator = new StudentOperatorImpl1();
    private StudentOperatorImpl2 studentOperator = new StudentOperatorImpl2();

    public ClassManger() {
        students.add(new Student("name1", '男', 99));
        students.add(new Student("name2", '女', 91));
        students.add(new Student("name3", '男', 92));
        students.add(new Student("name4", '女', 94));
        students.add(new Student("name5", '男', 100));
    }

    //打印全部学生的信息
    public void printInfo() {
        studentOperator.printAllInfo(students);
    }

    //打印班级全部学生的平均分。
    public void printAverageScore() {
        studentOperator.printAverageScore(students);
    }
    
}

StudentOpeartpr.java

import java.util.ArrayList;

public interface StudentOperator {
    void printAllInfo(ArrayList<Student> students);

    void printAverageScore(ArrayList<Student> students);
}

StudentOperatprImpl1.java

import java.util.ArrayList;

public class StudentOperatorImpl1 implements StudentOperator {
    @Override
    public void printAllInfo(ArrayList<Student> students) {
        int countM = 0;
        int countW = 0;

        System.out.println("==========全班全部学生信息==========");
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            System.out.println("姓名:" + s.getName() + ",性别:" + s.getSex() + ",成绩:" + s.getScore());
            if (s.getSex() == '男') {
                countM += 1;
            } else {
                countW += 1;
            }
        }

        System.out.println("男生:" + countM + "人,女生:" + countW + "人");
        System.out.println("班级总人数" + students.size());

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

    @Override
    public void printAverageScore(ArrayList<Student> students) {
        double allScore = 0;//总成绩
        double maxScore = students.get(0).getScore();//最高分
        double minScore = students.get(0).getScore();//最低分
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            double score = s.getScore();
            allScore += score;//总成绩
            //判断最低分
            if (score < minScore) {
                minScore = score;
            }
            //判断最高分
            if (score > maxScore) {
                maxScore = score;
            }
        }
        System.out.println("最高分:" + maxScore + "最低分:" + minScore);
        System.out.println("平均分:" + (allScore - maxScore - minScore) / (students.size() - 2));
    }
}

StudentOperatprImpl1.java

import java.util.ArrayList;

public class StudentOperatorImpl2 implements StudentOperator {
    @Override
    public void printAllInfo(ArrayList<Student> students) {
        System.out.println("=========全班全部学生信息=========");
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            System.out.println("姓名:" + s.getName() + ",性别:" + s.getSex() + ",成绩:" + s.getScore());
        }
        System.out.println("---------------------------------");
    }

    @Override
    public void printAverageScore(ArrayList<Student> students) {
        double allScore = 0;
        for (int i = 0; i < students.size(); i++) {
            allScore += students.get(i).getScore();
        }
        System.out.println("平均分:" + (allScore) / students.size());
    }
}
JDK8开始,接口新增了三种形式的方法:
public interface A {
    /**
     * 1.默认方法(实例方法):使用default修饰,默认会被加上public修饰
     * 注意:只能使用接口的实现类对象调用
     */
    default void test1(){
        //...
    }

    /**
     * 2. 私有方法:必须用private修饰(JDK 9开始才支持)
     * 在此接口类中调用
     */
    private void test2(){
        //...
    }

    /**
     * 3.类方法(静态方法):使用static修饰,默认会被加上public修饰。
     * 注意:只能用接口名来调用。
     */
    static void test3(){
        //...
    }
}

JDK8开始,接口中为什么要新增这些方法?

  • 增强了接口的能力,更便于项目的拓展和维护。

接口的多继承

一个接口可以同时继承多个接口。

接口多继承的作用:便于实现类去实现。

接口其他注意事项:

  1. 一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。
  2. 一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持实现。
  3. 一个类继承了父类,有同时实现了接口,父类中和接口中有同名的默认方法,实现类会有限使用父类的。
  4. 一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黎丶辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值