Home | | Object Oriented Programming | | The Complete Reference Java | | Internet Programming | | Web Programming | Method References - Lambda Expressions

Chapter: Java The Complete Reference - The Java Language - Lambda Expressions

Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail

Method References - Lambda Expressions

There is an important feature related to lambda expressions called the method reference.

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.


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail


Copyright © 2018-2020 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.