允许不同类的对象对同一消息作出不同的响应。
1.编译时多态: 也称为设计时多态,通过方法重载实现。
2.运行时多态: 在程序运行时动态决定调用哪个方法。
1.满足继承关系。
2.父类引用指向子类对象。
多态可以由向上转型和动态绑定机制一起完成。
定义: 向上转型是指把父类的引用指向子类的实例,也就是把子类的对象当做父类的对象用,向上转型也叫隐式转型或自动转型。向上转型是安全的,因为任何子类都继承了父类的非私有成员方法。向上转型后的子类实例可以调用子类重写的父类方法和父类自己的派生方法,无法调用子类自己的方法。
向上转型的语法: 父类类型 父类引用 = new 子类类型(参数);
代码例子:
public class Subject {
private String subjectName;
public int subjectNum;
public String subjectCategory;
public void subjectMethod1() {
System.out.println("这是父类的被重写方法输出。");
}
public void subjectMethod2() {
System.out.println("这是父类的未被重写方法输出。");
}
}
public class Science extends Subject {
public String scienceChapterName;
public int scienceChapterNum;
public static void main(String[] args) {
// 实例化子类对象,并向上转型父类引用指向子类对象。
Subject scienceToSubject = new Science();
// 调用子类重写的方法以及父类的非私有方法。
scienceToSubject.subjectMethod1();
scienceToSubject.subjectMethod2();
// 调用父类的成员变量。
scienceToSubject.subjectNum = 8;
}
// 重写父类的方法。
public void subjectMethod1() {
System.out.println("这是子类的重写方法输出。");
}
//子类自己的方法。
public void scienceMethod1() {
System.out.println("这是子类的独有的方法输出。");
}
}
输出结果为:
输出结果显示子类实例向上转型,父类引用指向子类实例时可以调用子类重写的方法和父类的方法。也可以调用父类的成员属性。但如果调用子类自己的成员属性或者成员方法就会报错。
调用子类的成员属性时报的错为:scienceChapterNum cannot be resolved or is not a field.
调用子类的成员方法时报的错为:The method scienceMethod1() is undefined for the type Subject.
可以看出编译器将向上转型后的science子类实例认为是subject父类对象。
绑定机制: Java中有静态和动态两种绑定机制。
静态绑定是指在程序运行之前进行绑定,这种前期绑定由编译器和链接程序完成。动态绑定是指在程序运行期间由JVM根据对象的类型自动判断调用哪个方法。
定义: 向下转型也叫强制类型转换,就是把子类的引用指向父类的对象。子类对象向上转型再向下转型还原为子类对象之后,可以正常的调用子类独有的方法。向下转型只能把从子类对象转为父类的再还原为子类,而不能把原来的父类对象转为子类。
向上转型的语法: 子类类型 子类引用 = (子类类型)父类引用;
代码例子:
public class polymorphismTest {
public static void main(String[] args) {
// 向上转型Subject的父类引用指向子类Science的实例。
Subject scienceToSubject = new Science();
// 向下转型Science的子类引用指向父类Subject的实例,转换为父类对象的scienceToSubject对象被还原为Science子类对象。
Science subjectToScience = (Science) scienceToSubject;
// 向下转型只能还原从子类对象转为父类对象的实例,而不能把原来就是父类对象的实例转为子类的对象。
// Science subjectToScience3 = (Science)new Subject();
// 子类对象调用子类的独有方法。
subjectToScience.scienceMethod1();
}
}
输出结果为:
输出结果显示向下转型之后,从Subject父类还原为子类的scienceToSubject对象可以调用子类独有的方法。注意向下转型只能还原从子类对象转为父类对象的实例,而不能把原来就是父类对象的实例转为子类的对象。否则控制台会报 Exception in thread “main” java.lang.ClassCastException: com.csdn.test.Subject cannot be cast to com.csdn.test.Science
at com.csdn.test.polymorphismTest.main(polymorphismTest.java:14) 异常信息。
定义: 用来判断对象是否是一个类的实例,如果有就返回true如果没有就返回false。
instanceof关键字语法: 对象 instanceof 类的类型
代码例子:
public class PolymorphismTest {
public static void main(String[] args) {
// 实例化一个science子类。
Science science = new Science();
// 实例化一个subject父类。
Subject subject = new Subject();
// 向上转型把父类引用指向一个子类的实例。
Subject scienceToSubject = new Science();
// 测试instanceof运算符的判断方法。
if (subject instanceof Science) {
System.out.println("父类具有子类的特征。");
} else {
System.out.println("父类不具有子类的特征。");
}
if (science instanceof Subject) {
System.out.println("子类具有父类的特征。");
} else {
System.out.println("子类不具有父类的特征。");
}
if (scienceToSubject instanceof Subject) {
System.out.println("向上转型后的子类具有父类的特征。");
} else {
System.out.println("向上转型后的子类不具有父类的特征。");
}
if (scienceToSubject instanceof Science) {
System.out.println("向上转型后的子类具有子类的特征。");
} else {
System.out.println("向上转型后的子类不具有子类的特征。");
}
}
}
输出结果为:
输出结果显示除了当前对象是类的实例外,子类或间接子类的实例也是父类的实例。
模拟这样一个场景,有很多的学科进行考试,每个学科都有自己的考试注意事项。如果所有的学科都调用自己的考试注意方法,则要写很多的方法或者一个方法有很多的参数。这个时候使用向上转型则可以只写一个方法,参数设置为父类的对象,这样无论有多少的学科只写一个传入参数就可以了。
这个例子由一个父类Subject和两个子类Science和Math还有一个测试类组成,为了方便理解我把4个类的完整代码都放到下面。
代码例子:
public class Subject {
private String subjectName;
public int subjectNum;
public String subjectCategory;
public void subjectMethod1() {
System.out.println("这是父类的被重写方法输出。");
}
public void subjectMethod2() {
System.out.println("这是父类的未被重写方法输出。");
}
}
public class Science extends Subject {
// 重写父类的方法。
public void subjectMethod1() {
System.out.println("科学考试注意事项为:");
}
// 子类自己的方法。
public void scienceMethod1() {
System.out.println("科学考试可以带公式表。");
}
}
public class Math extends Subject {
// 重写父类的方法。
public void subjectMethod1() {
System.out.println("数学考试注意事项为:");
}
// 子类自己的方法。
public void mathMethod1() {
System.out.println("数学考试可以带计算器。");
}
}
public class PolymorphismTest {
public static void main(String[] args) {
PolymorphismTest pt = new PolymorphismTest();
Science science = new Science();
Math math = new Math();
pt.exam(science);
System.out.println("================================");
pt.exam(math);
}
// 调用方法传入的参数是方法参数的子类,执行了一次向上转型。
public void exam(Subject subject) {
// 向上转型后的子类实例可以调用在子类重写的方法。
subject.subjectMethod1();
if (subject instanceof Science) {
// 向下转型把subject对象还原为原来的子类实例。
Science subjectToScience = (Science) subject;
// 还原后的子类实例可以调用子类独有的方法。
subjectToScience.scienceMethod1();
}
if (subject instanceof Math) {
Math subjectToMath = (Math) subject;
subjectToMath.mathMethod1();
}
}
}
输出结果为:
输出结果显示根据传入参数对象的不同,对象执行的方法也不同。
定义: abstract关键字可以修饰类和方法,abstract修饰的类和方法叫做抽象类和抽象方法。
抽象类: 不能被实例化,只能被其它的类继承,继承抽象类的子类可以使用向上转型让父类的引用指向它。abstract修饰类的时候没不能和final共存,因为final修饰的类不能被继承,而抽象类不能实例化只能被继承。
抽象方法: 必须被子类进行重写,抽象方法必须在抽象类中,抽象类可以没有抽象方法。抽象方法不能有方法体。abstract修饰方法的时候不能和private、static还有final共存,因为这三个关键字修饰的方法不能被子类重写,但抽象方法必须被子类重写。
应用场景: 父类知道子类有这个方法,但不知道子类怎么实现这个方法,使用抽象关键字可以避免子类设计的随意性和父类无意义的实例化。
代码例子:
public abstract class Subject {
public abstract void subjectMethod3();
}
定义: 接口定义了某一批类所需要遵守的规范。接口不关心这些类里的内部数据,也不关心类里方法的实现细节。只规定这些类里必须提供某些方法。
为什么要有接口: Java中只支持单继承的关系,一个子类只有唯一的一个直接父类。接口可以解决一个类型中需要兼容多种类型特征的问题和多个不同类型具有相同特征的问题。
接口的访问修饰符: 只能是public或默认。
接口中的抽象方法: 可以不写abstract关键字,访问修饰符可以不写,但默认为public。实现了接口的类必须重写接口中的所有抽象方法,如果不想重写则可以把类设为抽象类,让子类去实现接口中的抽象方法。
接口中的常量: 接口中可以定义常量,默认访问修饰符为public默认关键字为static final。因为是常量,有static静态关键字,在外部可以直接用接口名.常量名的方式调用。
接口可以在不同的包创建进行跨包调用,接口不能实例化对象,只能作为指向具体实例化类的引用。
接口中的默认方法: 接口的默认方法是JDK1.8版本之后的新特性,可以在接口中用default关键字定义带方法体的默认方法。默认方法可以不被实现接口的类重写。
接口中的静态方法: 接口的静态方法是JDK1.8版本之后的新特性,可以在接口中用static关键字定义带方法体的静态方法。静态方法不能被实现接口的类重写。
默认方法可以让类有选择的重写接口的方法,让接口更灵活。静态方法则可以让接口在没有实例的情况下通过接口名调用。
下面来模拟一个场景,学习和工作都要提交作业或项目,所以我们把提交作业抽象成一个接口,来让学习类和工作类实现它。这个例子一共有1个接口和4个类,其中Study类和Work类实现了IAssignment接口,然后Subject类是Study类的子类,InterfaceTest类是测试类。
代码例子:
public interface IAssignment {
// 接口中的常量,默认前面是public static final,可以不写。
public static final int deadline = 30;
// 接口中的方法,默认前面添加public abstract,可以不写。
public abstract void submit();
// 接口的默认方法。
public default void online() {
System.out.println("IAssignment:接口的默认方法,只有工作需要线上项目。");
}
// 接口的静态方法。
public static void assignMethod() {
System.out.println("IAssignment:接口的静态方法,不需要实例化也能调用。");
}
}
public class Work implements IAssignment {
// 接口的抽象方法必须被实现的类重写。
public void submit() {
System.out.println("Work:提交工作项目。");
}
// 接口的默认方法可以被实现的类重写,重写默认方法时要把default关键字去掉。
public void online() {
System.out.println("Work:实现类的默认方法,只有工作需要线上项目。");
}
}
public abstract class Study implements IAssignment {
// 接口的抽象方法必须被实现的类重写,如果把类设为抽象类则可以不重写,但必须由其子类实现。
Study() {
System.out.println("Study:接口的抽象方法让子类Subject重写。");
}
}
public class Subject extends Study {
// 接口的抽象方法必须被实现的类重写,如果把类设为抽象类则可以不重写,但必须由其子类实现。
public void submit() {
System.out.println("Study:提交专业作业。");
}
}
public class InterfaceTest {
public static void main(String[] args) {
// 接口名可以直接调用静态方法。
IAssignment.assignMethod();
// 接口的引用指向类的实例。
IAssignment iaw = new Work();
IAssignment ias = new Subject();
// 接口的引用调用实例化类中重写的方法。
iaw.submit();
ias.submit();
// 实现接口的Work类重写了默认方法。
iaw.online();
// 如果没有重写默认方法,则只能调接口自己的默认方法。
ias.online();
}
}
输出结果为:
输出结果显示接口的静态方法assignMethod不需要用实例类,可以直接通过接口名调用。接口的抽象方法必须被重写,如IAssignment接口的submit方法,但如果不想在实现接口的类中重写这个方法,则可以把实现的类设为抽象类,如例子中的Study类实现了IAssignment接口,但是没有重写接口的抽象方法,submit抽象方法被重写在它的子类Subject里。而接口的默认方法则可以不被重写,如IAssignment接口的online方法,Work类重写了online方法,但是Study类没有重写online方法。
注: 如果一个类实现了2个接口,接口中有相同名字的方法,这个类必须要重写这个方法。如果类的父类和实现的接口有同名的方法,则默认调用父类的方法。如果一个类实现的2个接口有相同名字的常量,或者接口和父类有相同名称的常量或变量,类需要定义自己的同名成员属性。
接口中有继承关系,一个子接口可以继承多个父接口,如果父接口中有相同名字的默认方法或者常量子接口必须重写父接口的默认方法或者定义自己的同名的常量。
定义: 一个类定义在另一个类的里面或者方法的里面的类叫做内部类,包含内部类的类叫做外部类。内部类隐藏在外部类的里面更好的隐藏了信息。
分类: 成员内部类、静态内部类、方法内部类和匿名内部类。
成员内部类: 内部类获取对象实例的方法:1.外部类名.内部类名 内部类引用 = new 外部类名().new 内部类名(); 2.先实例化一个外部类的对象,外部类名.内部类名 内部类引用 = 外部类对象.new 内部类名();,然后用外部类对象来获取内部类对象。3. 用外部类对象的方法获取,外部类名.内部类名 内部类引用 = 外部类对象.方法名();。
1.内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化。
2. 内部类的访问修饰符可以是任意的。
3. 内部类可以直接访问外部类的成员,如果出现同名属性和方法,优先访问调用内部类的。
4. 可以使用外部类名.this.成员名的方式访问外部类中同名的信息。
5. 外部类访问内部类的信息,需要通过内部类的实例,无法直接访问。
6. 内部类编译后.class文件命名:外部类$内部类.class。
代码例子:
public class Laptop {
int temperature = 30;
// 外部类的返回内部类对象的方法。
public CenProUnit getCenProUnit() {
CenProUnit cenProUnit = new CenProUnit();
return cenProUnit;
}
public class CenProUnit {
int temperature = 65;
// 内部类可以直接访问外部类的成员,如果出现同名属性,优先访问内部类的。
public void getCPUTemperature1() {
System.out.println("CPU的内部类温度是:" + temperature);
}
// 可以使用外部类名.this.成员名的方式访问外部类中同名的信息。
public void getCPUTemperature2() {
System.out.println("CPU的外部类温度是:" + Laptop.this.temperature);
}
}
}
public class InnerClassTest {
public static void main(String[] args) {
// 获取内部类对象的方法,外部类名.内部类名 内部类引用 = new 外部类名().new 内部类名();。
Laptop.CenProUnit ltCPUa = new Laptop().new CenProUnit();
// 获取内部类对象的方法,先实例化一个外部类的对象,外部类名.内部类名 内部类引用 = 外部类对象.new 内部类名();。
Laptop lt = new Laptop();
Laptop.CenProUnit ltCPUb = lt.new CenProUnit();
// 获取内部类对象的方法,外部类名.内部类名 内部类引用 = 外部类对象.方法名();。
Laptop.CenProUnit ltCPUc = lt.getCenProUnit();
ltCPUa.getCPUTemperature1();
ltCPUb.getCPUTemperature1();
ltCPUc.getCPUTemperature1();
ltCPUa.getCPUTemperature2();
ltCPUb.getCPUTemperature2();
ltCPUc.getCPUTemperature2();
}
}
输出结果为:
输出结果显示内部类引用优先调用内部类的属性,需要调用外部类的同名属性可以通过外部类名.this.属性名的方法。
静态内部类:
1. 静态内部类中,只能直接访问外部类的静态成员,如果要调用非静态成员,可以通过对象实。
2. 静态内部类的对象实例化方法为:外部类名.内部类名 内部类引用 = new 外部类名.内部类名(); 可以不依赖于外部类对象。
3. 可以通过外部类名.内部类名.静态成员的方式,访问内部类中的静态成员。
4. 当内部类属性和外部类属性同名时,默认直接调用内部类中的成员。 如果需要访问外部类中的静态属性,可以通过 外部类名.属性名的方式。如果需要访问外部类中的非静态属性,可以通过 new 外部类名().属性名的方式。
代码例子:
public class Laptop {
int temperature = 30;
static int power = 100;
static class CenProUnit {
int temperature = 65;
// 内部类可以直接访问外部类的成员,如果出现同名属性,优先访问内部类的。
public void getInfo() {
System.out.println("CPU的内部类温度是:" + temperature);
// 访问外部类中的静态属性,可以通过 外部类名.属性名的方式。
System.out.println("笔记本的外部类温度是:" + new Laptop().temperature);
// 访问外部类中的非静态属性,可以通过 new 外部类名().属性名的方式。
System.out.println("笔记本的外部类电源是:" + Laptop.power);
}
}
}
public class InnerClassTest {
public static void main(String[] args) {
// 静态内部类的对象实例化方法为:外部类名.内部类名 内部类引用 = new 外部类名.内部类名();
Laptop.CenProUnit ltCPUs = new Laptop.CenProUnit();
ltCPUs.getInfo();
}
}
代码例子:
public Object getCPUTemperature1() {
//方法内部类。
class CenProUnit {
int temperature = 65;
public String getInfo() {
return "CPU的内部类温度是:" + temperature;
}
}
return new CenProUnit().getInfo();
}
}
public class InnerClassTest {
public static void main(String[] args) {
Laptop lt = new Laptop();
System.out.println(lt.getCPUTemperature1());
}
}
匿名内部类:
定义:对一个类的实例只使用一次。将类的定义与类的创建放到一起完成。
在实例对象的同时完成对于对象内容的创建。
匿名内部类的特点:
1.匿名内部类没有类型名称、实例对象名称。
2. 编译后的文件命名: 外部类$数字.class。
3. 无法使用private、public、protected、abstract、static修饰。
4. 无法编写构造方法,可以添加构造代码块。
5. 不能出现静态成员。
6. 匿名内部类可以实现接口也可以继承父类,但是不可兼得。
代码例子:
public abstract class Laptop {
int temperature = 65;
public abstract void getInfo();
}
public class InnerClassTest {
public static void main(String[] args) {
InnerClassTest ict = new InnerClassTest();
// 匿名内部类没有类型名称、实例对象名称。
ict.getTemperature(new Laptop() {
// 匿名类中重写抽象类的方法。
public void getInfo() {
System.out.println("CPU的匿名类温度是:" + temperature);
}
});
}
public void getTemperature(Laptop laptop) {
laptop.getInfo();
}
}