Method
References
There is an important feature
related to lambda expressions called the method
reference. A method reference provides a way to refer to a method without
executing it. It relates to lambda expressions because it, too, requires a target
type context that consists of a compatible functional interface. When
evaluated, a method reference also creates an instance of the functional
interface.
There are different types of
method references. We will begin with method references to static methods.
Method
References to static Methods
To create a static method reference, use this
general syntax:
ClassName::methodName
Notice that the class name is
separated from the method name by a double colon. The :: is a new separator that has been added to Java by JDK 8
expressly for this purpose. This method reference can be used anywhere in which
it is compatible with its target type.
The following program
demonstrates a static method
reference:
//Demonstrate a method reference for a static
method.
//A functional interface for string operations.
interface StringFunc {
String func(String n);
}
//This class defines a static method called
strReverse().
class MyStringOps {
//A static method that reverses a string.
static String strReverse(String str) { String
result = "";
int i;
for(i = str.length()-1; i >= 0; i--) result
+= str.charAt(i);
return result;
}
}
class MethodRefDemo {
//This method has a functional interface as the
type of
//its first parameter. Thus, it can be passed
any instance
//of that interface, including a method
reference.
static String stringOp(StringFunc sf, String s)
{ return sf.func(s);
}
public static void main(String args[])
{
String inStr = "Lambdas add power to
Java"; String outStr;
// Here, a method reference to strReverse is
passed to stringOp().
outStr =
stringOp(MyStringOps::strReverse, inStr);
System.out.println("Original string:
" + inStr);
System.out.println("String reversed:
" + outStr);
}
}
The output is shown here:
Original string: Lambdas add power to Java
String reversed: avaJ ot rewop dda sadbmaL
In the program, pay special
attention to this line:
outStr = stringOp(MyStringOps::strReverse,
inStr);
Here, a reference to the static method strReverse( ), declared inside MyStringOps,
is passed as the first argument to stringOp(
). This works because strReverse
is compatible with the StringFunc functional
interface. Thus, the expression
MyStringOps::strReverse evaluates to
a reference to an object in which strReverse
provides the implementation of func( )
in
StringFunc.
Method
References to Instance Methods
To pass a reference to an
instance method on a specific object, use this basic syntax:
objRef::methodName
As you can see, the syntax is
similar to that used for a static
method, except that an object reference is used instead of a class name. Here
is the previous program rewritten to use an instance method reference:
//Demonstrate a method reference to an instance
method
//A functional interface for string operations.
interface StringFunc {
String func(String n);
}
//Now, this class defines an instance method
called strReverse().
class MyStringOps {
String strReverse(String str) { String result =
"";
int i;
for(i = str.length()-1; i >= 0; i--) result
+= str.charAt(i);
return result;
}
}
class MethodRefDemo2 {
//This method has a functional interface as the
type of
//its first parameter. Thus, it can be passed
any instance
//of that interface, including method
references.
static String stringOp(StringFunc sf, String s)
{ return sf.func(s);
}
public static void main(String args[])
{
String inStr = "Lambdas add power to
Java"; String outStr;
//Create a MyStringOps object.
MyStringOps strOps = new MyStringOps( );
//Now, a method reference to the instance
method strReverse
//is passed to stringOp().
outStr = stringOp(strOps::strReverse, inStr);
System.out.println("Original string:
" + inStr);
System.out.println("String reversed:
" + outStr);
}
}
This program produces the
same output as the previous version.
In the program, notice that strReverse( ) is now an instance method
of MyStringOps. Inside main( ), an instance of MyStringOps called strOps is created. This instance is used to create the method
reference to strReverse in the call
to stringOp, as shown again, here:
outStr = stringOp(strOps::strReverse, inStr);
In this example, strReverse( ) is called on the strOps object.
It is also possible to handle
a situation in which you want to specify an instance method that can be used
with any object of a given class—not just a specified object. In this case, you
will create a method reference as shown here:
ClassName::instanceMethodName
Here, the name of the class
is used instead of a specific object, even though an instance method is
specified. With this form, the first parameter of the functional interface
matches the invoking object and the second parameter matches the parameter
specified by the method. Here is an example. It defines a method called counter( ) that counts the number of
objects in an array that satisfy the condition defined by the func( ) method of the MyFunc functional interface. In this
case, it counts instances of the HighTemp
class.
//Use an instance method reference with
different objects.
//A functional interface that takes two
reference arguments
//and returns a boolean result.
interface MyFunc<T> { boolean func(T v1,
T v2);
}
// A class that stores the temperature high for
a day.
class HighTemp {
private int hTemp;
HighTemp(int ht) { hTemp = ht; }
//Return true if the invoking HighTemp object
has the same
//temperature as ht2.
boolean sameTemp(HighTemp ht2) { return hTemp
== ht2.hTemp;
}
//Return true if the invoking HighTemp object
has a temperature
//that is less than ht2.
boolean lessThanTemp(HighTemp ht2) { return
hTemp < ht2.hTemp;
}
}
class InstanceMethWithObjectRefDemo {
//A method that returns the number of
occurrences
//of an object for which some criteria, as
specified by
//the MyFunc parameter, is true.
static <T> int counter(T[] vals,
MyFunc<T> f, T v) {
int count = 0;
for(int i=0; i < vals.length; i++)
if(f.func(vals[i], v)) count++;
return count;
}
public static void main(String args[])
{
int count;
// Create an array of HighTemp objects.
HighTemp[] weekDayHighs = { new HighTemp(89),
new HighTemp(82), new HighTemp(90), new HighTemp(89), new HighTemp(89), new
HighTemp(91), new HighTemp(84), new HighTemp(83) };
//Use counter() with arrays of the class
HighTemp.
//Notice that a reference to the instance
method
//sameTemp() is passed as the second argument.
count = counter(weekDayHighs,
HighTemp::sameTemp,
new HighTemp(89)); System.out.println(count +
" days had a high of 89");
Now, create and use another array of HighTemp
objects. HighTemp[] weekDayHighs2 = { new HighTemp(32), new HighTemp(12),
new HighTemp(24), new HighTemp(19), new
HighTemp(18), new HighTemp(12), new HighTemp(-1), new HighTemp(13) };
count = counter(weekDayHighs2,
HighTemp::sameTemp, new HighTemp(12));
System.out.println(count + " days had a
high of 12");
//Now, use lessThanTemp() to find days when
temperature was less
//than a specified value.
count = counter(weekDayHighs,
HighTemp::lessThanTemp, new HighTemp(89));
System.out.println(count + " days had a
high less than 89");
count = counter(weekDayHighs2,
HighTemp::lessThanTemp, new HighTemp(19));
System.out.println(count + " days had a
high of less than 19");
}
}
The output is shown here:
3 days had a high of 89
2 days had a high of 12
3 days had a high less than 89
5 days had a high of less than 19
In the program, notice that HighTemp has two instance methods: sameTemp( ) and lessThanTemp( ). The first returns true if two HighTemp objects
contain the same temperature. The
second returns true if the
temperature of the invoking object is less than that of the passed object. Each
method has a parameter of type HighTemp
and each method returns a boolean
result. Thus, each is compatible with the MyFunc
functional interface because the invoking object type can be mapped to the
first parameter of func( ) and the
argument mapped to func( )’s second
parameter. Thus, when the expression
HighTemp::sameTemp
is passed to the counter( ) method, an instance of the
functional interface MyFunc is
created in which the parameter type of the first parameter is that of the
invoking object of the instance method, which is HighTemp. The type of the second parameter is also HighTemp because that is the type of
the parameter to sameTemp( ). The
same is true for the lessThanTemp( )
method.
One other point: you can
refer to the superclass version of a method by use of super, as shown here:
super::name
The name of the method is
specified by name.
Method
References with Generics
You can use method references
with generic classes and/or generic methods. For example, consider the
following program:
//Demonstrate a method reference to a generic
method
//declared inside a non-generic class.
//A functional interface that operates on an
array
//and a value, and returns an int result.
interface MyFunc<T> {
int func(T[] vals, T v);
}
//This class defines a method called
countMatching() that
//returns the number of items in an array that
are equal
//to a specified value. Notice that
countMatching()
//is generic, but MyArrayOps is not.
class MyArrayOps {
static <T> int countMatching(T[] vals, T
v) { int count = 0;
for(int i=0; i < vals.length; i++)
if(vals[i] == v) count++;
return count;
}
}
class GenericMethodRefDemo {
//This method has the MyFunc functional
interface as the
//type of its first parameter. The other two
parameters
//receive an array and a value, both of type T.
static <T> int myOp(MyFunc<T> f,
T[] vals, T v) { return f.func(vals, v);
}
public static void main(String args[])
{
Integer[] vals = { 1, 2, 3, 4, 2, 3, 4, 4, 5 };
String[] strs = { "One", "Two", "Three", "Two"
}; int count;
count =
myOp(MyArrayOps::<Integer>countMatching, vals, 4);
System.out.println("vals contains " + count + " 4s");
count =
myOp(MyArrayOps::<String>countMatching, strs, "Two");
System.out.println("strs contains " + count + " Twos");
}
}
The output is shown here:
vals contains 3 4s strs contains 2 Twos
In the program, MyArrayOps is a non-generic class that
contains a generic method called countMatching(
). The method returns a count of the elements in an array that match a
specified value. Notice how the generic type argument is specified. For
example, its first call in main( ),
shown here:
count =
myOp(MyArrayOps::<Integer>countMatching, vals, 4);
passes the type argument Integer. Notice that it occurs after
the ::. This syntax can be
generalized: When a generic method is specified as a method reference, its type
argument comes after the :: and
before the method name. It is important to point out, however, that explicitly
specifying the type argument is not required in this situation (and many
others) because the type argument would have been automatically inferred. In
cases in which a generic class is specified, the type argument follows the
class name and precedes the ::.
Although the preceding
examples show the mechanics of using method references, they don’t show their
real benefits. One place method references can be quite useful is in
conjunction with the Collections Framework, which is described later in Chapter
18. However, for completeness, a short, but effective, example that uses a
method reference to help determine the largest element in a collection is
included here. (If you are unfamiliar with the Collections Framework, return to
this example after you have worked through Chapter 18.)
One way to find the largest
element in a collection is to use the max(
) method defined by the Collections
class. For the version of max( )
used here, you must pass a reference to the collection and an instance of an
object that implements the Comparator<T>
interface. This interface specifies how two objects are compared. It defines
only one abstract method, called compare(
), that takes two arguments, each the type of the objects being compared.
It must return greater than zero if the first argument is greater than the
second, zero if the two arguments are equal, and less than zero if the first
object is less than the second.
In the past, to use max( ) with user-defined objects, an
instance of Comparator<T> had
to be obtained by first explicitly implementing it by a class, and then
creating an instance of that class. This instance was then passed as the
comparator to max( ). With JDK 8, it
is now possible to simply pass a reference to a comparison method to max( ) because doing so automatically
implements the comparator. The following simple example shows the process by
creating an ArrayList of MyClass objects and then finding the
one in the list that has the highest value (as defined by the comparison
method).
// Use a method reference to help find the
maximum value in a collection.
import java.util.*;
class MyClass { private int val;
MyClass(int v) { val = v; }
int getVal() { return val; }
}
class UseMethodRef {
// A compare() method compatible with the one
defined by Comparator<T>.
static int compareMC(MyClass a, MyClass b) {
return a.getVal() - b.getVal();
}
public static void main(String args[])
{
ArrayList<MyClass> al = new
ArrayList<MyClass>();
al.add(new MyClass(1)); al.add(new MyClass(4));
al.add(new MyClass(2)); al.add(new MyClass(9)); al.add(new MyClass(3));
al.add(new MyClass(7));
// Find the maximum value in al using the
compareMC() method.
MyClass maxValObj = Collections.max(al,
UseMethodRef::compareMC);
System.out.println("Maximum value is:
" + maxValObj.getVal());
}
The output is shown here:
Maximum value is: 9
In the program, notice that MyClass neither defines any comparison
method of its own, nor does it implement Comparator.
However, the maximum value of a list of MyClass
items can still be obtained by calling max(
) because UseMethodRef defines
the static method compareMC( ),
which is compatible with the compare( ) method
defined by Comparator. Therefore, there is no need to
explicitly implement and create an instance of Comparator.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.