Java neden bir süper tip çıkaramıyor?


19

Hepimiz Long'un uzandığını biliyoruz Number. Peki bu neden derlenmiyor?

Ve withprogramın herhangi bir manuel döküm olmadan derlenmesi için yöntemi nasıl tanımlayabilirim ?

import java.util.function.Function;

public class Builder<T> {
  static public interface MyInterface {
    Number getNumber();
    Long getLong();
  }

  public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
    return null;//TODO
  }

  public static void main(String[] args) {
    // works:
    new Builder<MyInterface>().with(MyInterface::getLong, 4L);
    // works:
    new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
    // works:
    new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
    // works:
    new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
    // compilation error: Cannot infer ...
    new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
    // compilation error: Cannot infer ...
    new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
    // compiles but also involves typecast (and Casting Number to Long is not even safe):
    new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
    // compiles but also involves manual conversion:
    new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
    // compiles (compiler you are kidding me?): 
    new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);

  }
  static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
    return f;
  }

}
  • İçin tür bağımsız değişkenleri çıkartılamıyor <F, R> with(F, R)
  • Builder.MyInterface türündeki getNumber () türü Number'dır, bu tanımlayıcının dönüş türüyle uyumsuzdur: Uzun

Kullanım durumu için bakınız: Lamda dönüş tipi derleme zamanında neden kontrol edilmiyor?


Gönderebilir misin MyInterface?
Maurice Perry

zaten sınıfın içinde
jukzi

Hmm denedim <F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)ama aldım java.lang.Number cannot be converted to java.lang.Long), bu şaşırtıcı çünkü derleyicinin dönüş değerinin getterdönüştürülmesi gerekeceği fikrini nereden aldığını göremiyorum returnValue.
Jingx

@jukzi Tamam. Üzgünüm kaçırdım.
Maurice Perry

Değişen Number getNumber()için <A extends Number> A getNumber()markaları şeyler çalışması. İstediğin bu olup olmadığı hakkında hiçbir fikrim yok. Diğerlerinin söylediği gibi, sorun, örneğin MyInterface::getNumbergeri dönen bir işlev olabilir . Beyanınız, derleyicinin mevcut diğer bilgilere dayanarak dönüş türünü daraltmasına izin vermez. Genel bir dönüş türü kullanarak, derleyicinin bunu yapmasına izin verirsiniz, dolayısıyla çalışır. DoubleLong
Giacomo Alzetta

Yanıtlar:


9

Bu ifade :

new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

şu şekilde yeniden yazılabilir:

new Builder<MyInterface>().with(myInterface -> myInterface.getNumber(), 4L);

Yöntem imzasını dikkate alarak:

public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue)
  • R çıkarılacak Long
  • F olacak Function<MyInterface, Long>

ve bu çıkarım yapılacak bir yöntem referansı geçiyorsunuz Function<MyInterface, Number>Bu anahtar - derleyici aslında Longböyle bir imzalı bir fonksiyondan geri dönmek istediğinizi nasıl tahmin etmeli ? Senin için downcasting yapmayacak.

Yana Numberüst sınıf olduğunu Longve Numbernecessairly olmayan bir Long(o derleme değil bu yüzden) - kendi başınıza açıkça döküm olurdu:

new Builder<MyInterface>().with(myInterface -> (Long) myInterface.getNumber(), 4L);

yöntem çağrısı sırasında yaptığınız gibi genel bağımsız değişkenleri açık bir Fşekilde yapmak Function<MyIinterface, Long>veya iletmek:

new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);

ve biliyorum Rolarak görülecektir Numberve kod derlemek olacaktır.


Bu ilginç bir örnek. Ama yine de arayan herhangi bir döküm olmadan derlemek yapacak "ile" yönteminin bir tanımını arıyorum. Her neyse, bu fikir için teşekkürler.
jukzi

@jukzi Yapamazsın. Senin nasıl withyazdığı önemli değil . Sen var MJyInterface::getNumbertürüne sahip Function<MyInterface, Number>böylece R=Numberve daha sonra da sahip R=Longdiğer tartışmadan (Java değişmezleri polimorfik olmadığını unutmayın!). Bir dönüştürmek için her zaman mümkün değildir, çünkü bu noktada derleyici durdurur Numbera Long. Bunu düzeltmek için tek yol değiştirmektir MyInterfacekullanmak <A extends Number> Numberdönüş türü olarak, bu derleyici var kılan R=Ave daha sonra R=Longve o zamandan beri A extends Numbero yerini alabilirA=Long
Giacomo Alzetta

4

Hatanızla anahtarı tipi jenerik bildiriminde olduğunu F: F extends Function<T, R>. Çalışmayan ifade: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);İlk önce bir yeniniz var Builder<MyInterface>. Bu nedenle sınıfın ilanı ima eder T = MyInterface. Beyanınıza göre with, bu durumda Fbir Function<T, R>, bir olmalıdır Function<MyInterface, R>. Bu nedenle, parametre getterbir MyInterfaceas parametresini (yöntem başvuruları MyInterface::getNumberve memnuniyeti tarafından karşılanır MyInterface::getLong) Ralmalı ve işleve ikinci parametre ile aynı türde olması gereken döndürmelidir with. Şimdi, bunun tüm davalarınız için geçerli olup olmadığını görelim:

// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time, 
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

Bu sorunu aşağıdaki seçeneklerle "düzeltebilirsiniz":

// stick to Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// stick to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// explicitly convert the result of getNumber:
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
// explicitly convert the result of getLong:
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);

Bu noktanın ötesinde, çoğunlukla hangi seçeneğin uygulamanız için kod karmaşıklığını azalttığı için bir tasarım kararıdır, bu nedenle size en uygun olanı seçin.

Döküm yapmadan bunu yapamamanızın nedeni , Java Dil Spesifikasyonu'ndan aşağıdadır :

Boks dönüşümü, ilkel tipteki ifadeleri, karşılık gelen bir referans tipinin ifadeleri olarak kabul eder. Özellikle, aşağıdaki dokuz dönüşüme boks dönüşümleri denir :

  • Boolean türünden Boolean türüne
  • Tip baytından bayt tipine
  • Kısa tipten Kısa tipe
  • Karakter türünden Karakter türüne
  • İnt türünden Integer türüne
  • Uzun tipten Uzun tipe
  • Şamandıra tipinden Şamandıra tipine
  • Çift tipten Çift tipe
  • Null tipinden null tipine

Açıkça görebileceğiniz gibi, uzuntan Sayıya örtülü bir boks dönüşümü yoktur ve Uzuntan Sayıya genişletme dönüşümü yalnızca derleyici bir Uzun değil, bir Sayı gerektirdiğinden emin olduğunda gerçekleşebilir. Bir sayı gerektiren yöntem başvurusu ile Long sağlayan 4L arasında bir çakışma olduğu için, derleyici (bazı nedenlerden dolayı ???) Long'un bir Sayı olduğu mantıksal sıçramasını yapamaz ve bunun Fbir sonucudur Function<MyInterface, Number>.

Bunun yerine, işlev imzasını hafifçe düzenleyerek sorunu çözmeyi başardım:

public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
  return null;//TODO
}

Bu değişiklikten sonra aşağıdakiler gerçekleşir:

// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

Düzenleme:
Üzerinde biraz daha zaman geçirdikten sonra, alıcı tabanlı tip güvenliğini zorlamak can sıkıcı bir şekilde zordur. Aşağıda, bir üreticinin tip güvenliğini zorunlu kılmak için ayarlayıcı yöntemlerini kullanan çalışan bir örnek verilmiştir:

public class Builder<T> {

  static public interface MyInterface {
    //setters
    void number(Number number);
    void Long(Long Long);
    void string(String string);

    //getters
    Number number();
    Long Long();
    String string();
  }
  // whatever object we're building, let's say it's just a MyInterface for now...
  private T buildee = (T) new MyInterface() {
    private String string;
    private Long Long;
    private Number number;
    public void number(Number number)
    {
      this.number = number;
    }
    public void Long(Long Long)
    {
      this.Long = Long;
    }
    public void string(String string)
    {
      this.string = string;
    }
    public Number number()
    {
      return this.number;
    }
    public Long Long()
    {
      return this.Long;
    }
    public String string()
    {
      return this.string;
    }
  };

  public <R> Builder<T> with(BiConsumer<T, R> setter, R val)
  {
    setter.accept(this.buildee, val); // take the buildee, and set the appropriate value
    return this;
  }

  public static void main(String[] args) {
    // works:
    new Builder<MyInterface>().with(MyInterface::Long, 4L);
    // works:
    new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
    // compile time error, as it shouldn't work
    new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
    // works, as it always did
    new Builder<MyInterface>().with(MyInterface::Long, 4L);
    // works, as it should
    new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
    // works, as you wanted
    new Builder<MyInterface>().with(MyInterface::number, 4L);
    // compile time error, as you wanted
    new Builder<MyInterface>().with(MyInterface::number, "blah");
  }
}

Bir nesneyi inşa etmek için güvenli bir özellik sağlandığında, umarım gelecekte bir noktada yapıcıdan değiştirilemez bir veri nesnesi döndürebiliriz (belki toRecord()arabirime bir yöntem ekleyerek ve oluşturucuyu a olarak belirterek Builder<IntermediaryInterfaceType, RecordType>), böylece ortaya çıkan nesnenin değiştirildiği konusunda endişelenmenize bile gerek kalmaz. Dürüst olmak gerekirse, bir tür güvenli alan esnek oluşturucu elde etmek için çok çaba gerektirmesi mutlak bir utançtır, ancak bazı yeni özellikler, kod üretimi veya sinir bozucu bir yansıma olmadan muhtemelen imkansızdır.


Tüm çalışmalarınız için teşekkürler, ancak manuel yazımdan kaçınmaya yönelik herhangi bir gelişme görmüyorum. Benim saf anlayışım, bir derleyicinin bir insanın yapabileceği her şeyi çıkarımda bulunabilmesi (yani yazım yapabilmesi) gerektiğidir.
jukzi

Saf anlayışım aynı, ama her ne sebeple olursa olsun bu şekilde çalışmaz. Yine de istenen etkiyi sağlayan bir geçici çözüm buldum
Avi

Tekrar teşekkürler. Ancak yeni teklifiniz çok geniştir (bkz. Stackoverflow.com/questions/58337639 ) ".with (MyInterface :: getNumber," BEN BİR NUMARA DEĞİLİMİZ ")";
jukzi

"Derleyici yöntem başvuru türünü çıkartamaz ve aynı anda 4L" cümleniz iyidir. Ama sadece tam tersini istiyorum. derleyici Sayı'yı ilk parametreye dayalı olarak denemeli ve ikinci parametrenin Numarası'na genişletmelidir.
jukzi

1
WHAAAAAAAAAAAT? Neden BiConsumer, İşlev çalışmazken istendiği gibi çalışıyor? Ben bir fikrim yok. Bu tam olarak istediğim typesafety olduğunu itiraf ama maalesef alıcıları ile çalışmaz. NEDEN NEDEN NEDEN.
jukzi

1

Derleyici, R'nin Uzun olduğuna karar vermek için 4L değerini kullandı ve getNumber (), mutlaka Uzun olmayan bir Sayı döndürür.

Ama değerin neden yönteme göre önceliğe sahip olduğundan emin değilim ...


0

Java derleyicisi genellikle birden çok / iç içe genel tür veya joker karakter çıkarmada iyi değildir. Çoğu zaman, bazı türleri yakalamak veya çıkarmak için yardımcı bir işlev kullanmadan derleyecek bir şey elde edemiyorum.

Ancak, tam Functionolarak as türünü yakalamanız gerekiyor Fmu? Değilse, belki aşağıdaki işler ve gördüğünüz gibi, aynı zamanda alt tipleri ile çalışıyor gibi görünüyor Function.

import java.util.function.Function;
import java.util.function.UnaryOperator;

public class Builder<T> {
    public interface MyInterface {
        Number getNumber();
        Long getLong();
    }

    public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
        return null;
    }

    // example subclass of Function
    private static UnaryOperator<String> stringFunc = (s) -> (s + ".");

    public static void main(String[] args) {
        // works
        new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
        // works
        new Builder<String>().with(stringFunc, "s");

    }
}

"(MyInterface :: getNumber," BİR SAYI DEĞİL ") ile derlenmemelidir
jukzi

0

En ilginç kısım, bu 2 hat arasındaki farkta yatıyor, bence:

// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

İlk durumda, bu Taçıkça Number, 4Laynı zamanda a Number, sorun değil. İkinci durumda, 4La Long, yani Ta Long, yani işleviniz uyumlu değildir ve Java Numberya da kastettiğinizi bilemez Long.


0

Aşağıdaki imzayla:

public <R> Test<T> with(Function<T, ? super R> getter, R returnValue)

yöntem dışında iki tür değişkeni olmasını gerektiren üçüncü hariç tüm örnekleriniz derlenir.

Sürümünüzün çalışmamasının nedeni, Java'nın yöntem başvurularının belirli bir türü olmamasıdır. Bunun yerine, verilen bağlamda gerekli olan türe sahiptirler. Senin durumda, Rolması anlaşılmaktadır Longnedeniyle 4L, ancak alıcı türü olamaz Function<MyInterface,Long>Java, jenerik tipleri kendi argümanları içinde değişmez çünkü.


Kodunuz with( getNumber,"NO NUMBER")istenmeyecek şekilde derlenir. Ayrıca jeneriklerin her zaman değişmez olduğu doğru değildir (ayarlayıcıların jeneriklerinin alıcılardan farklı davrandığını kanıtlamak için stackoverflow.com/a/58378661/9549750'ye bakın )
jukzi

@jukzi Ah, çözümüm zaten Avi tarafından önerilmişti. Çok kötü... :-). Bu arada, Thing<Cat>bir Thing<? extends Animal>değişkene a atayabileceğimiz doğrudur , ancak gerçek kovaryans için a'nın a'ya Thing<Cat>atanabileceğini beklerim Thing<Animal>. Kotlin gibi diğer diller, eş ve karşıt değişken tipi değişkenleri tanımlamaya izin verir.
Hoopje
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.