1. Function<F, T> & Functions
2. Predicate<T> & Predicates
3. Supplier<T> & Suppliers
1. Function & Functions
1> Function
With Java in its current state, we don't have closures as they exist in other languages.
For now, Java's answer to closures is to use anonymous classes.
While an anonymous class functions effectively in the same way as a closure, the syntax can be bulky and when used too much, can make your code hard to follow and maintain.
@Test
public void functionTest2() {
String dateStr = new Function<Date, String>() {
private final SimpleDateFormat dateFormat = new SimpleDateFormat(
"dd/MM/YYYY");
public String apply(Date input) {
return dateFormat.format(input);
}
}.apply(Calendar.getInstance().getTime());
logger.info(dateStr);
}
public String formatDate(Date date) {
return new SimpleDateFormat("dd/MM/YYYY").format(date);
}
Now compare the previous example of the anonymous class implementing Function interface.
This final example is much easier to read.
However, if you have a collection of Date objects and need to obtain a list contains the string representations of those date, the Function interface could be a better approach.
2> Functions.forMap
public static <K, V> Function<K, V> forMap(Map<K, V> map);
public static <K, V> Function<K, V> forMap(Map<K, ? extends V> map, @Nullable V defaultValue);
@Test
public void functionsTest() {
Map<String, State> map = Maps.newHashMap();
Function<String, State> stateLookUpFunction = Functions.forMap(map,
new State("UN", "Unknown"));
map.put("NY", new State("NY", "New York"));
map.put("CA", new State("CA", "California"));
map.put("LA", new State("LA", "Los Angeles"));
State state = stateLookUpFunction.apply("NY");
logger.info(state);
state = stateLookUpFunction.apply("MS");
logger.info(state);
}
There is one caveat to using the Functions.forMap method.
The map returned by Functions.forMap will throw an IllegalArgumentException if the given key is not found in the map.
Functions.forMap(map); // throw IllegalArgumentException if cannot find key
Functions.forMap(map, null); // return "null" if cannot find key
Functions.forMap(map, defaultValue); // return "defaultValue" if cannot find key
By using a Function interface to perform the state lookups, you can easily change out the implementation.
When combining it with a Splitter object to create a map or when using some of the other methods for map creation in Guava collection package,
we are leveraging the power of Guava in our code.
3> Functions.compose
public static <A, B, C> Function<A, C> compose(Function<B, C> g, Function<A, ? extends B> f) {
return new FunctionComposition<A, B, C>(g, f);
}
public FunctionComposition(Function<B, C> g, Function<A, ? extends B> f) {
this.g = checkNotNull(g);
this.f = checkNotNull(f);
}
@Override
public C apply(@Nullable A a) {
return g.apply(f.apply(a));
}
Return the composition of two functions. For f: A->B and g: B->C, composition is defined as the function h such that h(a) == g(f(a)) for each a.
@Test
public void composeTest() {
Map<String, State> map = Maps.newHashMap();
map.put("NY", new State("NY", "New York"));
map.put("CA", new State("CA", "California"));
map.put("LA", new State("LA", "Los Angeles"));
Function<String, String> function = Functions.compose(
new Function<State, String>() {
public String apply(State input) {
return input.getStateAddress();
}
}, Functions.forMap(map, new State("UN", "Unknown")));
logger.info(function.apply("NY")); // "New York"
logger.info(function.apply("DA")); // "Unknown"
// Functions.forMap() returns Function<String, State>
// Anonymous innner Function<State, String>
// The composed Function<String, String>
}
2. Predicate & Predicates
1> Predicate
The Predicate interface is a functional cousin to the Function interface.
public interface Predicate<T>{
boolean apply(T input)
}
@Test
public void applyTest() {
String input = "Davy Jones";
boolean containsKeyWords = new Predicate<String>() {
public boolean apply(String input) {
return input.contains("Davy");
}
}.apply(input);
logger.info(containsKeyWords);
}
2> Predicates.and
@Test
public void andTest() {
String str = "Davy Jones";
boolean containsKeyWords = Predicates.and(new Predicate<String>() {
public boolean apply(String input) {
return input.contains("Davy");
}
}, new Predicate<String>() {
public boolean apply(String input) {
return input.contains("Cool");
}
}).apply(str);
logger.info(containsKeyWords); //false
}
3> Predicates.or
@Test
public void orTest() {
String str = "Davy Jones";
boolean containsKeyWords = Predicates.or(new Predicate<String>() {
public boolean apply(String input) {
return input.contains("Davy");
}
}, new Predicate<String>() {
public boolean apply(String input) {
return input.contains("Cool");
}
}).apply(str);
logger.info(containsKeyWords); //true
}
4> Predicates.not
@Test
public void notTest() {
String str = "Davy Jones";
boolean containsKeyWords = Predicates.not(new Predicate<String>() {
public boolean apply(String input) {
return input.contains("Davy");
}
}).apply(str);
logger.info(containsKeyWords); //false
}
5> Predicates.compose
private CompositionPredicate(Predicate<B> p, Function<A, ? extends B> f) {
this.p = checkNotNull(p);
this.f = checkNotNull(f);
}
@Override
public boolean apply(@Nullable A a) {
return p.apply(f.apply(a));
}
@Test
public void composeTest() {
boolean isPersonMale = Predicates.compose(new Predicate<String>() {
public boolean apply(String input) {
return "Male".equals(input);
}
}, new Function<Person, String>() {
public String apply(Person input) {
return input.getGender();
}
}).apply(new Person("Davy", "Male"));
assertTrue(isPersonMale);
}
class Person {
String name;
String gender;
public Person(String name, String gender) {
super();
this.name = name;
this.gender = gender;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
}
3. Supplier & Suppliers
1> Supplier
public interface Supplier<T>{
T get();
}
The Supplier interface helps us implement several of the typical creational patterns.
When get is called, we would always return the same instance(singleton) or a new instance with each invocation.
@Test
public void getTest() {
String str = new Supplier<String>() {
public String get() {
return "AAA";
}
}.get();
assertEquals("AAA", str);
}
@Test
public void getTest2() {
Predicate<String> predicate = new Supplier<Predicate<String>>() {
public Predicate<String> get() {
return new Predicate<String>() {
public boolean apply(String input) {
return "AAA".equals(input);
}
};
}
}.get();
assertTrue(predicate.apply("AAA"));
assertFalse(predicate.apply("BBB"));
}
2> Suppliers.memoize
public static <T> Supplier<T> memoize(Supplier<T> delegate) {
return (delegate instanceof MemoizingSupplier)
? delegate
: new MemoizingSupplier<T>(Preconditions.checkNotNull(delegate));
}
MemoizingSupplier(Supplier<T> delegate) {
this.delegate = delegate;
}
@Override public T get() {
// A 2-field variant of Double Checked Locking.
if (!initialized) {
synchronized (this) {
if (!initialized) {
T t = delegate.get();
value = t;
initialized = true;
return t;
}
}
}
return value;
}
We can see from the source code that the MemoizingSupplier is just a proxy for innerSupplier, it stores an inner instance which is returned by delegate. Everytime we call get(), the proxy will return this instance.
@Test
public void getTest() {
Supplier<Object> supplier = new Supplier<Object>() {
public Object get() {
return new Object();
}
};
Object obj1 = supplier.get();
Object obj2 = supplier.get();
assertNotEquals(obj1, obj2);
Supplier<Object> proxy = Suppliers.memoize(supplier);
obj1 = proxy.get();
obj2 = proxy.get();
assertEquals(obj1, obj2);
}
3> Suppliers.memoizeWithExpiration
public static <T> Supplier<T> memoizeWithExpiration(
Supplier<T> delegate, long duration, TimeUnit unit) {
return new ExpiringMemoizingSupplier<T>(delegate, duration, unit);
}
ExpiringMemoizingSupplier(
Supplier<T> delegate, long duration, TimeUnit unit) {
this.delegate = Preconditions.checkNotNull(delegate);
this.durationNanos = unit.toNanos(duration);
Preconditions.checkArgument(duration > 0);
}
@Override public T get() {
long nanos = expirationNanos;
long now = Platform.systemNanoTime();
if (nanos == 0 || now - nanos >= 0) {
synchronized (this) {
if (nanos == expirationNanos) { // recheck for lost race
T t = delegate.get();
value = t;
nanos = now + durationNanos;
expirationNanos = (nanos == 0) ? 1 : nanos;
return t;
}
}
}
return value;
}
@Test
public void memoizeWithExpirationTest() throws InterruptedException {
Supplier<Object> supplier = new Supplier<Object>() {
public Object get() {
return new Object();
}
};
Object obj1 = supplier.get();
Object obj2 = supplier.get();
assertNotEquals(obj1, obj2);
Supplier<Object> proxy = Suppliers.memoizeWithExpiration(supplier, 1L,
TimeUnit.SECONDS);
obj1 = proxy.get();
obj2 = proxy.get();
assertEquals(obj1, obj2);
obj1 = proxy.get();
Thread.sleep(1000 * 2);
obj2 = proxy.get();
assertNotEquals(obj1, obj2);
}
4> Suppliers.compose
public static <F, T> Supplier<T> compose(
Function<? super F, T> function, Supplier<F> supplier) {
return new SupplierComposition<F, T>(function, supplier);
}
SupplierComposition(Function<? super F, T> function, Supplier<F> supplier) {
this.function = function;
this.supplier = supplier;
}
@Override public T get() {
return function.apply(supplier.get());
}
@Test
public void composeTest() {
Map<String, String> map = Maps.newHashMap();
map.put("A", "A for alcohol");
map.put("B", "B for boycott");
Supplier<String> supplier = Suppliers.compose(Functions.forMap(map),
new Supplier<String>() {
public String get() {
return "A";
}
});
String str = supplier.get();
assertEquals("A for alcohol", str);
}
Summary:
We've seen how Guava can add some functional aspects to Java with the Function and Predicate interfaces.
The Function interface provides us with the ability to transform objects and the Predicate interface gives us a powerful machanism for filtering.
The Functions and Predicates classes also help us write code that is easier to maintain and much easier to change.
The Suppliers help by providing essential collaborating objects while completely hiding the details of how those objects are created.
Combined with a dependency injection framework such as a Spring or Guice, these interfaces will allow us to seamlessly change the behavior of our programs by simply providing a different implementation.
Reference Links:
1) "Getting Started with Google Guava" -Bill Bejeck