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)