pc网站转换成app,网站开发流程记住吧,做网站大概一个月多少工资,罗湖网站制作#x1f4ac; 欢迎讨论#xff1a;如对文章内容有疑问或见解#xff0c;欢迎在评论区留言#xff0c;我需要您的帮助#xff01; #x1f44d; 点赞、收藏与分享#xff1a;如果这篇文章对您有所帮助#xff0c;请不吝点赞、收藏或分享#xff0c;谢谢您的支持#x… 欢迎讨论如对文章内容有疑问或见解欢迎在评论区留言我需要您的帮助 点赞、收藏与分享如果这篇文章对您有所帮助请不吝点赞、收藏或分享谢谢您的支持 传播技术之美期待您将这篇文章推荐给更多对需要学习Java语言、低代码开发感兴趣的朋友让我们共同学习、成长 1. 什么是多态
1.1 什么是多态
举一个简单的例子
小滑是一个比较狡诈的人小刚是一个性格比较值的人李华比较喜欢交朋友当李华与小滑交朋友的时候就需要谨慎当李华和小刚交朋友的时候需要柔和。 同样是交朋友李华却需要表现出两种状态。
换言之多态它允许同一个行为在不同的对象上有不同的表现形式。
多态的定义
多态Polymorphism是面向对象编程的一个核心特性它允许同一个行为方法调用在不同的对象上有不同的表现形式。简单来说多态使得程序可以以统一的方式调用不同类型的对象从而提高了代码的灵活性和可扩展性。
示例
class Animal{public void eat(){System.out.println(吃饭~);}}class Dog extends Animal{// 重写父类方法public void eat(){System.out.println(吃骨头);}}public class Cat extends Animal{public void eat(){System.out.println(吃鱼);}public static void main(String[] args){// 引用类型都为 AnimalAnimal cat new Cat();Animal dog new Dog();//都调用 eat方法cat.eat();dog.eat();}
} 1.2 实现多态需要满足的条件
需要满足的提条件
继承关系 最基本的条件子类重写父类的方法通过父类对象的引用取调用重写的方法
符合上述的三个部分就会发生动态的绑定而动态的绑定是多态的基础
2. 向上转型
2.1 向上转型的本质及原理
向上转型Upcasting的本质是 子类对象可以被赋值给父类引用也就是说将一个子类对象看作是它的父类类型。这种机制基于面向对象的 继承关系 和 IS-A是一个原则即子类对象是父类对象的一种特殊形式。
向上转型的原理
继承关系子类继承父类因此子类对象自然包含父类中定义的所有方法和属性。 在向上转型时父类引用只会访问父类中声明的方法和属性而不会直接访问子类的扩展方法和属性。
运行时多态尽管父类引用只能看到父类的接口但调用方法时具体执行的是子类的重写方法。这就是 运行时多态 的体现。
示例
class Animal{public String name;public String color;protected int age;//构造器public Animal(String name,String color, int age){this.name name;this.color color;this.age age;}public void eat(){System.out.println(name 吃饭~);}public void sleep(){System.out.println(name 睡觉~);}}public class Cat extends Animal{// 构造器public Cat(String name, String color, int age){super(name,color,age);}public void eat(){System.out.println(name 吃鱼);}public void mimi() {System.out.println(喵喵~~);}public static void main(String[] args){Animal cat new Cat(小花,白色,2);// 调用子类重写的cat.eat();// 调用子类特殊的会报错cat.mimi();}}一般来说只有数据类型一样的变量才能赋值为什么这两个变量也能用等号呢
因为他们是继承关系。
2.2 使用场景及说明
直接赋值将子类对象直接赋值给父类类型的引用。方法传参在方法调用时将子类对象作为父类类型的参数传递。方法返回方法返回一个父类类型的对象但实际返回的是子类对象。
示例
package cn.nyist.animal;class Animal{public String name;public String color;protected int age;//构造器public Animal(String name,String color, int age){this.name name;this.color color;this.age age;}public void eat(){System.out.println(name 吃饭~);}public void sleep(){System.out.println(name 睡觉~);}}public class Cat extends Animal{// 构造器public Cat(String name, String color, int age){super(name,color,age);}public void eat(){System.out.println(name 吃鱼);}public void mimi() {System.out.println(喵喵~~);}public static void F(Animal a){a.eat();}public static Animal fAnimal(){return new Cat(小灰,灰色,4);}public static void main(String[] args){// 直接赋值Cat cat1 new Cat(小白,白色,3);Animal animal1 cat1;animal1.eat();// 直接赋值Animal animal2 new Cat(小花,花色,2);animal2.eat();// 方法传参Cat cat2 new Cat(小黑,黑色,3);F(cat2);// 方法返回Animal animal4 fAnimal(); animal4.eat();}}说明 Cat 类型的对象 cat1 被赋值给 Animal 类型的引用 animal1这是 向上转型。 animal1 虽然是 Animal 类型但实际指向的是 Cat 对象因此调用 animal1.eat() 时通过多态机制执行的是 Cat 类中重写的 eat() 方法。 使用 new Cat(…) 创建了 Cat 对象并将其赋值给 Animal 类型的引用 animal2。 和前面的场景一样通过多态机制调用的是 Cat 类中重写的 eat() 方法。 F 方法的参数是 Animal 类型因此当调用 F(cat2) 时Cat 类型的对象 cat2 被 向上转型 为 Animal 类型。 在方法内部a.eat() 调用的是实际对象 cat2 的 eat() 方法通过多态机制执行 Cat 类中的重写方法。 方法 fAnimal 的返回类型是 Animal但方法内部实际上返回了一个 Cat 对象。 当返回值被赋值给 Animal 类型的引用 animal4 时发生 向上转型。 调用 animal4.eat() 时通过多态机制调用了 Cat 类中重写的 eat() 方法。
向上转型的优点让代码实现更简单灵活。 向上转型的缺陷不能调用到子类特有的方法。
3. 静态绑定和动态绑定
3.1 什么是静态绑定什么是动态绑定
静态绑定也称为前期绑定(早绑定)即在编译时根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定也称为后期绑定(晚绑定)即在编译时不能确定方法的行为需要等到程序运行时才能够确定具体调用哪个类的方法。
用Java 字节码来帮助我们理解 Java 字节码中有两种常见的方法调用指令invokespecial 和 invokevirtual。这两种方法调用指令可以帮助我们区分 静态绑定 和 动态绑定。下面逐一解释。
写一个继承类型的java程序
package cn.nyist.animal;class Animal{public String name;public String color;protected int age;//构造器public Animal(String name,String color, int age){this.name name;this.color color;this.age age;}public void eat(){System.out.println(name 吃饭~);}public void sleep(){System.out.println(name 睡觉~);}}public class Cat extends Animal{// 构造器public Cat(String name, String color, int age){super(name,color,age);}public void eat(){System.out.println(name 吃鱼);}public void mimi() {System.out.println(喵喵~~);}public static void F(Animal a){a.eat();}public static Animal fAnimal(){return new Cat(小灰,灰色,4);}public static void main(String[] args){// 直接赋值Cat cat1 new Cat(小白,白色,3);Animal animal1 cat1;animal1.eat();// 直接赋值Animal animal2 new Cat(小花,花色,2);animal2.eat();// 方法传参Cat cat2 new Cat(小黑,黑色,3);F(cat2);// 方法返回Animal animal4 fAnimal();animal4.eat();}}先用 javac 编译 .java 文件生成 .class 文件。 然后使用 javap 针对 .class 文件反编译不加 .java 后缀
javac Cat.javajavap -c Cat运行后显示就是Java字节码
然后我们需要找到main方法中的字节码 invokespecial 指令绑定的是父类方法、私有方法或构造方法这些在编译时已经明确目标因此属于 静态绑定如下。
9: invokespecial #39 // Method init:(Ljava/lang/String;Ljava/lang/String;I)V
28: invokespecial #39 // Method init:(Ljava/lang/String;Ljava/lang/String;I)V
45: invokespecial #39 // Method init:(Ljava/lang/String;Ljava/lang/String;I)Vinvokevirtual 指令绑定的是普通实例方法如 eat()这些方法会在运行时根据实际对象类型决定调用的目标因此属于 动态绑定如下。
16: invokevirtual #31 // Method cn/nyist/animal/Animal.eat:()V
33: invokevirtual #31 // Method cn/nyist/animal/Animal.eat:()V
62: invokevirtual #31 // Method cn/nyist/animal/Animal.eat:()V3.2 toString动态绑定
创建 Dog 对象并打印
Dog dog new Dog(name: 小黑, age: 5);
System.out.println(dog);这里创建了一个 Dog 类型的对象并通过 System.out.println(dog) 输出。
System.out.println() 方法内部接受的是一个 Object 类型的参数也就是 dog 发生了向上转型变成了 Object 类型。
System.out.println() 的核心方法定义如下
public void println(Nullable Object x) {String s String.valueOf(x); // 将对象转为字符串synchronized (this) {print(s); // 输出字符串newLine(); // 换行}
}调用 println(Object x) 方法时dog 被向上转型为 Object 类型作为参数传递进去。
然后String.valueOf(x) 将对象 x 转为字符串。
String.valueOf(Object obj) 是一个静态方法其代码如下
public static String valueOf(Object obj) {return (obj null) ? null : obj.toString();
}这里会检查对象 obj 是否为 null 如果是 null返回字符串 “null”。 如果不是 null调用对象的 toString() 方法将对象转为字符串。
调用 toString() 方法
如果 Dog 类没有重写 toString() 方法则默认会调用 Object 类的 toString() 方法。 Object.toString() 的默认实现是
public String toString() {return getClass().getName() Integer.toHexString(hashCode());
}默认输出为 类的全限定名 对象的哈希值比如 Dog1a2b3c。
4. 向下转型
4.1 概念
向下转型Downcasting 是指将 父类的引用 转换为 子类的引用。这通常发生在需要调用子类特有的方法或属性时。
向下转型需要开发者明确知道父类引用所指向的实际对象是哪个子类因为只有当实际对象是目标子类类型时向下转型才是安全的。 public class TestAnimal {public static void main(String[] args) {Cat cat new Cat(小黑,2);Dog dog new Dog(大黄, 1);// 向上转型Animal animal cat;animal.eat();animal dog; // 注意animal 引用的是Dog类型animal.eat();// 向下转型// 程序可以通过编程但运行时抛出异常---因为animal实际指向的是狗// 现在要强制还原为猫无法正常还原运行时抛出ClassCastExceptioncat (Cat)animal;cat.mew();// animal本来指向的就是狗因此将animal还原为狗也是安全的 dog (Dog)animal;dog.bark();}}4.2 正确使用
向下转型前提是对象实际类型必须匹配向下转型的对象实际类型必须是目标类型否则会抛出 ClassCastException。
向下转型用的比较少而且不安全万一转换失败运行时就会抛异常。Java中为了提高向下转型的安全性引入了instanceof如果该表达式为true则可以安全转换
使用 instanceof 检查
if (animal instanceof Dog) {Dog dog (Dog) animal;dog.fetch();
} else {System.out.println(无法转换为 Dog 类型);
}示例
import java.util.ArrayList;class Animal {void sound() {System.out.println(动物发出声音);}
}class Dog extends Animal {void fetch() {System.out.println(狗在刨土);}
}class Cat extends Animal {void climb() {System.out.println(猫在爬树);}
}public class DowncastingExample {public static void main(String[] args) {ArrayListAnimal animals new ArrayList();animals.add(new Dog());animals.add(new Cat());for (Animal animal : animals) {if (animal instanceof Dog) {Dog dog (Dog) animal;dog.fetch(); // 输出狗在刨土} else if (animal instanceof Cat) {Cat cat (Cat) animal;cat.climb(); // 输出猫在爬树}}}
}
5. 小练
一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func
class B {public B() {// do nothingfunc();}public void func() {System.out.println(B.func());}}class D extends B {private int num 1;Overridepublic void func() {System.out.println(D.func() num);}}public class Test {public static void main(String[] args) {D d new D();}}执行结果 D.func() 0
解析
构造 D 对象的同时, 会调用 B 的构造方法.B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性num的值应该是1.所以在构造函数内尽量避免使用实例方法除了final和private方法。
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.