一、多态的概念
多态:意味着允许不同类的对象对同一消息做出不同的响应
分类:
1)编译时多态(也叫设计时多态,举例如方法重载)
2)运行时多态(程序运行时决定调用哪个方法)
必要条件:
1)满足继承关系;
2)父类引用指向子类对象。
二、多态的实现
1、向上转型
向上转型
又叫隐式转型、自动转型
是子类转型为父类,小类转型为大类这种式的名称。父类引用指向子类实例可以调用子类重写父类方法以及父类派生的方法无法调用子类独有方法。
例:
//父类 实例化对象=new 子类();
Animal two=new Cat()
2、向下转型
向下转型也叫强制类型转换
子类引用指向父类对象,此处必须进行强转,可以调用子类特有的方法。必须满足转型条件才能进行强转
例:
Dog temp2 = (Dog)two;是不对的,因为two在cat中已开辟有空间
Cat temp = (Cat)two;是正确的的,向下类型转换。相当于子类对象的还原
3、instanceof运算符
instanceof
A instanceof B判断对象A是否为B类的实例,或者对象A是否有B类的特征(如父类Animal,子类Cat,Animal one=new Cat(); 则one既有Cat类特征又有Animal类特征,同理one还有Object类特征),是返回true,否返回false。
4、类型转换
- 在父类当中使用static 关键字修饰的方法不能被子类所重写。加上注解会报错。
- 父类引用指向子类实例,可以调用子类重写的方法以及父类派生的方法,无法调用子类独有的方法。
注意:父类中的静态方法无法被子类重写,所以向上转型之后,只能调用到父类原有的静态方法。如果要调用子类的静态方法,向下转型后子类对象.方法()即可。
三、抽象类
1、抽象类
在类的定义前加上abstract关键字,成为一个抽象类
eg: public abstract class Animal{}
抽象类不允许实例化,可以通过向上转型,指向子类实例,调用子类重写父类的方法以及父类派生的方法
eg: Animal animal=new Dog();
抽象类利用子类与父类的继承关系,既限制了子类的设计随意性,又避免了父类无意义的实例化
2、抽象方法
抽象类:不允许被实例化
抽象方法:不允许包含方法体;子类中需要重写父类的抽象方法;否则子类也是抽象类
应用场景:
某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法
使用规则:
- abstract定义抽象类
- 抽象类不能直接实例化,只能被继承,可以通过向上转型完成对象实例
- abstract定义抽象方法,不需要具体实现
- 包含抽象方法的类一定是抽象类
- 抽象类中可以没有抽象方法
- 当一个类包含抽象方法的时候,这个类的子类一定要重写抽象方法,如果
这个子类没有重写抽象方法的时候,要把这个类变成抽象类 - static final private不能与abstract并存
- 子类的设计随意性:在不同的子类中存在相同功能的方法,但方法名、参数返回类型都不一样,但实现效果是一样的,实例化不同子类对象实现同一个的功能,则需要调用不同的方法,代码可读性不高。
- 而抽象类可以作为子类的模板,规定方法名称、参数,返回值从而避免了子类设计的随意性。
四、接口
1、问题引发的思考
- java只支持单继承, 一个子类只有一个唯一的一个直接父类. 当两个类满足 A is a B的时候, 可以通过集成限定子类必须具有父类的通用特征…
- 如何解决一个类型中同时兼容多种类型特征的问题?
- 以及多个不同类型在无法具有共同父类的前提下依然具有相同特征的问题?
2、定义接口并测试
1)接口和类分开理解!“接口不是类,而是对类的一组需求描述。”出自书《Java核心技术 卷1》的214页。
2)接口是作为一种规范,它只能在类中实现,而不能直接实例化!
接口类:
- 在面向对象的思想中,对于一种类型下的多个对象具有相同特征(如狗包括很多种,它们都有共同的属性和行为),可以抽象出一个类来描述。而如果考虑不适合抽象出类(多种类型)的情况(例如手机、照相机都有拍照的功能,但不能很好地抽象出一个类来描述),则可以定义接口来描述共同的行为能力,然后手机类和照相机类实现此接口即可。
- 接口类的方法没有方法体
- 使用implements 来继承接口类
public class Camera implements IPhoto{} - 如果一个类调用了接口类,那就必须实现重写接口类中的所有方法(特性类似于抽象方法,如果不实现则此类必须设置为抽象类)
- 接口无法直接实例化出对象,用接口的引用指向实现类的实例对象。例如:IPhoto ip=new FourthPhone(); 实例化的对象ip只能调用IPhoto这个接口中的方法,调用方法具体的实现细节则由其实例化对象时使用的类决定(如在此如果ip.photo(); 实际上是运行的FourthPhone类中的photo()方法)。
注意:只能用含有接口类方法的类来实例化对象,而且接口类本身不能创建对象(特性类似于抽象类)
3、接口成员–抽象方法&常量
- //接口类前面的访问修饰符一般是:public和默认,默认也是public
public interface INet{} - //接口中抽象方法可以不写abstract,方法就算不写public也默认是public修饰
void network(); - 当一个类实现接口的时候,需要去实现接口中的所有抽象方法,否则需要将该类设置为抽象类
- 接口类不同于抽象方法,里面不能有方法体。
- 接口中无变量,只能定义静态常量,且定义就初始化,修饰符默认public static final,//static意义:使接口常量在测试类中直接调用,无需对象实例化,final意义:定义属性时为常量,定义方法时子类不可覆写,定义类时不可被继承.
- 接口类实例化对象也需要用向上转型,如果分配空间的那个类里面有和接口类一样的属性(不需要加上接口类默认的修饰符),那么优先调用接口类的。
- 就算属性和接口类一样,自己创建实例化对象时,还是用自己的属性
- 接口无法实例化,原因包含了抽象方法,可理解为特殊的抽象类
- 接口名.常量去访问接口中的常量
4、接口成员–默认方法&静态方法
jdk1.8后新增的方法设置:
default关键字:默认方法,可以带方法体;
default void connect(){
System.out.println("我是接口中的默认链接");
}
static关键字:静态方法,可以带方法体;(通过 类名.静态方法名调用)
静态方法不可以在实现类中重写,可以通过 接口名.静态方法调用;
static void stop(){
System.out.println("我是接口中的静态方法");
}
接口中默认方法可以在实现类中重写,并可以通过接口的引用调用;
接口默认方法的调用:
- 接口名.super.默认方法;
5、关于多接口中重名默认方法处理的解决方案
子类继承父类及实现多接口时方法重名的问题:
①多接口重名同名方法
在继承接口的方法中会报错
【解决】在继承接口的方法中,【重写】一个自己的同名默认方法
②父类与继承接口中重名其中的方法
【默认】指向【父类当中】的重名方法
若自己重写一个自己的重名方法 , 【指向子类的重写方法】
1)一个类可以实现多个接口。只需要在implements后写接口名并用,隔开即可。
2)如果一个实现类实现了多个接口,而在这多个接口中有同名的默认方法,那么实现类必须重写此默认方法,测试类中接口引用指向实现类的对象调用此重名方法时实际上调用的是实现类的方法。
3)如果一个类的父类及它实现的接口中都有重名的方法,那么实现类不必重写这个方法,在不重写的情况下,测试类中接口引用指向实现类的对象调用此重名方法时实际上调用的是父类的方法,如果重写后则测试类中接口引用指向实现类的对象调用此重名方法时实际上调用的是实现类重写后的方法。
子类继承父类及实现多接口时常量重名的问题:
①若实现类的多接口中有重名常量,那么实现类如果调用重名常量会报错。
解决办法:
(1)在实现类中通过“接口名.常量名”的方式明确指定调用的是哪个接口中的常量。
(2)在实现类中写个重名的常量,测试类主方法中调用时,调用的就是实现类中写的重名常量。
②若子类继承的父类和实现的多接口中有重名常量,那么子类如果调用重名常量会报错。
解决办法:
(1)在实现类中通过“接口名.常量名”或“父类名.常量名”(为父类中静态常量时)或“new 父类名().常量名”(为父类中普通常量时)的方式明确指定调用的是哪个接口或父类中的常量。
(2)在子类中写个重名的常量,测试类主方法中调用时,调用的就是子类中写的重名常量。
6、接口的继承
-
接口也可以实现继承关系,并且可以继承多个父接口,使用extends关键字。
如public interface ISon extends IFather1,IFather2{} -
子接口的多个父接口中存在方法重名时的情况:
(1)若子接口的多个父接口中有重名的抽象方法,并不会产生冲突,因为都没有方法体。
(2)若子接口的多个父接口中有重名的静态方法,并不会产生冲突,因为静态方法无法被子接口继承;
(3)若子接口的多个父接口中有重名的默认方法,会产生冲突,必须要在子接口中写一个重名的默认方法。 -
若一个子接口有多个父接口,则此子接口的实现类必须实现此接口及其父接口中的所有抽象方法。
注意:
(1)子类可以继承父类的静态方法与静态成员变量,但是无法构成重写关系,可以通过"子类对象.静态方法()"和"子类对象.静态成员变量"来调用;
(2)实现类或子接口可以继承父接口的静态常量,并通过“实现类对象.静态常量”或“子接口引用.静态常量”来调用父接口中的静态常量;
(3)但实现类或子接口无法继承父接口的静态方法,因而完全无法通过实现类对象和子接口引用来调用父接口的静态方法,会编译报错。
五、内部类
1、概述
- 在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。与之对应,包含内部类的类被称为外部类。
- 内部类隐藏在外部类之内,更好的实现了信息隐藏。
- 在Java中内部类主要分为:成员内部类、静态内部类、方法内部类、匿名内部类。
2、成员内部类
调用规则:
- 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
- 内部类的访问修饰符,可以任意,但是访问范围会受到影响
- 内部类可以直接访问外部类的成员;如果出现同名属性,优先访问内类中定义的;
- 可以使用外部类.this.成员的方式,访问同名外部类成员信息
- 外部类访问内部类信息,需要通过内部类实例,无法直接访问
- 内部类编译后.class文件命名:外部类$内部类.class
实例化规则:
- 外部类.内部类 外部类对象=new 外部类构造方法().new 内部类构造方法();
- 外部类 外部类对象=new 外部类构造方法();
内部类对象=外部类对象.new 内部类构造方法(); - 外部类 外部类对象=new 外部类构造方法();
内部类对象=外部类对象.外部类调用内部类的方法();
3、静态内部类
静态内部类:
- 静态方法中不可以出现this关键字
- 在静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例调用
- 静态内部类对象实例时,可以不依赖于外部类对象
- 可以通过 外部类.内部类.静态成员 的方式,访问内部类中的静态成员
- 在内部类的方法中:
(1)当内部类属性与外部类属性同名的时候,默认直接调用内部类中的成员;
(2)如果需要访问外部类当中的的静态属性,则可以通过 外部类.属性 的方式;
(3)如果需要访问外部类当中的的非静态属性,则可以通过 new 外部类().属性 的方式访问;
- 注意:外部类的非静态成员内部不可以有静态成员,而静态成员内部可以有静态成员和非静态成员。
内部类也是外部类的成员,因而一样符合这个原则。
4、方法内部类
定义在外部类方法中的内部类,也城称局部内部类
- 定义在方法内部,作用范围也在方法内
- 和方法内部成员使用规则一样,class前面不可以添加public、private、protected、static
- 类中不能包含静态成员,但可以有final、abstract修饰,也可以有抽象方法,若有抽象方法,类也得变成抽象类。(一般不推荐改成抽象类)
5、匿名内部类
匿名内部类:没有名字、隐藏名字。
适用场景:
- 只用到类的一个实例
- 类在定义后马上用到
- 给类命名不会导致代码更容易被理解
特点:
- 匿名内部类没有类型名称、实例对象名称
- 编译后的文件命名:外部类$数字.class
- 无法使用public、private、protected、static、abstract修饰
- 无法编写构造方法,可以添加构造代码块
- 不能出现静态成员
- 匿名内部类可以实现接口也可以继承父类,但是不可兼得
关于语法理解:test.getRead(new Person(){…}); 相当于调用这个方法,与调用普通方法不同的是:因为Person类是一个抽象类而且没有定义子类,所以没有办法实例化出一个对象,所以通过这种写法调用,其中{…}代表大括号中必须重写Person类中的抽象方法。
优点:内存消耗小,提升系统性能。
缺点:重复调用不方便, 如果想多次调用,则需要再写多次。