ดาวน์สตรีมใน Java


179

อนุญาตให้ใช้การส่งผ่านข้อมูลได้ใน Java แต่การดาวน์สตรีมจะทำให้เกิดข้อผิดพลาดในการคอมไพล์

ข้อผิดพลาดในการคอมไพล์สามารถลบออกได้โดยการเพิ่ม cast แต่จะหยุดพักที่รันไทม์

ในกรณีนี้เหตุใดจาวาอนุญาตให้ดาวน์สตรีมหากไม่สามารถรันที่รันไทม์ได้
มีการใช้งานจริงสำหรับแนวคิดนี้หรือไม่?

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
ตัวอย่างโค้ดพร้อมกับข้อผิดพลาดจะทำให้คำถามนี้ดีขึ้นสำหรับผู้ที่พยายามเรียนรู้แนวคิด
Bob Cross

3
+1 สำหรับความคิดเห็นของ Bob คำถามไม่ชัดเจนเลย
Jon Skeet

ฉันเห็นตัวอย่างข้างต้นนำมาจากvelocityreviews.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

โปรดทราบว่าบาง casts จะไม่ได้รับอนุญาตในเวลารวบรวมเพราะพวกเขาจะไม่ประสบความสำเร็จเลย:

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 ได้อย่างไร
Asif Mushtaq

@ ไม่ทราบ: มันไม่ควร ตรวจสอบอีกครั้งว่าคุณรวบรวมและเรียกใช้เวอร์ชันนั้นและหากคุณยังสามารถทำซ้ำได้ให้โพสต์คำถามแยกต่างหาก (ด้วยSSCCE )
Joachim Sauer

@JoachimSauer คุณหมายถึงอะไรในเวอร์ชันนั้น? ฉันใช้ Java 8
Asif Mushtaq

1
@UnKnown: ฉันหมายถึงรหัสที่คุณโพสต์ไม่ควรรัน (มันจะรวบรวม แต่จะมีข้อยกเว้นในการรันไทม์) ความคิดเห็นเหล่านี้ไม่ใช่พื้นที่สำหรับการดีบัก กรุณาโพสต์คำถามแยกต่างหาก
Joachim Sauer

การร่ายล้มเหลว ณ รันไทม์อย่างไร ตั้งค่าการอ้างอิงวัตถุเป้าหมายเป็น null? โยนข้อยกเว้น?
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();
}

ฉันเพิ่งได้เรียนรู้ถึงความสำคัญของอินสแตนซ์เมื่อชั้นนามธรรมของฉันถูกขยายออกไปหลายชั้นเรียนและฉันต้องการที่จะใช้วิธีการพิเศษของชั้นเรียนเหล่านั้นในขณะที่อ้างอิงถึงประเภทชั้นนามธรรม ไม่ได้ใช้อินสแตนซ์ของฉันฉันได้รับการยกเว้นในชั้นเรียน
Tarun

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

typecast พูดว่าอย่างมีประสิทธิภาพ: ถือว่านี่เป็นการอ้างอิงถึงคลาส cast และใช้มัน ทีนี้สมมติว่า o เป็นจำนวนเต็มจริง ๆโดยสมมติว่าสตริงนี้ไม่มีเหตุผลและจะให้ผลลัพธ์ที่ไม่คาดคิดดังนั้นจึงต้องมีการตรวจสอบรันไทม์และข้อยกเว้นเพื่อแจ้งให้ทราบถึงสภาพแวดล้อมรันไทม์ว่ามีบางอย่างผิดปกติ

ในการใช้งานจริงคุณสามารถเขียนโค้ดที่ทำงานในคลาสทั่วไปได้มากกว่า แต่ส่งไปยังคลาสย่อยถ้าคุณรู้ว่าคลาสย่อยนั้นคืออะไรและจำเป็นต้องจัดการมันอย่างไร ตัวอย่างทั่วไปคือการแทนที่ Object.equals () สมมติว่าเรามีคลาสสำหรับรถยนต์:

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

ฉันชอบคำจริงๆและฉันจะแก้ไขโพสต์ของคุณเพื่อให้ชัดเจนยิ่งขึ้น
Charaf JRA

5

เราทุกคนเห็นว่ารหัสที่คุณระบุไม่สามารถใช้งานได้ในเวลาทำงาน นั่นเป็นเพราะเรารู้ว่าการแสดงออกnew A()สามารถไม่เคยBเป็นเป้าหมายของการพิมพ์

แต่นั่นไม่ใช่วิธีที่คอมไพเลอร์มองเห็น เมื่อถึงเวลาที่คอมไพเลอร์ตรวจสอบว่าอนุญาตให้ส่งได้หรือไม่ก็เห็นเพียงสิ่งนี้:

variable_of_type_B = (B)expression_of_type_A;

และอย่างที่คนอื่น ๆ ได้แสดงออกมานักแสดงประเภทนั้นถูกกฎหมายอย่างสมบูรณ์แบบ การแสดงออกทางด้านขวาสามารถประเมินวัตถุประเภทBนั้นได้เป็นอย่างดี คอมไพเลอร์เห็นว่าAและBมีความสัมพันธ์ชนิดย่อยดังนั้นด้วยมุมมอง "การแสดงออก" ของรหัสนักแสดงอาจทำงานได้

คอมไพเลอร์ไม่พิจารณากรณีพิเศษเมื่อทราบว่าชนิดของวัตถุexpression_of_type_Aจะมีอะไรจริงๆ มันก็เห็นประเภทคงเป็นAและคิดว่าประเภทแบบไดนามิกที่อาจจะAหรือลูกหลานของใด ๆรวมทั้งAB


3

ในกรณีนี้เหตุใดจาวาอนุญาตให้ดาวน์สตรีมหากไม่สามารถรันที่รันไทม์ได้

ฉันเชื่อว่าเป็นเพราะไม่มีวิธีใดที่คอมไพเลอร์จะรู้เวลารวบรวมถ้านักแสดงจะประสบความสำเร็จหรือไม่ สำหรับตัวอย่างของคุณการดูว่านักแสดงจะล้มเหลว แต่มีบางครั้งที่ไม่ชัดเจนนัก

ตัวอย่างเช่นลองจินตนาการว่าประเภท B, C และ D ทั้งหมดขยายประเภท A แล้ววิธีpublic A getSomeA()จะคืนค่าตัวอย่างของ B, C หรือ D ขึ้นอยู่กับจำนวนที่สร้างแบบสุ่ม คอมไพเลอร์ไม่ทราบชนิดของรันไทม์ที่แน่นอนที่จะถูกส่งคืนโดยวิธีนี้ดังนั้นหากคุณส่งผลลัพธ์ไปในภายหลังBจะไม่มีทางทราบว่านักแสดงจะประสบความสำเร็จ (หรือล้มเหลว) ดังนั้นคอมไพเลอร์ต้องถือว่าปลดเปลื้องจะประสบความสำเร็จ


2

@ Original Poster - ดูความคิดเห็นแบบอินไลน์

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 ใช้งานได้ในกรณีที่เรากำลังจัดการกับวัตถุที่ถูกเร่ง Upcasting:

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

ดังนั้นตอนนี้objValueตัวแปรสามารถเสมอ downcasted ไปintเนื่องจากวัตถุซึ่งเป็นนักแสดงเป็นInteger,

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

แต่เนื่องจากobjValueเป็นวัตถุจึงไม่สามารถร่ายได้Stringเนื่องจากintไม่สามารถร่ายStringได้


0

Downcasting มีประโยชน์มากในข้อมูลโค้ดต่อไปนี้ฉันใช้มันตลอดเวลา ดังนั้นการพิสูจน์ว่า downcasting มีประโยชน์

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

ฉันเก็บสตริงในรายการที่เชื่อมโยง เมื่อฉันดึงองค์ประกอบของรายการที่เชื่อมโยงวัตถุจะถูกส่งกลับ ในการเข้าถึงองค์ประกอบต่างๆเช่น Strings (หรือ Class Objects อื่น ๆ ) การดาวน์สตรีมช่วยฉันได้

Java อนุญาตให้เรารวบรวมรหัส downcast ที่ไว้วางใจเราว่าเรากำลังทำสิ่งที่ผิด อย่างไรก็ตามหากมนุษย์ทำผิดพลาดมันจะติดอยู่ที่รันไทม์


การใช้คอลเล็กชันที่ไม่ใช่ทั่วไปใน 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 และกำหนดให้การอ้างอิงคลาส Super AOne และตอนนี้การอ้างอิง superclass ไม่ทราบวิธี method2 ในคลาสย่อยเช่น Bone ในช่วงเวลารวบรวม การอ้างอิงผลลัพธ์สามารถทราบเกี่ยวกับการปรากฏตัวของวิธีการในคลาสย่อยเช่นกระดูก


AOne ดูค่อนข้างสับสน โปรดเปลี่ยนชื่อชั้นเรียนของคุณเป็น Dog and Animal หรือบางอย่าง
Kartik Chugh

0

หากต้องการ downcasting ใน Java และหลีกเลี่ยงข้อยกเว้นรันไทม์ให้อ้างอิงของรหัสต่อไปนี้:

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

ที่นี่สัตว์เป็นคลาสพ่อแม่และสุนัขเป็นชั้นเด็ก
instanceofเป็นคำหลักที่ใช้สำหรับตรวจสอบว่าตัวแปรอ้างอิงมีชนิดของการอ้างอิงวัตถุที่กำหนดหรือไม่


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

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.