Java多态
一.多态概念
注:Python支持多态(同样还有鸭子类型【可以不必继承重写方法,只要方法名相同,动态语言的特性】)
意味着允许不同类的对象对同一信息做出不同的反应。
分类:
编译时多态:设计时多态,方法重载
运行时多态:程序运行时动态决定调用哪个方法
Java多态必要条件:
满足继承关系;
父类引用指向子类对象
二.向上转型和向下转型
1.向上转型(隐式转型/自动转型):
父类引用指向子类实例,可以调用子类重写父类的方法以及父类派生的方法,无法调用子类独有的方法【小类转型为大类】
注意:父类中的静态方法无法被子类重写,所以向上转型之后,只有调用父类原有的静态方法(如果想调用子类的静态方法就得向下转型)
2.向下转型(强制类型转换):
子类引用指向父类对象,此处必须进行强转,可以调用子类特有的方法【 必须满足转换条件才能进行强制转换】
1 package com.swpu.test; 2 3 import com.swpu.plo.Animal; 4 import com.swpu.plo.Cat; 5 import com.swpu.plo.Dog; 6 7 public class Test { 8 9 public static void main(String[] args) { 10 /** 11 * 向上转型,隐式转型,自动转型 12 * 父类引用指向子类实例,可以调用子类重写父类的方法以及父类派生的方法,无法调用子类独有的方法 13 * 【小类转型为大类】 14 */ 15 Animal one =new Animal(); 16 Animal two=new Cat(); 17 Animal three=new Dog(); 18 one.eat(); 19 //无法调用Cat和Dog独有的方法 20 two.eat(); 21 three.eat(); 22 /** 23 * 输出: 24 * 所有的动物都要吃东西 25 小猫吃东西!!! 26 狗吃东西!!! 27 28 */ 29 System.out.println("**************"); 30 31 /** 32 * 向下转型,强制类型转换 33 * 子类引用指向父类对象,此处必须进行强转,可以调用子类特有的方法 34 * 必须满足转换条件才能进行强制转换 35 */ 36 Cat tmp=(Cat)two; 37 tmp.eat(); 38 tmp.run(); 39 //会报错,Cat和Dog没有关系,能转换为Cat是因为two原来指向的就是Cat空间【相当于还原】 40 // Cat tmp2 =(Dog)two; 41 42 43 44 } 45 46 }
3.instanceof:判断对象是否是某个类的实例
注:相当于Python中的isinstance方法(会检查继承链,肯定是所实例类的对象实例,也是其所继承类的实例),而Python中type只判断是否指向同一个对象(不会检查继承链)
1 if(two instanceof Cat){ 2 Cat tmp=(Cat)two; 3 tmp.eat(); 4 tmp.run(); 5 System.out.println("我是猫对象"); 6 } 7 8 if (two instanceof Dog){ 9 Dog tmp=(Dog)two; 10 tmp.eat(); 11 tmp.sleep(); 12 System.out.println("我是狗对象"); 13 } 14 if (two instanceof Animal){ 15 System.out.println("我是Animal对象"); 16 17 18 } 19 if (two instanceof Object){ 20 System.out.println("我是Object对象"); 21 22 } 23 /*输出: 24 * 小猫吃东西!!! 25 小猫跑步~~~ 26 我是猫对象 27 我是Animal对象 28 我是Object对象 29 30 */ 31 32
4.类型转换总结:
注意:父类的静态方法不能在子类中被重写,可以同名(但是不构成重写,是子类特有的静态方法)
5.instanceof使用例子:
1 package com.swpu.plo; 2 3 public class Master { 4 /** 5 * 猫:吃完东西就跑 6 * 狗:吃完东西就睡觉 7 * 如果有很多动物,则需要一直重载方法 8 */ 9 //方案一:编写方法,传入不同类型得动物,调用各自的方法 10 // public void feed(Cat cat){ 11 // 12 // cat.eat(); 13 // cat.run(); 14 // } 15 // public void feed(Dog dog){ 16 // 17 // dog.eat(); 18 // dog.sleep(); 19 // } 20 /** 21 * 使用instanceof 22 */ 23 //方案二:编写方法传入动物的父类,方法中通过类型的转换,调用指定子类的方法 24 //视情况用不同的方案 25 public void feed(Animal obj){ 26 obj.eat(); 27 if(obj instanceof Cat){ 28 Cat tmp=(Cat)obj; 29 //tmp.eat(); 30 tmp.run(); 31 } 32 else if(obj instanceof Dog){ 33 Dog tmp=(Dog)obj; 34 //tmp.eat(); 35 tmp.sleep(); 36 } 37 } 38 39 /** 40 * 饲养何种动物 41 * 时间多:养狗 42 * 时间少:养猫 43 */ 44 //方案一 45 public Dog hasManytime(){ 46 System.out.println("时间多,养狗"); 47 return new Dog(); 48 } 49 public Cat hasLittletime(){ 50 System.out.println("时间少,养猫"); 51 return new Cat(); 52 } 53 //方案二 54 public Animal raise(boolean haManyTime){ 55 if(haManyTime){ 56 System.out.println("时间多,养狗"); 57 return new Dog(); 58 } 59 else{ 60 System.out.println("时间少,养猫"); 61 return new Cat(); 62 } 63 } 64 }
1 package com.swpu.test; 2 3 import com.swpu.plo.Animal; 4 import com.swpu.plo.Cat; 5 import com.swpu.plo.Dog; 6 import com.swpu.plo.Master; 7 8 public class MasterTest { 9 10 public static void main(String[] args) { 11 Cat cat=new Cat(); 12 Dog dog=new Dog(); 13 Master master=new Master(); 14 master.feed(cat); 15 master.feed(dog); 16 System.out.println("**************"); 17 boolean hasManyTime=true; 18 // if(hasManyTime){ 19 // master.hasManytime(); 20 // } 21 // else{ 22 // master.hasLittletime(); 23 // } 24 Animal tmp=master.raise(hasManyTime); 25 26 27 28 } 29 30 }
三.抽象类和抽象方法
1.抽象类:加关键字abstract
不允许实例化,可以通过向上转型指向子类实例。
注:Python中使用abc模块实现抽象基类和抽象方法,但是抽象基类仍然可以是实例化,但抽象方法必须被重写。
无法调用抽象类Animal(如果要用,可以向上转型,Animal one=new Cat();)
2.抽象方法:
不允许包含方法体且抽象方法需要在抽象类中,子类当中需要重写父类的方法,否则子类也是抽象类。
3.总结:
abstract定义抽象类;
抽象类不能直接实例化,只能被继承,可以通过向上转型完成对象实例;
abstract定义抽象方法,不需要具体实现;
包含抽象方法的类是抽象类;
抽象类中可以没有抽象方法;
static(不允许子类重写),final(最终得,不能改变),private(私有的,只能当前类使用)不能和abstract并存。
四.接口
如电脑,手机,只能表都有照相的功能,如果分别写,则需要给每个类定义照相的方法。
1.定义接口并测试:
建立关系,把接口引用指向实现类的方式,从而完成不同的功能
1.1接口定义:
1 package com.swpu.photos; 2 //定义接口 3 public interface Iphoto { 4 //具有照相功能 5 public void photo(); 6 7 }
1.2重写接口方法:
1 package com.swpu.photos; 2 3 public class Computer implements Iphoto { 4 public void game() { 5 System.out.println("电脑可以玩游戏"); 6 7 } 8 9 @Override 10 public void photo() { 11 System.out.println("电脑也可以照相"); 12 13 } 14 }
1 package com.swpu.photos; 2 3 public class Phone implements Iphoto{ 4 public void phone(){ 5 System.out.println("手机可以打电话"); 6 } 7 @Override 8 public void photo() { 9 System.out.println("手机可以照相"); 10 11 } 12 }
1 package com.swpu.photos; 2 3 public class Watch implements Iphoto{ 4 public void times() { 5 System.out.println("智能手表可以看时间"); 6 7 } 8 @Override 9 public void photo() { 10 System.out.println("智能手表也可以照相"); 11 12 } 13 14 }
1.3测试:
package com.swpu.test; import com.swpu.photos.Computer; import com.swpu.photos.Iphoto; import com.swpu.photos.Phone; import com.swpu.photos.Watch; public class Test1 { public static void main(String[] args){ Iphoto ip=new Phone(); ip.photo(); ip =new Computer(); ip.photo(); ip=new Watch(); ip.photo(); } } /** * 输出: * 手机可以照相 电脑也可以照相 智能手表也可以照相 **/
2.接口成员:
2.1接口规则:
接口定义了某一批类所需要遵守的规范;
接口不关心这些类的内部数据,也不关心这些类里的方法的实现细节,它只规定这些类里必须提供什么方法。
注:
接口最好以I开头【规范】,接口访问修饰符只能是public或者是默认的;
接口当中抽象方法可以不写abstract关键字,修饰符可以不写public【默认也会使用public】;
当类实现接口时,需要去实现接口中的所有抽象方法,否则可以将类定义为抽象类不重写,继续延续下去;
接口中可以定义常量 ,默认public static final,可以用接口名(引用)加常量直接访问(INet.TMP);
如果实现类和接口中的有同名的常量,如果是接口引用指向实现类,调用的是接口的常量,如果是实现类引用指向实现类实例,则是实现类中的常量
1 package com.swpu.photos; 2 3 //接口最好以I开头【规范】,接口访问修饰符只能是public或者是默认的 4 public interface INet { 5 // 接口当中抽象方法可以不写abstract关键字,修饰符可以不写public【默认也会使用public】 6 // 当类实现接口时,需要去实现接口中的所有抽象方法,否则可以将类定义为抽象类不重写,继续延续下去 7 public void net(); 8 public void connection(); 9 10 // 接口中可以定义常量 ,默认public static final,可以用接口名(引用)加常量直接访问(INet.TMP) 11 // 如果实现类和接口中的有同名的常量,如果是接口引用指向实现类,调用的是接口的常量,如果是实现类引用指向实现类实例,则是实现类中的常量 12 int TMP = 20; 13 }
3.默认方法和静态方法:
3.1默认方法:
default:默认方法 可以带方法体 JDK1.8后新增;
可以在实现类中重写,并可以通过接口的引用调用。
3.2静态方法:
static:静态方法 可以带方法体 JDK1.8后新增;
不可以在实现类中重写,可以使用接口名调用。
3.3例:
1 package com.swpu.photos; 2 3 //接口最好以I开头【规范】,接口访问修饰符只能是public或者是默认的 4 public interface INet { 5 // 接口当中抽象方法可以不写abstract关键字,修饰符可以不写public【默认也会使用public】 6 // 当类实现接口时,需要去实现接口中的所有抽象方法,否则可以将类定义为抽象类不重写,继续延续下去 7 public void net(); 8 9 // 接口中可以定义常量 ,默认public static final,可以用接口名(引用)加常量直接访问(INet.TMP) 10 // 如果实现类和接口中的有同名的常量,如果是接口引用指向实现类,调用的是接口的常量,如果是实现类引用指向实现类实例,则是实现类中的常量 11 int TMP = 20; 12 //default:默认方法 可以带方法体 JDK1.8后新增 13 //可以在实现类中重写,并可以通过接口的引用调用 14 default void connection(){ 15 System.out.println("我是接口中的默认方法"); 16 } 17 //static:静态方法 可以带方法体 JDK1.8后新增 18 //不可以在实现类中重写,可以使用接口名调用 19 static void stop(){ 20 System.out.println("我是接口中的静态方法"); 21 } 22 }
1 package com.swpu.photos; 2 3 public class Phone implements INet{ 4 public void phone(){ 5 System.out.println("手机可以打电话"); 6 } 7 @Override 8 public void net() { 9 System.out.println("手机能上网"); 10 } 11 @Override 12 public void connection() { 13 INet.super.connection();//调用接口中默认方法 14 } 15 16 17 }
3.4处理多接口同名的默认方法:
需要在子类中重写同名方法。
1 package com.swpu.photos; 2 3 //接口最好以I开头【规范】,接口访问修饰符只能是public或者是默认的 4 public interface INet { 5 // 接口当中抽象方法可以不写abstract关键字,修饰符可以不写public【默认也会使用public】 6 // 当类实现接口时,需要去实现接口中的所有抽象方法,否则可以将类定义为抽象类不重写,继续延续下去 7 public void net(); 8 9 // 接口中可以定义常量 ,默认public static final,可以用接口名(引用)加常量直接访问(INet.TMP) 10 // 如果实现类和接口中的有同名的常量,如果是接口引用指向实现类,调用的是接口的常量,如果是实现类引用指向实现类实例,则是实现类中的常量 11 int TMP = 20; 12 //default:默认方法 可以带方法体 JDK1.8后新增 13 //可以在实现类中重写,并可以通过接口的引用调用 14 default void connection(){ 15 System.out.println("我是INet接口中的默认方法"); 16 } 17 //static:静态方法 可以带方法体 JDK1.8后新增 18 //不可以在实现类中重写,可以使用接口名调用 19 static void stop(){ 20 System.out.println("我是接口中的静态方法"); 21 } 22 }
1 package com.swpu.photos; 2 //定义接口 3 public interface Iphoto { 4 //具有照相功能 5 public void photo(); 6 default void connection(){ 7 System.out.println("我是Iphoto接口中的默认方法"); 8 } 9 10 11 }
1 package com.swpu.photos; 2 3 public class Phone implements INet,Iphoto{ 4 public void phone(){ 5 System.out.println("手机可以打电话"); 6 } 7 @Override 8 public void net() { 9 System.out.println("我是INet接口中的方法,手机能上网"); 10 } 11 @Override 12 public void connection() { 13 System.out.println("我自己的collection方法 "); 14 } 15 @Override 16 public void photo() { 17 System.out.println("我是Iphoto接口中的方法,我可以照相"); 18 19 } 20 21 22 }
3.5注:
可以继承加实现接口(注继承要在实现类前,如public class Phone extends Tel implements IPhoto,INet(){},只能继承一个类,当可以实现多个接口)
如果继承的类里面和实现的接口里面有同名的方法,默认指向父类当中的方法。
3.6处理多接口同名的常量:
需要明确表明是哪个接口中的常量。
1 package com.swpu.test; 2 interface one{ 3 static int x=10; 4 } 5 interface two{ 6 static int x=20; 7 } 8 9 public class Test2 implements one,two{ 10 public void test(){ 11 System.out.println(one.x); 12 System.out.println(two.x); 13 } 14 public static void main(String[] args){ 15 new Test2().test(); 16 } 17 18 }
如果继承的类里面和实现的接口有同名的常量:无法像同名方法那样默认为父类,需要自己在实现类中从新定义常量才不会报错。
4.接口的继承:
接口的继承可以继承多个接口,如果有重名的默认方法,需要在子接口中自定义。
1 package com.swpu.photos; 2 3 public interface ISon extends IFather1,IFather2{ 4 void fly(); 5 void run(); 6 }
五.内部类
1.含义:
在Java中,可以将一个类定义在另一个类里面或者方法里面,这样的类被称为内部类。与之对应,包含内部类的类被称为外部类。
2.作用:
内部类隐藏在外部类之内,更好的实现消息隐藏。
3.内部类分类:
成员内部类;静态内部类;方法内部类;匿名内部类
3.1成员内部类:
内部类中最常见的,也成为普通内部类。
1 package com.swpu.people; 2 //外部类 3 public class Person { 4 int age; 5 public Heart getheart(){ 6 7 return new Heart(); 8 } 9 public void eat(){ 10 System.out.println("吃东西"); 11 } 12 //成员内部类 13 /* 14 * 1.内部类在外部使用时,无法直接实例化,需要借由外部信息才能完成实例化; 15 * 2.内部类的访问修饰符可以任意。但是访问范围会受到影响; 16 * 3.内部类可以直接访问外部类的成员;如果出现同名的属性,优先访问内部类定义的; 17 * 4.可以通过外部类.this.成员的方式,访问外部类中同名的信息; 18 * 5.外部类访问内部类信息,需要通过内部类实例,无法直接访问; 19 * 6.内部类编译后.class文件命名:外部类$内部类.class 20 */ 21 class Heart{ 22 public void eat(){ 23 System.out.println("我是内部类里的吃东西"); 24 } 25 public String beat(){ 26 //调用外部类的eat方法 27 Person.this.eat(); 28 return age+"heart is running"; 29 } 30 } 31 32 }
1 package com.swpu.people; 2 3 public class PeopleTest { 4 5 public static void main(String[] args){ 6 Person p1=new Person(); 7 p1.age=15; 8 /** 9 * 获取内部类对象实例: 10 * 方式一:new 外部类.new 内部类 11 * 12 */ 13 Person.Heart heart=new Person().new Heart(); 14 System.out.println(heart.beat()); 15 //方式二:外部类对象.new 内部类 16 heart =p1.new Heart(); 17 System.out.println(heart.beat()); 18 //方式三:外部类对象.获取方法 19 heart =p1.getheart(); 20 System.out.println(heart.beat()); 21 22 } 23 }
3.2静态内部类:
静态内部类对象可以不依赖于外部类对象直接创建。
1 package com.swpu.people; 2 //外部类 3 public class Person { 4 int age; 5 public Heart getheart(){ 6 7 return new Heart(); 8 } 9 public void eat(){ 10 System.out.println("吃东西"); 11 } 12 /* 13 * 静态内部类: 14 * 1.静态内部类中,只能直接访问外部类的静态方法,如果需要调用非静态方法可以通过对象实例调用 15 */ 16 static class Heart{ 17 static void say(){ 18 System.out.println("Hello"); 19 } 20 public String beat(){ 21 //调用外部类的eat方法 22 new Person().eat(); 23 return new Person().age+"heart is running"; 24 } 25 } 26 27 }
package com.swpu.people; public class PeopleTest { public static void main(String[] args){ Person p1=new Person(); p1.age=15; /* * 获取静态内部类实例的方法: * 1.静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例; * 2.静态内部类对象实例时,可以不依赖于外部类对象; * 3.可以通过外部类.内部类.静态成员的方式,访问内部类的静态成员; * 4.当内部类属性与外部类属性同名时,默认直接调用内部类中的成员; * 如果需要访问外部类中的静态属性,则可以通过 外部类.属性 的方式 * 如果需要访问外部类中的非静态属性,则可以通过 new 外部类().属性 的方式 * */ Person.Heart heart=new Person.Heart(); System.out.println(heart.beat()); Person.Heart.say(); } }
3.3方法内部类:
定义在外部类方法中的内部类,也称局部内部类。
1 package com.swpu.people; 2 //外部类 3 public class Person { 4 int age; 5 public void eat(){ 6 System.out.println("吃东西"); 7 } 8 9 public Object getheart(){ 10 /* 11 * 方法内部类: 12 * 1.定义在方法内部,作用范围也在方法内; 13 * 2.和方法内部成员使用规则一样,class前面不能加public,private,protected,static; 14 * 3.类中不能包含静态成员’ 15 * 4.类中可以包含final,abstract修饰的成员(不推荐) 16 * 17 */ 18 class Heart{ 19 public void say(){ 20 System.out.println("Hello"); 21 } 22 public String beat(){ 23 //调用外部类的eat方法 24 new Person().eat(); 25 return new Person().age+"heart is running"; 26 } 27 } 28 return new Heart().beat(); 29 30 } 31 32 }
1 package com.swpu.people; 2 3 public class PeopleTest { 4 5 public static void main(String[] args){ 6 Person p1=new Person(); 7 p1.age=15; 8 /* 9 * 获取方法内部类 10 */ 11 System.out.println(p1.getheart()); 12 } 13 }
3.4匿名内部类:
没有名字,将类的定义和创建一起完成(如果只用一次可以这样,对系统内存消耗相对较小)
1 package com.swpu.test; 2 3 import com.swpu.people.Man; 4 import com.swpu.people.Person; 5 import com.swpu.people.Woman; 6 7 public class PersonTest { 8 //方案一 9 /* 10 public void getRead(Man man){ 11 man.read(); 12 } 13 public void getRead(Woman woman){ 14 woman.read(); 15 } 16 */ 17 //方案二 18 public void getRead(Person p){ 19 p.read(); 20 } 21 public static void main(String[] args) { 22 PersonTest p1=new PersonTest(); 23 // TODO Auto-generated method stub 24 /* 25 Man man=new Man(); 26 Woman woman =new Woman(); 27 p1.getRead(man); 28 p1.getRead(woman); 29 */ 30 //匿名内部类 31 /* 32 * 1.匿名内部类没有类型名称,实例对象名称; 33 * 2.编译后的文件命名:外部类$数字.class; 34 * 3.无法使用private/public/protected/adstract/static等; 35 * 4.无法在匿名内部类编写构造方法,可以添加构造代码块; 36 * 5.不能出现静态成员 37 * 6.匿名内部类可以继承父类,也可以实现接口(但是不能同时实现) 38 */ 39 p1.getRead(new Person(){ 40 @Override 41 public void read() { 42 // TODO Auto-generated method stub 43 System.out.println("我是匿名内部类"); 44 45 } 46 }); 47 48 49 } 50 51 }