Понижение в Java

В Java допускается восходящее приведение, однако приведение вниз приводит к ошибке компиляции.

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

В таком случае, почему 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");
  }
}

person Warrior    schedule 19.12.2008    source источник
comment
Пример фрагмента кода плюс ошибка сделали бы этот вопрос лучшим вопросом для людей, которые пытаются изучить концепции.   -  person Bob Cross    schedule 19.12.2008
comment
Я вижу, что приведенный выше пример взят из velocityreviews.com/forums/t151266-downcasting-problem. .html, на который уже есть несколько хороших ответов.   -  person PhiLho    schedule 19.12.2008
comment
@PhiLho - Основное намерение Джоэла состояло в том, чтобы собрать все замечательные вопросы и ответы под одним общим зонтиком. Не имеет значения, если вопрос/код/ответы уже размещены на других сайтах. Надеюсь, вы уловили суть, иначе послушайте подкасты Джоэла.   -  person Omnipotent    schedule 19.12.2008
comment
Пожалуйста, отредактируйте это так, чтобы все фрагменты кода были с отступом в четыре пробела. Это исправит форматирование.   -  person slim    schedule 19.12.2008
comment
B b = (B) новый A(); является незаконным, вы не должны называть это унижением. Когда мы говорим о приведении вверх/вниз, фактический объект не изменяется, это просто вопрос того, какой тип переменной относится к этому объекту. Вы не можете использовать переменную подтипа для ссылки на объект базового типа.   -  person Ziyang Zhang    schedule 28.08.2012


Ответы (11)


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

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.
person Joachim Sauer    schedule 19.12.2008
comment
Object o = new Object(); String s = (String) o; У меня все работает нормально.. :O Как? - person Asif Mushtaq; 20.04.2016
comment
@UnKnown: не должно. Дважды проверьте, действительно ли вы скомпилировали и запустили эту версию, и если вы все еще можете ее воспроизвести, опубликуйте отдельный вопрос (с SSCCE). - person Joachim Sauer; 20.04.2016
comment
@JoachimSauer, что вы подразумеваете под этой версией? Я использую Java 8. - person Asif Mushtaq; 20.04.2016
comment
@UnKnown: я имею в виду, что опубликованный вами код не должен запускаться (он скомпилируется, но выдаст исключение во время выполнения). Эти комментарии не место для отладки. Пожалуйста, напишите отдельный вопрос. - person Joachim Sauer; 20.04.2016
comment
Как кастинг терпит неудачу во время выполнения? Устанавливает ссылку на целевой объект в null? Выдает исключение? - person CygnusX1; 18.03.2017
comment
@CygnusX1 выдает исключение cannot be cast. - person Nelda.techspiress; 08.06.2017
comment
В чем разница первого примера и второго, которые дают разные результаты? Что вернул getSomeObject()? - person Majid; 11.10.2018
comment
@JoachimSauer Ваше редактирование вставлено не в ту строку - person Gibson; 14.08.2019
comment
@Gibson: действительно, интересно, что никто так долго этого не замечал. Обратите внимание, что вы всегда можете сами предложить редактирование, чтобы исправить это. - person Joachim Sauer; 14.08.2019
comment
@JoachimSauer О, понятно, я думал, что редактирование предназначено для суперпользователей, думаю, я попробую это в следующий раз. Оцените быструю обратную связь - person Gibson; 14.08.2019
comment
@Gibson: с репутацией 2000 вы можете немедленно применить редактирование, но вы можете предложить его намного раньше. Предлагаемые изменения голосуются сообществом. - person Joachim Sauer; 15.08.2019

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

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();
}
person Rolf Rander    schedule 19.12.2008
comment
Я только что узнал о важности instanceof, когда мой абстрактный класс расширялся несколькими классами, и я хотел использовать эксклюзивные методы этих классов, ссылаясь на тип абстрактного класса. Не используя instanceof, у меня было исключение приведения класса - person Tarun; 23.12.2011

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

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
}
person Rolf Rander    schedule 19.12.2008
comment
Мне нравится слово «На самом деле», и я отредактирую ваш пост, чтобы сделать его более очевидным. - person Charaf JRA; 22.01.2014

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

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

variable_of_type_B = (B)expression_of_type_A;

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

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

person Rob Kennedy    schedule 19.12.2008

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

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

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

person matt b    schedule 19.12.2008

@ Исходный постер — см. встроенные комментарии.

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");
    }
}
person Alok Sharma    schedule 16.03.2010

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

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

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

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

но поскольку objValue является объектом, его нельзя преобразовать в String, потому что int нельзя преобразовать в String.

person Uday Reddy    schedule 01.07.2013

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

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

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

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

person Drishti    schedule 13.10.2013
comment
Использование необобщенных коллекций в Java эквивалентно указателям void* в C++. Мне это вообще не кажется хорошей идеей. - person Jezor; 10.09.2016

Рассмотрим приведенный ниже пример

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

person ZohebSiddiqui    schedule 08.06.2014
comment
AOne выглядит несколько запутанно. Пожалуйста, рассмотрите возможность изменения имен ваших классов на Dog и Animal или что-то в этом роде. - person Kartik Chugh; 26.10.2016

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

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

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

person user11949964    schedule 06.09.2019

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

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

}
person Aliaksandr Shpak    schedule 30.07.2019