继承和多态

Java继承和多态

Java作为一个面向对象的语言,有三大特性:封装、继承和多态。封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是:代码重用。而多态则是为了实现另一个目的——接口重用。

1. 继承

1.1 构造函数和属性访问

通过继承,我们可以扩展已存在的代码模块,以下面代码为例,我们首先创建一个Person类,该类有name和age这两个属性,然后创建一个Student,继承自Person类,对于Student类的构造函数,可以通过super来构造和父类相关的内容,然后我们的Student类,也可以使用Person类中的方法,比如下面我们的Student类使用了父类的getName和getAge方法。

package com.young.demo;

public class Person {
    private String name;
    private Integer age;
    
    public Person(){
        
    }
    
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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



package com.young.demo;

public class Student extends Person{
    private String grade;

    public Student() {
        super();
    }

    public Student(String name, Integer age, String grade) {
        super(name, age);
        this.grade = grade;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }
}



package com.young.demo;

public class Test1 {
    public static void main(String[] args) {
        Person person = new Person("cxy", 21);
        Student student = new Student("cxy", 11, "大四");
        System.out.println(student);
        System.out.println(student.getName() + " " + student.getAge());
    }
}

但有时候我们希望子类可以直接使用父类的一些属性,而在上面我们将父类的属性访问符设置为private,因此子类不可以直接使用,我们可以通过将父类的属性访问符设置为protected,这样子类就可以直接访问父类的属性,如下面代码所示:

package com.young.demo;

public class Person {
    protected String name;
    protected Integer age;

    public Person(){

    }

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


    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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





package com.young.demo;

public class Student extends Person{
    private String grade;

    public Student() {
        super();
    }

    public Student(String name, Integer age, String grade) {
        super(name, age);
        this.grade = grade;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    public void introduce() {
        System.out.println("My name is " + this.name +
                ", my age is " + this.age +
                ", my grade is " + this.grade);
    }
}

各个访问修饰符以及它们的可访问性如下表所示:

访问访问privatedefault(默认)protectedpublic
同一个类可访问可访问可访问可访问
同一包中的其他类不可访问可访问可访问可访问
不同包中的子类不可访问不可访问可访问可访问
不同包中的非子类不可访问不可访问不可访问可访问
1.2 抽象类

有时候,父类可能只是一个抽象的概念,在现实生活中,没有具体的形式,比如说动物、水果,这些都是我们人为进行定义的概念,对于这些父类,我们可以把他们以抽象类的方式定义下来。以动物为例,每个动物都会进行移动,都需要进食,所以我们定义一个动物抽象类,它有move和eat这两个抽象方法。(抽象类需要用到abstract来进行定义,除此之外,对于抽象类,一般我们在命名的时候,要加上Abstract来表示这是一个抽象类)

package com.young.demo.demo2;

public abstract class AbstractAnimal {
    abstract void move();
    abstract void eat();
}

然后我们定义具体的动物,这个动物需要实现这些具象的行为,如下面代码所示

package com.young.demo.demo2;

public class Bird extends AbstractAnimal{
    @Override
    void move() {
        System.out.println("Fly -----------");
    }

    @Override
    void eat() {
        System.out.println("Eat glass");
    }
}



package com.young.demo.demo2;

public class Pig extends AbstractAnimal{
    @Override
    void move() {
        System.out.println("Run ------------");
    }

    @Override
    void eat() {
        System.out.println("Eat anything");
    }
}

对于抽象类的方法,如果有abstract修饰,子类则必须实现对应的方法,而对于一些通用的方法,子类可以直接复用父类的,不需要重复实现。以动物为例,每一个动物都需要呼吸,因此我们可以在动物类中定义并实现对应的方法,而子类不需要重新实现,如下面代码所示:

package com.young.demo.demo2;

public abstract class AbstractAnimal {
    abstract void move();
    abstract void eat();
    
    public void breathe() {
        System.out.println("Breathing by nose");
    }
}

当然,子类也可以重写父类已经实现的方法,比如鱼是通过鱼鳃进行呼吸的,那么我们可以重新对应的breathe方法:

package com.young.demo.demo2;

public class Fish extends AbstractAnimal{
    @Override
    void move() {
        System.out.println("Swim-------------");
    }

    @Override
    void eat() {
        System.out.println("Eat small fish");
    }

    @Override
    public void breathe() {
        System.out.println("通过鱼鳃呼吸-------");
    }
}

1.3 接口

Java不支持多继承,但是可以通过接口来扩展对应的能力。接口,可以看作更细粒度的一个父类,父类一般表示的是共有的一些特性,而接口,表示的是我们这个类拥有的一些能力,以程序员为例,程序员首先是一个人,他拥有人类所具有的各种属性,而同时,程序员又具有编程能力,编程能力又不是所有人都会的,那么我们就可以将人作为程序员的父类,而编程能力,就是程序员所拥有的具体能力,可以看作一个接口,代码如下所示:

package com.young.demo.demo3;

public class Person {
    private String name;
    public Person(String name ){
        this.name = name;
    }
}



package com.young.demo.demo3;

public interface Programming {
    void program();
}



package com.young.demo.demo3;

public class Programmer extends Person implements Programming{
    public Programmer(String name) {
        super(name);
    }

    @Override
    public void program() {
        System.out.println("Java is the best language");
    }
}

刚才说过,接口可以看作更细粒度的父类,但是,我们在定义接口的时候,其实也不能把接口定义得太细粒度,以Programming为例,它首先有program方法,就是编码能力,除此之外,每一个程序员都应该会增删改查,增删改查也是Programming的内容,那么我们就不应该定义一个增删改查的接口,而是在Programming定义对应的方法,如下所示:

package com.young.demo.demo3;

public interface Programming {
    void program();
    void crud();
}




package com.young.demo.demo3;

public class Programmer extends Person implements Programming{
    public Programmer(String name) {
        super(name);
    }

    @Override
    public void program() {
        System.out.println("Java is the best language");
    }

    @Override
    public void crud() {
        System.out.println("CRUD");
    }
}

在Java8之前,接口只能定义对应的方法,不能有具体的实现,但是在Java8之后,我们的接口方法也可以有默认的实现,对应默认实现,需要使用default进行修饰。如下所示:

package com.young.demo.demo3;

public interface Programming {
    void program();
    void crud();
    
    default void click() {
        System.out.println("敲击键盘进行编码");
    }
}

1.4 父类与子类的转化

子类可以转为父类,但是父类不能转为子类。如下图所示,我们可以通过父类来接收new 出来的子类。

package com.young.demo.demo4;

import lombok.Data;

@Data
public class Person {
    private Integer age;
    private String name;
}


package com.young.demo.demo4;

import lombok.Data;

@Data
public class Student extends Person{
    private String grade;
}


package com.young.demo.demo4;

public class Test1 {
    public static void main(String[] args) {
        Person student1 = new Student();
        System.out.println(student1);

        Student student2 = new Student();
        Person person2 = student2;
        System.out.println(person2);
    }
}

运行结果如下:
avatar
接着,我们先new一个父类,然后用子类来接收,这里很明显是无法编译通过的。
avatar
我们尝试强转,如下:
avatar
此时不会显示有错误,但是运行的时候,会有编译错误,提示不能将父类强转为子类
avatar
但其实,并不是所有情况下父类都不能强转为子类的,如果我们原先new了一个子类,然后用父类接收,接着再将父类转为对应的子类,此时是没有问题的,如下面代码所示:(这个其实是多态的内容,后面会讲)

package com.young.demo.demo4;

public class Test1 {
    public static void main(String[] args) {
        Person person = new Student();
        Student student = (Student) person;
        System.out.println(student);
    }
}

运行结果如下:
avatar

2. 多态

2.1 多态介绍

多态是方法或对象具有多种形态。它的前提是两个对象(类)之间存在继承关系,也就是说它是建立在继承的基础上的。
一个对象的编译类型与运行类型可以不一致,编译类型在定义对象时就确定了,不能改变,而运行类型是可以变化的。
以下面代码为例,我们定义一个Person类为父类,然后定义Student类和Teacher类为子类,Person类有一个mission方法,然后Student类和Teacher类都重写它,如下面代码所示:

package com.young.demo.deom;

public abstract class Person {
    public abstract void mission();
}



package com.young.demo.deom;

public class Student extends Person {
    @Override
    public void mission() {
        System.out.println("学生在学习");
    }
}



package com.young.demo.deom;

public class Teacher extends Person {
    @Override
    public void mission() {
        System.out.println("老师在教学生");
    }
}



package com.young.demo.deom;

public class Test1 {
    public static void main(String[] args) {
        Person student = new Student();
        Person teacher = new Teacher();
        student.mission();
        teacher.mission();
    }
}

运行结果如下:
avatar

2.2 多态的转型

在之前的继承章节中,我们提到父类和子类之间的转化,其实说的专业一点,叫作向上转型和向下转型。

2.2.1 向上转型

向上转型指父类的引用指向子类的对象。它有如下特点:

  1. 编译类型看左边,运行类型看右边
  2. 可以调用父类的所有成员(需要遵守访问权限)
  3. 不能调用子类的特有成员
  4. 运行效果看子类的具体实现
    如下面代码所示,我们定义一个Person父类和一个Student子类,子类重写父类的mission方法,然后子类还有一个doHomework方法
package com.young.demo.deom;

public  class Person {
    public  void mission(){
        System.out.println("Person 默认实现");
    }
}


package com.young.demo.deom;

public class Student extends Person {
    @Override
    public void mission() {
        System.out.println("学生在学习");
    }

    public void doHomework(){
        System.out.println("做作业");
    }
}



package com.young.demo.deom;

public class Test1 {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Student();
        person1.mission();
        person2.mission();
    }
}

结果如下:
avatar
学生类可以有自己的其他方法,如doHomework方法,父类不能调用子类的方法,如下面代码所示:
avatar

2.2.2 向下转型

本质:一个已经向上转型的子类对象,将父类引用转为子类引用
特点:

  1. 只能强制转换父类的引用,不能强制转换父类的对象
  2. 要求父类的引用必须指向的是当前目标类型的对象
  3. 当向下转型后,可以调用子类类型的所有成员
    首先是只能强制转换父类的引用,不能强制转换父类的对象。如下面代码所示:
package com.young.demo.deom;

public class Test1 {
    public static void main(String[] args) {
        Person person1 = new Student();
        Person person2 = new Person();

        Student student1 = (Student) person1;
        System.out.println("student 向下转型成功");

        Student student2 = (Student) person2;
        System.out.println("student 向下转型成功");
    }
}

结果如下:
avatar
父类的引用必须指向的是当前目标类型的对象,示例代码如下:

package com.young.demo.deom;

public class Test1 {
    public static void main(String[] args) {
        Person person1 = new Student();
        Person person2 = new Teacher();

        Student student1 = (Student) person1;
        System.out.println("student 向下转型成功");

        Student student2 = (Student) person2;
        System.out.println("student 向下转型成功");
    }
}

结果如下:
avatar

3. 应用

继承和多态在项目中经常被使用到,以支付为例,我们定义一个支付接口,然后这个支付接口有微信支付和支付宝支付这两个实现类,示例代码如下:
首先定义一个支付接口:

package com.young.demo;

public interface PayTool {
    /**
     * 风控验证
     */
    boolean riskProcess();

    /**
     * 支付
     */
    boolean pay(Integer amount);

    /**
     * 支付类型
     */
    String getType();
}

支付工具类型枚举类

package com.young.demo;

public enum PayToolEnum {
    WECHAT("wechat"),
    ALIPAY("aliPay");

    private String name;

    private PayToolEnum(String name) {
        this.name = name;
    }

    public final String getName(){
        return this.name;
    }
}

对应的支付工具子类

package com.young.demo;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class WechatPayTool implements PayTool{
    @Override
    public boolean riskProcess() {
        System.out.println("微信验证============");
        return true;
    }

    @Override
    public boolean pay(Integer amount) {
        System.out.println("微信支付" + amount + "元=============");
        return true;
    }

    @Override
    public String getType() {
        return PayToolEnum.WECHAT.getName();
    }

    @PostConstruct
    public void init(){
        PayToolFactory.addPayTool(getType(), this);
    }
}




package com.young.demo;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class AliPayTool implements PayTool{
    @Override
    public boolean riskProcess() {
        System.out.println("支付宝验证============");
        return true;
    }

    @Override
    public boolean pay(Integer amount) {
        System.out.println("支付宝支付" + amount + "元============");
        return true;
    }

    @Override
    public String getType() {
        return PayToolEnum.ALIPAY.getName();
    }

    @PostConstruct
    public void init() {
        PayToolFactory.addPayTool(getType(), this);
    }
}

支付工具工厂类

package com.young.demo;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PayToolFactory {
    private static final Map<String, PayTool> payToolFactory = new ConcurrentHashMap<>();

    public static void addPayTool(String type, PayTool payTool) {
        payToolFactory.put(type, payTool);
    }

    public static PayTool getPayTool(String type) {
        return payToolFactory.get(type);
    }
}

request类和dto类

package com.young.demo;

import lombok.Data;

//请款请求类 
@Data
public class CaptureRequest {
    private String type;
    private Integer amount;
    private Integer userId;
}


package com.young.demo.dto;

import lombok.Data;

@Data
public class Money {
    private Integer amount;
    private String currency;
}


package com.young.demo.dto;

import lombok.Data;

@Data
public class UserDTO {
    private Integer id;
    private String name;
    private Boolean isUsable;
}




package com.young.demo;

import com.young.demo.dto.Money;
import com.young.demo.dto.UserDTO;
import lombok.Data;

@Data
public class CaptureModel {
    private PayTool payTool;
    private UserDTO user;
    private Money money;

    public String capture() {
        Integer amount = this.money.getAmount();
        payTool.pay(amount);
        return "用户" + user.getName() + "使用" + payTool.getType() + "支付了" + amount + "元";
    }

    public CaptureModel (PayTool payTool, UserDTO userDTO, Money money) {
        this.payTool = payTool;
        this.user = userDTO;
        this.money =money;
    }

    public static  CaptureModel of(PayTool payTool, UserDTO userDTO, Money money) {
        System.out.println("前置的一些校验==============");
       return new CaptureModel(payTool, userDTO, money);
    }
}


controller类

package com.young.demo.controller;

import com.young.demo.CaptureModel;
import com.young.demo.CaptureRequest;
import com.young.demo.PayTool;
import com.young.demo.PayToolFactory;
import com.young.demo.dto.Money;
import com.young.demo.dto.UserDTO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PayController {
    @GetMapping("/pay")
    public String pay(CaptureRequest captureRequest) {
        String type = captureRequest.getType();
        PayTool payTool = PayToolFactory.getPayTool(type);
        UserDTO userDTO = getUserDTO(captureRequest.getUserId());
        Money money = getMoney(captureRequest.getAmount());
        CaptureModel captureModel = CaptureModel.of(payTool, userDTO, money);
        return captureModel.capture();
    }

    private Money getMoney(Integer amount) {
        Money money = new Money();
        money.setAmount(amount);
        money.setCurrency("CNY");
        return money;
    }

    private UserDTO getUserDTO(Integer userId) {
        UserDTO userDTO = new UserDTO();
        userDTO.setId(userId);
        userDTO.setIsUsable(true);
        userDTO.setName("cxy");
        return userDTO;
    }
}

访问http://localhost:8080/pay?type=wechat&userId=1&amount=100
结果如下:
avatar
访问http://localhost:8080/pay?type=aliPay&userId=1&amount=100,结果如下:
avatar
上面的代码其实还可以优化一下,我们原先是在初始化bean后,将bean加到对应的支付工具工厂类中进行管理,但是这样每次添加一个支付工具类后都要显式地将类加入到工厂类,容易遗漏掉。我们先添加一个常量类如下:

package com.young.demo;

public class PayToolConstant {
    public static final String WECHAT = "wechat";
    public static final String ALIPAY = "aliPay";
}

然后修改WechatPayTool和AliPayTool

package com.young.demo;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component(PayToolConstant.ALIPAY)
public class AliPayTool implements PayTool{
    @Override
    public boolean riskProcess() {
        System.out.println("支付宝验证============");
        return true;
    }

    @Override
    public boolean pay(Integer amount) {
        System.out.println("支付宝支付" + amount + "元============");
        return true;
    }

    @Override
    public String getType() {
        return PayToolEnum.ALIPAY.getName();
    }


}



package com.young.demo;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component(PayToolConstant.WECHAT)
public class WechatPayTool implements PayTool{
    @Override
    public boolean riskProcess() {
        System.out.println("微信验证============");
        return true;
    }

    @Override
    public boolean pay(Integer amount) {
        System.out.println("微信支付" + amount + "元=============");
        return true;
    }

    @Override
    public String getType() {
        return PayToolEnum.WECHAT.getName();
    }

}

如上面代码所示,我们在component注解中,加上这个bean对应的名称,接着,我们修改controller如下:

package com.young.demo.controller;

import com.young.demo.CaptureModel;
import com.young.demo.CaptureRequest;
import com.young.demo.PayTool;
import com.young.demo.PayToolFactory;
import com.young.demo.dto.Money;
import com.young.demo.dto.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class PayController {
    @Autowired
    private Map<String, PayTool> payToolMap;

    @GetMapping("/pay")
    public String pay(CaptureRequest captureRequest) {
        String type = captureRequest.getType();
        PayTool payTool = payToolMap.get(type);
        UserDTO userDTO = getUserDTO(captureRequest.getUserId());
        Money money = getMoney(captureRequest.getAmount());
        CaptureModel captureModel = CaptureModel.of(payTool, userDTO, money);
        return captureModel.capture();
    }

    private Money getMoney(Integer amount) {
        Money money = new Money();
        money.setAmount(amount);
        money.setCurrency("CNY");
        return money;
    }

    private UserDTO getUserDTO(Integer userId) {
        UserDTO userDTO = new UserDTO();
        userDTO.setId(userId);
        userDTO.setIsUsable(true);
        userDTO.setName("cxy");
        return userDTO;
    }
}

如上面代码所示,我们可以直接使用一个payToolMap,这个map中将bean的名称和对应的paytoolbean对应起来,然后我们可以直接使用,效果如下:
avatar

4. 参考文章

Java多态详解:https://blog.csdn.net/m0_67599274/article/details/124314210

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值