Java多态
编译时类型和运行时类型
理解编译时类型和运行时类型是理解多态的关键
上最直白的定义:
Java的引用变量有两个类型,一个是编译时类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定
这边搬出大佬的解释:
Person p=new Women()(Women类继承自Person类)那么,假如p的属性修饰符为public 访问属性时得到的是Person类的属性还是Women类的属性,方法调用又是哪个类?
答案:会得到Person类的属性,调用Women类的方法。为什么会这样呢?这里就需要知道什么是编译时类型和运行时类型,
Java程序状态会分为编译和运行这两种状态
编译时,JVM会在栈中静态创建基本数据变量,和引用数据变量的引用,回到刚刚那句代码,显然,p这个引用就是在编译时创建的,那么,p的编译时类型就是Person了,
当运行这句java代码时,JVM在堆中为p新建一块内存,对应new Women()这句代码,所以p的运行时类型就是Women
在栈中存放的是一些引用和一些变量,而堆内存中存放的是对象
所以:编译时期可以理解为堆内存中还没有为该对象创建内存,只是在栈中创建了一些基本类型的变量和引用,所以编译时类型就是指在new该对象之前被加载到栈中的属性或方法。而运行时类型,是指new了一个对象出来,在栈中为该对象分配了内存,此时的运行时类型也就是栈中对象的属性或方法了
Person person = new student();
这里就可以很好地理解编译时类型和运行时类型了。
编译时类型是Person,当new了一个对象之后,堆内存中产生了一个Student对象,此时的类型就是运行时类型,也就是Student类型了
class Person {
public String name;
public Person() {
name = "person";
}
public void show() {
System.out.println("person show");
}
}
class Woman extends Person {
public String name;
public Woman() {
name = "woman";
}
public void show() {
System.out.println("woman show");
}
public void myshow(){
System.out.println("++++++++++++++++++");
}
}
public class TestDemo {
// 当使用该对象引用进行调用的时候,有这么一条规则,对象调用编译时类型的属性和运行时类型的方法
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name);
person.show();
System.out.println();
Person person1 = new Woman();
System.out.println(person1.name);
person1.show();
// person1.myshow();报错
System.out.println();
Woman woman = new Woman();
System.out.println(woman.name);
woman.show();
woman.myshow();
}
}
多态
理解了上面之后,多态的产生也很简单
编译时类型和运行时类型不一致就可能产生多态
例子:
class BaseClass
{
public int book = 6;
public void base()
{
System.out.println("父类的普通方法");
}
public void test()
{
System.out.println("父类的被覆盖的方法");
}
}
public class SubClass extends BaseClass
{
//重新定义一个book实例变量隐藏父类的book实例变量
public String book = "book";
public void test()
{
System.out.println("子类的覆盖父类的方法");
}
public void sub()
{
System.out.println("子类的普通方法");
}
public static void main(String[] args)
{
// 下面编译时类型和运行时类型完全一样,因此不存在多态
BaseClass bc = new BaseClass();
// 输出 6
System.out.println(bc.book);
// 下面两次调用将执行BaseClass的方法
bc.base();
bc.test();
System.out.println("++++++++++++++++++++++++++++++++++++++++++");
// 下面编译时类型和运行时类型完全一样,因此不存在多态
SubClass sc = new SubClass();
// 输出"book"
System.out.println(sc.book);
// 下面调用将执行从父类继承到的base()方法
sc.base();
// 下面调用将执行从当前类的test()方法
sc.test();
System.out.println("++++++++++++++++++++++++++++++++++++++++++");
// 下面编译时类型和运行时类型不一样,多态发生
BaseClass ploymophicBc = new SubClass();
// 输出6 —— 表明访问的是父类对象的实例变量
System.out.println(ploymophicBc.book);
// 下面调用将执行从父类继承到的base()方法
ploymophicBc.base();
// 下面调用将执行从当前类的test()方法
ploymophicBc.test();
// 因为ploymophicBc的编译类型是BaseClass,
// BaseClass类没有提供sub方法,所以下面代码编译时会出现错误。
// ploymophicBc.sub();
}
}
子类其实是一种特殊的父类,Java允许把一个子类对象直接赋给一个父亲引用变量,无需任何类型转换,也就是向上转型
在上面的BaseClass ploymophicBc = new SubClass();中:
ploymophicBc引用变量的编译时类型是BaseClass,运行时类型是SubClass,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量,调用同一个方法时呈现出多种不同的行为特征,也就是多态
与方法不同,实例变量不具备多态性,比如上面的ploymophicBc引用变量,程序中输出它的book实例变量时,并不是输出SubClass中定义的实例变量,而是BaseClass里面的
instanof运算符
instanof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口),它用于判断前面的对象是否是后面的类,或者其子类,实现类的实例,是就返回true
注意:
instanof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系
public class InstanceofTest {
public static void main(String[] args) {
// 声明hello时使用Object类,则hello的编译类型是Object,
// Object是所有类的父类, 但hello变量的实际类型是String
Object hello = "Hello";
// String与Object类存在继承关系,可以进行instanceof运算。返回true。
System.out.println("字符串是否是Object类的实例:"
+ (hello instanceof Object));
System.out.println("字符串是否是String类的实例:"
+ (hello instanceof String)); // 返回true。
// Math与Object类存在继承关系,可以进行instanceof运算。返回false。
System.out.println("字符串是否是Math类的实例:"
+ (hello instanceof Math));
// String实现了Comparable接口,所以返回true。
System.out.println("字符串是否是Comparable接口的实例:"
+ (hello instanceof Comparable));
String a = "Hello";
// // String类与Math类没有继承关系,所以下面代码编译无法通过
// System.out.println("字符串是否是Math类的实例:"
// + (a instanceof Math));
}
}
最终总结起来:
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
使用继承的注意点
继承带来便利的同时也破坏了父类的封装性,因为子类可以直接访问父类的成员变量和方法
所以设计父类时应该注意:
-
尽量隐藏父类内部数据,设置private访问权限
-
除了需要被外部类调用的方法外,其余应该用private修饰,如果不想子类修改暴露的方法,可以用final修饰,如果希望某个方法被子类重写,但又不想被其他类访问,那么使用protected修饰
-
尽量不要在父类构造函数中调用将要被子类重写的方法
class Base
{
public Base()
{
test();
}
public void test() // ①号test()方法
{
System.out.println("将被子类重写的方法");
}
}
public class Sub extends Base
{
private String name;
public void test() // ②号test()方法
{
System.out.println("子类重写父类的方法,"
+ "其name字符串长度" + name.length());
}
public static void main(String[] args)
{
// 下面代码会引发空指针异常
Sub s = new Sub();
}
}
继承与组合
先看继承:
class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
}
public void breath()
{
beat();
System.out.println("吸气,吐气,呼吸中...");
}
}
// 继承Animal,直接复用父类的breath()方法
class Bird extends Animal
{
public void fly()
{
System.out.println("天空飞翔...");
}
}
// 继承Animal,直接复用父类的breath()方法
class Wolf extends Animal
{
public void run()
{
System.out.println("陆地奔跑...");
}
}
public class InheritTest {
public static void main(String[] args) {
Bird b = new Bird();
b.breath();
b.fly();
Wolf w = new Wolf();
w.breath();
w.run();
}
}
使用组合
class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
}
public void breath()
{
beat();
System.out.println("吸气,吐气,呼吸中...");
}
}
class Bird
{
// 将原来的父类组合到原来的子类,作为子类的一个组合成分
private Animal a;
public Bird(Animal a)
{
this.a = a;
}
// 重新定义一个自己的breath()方法
public void breath()
{
// 直接复用Animal提供的breath()方法来实现Bird的breath()方法。
a.breath();
}
public void fly()
{
System.out.println("天空飞翔...");
}
}
class Wolf
{
// 将原来的父类组合到原来的子类,作为子类的一个组合成分
private Animal a;
public Wolf(Animal a)
{
this.a = a;
}
// 重新定义一个自己的breath()方法
public void breath()
{
// 直接复用Animal提供的breath()方法来实现Wolf的breath()方法。
a.breath();
}
public void run()
{
System.out.println("陆地奔跑...");
}
}
public class CompositeTest
{
public static void main(String[] args)
{
// 此时需要显式创建被组合的对象
Animal a1 = new Animal();
Bird b = new Bird(a1);
b.breath();
b.fly();
// 此时需要显式创建被组合的对象
Animal a2 = new Animal();
Wolf w = new Wolf(a2);
w.breath();
w.run();
}
}
参考
https://blog.csdn.net/snow_7/article/details/51579278
https://blog.csdn.net/qq_29513537/article/details/60765552
https://blog.csdn.net/qq_23419401/article/details/52064871#java
网友评论