在Java中,为什么需要使用向上转型?

19

我已经阅读了网络上的大部分论文,但仍然无法理解为什么我们必须使用向上转型。

class Animal 
{ 
    public void callme()
    {
        System.out.println("In callme of Animal");
    }
}

class Dog extends Animal 
{ 
    public void callme()
    {
        System.out.println("In callme of Dog");
    }

    public void callme2()
    {
        System.out.println("In callme2 of Dog");
    }
}

public class UseAnimlas 
{
    public static void main (String [] args) 
    {
        Dog d = new Dog();      
        Animal a = (Animal)d;
        d.callme();
        a.callme();
        ((Dog) a).callme2();
    }
}

你可以考虑这个向上转型的例子。在这里向上转型有什么用?da都会给出相同的输出!


你在进行什么类型的上转型操作? - Ant's
9个回答

26
在大多数情况下,显式向上转型是完全不必要的,也没有任何影响。
在您的例子中,显式向上转型
    Animal a = (Animal)d;

可以用这个来替代:

    Animal a = d;    // implicit upcast

Java对象类型的隐式向上转换的目的是“遗忘”静态类型信息,以便可以在需要更一般类型的情况下使用具有特定类型的对象。这会影响编译时类型检查和重载解析,但不会影响运行时行为。
(对于原始类型,向上转换会导致转换,在某些情况下可能会导致精度损失;例如,long -> float。)
但是,存在一些情况,显式向上转换的存在会改变语句/表达式的含义。
在Java中需要使用向上转型的一个情况是当您想要强制使用特定方法重载时;例如,假设我们有重载的方法:
public void doIt(Object o)...
public void doIt(String s)...

如果我有一个字符串并且想要调用第一个重载而不是第二个,我必须这样做:
String arg = ...

doIt((Object) arg);

一个相关的案例是:
doIt((Object) null);

如果没有类型转换,代码将无法编译。我不确定这是否算作向上转型(请参见JLS 5.1.13的最后一段),但仍应该提到。

第二种情况涉及可变参数:

public void doIt(Object... args)...

Object[] foo = ...

doIt(foo);  // passes foo as the argument array
doIt((Object) foo); // passes new Object[]{foo} as the argument array.

第三种情况是在执行原始数字类型的操作时,例如:
int i1 = ...
int i2 = ...
long res = i1 + i2;           // 32 bit signed arithmetic ... might overflow
long res2 = ((long) i1) + i2; // 64 bit signed arithmetic ... won't overflow

当我发布这个问题时,我只考虑了对象引用转换,但我从未想过你提供的例子...听起来不错!!太棒了!! - Miko

13

在Java中,为什么需要使用Upcasting?

不确定您是否掌握了术语,但是这里有一段引用可以澄清:

向更通用的基类进行派生类转换。
进行从派生类到更通用的基类的转换。

以下是一个实际情况,其中它确实很重要:

class A {
}

class B extends A {
}

public class Test {

    static void method(A a) {
        System.out.println("Method A");
    }

    static void method(B b) {
        System.out.println("Method B");
    }

    public static void main(String[] args) {
        B b = new B();
        method(b);                      // "Method B"

        // upcasting a B into an A:
        method((A) b);                  // "Method A"
    }
}

还有一种更微妙的情况(与访问修饰符相关)在这里描述:Java Oddity: How an upcast can save the day


更普遍的情况是将子类型转换为超类型。 - Paŭlo Ebermann

5

当您有重载方法且不想调用特定的方法时,像aioobe所写的那样,向上转型可能是必要的。(您可以在此处将类型为A的新变量分配给它。)

另一个需要向上转型的例子涉及 ?:运算符,但我现在记不清了。有时您需要在可变参数方法的情况下进行向上转型:

public void doSomething(Object...  bla) {}

如果我们想将一个Object[]数组作为单个参数传递(而不是将其对象作为单独的参数),我们必须编写以下代码之一:
doSomething((Object)array);

或者

doSomething(new Object[]{array});

5
在你的例子中,向上转型没有任何意义(事实上我无法想象在任何情况下都有意义),应该避免使用它,因为它只会让开发人员感到困惑。一些IDE(例如IntelliJ)将在此行发出警告并建议删除向上转型。
编辑:此代码给出相同结果,因为Java中的所有对象方法都是虚拟的,这意味着目标方法是根据运行时对象的实际类型而不是引用类型来发现的。尝试使`callme()`静态并看看会发生什么。

是的,你说得对,我的示例中不需要上转型。但我仍然不确定在哪些情况下需要使用以及使用什么样的场景? - Miko
好的,正如我所说,我从来没有使用过向上转型,多态处理了这个问题。也许有时候为了可读性会用到,但我对此表示怀疑。请看我的关于“静态”方法的编辑。 - Tomasz Nurkiewicz
如果将callme()方法改为静态的... a.callme()将导致基类.. - Miko
1
是的,因为静态方法在编译时解析,而普通(虚拟)方法在运行时解析。编译器无法确定真实对象类型,因此使用引用类型。 - Tomasz Nurkiewicz

3

考虑一种场景,银行是一个提供获取利率的方法的类。但是,不同的银行的利率可能会有所不同。例如,SBI、ICICI和AXIS银行分别提供8.4%、7.3%和9.7%的利率。

class Bank{  
    float getRateOfInterest(){return 0;}  
}

class SBI extends Bank{  
    float getRateOfInterest(){return 8.4f;}  
}

class ICICI extends Bank{  
    float getRateOfInterest(){return 7.3f;}  
}

class AXIS extends Bank{  
    float getRateOfInterest(){return 9.7f;}  
}  

class TestPolymorphism{  
    public static void main(String args[]){  
        Bank b;  
        b=new SBI();  
        System.out.println("SBI Rate of Interest: "+b.getRateOfInterest());  
        b=new ICICI();  
        System.out.println("ICICI Rate of Interest: "+b.getRateOfInterest());  
        b=new AXIS();  
        System.out.println("AXIS Rate of Interest: "+b.getRateOfInterest());  
    }  
}

输出:

SBI Rate of Interest: 8.4
ICICI Rate of Interest: 7.3
AXIS Rate of Interest: 9.7

那么有什么区别呢?如果我写了 SBI sbiBank = new SBI(); sbiBank.getRateOfInterest(),我会得到同样的结果,这里的重点在哪里呢? - Tanvir

2

Upcasting的简单解释是...

有两个类,一个是父类(Demo1),另一个是它的子类(Demo)。

  1. If Method run1 does not have parameters then the output of method will be same. There is no use of Upcasting.

     class Demo1{
    
     void run1(){
     System.out.println("hello");  
     }
     }
    
    class Demo extends Demo1 {
    
    void run1(){
    System.out.println("hello1");  
    }
    
    public static void main(String args[]){
    
     Demo d = new Demo();
     d.run1();
    
     Demo1 d1 = new Demo();
     d1.run1();
    
      }
      }
    

    Output for d.run1(); and d1.run1(); will be same.

     output: hello1
    
    1. If we uses Parameterised overloading then the use of upcasting can be seen clearly. There is the sense for Upcasting.

        class Demo1{
      
        void run1(int a){
      
        System.out.println(a);  
        }
        }
      
       class Demo extends Demo1 {
      
        void run1(String b){
      
        System.out.println(b);  
        }
      
        public static void main(String args[]){
      
        Demo d = new Demo();
        d.run1("b");
      
        Demo1 d1 = new Demo();
        d1.run1(2);
      
      }
      }
      

d.run1();d1.run1();的输出结果将会不同。

output: b
        2

2

一些答案:

  • 向一个接受更通用对象的方法传递一个特定对象时,隐式进行向上转型。在你的例子中,当你将一个Dog传递给一个接受Animal的方法时,即使你没有使用括号显式地进行向上转型,也会发生向上转型。
  • 在方法重载的场景中,你可能会遇到一些通用和特定的方法。在你的例子中,想象一下一个接受Dog的方法和另一个同名方法接受Animal。如果出于某种原因你需要调用通用版本,你需要显式地向上转型你的对象。
  • 显式的向上转型可以作为文档的一种形式。如果在代码的某个点上,你不再需要将一个Dog称为Dog,而是继续将它作为Animal处理,那么进行向上转型可能是一个好主意,让其他开发人员了解正在发生什么。

0

当我们执行向上转型时,只能访问被覆盖的方法,而不能访问子类的所有方法。

示例代码:

//class 1
public class Class1 {
  protected int value;

  public Class1(int value) {
    this.value = value;
  }

  protected int getValue() {
    return value;
  }
}

// class 2
public class Class2 extends Class1 {

  public Class2(int value) {
     super(value);
  }

  @Override
  public int getValue() {
    return super.getValue()+1;
  }

  public int getHundred() {
    return 100;
  }
}



//Main method
public class Main {
  public static void main(String args[]) {
    Class1 cls2 = new Class2(10);
    cls2.getValue();
    cls2.getHundred() // gives compile error
  }
}

0

鉴于Java的对象方法默认为虚拟方法,我根本看不出进行向上转型的任何用处。

你在哪里看到有教程报告需要进行向上转型?这是我第一次听说Java中需要这样做。(请注意,在C#或C++中它有意义,但通常是设计不良的标志。)


抱歉我之前没有提到向上转型是必要的!但我必须理解为什么我们需要使用这个概念?向上转型的概念有什么用处? - Miko
@Mayilarun - “抱歉我从未提到过向上转型是必要的!!”实际上,当你在问题中说“为什么我们必须使用向上转型”时,你确实说了它是必要的。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接