我们都知道面向对象三大特征:封装,继承,多态
相比于封装和继承,多态似乎理解起来更加抽象,这篇文章将彻底揭露java多态性,文章内容较多,请耐心观看
1.通过代码体现多态性
我们先准备好三个类,男人类male
,女人类female
,以及他们共同的父类person
类
public class person {
public void eat(){
System.out.println("person中的eat方法");
}
}
public class female extends person {
@Override
public void eat() {
System.out.println("细嚼慢咽");
}
public void walk() {
System.out.println("female中的的walk");
}
}
public class male extends person {
@Override
public void eat() {
System.out.println("狼吞虎咽");
}
public void walk() {
System.out.println("male中的的walk");
}
}
先通过最熟悉的创建对象的方式来测试这些类
public class test {
public static void main(String[] args) {
person p = new person();
male man = new male();
female woman = new female();
p.eat();
man.eat();
woman.eat();
}
}
输出结果:
person中的eat方法
狼吞虎咽
细嚼慢咽
这些都是非常正常的,毫无疑问,我们改一下test
中的代码
public class test {
public static void main(String[] args) {
person p = new person();
person man = new male();
person woman = new female();
p.eat();
man.eat();
woman.eat();
}
}
请看输出结果:
person中的eat方法
狼吞虎咽
细嚼慢咽
通过上面的代码,可以提出几个疑问:
person man = new male();
为什么没有报错?- 在
person man = new male();
中,man
属于person
类声明的对象引用,为什么不调用person
中的方法
2.解释
在本例中,male
和female
都是person
的子类,我们做的测试都是基于这个条件,这就引出了多态成立的一个条件:
- 类和类存在继承关系,父类引用指向子类对象,子类重写父类的方法
通过代码我们可以发现,通过person
声明的引用可以是male
,也可以是female
,这就体现了多态性,即一个引用可以有不同的实现方式,说简单就是,只要是person的子类,都可以赋给person声明的引用
- 需要注意的是: 在
person man = new male();
,中,虽然man
是person的引用,但实际指向的是male
在堆上创建的male对象,所以man调用的方法就是male中重写父类的方法了,这就涉及到了一个概念:虚拟方法调用,文章下面会讲
不要忘了我们male
类中还有一个方法,这不是白写的,我们在person man = new male();
前提下来调用这个方法:
发现报错了,无法通过编译!,为什么会这样,请接着往下看:
3.多态条件下的同名同参的方法调用
在编译期,也就是将我们写好的java代码编译成class文件的时候,在编译器编译
person man = new male();
这段代码的时候,因为man
是person
类型的,所以man
在本质上一个person
类型的引用,这是我们写代码的时候声明的,毫无疑问,所以所以man
只能调用在person
类中出现过的方法,请记住下面四句话:
- 如果子类重写了这个方法,且这两个方法同名同参,就调用子类的
- 如果子类没有这个方法,就调用父类的,
- 不能调用子类中存在而父类中不存在的方法!
- 编译器看左边,运行期执行的是子类重写父类的方法
4.为什么要有多态
在我们编写代码的时候,尽量本着高内聚,低耦合的原则去编写,不理解这个概念没关系,我们通过一个案例来表示
public class test {
public static void main(String[] args) {
fun(new Dog());
fun(new Cat());
}
public static void fun(Animal animal){
animal.action();
}
}
class Animal{
public void action(){}
}
class Dog extends Animal{
@Override
public void action() {
System.out.println("狗类的动作");
}
}
class Cat extends Animal{
@Override
public void action() {
System.out.println("猫类的动作");
}
}
在test
类中定义的fun
方法中,参数并没有写死,而是只要是Animal
的子类都可以调用,这样就避免课将参数写死而导致代码的难以服用,在以后的框架以及其他技术中,这类的写法非常之多,如果没有多态,那么抽象类和接口也就没有意义,体现不了java的面向对象编程思想
5.关于属性的多态
我们来一个骚操作
public class test {
public static void main(String[] args) {
Animal a = new Dog();
System.out.println(a.id);
}
}
class Animal{
int id = 1;
}
class Dog extends Animal{
int id = 2;
}
输出:1
到这里就看不懂了,a是animal类型的,调用的是animal中的属性,不做多解释,我们抛出一个结论:
- 多态只适用于方法,不适合属性
6.虚拟方法调用
这只是一个粗糙的管你虚拟方法的介绍,更加具体深入的介绍请看:重载重写的本质
7.向下转型
有了对象的多态性之后,内存中其实是加载了子类的属性和方法的,但由于声明的引用是父类类型,导致编译时只能调用父类中的属性和方法,子类特有的属性和方法无法调用,仔细看这句话,如何才能调用子类特有的属性和方法?
解决办法:强转
用我们第一步的代码来看:
public class test {
public static void main(String[] args) {
person p = new male();
male m =(male)p;
}
}
这和基本数据类型的强转差不多,这样就可以使用子类特有的属性和方法了,这就是向下转型
类比基本数据类型,可以看一下这张图:
是不是容易理解多了,这里多提一下向上转型,说白了向上转型就是多态,我们之前的案例都是向上转型,即父类引用指向子类对象
在进行类型转换的时候可能会出现很多问题,在这里有必要说一下:
- 问题一:编译时通过,运行时报错
看代码:用第一步的代码来做演示:
在运行的时候会抛出:person p = new male(); female f = (female)p; f.walk();
Exception in thread "main" java.lang.ClassCastException
类型转换错误异常 - 问题二:编译时通过,运行时通过
Object o = new male(); person p1 = (person)o; p1.eat();
8.instanceof
在做类型转换的时候要使用instanceof
a instanceof A
:判断a是不是A的一个实例,返回true
或false
拿第一部的代码来演示:
public class test {
public static void main(String[] args) {
person p = new male();
System.out.println(p instanceof male);
female f = new female();
System.out.println(f instanceof person);
}
}
输出结果:
true
true
9.关于向下转型的碎碎念
还是第一步的代码,对比两段代码
person p2 = new person();
if(p2 instanceof male){
male m1 = (male)p2;
}else{
System.out.println("wrong");
}
person p3 = new male();
if(p3 instanceof male){
male m2 = (male)p3;
m2.eat();
}else{
System.out.println("wrong");
}
结果输出的是wrong
和狼吞虎咽
,父类不可以强转为子类,如果强转成功,就失去了父类的特性,多态也就丧失了意义