refactoring to java 8 (devoxx be)

100
Trisha Gee (@ trisha_gee ) Developer & Technical Advocate, JetBrains Refactoring to Java 8

Upload: trisha-gee

Post on 09-Jan-2017

30.444 views

Category:

Technology


0 download

TRANSCRIPT

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

180O

ps/

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

nse

c/o

p

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

Op

s/m

sLogging Performance

Direct call Lambda

Performance Analysis: Lazy Evaluation

Refactoring usingCollections & 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

Op

s/m

sEntityScanner – forEach()

original refactored

DatastoreImpl – filter().flatMap().forEach()

0

0.1

0.2

0.3

0.4

0.5

0.6

0.7

Op

s/m

sDatastoreImpl

original refactored

DuplicatedAttributeNames – filter().map().forEach()

0

200

400

600

800

1000

1200

Op

s/m

sDuplicatedAttributeNames

original refactored

Performance Analysis: forEach()

For loop to collect()

BasicDAO – map & collectpublic 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

Op

s/s

BasicDAO – map().collect() - 10 elements

original simplified refactored parallel

0

0.5

1

1.5

2

2.5

3

3.5

Op

s/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

Axi

s Ti

tle

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

Op

s/s

Mapping Validator - multiple operations

original refactored

QueryImpl – multiple operationspublic 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

Op

s/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

Op

s/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

Axi

s Ti

tle

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.class

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, Reference

.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

Op

s/s

Mapper – Arrays.stream().filter().findFirst()

original refactored more

Converters – stream().findFirst()

0

1000

2000

3000

4000

5000

6000

7000

8000

9000

10000

Op

s/s

Converters - stream().filter().findFirst() – 10 Elements

original refactored parallel

0

0.5

1

1.5

2

2.5

3

3.5

Op

s/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

Op

s/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

Op

s/m

s…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

Op

s/m

sLogging 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.class

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

Op

s/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

Op

s/m

sDuplicatedAttributeNames

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

Op

s/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

Op

s/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