Даункинг в Java

179

Upcasting разрешен в Java, однако downcasting дает ошибку компиляции.

Ошибка компиляции может быть удалена путем добавления приведения, но она все равно прервется во время выполнения.

В этом случае, почему Java допускает даункастинг, если он не может быть выполнен во время выполнения?
Есть ли практическое применение этой концепции?

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");
  }
}
воин
источник
9
Пример фрагмента кода плюс ошибка сделали бы этот вопрос лучше для людей, которые пытаются изучить концепции.
Боб Кросс
3
+1 за комментарий Боба. Вопрос не ясен вообще.
Джон Скит
Я вижу, пример выше взят из speedreviews.com/forums/t151266-downcasting-problem.html, в котором уже есть несколько хороших ответов.
PhiLho
2
@PhiLho - Главное намерение Джоэла состояло в том, чтобы собрать все замечательные вопросы и ответы под одним общим зонтиком. Неважно, если вопрос / код / ​​ответы уже размещены на некоторых других сайтах. Я надеюсь, что вы поняли, иначе послушайте подкасты Джоэла.
Всемогущий
Пожалуйста, отредактируйте это так, чтобы все фрагменты кода имели отступ в четыре пробела. Это исправит форматирование.
тонкий

Ответы:

298

Даункинг допускается, когда есть вероятность того, что он преуспеет во время выполнения:

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

В некоторых случаях это не удастся:

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

Если во время выполнения ClassCastExceptionпроизойдет сбой приведения (например, последнего), будет сгенерировано.

В остальных случаях это будет работать:

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

Обратите внимание, что некоторые приведения будут запрещены во время компиляции, потому что они никогда не будут успешны вообще:

Integer i = getSomeInteger();
String s = (String) i; // the compiler will not allow this, since i can never reference a String.
Йоахим Зауэр
источник
Object o = new Object(); String s = (String) o;У меня нормально работает ..: O как?
Асиф Муштак
@ Не знаю: не должно. Дважды проверьте, что вы действительно скомпилировали и запустили эту версию, и если вы все еще можете воспроизвести ее, опубликуйте отдельный вопрос (с помощью SSCCE ).
Иоахим Зауэр
@JoachimSauer, что вы подразумеваете под этой версией? Я использую Java 8.
Асиф Муштак
1
@UnKnown: я имею в виду, что отправленный вами код не должен запускаться (он будет компилироваться, но выдает исключение во время выполнения). Эти комментарии не место для отладки этого. Пожалуйста, оставьте отдельный вопрос.
Иоахим Зауэр
Как происходит сбой приведения во время выполнения? Устанавливает ссылку на целевой объект на ноль? Выдает исключение?
CygnusX1
17

Используя ваш пример, вы можете сделать:

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();
}
Рольф Рандер
источник
Я только что узнал важность instanceof, когда мой абстрактный класс расширялся несколькими классами, и я хотел использовать исключительные методы этих классов, обращаясь к типу абстрактного класса. Не используя instanceof у меня было исключение приведения класса
Тарун
16

Я считаю, что это относится ко всем статически типизированным языкам:

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 на самом деле является целым числом, предполагая, что это строка не имеет смысла и даст неожиданные результаты, поэтому необходимо выполнить проверку во время выполнения и исключение, чтобы уведомить среду выполнения о том, что что-то не так.

На практике вы можете написать код, работающий с более общим классом, но привести его к подклассу, если вы знаете, что это за подкласс, и вам нужно обращаться с ним как с таковым. Типичным примером является переопределение Object.equals (). Предположим, у нас есть класс для автомобиля:

@Override
boolean equals(Object o) {
    if(!(o instanceof Car)) return false;
    Car other = (Car)o;
    // compare this to other and return
}
Рольф Рандер
источник
Мне нравится слово «Действительно», и я отредактирую ваше сообщение, чтобы сделать его более
подходящим
5

Мы все видим, что предоставленный вами код не будет работать во время выполнения. Это потому, что мы знаем, что выражение никогда неnew A() может быть объектом типа .B

Но это не то, как это видит компилятор. К тому времени, когда компилятор проверяет, разрешено ли приведение, он просто видит это:

variable_of_type_B = (B)expression_of_type_A;

И, как показали другие, такого рода актеры совершенно законны. Выражение справа может очень хорошо оценить объект типа B. Компилятор видит это Aи Bимеет отношение подтипа, так что с представлением «выражения» кода приведение может работать.

Компилятор не рассматривает особый случай, когда он точно знает , какой тип объекта expression_of_type_Aбудет действительно иметь. Он просто видит статический тип как Aи считает, что динамический тип может быть Aили любым потомком A, в том числе B.

Роб Кеннеди
источник
3

В этом случае, почему Java допускает даункастинг, если он не может быть выполнен во время выполнения?

Я полагаю, что это потому, что у компилятора нет возможности узнать во время компиляции, будет ли приведение выполнено успешно или нет. Для вашего примера просто увидеть, что приведение не удастся, но в других случаях это не так ясно.

Например, представьте, что все типы B, C и D расширяют тип A, а затем метод public A getSomeA()возвращает экземпляр B, C или D в зависимости от случайно сгенерированного числа. Компилятор не может знать, какой именно тип времени выполнения будет возвращен этим методом, поэтому, если вы позднее приведете результаты к B, нет способа узнать, будет ли приведение выполнено успешно (или не получится). Поэтому компилятор должен предполагать, что приведение завершится успешно.

Мэтт Б
источник
2

@ Оригинальный постер - см. Встроенные комментарии.

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");
    }
}
Алок Шарма
источник
2

Downcast работает в том случае, когда мы имеем дело с объектом, находящимся на подъеме. Приведение к базовому типу:

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

Так что теперь эта objValueпеременная всегда может быть уменьшена, intпотому что объект, который был приведен Integer,

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

но поскольку objValueэто объект, он не может быть приведен, Stringпотому что intне может быть приведен String.

Удай Редди
источник
0

Даункастинг очень полезен в следующем фрагменте кода, которым я пользуюсь все время. Таким образом, доказательство того, что уныние является полезным.

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;
}

Я храню строку в связанном списке. Когда я получаю элементы связанного списка, объекты возвращаются. Чтобы получить доступ к элементам в виде строк (или любых других объектов класса), мне помогает downcasting.

Java позволяет нам компилировать унылый код, доверяя нам, что мы поступаем неправильно. Тем не менее, если люди совершают ошибку, она ловится во время выполнения.

Drishti
источник
Использование неуниверсальных коллекций в Java является эквивалентом void*указателей в C ++. Это совсем не похоже на хорошую идею.
Jezor
0

Рассмотрим пример ниже

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");
}
}

здесь мы создаем объект подкласса Bone и назначаем его ссылке на суперкласс AOne, и теперь ссылка на суперкласс не знает о методе method2 в подклассе, т.е. Bone во время компиляции. Поэтому нам нужно уменьшить эту ссылку суперкласса до ссылки на подкласс результирующая ссылка может знать о наличии методов в подклассе, т.е. Bone

ZohebSiddiqui
источник
Один выглядит несколько запутанным. Пожалуйста, подумайте об изменении названий классов на «Собака и животное» или что-то в этом роде
Kartik Chugh
0

Чтобы выполнить downcasting в Java и избежать исключений во время выполнения, воспользуйтесь ссылкой на следующий код:

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

Здесь Animal - родительский класс, а Dog - дочерний класс.
instanceof - это ключевое слово, которое используется для проверки, содержит ли ссылочная переменная заданный тип ссылки на объект или нет.

user11949964
источник
0

Унизительное преобразование объектов невозможно. Только

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

возможно

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();
        }
    }

}
Александр Шпак
источник