Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
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 Concurre...
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....
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
n...
Performance Analysis: Lambdas
Designing for Lambda Expressions
Performance of Lazy Evaluation
Logging Performance
public void loggingConstantMessage(BenchmarkState state) {
log("Logging");
}
public void loggingConsta...
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 ...
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(...
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)
f...
0
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
Ops/s BasicDAO – map().collect() - 10 elements
original simplified re...
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<Fie...
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 operati...
QueryImpl – multiple operations
public String[] retrieveKnownFields(org.mongodb.morphia.DatastoreImpl ds, Class clazz) {
f...
0
500
1000
1500
2000
2500
3000
3500
4000
Ops/s QueryImpl - multiple operations
original simplified refactored refactoredMo...
Performance Analysis: Multiple Operations
For loop to anyMatch()
TypeConverter – anyMatch()
protected boolean oneOfClasses(final Class f, final Class[] classes) {
for (final Class c : cla...
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...
0
5000
10000
15000
20000
25000
30000
35000
40000
45000
50000
Ops/s Mapper – Arrays.stream().filter().findFirst()
original ...
Converters – stream().findFirst()
0
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
Ops/s Converters - stream().filter().findFirst() – 10 Elements
origin...
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 ...
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...
protected boolean oneOfClasses(final Class f, final Class[] classes) {
for (final Class c : classes) {
if (c.equals(f)) {
...
public static Class<? extends Annotation> original(final MappedField mf) {
Class<? extends Annotation> annType = null;
for...
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
Upcoming SlideShare
Loading in …5
×

Refactoring to Java 8 (Devoxx BE)

60,777 views

Published on

Updated presentation of how to migrate code to the latest version of Java, and the performance implications of these refactorings

Published in: Technology
  • Be the first to comment

Refactoring to Java 8 (Devoxx BE)

  1. 1. Trisha Gee (@trisha_gee) Developer & Technical Advocate, JetBrains Refactoring to Java 8
  2. 2. Why Java 8?
  3. 3. It’s Faster •Performance Improvements in Common Data Structures •Fork/Join Speed Improvements •Changes to Support Concurrency •…and more http://bit.ly/refJ8
  4. 4. Easy to Parallelize
  5. 5. Fewer Lines of Code
  6. 6. New Solutions to Problems
  7. 7. Minimizes Errors
  8. 8. Safety Check
  9. 9. Test Coverage
  10. 10. Performance Tests
  11. 11. Decide on the Goals •Readability •Fewer lines of code •Performance •Learning
  12. 12. Limit the Scope
  13. 13. Morphia https://github.com/mongodb/morphia
  14. 14. Refactoring to Lambda Expressions
  15. 15. Automatic Refactoring •Predicate •Comparator •Runnable •etc…
  16. 16. Abstract classes
  17. 17. Performance of Lambda Expressions
  18. 18. 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) ; }
  19. 19. 0 20 40 60 80 100 120 140 160 180Ops/ms Anonymous Inner Classes vs Lambdas Anonymous Inner Class Lambda
  20. 20. 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
  21. 21. Performance Analysis: Lambdas
  22. 22. Designing for Lambda Expressions
  23. 23. Performance of Lazy Evaluation
  24. 24. 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); }
  25. 25. 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
  26. 26. Performance Analysis: Lazy Evaluation
  27. 27. Refactoring using Collections & Streams
  28. 28. For loop to forEach() …with and without Streams
  29. 29. 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); }
  30. 30. 0 0.01 0.02 0.03 0.04 0.05 0.06 Ops/ms EntityScanner – forEach() original refactored
  31. 31. DatastoreImpl – filter().flatMap().forEach()
  32. 32. 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 Ops/ms DatastoreImpl original refactored
  33. 33. DuplicatedAttributeNames – filter().map().forEach()
  34. 34. 0 200 400 600 800 1000 1200 Ops/ms DuplicatedAttributeNames original refactored
  35. 35. Performance Analysis: forEach()
  36. 36. For loop to collect()
  37. 37. 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());
  38. 38. 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 Ops/s BasicDAO – map().collect() - 10 elements original simplified refactored parallel
  39. 39. 0 0.5 1 1.5 2 2.5 3 3.5 Ops/s BasicDAO – map().collect() - 10 000 element original simplified refactored parallel
  40. 40. 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()); }
  41. 41. 0 2000 4000 6000 8000 10000 12000 14000 AxisTitle ReflectionUtils – Arrays.stream().map().collect() original refactored
  42. 42. Performance Analysis: collect()
  43. 43. Collapsing multiple operations
  44. 44. MappingValidator – single stream operation
  45. 45. 0 1 2 3 4 5 6 7 8 9 10 EntityWithOneError EntityWith10Errors EntityWith20Errors Ops/s Mapping Validator - multiple operations original refactored
  46. 46. 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); }
  47. 47. 0 500 1000 1500 2000 2500 3000 3500 4000 Ops/s QueryImpl - multiple operations original simplified refactored refactoredMore
  48. 48. Performance Analysis: Multiple Operations
  49. 49. For loop to anyMatch()
  50. 50. 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)); }
  51. 51. 0 10000 20000 30000 40000 50000 60000 70000 Ops/s TypeConverter – anyMatch() – 10 Values original refactored parallel
  52. 52. 0 20 40 60 80 100 120 140 TypeConverter – anyMatch() – 10 000 Values original refactored parallel
  53. 53. 0 2 4 6 8 10 12 14 TypeConverter – anyMatch() – 100 000 Values original refactored parallel
  54. 54. Performance Analysis: anyMatch()
  55. 55. Performance Analysis: Arrays.stream()
  56. 56. For loop to findFirst()
  57. 57. MapreduceType – IntStream().map().filter().findFirst()
  58. 58. 0 5000 10000 15000 20000 25000 30000 AxisTitle MapreduceType - IntStream().map().filter().findFirst() original refactored
  59. 59. 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); }
  60. 60. 0 5000 10000 15000 20000 25000 30000 35000 40000 45000 50000 Ops/s Mapper – Arrays.stream().filter().findFirst() original refactored more
  61. 61. Converters – stream().findFirst()
  62. 62. 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 Ops/s Converters - stream().filter().findFirst() – 10 Elements original refactored parallel
  63. 63. 0 0.5 1 1.5 2 2.5 3 3.5 Ops/s Converters – stream().filter().findFirst() – 10 000 elements original refactored parallel
  64. 64. Performance Analysis: findFirst()
  65. 65. Use removeIf()
  66. 66. EntityScanner – removeIf()
  67. 67. 0 10 20 30 40 50 60 Ops/s EntityScanner - removeIf() original refactored
  68. 68. Performance Analysis: removeIf()
  69. 69. Summary of Findings
  70. 70. Refactoring to use lambda expressions is easy to automate…
  71. 71. 0 20 40 60 80 100 120 140 160 180 Ops/ms …and pretty safe performance-wise Anonymous Inner Class Lambda
  72. 72. Designing for lambda expressions could give a big performance benefit
  73. 73. 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
  74. 74. Generally the new idioms increase readability
  75. 75. Anonymous Inner Classes vs Lambdas
  76. 76. EntityScanner – forEach()
  77. 77. 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()
  78. 78. 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()
  79. 79. 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()
  80. 80. EntityScanner – removeIf()
  81. 81. Particularly with multiple operations
  82. 82. MappingValidator – single stream operation
  83. 83. QueryImpl – multiple operations
  84. 84. But be aware of performance
  85. 85. Arrays.stream() is probably substantially slower than using an array
  86. 86. 0 10000 20000 30000 40000 50000 60000 70000 Ops/s TypeConverter - 10 Values original refactored parallel
  87. 87. Using forEach() or collect() may be slower than iterating over a collection
  88. 88. 0 200 400 600 800 1000 1200 Ops/ms DuplicatedAttributeNames original refactored
  89. 89. Parallel is probably not going to give you speed improvements Unless your data is very big
  90. 90. 0 0.5 1 1.5 2 2.5 3 3.5 Ops/s BasicDAO – map().collect() - 10 000 element original simplified refactored parallel
  91. 91. Parallel is probably not going to give you speed improvements Or your operation is very expensive
  92. 92. But sometimes you get improved readability and better performance
  93. 93. 0 10 20 30 40 50 60 Ops/s EntityScanner - removeIf() original refactored
  94. 94. Conclusion
  95. 95. Should you migrate your code to Java 8?
  96. 96. It Depends
  97. 97. Always remember what your goal is And compare results to it
  98. 98. Understand what may impact performance And if in doubt, measure
  99. 99. Your tools can help you But you need to apply your brain too
  100. 100. http://bit.ly/refJ8 @trisha_gee

×