编程中的多态是一种重要概念,也是成功的关键之一。掌握多态的知识能够提高我们在编程工作和日常生活中的多元性、灵活性和创造性。尝试去扩大你的思维空间并应用到实际问题中,你一定会找到更多成功的机会。
一、什么是多态?
很多人蒙了,多态?是指生态环境有很多中那个多态么?其实并不是。
在Java中,多态是指同一个类型的对象,在不同的情况下所呈现出的不同行为。它与继承和方法重写有关,允许使用父类(基类)引用来引用子类(派生类) 对象,从而使代码更加灵活和可扩展。通过多态性,我们可以编写出更专业、更通用的代码。
二、多态的意义是什么?
多态性是Java面向对象编程中的一个重要概念,其意义主要体现在以下几个方面:
(1)简化代码:通过多态的机制,可以将具有相同特征的类用同样的方式来处理,从而大大简化了代码的编写和维护。
(2)增强灵活性:利用多态的特性,程序不必针对具体类型编写过多的分支逻辑,从而增加程序的灵活性和可扩展性。
(3)提高代码的可读性:使用多态可以使代码更加简洁、易读、易懂,从而提高了代码的可读性和可维护性。
(4)实现接口隔离原则:通过多态,程序只依赖于接口而不依赖于具体实现,这符合编程中的接口隔离原则,降低了代码耦合度。
(5)减少类型转换:多态能够避免程序中频繁的类型转换,提高了程序的效率和性能。
总结(划重点):
多态性是Java面向对象编程中非常重要的一种设计思想和技术手段。它使得程序更加简单、灵活、易读,能够满足复杂需求并降低代码的耦合度,是面向对象程序设计中不可或缺的一环。
三、如何实现多态?
1、转型
(1)向上转型
什么是向上转型呢?下面来看一段代码:
class Animal {
public void run() {
System.out.println("Animal::run");
}
}
class Dog extends Animal{
public void run() {
System.out.println("Dou::run");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.run();
}
}
这段代码编译的时候没问题,是调用父类的run
方法,但是在运行时,出现调用子类run
的情况,我们称这种为向上转型。向上转型之后,无法调用子类特殊的方法,例如下面代码:
class Animal {
public void run() {
System.out.println("Animal::run");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("Dou::eat");
}
public void run() {
System.out.println("Dou::run");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.run();
}
}
如果此时再在main
函数里面通过animal
调用eat方法时做不到的,会报错。
总结(划重点):
通过向上转型,我们可以实现多态性,也就是通过统一的接口来调用具有不同行为的对象,从而增加了程序的灵活性和可读性。
需要注意的是,向上转型只能转换为父类或者实现接口的类型,不能转换为其他任意类型。而且向上转型之后,不能再直接使用子类中独有的方法或属性。
(2)向下转型
那么,承接上文,怎么才能调用子类独有的方法或者属性呢?有的同志很聪明啊,想着:我把animal
这个变量,强制转换成Dog
类型的,不久可以调用Dog
里面特殊的方法了吗,这种方法固然可以,如下:
class Animal {
public void run() {
System.out.println("Animal::run");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("Dou::eat");
}
public void run() {
System.out.println("Dou::run");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
Dog dog = (Dog)animal;
dog.eat();
}
}
这就是将 animal
强转成Dog
类型,不得不说,很聪明,但是也存在一定的风险,如果我再加一个Cat
类,就很有可能出现问题,如下:
class Cat extends Animal {
public void catchMice() {
System.out.println("Cat::catchMice");
}
public void run() {
System.out.println("Cat::run");
}
}
main方法里面我这么写:
public static void main(String[] args) {
Animals animals = new Dog();
Dogs dog = (Dogs)animal;
dogs.eat();
Cat cat = (Cat)animal;//①
cat.catchMice();
}
乍一看:①这里将animal
强转为Cat
类型,然后再调用cat里面的catchMice()
方法,没问题呀,有啥问题?但是,我只能送一张图片给你
想到问题所在了么?不能强转的原因是因为Cat
和Dog
都是子类呀,子类是不能互相转型的,所以我们要这样子写才更安全:
public static void main(String[] args) {
Animals animals = new Dogs();
Dogs dogs = (Dogs)animals;
dogs.eat();
animals = new Cat();
if (animals instanceof Cat) {
Cat cat = (Cat)animals;
cat.catchMice();
}
}
instanceof
:是一个关键字,判断animals
是否是Cat
类型的,加上会更安全点。
总结(划重点):
向下转型是指将一个父类类型的变量转换为子类类型的变量,这种转换需要使用强制类型转换运算符。向下转型在某些情况下是必要的,但同时也存在一些风险和不安全因素:
(1)运行时出错:如果错误地将一个对象向下转型为一个不兼容的类,那么在程序运行时会抛出ClassCastException异常。如果没有足够的检查或假设,则此错误很难及早检测出来,导致程序崩溃。
(2)不可控的Null指针:在向下转型时,如果对象是null,则会抛出NullPointerException异常。由于代码中的null检查可能会被忽略或者漏掉,所以这种异常很难调试和修复。
(3)破坏代码设计:向下转型可能会暴露复杂的类型层次结构,并破坏了程序的封装性和抽象性。这样,代码的可读性、可维护性和可重用性都受到影响。
因此,向下转型确实是一种潜在的危险操作,需要谨慎使用。如果不确定对象的具体类型,就应该避免向下转型。如果必须执行向下转型,那么应该进行必要的检查和判断,如instanceof运算符。同时,在设计程序时应该尽量避免频繁使用向下转型,采用更安全、可靠的高层抽象来代替底层细节,从而提高代码的稳定性和可靠性。
2、方法的重写
(1)重写的条件有什么?
重写一共有三个条件:
a、返回值类型一样
b、方法名称一样
c、方法的参数一样
(2)哪些情况下不能发生重写?
a、被static
和final
修饰的方法不能被重写
b、父类没有的方法不可进行重写
(3)子类的访问修饰限定符要大于等于父类的修饰限定符
3、通过父类引用,调用这个重写的方法。运行时绑定
(1)静态绑定:
在Java中,静态绑定是指在编译时确定方法或变量的具体访问对象,即通过变量的类型来决定要调用的具体方法或变量。通常情况下,在使用类的成员时,Java会根据变量的声明类型为其分配一块地址空间,当类的实例化对象被赋值给变量时,该变量就只能访问它所声明的成员。
与之相对应的是动态绑定,也叫运行时绑定,是指在程序执行期间才能确定要调用的具体方法或变量。通常情况下,当存在多个同名属性或方法时,Java会根据实际对象类型动态选择调用哪一个版本的方法或变量。这种机制实现了多态性。
(2)动态绑定:
在Java中,一个子类可以重写其父类的方法,并且当通过父类引用调用该方法时,会根据实际对象类型的不同而动态地绑定到相应的子类实现,这个过程称为运行时绑定,也叫动态绑定或者多态。