Downcasting trong Java


179

Upcasting được cho phép trong Java, tuy nhiên việc downcasting gây ra lỗi biên dịch.

Lỗi biên dịch có thể được loại bỏ bằng cách thêm một cast nhưng dù sao cũng sẽ bị hỏng khi chạy.

Trong trường hợp này tại sao Java cho phép downcasting nếu nó không thể được thực thi trong thời gian chạy?
Có bất kỳ sử dụng thực tế cho khái niệm này?

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
Một đoạn mã ví dụ cộng với lỗi sẽ làm cho câu hỏi này tốt hơn cho những người đang cố gắng tìm hiểu các khái niệm.
Bob Cross

3
+1 cho nhận xét của Bob. Câu hỏi không rõ ràng chút nào.
Jon Skeet

Tôi thấy ví dụ trên được lấy từ velocityreviews.com/forums/t151266-downcasting-pro Hiệu.html đã có một số câu trả lời hay.
PhiLho

2
@PhiLho - Mục đích chính của Joel là nhận được tất cả các câu hỏi và câu trả lời tuyệt vời dưới một chiếc ô chung. Sẽ không có vấn đề gì nếu câu hỏi / mã / câu trả lời đã được đăng trong một số trang web khác. Tôi hy vọng bạn có được điểm, người khác nghe podcast của Joel.
Toàn năng

Vui lòng chỉnh sửa điều này để các đoạn mã được thụt vào bởi bốn khoảng trắng. Điều đó sẽ sửa định dạng.
mỏng

Câu trả lời:


298

Downcasting được cho phép khi có khả năng nó thành công trong thời gian chạy:

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

Trong một số trường hợp, điều này sẽ không thành công:

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

Khi một dàn diễn viên (ví dụ như người cuối cùng này) thất bại khi chạy một ClassCastExceptionsẽ được ném ra.

Trong các trường hợp khác, nó sẽ hoạt động:

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

Lưu ý rằng một số phôi sẽ không được phép trong thời gian biên dịch, bởi vì chúng sẽ không bao giờ thành công cả:

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;Nó hoạt động tốt với tôi ..: O thế nào?
Asif Mushtaq

@UnKnown: không nên. Kiểm tra kỹ xem bạn có thực sự biên dịch và chạy phiên bản đó không và nếu bạn vẫn có thể sao chép nó, hãy đăng một câu hỏi riêng (với SSCCE ).
Joachim Sauer

@JoachimSauer Ý của phiên bản đó là gì? Tôi đang sử dụng Java 8.
Asif Mushtaq

1
@UnKnown: Ý tôi là mã bạn đã đăng không nên chạy (nó sẽ biên dịch, nhưng ném ngoại lệ khi chạy). Những bình luận này không phải là không gian để gỡ lỗi đó. Xin vui lòng gửi một câu hỏi riêng biệt.
Joachim Sauer

Làm thế nào đúc thất bại trong thời gian chạy? Đặt tham chiếu đối tượng đích thành null? Ném một ngoại lệ?
CygnusX1

17

Sử dụng ví dụ của bạn, bạn có thể làm:

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

Tôi vừa học được tầm quan trọng của thể hiện khi lớp trừu tượng của tôi được mở rộng bởi nhiều lớp và tôi muốn sử dụng các phương thức độc quyền của các lớp đó, trong khi đề cập đến loại lớp trừu tượng. Không sử dụng thể hiện, tôi có ngoại lệ trong lớp
Tarun

16

Tôi tin rằng điều này áp dụng cho tất cả các ngôn ngữ gõ tĩnh:

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

Typecast có hiệu quả nói: giả sử đây là một tham chiếu đến lớp cast và sử dụng nó như vậy. Bây giờ, giả sử o thực sự là một Integer, giả sử đây là một Chuỗi không có ý nghĩa và sẽ cho kết quả bất ngờ, do đó cần phải có kiểm tra thời gian chạy và một ngoại lệ để thông báo cho môi trường thời gian chạy rằng có gì đó không đúng.

Trong sử dụng thực tế, bạn có thể viết mã làm việc trên một lớp tổng quát hơn, nhưng chuyển nó thành một lớp con nếu bạn biết nó là lớp con nào và cần phải xử lý nó như vậy. Một ví dụ điển hình là ghi đè Object.equals (). Giả sử chúng ta có một lớp học cho Xe hơi:

@Override
boolean equals(Object o) {
    if(!(o instanceof Car)) return false;
    Car other = (Car)o;
    // compare this to other and return
}

Tôi thích từ Thực sự và tôi sẽ chỉnh sửa bài đăng của bạn để làm cho nó rõ ràng hơn
Charaf JRA

5

Tất cả chúng ta đều có thể thấy rằng mã bạn cung cấp sẽ không hoạt động trong thời gian chạy. Đó là bởi vì chúng ta biết rằng biểu thức không bao giờnew A() có thể là một đối tượng của kiểu .B

Nhưng đó không phải là cách trình biên dịch nhìn thấy nó. Vào thời điểm trình biên dịch đang kiểm tra xem diễn viên có được phép hay không, nó chỉ thấy điều này:

variable_of_type_B = (B)expression_of_type_A;

Và như những người khác đã chứng minh, loại diễn viên đó là hoàn toàn hợp pháp. Biểu thức bên phải rất có thể đánh giá một đối tượng thuộc loại B. Trình biên dịch thấy điều đó ABcó một mối quan hệ kiểu con, vì vậy với khung nhìn "biểu thức" của mã, diễn viên có thể hoạt động.

Trình biên dịch không xem xét trường hợp đặc biệt khi nó biết chính xác loại đối tượng expression_of_type_Asẽ thực sự có. Nó chỉ nhìn thấy loại tĩnh như Avà xem xét loại động có thể Ahoặc bất kỳ hậu duệ nào A, bao gồm B.


3

Trong trường hợp này tại sao Java cho phép downcasting nếu nó không thể được thực thi trong thời gian chạy?

Tôi tin rằng điều này là do không có cách nào để trình biên dịch biết vào thời gian biên dịch nếu diễn viên sẽ thành công hay không. Ví dụ của bạn, thật đơn giản để thấy rằng dàn diễn viên sẽ thất bại, nhưng có những lúc khác không rõ ràng lắm.

Ví dụ, hãy tưởng tượng rằng các loại B, C và D đều mở rộng loại A và sau đó một phương thức public A getSomeA()trả về một thể hiện của B, C hoặc D tùy thuộc vào một số được tạo ngẫu nhiên. Trình biên dịch không thể biết loại thời gian chạy chính xác nào sẽ được trả về bằng phương thức này, vì vậy nếu sau này bạn đưa kết quả đến B, không có cách nào để biết liệu diễn viên sẽ thành công (hay thất bại). Do đó trình biên dịch phải giả sử phôi sẽ thành công.


2

@ Poster gốc - xem bình luận nội tuyến.

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 hoạt động trong trường hợp khi chúng ta đang xử lý một đối tượng bị u ám. Upcasting:

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

Vì vậy, bây giờ objValuebiến này luôn có thể bị hạ thấp xuống intvì đối tượng được truyền là một Integer,

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

nhưng vì objValuelà một vật thể nên nó không thể được sử dụng Stringintkhông thể truyền tới String.


0

Downcasting rất hữu ích trong đoạn mã sau tôi sử dụng nó mọi lúc. Do đó chứng minh rằng downcasting là hữu ích.

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

Tôi lưu trữ Chuỗi trong Danh sách Liên kết. Khi tôi truy xuất các phần tử của Danh sách được liên kết, các đối tượng được trả về. Để truy cập các phần tử dưới dạng Chuỗi (hoặc bất kỳ Đối tượng Lớp nào khác), downcasting giúp tôi.

Java cho phép chúng tôi biên dịch mã downcast tin tưởng rằng chúng tôi đang làm sai. Tuy nhiên, nếu con người phạm sai lầm, nó bị bắt khi chạy.


Sử dụng các bộ sưu tập không chung trong Java là tương đương với các void*con trỏ trong C ++. Nó không có vẻ như là một ý tưởng tốt cả.
Jezor

0

Hãy xem xét ví dụ dưới đây

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

Ở đây chúng ta tạo đối tượng của lớp con Bone và gán nó cho tham chiếu AOne siêu lớp và bây giờ tham chiếu siêu lớp không biết về phương thức 2 trong lớp con, tức là Bone trong thời gian biên dịch. tham chiếu kết quả có thể biết về sự hiện diện của các phương thức trong lớp con, tức là Bone


AOne có vẻ hơi khó hiểu. Vui lòng xem xét việc thay đổi tên lớp của bạn thành Chó và Động vật hoặc một cái gì đó
Kartik Chugh

0

Để thực hiện downcasting trong Java và tránh các ngoại lệ trong thời gian chạy, hãy tham khảo đoạn mã sau:

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

Ở đây, Animal là lớp cha mẹ và Dog là lớp con.
instanceof là một từ khóa được sử dụng để kiểm tra xem một biến tham chiếu có chứa một loại tham chiếu đối tượng nhất định hay không.


0

Biến đổi hạ lưu của các đối tượng là không thể. Chỉ có

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

là khả thi

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.