Java 8 is one of the largest upgrades to the popular language and framework in over a decade. In this talk, I will first overview several new, key features of Java 8 that can help make programs easier to read, write, and maintain, especially in regards to collections. These features include Lambda Expressions, the Stream API, and enhanced interfaces, many of which help bridge the gap between functional and imperative programming paradigms and allow for succinct concurrency implementations. Next, I will discuss several open issues related to automatically migrating (refactoring) legacy Java software to use such features correctly, efficiently, and as completely as possible. Solving these problems will help developers to maximally understand and adopt these new features thus improving their software.
Automatic Migration of Legacy Java Method Implementations to Interfaces
1. Automatic Migration of LegacyAutomatic Migration of Legacy
Java Method ImplementationsJava Method Implementations
to Interfacesto Interfaces
Work in ProgressWork in Progress
Raffi Khatchadourian
Computer Systems Technology
New York City College of Technology
City University of New York
Department of Computer Science
College of Staten Island
City University of New York
June 12, 2015
Based on slides by Duarte Duarte, Eduardo Martins, Miguel Marques and
Ruben Cordeiro and Horstmann, Cay S. (2014-01-10). Java SE8 for the
Really Impatient: A Short Course on the Basics (Java Series). Pearson
Education.
3. Some HistorySome History
Java was invented in the 90's as an Object-Oriented language.
Largest change was in ~2005 with Java 5.
Generics.
Enhanced for loops.
Annotations.
Type-safe enumerations.
Concurrency enhancements (AtomicInteger).
4. Java 8 is MassiveJava 8 is Massive
10 years later, Java 8 is packed with new features.
Core changes are to incorporate functional language features.
5. Functional LanguagesFunctional Languages
Declarative ("what not how").
The only state is held in parameters.
Traditionally popular in academia and AI.
Well-suited for event-driven/concurrent ("reactive") programs.
6. Lambda ExpressionsLambda Expressions
A block of code that you can pass around so it can be
executed later, once or multiple times.
Anonymous methods.
Reduce verbosity caused by anonymous classes.
12. Functional InterfacesFunctional Interfaces
ExampleExample
@FunctionalInterface
public interface Runnable {
public void run();
}
Runnable r = () -> System.out.println("Hello World!");
@FunctionalInterface:
May be omitted.
Generates an error when there is more than one abstract method.
Can store a lambda expression in a variable.
Can return a lambda expression.
13. java.util.functionjava.util.function
Predicate<T> - a boolean-valued property of an object
Consumer<T> - an action to be performed on an object
Function<T,R> - a function transforming a T to a R
Supplier<T> - provide an instance of a T (such as a factory)
UnaryOperator<T> - a function from T to T
BinaryOperator<T> - a function from (T,T) to T
15. Method ReferencesMethod References
ExamplesExamples
class Person {
private String name;
private int age;
public int getAge() {return this.age;}
public String getName() {return this.name;}
}
Person[] people = ...;
Comparator<Person> byName = Comparator.comparing(Person::getName);
Arrays.sort(people, byName);
16. Method ReferencesMethod References
Kinds of method referencesKinds of method references
A static method (ClassName::methName)
An instance method of a particular object
(instanceRef::methName)
A super method of a particular object (super::methName)
An instance method of an arbitrary object of a particular type
(ClassName::methName)
A class constructor reference (ClassName::new)
An array constructor reference (TypeName[]::new)
19. Why default methods?Why default methods?
Java 8 has lambda expressions. We want to start using them:
List<?> list = ...
list.forEach(...); // lambda code goes here
20. There's a problemThere's a problem
The forEach method isn’t declared by java.util.List nor the
java.util.Collection interface because doing so would break
existing implementations.
22. Default MethodsDefault Methods
Traditionally, interfaces can't have method definitions (just
declarations).
Default methods supply default implementations of interface
methods.
By default, implementers will receive this implementation if they don't
provide their own.
28. public interface A {
default void foo(){
System.out.println("Calling A.foo()");
}
}
public interface B {
default void foo(){
System.out.println("Calling B.foo()");
}
}
public class Clazz implements A, B {
}
Does this code compile?
29. Of course not (Java is not C++)!
class Clazz inherits defaults for foo() from both types
A and B
How do we fix it?
30. Option A:
public class Clazz implements A, B {
public void foo(){/* ... */}
}
We resolve it manually by overriding the conflicting method.
Option B:
public class Clazz implements A, B {
public void foo(){
A.super.foo(); // or B.super.foo()
}
}
We call the default implementation of method foo() from either
interface A or B instead of implementing our own.
31. Going back to the example of forEach method, how can we force it's
default implementation on all of the iterable collections?
32. Let's take a look at the Java UML for all the iterable Collections:
To add a default behavior to all the iterable collections, a default
forEach method was added to the Iterable<E> interface.
33. We can find its default implementation in java.lang.Iterable
interface:
@FunctionalInterface
public interface Iterable {
Iterator iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
34. The forEach method takes a java.util.function.Consumer
functional interface type as a parameter, which enables us to pass in a
lambda or a method reference as follows:
List<?> list = ...
list.forEach(System.out::println);
This is also valid for Sets and Queues, for example, since both classes
implement the Iterable interface.
Specific collections, e.g., UnmodifiableCollection, may override
the default implementation.
35. SummarySummary
Default methods can be seen as a bridge between lambdas and JDK
libraries.
Can be used in interfaces to provide default implementations of
otherwise abstract methods.
Clients can optionally implement (override) them.
Can eliminate the need for skeletal implementators like
.
Can eliminate the need for utility classes like .
static methods are now also allowed in interfaces.
AbstractCollection
Collections
37. MotivationMotivation
Two cases:
1. Complete migration of skeletal implementation to interface.
Makes type hierarchy less complicated.
One less type.
Makes interfaces easier to implement.
No global analysis required to find skeletal implemention.
Makes interfaces easier to maintain.
No simultaneous modifications between interface and skeletal
implementation required.
2. Partial migration of skeletal implementation to interface.
Possibly makes interfaces easier to implement.
All implementations may not need to extended the skeletal
implementation.
Possibly makes interfaces easier to maintain.
Possibily less simultaneous modifications between interface
and skeletal implementation required.
39. MotivationMotivation
Two cases:
1. Complete migration of utility classes to interface(s).
Makes type hierarchy less complicated.
One less type.
Makes interfaces easier to use.
No global analysis required to find utility class.
Only methods applicable to the interface are present.
Can call methods like instance methods (a.b() instead of b(a)).
IDE will show applicable methods.
2. Partial migration of utility classes to interface(s).
Still makes interfaces easier to use for certain methods.
Can avoid global analysis in some cases.
40. Open ProblemsOpen Problems
Can eliminate the need for skeletal implementators like
.AbstractCollection
Can we automate this?Can we automate this?
1. How do we determine if an interface
implementor is "skeletal?"
2. What is the criteria for an instance method to be
converted to a default method?
3. What if there is a hierarchy of skeletal
implementors, e.g., AbstractCollection,
AbstractList?
4. How do we preserve semantics?
5. What client changes are necessary?
42. Open ProblemsOpen Problems
Can eliminate the need for utility classes like .Collections
Can we automate this?Can we automate this?
1. How do we determine if a class is a
"utility" class?
2. What is the criteria for a static method to
be:
Converted to an instance method?
Moved to an interface?
3. How do we preserve semantics?
4. What client changes are necessary?
43. Let's start with question #2 for both the previous slides. There seems to
be a few cases here:
# From a class To an interface
1. instance method default method
2. static method default method
3. static method static method
Is case #3 a simple move method refactoring? (Yes
but to where? -- more later)
44. Why Is This Complicated?Why Is This Complicated?
Case #1 corresponds to moving a skeletal implementation to an
interface.
# From a class To an interface
1. instance method default method
For case #1, can we not simply use a move method refactoring?
No. The inherent problem is that, unlike classes,
interfaces may extend multiple interfaces. As such, we
now have a kind of multiple inheritance problem to
deal with.
45. Case #1Case #1
Some (perhaps) Obvious PreconditionsSome (perhaps) Obvious Preconditions
Source instance method must not access any instance fields.
Interfaces may not have instance fields.
Can't currently have a default method in the target interface with
the same signature.
Can't modify target method's visibility.
Some Obvious TasksSome Obvious Tasks
Must replace the target method in the interface (add a body to it).
Must add the default keyword.
Must remove any @Override annotations (it's top-level now).
If nothing left in abstract class, remove it?
Need to deal with documentation.
46. Case #1Case #1
Some Not-so-obvious PreconditionsSome Not-so-obvious Preconditions
Suppose we have the following situation:
interface I {
void m();
}
interface J {
void m();
}
abstract class A implements I, J {
@Override
void m() {...}
}
Here, A provides a partial implementation of I.m(), which also happens
to be declared as part of interface J.
47. Case #1Case #1
Some Not-so-obvious PreconditionsSome Not-so-obvious Preconditions
Now, we pull up A.m() to be a default method of I:
interface I {
default void m() {..} //was void m();
}
interface J {
void m(); //stays the same.
}
abstract class A implements I, J {
//now empty, was: void m() {...}
}
We now have a compile-time error in A because A needs to declare
which m() it inherits.
In general, inheritance hierarchies may be large and complicated.
Other preconditions may also exist (work in progress).
48. Why Is This Complicated?Why Is This Complicated?
Case #3 corresponds to moving a static utility method to an interface.
# From a class To an interface
3. static method default method
For case #1, can we not simply use a move method refactoring?
No and for a few reasons:
Method signature must change (not only removing
the static keyword).
Client code must change from static method calls
to instance method calls.
Which parameter is to be the receiver of the call?
What is the target interface?
49. Case #3Case #3
Some (perhaps) Obvious PreconditionsSome (perhaps) Obvious Preconditions
The source method vaciously doesn't access instance fields.
Default version of the source method can't already exist in the target
interface.
Some Obvious TasksSome Obvious Tasks
Must add the default keyword.
If nothing left in utility class, remove it?
Need to deal with documentation.
50. Case #3Case #3
Some Not-so-obvious PreconditionsSome Not-so-obvious Preconditions
/**
* Copies all of the elements from one list into another.
*/
public static <T> void Collections.copy(List<? super T> dest, List<? extends T
Should this be a static or default method?
Call it as Collections.copy(l2, l1) or l2.copy(l1)?
It's probably more natural to leave it as static .
Also, we can't express the generics if it was refactored to a
default method.
The generics here express the relationship between the two
lists in a single method.
We can't do if it was default.
51. Case #3Case #3
You may be tempted to move the copy method to a default method in
List, but since the method uses generics and expresses a very distinct
relationship between two two Lists, you will lose the type safety
offered by the generics. For example, suppose you have:
List<String> l1;
List<String> l2;
Now, you copy the contents of l2 into l1:
Collections.copy(l1, l2);
If l2 is, instead, List<Integer>, you will receive a compile-time error.
However, if copy was a default method of, say, the List interface, we
would have something like this:
l1.copy(l2)
Making l2 a List<Integer> would not result in a compile-time error.
52. Case #3Case #3
This method should actually move to the List interface and remain
static, but:
What is there's another method named copy in the List interface?
List is an interface, and interfaces can extend other interfaces.
What if there's another method with the same signature in the
hierarchy?
53. Case #4Case #4
Determining the target interface for caseDetermining the target interface for case
#4#4
We can apply a heuristic: take the least common interface of the
parameters. For example:
public static void m(I1 p1, I2 p2, ..., In pn);
Here, we would take the closest sibling to I1, I2, ..., In as the target
interface. Otherwise, we normally take the first parameter if it's an
interface.
56. java.util.streamjava.util.stream
Stream sourcesStream sources
CollectionsCollections
Set<Student> set = new LinkedHashSet<>();
Stream<Student> stream = set.stream();
GeneratorsGenerators
Random random = new Random();
Stream<Integer> randomNumbers = Stream.generate(random::nextInt);
// or simply ...
IntStream moreRandomNumbers = random.ints();
From other streamsFrom other streams
Stream newStream = Stream.concat(stream, randomNumbers);
57. java.util.streamjava.util.stream
Intermediate operationsIntermediate operations
.filter - excludes all elements that don’t match a Predicate
.map - perform transformation of elements using a Function
.flatMap - transform each element into zero or more elements by
way of another Stream
.peek - performs some action on each element
.distinct - excludes all duplicate elements (equals())
.sorted - orderered elements (Comparator)
.limit - maximum number of elements
.substream - range (by index) of elements
List<Person> persons = ...;
Stream<Person> tenPersonsOver18 = persons.stream()
.filter(p -> p.getAge() > 18)
.limit(10);
58. java.util.streamjava.util.stream
Terminating operationsTerminating operations
1. Obtain a stream from some sources
2. Perform one or more intermidate operations
3. Perform one terminal operation
reducers like reduce(), count(), findAny(), findFirst()
collectors (collect())
forEach
iterators
List<Person> persons = ..;
List<Student> students = persons.stream()
.filter(p -> p.getAge() > 18)
.map(Student::new)
.collect(Collectors.toList());