เหตุใดพารามิเตอร์ประเภทจึงแข็งแรงกว่าจึงเป็นพารามิเตอร์วิธี


12

ทำไม

public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {...}

เข้มงวดมากขึ้นแล้ว

public <R> Builder<T> with(Function<T, R> getter, R returnValue) {...}

นี่คือการติดตามขึ้นไปบนทำไมแลมบ์ดาชนิดกลับไม่ได้ตรวจสอบที่รวบรวมเวลา ฉันพบว่าใช้วิธีการwithX()เช่น

.withX(MyInterface::getLength, "I am not a Long")

สร้างข้อผิดพลาดเวลารวบรวมที่ต้องการ:

ประเภทของ getLength () จากประเภท BuilderExample.MyInterface มีความยาวไม่สามารถใช้ร่วมกับชนิดส่งคืนของ descriptor: String

ในขณะที่ใช้วิธีการwith()ไม่ได้

ตัวอย่างเต็มรูปแบบ:

import java.util.function.Function;

public class SO58376589 {
  public static class Builder<T> {
    public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {
      return this;
    }

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

  }

  static interface MyInterface {
    public Long getLength();
  }

  public static void main(String[] args) {
    Builder<MyInterface> b = new Builder<MyInterface>();
    Function<MyInterface, Long> getter = MyInterface::getLength;
    b.with(getter, 2L);
    b.with(MyInterface::getLength, 2L);
    b.withX(getter, 2L);
    b.withX(MyInterface::getLength, 2L);
    b.with(getter, "No NUMBER"); // error
    b.with(MyInterface::getLength, "No NUMBER"); // NO ERROR !!
    b.withX(getter, "No NUMBER"); // error
    b.withX(MyInterface::getLength, "No NUMBER"); // error !!!
  }
}

javac SO58376589.java

SO58376589.java:32: error: method with in class Builder<T> cannot be applied to given types;
    b.with(getter, "No NUMBER"); // error
     ^
  required: Function<MyInterface,R>,R
  found: Function<MyInterface,Long>,String
  reason: inference variable R has incompatible bounds
    equality constraints: Long
    lower bounds: String
  where R,T are type-variables:
    R extends Object declared in method <R>with(Function<T,R>,R)
    T extends Object declared in class Builder
SO58376589.java:34: error: method withX in class Builder<T> cannot be applied to given types;
    b.withX(getter, "No NUMBER"); // error
     ^
  required: F,R
  found: Function<MyInterface,Long>,String
  reason: inference variable R has incompatible bounds
    equality constraints: Long
    lower bounds: String
  where F,R,T are type-variables:
    F extends Function<MyInterface,R> declared in method <R,F>withX(F,R)
    R extends Object declared in method <R,F>withX(F,R)
    T extends Object declared in class Builder
SO58376589.java:35: error: incompatible types: cannot infer type-variable(s) R,F
    b.withX(MyInterface::getLength, "No NUMBER"); // error
           ^
    (argument mismatch; bad return type in method reference
      Long cannot be converted to String)
  where R,F,T are type-variables:
    R extends Object declared in method <R,F>withX(F,R)
    F extends Function<T,R> declared in method <R,F>withX(F,R)
    T extends Object declared in class Builder
3 errors

ตัวอย่างเพิ่มเติม

ตัวอย่างต่อไปนี้แสดงพฤติกรรมที่แตกต่างของวิธีการและพารามิเตอร์ประเภทที่ต้มลงไปในซัพพลายเออร์ นอกจากนี้ยังแสดงความแตกต่างของพฤติกรรมผู้บริโภคสำหรับพารามิเตอร์ประเภท และมันแสดงให้เห็นว่ามันไม่ได้สร้างความแตกต่างไม่ว่าจะเป็น Consumer หรือ Supplier สำหรับพารามิเตอร์ method

import java.util.function.Consumer;
import java.util.function.Supplier;
interface TypeInference {

  Number getNumber();

  void setNumber(Number n);

  @FunctionalInterface
  interface Method<R> {
    TypeInference be(R r);
  }

  //Supplier:
  <R> R letBe(Supplier<R> supplier, R value);
  <R, F extends Supplier<R>> R letBeX(F supplier, R value);
  <R> Method<R> let(Supplier<R> supplier);  // return (x) -> this;

  //Consumer:
  <R> R lettBe(Consumer<R> supplier, R value);
  <R, F extends Consumer<R>> R lettBeX(F supplier, R value);
  <R> Method<R> lett(Consumer<R> consumer);


  public static void main(TypeInference t) {
    t.letBe(t::getNumber, (Number) 2); // Compiles :-)
    t.lettBe(t::setNumber, (Number) 2); // Compiles :-)
    t.letBe(t::getNumber, 2); // Compiles :-)
    t.lettBe(t::setNumber, 2); // Compiles :-)
    t.letBe(t::getNumber, "NaN"); // !!!! Compiles :-(
    t.lettBe(t::setNumber, "NaN"); // Does not compile :-)

    t.letBeX(t::getNumber, (Number) 2); // Compiles :-)
    t.lettBeX(t::setNumber, (Number) 2); // Compiles :-)
    t.letBeX(t::getNumber, 2); // !!! Does not compile  :-(
    t.lettBeX(t::setNumber, 2); // Compiles :-)
    t.letBeX(t::getNumber, "NaN"); // Does not compile :-)
    t.lettBeX(t::setNumber, "NaN"); // Does not compile :-)

    t.let(t::getNumber).be(2); // Compiles :-)
    t.lett(t::setNumber).be(2); // Compiles :-)
    t.let(t::getNumber).be("NaN"); // Does not compile :-)
    t.lett(t::setNumber).be("NaN"); // Does not compile :-)
  }
}

1
เพราะการอนุมานด้วยอันหลัง แม้ว่าทั้งคู่จะเป็นฐานในกรณีการใช้งานที่จำเป็นต้องใช้ สำหรับคุณในอดีตอาจเข้มงวดและดี เพื่อความยืดหยุ่นคนอื่นสามารถชอบหลัง
Naman

คุณพยายามรวบรวมสิ่งนี้ใน Eclipse หรือไม่? การค้นหาสตริงข้อผิดพลาดของรูปแบบที่คุณวางแนะนำว่านี่เป็นข้อผิดพลาดเฉพาะของ Eclipse (ecj) คุณได้รับปัญหาเดียวกันเมื่อคอมไพล์ด้วย raw javacหรือเครื่องมือ build เช่น Gradle หรือ Maven หรือไม่?
user31601

@ user31601 ฉันได้เพิ่มตัวอย่างแบบเต็มด้วย javac output ข้อความแสดงข้อผิดพลาดมีรูปแบบแตกต่างกันเล็กน้อย แต่ก็ยังคงมีคราสและ javac มีพฤติกรรมเดียวกัน
jukzi

คำตอบ:


12

นี่เป็นคำถามที่น่าสนใจจริงๆ คำตอบฉันเกรงว่าจะซับซ้อน

TL; DR

การหาข้อแตกต่างนั้นเกี่ยวข้องกับการอ่านเชิงลึกของข้อกำหนดการอนุมานแบบจาวาของ Java แต่โดยทั่วไปแล้วจะต้องทำสิ่งนี้:

  • สิ่งอื่น ๆ ที่เท่าเทียมกันคอมไพเลอร์อ้างถึงประเภทที่เฉพาะเจาะจงที่สุดเท่าที่จะทำได้
  • อย่างไรก็ตามหากสามารถค้นหาการแทนที่สำหรับพารามิเตอร์ type ที่เป็นไปตามข้อกำหนดทั้งหมดการรวบรวมจะสำเร็จอย่างไรก็ตามการทดแทนที่คลุมเครือจะกลายเป็น
  • สำหรับwithการทดแทน (คลุมเครือ) นั้นเป็นไปตามข้อกำหนดทั้งหมดในR:Serializable
  • สำหรับwithXการแนะนำของชนิดพารามิเตอร์เพิ่มเติมFกองกำลังคอมไพเลอร์ในการแก้ไขครั้งแรกโดยไม่คำนึงถึงข้อ จำกัดR แก้ไขเป็น (เฉพาะเจาะจงมากขึ้น) ซึ่งหมายความว่าการอนุมานว่าล้มเหลวF extends Function<T,R>RStringF

สัญลักษณ์แสดงหัวข้อสุดท้ายนี้เป็นสิ่งสำคัญที่สุด แต่ก็เป็นคลื่นด้วยมือมากที่สุด ฉันไม่สามารถคิดถึงการใช้ถ้อยคำที่กระชับได้ดีกว่าดังนั้นหากคุณต้องการรายละเอียดเพิ่มเติมฉันขอแนะนำให้คุณอ่านคำอธิบายแบบเต็มด้านล่าง

นี่เป็นพฤติกรรมที่ตั้งใจหรือไม่?

ฉันจะออกไปบนกิ่งไม้ที่นี่และบอกว่าไม่มี

ฉันไม่ได้บอกว่ามีข้อผิดพลาดในข้อมูลจำเพาะมากขึ้นว่า (ในกรณีของwithX) นักออกแบบภาษาได้ใส่มือของพวกเขาขึ้นและกล่าวว่า"มีบางสถานการณ์ที่อนุมานชนิดได้รับยากเกินไปดังนั้นเราก็จะล้มเหลว" แม้ว่าพฤติกรรมของคอมไพเลอร์ด้วยความเคารพwithXดูเหมือนจะเป็นสิ่งที่คุณต้องการฉันจะพิจารณาว่าเป็นผลข้างเคียงที่เกิดขึ้นโดยบังเอิญของข้อมูลจำเพาะปัจจุบันแทนที่จะตัดสินใจออกแบบเชิงบวก

เรื่องนี้เพราะมันแจ้งคำถามฉันควรพึ่งพาพฤติกรรมนี้ในการออกแบบแอปพลิเคชันของฉันหรือไม่ ฉันจะโต้แย้งว่าคุณไม่ควรเพราะคุณไม่สามารถรับประกันได้ว่าภาษาในอนาคตของรุ่นจะยังคงทำงานอย่างนี้ต่อไป

ในขณะที่มันเป็นความจริงที่นักออกแบบภาษาพยายามอย่างหนักที่จะไม่ทำลายโปรแกรมที่มีอยู่เมื่อพวกเขาปรับปรุงสเปคของพวกเขา / ออกแบบ / เรียบเรียงปัญหาคือว่าพฤติกรรมที่คุณต้องการที่จะพึ่งพาเป็นหนึ่งที่คอมไพเลอร์ในปัจจุบันล้มเหลว (คือไม่ได้เป็นโปรแกรมที่มีอยู่ ) การอัพเดท Langauge ทำให้โค้ดที่ไม่ได้คอมไพล์กลายเป็นคอมไพล์โค้ดตลอดเวลา ตัวอย่างเช่นรหัสต่อไปนี้สามารถรับประกันได้ว่าจะไม่รวบรวมใน Java 7 แต่จะรวบรวมใน Java 8:

static Runnable x = () -> System.out.println();

กรณีการใช้งานของคุณไม่แตกต่างกัน

อีกเหตุผลหนึ่งที่ฉันระมัดระวังเกี่ยวกับการใช้withXวิธีการของคุณคือFพารามิเตอร์เอง โดยทั่วไปแล้วพารามิเตอร์ประเภททั่วไปในวิธีการ (ที่ไม่ปรากฏในประเภทย้อนกลับ) มีอยู่ในการผูกประเภทของหลายส่วนของลายเซ็นด้วยกัน มันกำลังพูดว่า:

ฉันไม่สนใจสิ่งที่Tเป็น แต่ต้องการให้แน่ใจว่าทุกที่ที่ฉันใช้Tมันเป็นชนิดเดียวกัน

ตามหลักเหตุผลแล้วเราคาดว่าพารามิเตอร์แต่ละประเภทจะปรากฏอย่างน้อยสองครั้งในลายเซ็นวิธีการมิฉะนั้น "มันไม่ได้ทำอะไรเลย" FในของคุณwithXปรากฏเพียงครั้งเดียวในลายเซ็นซึ่งแนะนำให้ฉันใช้พารามิเตอร์ประเภทไม่สอดคล้องกับความตั้งใจของคุณสมบัติของภาษานี้

การดำเนินการทางเลือก

วิธีหนึ่งในการนำสิ่งนี้ไปใช้ใน "พฤติกรรมที่ตั้งใจ" อีกเล็กน้อยจะเป็นการแบ่งwithวิธีของคุณออกเป็นสาย 2:

public class Builder<T> {

    public final class With<R> {
        private final Function<T,R> method;

        private With(Function<T,R> method) {
            this.method = method;
        }

        public Builder<T> of(R value) {
            // TODO: Body of your old 'with' method goes here
            return Builder.this;
        }
    }

    public <R> With<R> with(Function<T,R> method) {
        return new With<>(method);
    }

}

สิ่งนี้สามารถใช้ได้ดังนี้:

b.with(MyInterface::getLong).of(1L); // Compiles
b.with(MyInterface::getLong).of("Not a long"); // Compiler error

สิ่งนี้ไม่รวมพารามิเตอร์ประเภทที่ไม่เกี่ยวข้องเหมือนที่คุณwithXทำ โดยการแบ่งวิธีออกเป็นสองลายเซ็นมันเป็นการแสดงออกถึงเจตนาของสิ่งที่คุณพยายามทำดีกว่าจากมุมมองความปลอดภัยประเภท:

  • วิธีแรกตั้งค่าคลาส ( With) ที่กำหนดประเภทตามการอ้างอิงวิธีการ
  • วิธี scond ( of) จำกัดประเภทของที่valueจะเข้ากันได้กับสิ่งที่คุณตั้งไว้ก่อนหน้านี้

วิธีเดียวที่เวอร์ชันในอนาคตของภาษาจะสามารถรวบรวมได้ก็คือถ้ามีการใช้การพิมพ์เป็ดแบบสมบูรณ์ซึ่งดูเหมือนจะไม่น่าเป็นไปได้

ข้อสังเกตสุดท้ายที่ทำให้สิ่งนี้ไม่เกี่ยวข้องทั้งหมด: ฉันคิดว่าMockito (และโดยเฉพาะอย่างยิ่งฟังก์ชั่นการขัดถู ) อาจทำสิ่งที่คุณพยายามจะทำได้ด้วย "เครื่องมือสร้างทั่วไปประเภทปลอดภัย" ของคุณ บางทีคุณอาจใช้สิ่งนั้นแทน

คำอธิบายแบบเต็ม (ish)

ฉันจะทำงานผ่านขั้นตอนการอนุมานชนิดสำหรับทั้งสองและwith withXนี่ค่อนข้างนานดังนั้นค่อยช้าหน่อย แม้จะมีความยาว แต่ฉันก็ยังมีรายละเอียดค่อนข้างมาก คุณอาจต้องการอ้างอิงรายละเอียดเพิ่มเติม (ตามลิงค์) เพื่อโน้มน้าวตัวเองว่าฉันพูดถูก (ฉันอาจทำผิดพลาดไป)

นอกจากนี้เพื่อลดความซับซ้อนของสิ่งเล็กน้อยฉันจะใช้ตัวอย่างโค้ดที่น้อยที่สุด แตกต่างที่สำคัญก็คือว่ามันสลับออกFunctionสำหรับSupplierจึงมีน้อยชนิดและพารามิเตอร์ในการเล่น ต่อไปนี้เป็นตัวอย่างข้อมูลแบบสมบูรณ์ที่จำลองพฤติกรรมของคุณตามที่อธิบายไว้:

public class TypeInference {

    static long getLong() { return 1L; }

    static <R> void with(Supplier<R> supplier, R value) {}
    static <R, F extends Supplier<R>> void withX(F supplier, R value) {}

    public static void main(String[] args) {
        with(TypeInference::getLong, "Not a long");       // Compiles
        withX(TypeInference::getLong, "Also not a long"); // Does not compile
    }

}

มาทำงานกับการอนุมานการบังคับใช้ชนิดและขั้นตอนการอนุมานแบบสำหรับแต่ละวิธีการในการเปิดใช้

with

เรามี:

with(TypeInference::getLong, "Not a long");

ชุดเริ่มต้นที่ถูกผูกไว้B 0คือ:

  • R <: Object

การแสดงออกพารามิเตอร์ทั้งหมดจะเกี่ยวข้องกับการบังคับใช้

ดังนั้นชุด จำกัด เริ่มต้นสำหรับการบังคับใช้การอนุมาน , Cคือ:

  • TypeInference::getLong เข้ากันได้กับ Supplier<R>
  • "Not a long" เข้ากันได้กับ R

นี้จะช่วยลดการผูกพันชุดB 2ของ:

  • R <: Object(จากB 0 )
  • Long <: R (จากข้อ จำกัด แรก)
  • String <: R (จากข้อ จำกัด ที่สอง)

เนื่องจากสิ่งนี้ไม่ได้มีขอบเขตที่ ' เท็จ ' และ (ฉันถือว่า) การแก้ปัญหาของการRประสบความสำเร็จ (ให้Serializable) ดังนั้นจึงสามารถเรียกใช้ได้

ดังนั้นเราย้ายไปภาวนาอนุมานชนิด

ชุดข้อ จำกัด ใหม่Cพร้อมตัวแปรอินพุตและเอาต์พุตที่เกี่ยวข้องคือ:

  • TypeInference::getLong เข้ากันได้กับ Supplier<R>
    • ตัวแปรอินพุต: none
    • ตัวแปรเอาต์พุต: R

นี้มีการสัมพันธ์กันระหว่างไม่มีการป้อนข้อมูลและการส่งออกตัวแปรเพื่อให้สามารถลดลงในขั้นตอนเดียวและชุดที่ถูกผูกไว้สุดท้ายB 4 , เป็นเช่นเดียวกับB 2 ดังนั้นการแก้ไขจึงประสบความสำเร็จเหมือนก่อน

withX

เรามี:

withX(TypeInference::getLong, "Also not a long");

ชุดเริ่มต้นที่ถูกผูกไว้B 0คือ:

  • R <: Object
  • F <: Supplier<R>

เพียง แต่การแสดงออกพารามิเตอร์ที่สองคือการที่เกี่ยวข้องกับการบังคับใช้ อันแรก ( TypeInference::getLong) ไม่ได้เนื่องจากเป็นไปตามเงื่อนไขต่อไปนี้:

หากmเป็นวิธีการทั่วไปและวิธีการอุทธรณ์ไม่ได้ให้การขัดแย้งประเภทอย่างชัดเจน, การแสดงออกแลมบ์ดาพิมพ์ชัดเจนหรือการแสดงออกวิธีการอ้างอิงที่แน่นอนซึ่งประเภทเป้าหมายที่สอดคล้องกัน (ตามที่ได้รับมาจากลายเซ็นของm) mเป็นพารามิเตอร์ชนิดของ

ดังนั้นชุด จำกัด เริ่มต้นสำหรับการบังคับใช้การอนุมาน , Cคือ:

  • "Also not a long" เข้ากันได้กับ R

นี้จะช่วยลดการผูกพันชุดB 2ของ:

  • R <: Object(จากB 0 )
  • F <: Supplier<R>(จากB 0 )
  • String <: R (จากข้อ จำกัด )

อีกครั้งเนื่องจากสิ่งนี้ไม่มีขอบเขตที่ ' เท็จ ' และการแก้ปัญหาของการRประสบความสำเร็จ (การให้String) ดังนั้นจึงสามารถใช้การภาวนาได้

การอนุมานประเภทการเรียกอีกครั้ง ...

เวลานี้ชุดข้อ จำกัด ใหม่Cพร้อมตัวแปรอินพุตและเอาต์พุตที่เกี่ยวข้องคือ:

  • TypeInference::getLong เข้ากันได้กับ F
    • ตัวแปรอินพุต: F
    • ตัวแปรเอาต์พุต: ไม่มี

อีกครั้งเราไม่มีการพึ่งพาระหว่างตัวแปรอินพุตและเอาต์พุต แต่เวลานี้มีเป็นตัวแปร ( F) ดังนั้นเราจึงต้องแก้ปัญหานี้ก่อนที่จะพยายามลด ดังนั้นเราจึงเริ่มต้นด้วยชุดของเราผูกพันB 2

  1. เรากำหนดเซตย่อยVดังนี้:

    เมื่อกำหนดชุดของตัวแปรอนุมานเพื่อแก้ไขให้Vรวมกันของชุดนี้และตัวแปรทั้งหมดที่ความละเอียดของตัวแปรอย่างน้อยหนึ่งตัวในชุดนี้ขึ้นอยู่กับ

    โดยที่สองที่ถูกผูกไว้ในB 2 , มติที่Fขึ้นอยู่กับดังนั้นRV := {F, R}

  2. เราเลือกเซตย่อยVตามกฎ:

    ปล่อยให้{ α1, ..., αn }เป็นเซตย่อยที่ไม่ว่างเปล่าของตัวแปรที่ไม่มีค่าคงที่Vเช่นนั้น i) สำหรับทุกคนi (1 ≤ i ≤ n)หากαiขึ้นอยู่กับความละเอียดของตัวแปรβจากนั้นอาจβมีอินสแตนซ์หรือมีบางjอย่างที่β = αj; และ ii) ไม่มีชุดย่อยที่ไม่ว่างเปล่าของ{ α1, ..., αn }คุณสมบัตินี้

    เซตย่อยเดียวของVคุณสมบัตินี้ที่เป็น{R}ไปตาม

  3. การใช้ขอบเขตที่สาม ( String <: R) เรายกตัวอย่างR = Stringและรวมสิ่งนี้เข้ากับชุดที่ถูกผูกไว้ของเรา ได้รับการแก้ไขในขณะนี้และที่สองที่ถูกผูกไว้อย่างมีประสิทธิภาพจะกลายเป็นRF <: Supplier<String>

  4. การใช้ (แก้ไข) ผูกพันสองเรา F = Supplier<String>instantiate Fได้รับการแก้ไขแล้ว

ตอนนี้Fได้รับการแก้ไขแล้วเราสามารถดำเนินการลดต่อโดยใช้ข้อ จำกัด ใหม่:

  1. TypeInference::getLong เข้ากันได้กับ Supplier<String>
  2. ... ลดการLong เข้ากันได้กับ String
  3. ... ซึ่งจะลดความผิดพลาด

... และเราได้รับข้อผิดพลาดของคอมไพเลอร์!


หมายเหตุเพิ่มเติมเกี่ยวกับ 'ตัวอย่างเพิ่มเติม'

ตัวอย่างขยายในลักษณะคำถามที่น่าสนใจไม่กี่กรณีที่ไม่ได้ครอบคลุมโดยตรงจากผลงานดังกล่าวข้างต้น:

  • โดยที่ประเภทค่าเป็นประเภทย่อยของวิธีการส่งคืนชนิด ( Integer <: Number)
  • โดยที่ส่วนต่อประสานการทำงานนั้นเป็นประเภทที่อนุมาน (เช่นConsumerมากกว่าSupplier)

โดยเฉพาะอย่างยิ่งการเรียกใช้ 3 รายการที่ได้รับนั้นมีความโดดเด่นว่าอาจมีพฤติกรรมของคอมไพเลอร์ที่แตกต่างกันไปตามที่อธิบายไว้ในคำอธิบาย:

t.lettBe(t::setNumber, "NaN"); // Does not compile :-)

t.letBeX(t::getNumber, 2); // !!! Does not compile  :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)

วินาทีที่สองของทั้งสามจะผ่านกระบวนการอนุมานแบบเดียวกับที่กล่าวwithXไว้ข้างต้น (แทนที่Longด้วยNumberและStringด้วยInteger) สิ่งนี้แสดงให้เห็นอีกเหตุผลหนึ่งว่าทำไมคุณไม่ควรพึ่งพาพฤติกรรมการอนุมานแบบล้มเหลวนี้สำหรับการออกแบบชั้นเรียนของคุณเนื่องจากความล้มเหลวในการคอมไพล์ที่นี่อาจเป็นพฤติกรรมที่ไม่พึงประสงค์

สำหรับอีก 2 (และแน่นอนการเรียกร้องอื่น ๆ ที่เกี่ยวข้องกับConsumerคุณต้องการทำงาน) พฤติกรรมที่ควรจะชัดเจนถ้าคุณทำงานผ่านขั้นตอนการอนุมานประเภทที่วางไว้สำหรับหนึ่งในวิธีการดังกล่าวข้างต้น (เช่นwithแรกwithXสำหรับ สาม) มีการเปลี่ยนแปลงเพียงเล็กน้อยเพียงอย่างเดียวที่คุณต้องทราบ:

  • ข้อ จำกัด ในพารามิเตอร์แรก ( t::setNumber เข้ากันได้กับ Consumer<R> ) จะลดการR <: Numberแทนมันไม่สำหรับNumber <: R Supplier<R>นี่คือคำอธิบายในเอกสารที่เชื่อมโยงเกี่ยวกับการลด

ฉันปล่อยให้มันเป็นเครื่องออกกำลังกายสำหรับผู้อ่านที่จะทำงานอย่างระมัดระวังผ่านหนึ่งในขั้นตอนข้างต้นติดอาวุธด้วยความรู้เพิ่มเติมชิ้นนี้เพื่อแสดงให้เห็นถึงตัวเองว่าทำไมการร้องขอเฉพาะนั้นทำหรือไม่รวบรวม


ในเชิงลึกมากการวิจัยที่ดีและสูตร ขอบคุณ!
Zabuzard

@ user31601 คุณช่วยชี้ให้เห็นถึงความแตกต่างของผู้จัดหาต่อผู้บริโภคที่เข้ามาเล่น ฉันเพิ่มตัวอย่างเพิ่มเติมในคำถามเดิมสำหรับสิ่งนั้น มันแสดงให้เห็นถึงพฤติกรรมแปรปรวน, แปรปรวนและไม่แปรเปลี่ยนสำหรับรุ่นที่แตกต่างกันของ letBe (), letBeX () และ let (). เป็น () ขึ้นอยู่กับซัพพลายเออร์ / ผู้บริโภค
jukzi

@jukzi ฉันได้เพิ่มบันทึกย่อไปเล็กน้อย แต่คุณควรมีข้อมูลเพียงพอที่จะทำงานผ่านตัวอย่างใหม่เหล่านี้ด้วยตัวคุณเอง
user31601

นั่นคือการแทรกแซง: กรณีพิเศษมากมายใน 18.2.1 สำหรับ lambdas และการอ้างอิงเมธอดซึ่งฉันไม่ได้คาดหวังว่าจะมีกรณีพิเศษสำหรับพวกเขาเลยจากความเข้าใจที่ไร้เดียงสาของฉัน และอาจไม่มีผู้พัฒนาสามัญคาดหวัง
jukzi

ฉันคิดว่าเหตุผลก็คือว่าด้วยแลมบ์ดาและการอ้างอิงเมธอดคอมไพเลอร์จำเป็นต้องตัดสินใจว่าแลมบ์ดาควรจะใช้ประเภทใด - มันต้องเลือก! ตัวอย่างเช่นTypeInference::getLongสามารถเลียนแบบSupplier<Long>หรือSupplier<Serializable>หรือSupplier<Number>อื่น ๆ แต่มันสามารถนำไปใช้อย่างใดอย่างหนึ่งเท่านั้น (เหมือนคลาสอื่น ๆ )! สิ่งนี้แตกต่างจากนิพจน์อื่น ๆ ทั้งหมดซึ่งชนิดที่นำไปใช้นั้นเป็นที่รู้จักทั้งหมดและคอมไพเลอร์ต้องทำงานออกมาว่าหนึ่งในนั้นตรงตามข้อกำหนดของข้อ จำกัด หรือไม่
user31601
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.