Pemilihan metode yang kelebihan beban berdasarkan jenis parameter yang sebenarnya

115

Saya bereksperimen dengan kode ini:

interface Callee {
    public void foo(Object o);
    public void foo(String s);
    public void foo(Integer i);
}

class CalleeImpl implements Callee
    public void foo(Object o) {
        logger.debug("foo(Object o)");
    }

    public void foo(String s) {
        logger.debug("foo(\"" + s + "\")");
    }

    public void foo(Integer i) {
        logger.debug("foo(" + i + ")");
    }
}

Callee callee = new CalleeImpl();

Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();

callee.foo(i);
callee.foo(s);
callee.foo(o);

Ini mencetak foo(Object o)tiga kali. Saya berharap pemilihan metode mempertimbangkan jenis parameter yang sebenarnya (bukan yang dideklarasikan). Apakah saya melewatkan sesuatu? Apakah ada cara untuk mengubah kode ini agar dapat dicetak foo(12), foo("foobar")dan foo(Object o)?

Sergey Mikhanov
sumber

Jawaban:

96

Saya berharap pemilihan metode mempertimbangkan jenis parameter yang sebenarnya (bukan yang dideklarasikan). Apakah saya melewatkan sesuatu?

Iya. Harapan Anda salah. Di Java, pengiriman metode dinamis hanya terjadi untuk objek tempat metode dipanggil, bukan untuk jenis parameter metode yang kelebihan beban.

Mengutip Spesifikasi Bahasa Java :

Ketika sebuah metode dipanggil (§15.12), jumlah argumen aktual (dan argumen tipe eksplisit apa pun) dan tipe waktu kompilasi argumen digunakan, pada waktu kompilasi, untuk menentukan tanda tangan dari metode yang akan dipanggil ( §15.12.2). Jika metode yang akan dipanggil adalah metode instance, metode sebenarnya yang akan dipanggil akan ditentukan pada waktu proses, menggunakan pencarian metode dinamis (§15.12.4).

Michael Borgwardt
sumber
4
Bisakah Anda menjelaskan spesifikasi yang Anda kutip. Kedua kalimat tersebut tampaknya saling bertentangan. Contoh di atas menggunakan metode instance, namun metode yang dipanggil jelas tidak ditentukan pada waktu proses.
Alex Worden
15
@Alex Worden: tipe waktu kompilasi dari parameter metode digunakan untuk menentukan tanda tangan dari metode yang akan dipanggil, dalam kasus ini foo(Object). Pada waktu proses, kelas objek tempat metode dipanggil menentukan penerapan metode itu yang dipanggil, dengan mempertimbangkan bahwa itu mungkin turunan dari subkelas dari tipe yang dideklarasikan yang menggantikan metode.
Michael Borgwardt
86

Seperti disebutkan sebelumnya, resolusi overloading dilakukan pada waktu kompilasi.

Java Puzzlers memiliki contoh yang bagus untuk itu:

Teka-teki 46: Kasus Pembuat yang Membingungkan

Teka-teki ini memberi Anda dua konstruktor yang Membingungkan. Metode utama memanggil konstruktor, tetapi yang mana? Keluaran program tergantung pada jawabannya. Apa yang dicetak program itu, atau apakah itu legal?

public class Confusing {

    private Confusing(Object o) {
        System.out.println("Object");
    }

    private Confusing(double[] dArray) {
        System.out.println("double array");
    }

    public static void main(String[] args) {
        new Confusing(null);
    }
}

Solusi 46: Kasus Pembuat yang Membingungkan

... Proses resolusi kelebihan beban Java beroperasi dalam dua fase. Tahap pertama memilih semua metode atau konstruktor yang dapat diakses dan dapat diterapkan. Fase kedua memilih metode atau konstruktor yang paling spesifik yang dipilih pada fase pertama. Satu metode atau konstruktor kurang spesifik dari yang lain jika ia dapat menerima parameter yang diteruskan ke yang lain [JLS 15.12.2.5].

Dalam program kami, kedua konstruktor dapat diakses dan diterapkan. Konstruktor Confusing (Object) menerima parameter apapun yang diteruskan ke Confusing (double []) , jadi Confusing (Object) kurang spesifik. (Setiap larik ganda adalah Objek , tetapi tidak setiap Objek adalah larik ganda .) Oleh karena itu, konstruktor paling spesifik adalah Membingungkan (ganda []) , yang menjelaskan keluaran program.

Perilaku ini masuk akal jika Anda mengirimkan nilai bertipe double [] ; itu berlawanan dengan intuisi jika Anda meneruskan null . Kunci untuk memahami teka-teki ini adalah bahwa pengujian untuk metode atau konstruktor mana yang paling spesifik tidak menggunakan parameter sebenarnya : parameter yang muncul dalam pemanggilan. Mereka hanya digunakan untuk menentukan kelebihan beban yang berlaku. Setelah compiler menentukan overloading mana yang dapat diterapkan dan dapat diakses, compiler akan memilih overloading yang paling spesifik, dengan hanya menggunakan parameter formal: parameter yang muncul dalam deklarasi.

Untuk memanggil konstruktor Confusing (Object) dengan parameter null , tulis Confusing ((Object) null) baru . Ini memastikan bahwa hanya Confusing (Object) yang berlaku. Secara lebih umum, untuk memaksa compiler memilih overloading tertentu, masukkan parameter aktual ke tipe yang dideklarasikan dari parameter formal.

denis.zhdanov
sumber
4
Saya harap belum terlambat untuk mengatakan - "salah satu penjelasan terbaik tentang SOF". Terima kasih :)
TheLostMind
5
Saya percaya jika kita juga menambahkan konstruktor 'private Confusing (int [] iArray)' itu akan gagal untuk dikompilasi, bukan? Karena sekarang ada dua konstruktor dengan kekhususan yang sama.
Risser
Jika saya menggunakan tipe pengembalian dinamis sebagai input fungsi, itu selalu menggunakan yang kurang spesifik ... mengatakan bahwa metode yang dapat digunakan untuk semua nilai pengembalian yang mungkin ...
kaiser
16

Kemampuan untuk mengirimkan panggilan ke suatu metode berdasarkan jenis argumen disebut pengiriman ganda . Di Jawa ini dilakukan dengan pola Pengunjung .

Namun, karena Anda berurusan dengan Integers dan Strings, Anda tidak dapat dengan mudah menggabungkan pola ini (Anda tidak dapat memodifikasi kelas-kelas ini). Jadi, waktu berjalan raksasa switchdi objek akan menjadi senjata pilihan Anda.

Anton Gogolev
sumber
11

Di Java, metode untuk memanggil (seperti di mana tanda tangan metode yang akan digunakan) ditentukan pada waktu kompilasi, begitu juga dengan tipe waktu kompilasi.

Pola tipikal untuk mengatasi hal ini adalah dengan memeriksa tipe objek dalam metode dengan tanda tangan Objek dan mendelegasikan ke metode dengan cast.

    public void foo(Object o) {
        if (o instanceof String) foo((String) o);
        if (o instanceof Integer) foo((Integer) o);
        logger.debug("foo(Object o)");
    }

Jika Anda memiliki banyak tipe dan ini tidak dapat dikelola, maka overloading metode mungkin bukan pendekatan yang tepat, melainkan metode publik seharusnya hanya mengambil Object dan mengimplementasikan beberapa jenis pola strategi untuk mendelegasikan penanganan yang sesuai per tipe objek.

Yishai
sumber
4

Saya memiliki masalah serupa dengan memanggil konstruktor yang tepat dari kelas yang disebut "Parameter" yang dapat mengambil beberapa jenis Java dasar seperti String, Integer, Boolean, Long, dll. Dengan adanya array Objek, saya ingin mengubahnya menjadi array objek Parameter saya dengan memanggil konstruktor paling spesifik untuk setiap Objek dalam larik input. Saya juga ingin mendefinisikan Parameter konstruktor (Object o) yang akan melempar IllegalArgumentException. Saya tentu saja menemukan metode ini dipanggil untuk setiap Objek dalam array saya.

Solusi yang saya gunakan adalah mencari konstruktor melalui refleksi ...

public Parameter[] convertObjectsToParameters(Object[] objArray) {
    Parameter[] paramArray = new Parameter[objArray.length];
    int i = 0;
    for (Object obj : objArray) {
        try {
            Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
            paramArray[i++] = cons.newInstance(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
        }
    }
    return paramArray;
}

Tidak diperlukan contoh yang jelek, pernyataan sakelar, atau pola pengunjung! :)

Alex Worden
sumber
2

Java melihat jenis referensi saat mencoba menentukan metode mana yang akan dipanggil. Jika Anda ingin memaksa kode Anda, Anda memilih metode yang 'benar', Anda dapat mendeklarasikan bidang Anda sebagai contoh dari jenis tertentu:

Integeri = new Integer(12);
String s = "foobar";
Object o = new Object();

Anda juga bisa memasukkan param Anda sebagai tipe param:

callee.foo(i);
callee.foo((String)s);
callee.foo(((Integer)o);
akf
sumber
1

Jika ada kecocokan persis antara jumlah dan jenis argumen yang ditentukan dalam pemanggilan metode dan tanda tangan metode dari metode yang kelebihan beban, maka metode itulah yang akan dipanggil. Anda menggunakan referensi Objek, jadi java memutuskan pada waktu kompilasi bahwa untuk parameter Objek, ada metode yang menerima Objek secara langsung. Jadi itu disebut metode itu 3 kali.

Ashish Thukral
sumber