Java知识易错点(三):对象的上转型对象

本文深入探讨了Java中对象转换的概念,特别是上转型对象的使用,包括不能访问子类新增数据域和方法、调用继承及重写方法的规则,以及静态方法和静态变量的访问特性。通过示例代码详细解析了上转型对象的限制与转换后的行为,强调了构造方法在上转型中的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 对象转换概念

不同类型的基本数据类型变量之间可以进行类型转换(自动转换与强制转换),在Java中对于具有继承关系的对象类型也可以进行转换。Java允许父类类型的引用变量直接引用子类类型的对象。

2. 上转型对象

假设,A类是B类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,称对象a是对象b的上转型对象。

A a = new B();

3. 使用小结

要点1

  • 上转型对象不能访问子类新增的数据域;不能直接访问子类新增的方法(子类中定义的覆盖、隐藏方法不算新增)。只有当对象类型强制转换为子类类型,才能进行相应的调用
class  Monkey{
    void crySpeak(String s) {
        System.out.println(s);
    }
    void crawl(){
        System.out.println("crawling");
    }
}

class People extends Monkey {
    int d =0; //子类新增的数据域
    void computer(int a,int b) { //子类新增的方法
        int c=a*b;
        System.out.println(c);
    }
    @Override
    void crySpeak(String s) {
        System.out.println("***"+s+"***");
    }
}

public class Example5_10 {
    public static void main(String args[]) {
        Monkey monkey = new People();
        monkey.crySpeak("I love this game"); //调用子类,重写不算新增
        monkey.crawl();
        //monkey.computer(10,10); //上转型后,子类新增的方法失去
        //System.out.println(monkey.d);//上转型后,子类新增的变量失去
        People people = null;
        if (monkey instanceof People)
            people=(People)monkey; //把上转型对象强制转化为子类的对象
        people.computer(10,10);
        System.out.println(people.d);
    }
}
***I love this game***
crawling
100
0

在上述的例子中,父类为Monkey,子类为People。其中,在子类中,int d为新增变量,computer为新增方法,因此上转型对象无法直接调用这两个。强制转换为子类类型后,才能正常调用。

要点2

  • 上转型对象可以访问子类从父类继承来的数据域、方法或子类中对父类覆盖重写的实例方法,但不能直接访问子类中对父类隐藏重写的静态方法和对父类隐藏定义的数据域。
  • 如果子类覆盖了父类的某个实例方法后,当用上转型对象调用这个实例方法时,一定是调用子类中的这个实例方法。
  • 如果子类隐藏了父类的某个静态方法后,当用上转型对象调用这个静态方法时,一定是调用父类中的这个静态方法,而不是子类中的这个静态方法,输出的值若为静态变量也应该是父类中的静态变量。
public class MethodOverride {

    public static void main(String[] args) {
        Child c=new Child();
        System.out.println(c.getClassName());
        System.out.println("----------------");
        Parent p=new Parent();
        System.out.println(p.getClassName());
        System.out.println("----------------");
        Parent pc=new Child();
        System.out.println(pc.getClassName());
        System.out.println(pc.getX());
        System.out.println(pc.getZ());
    }
}

class Parent{
    int x =5;
    static int z = 10;
    String getClassName(){
        return "this is Parent!";
    }
    int  getX(){
        return x;
    }
    static int  getZ(){
        return z;
    }
}

class Child extends Parent{
    int x =6;
    int y =7;
    static int z =11;
    String getClassName(){
        System.out.println(x+" "+y);
        return "this is Child!";
    }

    int  getX(){
        return x;
    }

    static int  getZ(){
        return z;
    }
}
6 7
this is Child!
----------------
this is Parent!
----------------
6 7
this is Child!
6
10

Process finished with exit code 0

在上述MethodOverride文件中,父类为Parent,子类为Child。前两个对象为具体类的实例化,因此调用的是自己所属类的实例方法。
第三个为上转型对象,getClassName()是对父类的重写方法,因此调用的是子类的getClassName(),方法中的x,y为间接访问数据域,也为子类的。getX()也是一个道理。
getZ()为静态方法,子类虽然对静态变量及静态方法进行了隐藏,但无法直接访问。根据静态调用看左边的原则,调用的应该是父类的getZ(),z值也为父类的数据域。

要点3

  • 子类从父类继承来的方法如果没有被覆盖或隐藏,此方法中如果存在成员变量调用,则此调用是针对父类的成员变量的调用。
package com.codeslogan.inherit;


public class HidingDemo {
    public static void main(String[] args) {
        A  x = new B();
        System.out.println("(1) x.i is " + x.i);
        System.out.println("(2) (B)x.i is " + ((B)x).i);
        System.out.println("(3) x.j is " + x.j);
        System.out.println("(4) ((B)x).j is " + ((B)x).j);
        System.out.println("(5) x.m1() is " + x.m1());
        System.out.println("(6) ((B)x).m1() is " + ((B)x).m1());
        System.out.println("(7) x.m2() is " + x.m2());
        System.out.println("(8) x.m3() is " + x.m3());
        System.out.println("(9) x.z is " + x.z);
        System.out.println("(10) x.z is " + x.w);
    }
}

class A {
    public int i = 1;
    public static int j = 11;
    public int z = 3;
    public static int w = 13;

    public static String m1() {
        return "A's static m1";
    }

    public String m2() {
        return "A's instance m2";
    }

    public String m3() {
        return "A's instance m3" + j;
    }
}

class B extends A {
    public int i = 2;
    public static int j = 12;

    public static String m1() {
        return "B's static m1";
    }

    public String m2() {
        return "B's instance m2";
    }
}
(1) x.i is 1
//上转型对象实例数据域看父类
(2) (B)x.i is 2
//强转后看子类
(3) x.j is 11
//上转型对象静态数据域看父类
(4) ((B)x).j is 12
//强转后看子类
(5) x.m1() is A's static m1
//静态方法看父类(左)
(6) ((B)x).m1() is B's static m1
//强转后看子类
(7) x.m2() is B's instance m2
//实例方法重写,看子类
(8) x.m3() is A's instance m3 11
//继承来的方法,子类没有其对应的重写,结果看父类
//若其中有涉及数据域,无论是静态数据,还是实例数据,都应该是父类的数据域
(9) x.z is 3
//显而易见,继承而来的实例数据
(10) x.z is 13
//继承而来的静态数据

总结:

  • 继承而来(子类没有对其进行额外的操作),全部看父类的内容
  • 强转后,无条件看强转后的内容
  • 数据域默认看的是父类
  • 如果调用了上转型对象调用了父类的方法,那么对应的数据,也应该是父类的数据

要点4

  • 上转型对象即使采用父类做一个强制转换,所访问到的被覆盖的实例方法依旧是子类的
A a = new B();
a.getX();
((A)a).getX(); //a.getX();

简单来说就是,原来它的类型本来就是A,再转为A相当于没转。

package com.codeslogan.inherit;


public class TestChange {

    public static void main(String[] args) {
        Animal x=new Tiger();
        System.out.println("(1):x.news is "+x.news);
        System.out.println("(2):((Tiger)x).news is "+((Tiger)x).news);
        System.out.println("(3):x.smile() is "+x.smile());
        System.out.println("(4):((Tiger)x).smile() is "+((Tiger)x).smile());
        System.out.println("(5):x.getNews() is "+((Animal)x).getNews());
        System.out.println("(6):x.getNews() is "+x.getNews());
        System.out.println("(7):x.getMessage() is "+x.getMessage());
        System.out.println("(8):((Tiger)x).eat() is "+((Tiger)x).eat());
    }
}


class Animal{
    public String news="Animal's news";
    public String message="Animal's message";

    public static String smile(){
        return "smile from Animal";
    }

    public String getNews(){
        return news;
    }

    public String getMessage(){
        return message;
    }
}


class Tiger extends Animal{
    public String news="Tiger's  news";
    public String message="Tiger's message";

    public static String smile(){ //隐藏
        return "smile from Tiger";
    }

    public String getNews(){ //覆盖
        return news;
    }

    //新增的方法
    public  String eat(){ //新
        return "need eat meat";
    }
}
(1):x.news is Animal's news
(2):((Tiger)x).news is Tiger's  news
(3):x.smile() is smile from Animal
(4):((Tiger)x).smile() is smile from Tiger
(5):x.getNews() is Tiger's  news
(6):x.getNews() is Tiger's  news
(7):x.getMessage() is Animal's message
(8):((Tiger)x).eat() is need eat meat

经过上面的沉淀,到这里应该没有问题了,有问题底下留言

4. *构造方法

本小节讨论的是上转型对象的构造方法,以及构造方法调用实例方法所出现的情况,具体如下:


class A {
    int  value = 5;
    A( ) {
        setValue(10);
        System.out.println( "A:" + value);
    }
    void setValue(int v) {
        value = 2 * v; }
}


class B extends A {
    B( ) {
        //super();
        System.out.println( "B:" + value);
    }
    @Override
    void setValue(int v) {
        value = 5 * v; }
}


public class TestC {
    public  static  void  main(String[ ] args){
        new A( );
        new B( );
        A c =new B();
    }
}

运行答案如下:

A:20
-------------
A:50
B:50
-------------
A:50
B:50

刚刚接触这道题我是有点意外的,没想到Java还能这么考,感觉自己平常学的Java和老师教的Java不是一个东西,但是还是得默默承受(4.0万岁!!!) 话不多说,进入正题

  • 第一值输出为20,创建的是父类对象,调用其构造方法+实例方法,结果无需多言
  • 第二组为两个50,在创建B对象时,因为它是A类的子类,默认会先调用A的构造方法。但是在A的构造方法中,调用了个实例方法。根据前面的知识我们知道,如果子类对父类的实例方法进行了重写覆盖,那么优先调用的应该是子类的方法,所以value值为50。
  • 第三组的答案与第二组相同,道理同上,作为上转型只是一个障眼法。

参考资料

某海大某张姓老师

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeSlogan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值