जावा एक सुपरपाइप का अनुमान क्यों नहीं लगा सकता है?


19

हम सभी जानते हैं कि लोंग का विस्तार होता है Number। तो यह संकलन क्यों नहीं करता है?

और विधि को कैसे परिभाषित किया withजाए ताकि कार्यक्रम बिना किसी मैनुअल कास्ट के संकलित हो?

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

}
  • के लिए अनुमान प्रकार तर्क नहीं कर सकते <F, R> with(F, R)
  • प्रकार बिल्डर से getNumber () का प्रकार। MyInterface संख्या है, यह विवरणक के रिटर्न प्रकार के साथ असंगत है: लंबा

उपयोग के मामले के लिए देखें: संकलित समय पर लंबा रिटर्न प्रकार की जांच क्यों नहीं की जाती है


क्या आप पोस्ट कर सकते हैं MyInterface?
मौरिस पेरी

इसके पहले से ही वर्ग के अंदर
jukzi

हम्म मैंने कोशिश की, <F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)लेकिन मिला java.lang.Number cannot be converted to java.lang.Long), जो आश्चर्य की बात है क्योंकि मैं नहीं देखता कि कंपाइलर को यह विचार कहां से मिलता है कि रिटर्न वैल्यू को इसमें getterबदलना होगा returnValue
जिंगक्स

@ जुकीजी ओके। क्षमा करें, मुझे वह याद आया।
मौरिस पेरी

बदलने Number getNumber()के लिए <A extends Number> A getNumber()बनाता है सामान काम करते हैं। कोई विचार नहीं कि यह वही है जो आप चाहते थे। जैसा कि अन्य लोगों ने कहा है कि मुद्दा यह है कि MyInterface::getNumberएक ऐसा कार्य हो सकता है जो Doubleउदाहरण के लिए लौटे और नहीं Long। आपकी घोषणा संकलक को अन्य सूचना के आधार पर रिटर्न प्रकार को संकीर्ण करने की अनुमति नहीं देती है। एक सामान्य रिटर्न प्रकार का उपयोग करके आप संकलक को ऐसा करने की अनुमति देते हैं, इसलिए यह काम करता है।
जियाको अल्जेटा

जवाबों:


9

यह अभिव्यक्ति:

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

के रूप में फिर से लिखा जा सकता है:

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

खाता विधि हस्ताक्षर में लेना:

public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue)
  • R से अनुमान लगाया जाएगा Long
  • F होगा Function<MyInterface, Long>

और आप एक विधि संदर्भ पास करते हैं जो कि अनुमानित होगा। Function<MyInterface, Number>यह कुंजी है - कंपाइलर को यह कैसे अनुमान लगाना चाहिए कि आप वास्तव में Longइस तरह के हस्ताक्षर के साथ एक फ़ंक्शन से वापस लौटना चाहते हैं ? यह आपके लिए डाउनकास्टिंग नहीं करेगा।

के बाद से Numberकी सुपर क्लास है Longऔर Numbernecessairly नहीं है एक Long(यही कारण है कि यह संकलन नहीं करता है) - आप अपने दम पर स्पष्ट रूप से कास्ट करने के लिए होगा:

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

जेनेरिक तर्कों Fको बनाने Function<MyIinterface, Long>या पास करने के लिए स्पष्ट रूप से विधि कॉल के दौरान जैसा आपने किया था:

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

और कोड के Rरूप में देखा जाएगा और पता चल जाएगा Number


यह एक दिलचस्प टाइपकास्ट है। लेकिन फिर भी मैं "के साथ" विधि की परिभाषा खोज रहा हूं, जो बिना किसी कलाकारों के कॉलर को संकलित करेगा। वैसे भी उस विचार के लिए धन्यवाद।
jukzi

@jukzi तुम नहीं कर सकते। इससे कोई फर्क नहीं पड़ता कि आपका withलिखा कैसा है। आपके पास MJyInterface::getNumberप्रकार Function<MyInterface, Number>है R=Numberऔर फिर आपके पास R=Longअन्य तर्क भी हैं (याद रखें कि जावा शाब्दिक शब्द बहुरूपी नहीं हैं)। इस बिंदु पर कंपाइलर बंद हो जाता है क्योंकि ए Numberको ए में बदलना हमेशा संभव नहीं होता है Long। इसे ठीक करने का एकमात्र तरीका रिटर्न प्रकार के रूप में MyInterfaceउपयोग करने के लिए बदलना है <A extends Number> Number, इससे कंपाइलर R=Aऔर उसके बाद R=Longऔर A extends Numberइसके स्थानापन्न हो सकते हैंA=Long
जियाको अल्जेटा

4

अपने त्रुटि की कुंजी के प्रकार के जेनेरिक घोषणा में है F: F extends Function<T, R>। काम नहीं करने वाला कथन है: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);पहला, आपके पास एक नया है Builder<MyInterface>। इसलिए वर्ग की घोषणा का अर्थ है T = MyInterface। आपकी घोषणा के अनुसार with, Fएक होना चाहिए Function<T, R>, जो Function<MyInterface, R>इस स्थिति में है। इसलिए, पैरामीटर getterएक रखना चाहिए MyInterfaceपैरामीटर (विधि संदर्भ से संतुष्ट के रूप में MyInterface::getNumberऔर MyInterface::getLong), और बदले R, जो कार्य करने के लिए दूसरा पैरामीटर के रूप में एक ही प्रकार का होना चाहिए with। अब, देखते हैं कि क्या यह आपके सभी मामलों के लिए है:

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

आप निम्न विकल्पों के साथ इस समस्या को "ठीक" कर सकते हैं:

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

इस बिंदु से परे, यह ज्यादातर एक डिजाइन निर्णय है जिसके लिए विकल्प आपके विशेष एप्लिकेशन के लिए कोड जटिलता को कम करता है, इसलिए जो भी आपको सबसे अच्छा लगता है उसे चुनें।

जावा भाषा विनिर्देश से निम्नलिखित कारण यह हो सकता है कि आप ऐसा नहीं कर सकते हैं :

बॉक्सिंग रूपांतरण एक आदिम प्रकार के भावों को संबंधित संदर्भ प्रकार के अभिव्यक्तियों के रूप में मानता है। विशेष रूप से, निम्नलिखित नौ रूपांतरणों को बॉक्सिंग रूपांतरण कहा जाता है :

  • टाइप बूलियन से टाइप बूलियन
  • टाइप बाइट से टाइप बाइट तक
  • टाइप शॉर्ट से टाइप शॉर्ट तक
  • टाइप चार से टाइप कैरेक्टर
  • टाइप इंट से टाइप इंटेगर
  • टाइप लॉन्ग से टाइप लॉन्ग
  • फ्लोट टाइप से फ्लोट टाइप करें
  • टाइप डबल से टाइप डबल
  • अशक्त प्रकार से अशक्त प्रकार तक

जैसा कि आप स्पष्ट रूप से देख सकते हैं कि लंबे समय से नंबर तक कोई निहित बॉक्सिंग रूपांतरण नहीं है, और लॉन्ग से नंबर तक का व्यापक रूपांतरण केवल तब हो सकता है जब कंपाइलर को यह सुनिश्चित हो कि उसे नंबर की आवश्यकता है और लॉन्ग की नहीं। जैसा कि विधि संदर्भ के बीच एक संघर्ष होता है जिसके लिए एक नंबर और 4L की आवश्यकता होती है जो लॉजिकल प्रदान करता है (किसी कारण से ???) तार्किक छलांग लगाने में असमर्थ है कि लॉन्ग एक संख्या है और यह घटाता है कि FFunction<MyInterface, Number>

इसके बजाय, मैं फ़ंक्शन हस्ताक्षर को थोड़ा संपादित करके समस्या को हल करने में कामयाब रहा:

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

इस परिवर्तन के बाद, निम्नलिखित होता है:

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

संपादित करें:
इस पर कुछ और समय बिताने के बाद, गेट्टर-आधारित प्रकार की सुरक्षा को लागू करना मुश्किल है। यहां एक कार्यशील उदाहरण है जो बिल्डर के प्रकार-सुरक्षा को लागू करने के लिए सेटर विधियों का उपयोग करता है:

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

ऑब्जेक्ट के निर्माण की सुरक्षित-सुरक्षित क्षमता प्रदान की है, उम्मीद है कि भविष्य में किसी बिंदु पर हम बिल्डर से एक अपरिवर्तनीय डेटा ऑब्जेक्ट को वापस करने में सक्षम होंगे (हो सकता है toRecord()कि इंटरफ़ेस में एक विधि जोड़कर , और बिल्डर को निर्दिष्ट करते हुए Builder<IntermediaryInterfaceType, RecordType>), इसलिए आपको परिणामी वस्तु के संशोधित होने की भी चिंता नहीं करनी चाहिए। ईमानदारी से, यह एक पूरी तरह से शर्म की बात है कि इसे एक प्रकार का सुरक्षित क्षेत्र-लचीला बिल्डर प्राप्त करने के लिए बहुत प्रयास की आवश्यकता है, लेकिन कुछ नई सुविधाओं, कोड पीढ़ी या प्रतिबिंब की कष्टप्रद राशि के बिना यह असंभव है।


आपके सभी काम के लिए धन्यवाद, लेकिन मुझे मैनुअल टाइपकास्ट से बचने की दिशा में कोई सुधार नहीं दिखता है। मेरी भोली समझ यह है कि एक कंपाइलर को वह सब कुछ (यानी टाइपकास्ट) करने में सक्षम होना चाहिए जो एक मानव कर सकता है।
जुकज़ी

@ जुकज़ी मेरी भोली समझ वही है, लेकिन जिस भी कारण से यह उस तरह से काम नहीं करता है। मैं एक ऐसे समाधान के साथ आया हूं जो बहुत वांछित प्रभाव प्राप्त करता है, हालांकि
AVI

एक बार फिर धन्यवाद। लेकिन आपका नया प्रस्ताव बहुत व्यापक है (देखें stackoverflow.com/questions/58337639 ) क्योंकि यह ".with (MyInterface :: getNumber," I AM NOT A NUMBER ") के संकलन की अनुमति देता है;
जुकज़ी

आपका वाक्य "कंपाइलर विधि संदर्भ के प्रकार का पता नहीं लगा सकता है और 4L एक ही समय में" शांत है। लेकिन मैं इसे दूसरे तरीके से घेरना चाहता हूं। कंपाइलर को पहले पैरामीटर के आधार पर नंबर की कोशिश करनी चाहिए और दूसरे पैरामीटर की संख्या के लिए एक चौड़ीकरण करना चाहिए।
जुकज़ी

1
WHAAAAAAAAAAAT? BiConsumer कार्य क्यों नहीं करता है जबकि फ़ंक्शन नहीं करता है? मुझे अंदाजा नहीं है। मैं मानता हूँ कि यह बिल्कुल वैसा ही है जैसा मैं चाहता था लेकिन दुर्भाग्य से गेटर्स के साथ काम नहीं करता। क्यों क्यों क्यों।
जुकज़ी

1

ऐसा लगता है कि संकलक ने 4L का उपयोग यह तय करने के लिए किया है कि R लंबा है, और getNumber () एक नंबर लौटाता है, जो जरूरी नहीं कि एक लंबा हो।

लेकिन मुझे यकीन नहीं है कि मूल्य विधि पर पूर्वता क्यों लेता है ...


0

जावा कंपाइलर सामान्य रूप से कई / नेस्टेड जेनेरिक प्रकारों या वाइल्डकार्डों के संदर्भ में अच्छा नहीं है। अक्सर मैं कुछ प्रकारों को पकड़ने या उनका पता लगाने के लिए एक सहायक फ़ंक्शन का उपयोग किए बिना संकलन करने के लिए कुछ नहीं प्राप्त कर सकता हूं।

लेकिन, क्या तुम सच में, ठीक वैसी ही कब्जा करने की आवश्यकता है Functionके रूप में F? यदि नहीं, तो शायद निम्नलिखित काम करता है, और जैसा कि आप देख सकते हैं, यह भी उप के साथ काम करने लगता है 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," NOT A NUMBER ") के साथ"
jukzi

0

सबसे दिलचस्प हिस्सा उन 2 लाइनों के बीच अंतर में है, मुझे लगता है:

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

पहले मामले में, Tस्पष्ट रूप से है Number, इसलिए 4Lभी Numberकोई समस्या नहीं है। दूसरे मामले में, 4Lएक है Long, तो Tएक है Long, इसलिए आपका फ़ंक्शन संगत नहीं है, और जावा को पता नहीं चल सकता है कि आपका मतलब क्या है Numberया Long


0

निम्नलिखित हस्ताक्षर के साथ:

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

आपके सभी उदाहरण संकलित करते हैं, तीसरे को छोड़कर, जिसमें स्पष्ट रूप से दो प्रकार के चर की विधि की आवश्यकता होती है।

आपके संस्करण के काम न करने का कारण यह है कि जावा के विधि संदर्भों में एक विशिष्ट प्रकार नहीं है। इसके बजाय, उनके पास वह प्रकार है जो दिए गए संदर्भ में आवश्यक है। आपके मामले में, के कारण Rहोने का अनुमान है , लेकिन गेट्टर के प्रकार नहीं हो सकते क्योंकि जावा में, सामान्य प्रकार उनके तर्कों में अपरिवर्तनीय हैं।Long4LFunction<MyInterface,Long>


आपका कोड संकलन करेगा with( getNumber,"NO NUMBER")जो वांछित नहीं है। यह भी सच नहीं है कि जेनेरिक हमेशा अपरिवर्तनीय होते हैं (देखें) stackoverflow.com/a/58378661/9549750 के लिए एक साबित अन्य व्यवहार setters के कि जेनरिक तो टिककर खेल के उन)
jukzi

@ जूकीजी आह, मेरा समाधान पहले से ही अवि द्वारा प्रस्तावित किया गया था। बहुत बुरा... :-)। वैसे, यह सच है कि हम एक असाइन कर सकते हैंThing<Cat> एक Thing<? extends Animal>वेरिएबल को , लेकिन वास्तविक सहसंयोजक के लिए मैं उम्मीद करूंगा कि ए Thing<Cat>को सौंपा जा सकता है Thing<Animal>। अन्य भाषाएँ, जैसे कोटलिन, सह-और विपरीत प्रकार के चर को परिभाषित करने की अनुमति देती हैं।
होपजे
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.