|
JavaSE-No.7.1——Java的继承(super关键字)
目录
1. 多态的概念
通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。
2. 多态的实现条件
在java中要实现多态,必须要满足如下几个条件:
- 在继承体系下,完成向上转型
- 子类对父类的方法进行重写
- 通过父类的引用调用重写的方法
我们先创建一个父类Flower:
class Flower {
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Flower(String name, String color) {//构造方法
this.name = name;
this.color = color;
}
@Override
public String toString() {
return "Flower{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
public void flowering() {
System.out.println(name+"开花了💐");
}
}
我们再写两个子类CherryBlossom和Rose:
class CherryBlossom extends Flower{
public CherryBlossom(String name, String color) {
super(name, color);
}
public void view() {
System.out.println("观赏"+getName()+"🌸");
}
}
class Rose extends Flower{
public Rose(String name, String color) {
super(name, color);
}
public void gift() {
System.out.println("一束"+getName()+"🌹作为礼物");
}
}
什么叫做向上转型?
把子类对象(例:Rose),赋值给父类对象(例:Flower)。
public class Test {
public static void main(String[] args) {
Rose rose = new Rose("白玫瑰","white");
Flower flower1 = rose;//向上转型
//简写
Flower flower = new Rose("红玫瑰","red");
}
}
我们通过新定义的flower
来分别调用子类,父类的方法,我们发现在调用子类特有的方法的时候报错
了。
因为:flower是Flower类型的,flower只能调用Flower中的方法。
重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为。
重写的规则
-
一般子类与父类方法原型
一致
:修饰符 返回值类型 方法名(参数列表) 要完全一致 -
被重写的方法返回值类型可以不同,但是必须是具有
父子关系
的。子类和父类被重写方法的返回值构成协变类型(子类的返回值和父类的返回值构成父子关系)
-
父类被
static
、private
修饰的方法、构造方法
都不能被重写。 -
访问权限大于等于父类中被重写的方法的访问权限。
# 注意 #
如果一个方法不想被重写,用final
修饰(密封方法)。
接下来我们要子类对父类的方法(例:flowering)进行重写
//class CherryBlossom中重写flowering
public void flowering() {
System.out.println(getName()+"开了🌸");
}
//class Rose中重写flowering
public void flowering() {
System.out.println(getName()+"开了🌹");
}
通过父类的引用调用重写的方法
这时我们再调用flowering
方法会发生什么?
public class Test {
public static void main(String[] args) {
Flower flower3 = new Flower("花田","colorful");
flower3.flowering();
Flower flower2 = new CherryBlossom("樱花","pink");
flower2.flowering();//动态绑定
Flower flower1 = new Rose("红玫瑰","red");
flower1.flowering();//动态绑定
}
}
运行结果展示:向上转型后的变量调用了子类
的flowering,发生了动态绑定(本篇文章后面会介绍什么是动态绑定)
3. 向上转型和向下转型
3.1 向上转型
直接赋值
public class Test {
public static void main(String[] args) {
Flower flower = new Rose("红玫瑰","red");
flower.flowering();
}
}
方法的传参
public class Test {
public static void func(Flower flower) {
flower.flowering();
}
public static void main(String[] args) {
func(new Rose("红玫瑰","red"));
func1(new CherryBlossom("樱花","pink"));
}
}
运行结果展示:
方法的返回值
public class Test {
public static Flower func() {
return new Rose("红玫瑰","red");
}
public static void main(String[] args) {
Flower flower = func();
}
}
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
3.2 向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
public static void main(String[] args) {
Flower flower = new Rose("红玫瑰","red");
Rose rose = (Rose) flower;//向下转型
rose.gift();
}
# 注意 #
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。
我们来看一段错误
代码。
public static void main(String[] args) {
Flower flower = new CherryBlossom("樱花","pink");
Rose rose = (Rose) flower;//向下转型,樱花时玫瑰花吗??
rose.gift();
}
instanceof
不是所有的花都是"玫瑰花",Java中为了提高向下转型的安全性,引入了 instanceof
,如果该表达式为true
,则可以安全转换。
public static void main(String[] args) {
Flower flower = new CherryBlossom("樱花","pink");
//判断 flower是不是引用了Rose这个对象
if (flower instanceof Rose) {
Rose rose = (Rose) flower;//向下转型
rose.gift();
}
flower = new Rose("红玫瑰","red");
if (flower instanceof Rose) {
Rose rose = (Rose) flower;//向下转型
rose.gift();
}
}
4.重写的设计规则
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
例如: 若干年前的手机,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。
5. 动态绑定和静态绑定
5.1 动态绑定
动态绑定: 也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
继续上面的代码,我们在main写入这样两行代码:
public class Test {
public static void main(String[] args) {
Flower flower = new Rose("红玫瑰","red");
flower.flowering();
}
}
//运行结果
红玫瑰开了🌹
我们打开Powershell窗口,使用javap -c Test
反编译一下。
编译的时候调用的是父类的方法,但是在运行时,程序发生了动态绑定
,调用了子类的方法。
# 注意事项 #
在父类的构造方法中也会发生动态绑定。但是不要这样写!!
class A {
public A() {
print();
}
public void print() {
System.out.println("A");
}
}
class B extends A {
public void print() {
System.out.println("B");
}
}
public class Example {
public static void main(String[] args) {
B bb = new B();
}
}
//运行结果
B
结论:“用尽量简单的方式使对象进入可工作状态”, 尽量不要
在构造器中调用方法, 可能会出现一些隐藏的但是又极难发现的问题。
5.2 静态绑定
静态绑定: 也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
public class Example {
public static void print() {
System.out.println("hh");
}
public static void print(int a) {
System.out.println("haha");
}
public static void main(String[] args) {
//静态绑定:编译的时候,就确定了最终要调用的方法
print();
print(1);
}
}
6. 多态的优缺点
使用多态的好处
- 能够降低代码的"圈复杂度", 避免使用大量的 if - else
小知识: 圈复杂度
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个
个数
就称为 “圈复杂度”.
我们改写一下的代码,写入几个新的子类:
class Flower {
public String name;
public String color;
public void flowering() {
System.out.println("花开了💐");
}
}
class CherryBlossom extends Flower{
@Override
public void flowering() {
System.out.println("🌸开了");
}
}
class Rose extends Flower{
@Override
public void flowering() {
System.out.println("🌹开了");
}
}
class Sunflower extends Flower{
@Override
public void flowering() {
System.out.println("🌻开了");
}
}
class Tulip extends Flower{
@Override
public void flowering() {
System.out.println("🌷开了");
}
}
如果我们要让💐按照"🌸🌻🌷🌹🌻🌷"的顺序开花,用if-else
应该怎么写?我们可以看到非常繁琐。
public class Test {
public static void func() {
CherryBlossom cherryBlossom = new CherryBlossom();
Rose rose = new Rose();
Sunflower sunflower = new Sunflower();
Tulip tulip = new Tulip();
//"🌸🌻🌷🌹🌻🌷"
String[] flowers = {"cherryBlossom","sunflower","tulip","rose","sunflower","tulip"};
for (String flower:flowers) {
if (flower.equals("cherryBlossom")) {
cherryBlossom.flowering();
} else if (flower.equals("sunflower")) {
sunflower.flowering();
} else if (flower.equals("rose")) {
rose.flowering();
} else if (flower.equals("tulip")) {
tulip.flowering();
}
}
}
}
如果我们使用多态
:
public class Test {
public static void func() {
CherryBlossom cherryBlossom = new CherryBlossom();
Rose rose = new Rose();
Sunflower sunflower = new Sunflower();
Tulip tulip = new Tulip();
//"🌸🌻🌷🌹🌻🌷"
Flower[] flowers = {cherryBlossom,sunflower,tulip,rose,sunflower,tulip};
for (Flower flower:flowers) {
flower.flowering();
}
}
}
-
可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
对于类的调用者来说(func方法), 只要创建一个新类的实例就可以了, 改动成本很低.
多态缺陷:代码的运行效率降低。
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!下一篇文章会讲述Java的抽象类的相关知识,期待大家支持,谢谢~