Java面向对象的多态
Java中多态的概念是面向对象中除封装和继承外非常重要的知识点,也是Java面向对象三大特性最后一个特性
多态其实就是指对象存在的多种形态,多态分为引用多态和方法多态
引用多态的含义就是:父类的引用可以指向它本类的对象,不仅如此父类的引用还可以指向子类的对象,这就是引用多态
用简单的代码来看一下引用多态的含义:
动物类:Animal
1 public class Animal { 2 public Animal(){ 3 System.out.println("Animal类的构造方法被执行"); 4 } 5 }
狗类:Dog
1 public class Dog extends Animal{ 2 public Dog(){ 3 System.out.println("Dog类的构造方法被调用"); 4 } 5 }
然后来测试一下:
1 public class Initail { 2 public static void main(String[] args){ 3 Animal animal = new Animal(); //父类的引用指向本类对象 4 Animal dog = new Dog(); //父类的引用指向了子类对象 5 Dog dog1 = new Dog(); //子类引用只能指向子类对象 6 } 7 }
由代码可以看出,父类的引用既可以指向本类对象,也可以指向子类对象,体现了引用多态,但是注意子类引用不能指向父类的对象,只能指向本类的对象
另外看一下方法多态,当我们使用引用多态创建对象时,如果是创建的本类对象,那么调用方法是本类的方法,如果创建的是子类对象,那么调用的方法是子类重写的方法或者未重写直接继承父类的方法,这个和前面继承只是多了个引用,其实还是很好理解的,正因为引用的多态,所以方法的调用也存在多态性,这就是方法多态
另外,如果我们在子类中定义了一个子类独有的方法,而不是父类继承过来或者重写的方法,只有子类中有方法,那么我们只能用子类的引用去访问该方法,比如实例中只能通过dog1去访问独有的方法,用父类的引用指向子类对象的方式是无法访问的
引用类型转换
因为多态有多种引用类型,因此我们会经常用到引用类型转换,引用类型转换也分为以下两种:
首先是向上类型转换,这种转换有时候也是系统隐式进行转换的,这个时候编译器自动完成小类型到大类型的转换,我们也可以手动转换
然后是向下类型转换,也叫强制类型转换,这是大类型到小类型的转换
向上类型转换是不存在风险的,就好像把水杯的水倒在水壶里一样,因为由小到大所以不存在任何问题
但是向下类型转换是存在风险的,我们可以强制进行转换,但是我们必须对引用类型有个明确的认识,否则有可能产生溢出等严重的错误
下面看一个简单的例子:
1 Dog dog = new Dog(); 2 Animal animal = dog; //向上类型转换,自动类型转换 3 //Dog dog2 = animal;虽然这样是可以的,但是为了安全,编译器还是报错 4 Dog dog2 = (Dog)animal; //向下强制类型转换,给出警告但不报错 5 Cat cat = (Dog)animal; //强制类型转换,不报错但程序无法执行
我们实例化了dog对象,那么第2行是子类对象转换为父类对象,所以是安全的,第3行虽然根据前面的引用,直接转换是可行的,但是编译器不允许这么做,因为程序存在风险,所以第4行中进行了强制类型转换是可以转换的,只是编译器还是提示有风险,在第5行,虽然进行了强制类型转换,编译器也没有报错,但是程序执行会发生异常,因为cat对象和dog对象两个对象是实例化自不同的类本身就没有什么关系,类型不存在转换关系,所以是无法强制转换的;
那么怎么解决引用类型转换中的问题实现安全转换呢? 我们可以使用instanceof关键字来避免类型转换的安全问题,代码如下:
1 Dog dog = new Dog(); 2 Animal animal = dog; //自动类型转换无需做任何处理 3 if(animal instanceof Dog){ 4 Dog dog2 = (Dog)animal; //可以进行强制类型转换 5 }else{ 6 System.out.println("animal无法进行强制类型转换为Dog"); 7 } 8 if(animal instanceof Cat){ 9 Cat cat = (Cat)animal; //可以转换 10 }else{ 11 System.out.println("animal无法进行强制类型转换为Cat"); 12 }
所以这个程序执行输出是:
animal无法进行强制类型转换为Cat
所以前者可以转换,后面无法转换,因此通过instanceof关键字可以很好地解决引用类型转换的问题
抽象类
抽象类也是类的一种,前面使用abstract修饰,来表示为抽象类
抽象类是作为某个类的父类,只知道子类中应该包含哪些方法,而并不关注子类如何实现这些方法,所以抽象类的存在是主要目的就是为了约束子类内部必须拥有某些方法;
另外我们可以把具有大量共同特点的类抽象出来,总结一系列系统的方法,所有的子类都以该抽象类为模板进行设计,简单来说,抽象类就是一个包含特定方法名的模板,子类必须去一一实现他,这样避免了子类设计的随意性,导致设计混乱
总结一句话:抽象类规定了子类必须有这些方法,但不考虑如何实现
1 public abstract class Telphone{ 2 //抽象方法定义 3 public abstract void call(); //直接以分号结束 4 public abstract void message(); 5 }
如同上面类就是一个抽象类,定义的是一个手机类,意思是不管是什么类型的手机,必须具备打电话和发短信的方法,注意抽象方法没有实现,直接在方法后加分号即可
下面我们可以写一个类继承这个抽象类
1 public class Cellphone extends Telphone{ 2 //普通手机类继承电话类 3 public void call(){ 4 //具体实现打电话方法,用键盘拨号打电话 5 } 6 public void message(){ 7 //具体实现发短信方法,用键盘打字发短信 8 } 9 }
我们定义了一个普通手机类来继承了手机类,并且具体根据实际情况实现了手机类中全部的抽象方法,
1 public class Smartphone extends Telphone{ 2 //智能手机类继承手机类 3 public void call(){ 4 //具体实现打电话方法,用触屏拨号打电话 5 } 6 public void message(){ 7 //具体实现发短信方法,用语音直接发短信 8 } 9 }
现在我们又定义了一个智能手机类来继承手机类,并且以另一种新的实现方法实现了打电话和发短信的功能
通过这个例子,手机抽象类其实就是一个模板,里面定义了作为手机必须要实现的方法,而不考虑怎么实现,具体实现由不同的手机类型来实现各自的方法,这样体现了同一个抽象类的子类既有共性,又有特点
在引用的时候,我们可以根据引用多态的方式,统一创建父类抽象类的引用指向具体的不同对象,这样提高了程序设计的灵活性,让程序更加清晰
接口
接口可以认为是java中非常重要而又特殊的一种类,类是一种具体实现体,而接口定义了某一批类所要遵守的规范,接口和抽象类类似,也是不关注这些类的内部数据,也不关心这些类里方法的实现细节,它只是规定这些类里面必须提供某些方法,满足我们的某些需要,所以接口也是约束类的一个规范;
接口定义的语法如下:
public interface 接口名 [extends 父接口1,父接口2,....]
{
多个常量定义....
多个抽象方法定义...
}
接口的存在就是约束类的一个规范,所以必须被类继承、实现的,所以访问修饰符就用public,不要用private或者protected来修饰,否则就失去了接口的作用
因为接口内部全部是常量或者抽象方法的定义,所以接口也是abstract抽象类型的,所以准确来说在public和interface之间应该加上abstract来修饰,因为系统会自动添加抽象关键字,所以我们一般就不写了
接口里面的属性都是常量,定义属性应该是:public static final int a = 1;即使我们定义的时候不写那么全,系统也会自动补全
接口中的方法都是抽象公共的方法,定义方法应该是:public abstract void call();即使我们不写abstract和public,系统也会自动补全
类是单继承的,但是接口是多继承的,接口可以继承一个或多个接口,也可以不继承接口
具体类可以实现某个接口,并且可以实现一个或多个接口,所以这样使用起来更加灵活,实现方法如下:
public class 类名 implements 接口1,接口2,...
{
//要实现接口中规定的抽象方法
}
所以实现接口的关键字是implements,一个类还可以在继承某一个类的同时实现接口
public class 类名 extends 父类 implements 接口1,接口2,...
{
//实现接口中的抽象方法
}
并且上面的顺序不能颠倒,extends必须在implements前面
和抽象类一样,一个类继承了一个抽象类,则必须实现抽象类中所有的抽象方法,如果一个类实现了接口,则必须实现接口所规定的所有抽象方法,根据编程的习惯,抽象类一般以大写字母I开头,代表定义的接口
举个例子看一下:
1 public interface IPlayGame { 2 //定义一个玩游戏的接口,规定一个玩游戏的方法 3 public void playGame(); 4 }
1 public class Psp implements IPlayGame { 2 public void playGame() { 3 // Psp实现了游戏接口中的玩游戏方法 4 System.out.println("我用Psp玩游戏"); 5 } 6 7 }
1 public class SmartPhone extends Telphone implements IPlayGame{ 2 public void call() { 3 // 实现抽象类中的打电话方法 4 System.out.println("通过触屏拨号打电话"); 5 } 6 7 public void message() { 8 // 实现抽象类中发短信的方法 9 System.out.println("通过语音发短信"); 10 } 11 12 public void playGame() { 13 // 手机也实现接口中的玩游戏方法 14 System.out.println("我用手机也可以玩游戏"); 15 } 16 17 }
由上面的代码可以看出,对于一个玩游戏的接口,我可以用Psp来实现玩游戏这个方法,也可以用手机来实现玩游戏这个方法,手机实现玩游戏这个方法的同时还继承了手机抽象类,同时实现了抽象类中的所有抽象方法,个人感觉利用抽象类和接口可以让程序功能分工明确,管理系统,各司其职
public class Initail { /** * @param args */ public static void main(String[] args) { IPlayGame ip1 = new SmartPhone(); ip1.playGame(); IPlayGame ip2 = new Psp(); ip2.playGame(); } }
对于抽象类和接口,都可以使用抽象类和接口的引用具体指向某个类,接口的方法用接口指向,抽象类的方法用抽象类指向,让方法的调用更加简单统一
接口还有另外一种使用方式,就是和前面封装特性中提到的匿名内部类配合使用,匿名内部类就是没有名字的内部类,作用是关注具体的实现细节而不关注实现类的名称,只在使用的同时定义一次,下面看一下简单的匿名内部类实现接口:
1 public class Initail { 2 3 /** 4 * @param args 5 */ 6 public static void main(String[] args) { 7 IPayGame ip = new IPayGame(){ 8 //接口的具体实现 9 public void playGame(){ 10 System.out.println("使用匿名内部类的方式实现接口"); 11 } 12 };//这里用分号结束 13 ip.playGame(); //直接调用匿名内部类的playGame方法 14 } 15 16 }
注意匿名内部类花括号必须后面紧跟分号结尾,另外还有一种更简单的匿名内部类实现接口的方法:
1 public class Initail { 2 3 /** 4 * @param args 5 */ 6 public static void main(String[] args) { 7 new IPayGame(){ 8 //接口的具体实现 9 public void playGame(){ 10 System.out.println("使用匿名内部类的方式实现接口"); 11 } 12 }.playGame(); //通过点直接通过匿名内部类的实现接口的playGame方法 13 } 14 15 }
通过以上这种方式也可以直接连贯的实现接口,避免的其他的过渡,这样更加直接了当,其实在Android开发中以及Java高级框架中,经常会使用匿名内部类直接实现接口的方法
以上就是多态的特性介绍和抽象类接口的简单理解和使用,特别是接口概念,随着开发的不断进步,对接口的认识每次都不一样,对接口的理解也是不断深入的,只能不断通过实践加深认识和激发新的思考,如果有心得再继续补充