Downcasting Otomatis oleh Inferring the Type

11

Di java, Anda harus secara eksplisit melakukan cast untuk menurunkan variabel

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

Apakah ada alasan untuk persyaratan ini, selain dari fakta bahwa java tidak melakukan inferensi tipe apa pun?

Apakah ada "gotcha" dengan menerapkan downcasting otomatis dalam bahasa baru?

Misalnya:

Apple child = parent; // no cast required 
Sam Washburn
sumber
Anda mengatakan bahwa, jika kompiler dapat menyimpulkan bahwa objek yang downcasted selalu dari jenis yang benar Anda harus dapat tidak secara eksplisit mengekspresikan downcast? Tetapi kemudian tergantung pada versi kompiler dan berapa banyak tipe inferensi yang dapat dilakukan beberapa program mungkin tidak valid ... bug pada tipe inferencer dapat mencegah program yang valid ditulis, dll ... tidak masuk akal untuk memperkenalkan beberapa program khusus hal itu tergantung pada banyak faktor, itu tidak akan intuitif bagi pengguna jika mereka harus terus menambah atau menghapus gips tanpa alasan di setiap rilis.
Bakuriu

Jawaban:

24

Orang yang terbuang selalu berhasil.

Downcasts dapat menyebabkan kesalahan runtime, ketika tipe objek runtime bukan subtipe dari tipe yang digunakan dalam cetakan.

Karena yang kedua adalah operasi yang berbahaya, sebagian besar bahasa pemrograman yang diketik memerlukan programmer untuk secara eksplisit memintanya. Pada dasarnya, programmer memberitahu kompiler "percayalah, saya tahu lebih baik - ini akan baik-baik saja pada saat runtime".

Ketika jenis sistem prihatin, upcast menempatkan beban bukti pada kompiler (yang harus memeriksanya secara statis), downcast meletakkan beban bukti pada programmer (yang harus berpikir keras tentang hal itu).

Orang bisa berpendapat bahwa bahasa pemrograman yang dirancang dengan baik akan melarang sepenuhnya downcast, atau memberikan alternatif gips yang aman, misalnya mengembalikan jenis opsional Option<T>. Namun, banyak bahasa yang tersebar luas memilih pendekatan yang lebih sederhana dan lebih pragmatis untuk sekadar mengembalikan Tdan memunculkan kesalahan.

Dalam contoh spesifik Anda, kompiler dapat dirancang untuk menyimpulkan bahwa parentsebenarnya merupakan Appleanalisis statis sederhana, dan memungkinkan pemeran tersirat. Namun, secara umum masalahnya tidak dapat dipastikan, jadi kami tidak dapat mengharapkan kompiler melakukan terlalu banyak sihir.

chi
sumber
1
Hanya untuk referensi, contoh bahasa yang downcasts ke opsional adalah Rust, tapi itu karena tidak memiliki warisan yang benar, hanya "jenis apa pun" .
Kroltan
3

Biasanya downcasting adalah apa yang Anda lakukan ketika pengetahuan yang diketahui secara statis tentang kompiler tentang jenis sesuatu kurang spesifik dari apa yang Anda ketahui (atau setidaknya harapan).

Dalam situasi seperti contoh Anda, objek dibuat sebagai Appledan kemudian pengetahuan itu dibuang dengan menyimpan referensi dalam variabel tipe Fruit. Maka Anda ingin menggunakan referensi yang sama dengan Applelagi.

Karena informasi itu hanya dibuang "secara lokal", tentu saja, kompiler dapat mempertahankan pengetahuan yang parentbenar-benar sebuah Apple, meskipun jenis yang dinyatakannya adalah Fruit.

Tetapi biasanya tidak ada yang melakukan itu. Jika Anda ingin membuat Appledan menggunakannya sebagai Apple, Anda menyimpannya dalam Applevariabel, bukan variabel Fruit.

Ketika Anda memiliki Fruitdan ingin menggunakannya sebagai Applebiasanya berarti Anda memperoleh Fruitmelalui beberapa cara yang umumnya dapat mengembalikan segala jenis Fruit, tetapi dalam hal ini Anda tahu itu adalah Apple. Hampir selalu Anda tidak hanya membangunnya, Anda telah melewatinya dengan beberapa kode lain.

Contoh yang jelas adalah jika saya memiliki parseFruitfungsi yang dapat mengubah string seperti "apel", "oranye", "lemon", dll, menjadi subkelas yang sesuai; umumnya yang kita (dan kompiler) dapat tahu tentang fungsi ini adalah bahwa ia mengembalikan beberapa jenis Fruit, tetapi jika saya menelepon parseFruit("apple")maka saya tahu itu akan memanggil Appledan mungkin ingin menggunakan Applemetode, jadi saya bisa downcast.

Sekali lagi kompiler yang cukup pintar dapat mengetahui hal itu di sini dengan menguraikan kode sumber untuk parseFruit, karena saya memanggilnya dengan konstanta (kecuali ada di modul lain dan kami memiliki kompilasi terpisah, seperti di Jawa). Tetapi Anda harus dapat dengan mudah melihat bagaimana contoh yang lebih rumit yang melibatkan informasi dinamis dapat menjadi lebih sulit (atau bahkan tidak mungkin!) Bagi kompiler untuk memverifikasi.

Dalam kode downcasts realistis biasanya terjadi di mana kompiler tidak dapat memverifikasi bahwa downcast aman menggunakan metode generik, dan tidak dalam kasus sederhana seperti segera setelah upcast membuang informasi jenis yang sama yang kita coba untuk kembali dengan downcasting.

Ben
sumber
3

Ini masalah di mana Anda ingin menarik garis. Anda dapat merancang bahasa yang akan mendeteksi validitas downcast implisit:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Sekarang, mari kita ekstrak metode:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Kami masih bagus. Analisis statis jauh lebih sulit tetapi masih memungkinkan.

Tetapi masalah muncul begitu seseorang menambahkan:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

Sekarang, di mana Anda ingin meningkatkan kesalahan? Ini menciptakan dilema, orang bisa mengatakan bahwa masalahnya ada Apple child = parent;, tetapi ini bisa dibantah oleh "Tapi itu berhasil sebelumnya". Dari sisi lain, menambahkan eat(trouble);menyebabkan masalah ", tetapi seluruh titik polimorfisme adalah untuk memungkinkan hal itu.

Dalam hal ini, Anda dapat melakukan beberapa pekerjaan programmer, tetapi Anda tidak dapat melakukan semuanya. Semakin jauh Anda mengambilnya sebelum menyerah, semakin sulit untuk menjelaskan apa yang salah. Jadi, lebih baik berhenti sesegera mungkin, sesuai dengan prinsip kesalahan pelaporan awal.

BTW, di Jawa downcast yang Anda jelaskan sebenarnya bukan downcast. Ini adalah pemeran umum, yang bisa melempar apel ke jeruk juga. Jadi, secara teknis ide @ chi sudah ada di sini, tidak ada downcast di Jawa, hanya "everycasts". Masuk akal untuk merancang operator downcast khusus yang akan melempar kesalahan kompilasi ketika tipe hasil itu tidak dapat ditemukan hilir dari tipe argumen itu. Ini akan menjadi ide yang baik untuk membuat "everycast" jauh lebih sulit untuk digunakan untuk mencegah programmer menggunakannya tanpa alasan yang sangat bagus. XXXXX_cast<type>(argument)Sintaks C ++ muncul di pikiran.

Agent_L
sumber