::(双冒号)运算符在Java 8中
我正在研究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
?
通常,可以使用Math.max(int, int)
调用reduce
方法Math.max(int, int)
如下所示:
reduce(new IntBinaryOperator() { int applyAsInt(int left, int right) { return Math.max(left, right); } });
这只需要调用Math.max
就需要很多的语法。 这就是lambdaexpression式的作用。 从Java 8开始,它允许以更短的方式执行相同的操作:
reduce( (int left, int right) -> Math.max(left, right) );
这个怎么用? java编译器“检测”,你想实现一个接受两个int
并返回一个int
。 这相当于接口IntBinaryOperator
唯一方法的forms参数(要调用的方法的参数reduce
)。 所以编译器为你做了其余的事情 – 它只是假设你想实现IntBinaryOperator
。
但是由于Math.max(int, int)
本身满足IntBinaryOperator
的forms化需求,因此可以直接使用。 由于Java 7没有任何允许方法本身作为parameter passing的语法(只能传递方法结果,而不能传入方法引用),所以在Java 8中引入了::
语法来引用方法:
reduce(Math::max);
请注意,这将由编译器解释,而不是在运行时由JVM解释! 虽然它为所有三个代码片段生成不同的字节码,但它们在语义上是相同的,所以最后两个可以被认为是上面的IntBinaryOperator
实现的短版本(可能更有效)。
(另请参阅Lambdaexpression式的翻译 )
::
被称为方法参考。 这基本上是对单一方法的参考。 即它指的是一个现有的名字的方法。
简短说明 :下面是一个对静态方法的引用的例子:
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
是一个function界面 。 那么要完全解释::
,了解function接口是很重要的。 显然, 函数接口只是一个抽象方法的接口。
例如: Runnable
, Callable
, ActionListener
等等。
上面的function是一个function界面,只有一个方法apply
。 它需要一个参数并产生一个结果。
为什么::
真棒是因为:
方法引用是与lambda具有相同处理的expression式,但不是提供lambda体,而是通过名称引用现有的方法
即就像写lambda身体:
Function<Double, Double> square = (Double x) -> x * x;
你可以简单地做:
Function<Double, Double> square = Hey::square;
在运行时,它们的行为完全一样。 字节码可能不一样(对于上面的情况,它会生成相同的字节码(在上面编译并检查javap -c
))
要满足的唯一主要标准是: 您提供的方法应该与您用作对象引用的函数接口的方法具有类似的签名 。
以下是非法的:
Supplier<Boolean> p = Hey::square; //illegal
square
期待一个论点,并返回一个双。 Supplier中的 get
方法需要一个参数,但不返回任何内容。 所以这是一个错误。
方法引用是指函数接口的一个方法 (如前所述,函数接口只能有一个方法)。
一些更多的例子:在消费者 accept
方法accept
input,但不返回任何东西。
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");
在参数化types的情况下 :
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;
方法参考可以获得不同的风格,但基本上它们都是相同的,可以简单地被视为一个lambda:
- 一个静态方法(
ClassName::methName
) - 一个特定对象的实例方法(
instanceRef::methName
) - 特定对象的超级方法(
super::methName
) - 一个特定types的任意对象的实例方法(
ClassName::methName
) - 一个类的构造函数引用(
ClassName::new
) - 数组构造函数的引用(
TypeName[]::new
)
如需进一步参考: http : //cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
是的,这是真的。 它用于方法引用的::
运算符。 所以,我们可以从类中提取静态方法,或者从对象中提取方法。 即使是构造函数也可以使用相同的运算符。 在这里提到的所有情况都在下面的代码示例中举例说明。
Oracle的官方文档可以在这里find。
您可以更好地了解本文中的JDK 8更改。 在Method / Constructor引用部分中还提供了一个代码示例:
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); } }
::
是Java 8中包含的新操作符,用于引用现有类的方法。 你可以引用一个类的静态方法和非静态方法。
为了引用静态方法,语法是:
ClassName :: methodName
为了引用非静态方法,语法是
objRef :: methodName
和
ClassName :: methodName
引用方法的唯一先决条件是该方法存在于function接口中,该接口必须与方法引用兼容。
方法引用在评估时创build一个function接口的实例。
find: http : //www.speakingcs.com/2014/08/method-references-in-java-8.html
这是Java 8中的一个方法引用。 这里是oracle文档。
正如文件中所述…
方法引用Person :: compareByAge是对静态方法的引用。
以下是对特定对象的实例方法的引用的示例:
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 :: compareByName调用作为对象myComparisonProvider的一部分的方法compareByName。 JRE推断方法types参数,在这种情况下是(Person,Person)。
这似乎是晚了,但这是我的两分钱。 lambdaexpression式用于创build匿名方法。 它只是调用一个现有的方法,但直接用它的名字来引用这个方法就更加清楚了。 方法的引用使我们可以使用方法引用操作符::
。
考虑以下简单的课程,每个员工都有一个名字和等级。
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; } }
假设我们有一个由某种方法返回的员工名单,我们希望按照他们的等级对员工进行sorting。 我们知道我们可以使用匿名类作为:
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("Farhan", "F"), new Employee("Brian", "B"), new Employee("Donald", "D"), new Employee("Adam", "A"), new Employee("Evan", "E") ); }
现在我们知道Comparable是一个function接口。 function接口是一个抽象方法(尽pipe它可能包含一个或多个默认或静态方法)。 所以我们可以使用lambadaexpression式:
employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambada 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 | +------------------------------------------------------------+--------------------------------------+
::运算符是在java 8中引入的方法引用。 方法引用是执行一种方法的lambdaexpression式的简写语法。 以下是方法参考的一般语法:
Object :: methodName
我们知道我们可以使用lambdaexpression式而不是使用匿名类。 但有时,lambdaexpression式实际上只是对某个方法的调用,例如:
Consumer<String> c = s -> System.out.println(s);
要使代码更清晰,可以将该lambdaexpression式转换为方法引用:
Consumer<String> c = System.out::println;
::被称为方法引用。 比方说,我们想要调用类Purchase的calculatePrice方法。 那我们可以把它写成:
Purchase::calculatePrice
它也可以被看作是写入lambdaexpression式的简短forms。因为方法引用被转换成lambdaexpression式。
在运行时,它们的行为完全相同。字节码可能不一样(对于上面的Incase,它会生成相同的字节码(complie above并检查javaap -c;))
在运行时,它们的行为完全相同。方法(math :: max);它生成相同的math(complie上面并检查javap -c;))
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 ^_^
在java-8 Streams Reducer中的简单工作是一个函数,它将两个值作为input,并在计算后返回结果。 这个结果在下一次迭代中被提供。
在Math:max函数的情况下,方法不断返回最大的两个值传递,最后你有最大的数字。
由于许多答案在这里解释well ::
behavior,另外我想澄清一下::
运算符如果用于实例variables,不需要与引用的Functional Interface具有完全相同的签名 。 让我们假设我们需要一个具有TestObjecttypes的BinaryOperator 。 传统的方式是这样实现的:
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;
我的eclipse告诉我: “不能从typesTestObject的非静态方法testInstance(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
调用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;
它只是不编译,编译器会告诉: “typesTestUtil没有定义testInstance(TestObject,TestObject)” 。 所以编译器会寻找一个静态引用,如果它不是相同的types。 好的多态性呢? 如果我们删除最终修饰符并添加我们的SubTestObject类:
public class SubTestObject extends TestObject { public final TestObject testInstance(TestObject t){ return t; } }
并致电:
BinaryOperator<TestObject> binary = SubTestObject::testInstance;
它不会编译,编译器仍然会查找静态引用。 但是,下面的代码将通过testing:
public class TestObject { public SubTestObject testInstance(Object t){ return (SubTestObject) t; } } BinaryOperator<TestObject> binary = TestObject::testInstance;
*我只是在学习,所以我试着去了解,如果我错了,请随时纠正我
我发现这个来源非常有趣。
实际上,这是Lambda变成了双冒号 。 双冒号更可读。 我们遵循以下步骤:
步骤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());
STEP3:
// The magic Comparator c = Comparator.comparing(Person::getAge());