java里的多态主要表现在两个方面:
1.引用多态
父类的引用可以指向本类的对象;
父类的引用可以指向子类的对象;
这两句话是什么意思呢,让我们用代码来体验一下,首先我们创建一个父类Animal和一个子类Dog,在主函数里如下所示:
注意:我们不能使用一个子类的引用来指向父类的对象,如:
这里我们必须深刻理解引用多态的意义,才能更好记忆这种多态的特性。为什么子类的引用不能用来指向父类的对象呢?
我在这里通俗给大家讲解一下:就以上面的例子来说,我们能说“狗是一种动物”,但是不能说“动物是一种狗”,狗和动物是父类和子类的继承关系,它们的从属是不能颠倒的。
当父类的引用指向子类的对象时,该对象将只是看成一种特殊的父类(里面有重写的方法和属性),反之,一个子类的引用来指向父类的对象是不可行的!!
2.方法多态
根据上述创建的两个对象:本类对象和子类对象,同样都是父类的引用,当我们指向不同的对象时,它们调用的方法也是多态的。
创建本类对象时,调用的方法为本类方法;
创建子类对象时,调用的方法为子类重写的方法或者继承的方法;
使用多态的时候要注意:如果我们在子类中编写一个独有的方法(没有继承父类的方法),此时就不能通过父类的引用创建的子类对象来调用该方法!!!
注意:继承是多态的基础。
A.引用类型转换
了解了多态的含义后,我们在日常使用多态的特性时经常需要进行引用类型转换。
引用类型转换:
1.向上类型转换(隐式/自动类型转换),是小类型转换到大类型。
就以上述的父类Animal和一个子类Dog来说明,当父类的引用可以指向子类的对象时,就是向上类型转换。如:
2.向下类型转换(强制类型转换),是大类型转换到小类型(有风险,可能出现数据溢出)。
将上述代码再加上一行,我们再次将父类转换为子类引用,那么会出现错误,编译器不允许我们直接这么做,虽然我们知道这个父类引用指向的就是子类对象,但是编译器认为这种转换是存在风险的。如:
那么我们该怎么解决这个问题呢,我们可以在animal前加上(Dog)来强制类型转换。如:
但是如果父类引用没有指向该子类的对象,则不能向下类型转换,虽然编译器不会报错,但是运行的时候程序会出错,如:
其实这就是上面所说的子类的引用指向父类的对象,而强制转换类型也不能转换!!
还有一种情况是父类的引用指向其他子类的对象,则不能通过强制转为该子类的对象。如:
这是因为我们在编译的时候进行了强制类型转换,编译时的类型是我们强制转换的类型,所以编译器不会报错,而当我们运行的时候,程序给animal开辟的是Dog类型的内存空间,这与Cat类型内存空间不匹配,所以无法正常转换。
这两种情况出错的本质是一样的,所以我们在使用强制类型转换的时候要特别注意这两种错误!!下面有个更安全的方式来实现向下类型转换。。。。
3.instanceof运算符,来解决引用对象的类型,避免类型转换的安全性问题。
instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。
我们来使用instanceof运算符来规避上面的错误,代码修改如下:
利用if语句和instanceof运算符来判断两个对象的类型是否一致。
补充说明:在比较一个对象是否和另一个对象属于同一个类实例的时候,我们通常可以采用instanceof和getClass两种方法通过两者是否相等来判断,但是两者在判断上面是有差别的。
Instanceof进行类型检查规则是:你属于该类吗?或者你属于该类的派生类吗?而通过getClass获得类型信息采用==来进行检查是否相等的操作是严格的判断,不会存在继承方面的考虑;
总结:在写程序的时候,如果要进行类型转换,我们最好使用instanceof运算符来判断它左边的对象是否是它右边的类的实例,再进行强制转换。
B.抽象类
定义:抽象类前使用abstract关键字修饰,则该类为抽象类。
使用抽象类要注意以下几点:
1.抽象类是约束子类必须有什么方法,而并不关注子类如何实现这些方法。
2.抽象类应用场景:
a.在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法(可实现动态多态)。
b.从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免子类设计的随意性。
3.抽象类定义抽象方法,只有声明,不需要实现。抽象方法没有方法体以分号结束,抽象方法必须用abstract关键字来修饰。如:
4.包含抽象方法的类是抽象类。抽象类中可以包含普通的方法,也可以没有抽象方法。如:
5.抽象类不能直接创建,可以定义引用变量来指向子类对象,来实现抽象方法。以上述的Telephone抽象类为例:
1 | public abstract class Telephone { |
1 | public class Phone extends Telephone { |
以上是Telephone抽象类和子类Phone的定义,下面我们看main函数里:
运行结果(排错之后):
C.接口
1.概念
接口可以理解为一种特殊的类,由全局常量和公共的抽象方法所组成。也可理解为一个特殊的抽象类,因为它含有抽象方法。
如果说类是一种具体实现体,而接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供的某些方法。(这里与抽象类相似)
2.接口定义的基本语法
1 | [修饰符] [abstract] interface 接口名 [extends父接口1,2....](多继承){ |
其中[ ]里的内容表示可选项,可以写也可以不写;接口中的属性都是常量,即使定义时不添加public static final 修饰符,系统也会自动加上;
接口中的方法都是抽象方法,即使定义时不添加public abstract修饰符,系统也会自动加上。
3.使用接口
一个类可以实现一个或多个接口,实现接口使用implements关键字。java中一个类只能继承一个父类,是不够灵活的,通过实现多个接口可以补充。
继承父类实现接口的语法为:1
2
3 [修饰符] class 类名 extends 父类 implements 接口1,接口2...{
类体部分//如果继承了抽象类,需要实现继承的抽象方法;要实现接口中的抽象方法
}
注意:如果要继承父类,继承父类必须在实现接口之前,即extends关键字必须在implements关键字前
补充说明:通常我们在命名一个接口时,经常以I开头,用来区分普通的类。如:IPlayGame
以下我们来补充在上述抽象类中的例子,我们之前已经定义了一个抽象类Telephone和子类Phone,这里我们再创建一个IPlayGame的接口,然后在原来定义的两个类稍作修改,代码如下:1
2
3
4public interface IPlayGame {
public void paly();//abstract 关键字可以省略,系统会自动加上
public String name="游戏名字";//static final关键字可以省略,系统会自动加上
}
1 | public class Phone extends Telephone implements IPlayGame{ |
1 | public class train { |
运行结果:
4.接口和匿名内部类配合使用
接口在使用过程中还经常和匿名内部类配合使用。匿名内部类就是没有没名字的内部类,多用于关注实现而不关注实现类的名称。
语法格式:1
2
3
4
5
6Interface i =new interface(){
Public void method{
System.out.println(“利用匿名内部类实现接口1”);
}
};
i.method();
还有一种写法:(直接把方法的调用写在匿名内部类的最后)
1 | Interface i =new interface(){ |
加油!Coding For Dream!!
I never feared death or dying, I only fear never trying. –Fast & Furious