Downcasting em Java

179

A upcasting é permitida em Java, mas a downcasting gera um erro de compilação.

O erro de compilação pode ser removido adicionando uma conversão, mas seria interrompido no tempo de execução.

Nesse caso, por que o Java permite o downcasting se não pode ser executado no tempo de execução?
Existe algum uso prático para esse conceito?

public class demo {
  public static void main(String a[]) {
      B b = (B) new A(); // compiles with the cast, 
                         // but runtime exception - java.lang.ClassCastException
  }
}

class A {
  public void draw() {
    System.out.println("1");
  }

  public void draw1() {
    System.out.println("2");
  }
}

class B extends A {
  public void draw() {
    System.out.println("3");
  }
  public void draw2() {
    System.out.println("4");
  }
}
Guerreiro
fonte
9
Um exemplo de trecho de código mais o erro tornaria essa uma pergunta melhor para as pessoas que estão tentando aprender os conceitos.
Bob Cross
3
+1 no comentário de Bob. A questão não está clara.
Jon Skeet
Vejo o exemplo acima, obtido em velocityreviews.com/forums/t151266-downcasting-problem.html, que já tem algumas boas respostas.
PhiLho
2
@PhiLho - A principal intenção de Joel era obter todas as ótimas perguntas e respostas sob o mesmo guarda-chuva. Não importa se a pergunta / código / respostas já foram postadas em outros sites. Espero que você entenda, ouça os podcasts de Joel.
Omnipotent
Edite isso para que os trechos de código sejam todos recuados por quatro espaços. Isso irá corrigir a formatação.
magro

Respostas:

298

A downcasting é permitida quando existe a possibilidade de êxito no tempo de execução:

Object o = getSomeObject(),
String s = (String) o; // this is allowed because o could reference a String

Em alguns casos, isso não terá êxito:

Object o = new Object();
String s = (String) o; // this will fail at runtime, because o doesn't reference a String

Quando um elenco (como este último) falha no tempo de execução, um ClassCastExceptionserá lançado.

Em outros casos, ele funcionará:

Object o = "a String";
String s = (String) o; // this will work, since o references a String

Observe que algumas transmissões não serão permitidas no momento da compilação, porque nunca terão êxito:

Integer i = getSomeInteger();
String s = (String) i; // the compiler will not allow this, since i can never reference a String.
Joachim Sauer
fonte
Object o = new Object(); String s = (String) o;Está funcionando bem para mim ..: O Como?
Asif Mushtaq
@ Desconhecido: não deveria. Verifique novamente se você realmente compilou e executou essa versão e, se ainda conseguir reproduzi-la, poste uma pergunta separada (com um SSCCE ).
Joachim Sauer
@JoachimSauer, o que você quer dizer com essa versão? Eu estou usando o Java 8.
Asif Mushtaq
1
@ Desconhecido: quero dizer que o código que você postou não deve ser executado (ele será compilado, mas lançará uma exceção em tempo de execução). Esses comentários não são o espaço para depurar isso. Envie uma pergunta separada.
Joachim Sauer
Como a transmissão falha no tempo de execução? Define a referência do objeto de destino como nula? Lança uma exceção?
CygnusX1 18/03
17

Usando seu exemplo, você pode fazer:

public void doit(A a) {
    if(a instanceof B) {
        // needs to cast to B to access draw2 which isn't present in A
        // note that this is probably not a good OO-design, but that would
        // be out-of-scope for this discussion :)
        ((B)a).draw2();
    }
    a.draw();
}
Rolf Rander
fonte
Acabei de aprender a importância da instância de quando minha classe abstrata estava sendo estendida por várias classes e queria usar métodos exclusivos dessas classes, enquanto me referia ao tipo de classe abstrata. Não usando instanceof eu tinha exceção de elenco de classe
Tarun
16

Eu acredito que isso se aplica a todos os idiomas estaticamente tipados:

String s = "some string";
Object o = s; // ok
String x = o; // gives compile-time error, o is not neccessarily a string
String x = (String)o; // ok compile-time, but might give a runtime exception if o is not infact a String

O typecast efetivamente diz: assuma que essa é uma referência à classe de elenco e use-a como tal. Agora, digamos que o é realmente um número inteiro, supondo que essa seja uma string não faz sentido e dará resultados inesperados; portanto, é necessário haver uma verificação de tempo de execução e uma exceção para notificar o ambiente de tempo de execução de que algo está errado.

No uso prático, você pode escrever código trabalhando em uma classe mais geral, mas convertê-lo em uma subclasse se souber qual é a subclasse e precisar tratá-lo como tal. Um exemplo típico é substituir Object.equals (). Suponha que tenhamos uma aula para carro:

@Override
boolean equals(Object o) {
    if(!(o instanceof Car)) return false;
    Car other = (Car)o;
    // compare this to other and return
}
Rolf Rander
fonte
Eu gosto da palavra Realmente e irei editar sua postagem para torná-la mais aparente.
Charaf JRA
5

Todos nós podemos ver que o código que você forneceu não funcionará em tempo de execução. Isso porque sabemos que a expressão nuncanew A() pode ser um objeto do tipo .B

Mas não é assim que o compilador o vê. No momento em que o compilador está verificando se o elenco é permitido, ele apenas vê o seguinte:

variable_of_type_B = (B)expression_of_type_A;

E como outros demonstraram, esse tipo de elenco é perfeitamente legal. A expressão à direita poderia muito bem ser avaliada para um objeto do tipo B. O compilador vê isso Ae Btem uma relação de subtipo, portanto, com a visualização "expressão" do código, a conversão pode funcionar.

O compilador não considera o caso especial quando sabe exatamente que tipo de objeto expression_of_type_Arealmente terá. Ele apenas vê o tipo estático como Ae considera que o tipo dinâmico pode ser Aou qualquer descendente de A, inclusive B.

Rob Kennedy
fonte
3

Nesse caso, por que o Java permite downcasting se não pode ser executado no tempo de execução?

Eu acredito que isso ocorre porque não há como o compilador saber em tempo de compilação se o elenco será bem-sucedido ou não. Para o seu exemplo, é simples ver que o elenco falhará, mas há outros momentos em que não é tão claro.

Por exemplo, imagine que os tipos B, C e D estendem o tipo A e, em seguida, um método public A getSomeA()retorna uma instância de B, C ou D, dependendo de um número gerado aleatoriamente. O compilador não pode saber para qual tipo de tempo de execução exato será retornado por esse método; portanto, se você converter os resultados posteriormente B, não há como saber se a conversão será bem-sucedida (ou falhará). Portanto, o compilador deve assumir que as conversões serão bem-sucedidas.

matt b
fonte
2

@ Pôster original - ver comentários em linha.

public class demo 
{
    public static void main(String a[]) 
    {
        B b = (B) new A(); // compiles with the cast, but runtime exception - java.lang.ClassCastException 
        //- A subclass variable cannot hold a reference to a superclass  variable. so, the above statement will not work.

        //For downcast, what you need is a superclass ref containing a subclass object.
        A superClassRef = new B();//just for the sake of illustration
        B subClassRef = (B)superClassRef; // Valid downcast. 
    }
}

class A 
{
    public void draw() 
    {
        System.out.println("1");
    }

    public void draw1() 
    {
        System.out.println("2");
    }
}

class B extends A 
{
    public void draw() 
    {
        System.out.println("3");
    }

    public void draw2() 
    {
        System.out.println("4");
    }
}
Alok Sharma
fonte
2

O downcast funciona no caso em que estamos lidando com um objeto upcasted. Upcasting:

int intValue = 10;
Object objValue = (Object) intvalue;

Portanto, agora essa objValuevariável sempre pode ser baixada para, intporque o objeto que foi lançado é um Integer,

int oldIntValue = (Integer) objValue;
// can be done 

mas porque objValueé um Objeto para o qual não pode ser convertido, Stringporque intnão pode ser convertido para String.

Uday Reddy
fonte
0

O downcasting é muito útil no seguinte snippet de código que eu uso isso o tempo todo. Provando assim que o downcasting é útil.

private static String printAll(LinkedList c)
{
    Object arr[]=c.toArray();
    String list_string="";
    for(int i=0;i<c.size();i++)
    {
        String mn=(String)arr[i];
        list_string+=(mn);
    }
    return list_string;
}

Eu armazeno String na lista vinculada. Quando recupero os elementos da lista vinculada, os objetos são retornados. Para acessar os elementos como Strings (ou qualquer outro Objeto de Classe), o downcast me ajuda.

Java nos permite compilar código de downcast confiando em que estamos fazendo a coisa errada. Ainda assim, se os humanos cometem um erro, ele é pego em tempo de execução.

Drishti
fonte
O uso de coleções não genéricas em Java é o equivalente a void*ponteiros em C ++. Não me parece uma boa ideia.
Jezor
0

Considere o exemplo abaixo

public class ClastingDemo {

/**
 * @param args
 */
public static void main(String[] args) {
    AOne obj = new Bone();
    ((Bone) obj).method2();
}
}

class AOne {
public void method1() {
    System.out.println("this is superclass");
}
}


 class Bone extends AOne {

public void method2() {
    System.out.println("this is subclass");
}
}

aqui criamos o objeto da subclasse Bone e o atribuímos à referência de superclasse AOne e agora a referência de superclasse não sabe sobre o método method2 na subclasse, ou seja, Bone durante o tempo de compilação. a referência resultante pode saber sobre a presença de métodos na subclasse, ou seja, Bone

ZohebSiddiqui
fonte
AOne parece um pouco confuso. Por favor, considere mudar seus nomes de classe para cão e do animal ou algo
Kartik Chugh
0

Para fazer downcasting em Java e evitar exceções em tempo de execução, faça uma referência ao seguinte código:

if (animal instanceof Dog) {
  Dog dogObject = (Dog) animal;
}

Aqui, Animal é a classe dos pais e Dog é a classe dos filhos.
instanceof é uma palavra-chave usada para verificar se uma variável de referência contém um determinado tipo de referência de objeto ou não.

user11949964
fonte
0

A transformação de downcasting de objetos não é possível. Somente

DownCasting1 _downCasting1 = (DownCasting1)((DownCasting2)downCasting1);

é possível

class DownCasting0 {
    public int qwe() {
        System.out.println("DownCasting0");
        return -0;
    }
}

class DownCasting1 extends DownCasting0 {
    public int qwe1() {
        System.out.println("DownCasting1");
        return -1;
    }
}

class DownCasting2 extends DownCasting1 {
    public int qwe2() {
        System.out.println("DownCasting2");
        return -2;
    }
}

public class DownCasting {

    public static void main(String[] args) {

        try {
            DownCasting0 downCasting0 = new DownCasting0();
            DownCasting1 downCasting1 = new DownCasting1();
            DownCasting2 downCasting2 = new DownCasting2();

            DownCasting0 a1 = (DownCasting0) downCasting2;
            a1.qwe(); //good

            System.out.println(downCasting0 instanceof  DownCasting2);  //false
            System.out.println(downCasting1 instanceof  DownCasting2);  //false
            System.out.println(downCasting0 instanceof  DownCasting1);  //false

            DownCasting2 _downCasting1= (DownCasting2)downCasting1;     //good
            DownCasting1 __downCasting1 = (DownCasting1)_downCasting1;  //good
            DownCasting2 a3 = (DownCasting2) downCasting0; // java.lang.ClassCastException

            if(downCasting0 instanceof  DownCasting2){ //false
                DownCasting2 a2 = (DownCasting2) downCasting0;
                a2.qwe(); //error
            }

            byte b1 = 127;
            short b2 =32_767;
            int b3 = 2_147_483_647;
//          long _b4 = 9_223_372_036_854_775_807; //int large number max 2_147_483_647
            long b4 = 9_223_372_036_854_775_807L;
//          float _b5 = 3.4e+038; //double default
            float b5 = 3.4e+038F; //Sufficient for storing 6 to 7 decimal digits
            double b6 = 1.7e+038;
            double b7 = 1.7e+038D; //Sufficient for storing 15 decimal digits

            long c1 = b3;
            int c2 = (int)b4;

            //int       4 bytes     Stores whole numbers from -2_147_483_648 to 2_147_483_647
            //float     4 bytes     Stores fractional numbers from 3.4e−038 to 3.4e+038. Sufficient for storing 6 to 7 decimal digits
            float c3 = b3; //logic error
            double c4 = b4; //logic error


        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

}
Aliaksandr Shpak
fonte