Integer example = 1; // We can bind the instance variables, in this case just the return type is bound but there couple be method parameters also Supplier<String> s = example::toString; // We can bind to static method, here the generic parameter are the method parameter and return type Supplier<Long> f = System::currentTimeMillis; Function<Integer,Integer> f = example::compareTo;
A less common formulation is to pass in instance methods and find the first generic parameter of of the function to be the type, this allows you to easily pass in a range of actions to operate on a common type:
Function<Integer,String> f = Integer::toString
For example you can create an equals method that works on a subset of properties using functions mapped to instance methods as in the above example:
public static <T> boolean equals(T one, T two, Function<? super T, ?>... accessors) { if (one == two) { return true; } else if (one==null || two==null) { return false; } return Stream.of(accessors).allMatch(accessor -> Objects.equals(accessor.apply(one),accessor.apply(two))); } if (equals(one, two, Thing::getName, Thing:getOtherProperty)) ...;
Finally you can also bind the exception thrown from the method to one of the generic parameters. (Here I am using ThrowingException and ThrowingSupplier my home brew interfaces that are like there namesakes but have a generic parameter E for the exception thrown) This allows you to make you "closure" transparent to exceptions. This is more useful in a lot of cases when compared to the Stream throw nothing and "throws Exception" extremes.
ThrowingException<String,Integer,NumberFormatException> te = Integer::parseInt;
You can write funky closure methods that will throw different exceptions based on the passed in method reference does, no more catch (Exception).
public static <T, E extends Exception> T withCC(Class<?> contextClass, ThrowingSupplier<T,E> action) throws E { Thread t = Thread.current(); ClassLoader cl = t.getContextClassLoader(); try { t.setContextClassLoader(contextClass.getClassLoader()); return action.get(); } finally { s.setContextClassLoader(cl); } } // Throws IOException, complier knows that this method call throws IOException String value = withCC(Example.class, () -> { ... ... new FileOutpuStream(file); ... ... }); // Throws another exception, complier knows that this method call throws RMIExeption String value = withCC(Example.class, () -> { ... throw new RMIException(); });
Once you understand the last two, method reference start to become far more interesting.