:: (เครื่องหมายทวิภาคคู่) ใน Java 8


956

ฉันสำรวจแหล่งJava 8และพบว่าส่วนหนึ่งของรหัสนี้น่าประหลาดใจมาก:

//defined in IntPipeline.java
@Override
public final OptionalInt reduce(IntBinaryOperator op) {
    return evaluate(ReduceOps.makeInt(op));
}

@Override
public final OptionalInt max() {
    return reduce(Math::max); //this is the gotcha line
}

//defined in Math.java
public static int max(int a, int b) {
    return (a >= b) ? a : b;
}

เป็นMath::maxเหมือนตัวชี้วิธีการหรือไม่ วิธีการปกติstaticได้รับการแปลงเป็นIntBinaryOperatorอย่างไร


60
เป็นน้ำตาลทรายที่ให้คอมไพเลอร์สร้างอินเทอร์เฟซอัตโนมัติตามการใช้งานของคุณ
Neet

4
java.dzone.com/articles/java-lambda-expressions-vsอาจช่วยไม่ได้มองลึกลงไปในหัวข้อ
Pontus Backlund

8
@ เห็นว่ามันไม่ได้เป็น "น้ำตาล syntactic" เว้นแต่คุณจะพูดในสิ่งที่ นั่นคือ "x คือน้ำตาลประโยคสำหรับ y"
Ingo

6
@Ingo มันสร้างวัตถุใหม่ของแลมบ์ดาทุกครั้งที่ใช้งาน TestingLambda$$Lambda$2/8460669และTestingLambda$$Lambda$3/11043253ถูกสร้างขึ้นในสองคำร้อง
Narendra Pathai

13
การอ้างอิงแลมบ์ดาและเมธอดไม่ใช่ "คลาสภายในแบบไม่ระบุชื่อแบบเก่าธรรมดา" ดูprogrammers.stackexchange.com/a/181743/59134 ใช่ถ้าจำเป็นคลาสและอินสแตนซ์ใหม่จะถูกสร้างขึ้นทันทีหากจำเป็น แต่เฉพาะในกรณีที่จำเป็นเท่านั้น
Stuart Marks

คำตอบ:


1022

โดยปกติแล้วจะเรียกreduceใช้วิธีการMath.max(int, int)ดังต่อไปนี้:

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});

Math.maxที่ต้องการมากของไวยากรณ์เพียงโทร นั่นคือสิ่งที่แลมบ์ดาแสดงออกมา ตั้งแต่ Java 8 จะได้รับอนุญาตให้ทำสิ่งเดียวกันในวิธีที่สั้นกว่ามาก:

reduce((int left, int right) -> Math.max(left, right));

มันทำงานอย่างไร จาวาคอมไพเลอร์ "ตรวจจับ" ที่คุณต้องการที่จะใช้วิธีการที่ยอมรับทั้งสองและผลตอบแทนที่หนึ่งint intนี่เทียบเท่ากับพารามิเตอร์ที่เป็นทางการของวิธีการอินเทอร์เฟซเดียวเท่านั้นIntBinaryOperator(พารามิเตอร์ของวิธีที่reduceคุณต้องการโทรหา) ดังนั้นคอมไพเลอร์ไม่เหลือสำหรับคุณ - IntBinaryOperatorมันก็ถือว่าคุณต้องการที่จะใช้

แต่เมื่อMath.max(int, int)ตัวเองทำตามข้อกำหนดอย่างเป็นทางการของIntBinaryOperatorมันสามารถนำมาใช้โดยตรง เนื่องจาก Java 7 ไม่มีไวยากรณ์ใด ๆ ที่อนุญาตให้ส่งเมธอดเองเป็นอาร์กิวเมนต์ (คุณสามารถส่งผลลัพธ์ของเมธอดเท่านั้น แต่ไม่เคยอ้างถึงเมธอด) จึงมีการนำ::ไวยากรณ์ใน Java 8 ไปใช้กับวิธีการอ้างอิง:

reduce(Math::max);

ทราบว่าสิ่งนี้จะถูกตีความโดยคอมไพเลอร์ไม่ใช่โดย JVM ที่รันไทม์! ถึงแม้ว่ามันจะสร้างรหัสไบต์ที่แตกต่างกันสำหรับตัวอย่างโค้ดทั้งสามตัว แต่มันมีความหมายทางความหมายเท่ากันดังนั้นสองอันสุดท้ายจึงถือว่าสั้น (และอาจมีประสิทธิภาพมากกว่า) ของการIntBinaryOperatorติดตั้งข้างต้น!

(ดูคำแปลของแลมบ์ดานิพจน์ )


489

::เรียกว่าวิธีการอ้างอิง มันเป็นพื้นอ้างอิงถึงวิธีการเดียว คือมันหมายถึงวิธีการที่มีอยู่ตามชื่อ

คำอธิบายสั้น ๆ :
ด้านล่างเป็นตัวอย่างของการอ้างอิงถึงวิธีการแบบคงที่:

class Hey {
     public static double square(double num){
        return Math.pow(num, 2);
    }
}

Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);

squareสามารถส่งผ่านไปรอบ ๆ เช่นเดียวกับการอ้างอิงวัตถุและเรียกเมื่อจำเป็น ในความเป็นจริงมันสามารถนำมาใช้อย่างง่ายดายเช่นเดียวกับการอ้างอิงถึงวิธีการ "ปกติ" ของวัตถุเป็นstaticคน ตัวอย่างเช่น:

class Hey {
    public double square(double num) {
        return Math.pow(num, 2);
    }
}

Hey hey = new Hey();
Function<Double, Double> square = hey::square;
double ans = square.apply(23d);

Functionด้านบนเป็นอินเตอร์เฟซที่ใช้งานได้ เพื่อทำความเข้าใจอย่างสมบูรณ์::เป็นสิ่งสำคัญที่จะเข้าใจอินเตอร์เฟสการทำงานเช่นกัน ชัดถ้อยชัดคำอินเทอร์เฟซการใช้งานเป็นอินเทอร์เฟซด้วยวิธีนามธรรมเพียงอย่างเดียว

ตัวอย่างของการเชื่อมต่อการทำงานรวมRunnable, และCallableActionListener

Functionapplyข้างต้นเป็นอินเตอร์เฟซที่ใช้งานได้มีเพียงวิธีการหนึ่ง: ใช้เวลาหนึ่งอาร์กิวเมนต์และสร้างผลลัพธ์


เหตุผลที่ว่าทำไม::s ที่น่ากลัวคือว่า :

การอ้างอิงเมธอดคือนิพจน์ที่มีการรักษาเช่นเดียวกับแลมบ์ดานิพจน์ (... ) แต่แทนที่จะให้เนื้อความของเมธอดพวกเขาอ้างถึงเมธอดที่มีอยู่ตามชื่อ

เช่นแทนที่จะเขียนตัวแลมบ์ดา

Function<Double, Double> square = (Double x) -> x * x;

คุณสามารถทำได้

Function<Double, Double> square = Hey::square;

ที่รันไทม์squareวิธีการทั้งสองนี้ทำงานเหมือนกัน รหัสไบต์อาจจะเหมือนกันหรือไม่ก็ได้ (แม้ว่าในกรณีข้างต้นรหัสเดียวกันนี้จะถูกสร้างขึ้นให้รวบรวมข้างต้นและตรวจสอบกับjavap -c )

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

ด้านล่างเป็นสิ่งผิดกฎหมาย:

Supplier<Boolean> p = Hey::square; // illegal

squaredoubleคาดว่าจะมีข้อโต้แย้งและผลตอบแทน getวิธีการในการจัดจำหน่ายส่งกลับค่า แต่ไม่ได้ใช้อาร์กิวเมนต์ ดังนั้นผลลัพธ์นี้มีข้อผิดพลาด

การอ้างอิงวิธีการอ้างอิงถึงวิธีการของส่วนต่อประสานการทำงาน (ตามที่กล่าวไว้อินเทอร์เฟซที่ใช้งานได้มีเพียงวิธีเดียวเท่านั้น)

ตัวอย่างเพิ่มเติม: acceptเมธอดในConsumerจะรับอินพุต แต่จะไม่ส่งคืนสิ่งใด

Consumer<Integer> b1 = System::exit;   // void exit(int status)
Consumer<String[]> b2 = Arrays::sort;  // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)

class Hey {
    public double getRandom() {
        return Math.random();
    }
}

Callable<Double> call = hey::getRandom;
Supplier<Double> call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier is functional interface that takes no argument and gives a result

ข้างต้นจะไม่โต้แย้งและผลตอบแทนgetRandom doubleดังนั้นอินเตอร์เฟสการทำงานใด ๆ ที่เป็นไปตามเกณฑ์ของ: อย่าโต้แย้งและย้อนกลับdoubleสามารถนำมาใช้ได้

ตัวอย่างอื่น:

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");

ในกรณีของประเภทพารามิเตอร์ :

class Param<T> {
    T elem;
    public T get() {
        return elem;
    }

    public void set(T elem) {
        this.elem = elem;
    }

    public static <E> E returnSame(E elem) {
        return elem;
    }
}

Supplier<Param<Integer>> obj = Param<Integer>::new;
Param<Integer> param = obj.get();
Consumer<Integer> c = param::set;
Supplier<Integer> s = param::get;

Function<String, String> func = Param::<String>returnSame;

การอ้างอิงเมธอดสามารถมีสไตล์ที่แตกต่างกันได้ แต่โดยพื้นฐานแล้วพวกมันทั้งหมดมีความหมายเหมือนกัน

  1. วิธีคงที่ ( ClassName::methName)
  2. วิธีการอินสแตนซ์ของวัตถุเฉพาะ ( instanceRef::methName)
  3. วิธีการพิเศษของวัตถุเฉพาะ ( super::methName)
  4. วิธีการตัวอย่างของวัตถุโดยพลการของประเภทเฉพาะ ( ClassName::methName)
  5. การอ้างอิงตัวสร้างคลาส ( ClassName::new)
  6. การอ้างอิงตัวสร้างอาร์เรย์ ( TypeName[]::new)

สำหรับการอ้างอิงต่อไปดูhttp://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html


6
ขอบคุณสำหรับคำอธิบาย โดยสรุป: '::' ใช้เพื่อแยกเมธอดที่เป็นไปตาม FunctionalInterface (แลมบ์ดา): ClassX :: staticMethodX หรือ instanceX :: instanceMethodX "
jessarah

55

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

เอกสารอย่างเป็นทางการจาก Oracle สามารถพบได้ที่นี่

คุณสามารถมีภาพรวมที่ดีขึ้นของ JDK 8 การเปลี่ยนแปลงในนี้บทความ ในส่วนการอ้างอิงเมธอด / คอนสตรัคเตอร์จะมีตัวอย่างโค้ดให้ด้วย:

interface ConstructorReference {
    T constructor();
}

interface  MethodReference {
   void anotherMethod(String input);
}

public class ConstructorClass {
    String value;

   public ConstructorClass() {
       value = "default";
   }

   public static void method(String input) {
      System.out.println(input);
   }

   public void nextMethod(String input) {
       // operations
   }

   public static void main(String... args) {
       // constructor reference
       ConstructorReference reference = ConstructorClass::new;
       ConstructorClass cc = reference.constructor();

       // static method reference
       MethodReference mr = cc::method;

       // object method reference
       MethodReference mr2 = cc::nextMethod;

       System.out.println(cc.value);
   }
}

คำอธิบายที่ดีคือคำที่พบได้ที่นี่: doanduyhai.wordpress.com/2012/07/14/…
Olimpiu POP

1
@RichardTingle คือการภาวนาและความหมายของวิธีการจะเป็นเช่นmethod(Math::max); public static void method(IntBinaryOperator op){System.out.println(op.applyAsInt(1, 2));}นั่นคือวิธีการใช้งาน
Narendra Pathai

2
สำหรับผู้ที่คุ้นเคยกับ C # จะคล้ายกับ DelegateType d = new DelegateType (MethodName);
Adrian Zanescu

27

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

พิจารณาชั้นเรียนต่อไปนี้โดยที่พนักงานแต่ละคนมีชื่อและเกรด

public class Employee {
    private String name;
    private String grade;

    public Employee(String name, String grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }
}

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

    List<Employee> employeeList = getDummyEmployees();

    // Using anonymous class
    employeeList.sort(new Comparator<Employee>() {
           @Override
           public int compare(Employee e1, Employee e2) {
               return e1.getGrade().compareTo(e2.getGrade());
           }
    });

โดยที่ getDummyEmployee () เป็นวิธีการบางอย่าง:

private static List<Employee> getDummyEmployees() {
        return Arrays.asList(new Employee("Carrie", "C"),
                new Employee("Fanishwar", "F"),
                new Employee("Brian", "B"),
                new Employee("Donald", "D"),
                new Employee("Adam", "A"),
                new Employee("Evan", "E")
                );
    }

ตอนนี้เรารู้แล้วว่าComparatorเป็น Interface Interface ฟังก์ชั่นการเชื่อมต่อเป็นหนึ่งเดียวกับวิธีนามธรรมว่าหนึ่ง (แม้ว่ามันอาจจะมีหนึ่งหรือมากกว่าหนึ่งวิธีการเริ่มต้นหรือคงที่) การแสดงออกของแลมบ์ดาให้การดำเนินการ@FunctionalInterfaceเพื่อให้อินเตอร์เฟซการทำงานสามารถมีวิธีนามธรรมเพียงอย่างเดียว เราสามารถใช้แลมบ์ดานิพจน์เป็น:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp

ดูเหมือนว่าทั้งหมดจะดี แต่ถ้าชั้นเรียนEmployeeมีวิธีการที่คล้ายกัน:

public class Employee {
    private String name;
    private String grade;
    // getter and setter
    public static int compareByGrade(Employee e1, Employee e2) {
        return e1.grade.compareTo(e2.grade);
    }
}

ในกรณีนี้การใช้ชื่อเมธอดเองจะมีความชัดเจนมากขึ้น ดังนั้นเราสามารถอ้างถึงวิธีการโดยตรงโดยใช้วิธีการอ้างอิงเป็น:

employeeList.sort(Employee::compareByGrade); // method reference

ตามเอกสารมีวิธีการอ้างอิงสี่ประเภท:

+----+-------------------------------------------------------+--------------------------------------+
|    | Kind                                                  | Example                              |
+----+-------------------------------------------------------+--------------------------------------+
| 1  | Reference to a static method                          | ContainingClass::staticMethodName    |
+----+-------------------------------------------------------+--------------------------------------+
| 2  |Reference to an instance method of a particular object | containingObject::instanceMethodName | 
+----+-------------------------------------------------------+--------------------------------------+
| 3  | Reference to an instance method of an arbitrary object| ContainingType::methodName           |
|    | of a particular type                                  |                                      |  
+----+-------------------------------------------------------+--------------------------------------+
| 4  |Reference to a constructor                             | ClassName::new                       |
+------------------------------------------------------------+--------------------------------------+

25

::เป็นตัวดำเนินการใหม่ที่รวมอยู่ใน Java 8 ที่ใช้ในการอ้างอิงวิธีการของคลาสที่มีอยู่ คุณสามารถอ้างถึงวิธีการคงที่และวิธีการไม่คงที่ของชั้นเรียน

สำหรับการอ้างอิงวิธีการคงที่ไวยากรณ์คือ:

ClassName :: methodName 

สำหรับการอ้างอิงวิธีการไม่คงที่, ไวยากรณ์คือ

objRef :: methodName

และ

ClassName :: methodName

สิ่งที่จำเป็นต้องมีสำหรับการอ้างอิงเมธอดคือเมธอดนั้นมีอยู่ในอินเตอร์เฟสที่ใช้งานได้ซึ่งต้องเข้ากันได้กับการอ้างอิงเมธอด

การอ้างอิงเมธอดเมื่อประเมินผลให้สร้างอินสแตนซ์ของส่วนต่อประสานการทำงาน

พบได้ที่: http://www.speakingcs.com/2014/08/method-references-in-java-8.html


22

นี่คือการอ้างอิงวิธีการในชวา 8. เอกสารของ Oracle เป็นที่นี่

ตามที่ระบุไว้ในเอกสาร ...

การอ้างอิงเมธอด Person :: comparByAge เป็นการอ้างอิงถึงเมธอดสแตติก

ต่อไปนี้เป็นตัวอย่างของการอ้างอิงถึงวิธีการอินสแตนซ์ของวัตถุเฉพาะ:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }

    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}

ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName); 

การอ้างอิงเมธอด myComparisonProvider :: comparByName เรียกใช้เมธอด comparByName ที่เป็นส่วนหนึ่งของออบเจ็กต์ myComparisonProvider JRE อ้างถึงอาร์กิวเมนต์ชนิดวิธีซึ่งในกรณีนี้คือ (Person, Person)


2
แต่วิธีการ 'comparByAge' ไม่คงที่
abbas

3
@abbas Nor คือการเปรียบเทียบ ByName ดังนั้นคุณเข้าถึงวิธีการไม่คงที่เหล่านี้ผ่านผู้ประกอบการอ้างอิงโดยใช้วัตถุ หากพวกเขาคงที่คุณสามารถใช้ชื่อชั้นเช่น ComparisionProvider :: someStaticMethod
Seshadri R

6

:: ผู้ประกอบการได้รับการแนะนำใน java 8 สำหรับการอ้างอิงวิธีการ การอ้างอิงเมธอดคือไวยากรณ์ชวเลขสำหรับการแสดงออกแลมบ์ดาที่ดำเนินการเพียงหนึ่งวิธี นี่คือไวยากรณ์ทั่วไปของการอ้างอิงวิธีการ:

Object :: methodName

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

Consumer<String> c = s -> System.out.println(s);

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

Consumer<String> c = System.out::println;

3

การ :: เป็นที่รู้จักกันเป็นวิธีการอ้างอิง ให้บอกว่าเราต้องการเรียกวิธีการคำนวณราคาของคลาสที่ซื้อ จากนั้นเราสามารถเขียนมันเป็น:

Purchase::calculatePrice

นอกจากนี้ยังสามารถมองเห็นเป็นรูปแบบสั้น ๆ ของการเขียนการแสดงออกแลมบ์ดาเพราะการอ้างอิงวิธีการจะถูกแปลงเป็นการแสดงออกแลมบ์ดา


ฉันสามารถอ้างอิงวิธีซ้อนกันได้หรือไม่ เช่น groupingBy (คำสั่งซื้อ :: ลูกค้า :: ชื่อ)

คุณไม่สามารถทำการอ้างอิงเมธอดแบบซ้อนได้ในนั้น
Kirby

3

ฉันพบแหล่งนี้น่าสนใจมาก

ในความเป็นจริงแลมบ์ดาที่กลายเป็นอาณานิคมคู่ Colonลำไส้ใหญ่คู่สามารถอ่านได้มากขึ้น เราทำตามขั้นตอนเหล่านี้:

ขั้นตอนที่ 1:

// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

ขั้นตอนที่ 2:

// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

ขั้นตอนที่ 3:

// The magic using method reference
Comparator c = Comparator.comparing(Person::getAge);

3
ดูเหมือนว่าควรจะเป็นPerson::getAge() Person::getAge
Qwertiy

2

return reduce(Math::max);คือไม่เท่ากันไปreturn reduce(max());

แต่มันหมายถึงบางอย่างเช่นนี้:

IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);

คุณสามารถบันทึกการกดแป้นได้ 47 ครั้งถ้าคุณเขียนแบบนี้

return reduce(Math::max);//Only 9 keystrokes ^_^

2

เนื่องจากคำตอบมากมายที่นี่อธิบายได้ดี ::พฤติกรรมนอกจากนี้ผมอยากจะชี้แจงว่าจำเป็นที่จะต้องดำเนินการไม่ให้มีลายเซ็นเดียวกันตรงตามที่อินเตอร์เฟซการทำงานหมายถึงหากมีการใช้สำหรับตัวแปรเช่น:: ให้ถือว่าเราต้องBinaryOperatorซึ่งมีประเภทของTestObject ในแบบดั้งเดิมมันใช้งานเช่นนี้:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {

        @Override
        public TestObject apply(TestObject t, TestObject u) {

            return t;
        }
    };

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

public class TestObject {


    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

แล้วโทร:

BinaryOperator<TestObject> binary = TestObject::testStatic;

ตกลงรวบรวมได้ดี ถ้าเราต้องการวิธีการอินสแตนซ์ล่ะ? ให้อัปเดต TestObject ด้วยวิธีการแบบอินสแตนซ์:

public class TestObject {

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

ตอนนี้เราสามารถเข้าถึงอินสแตนซ์ดังต่อไปนี้:

TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;

รหัสนี้คอมไพล์ได้ แต่ไม่ต่ำกว่า:

BinaryOperator<TestObject> binary = TestObject::testInstance;

อุปราคาของฉันบอกฉัน "ไม่สามารถทำการอ้างอิงแบบคงที่ไปยังวิธีการแบบไม่คงที่ testInstance (TestObject, TestObject) จากประเภท TestObject ... "

ยุติธรรมเพียงพอเป็นวิธีการอินสแตนซ์ แต่ถ้าเราโอเวอร์โหลดtestInstanceดังนี้:

public class TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

และโทร:

BinaryOperator<TestObject> binary = TestObject::testInstance;

รหัสจะคอมไพล์ได้ดี เพราะมันจะเรียกtestInstanceพร้อมกับพารามิเตอร์เดียวแทนสองครั้ง ตกลงแล้วเกิดอะไรขึ้นกับพารามิเตอร์ทั้งสองของเรา ให้พิมพ์ออกมาและดู:

public class TestObject {

    public TestObject() {
        System.out.println(this.hashCode());
    }

    public final TestObject testInstance(TestObject t){
        System.out.println("Test instance called. this.hashCode:" 
    + this.hashCode());
        System.out.println("Given parameter hashCode:" + t.hashCode());
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

ซึ่งจะส่งออก:

 1418481495  
 303563356  
 Test instance called. this.hashCode:1418481495
 Given parameter hashCode:303563356

ตกลงดังนั้น JVM ฉลาดพอที่จะเรียก param1.testInstance (param2) เราสามารถใช้testInstanceจากแหล่งข้อมูลอื่น แต่ไม่ใช่ TestObject เช่น:

public class TestUtil {

    public final TestObject testInstance(TestObject t){
        return t;
    }
}

และโทร:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

มันก็จะไม่ได้รวบรวมและเรียบเรียงจะบอก: "ประเภท TestUtil ไม่ได้กำหนด testInstance (TestObject, TestObject)" คอมไพเลอร์จะมองหาการอ้างอิงแบบคงที่หากไม่ใช่ประเภทเดียวกัน ตกลงสิ่งที่เกี่ยวกับความหลากหลาย? หากเราลบตัวดัดแปลงสุดท้ายและเพิ่มคลาสSubTestObjectของเรา:

public class SubTestObject extends TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

}

และโทร:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

มันจะไม่รวบรวมเช่นกันคอมไพเลอร์จะยังคงมองหาการอ้างอิงแบบคงที่ แต่รหัสด้านล่างจะรวบรวมได้ดีเพราะผ่านการทดสอบคือ:

public class TestObject {

    public SubTestObject testInstance(Object t){
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* ฉันเพิ่งเรียนเพื่อที่ฉันจะได้คิดออกโดยลองและดูรู้สึกฟรีเพื่อแก้ไขฉันถ้าฉันผิด


2

ใน java-8 Streams Reducer ในงานง่าย ๆ คือฟังก์ชั่นที่รับค่าสองค่าเป็นอินพุตและส่งคืนผลลัพธ์หลังจากการคำนวณบางอย่าง ผลลัพธ์นี้ถูกป้อนในการทำซ้ำครั้งถัดไป

ในกรณีของ Math: ฟังก์ชั่น max วิธีการจะส่งกลับค่าสูงสุดสองค่าที่ส่งผ่านและในท้ายที่สุดคุณมีจำนวนมากที่สุดในมือ


1

ที่รันไทม์พวกเขาทำงานเหมือนกัน bytecode อาจ / ไม่เหมือนกัน (สำหรับกรณีข้างต้นก็จะสร้าง bytecode เดียวกัน (ตามมาตรฐานข้างต้นและตรวจสอบ javaap -c;))

ที่รันไทม์พวกเขาทำงานเหมือนเดิมวิธีการ (คณิตศาสตร์ :: สูงสุด); มันสร้างคณิตศาสตร์เดียวกัน (ตามมาตรฐานข้างต้นและตรวจสอบ javap -c;))


1

ใน Java เวอร์ชันเก่าแทน "::" หรือ lambd คุณสามารถใช้:

public interface Action {
    void execute();
}

public class ActionImpl implements Action {

    @Override
    public void execute() {
        System.out.println("execute with ActionImpl");
    }

}

public static void main(String[] args) {
    Action action = new Action() {
        @Override
        public void execute() {
            System.out.println("execute with anonymous class");
        }
    };
    action.execute();

    //or

    Action actionImpl = new ActionImpl();
    actionImpl.execute();
}

หรือผ่านไปยังวิธีการ:

public static void doSomething(Action action) {
    action.execute();
}

1

ดังนั้นฉันจึงเห็นคำตอบมากมายตรงนี้ที่ตรงไปตรงมามาก

คำตอบนั้นง่ายมาก: :: มันเรียกว่าวิธีการอ้างอิง https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

ดังนั้นฉันจะไม่คัดลอกวางบนลิงค์คุณสามารถค้นหาข้อมูลทั้งหมดหากคุณเลื่อนลงไปที่ตาราง


ทีนี้มาดูกันสั้น ๆ ว่าการอ้างอิงเมธอดคืออะไร:

A :: B ค่อนข้างทดแทนการแสดงออกแลมบ์ดาแบบอินไลน์ต่อไปนี้: (params ... ) -> AB (params ... )

หากต้องการเชื่อมโยงสิ่งนี้กับคำถามของคุณคุณจำเป็นต้องเข้าใจการแสดงออกของแลมบ์ดา java ซึ่งไม่ยาก

การแสดงออกแลมบ์ดาอินไลน์จะคล้ายกับการกำหนด อินเตอร์เฟซที่ใช้งานได้ (ซึ่งเป็นอินเตอร์เฟซที่มีไม่มากและไม่น้อยกว่า 1 วิธี) ลองมาดูสิ่งที่ฉันหมายถึง:

InterfaceX f = (x) -> x*x; 

InterfaceX จะต้องเป็นส่วนต่อประสานการทำงาน อินเตอร์เฟสการทำงานใด ๆ สิ่งเดียวที่สำคัญเกี่ยวกับ InterfaceX สำหรับคอมไพเลอร์นั้นคือคุณกำหนดรูปแบบ:

InterfaceX สามารถเป็นแบบนี้:

interface InterfaceX
{
    public Integer callMe(Integer x);
}

หรือสิ่งนี้

interface InterfaceX
{
    public Double callMe(Integer x);
}

หรือมากกว่าทั่วไป:

interface InterfaceX<T,U>
{
    public T callMe(U x);
}

ลองดูกรณีที่นำเสนอครั้งแรกและการแสดงออกแลมบ์ดาแบบอินไลน์ที่เรากำหนดไว้ก่อนหน้านี้

ก่อนหน้า Java 8 คุณสามารถกำหนดได้ในลักษณะเดียวกันนี้:

 InterfaceX o = new InterfaceX(){
                     public int callMe (int x, int y) 
                       {
                        return x*x;
                       } };

ในทางปฏิบัติมันเป็นสิ่งเดียวกัน ความแตกต่างมีมากขึ้นในวิธีที่คอมไพเลอร์รับรู้สิ่งนี้

ตอนนี้เรามาดูการแสดงออกแลมบ์ดาแบบอินไลน์แล้วกลับไปที่การอ้างอิงวิธีการ (: :) สมมติว่าคุณมีชั้นเรียนเช่นนี้:

class Q {
        public static int anyFunction(int x)
             {
                 return x+5;
             } 
        }

เนื่องจาก method anyFunctionsมีประเภทเดียวกันกับ InterfaceX callMeเราจึงสามารถเทียบได้ทั้งสองแบบด้วยการอ้างอิงวิธีการ

เราสามารถเขียนแบบนี้:

InterfaceX o =  Q::anyFunction; 

และนั่นก็เท่ากับ:

InterfaceX o = (x) -> Q.anyFunction(x);

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


1

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

ด้านล่างนี้เป็นสองตัวอย่างในการค้นหาวัตถุที่มีค่าสูงสุดในArrayListWITH และโดยไม่ใช้::การอ้างอิงวิธีการ คำอธิบายอยู่ในความคิดเห็นด้านล่าง


โดยไม่ต้องใช้ ::

import java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

class ByVal implements Comparator<MyClass> {
    // no need to create this class when using method reference
    public int compare(MyClass source, MyClass ref) {
        return source.getVal() - ref.getVal();
    }
}

public class FindMaxInCol {
    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, new ByVal());
    }
}

ด้วยการใช้ ::

import java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

public class FindMaxInCol {
    static int compareMyClass(MyClass source, MyClass ref) {
        // This static method is compatible with the compare() method defined by Comparator. 
        // So there's no need to explicitly implement and create an instance of Comparator like the first example.
        return source.getVal() - ref.getVal();
    }

    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, FindMaxInCol::compareMyClass);
    }
}

-1

ดับเบิลลำไส้ใหญ่เช่น::ผู้ประกอบการที่มีการแนะนำในชวา 8 เป็นอ้างอิงวิธี การอ้างอิงเมธอดเป็นรูปแบบของการแสดงออกแลมบ์ดาซึ่งใช้เพื่ออ้างอิงวิธีการที่มีอยู่ด้วยชื่อ

ClassName :: methodName

เช่น: -

  • stream.forEach(element -> System.out.println(element))

โดยการใช้ Double Colon ::

  • stream.forEach(System.out::println(element))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.