咱都知道Java是面向对象的,多态是它的核心特性。我觉得多态最有意思的地方在于「晚绑定」——就像拆盲盒,不到最后一步不知道里面是啥。
举个栗子🌰:
你养了只神奇宝贝,代码写成PocketMon pocketMon = new Pikaqiu();
,调用pocketMon.releaseSkill()
时,你知道是「电击」。但如果代码改成这样:
Properties pro = new Properties();
pro.load(new FileInputStream("pocketmon.properties"));
PocketMon pocketMon = (PocketMon) Class.forName(pro.getProperty("nextPocketMon")).newInstance();
pocketMon.releaseSkill();
这时候你猜它会放啥技能?看pocketmon.properties
里写的是「皮卡丘」?但运行时我可能偷偷改成「喷火龙」——JVM不到执行这行代码,根本不知道具体是哪只宝可梦在放技能。
这就是多态的魅力:代码写死了,但实际执行时的行为由运行时的对象决定。原理上,JVM得在运行时查方法表,匹配当前对象的具体实现。所以理论上,多态的性能比直接写死(早绑定)差点——毕竟得「现查」嘛。但多态是设计模式的灵魂,为了灵活性,这点性能牺牲大部分时候都值。
方法多态:给可乐鸡翅留个「自定义步骤」
之前山寨Stream API时,我生造了个「方法多态」的概念。其实就是用函数式接口把「不确定的步骤」占个坑,让调用者自己填逻辑——这更贴近函数式编程的思路。
举个做菜的例子:做可乐鸡翅,关键步骤是「放鸡翅」和「倒可乐」,但顺序因人而异。有人喜欢先放鸡翅再倒可乐,有人反过来。
要是按传统写法,步骤全写死:
public static CokaChickenWing cook(Chicken chicken, Coka coka) {
1. 放油、放姜;
2. 放鸡翅;
3. 倒可乐;
return 可乐鸡翅;
}
但众口难调啊!这时候可以用「方法多态」——定义个函数式接口占坑,让调用者自己决定步骤:
// 定义接口:占住「步骤2-3」的坑
@FunctionalInterface
interface TwoStep {
void execute(Chicken chicken, Coka coka);
}
// 通用烹饪方法
public static CokaChickenWing cook(Chicken chicken, Coka coka, TwoStep twoStep) {
1. 放油、放姜;
twoStep.execute(chicken, coka); // 调用者填步骤
return 可乐鸡翅;
}
调用时,想先放鸡翅就传:
cook(鸡翅, 可乐, (chicken, coka) -> {
2. 放鸡翅;
3. 倒可乐;
});
想先倒可乐就传:
cook(鸡翅, 可乐, (chicken, coka) -> {
2. 倒可乐;
3. 放鸡翅;
});
这就是「方法多态」——用函数式接口把不确定的逻辑抽象成参数,调用时通过Lambda传入具体实现。
晚绑定的亲兄弟:模板方法模式
设计模式里,策略模式和模板方法模式都用了晚绑定,但玩法不太一样。策略模式是用接口占坑,传不同实现类;模板方法模式更“抠细节”,用抽象类定骨架,把变化的部分拆成抽象方法,让子类填坑。
举个验证码发送的例子🌰:
不管是短信验证码还是邮箱验证码,流程大差不差:生成验证码→存Session→发送。但「发送」的具体方式不同(短信走阿里云,邮箱走QQ)。
用模板方法模式设计:
// 抽象类:定好流程骨架
public abstract class AbstractValidateCodeSender {
// 主流程(不变的部分)
public void sendValidateCode() {
String code = generateValidateCode(); // 生成验证码(固定)
saveToSession(code); // 存Session(固定)
sendCode(code); // 发送(变化的部分,抽象方法)
}
// 生成验证码(固定逻辑)
protected String generateValidateCode() {
return "123456";
}
// 存Session(固定逻辑)
private void saveToSession(String code) {
// 具体实现...
}
// 抽象方法:发送逻辑由子类填坑
protected abstract void sendCode(String code);
}
子类只需要填「发送」的坑:
// 短信验证码发送器
public class SmsValidateCodeSender extends AbstractValidateCodeSender {
@Override
protected void sendCode(String code) {
// 调用阿里云短信API发送
System.out.println("短信发送验证码:" + code);
}
}
// 邮箱验证码发送器
public class EmailValidateCodeSender extends AbstractValidateCodeSender {
@Override
protected void sendCode(String code) {
// 调用QQ邮箱API发送
System.out.println("邮箱发送验证码:" + code);
}
}
用的时候,想发邮件就new EmailValidateCodeSender
,想发短信就new SmsValidateCodeSender
——流程骨架由抽象类定死,变化的部分晚绑定到子类实现。
总结
-
接口多态:运行时才确定具体调用哪个实现(像拆神奇宝贝盲盒)。
-
方法多态:用函数式接口占坑,Lambda传逻辑(可乐鸡翅的自定义步骤)。
-
模板方法模式:抽象类定骨架,子类填变化的坑(验证码发送的通用流程)。
多态的本质是「晚绑定」,牺牲点性能换灵活性——这世上哪有十全十美的事儿?设计时根据场景选合适的模式,才是关键~