9.面向对象之多态
一:多态详解
什么是多态
封装,继承与多态是面向对象的三大基本特征,其中多态更是面向对象的灵魂。
那么,什么是多态?
多态首先是建立在继承的基础之上的。简单来说,多态就是:用父类的引用指向子类的对象,其既可以表现出子类独有的状态(通过 override 父类的方法);也可以表现出父类的状态。
来看示例程序:
public class Polymorphic {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal cat = new Cat();
cat.speak(); // 重写父类的speak方法 表现出了子类独有的状态
cat.sayHello();// 直接调用父类的sayHello方法,表现出了父类的状态
}
}
class Animal {
public void speak(){
System.out.println("我是一个动物");
}
public void sayHello(){
System.out.println("hello");
}
}
class Cat extends Animal{
@Override
public void speak() {
System.out.println("我是一只猫");
}
}
该程序输出的结果为:
我是一只猫
hello
静态绑定与动态绑定
我们先来说明下什么是方法的参数,什么是方法的接收者,举个栗子:
obj.f(param);
对于上述的方法调用,obj 是调用方法的对象,也叫做方法的接受者;param 则是方法的参数。
多态遵循一条规则:方法的参数为静态绑定,方法接收者为动态绑定。换句话说,多态只对方法的接收者生效;再换句话说,多态只选择方法接收者的类型,不选择参数的类型。
我们来看示例程序:
ParamBase
public class ParamBase {
}
ParamSub
public class ParamSub extends ParamBase{
}
Base
public class Base {
public void print(ParamBase param) {
System.out.println("I am Base,the param is ParamBase.");
}
public void print(ParamSub param) {
System.out.println("I am Base,the param is ParamSub.");
}
}
Sub
public class Sub extends Base {
@Override
public void print(ParamBase param) {
System.out.println("I am Sub,the param is ParamBase.");
}
@Override
public void print(ParamSub param) {
System.out.println("I am Sub,the param is ParamSub.");
}
}
Main
public class Main {
public static void main(String[] args) {
Base obj = new Sub();
ParamBase param = new ParamSub();
obj.print(param);
}
}
该程序运行的结果为:
I am Sub,the param is ParamBase.
解释:
obj.print(param);
obj 为方法的接收者,也就是动态绑定,它实际上是一个 Sub 类的对象;
param 是方法的参数,也就是静态绑定,传入到方法中的参数类型为 ParamBase。
所以,该程序运行后会输出:I am Sub,the param is ParamBase.
编译时的多态性与运行时的多态性
多态分为编译时的多态性和运行时的多态性,并且静态方法不具有多态性
- 多态中成员变量的访问特点:编译时看左边,运行时看左边
示例程序:
public class Polymorphic {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal cat = new Cat();
System.out.println(cat.name);
}
}
class Animal {
String name = "animal";
}
class Cat extends Animal{
String name = "cat";
}
程序输出结果为:
animal
解释:
在程序编译时期,首先 JVM 会看向 Animal cat = new Cat();
这句等号左边的父类引用是否有该变量(name)的定义,如果有则编译成功,如果没有则编译失败;在程序运行时期,对于成员变量,JVM 仍然会看左边的所属类型,获取的是引用父类的变量结果 。
- 多态中成员方法调用的特点:编译看左边,运行看右边
示例程序:
public class Polymorphic {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal cat = new Cat();
cat.speak(); // 重写父类的speak方法 表现出了子类独有的状态
}
}
class Animal {
public void speak(){
System.out.println("我是一个动物");
}
}
class Cat extends Animal{
@Override
public void speak() {
System.out.println("我是一只猫");
}
}
程序输出结果:
我是一只猫
解释:
在程序编译期,首先JVM 会看向 Animal cat = new Cat();
等号左边所属类型是否有该方法,如果有则编译成功,如果没有则编译失败;在程序运行时,则是要看等号右边的对象是如何实现该方法的,最终输出为右边对象对这个方法重写后的结果。
多态总结
实例方法默认是多态的
在运行时根据 this 来决定调用哪个方法
静态方法没有多态
参数静态绑定,接受者动态绑定
二:设计模式实战:策略模式
策略模式案例:打折策略
我们有一个打折策略的系统:
public class PriceCalculator {
public static int calculatePrice(String discountStrategy, int price, User user) {
switch (discountStrategy) {
case "NoDiscount":
return price;
case "Discount95":
return (int) (price * 0.95);
case "OnlyVip": {
if (user.isVip()) {
return (int) (price * 0.95);
} else {
return price;
}
}
default:
throw new IllegalStateException("Should not be here!");
}
}
}
该打折系统中共有三种策略,无折扣,全场 95 折,以及 VIP 用户的折扣。该代码的最大问题是,如果我们要新增
打折方案,就需要新增一个 case,如果后面陆陆续续增加几十种打折策略,就会使我们的代码变得冗长。
并且,我们具体的策略应该和具体的业务代码分开,这样才能降低代码之间的耦合,使得我们的代码变得易于维护。
我们可以使用策略模式来改进我们的代码:
DiscountStrategy
public class DiscountStrategy {
public int discount(int price, User user) {
throw new UnsupportedOperationException();
}
}
NoDiscountStrategy
public class NoDiscountStrategy extends DiscountStrategy {
// NoDiscountStrategy 不打折
@Override
public int discount(int price, User user) {
return price;
}
}
Discount95Strategy
public class Discount95Strategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
return (int) (price * 0.95);
}
}
OnlyVipDisountStrategy
public class OnlyVipDiscountStrategy extends DiscountStrategy {
// OnlyVipDiscountStrategy 只有VIP打95折,其他人保持原价
@Override
public int discount(int price, User user) {
if (user.isVip()) {
return (int) (price * 0.95);
} else {
return price;
}
}
}
PriceCalculator
public class PriceCalculator {
public static int calculatePrice(DiscountStrategy strategy, int price, User user) {
return strategy.discount(price, user);
}
public static void main(String[] args) {
calculatePrice(new NoDiscountStrategy(), 80, User.vip("Li Lei"));
calculatePrice(new OnlyVipDiscountStrategy(), 80, User.vip("Jack"));
}
}
我们看到,PriceCalculator 中的代码变得非常简洁,我们可以直接添加一个 DiscountStrategy 的子类来新增一个策略,并且我们代码中的业务逻辑和打折策略完全分开,实现了代码的低耦合。
策略模式得益于 Java 多态的好处。
JDK线程池中的策略:ThreadPoolExecutor
Java 为多线程场景提供了线程池 ThreadPoolExecutor,下面是一个线程池的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
该构造方法中就使用了策略模式,RejectedExecutionHandler 对应了可以传入不同的拒绝策略:
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
这几种策略(类)都实现了 RejectedExecutionHandler 接口,这也是 JDK 中策略模式的实践。