জাভা কেন একটি সুপারটাইপ অনুমান করতে পারে না?


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 () টাইপ করুন y

ব্যবহারের ক্ষেত্রে দেখুন: সংকলনের সময় ল্যাম্বদা রিটার্ন টাইপ কেন পরীক্ষা করা হয় না


আপনি পোস্ট করতে পারেন MyInterface?
মরিস পেরি

এটি ইতিমধ্যে শ্রেণীর অভ্যন্তরে
জুলুকি

হুম আমি চেষ্টা করেছিলাম <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এবং Numberএটি প্রয়োজনীয়ভাবে একটি নয় 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হবে এবং কোড সংকলন করবে।


একটি আকর্ষণীয় টাইপকাস্ট চালায়। তবে তবুও আমি "এর সাথে" পদ্ধতির একটি সংজ্ঞা অনুসন্ধান করছি যা কলারকে কোনও castালাই ছাড়াই সংকলন করবে। যাইহোক যে ধারণা জন্য ধন্যবাদ।
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
Giacomo, Alzetta

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

এই বিষয়টির বাইরে, এটি বেশিরভাগই একটি ডিজাইনের সিদ্ধান্ত যার জন্য বিকল্পটি আপনার নির্দিষ্ট অ্যাপ্লিকেশনটির জন্য কোড জটিলতা হ্রাস করে, তাই আপনি যা পছন্দ করেন তা চয়ন করুন।

জাভা ল্যাঙ্গুয়েজ স্পেসিফিকেশন থেকে আপনি নীচে মিথ্যা কাস্টিং ব্যতীত এটি করতে পারবেন না :

বক্সিং রূপান্তর কোনও আদিম ধরণের অভিব্যক্তিটিকে সংশ্লিষ্ট রেফারেন্স প্রকারের এক্সপ্রেশন হিসাবে বিবেচনা করে। বিশেষত, নিম্নলিখিত নয়টি রূপান্তরকে বক্সিং রূপান্তর বলা হয় :

  • টাইপ বুলিয়ান থেকে টাইপ করুন বুলিয়ান
  • টাইপ বাইট থেকে টাইপ বাইট
  • টাইপ শর্ট থেকে শর্ট টাইপ করুন
  • চরিত্র থেকে টাইপ চর থেকে টাইপ করুন
  • টাইপ int থেকে টাইপ পূর্ণসংখ্যার
  • টাইপ দীর্ঘ থেকে টাইপ দীর্ঘ
  • টাইপ ফ্লোট থেকে ফ্ল্যাট টাইপ করুন
  • টাইপ ডাবল থেকে টাইপ ডাবল
  • নাল টাইপ থেকে নাল টাইপ

আপনি স্পষ্টভাবে দেখতে পাচ্ছেন যে লম্বা থেকে সংখ্যায় কোনও অন্তর্নিহিত বক্সিং রূপান্তর নেই এবং লং থেকে সংখ্যায় প্রসারিত রূপান্তর কেবল তখনই ঘটতে পারে যখন সংকলকটি নিশ্চিত হয় যে এটির জন্য কোনও সংখ্যার প্রয়োজন এবং লম্বা নয়। যেহেতু পদ্ধতির রেফারেন্সের মধ্যে একটি সংখ্যার এবং একটি লং সরবরাহকারী 4L এর মধ্যে বিরোধ রয়েছে তাই সংকলক (কোনও কারণে ???) লম্বালটি হ'ল লজিকাল লিপ তৈরি করতে অক্ষম এবং Fএটি একটি সংখ্যাকে ছাড় করুনFunction<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>), সুতরাং ফলস্বরূপ অবজেক্টটি সংশোধিত হওয়ার বিষয়ে আপনাকে ভাবতে হবে না। সত্য, এটি একটি নিখুঁত লজ্জার বিষয় যে এটির জন্য একটি টাইপ-নিরাপদ ফিল্ড-নমনীয় নির্মাতা পেতে এত প্রচেষ্টা প্রয়োজন, তবে এটি কিছু নতুন বৈশিষ্ট্য, কোড জেনারেশন বা একটি বিরক্তিকর প্রতিফলন ছাড়া সম্ভবত অসম্ভব।


আপনার সমস্ত কাজের জন্য ধন্যবাদ তবে আমি ম্যানুয়াল টাইপকাস্ট এড়ানোর দিকে কোনও উন্নতি দেখতে পাচ্ছি না। আমার নিষ্পাপ বোধগম্যতা হ'ল একটি সংকলককে মানুষের দ্বারা যা কিছু করা যায় তা নির্ধারণ করা (অর্থাৎ টাইপকাস্ট) করা উচিত।
jukzi

@ জুকজি আমার নির্বোধ বোঝাপড়া একই, তবে যে কোনও কারণেই এটি সেভাবে কাজ করে না। আমি এমন একটি কাজের সাথে হাজির হয়েছি যা পছন্দসই প্রভাব অর্জন করে, যদিও
আভি

আবার ধন্যবাদ. কিন্তু আপনার নতুন প্রস্তাব খুবই চওড়া (দেখতে stackoverflow.com/questions/58337639 ) যেহেতু এটি ".with (MyInterface :: getNumber," আমি পূর্বাহ্ণ Not a Number ")" সংকলন পারবেন;
jukzi

আপনার বাক্য "সংকলক পদ্ধতি প্রকারের রেফারেন্স এবং একই সাথে 4L অনুমান করতে পারে না" দুর্দান্ত। তবে আমি এটিকে অন্যদিকে চাই। সংকলকটি প্রথম প্যারামিটারের উপর ভিত্তি করে নম্বর চেষ্টা করবে এবং দ্বিতীয় প্যারামিটারের সংখ্যায় একটি প্রশস্তকরণ করবে।
jukzi

1
WHAAAAAAAAAAAT? বায়কনসুমার যখন ফাংশনটি না করে তখন উদ্দেশ্য হিসাবে কেন কাজ করে? আমি ধারণা পাই না। আমি স্বীকার করি এটি হ'ল টাইপসফটিটি যা আমি চেয়েছিলাম তবে দুর্ভাগ্যজনকভাবে গ্রাহকদের সাথে কাজ করে না। কেন কেন।
jukzi

1

দেখে মনে হচ্ছে সংকলকটি আর 4 লম্বা হয় তা নির্ধারণের জন্য 4L মানটি ব্যবহার করেছে এবং 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");

    }
}

"সাথে (মাইইনফেরফেস :: getNumber," একটি নম্বর নয় ")" সংকলন করা উচিত নয়
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অনুমান করা Longহয় 4L, কিন্তু প্রাপ্তকারী টাইপ করতে পারে না Function<MyInterface,Long>কারণ জাভাতে, জেনেরিক প্রকারগুলি তাদের যুক্তিতে অবিচ্ছিন্ন হয়।


আপনার কোডটি সঙ্কলিত করবে with( getNumber,"NO NUMBER")যা পছন্দসই নয়। এছাড়াও এটা সত্য নয় যে জেনেরিক্স সবসময় পরিবর্তিত (দেখুন হয় stackoverflow.com/a/58378661/9549750 জন্য একটি প্রমাণ অন্যান্য আচরণ setters যে জেনেরিক্স তারপর getters খোলা থাকে)
jukzi

@ জুকজি আহ, আমার সমাধান ইতিমধ্যে অ্যাভি দ্বারা প্রস্তাবিত হয়েছিল। খুব খারাপ... :-). যাইহোক, এটি সত্য যে আমরা Thing<Cat>একটি Thing<? extends Animal>পরিবর্তনশীলকে একটি বরাদ্দ করতে পারি , তবে প্রকৃত স্বৈরশাসনের জন্য আমি প্রত্যাশা করব যে এটি একটিতে Thing<Cat>নির্ধারিত হতে পারে Thing<Animal>। অন্যান্য ভাষা যেমন কোটলিন কো-এবং বিপরীত প্রকারের ভেরিয়েবল সংজ্ঞায়িত করতে দেয়।
হুপজে
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.