KEMBAR78
Refactoring to Java 8 (Devoxx BE) | PDF
Trisha Gee (@trisha_gee)
Developer & Technical Advocate, JetBrains
Refactoring to
Java 8
Why Java 8?
It’s Faster
•Performance Improvements in Common Data
Structures
•Fork/Join Speed Improvements
•Changes to Support Concurrency
•…and more
http://bit.ly/refJ8
Easy to Parallelize
Fewer Lines of Code
New Solutions to Problems
Minimizes Errors
Safety Check
Test Coverage
Performance Tests
Decide on the Goals
•Readability
•Fewer lines of code
•Performance
•Learning
Limit the Scope
Morphia
https://github.com/mongodb/morphia
Refactoring to
Lambda Expressions
Automatic Refactoring
•Predicate
•Comparator
•Runnable
•etc…
Abstract classes
Performance of Lambda Expressions
Anonymous Inner Classes vs Lambdas
public String[] decodeWithAnonymousInnerClass(final BenchmarkState state) {
IterHelper.loopMap(state.values, new IterHelper.MapIterCallback<Integer, String>() {
@Override
public void eval(final Integer key, final String value) {
state.arrayOfResults[key] = value;
}
});
return state.arrayOfResults;
}
public void decodeWithLambda(final BenchmarkState state) {
IterHelper.<Integer, String>loopMap(state.values,
(key, value) -> state.arrayOfResults[key] = value) ;
}
0
20
40
60
80
100
120
140
160
180Ops/ms
Anonymous Inner Classes vs Lambdas
Anonymous Inner Class Lambda
http://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf
0
2
4
6
8
10
12
14
16
18
20
single thread max threads
nsec/op
Performance of Capture
anonymous(static) anonymous(non-static) lambda
Performance Analysis: Lambdas
Designing for Lambda Expressions
Performance of Lazy Evaluation
Logging Performance
public void loggingConstantMessage(BenchmarkState state) {
log("Logging");
}
public void loggingConstantMessageWithLambda(BenchmarkState state) {
log(() -> "Logging");
}
public void loggingVariableMessage(BenchmarkState state) {
log("Logging: " + state.i);
}
public void loggingVariableMessageWithLambda(BenchmarkState state) {
log(() -> "Logging: " + state.i);
}
0
50,000
100,000
150,000
200,000
250,000
300,000
350,000
400,000
450,000
500,000
Constant message Variable message
Ops/ms Logging Performance
Direct call Lambda
Performance Analysis: Lazy Evaluation
Refactoring using
Collections & Streams
For loop to forEach()
…with and without Streams
EntityScanner– forEach()
public void mapAllClassesAnnotatedWithEntity (Morphia m) {
final Reflections r = new Reflections(conf);
final Set<Class<?>> entities = r.getTypesAnnotatedWith(Entity.class);
for (final Class<?> c : entities) {
m.map(c);
}
}
public void mapAllClassesAnnotatedWithEntity(Morphia m) {
new Reflections(conf).getTypesAnnotatedWith(Entity.class).forEach(m::map);
}
0
0.01
0.02
0.03
0.04
0.05
0.06
Ops/ms
EntityScanner – forEach()
original refactored
DatastoreImpl – filter().flatMap().forEach()
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
Ops/ms
DatastoreImpl
original refactored
DuplicatedAttributeNames – filter().map().forEach()
0
200
400
600
800
1000
1200
Ops/ms
DuplicatedAttributeNames
original refactored
Performance Analysis: forEach()
For loop to collect()
BasicDAO – map & collect
public List originalIterationCode() {
final List<Object> ids = new ArrayList<>(keys.size() * 2)
for (final Key<Object> key : keys) {
ids.add(key.getId());
}
return ids;
}
public List simplifiedIterationCode() {
final List<Object> ids = new ArrayList<>()
for (final Key<Object> key : keys) {
ids.add(key.getId());
}
return ids;
}
public List refactoredCode() {
return keys.stream()
.map(Key::getId)
.collect(toList());
0
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
Ops/s BasicDAO – map().collect() - 10 elements
original simplified refactored parallel
0
0.5
1
1.5
2
2.5
3
3.5
Ops/s BasicDAO – map().collect() - 10 000 element
original simplified refactored parallel
ReflectionUtils
public static List<Field> original(final Field[] fields, final boolean returnFinalFields) {
final List<Field> validFields = new ArrayList<Field>();
// we ignore static and final fields
for (final Field field : fields) {
if (!Modifier.isStatic(field.getModifiers()) && (returnFinalFields || !Modifier.isFinal(field.getModifiers()))) {
validFields.add(field);
}
}
return validFields;
}
public static List<Field> refactored(final Field[] fields, final boolean returnFinalFields) {
return Arrays.stream(fields)
.filter(field -> isNotStaticOrFinal(returnFinalFields, field))
.collect(Collectors.toList());
}
0
2000
4000
6000
8000
10000
12000
14000
AxisTitle ReflectionUtils – Arrays.stream().map().collect()
original refactored
Performance Analysis: collect()
Collapsing multiple operations
MappingValidator – single stream operation
0
1
2
3
4
5
6
7
8
9
10
EntityWithOneError EntityWith10Errors EntityWith20Errors
Ops/s Mapping Validator - multiple operations
original refactored
QueryImpl – multiple operations
public String[] retrieveKnownFields(org.mongodb.morphia.DatastoreImpl ds, Class clazz) {
final MappedClass mc = ds.getMapper().getMappedClass(clazz);
final List<String> fields = new ArrayList<String>(mc.getPersistenceFields().size() + 1);
for (final MappedField mf : mc.getPersistenceFields()) {
fields.add(mf.getNameToStore());
}
return fields.toArray(new String[fields.size()]);
}
public String[] retrieveKnownFieldsRefactored(org.mongodb.morphia.DatastoreImpl ds, Class clazz) {
final MappedClass mc = ds.getMapper().getMappedClass(clazz);
return mc.getPersistenceFields()
.stream()
.map(MappedField::getNameToStore)
.collect(Collectors.toList())
.toArray(new String[0]);
}
public String[] retrieveKnownFields(org.mongodb.morphia.DatastoreImpl ds, Class clazz) {
final MappedClass mc = ds.getMapper().getMappedClass(clazz);
return mc.getPersistenceFields()
.stream()
.map(MappedField::getNameToStore)
.toArray(String[]::new);
}
0
500
1000
1500
2000
2500
3000
3500
4000
Ops/s QueryImpl - multiple operations
original simplified refactored refactoredMore
Performance Analysis: Multiple Operations
For loop to anyMatch()
TypeConverter – anyMatch()
protected boolean oneOfClasses(final Class f, final Class[] classes) {
for (final Class c : classes) {
if (c.equals(f)) {
return true;
}
}
return false;
}
protected boolean oneOfClasses(final Class f, final Class[] classes) {
return Arrays.stream(classes)
.anyMatch(c -> c.equals(f));
}
protected boolean oneOfClasses(final Class f, final Class[] classes) {
return Arrays.stream(classes)
.parallel()
.anyMatch(c -> c.equals(f));
}
0
10000
20000
30000
40000
50000
60000
70000
Ops/s TypeConverter – anyMatch() – 10 Values
original refactored parallel
0
20
40
60
80
100
120
140
TypeConverter – anyMatch() – 10 000 Values
original refactored parallel
0
2
4
6
8
10
12
14
TypeConverter – anyMatch() – 100 000 Values
original refactored parallel
Performance Analysis: anyMatch()
Performance Analysis: Arrays.stream()
For loop to findFirst()
MapreduceType – IntStream().map().filter().findFirst()
0
5000
10000
15000
20000
25000
30000
AxisTitle MapreduceType - IntStream().map().filter().findFirst()
original refactored
Mapper – findFirst()
public static Class<? extends Annotation> original(final MappedField mf) {
Class<? extends Annotation> annType = null;
for (final Class<? extends Annotation> testType : new Class[]{Property.class, Embedded.class, Serialized.class, Reference.c
if (mf.hasAnnotation(testType)) {
annType = testType;
break;
}
}
return annType;
}
public static Class<? extends Annotation> refactored(final MappedField mf) {
return (Class<? extends Annotation>) Arrays.stream(new Class[]{Property.class, Embedded.class, Serialized.class, Referenc
.filter(mf::hasAnnotation)
.findFirst()
.orElse(null);
}
public static Class<? extends Annotation> refactoredMore(final MappedField mf) {
return (Class<? extends Annotation>) Stream.of(Property.class, Embedded.class, Serialized.class, Reference.class)
.filter(mf::hasAnnotation)
.findFirst()
.orElse(null);
}
0
5000
10000
15000
20000
25000
30000
35000
40000
45000
50000
Ops/s Mapper – Arrays.stream().filter().findFirst()
original refactored more
Converters – stream().findFirst()
0
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
Ops/s Converters - stream().filter().findFirst() – 10 Elements
original refactored parallel
0
0.5
1
1.5
2
2.5
3
3.5
Ops/s Converters – stream().filter().findFirst() – 10 000 elements
original refactored parallel
Performance Analysis: findFirst()
Use removeIf()
EntityScanner – removeIf()
0
10
20
30
40
50
60
Ops/s EntityScanner - removeIf()
original refactored
Performance Analysis: removeIf()
Summary of Findings
Refactoring to use lambda expressions is
easy to automate…
0
20
40
60
80
100
120
140
160
180
Ops/ms …and pretty safe performance-wise
Anonymous Inner Class Lambda
Designing for lambda expressions could
give a big performance benefit
0
50,000
100,000
150,000
200,000
250,000
300,000
350,000
400,000
450,000
500,000
Constant message Variable message
Ops/ms Logging Performance
Direct call Lambda
Generally the new idioms increase
readability
Anonymous Inner Classes vs Lambdas
EntityScanner – forEach()
public List originalIterationCode() {
final List<Object> ids = new ArrayList<>(keys.size() * 2)
for (final Key<Object> key : keys) {
ids.add(key.getId());
}
return ids;
}
public List refactoredCode() {
return keys.stream()
.map(Key::getId)
.collect(toList());
BasicDAO – map().collect()
protected boolean oneOfClasses(final Class f, final Class[] classes) {
for (final Class c : classes) {
if (c.equals(f)) {
return true;
}
}
return false;
}
protected boolean oneOfClasses(final Class f, final Class[] classes) {
return Arrays.stream(classes)
.anyMatch(c -> c.equals(f));
}
TypeConverter – anyMatch()
public static Class<? extends Annotation> original(final MappedField mf) {
Class<? extends Annotation> annType = null;
for (final Class<? extends Annotation> testType : new Class[]{Property.class, Embedded.class, Serialized.class, Reference.c
if (mf.hasAnnotation(testType)) {
annType = testType;
break;
}
}
return annType;
}
public static Class<? extends Annotation> refactoredMore(final MappedField mf) {
return (Class<? extends Annotation>) Stream.of(Property.class, Embedded.class, Serialized.class, Reference.class)
.filter(mf::hasAnnotation)
.findFirst()
.orElse(null);
}
Mapper – findFirst()
EntityScanner – removeIf()
Particularly with multiple operations
MappingValidator – single stream operation
QueryImpl – multiple operations
But be aware of performance
Arrays.stream() is probably
substantially slower than using an array
0
10000
20000
30000
40000
50000
60000
70000
Ops/s TypeConverter - 10 Values
original refactored parallel
Using forEach() or collect() may be
slower than iterating over a collection
0
200
400
600
800
1000
1200
Ops/ms
DuplicatedAttributeNames
original refactored
Parallel is probably not going to give you
speed improvements
Unless your data is very big
0
0.5
1
1.5
2
2.5
3
3.5
Ops/s BasicDAO – map().collect() - 10 000 element
original simplified refactored parallel
Parallel is probably not going to give you
speed improvements
Or your operation is very expensive
But sometimes you get improved
readability and better performance
0
10
20
30
40
50
60
Ops/s EntityScanner - removeIf()
original refactored
Conclusion
Should you migrate your code to Java 8?
It Depends
Always remember what your goal is
And compare results to it
Understand what may impact performance
And if in doubt, measure
Your tools can help you
But you need to apply your brain too
http://bit.ly/refJ8
@trisha_gee

Refactoring to Java 8 (Devoxx BE)