一、如何理解多态?
教科书式的解释为,多态的存在有三个前提:
多态的弊端,就是:不能使用子类特有的成员属性和子类特有的成员方法。如需要,需要强转为子类(向下转型)。
纸上得来终觉浅,绝知此事要躬行,真正很好的理解多态用法, 还是的写代码。
举一个例子:假如一个人出门吃东西,吃了面条,又吃米饭,又吃水果。。。等等。
那么我们用代码来实现:
public class Food {
private String name = "食物";
public String getName() {
return name;
}
}
//面条继承食物
public class Noodles extends Food{
private String name = "面条";
public String getName() {
return name;
}
}
//米饭继承食物
public class Rice extends Food{
private String name = "米饭";
public String getName() {
return name;
}
}
在没有多态的时候,人每次想多吃一个东西,不仅需要新增一个食物继承类,还要新增一个人对应吃它的方法,非常麻烦。
public class Person {
//没有多态时候,写多个方法重载
//每多吃一个东西就要多加一个eatFood的方法
public void eatFood(Noodles food){
System.out.println("我在吃"+food.getName());
}
public void eatFood(Rice food){
System.out.println("我在吃"+food.getName());
}
// .
// .
// .
// .
// .
// 很多方法重载......
public static void main(String[] args) {
Person p = new Person();
Noodles n = new Noodles();
Rice r = new Rice();
p.eatFood(n);
p.eatFood(r);
}
}
假如我们现在引入多态,代码就变成了这样
//实体人类
public class Person {
//用多态,只创建一个方法,只传入父类对象,不用管是哪个具体的子类
public void eatFood(Food food){
System.out.println("我在吃"+food.getName());
}
public static void main(String[] args) {
//用继承实现多态时,实现向上转型
Food f = new Noodles();//编译时看左边,运行时看右边
p.eatFood(f);
f = new Rice();
p.eatFood(f);
}
}
我们发现,在用多态的时候,Food f = new Noodles(); 食物父类可以调用子类的普通方法getFoodName()来获得name为“面条“,而不是获得name为“食物“,像这种父类的引用指向了子类对象,也称作子类的“向上转型”。
我们同样也可以引入接口,来实现多态:
public interface IFood {
String getFoodName();
}
//面条实现IFood接口
public class Noodles implements IFood{
private String name = "面条";
@Override
public String getFoodName() {
return this.name;
}
}
//米饭实现IFood接口
public class Noodles implements IFood{
private String name = "米饭";
@Override
public String getFoodName() {
return this.name;
}
}
//实体人类
public class Person {
public void eatIFood(IFood food){
System.out.println("我在吃"+food.getFoodName());
}
public static void main(String[] args) {
//用接口实现多态时
Person pi = new Person();
IFood fi = new Noodles();
pi.eatIFood(fi);
fi = new Rice();
pi.eatIFood(fi);
}
}
现在我们知道,父类可以调用子类的普通方法(前提是这个方法子类成功覆写)
那么对于,成员变量,静态变量,静态方法,父类去调用会是调用子类的吗??结论是不会!
public class Food {
String name = "食物";
static String attribute = "食物的静态属性";
public String getName() {
return name;
}
public static void staticMethod(){
System.out.println("食物静态方法");
}
}
public interface IFood {
String attribute = "食物接口";//默认缺省static final
String getFoodName();//默认缺省public abstract
}
public class Noodles extends Food implements IFood{
String name = "面条";
static String attribute = "面条的静态属性";
public String getName() {
return name;
}
@Override
public String getFoodName() {
return this.getName();
}
public static void staticMethod(){
System.out.println("面条静态方法");
}
}
//实体人类
public class Person {
//用多态,只创建一个方法
public void eatFood(Food food){
System.out.println("我在吃"+food.getName());
}
public static void main(String[] args) {
Food ff = new Noodles();
System.out.println(ff.name);//食物---成员变量编译时看左边,运行时看左边
System.out.println(ff.attribute);//食物的静态属性---静态成员变量编译时看左边,运行时看左边
ff.staticMethod();//食物静态方法---静态方法编译时看左边,运行时看左边
System.out.println(ff.getName());//面条--只有非静态方法编译时看左边,运行时看右边!
//接口同理,接口中只能声明static final的成员变量
IFood ifood = new Noodle();
System.out.println(ifood.attribute);//食物的静态属性---静态成员变量编译时看左边,运行时看左边
}
}
结论:使用多态时候
成员变量:编译时看左边(父类),运行时看左边(子类)
静态成员变量:编译时看左边(父类),运行时看左边(子类)
静态方法:编译时看左边(父类),运行时看左边(子类)
只有非静态方法:编译时看左边(父类),运行时看右边(子类)
想必看到这里,你对于多态应该足够理解了,以上代码如有不正确的地方,欢迎指正,谢谢。
下面再贴一个知乎花木兰的故事,帮助理解多态。
花木兰替父从军
大家都知道花木兰替父从军的例子,花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有自己的成员属性年龄,姓名,性别。花木兰也有这些属性,但是很明显二者的属性完全不一样。花弧有自己的非静态成员方法‘骑马杀敌’,同样花木兰也遗传了父亲一样的方法‘骑马杀敌’。花弧还有一个静态方法‘自我介绍’,每个人都可以问花弧姓甚名谁。同时花木兰还有一个自己特有的非静态成员方法‘涂脂抹粉’。但是,现在花木兰替父从军,女扮男装。这时候相当于父类的引用(花弧这个名字)指向了子类对象(花木兰这个人),那么在其他类(其他的人)中访问子类对象(花木兰这个人)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(60岁)、性别(男)。当访问子类对象(花木兰这个人)的非静态成员方法(骑马打仗)时,其实都是看到花木兰自己运用十八般武艺在骑马打仗。当访问花木兰的静态方法时(自我介绍),花木兰自己都是用她父亲的名字信息在向别人作自我介绍。并且这时候花木兰不能使用自己特有的成员方法‘涂脂抹粉’。-----多态中的向上转型
那么终于一将功成万骨枯,打仗旗开得胜了,花木兰告别了战争生活。有一天,遇到了自己心爱的男人,这时候爱情的力量将父类对象的引用(花弧这个名字)强制转换为子类对象本来的引用(花木兰这个名字),那么花木兰又从新成为了她自己,这时候她完全是她自己了。名字是花木兰,年龄是28,性别是女,打仗依然那样生猛女汉子,自我介绍则堂堂正正地告诉别人我叫花木兰。OMG!终于,终于可以使用自己特有的成员方法‘涂脂抹粉’了。从此,花木兰完全回到了替父从军前的那个花木兰了。并且和自己心爱的男人幸福的过完了一生。-----多态中的向下转型
二、多态的实际使用:
在实际项目开发中,比如在做考核统计功能时,你可能需要收集项目组其他开发人员模块的数据。这个时候,就可以由你出一个接口,统一数据类型和获得数据的方法,让其他开发人员的service层来实现。
我们想要获取到某个接口的所有实现类。在这里大致介绍两种方式:
1.借助Spring容器实现:
import org.springframework.beans.BeansException;
2 import org.springframework.context.ApplicationContext;
3 import org.springframework.context.ApplicationContextAware;
4 import org.springframework.stereotype.Component;
5
6 @Component
7 public class ServiceLocator implements ApplicationContextAware{
8 /**
9 * 用于保存接口实现类名及对应的类
10 */
11 private Map map;
12
13 /**
14 * 获取应用上下文并获取相应的接口实现类
15 * @param applicationContext
16 * @throws BeansException
17 */
18 @Override
19 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
20 //根据接口类型返回相应的所有bean
21 Map map = applicationContext.getBeansOfType(IService.class);
22 }
23
24 public Map getMap() {
25 return map;
26 }
27 }
2.借助ServiceLoader类
ServiceLoader是JDK自带的一个类加载器,位于java.util包当中,作为 A simple service-provider loading facility. 具体使用方式如下:
1.在META-INF/services/目录下用你的接口全路径名称命名一个文件(不加后缀),然后在该文件中一行一个添加你的接口实现类的全路径名。
2.通过load方法来加载出所有的接口实现类
1 ServiceLoader loader = ServiceLoader.load(MyInterface.class);
在这里load方法的返回值是一个迭代器,用这个迭代器可以遍历出所有的接口实现类。
参考:https://www.cnblogs.com/heaveneleven/p/9125228.html