Parametrenin gerçek türüne göre aşırı yüklenmiş yöntem seçimi


115

Bu kodla deney yapıyorum:

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

Bu, foo(Object o)üç kez yazdırılır . Yöntem seçiminin gerçek (bildirilen değil) parametre türünü dikkate almasını bekliyorum. Bir şey mi kaçırıyorum? O basalım ki bu kodu değiştirmek için bir yolu var mı foo(12), foo("foobar")ve foo(Object o)?

Yanıtlar:


96

Yöntem seçiminin gerçek (bildirilen değil) parametre türünü dikkate almasını bekliyorum. Bir şey mi kaçırıyorum?

Evet. Beklentiniz yanlış. Java'da dinamik yöntem gönderimi, aşırı yüklenmiş yöntemlerin parametre türleri için değil, yalnızca yöntemin çağrıldığı nesne için gerçekleşir.

Java Dil Spesifikasyonuna Atıfta Bulunmak :

Bir yöntem çağrıldığında (§15.12), gerçek argüman sayısı (ve herhangi bir açık tip argümanı) ve argümanların derleme zamanı türleri, çağrılacak yöntemin imzasını belirlemek için derleme zamanında kullanılır ( §15.12.2). Çağrılacak yöntem bir örnek yöntem ise, çalıştırılacak asıl yöntem dinamik yöntem araması kullanılarak çalışma zamanında belirlenecektir (§15.12.4).


4
Lütfen alıntı yaptığınız özellikleri açıklayabilir misiniz? İki cümle birbiriyle çelişiyor gibi görünüyor. Yukarıdaki örnek, örnek yöntemlerini kullanır, ancak çalıştırılan yöntem çalışma zamanında açıkça belirlenmemektedir.
Alex Worden

15
@Alex Worden: Yöntem parametrelerinin derleme zamanı türü , bu durumda çağrılacak yöntemin imzasını belirlemek için kullanılır foo(Object). Çalışma zamanında, yöntemin çağrıldığı nesnenin sınıfı , yöntemi geçersiz kılan bildirilen türün bir alt sınıfının bir örneği olabileceğini dikkate alarak, bu yöntemin hangi uygulamasının çağrıldığını belirler.
Michael Borgwardt

86

Daha önce belirtildiği gibi, aşırı yükleme çözümlemesi derleme zamanında gerçekleştirilir.

Java Puzzlers'ın bunun için güzel bir örneği var:

Bulmaca 46: Kafa Karıştıran Yapıcı Vakası

Bu bulmaca size iki Kafa karıştırıcı kurucu sunar. Ana yöntem bir kurucu çağırır, ama hangisi? Programın çıktısı cevaba bağlıdır. Program neyi yazdırıyor ya da yasal mı?

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

Çözüm 46: Kafa Karıştıran Yapıcı Örneği

... Java'nın aşırı yük çözümleme süreci iki aşamada çalışır. İlk aşama, erişilebilir ve uygulanabilir tüm yöntemleri veya yapıcıları seçer. İkinci aşama , ilk aşamada seçilen yöntemlerin veya kurucuların en spesifikini seçer . Bir yöntem veya kurucu, diğerine geçirilen parametreleri kabul edebiliyorsa, diğerine göre daha az spesifiktir [JLS 15.12.2.5].

Programımızda her iki kurucu da erişilebilir ve uygulanabilirdir. Yapıcı Confusing (Object) , Confusing'e (double []) iletilen herhangi bir parametreyi kabul eder , bu nedenle Confusing (Object) daha az spesifiktir. (Her çift ​​dizi bir Nesnedir , ancak her Nesne bir çift ​​dizi değildir .) Bu nedenle, en spesifik kurucu, programın çıktısını açıklayan Karmaşıktır (çift []) .

Double [] türünde bir değer iletirseniz bu davranış mantıklıdır ; null geçerseniz mantık dışıdır . Bu bulmacayı anlamanın anahtarı, hangi yöntemin veya kurucunun en spesifik olduğu testin gerçek parametreleri kullanmamasıdır : çağrıda görünen parametreler. Yalnızca hangi aşırı yüklemelerin uygulanabilir olduğunu belirlemek için kullanılırlar. Derleyici, hangi aşırı yüklemelerin uygulanabilir ve erişilebilir olduğunu belirledikten sonra, yalnızca biçimsel parametreleri kullanarak en spesifik aşırı yüklemeyi seçer: bildirimde görünen parametreler.

Confusing (Object) yapıcısını null parametresiyle çağırmak için yeni Confusing ((Object) null) yazın . Bu, yalnızca Kafa Karıştıran (Nesne) seçeneğinin geçerli olmasını sağlar . Daha genel olarak, derleyiciyi belirli bir aşırı yüklemeyi seçmeye zorlamak için, gerçek parametreleri resmi parametrelerin bildirilen türlerine çevirin.


4
Umarım "SOF hakkındaki en iyi açıklamalardan biri" demek için çok geç değildir. Teşekkürler :)
TheLostMind

5
İnanıyorum ki 'private Confusing (int [] iArray)' kurucusunu da eklersek, derleme başarısız olur, değil mi? Çünkü şimdi aynı özgülüğe sahip iki kurucu var.
Risser

Fonksiyon girdisi olarak dinamik dönüş türlerini kullanırsam, her zaman daha az spesifik olanı kullanır ... tüm olası dönüş değerleri için kullanılabileceğini söyledi ...
kaiser

16

Bağımsız değişkenlerin türlerine göre bir yönteme çağrı gönderme yeteneğine çoklu gönderme denir . Java'da bu Ziyaretçi kalıbı ile yapılır .

Ancak, Integere ve Stringlerle uğraştığınız için , bu kalıbı kolayca dahil edemezsiniz (bu sınıfları değiştiremezsiniz). Böylelikle, dev bir switchnesne çalışma zamanı, seçim silahınız olacaktır.


11

Java'da çağırılacak yöntem (hangi yöntem imzasının kullanılacağı gibi) derleme zamanında belirlenir, bu nedenle derleme zamanı türüne göre gider.

Bunu aşmanın tipik modeli, yöntemdeki nesne türünü Nesne imzasıyla kontrol etmek ve yönteme bir dönüşümle delege etmektir.

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

Çok sayıda türünüz varsa ve bu yönetilemezse, o zaman yöntem aşırı yüklemesi muhtemelen doğru yaklaşım değildir, bunun yerine genel yöntem yalnızca Object'i almalı ve nesne türü başına uygun işlemeyi delege etmek için bir tür strateji modeli uygulamalıdır.


4

String, Integer, Boolean, Long vb. Gibi birkaç temel Java türünü alabilen "Parametre" adlı bir sınıfın doğru yapıcısını çağırmakla benzer bir sorun yaşadım. Bir Nesne dizisi verildiğinde, onları bir diziye dönüştürmek istiyorum girdi dizisindeki her bir Object için en spesifik kurucuyu çağırarak Parameter nesnelerimden. Ayrıca, IllegalArgumentException oluşturacak yapıcı Parametresini (Object o) tanımlamak istedim. Elbette bu yöntemin dizimdeki her Object için çağrıldığını buldum.

Kullandığım çözüm, kurucuya yansıma yoluyla bakmaktı ...

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

Çirkin bir örnek, anahtar ifadesi veya ziyaretçi kalıbı gerekmez! :)


2

Java, hangi yöntemin çağrılacağını belirlemeye çalışırken referans türüne bakar. Kodunuzu zorlamak istiyorsanız, 'doğru' yöntemi seçersiniz, alanlarınızı belirli bir türün örnekleri olarak ilan edebilirsiniz:

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

Ayrıca, parametrelerinizi parametre türü olarak da çevirebilirsiniz:

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

1

Yöntem çağrısında belirtilen bağımsız değişkenlerin sayısı ve türleri ile aşırı yüklenmiş bir yöntemin yöntem imzası arasında tam bir eşleşme varsa, o zaman çağrılacak yöntem budur. Nesne referanslarını kullanıyorsunuz, bu nedenle java derleme sırasında Object parametresi için doğrudan Object'i kabul eden bir yöntem olduğuna karar verir. Bu yöntemi 3 kez çağırdı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.