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);
}
}
各个访问修饰符以及它们的可访问性如下表所示:
访问访问 | private | default(默认) | protected | public |
---|---|---|---|---|
同一个类 | 可访问 | 可访问 | 可访问 | 可访问 |
同一包中的其他类 | 不可访问 | 可访问 | 可访问 | 可访问 |
不同包中的子类 | 不可访问 | 不可访问 | 可访问 | 可访问 |
不同包中的非子类 | 不可访问 | 不可访问 | 不可访问 | 可访问 |
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);
}
}
运行结果如下:
接着,我们先new一个父类,然后用子类来接收,这里很明显是无法编译通过的。
我们尝试强转,如下:
此时不会显示有错误,但是运行的时候,会有编译错误,提示不能将父类强转为子类
但其实,并不是所有情况下父类都不能强转为子类的,如果我们原先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);
}
}
运行结果如下:
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();
}
}
运行结果如下:
2.2 多态的转型
在之前的继承章节中,我们提到父类和子类之间的转化,其实说的专业一点,叫作向上转型和向下转型。
2.2.1 向上转型
向上转型指父类的引用指向子类的对象。它有如下特点:
- 编译类型看左边,运行类型看右边
- 可以调用父类的所有成员(需要遵守访问权限)
- 不能调用子类的特有成员
- 运行效果看子类的具体实现
如下面代码所示,我们定义一个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();
}
}
结果如下:
学生类可以有自己的其他方法,如doHomework方法,父类不能调用子类的方法,如下面代码所示:
2.2.2 向下转型
本质:一个已经向上转型的子类对象,将父类引用转为子类引用
特点:
- 只能强制转换父类的引用,不能强制转换父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型的所有成员
首先是只能强制转换父类的引用,不能强制转换父类的对象。如下面代码所示:
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 向下转型成功");
}
}
结果如下:
父类的引用必须指向的是当前目标类型的对象,示例代码如下:
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 向下转型成功");
}
}
结果如下:
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
结果如下:
访问http://localhost:8080/pay?type=aliPay&userId=1&amount=100,结果如下:
上面的代码其实还可以优化一下,我们原先是在初始化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对应起来,然后我们可以直接使用,效果如下:
4. 参考文章
Java多态详解:https://blog.csdn.net/m0_67599274/article/details/124314210