Java多态的理解与使用

一、如何理解多态?

教科书式的解释为,多态的存在有三个前提:

  • 1.要有继承关系
  • 2.子类要重写父类的方法
  • 3.父类引用指向子类对象

多态的弊端,就是:不能使用子类特有的成员属性和子类特有的成员方法。如需要,需要强转为子类(向下转型)。


纸上得来终觉浅,绝知此事要躬行,真正很好的理解多态用法, 还是的写代码。

举一个例子:假如一个人出门吃东西,吃了面条,又吃米饭,又吃水果。。。等等。

那么我们用代码来实现:

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 Rice 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 = "食物接口";//默认缺省public 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);//食物的静态属性---静态成员变量编译时看左边,运行时看左边
	}
}

结论:使用多态时候

(父类) Food ff = new Noodles(); (子类)

成员变量:编译时看左边(父类),运行时看左边(父类)
静态成员变量:编译时看左边(父类),运行时看左边(父类)
静态方法:编译时看左边(父类),运行时看左边(父类)

只有非静态方法:编译时看左边(父类),运行时看右边(子类)

想必看到这里,你对于多态应该足够理解了,以上代码如有不正确的地方,欢迎指正,谢谢。

下面再贴一个知乎花木兰的故事,帮助理解多态。

花木兰替父从军
大家都知道花木兰替父从军的例子,花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有自己的成员属性年龄,姓名,性别。花木兰也有这些属性,但是很明显二者的属性完全不一样。花弧有自己的非静态成员方法‘骑马杀敌’,同样花木兰也遗传了父亲一样的方法‘骑马杀敌’。花弧还有一个静态方法‘自我介绍’,每个人都可以问花弧姓甚名谁。同时花木兰还有一个自己特有的非静态成员方法‘涂脂抹粉’。但是,现在花木兰替父从军,女扮男装。这时候相当于父类的引用(花弧这个名字)指向了子类对象(花木兰这个人),那么在其他类(其他的人)中访问子类对象(花木兰这个人)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(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<String, IService> 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<String, IService> map = applicationContext.getBeansOfType(IService.class);
22     }
23 
24     public Map<String, IService> 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<MyInterface> loader = ServiceLoader.load(MyInterface.class);

在这里load方法的返回值是一个迭代器,用这个迭代器可以遍历出所有的接口实现类。

3.自己实现,通过实现类的包名与反射来获得所有某个接口的该包下的所有实现类。

参考:https://www.cnblogs.com/heaveneleven/p/9125228.html

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值