├── .gitignore ├── README.md └── src └── com └── xpinjection └── java8 ├── good ├── builder │ ├── Sample.java │ └── User.java ├── builder2 │ ├── Sample.java │ └── User.java ├── chain │ ├── DigitCounter.java │ ├── LambdaChainSample.java │ └── NaiveDigitCounter.java ├── dsl │ └── HtmlElement.java └── visitor │ ├── Circle.java │ ├── LambdaVisitor.java │ ├── PatternMatchingSample.java │ ├── Rectangle.java │ └── Square.java └── misused ├── Annotations.java ├── Permission.java ├── Role.java ├── User.java ├── UserDto.java ├── lambda ├── AvoidComplexLambdas.java ├── AvoidLongLambdas.java ├── ClassDesign.java ├── LambdasAreNotAlwaysTheBestOption.java ├── LazyCalculationsImprovePerformance.java └── collections │ ├── EmulateMultimap.java │ ├── ListSorting.java │ ├── MapIterating.java │ └── RemoveElementWithIterator.java ├── optional ├── HundredAndOneApproach.java ├── IfStatementIsNotAlwaysBadThing.java ├── OptionalElvis.java ├── OptionalOverEngineering.java ├── StrictCheckOfValuePresence.java └── usage │ ├── InternalOptionalUsage.java │ ├── OptionalConstructorParameters.java │ └── OptionalForCollections.java ├── stream ├── CreationOptions.java ├── DoNotNeglectDataStructures.java ├── ImperativeCodeMix.java ├── MatchElementInFunctionalStyle.java ├── NestedForEach.java ├── PreferSpecializedStreams.java ├── RichDomainModel.java ├── SameOldCodeStyleWithNewConstructs.java ├── SkipAndLimitOnListIsWaste.java ├── UntypedStreamsCouldBeConverted.java ├── WantToUseStreamsEverywhere.java ├── collectors │ ├── AvoidLoopsInStreams.java │ ├── CollectorsChain.java │ ├── ExternalCollectionForGrouping.java │ ├── StatisticsCalculation.java │ ├── StreamMayBeConvertedToArray.java │ └── TrueFunctionalApproach.java └── incorrect │ ├── ForgotTerminalOperation.java │ ├── InfiniteStreams.java │ └── UseStreamMoreThanOnce.java └── time └── TimeApiIgnorance.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/*.* 2 | .idea/*/*.* 3 | 4 | *.iml 5 | *.iws 6 | *.ipr 7 | 8 | /out/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java8-misuses 2 | 3 | Common misuses of new Java 8 features and other mistakes. 4 | 5 | Java 8 is being around for a while already and lot of us are already using Java 8 features on our projects. But do we use these great Java 8 features correctly and efficiently? Having done lots of code reviews during last years we’ve seen some common antipatterns of Java 8 features usage. Here we want to gather lots of examples where Java 8 features were misused or poorly used and show you how certain things could have been better implemented. 6 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/builder/Sample.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.builder; 2 | 3 | /** 4 | * @author Oleksandr Shkurko 5 | */ 6 | public class Sample { 7 | 8 | public static void main(String[] args) { 9 | User user = User.builder() 10 | .setId(42) 11 | .setName("Guest") 12 | .setAge(23) 13 | .build(); 14 | 15 | System.out.println(user); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/builder/User.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.builder; 2 | 3 | import java.util.StringJoiner; 4 | 5 | /** 6 | * @author Oleksandr Shkurko 7 | */ 8 | public class User { 9 | 10 | private Long id; 11 | private String name; 12 | private int age; 13 | 14 | private User(Long id, String name, int age) { 15 | this.id = id; 16 | this.name = name; 17 | this.age = age; 18 | } 19 | 20 | public static UserBuilderId builder() { 21 | return id -> name -> age -> () -> new User(id, name, age); 22 | } 23 | 24 | public Long getId() { 25 | return id; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public int getAge() { 33 | return age; 34 | } 35 | 36 | public interface UserBuilderId { 37 | UserBuilderName setId(long id); 38 | } 39 | 40 | public interface UserBuilderName { 41 | UserBuilderAge setName(String name); 42 | } 43 | 44 | public interface UserBuilderAge { 45 | UserBuilder setAge(int age); 46 | } 47 | 48 | public interface UserBuilder { 49 | User build(); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return new StringJoiner(", ", User.class.getSimpleName() + "[", "]") 55 | .add("id=" + id) 56 | .add("name='" + name + "'") 57 | .add("age=" + age) 58 | .toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/builder2/Sample.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.builder2; 2 | 3 | /** 4 | * @author Oleksandr Shkurko 5 | */ 6 | public class Sample { 7 | 8 | public static void main(String[] args) { 9 | User user = User.builder() 10 | .setId(1) 11 | .setName("guest") 12 | .setAge(18) 13 | .addRole("GUEST") 14 | .addRole("LOGIN") 15 | .build(); 16 | 17 | System.out.println(user); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/builder2/User.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.builder2; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.StringJoiner; 6 | 7 | import static java.util.Collections.unmodifiableSet; 8 | import static java.util.Objects.requireNonNull; 9 | 10 | /** 11 | * @author Oleksandr Shkurko 12 | */ 13 | public class User { 14 | private Long id; 15 | private String name; 16 | private int age; 17 | private Set roles; 18 | 19 | private User() { 20 | } 21 | 22 | public static UserBuilderId builder() { 23 | return id -> name -> age -> 24 | 25 | new UserBuilderRoles() { 26 | @Override 27 | public User build() { 28 | User user = new User(); 29 | user.id = id; 30 | user.name = requireNonNull(name); 31 | user.age = age; 32 | user.roles = unmodifiableSet(this.roles); 33 | return user; 34 | } 35 | }; 36 | } 37 | 38 | public Long getId() { 39 | return id; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public int getAge() { 47 | return age; 48 | } 49 | 50 | public Set getRoles() { 51 | return roles; 52 | } 53 | 54 | public interface UserBuilderId { 55 | UserBuilderName setId(long id); 56 | } 57 | 58 | public interface UserBuilderName { 59 | UserBuilderAge setName(String name); 60 | } 61 | 62 | public interface UserBuilderAge { 63 | UserBuilderRoles setAge(int age); 64 | } 65 | 66 | public static abstract class UserBuilderRoles { 67 | 68 | Set roles = new HashSet<>(); 69 | 70 | public UserBuilderRoles addRole(String role) { 71 | roles.add(requireNonNull(role)); 72 | return this; 73 | } 74 | 75 | public abstract User build(); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return new StringJoiner(", ", User.class.getSimpleName() + "[", "]") 81 | .add("id=" + id) 82 | .add("name='" + name + "'") 83 | .add("age=" + age) 84 | .add("roles=" + roles) 85 | .toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/chain/DigitCounter.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.chain; 2 | 3 | /** 4 | * @author Alimenkou Mikalai 5 | */ 6 | public interface DigitCounter { 7 | int count(String str); 8 | } 9 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/chain/LambdaChainSample.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.chain; 2 | 3 | import java.util.function.Function; 4 | 5 | /** 6 | * @author Alimenkou Mikalai 7 | */ 8 | public class LambdaChainSample { 9 | public static void main(String[] args) { 10 | int digitsCount = startWith(String::trim) 11 | .andThen(String::toUpperCase) 12 | .andThen(wrap(counter())::count) 13 | .apply(" \n 123 \t"); 14 | System.out.println(digitsCount + " digits found"); 15 | } 16 | 17 | private static DigitCounter counter() { 18 | return new NaiveDigitCounter(); 19 | } 20 | 21 | private static Function startWith(Function function) { 22 | return function; 23 | } 24 | 25 | private static DigitCounter wrap(DigitCounter counter) { 26 | return s -> { 27 | long startTime = System.currentTimeMillis(); 28 | int count = counter.count(s); 29 | long endTime = System.currentTimeMillis(); 30 | System.out.println("Counting took " + (endTime - startTime) + " ms"); 31 | return count; 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/chain/NaiveDigitCounter.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.chain; 2 | 3 | /** 4 | * @author Alimenkou Mikalai 5 | */ 6 | public class NaiveDigitCounter implements DigitCounter { 7 | @Override 8 | public int count(String str) { 9 | int count = 0; 10 | for (int i = 0; i < str.length(); i++) { 11 | char c = str.charAt(i); 12 | if (Character.isDigit(c)) { 13 | count++; 14 | } 15 | } 16 | return count; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/dsl/HtmlElement.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.dsl; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public interface HtmlElement { 6 | static void main(String[] args) { 7 | HtmlElement page = container("HTML", 8 | container("DIV", 9 | block("IMG", 100, 100), 10 | block("SPAN", 50, 20)), 11 | block("IMG", 75, 35) 12 | ); 13 | System.out.println(page.summary()); 14 | } 15 | 16 | static HtmlElement container(String tagName, HtmlElement... elements) { 17 | return new Container(tagName, elements); 18 | } 19 | 20 | static HtmlElement block(String tagName, int width, int height) { 21 | return new Block(tagName, width, height); 22 | } 23 | 24 | String getTagName(); 25 | 26 | int getWidth(); 27 | 28 | int getHeight(); 29 | 30 | default String summary() { 31 | return String.format("<%s>: w=%d, h=%d", getTagName(), getWidth(), getHeight()); 32 | } 33 | 34 | class Container implements HtmlElement { 35 | private final String tagName; 36 | private final HtmlElement[] elements; 37 | 38 | private Container(String tagName, HtmlElement[] elements) { 39 | this.tagName = tagName; 40 | this.elements = elements; 41 | } 42 | 43 | @Override 44 | public String getTagName() { 45 | return tagName; 46 | } 47 | 48 | @Override 49 | public int getWidth() { 50 | return Stream.of(elements) 51 | .mapToInt(HtmlElement::getWidth) 52 | .max() 53 | .orElse(0); 54 | } 55 | 56 | @Override 57 | public int getHeight() { 58 | return Stream.of(elements) 59 | .mapToInt(HtmlElement::getHeight) 60 | .sum(); 61 | } 62 | } 63 | 64 | class Block implements HtmlElement { 65 | private final String tagName; 66 | private final int width; 67 | private final int height; 68 | 69 | private Block(String tagName, int width, int height) { 70 | this.tagName = tagName; 71 | this.width = width; 72 | this.height = height; 73 | } 74 | 75 | @Override 76 | public String getTagName() { 77 | return tagName; 78 | } 79 | 80 | @Override 81 | public int getWidth() { 82 | return width; 83 | } 84 | 85 | @Override 86 | public int getHeight() { 87 | return height; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/visitor/Circle.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.visitor; 2 | 3 | /** 4 | * @author Alimenkou Mikalai 5 | */ 6 | class Circle { 7 | private final double radius; 8 | 9 | Circle(double radius) { 10 | this.radius = radius; 11 | } 12 | 13 | double getRadius() { 14 | return radius; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/visitor/LambdaVisitor.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.visitor; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.function.Function; 6 | 7 | /** 8 | * @author Alimenkou Mikalai 9 | */ 10 | public class LambdaVisitor implements Function { 11 | private final Map, Function> functions = new HashMap<>(); 12 | 13 | public Acceptor on(Class clazz) { 14 | return new Acceptor<>(this, clazz); 15 | } 16 | 17 | @Override 18 | public A apply(Object o) { 19 | return functions.get(o.getClass()).apply(o); 20 | } 21 | 22 | public static class Acceptor { 23 | private final LambdaVisitor visitor; 24 | private final Class clazz; 25 | 26 | public Acceptor(LambdaVisitor visitor, Class clazz) { 27 | this.visitor = visitor; 28 | this.clazz = clazz; 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | public LambdaVisitor then(Function f) { 33 | visitor.functions.put(clazz, f); 34 | return visitor; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/visitor/PatternMatchingSample.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.visitor; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.function.Function; 6 | 7 | /** 8 | * @author Alimenkou Mikalai 9 | */ 10 | public class PatternMatchingSample { 11 | private final static Function AREA_CALCULATOR = new LambdaVisitor() 12 | .on(Square.class).then(s -> s.getSide() * s.getSide()) 13 | .on(Circle.class).then(c -> Math.PI * c.getRadius() * c.getRadius()) 14 | .on(Rectangle.class).then(r -> r.getHeight() * r.getWidth()); 15 | 16 | private final static Function PERIMETER_CALCULATOR = new LambdaVisitor() 17 | .on(Square.class).then(s -> 4 * s.getSide()) 18 | .on(Circle.class).then(c -> 2 * Math.PI * c.getRadius()) 19 | .on(Rectangle.class).then(r -> 2 * r.getHeight() + 2 * r.getWidth()); 20 | 21 | public static void main(String[] args) { 22 | List figures = Arrays.asList(new Circle(4), new Square(5), new Rectangle(6, 7)); 23 | 24 | double totalArea = figures.stream() 25 | .map(AREA_CALCULATOR) 26 | .reduce(0.0, Double::sum); 27 | System.out.println("Total area = " + totalArea); 28 | 29 | double totalPerimeter = figures.stream() 30 | .map(PERIMETER_CALCULATOR) 31 | .reduce(0.0, Double::sum); 32 | System.out.println("Total perimeter = " + totalPerimeter); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/visitor/Rectangle.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.visitor; 2 | 3 | /** 4 | * @author Alimenkou Mikalai 5 | */ 6 | class Rectangle { 7 | private final double width; 8 | private final double height; 9 | 10 | Rectangle(double width, double height) { 11 | this.width = width; 12 | this.height = height; 13 | } 14 | 15 | double getWidth() { 16 | return width; 17 | } 18 | 19 | double getHeight() { 20 | return height; 21 | } 22 | } -------------------------------------------------------------------------------- /src/com/xpinjection/java8/good/visitor/Square.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.good.visitor; 2 | 3 | /** 4 | * @author Alimenkou Mikalai 5 | */ 6 | class Square { 7 | private final double side; 8 | 9 | Square(double side) { 10 | this.side = side; 11 | } 12 | 13 | double getSide() { 14 | return side; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/Annotations.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused; 2 | 3 | public class Annotations { 4 | public @interface Good{} 5 | public @interface Bad{} 6 | public @interface Ugly{} 7 | } 8 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/Permission.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused; 2 | 3 | public enum Permission { 4 | ADD, EDIT, SEARCH, DELETE 5 | } 6 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/Role.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused; 2 | 3 | import java.util.EnumSet; 4 | import java.util.Set; 5 | 6 | public class Role { 7 | private String name; 8 | private Set permissions = EnumSet.noneOf(Permission.class); 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public void setName(String name) { 15 | this.name = name; 16 | } 17 | 18 | public Set getPermissions() { 19 | return permissions; 20 | } 21 | 22 | public void setPermissions(Set permissions) { 23 | this.permissions = permissions; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | return name.equals(((Role) o).name); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return name.hashCode(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/User.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public class User { 7 | private Long id; 8 | private String name; 9 | private int age; 10 | private Set roles = new HashSet<>(); 11 | 12 | public User(long id, String name, int age) { 13 | this.id = id; 14 | this.name = name; 15 | this.age = age; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public int getAge() { 35 | return age; 36 | } 37 | 38 | public void setAge(int age) { 39 | this.age = age; 40 | } 41 | 42 | public Set getRoles() { 43 | return roles; 44 | } 45 | 46 | public void setRoles(Set roles) { 47 | this.roles = roles; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused; 2 | 3 | public class UserDto { 4 | private Long id; 5 | private String name; 6 | 7 | public Long getId() { 8 | return id; 9 | } 10 | 11 | public void setId(Long id) { 12 | this.id = id; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/AvoidComplexLambdas.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Permission; 6 | import com.xpinjection.java8.misused.Role; 7 | import com.xpinjection.java8.misused.User; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | import java.util.function.Predicate; 12 | 13 | import static java.util.stream.Collectors.toSet; 14 | 15 | public class AvoidComplexLambdas { 16 | private final Set users = new HashSet<>(); 17 | 18 | @Ugly 19 | class UsingComplexLambdaInPlace { 20 | public Set findEditors() { 21 | return users.stream() 22 | .filter(u -> u.getRoles().stream() 23 | .anyMatch(r -> r.getPermissions().contains(Permission.EDIT))) 24 | .collect(toSet()); 25 | } 26 | } 27 | 28 | @Good 29 | class ComplexityExtractedToMethodReference { 30 | public Set checkPermission(Permission permission) { 31 | return users.stream() 32 | //.filter(this::hasEditPermission) 33 | .filter(hasPermission(Permission.EDIT)) 34 | .collect(toSet()); 35 | } 36 | 37 | private Predicate hasPermission(Permission permission) { 38 | return user -> user.getRoles().stream() 39 | .map(Role::getPermissions) 40 | .anyMatch(permissions -> permissions.contains(permission)); 41 | } 42 | 43 | private boolean hasEditPermission(User user) { 44 | return hasPermission(Permission.EDIT).test(user); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/AvoidLongLambdas.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | import com.xpinjection.java8.misused.UserDto; 7 | 8 | import java.util.List; 9 | import java.util.function.Function; 10 | 11 | import static java.util.stream.Collectors.toList; 12 | 13 | public class AvoidLongLambdas { 14 | @Ugly 15 | class LongLambdaInPlace { 16 | public List convertToDto(List users){ 17 | return users.stream() 18 | .map(user -> { 19 | UserDto dto = new UserDto(); 20 | dto.setId(user.getId()); 21 | dto.setName(user.getName()); 22 | //it happens to be much more fields and much more logic in terms of remapping these fields 23 | return dto; 24 | }) 25 | .collect(toList()); 26 | } 27 | } 28 | 29 | @Good 30 | class MethodReferenceInsteadOfLambda { 31 | //particular toDto could be implemented as a separate class or as a lambda function 32 | private final Function toDto = this::convertToDto; 33 | 34 | public List convertToDto(List users){ 35 | return users.stream() 36 | .map(toDto) 37 | .collect(toList()); 38 | } 39 | 40 | private UserDto convertToDto(User user){ 41 | UserDto dto = new UserDto(); 42 | dto.setId(user.getId()); 43 | dto.setName(user.getName()); 44 | return dto; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/ClassDesign.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Bad; 4 | import com.xpinjection.java8.misused.Annotations.Good; 5 | 6 | import java.util.function.Function; 7 | import java.util.function.UnaryOperator; 8 | 9 | public class ClassDesign { 10 | @Bad 11 | static class AmbiguousOverloadedMethods { 12 | interface AmbiguousService { 13 | R process(Function fn); 14 | 15 | T process(UnaryOperator fn); 16 | } 17 | 18 | public void usage(AmbiguousService service) { 19 | //which method you intended to call??? both are acceptable. 20 | service.process(String::toUpperCase); 21 | } 22 | } 23 | 24 | @Good 25 | static class SeparateSpecializedMethods { 26 | interface ClearService { 27 | R convert(Function fn); 28 | 29 | T process(UnaryOperator fn); 30 | } 31 | 32 | public void usage(ClearService service) { 33 | //now it's clear which method will be called. 34 | service.convert(String::toUpperCase); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/LambdasAreNotAlwaysTheBestOption.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.util.Optional; 7 | 8 | public class LambdasAreNotAlwaysTheBestOption { 9 | @Ugly 10 | class UnneededLambdasUsage { 11 | public void processAndPrint(String name) { 12 | Optional.ofNullable(name) 13 | //.filter(s -> !s.isEmpty()) 14 | .map(s -> s.toUpperCase()) 15 | .map(s -> doProcess(s)) 16 | .ifPresent(s -> System.out.print(s)); 17 | } 18 | 19 | private String doProcess(String name) { 20 | return "MR. " + name; 21 | } 22 | } 23 | 24 | @Good 25 | class MethodReferenceUsage { 26 | public void processAndPrint(String name) { 27 | Optional.ofNullable(name) 28 | //.filter(StringUtils::isNotEmpty) // replace with appropriate library method ref 29 | .map(String::toUpperCase) 30 | .map(this::doProcess) 31 | .ifPresent(System.out::print); 32 | } 33 | 34 | private String doProcess(String name) { 35 | return "MR. " + name; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/LazyCalculationsImprovePerformance.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.Set; 8 | import java.util.function.Supplier; 9 | 10 | public class LazyCalculationsImprovePerformance { 11 | @Ugly 12 | static class LoggingWithAdditionalCheckToAvoidCalculations { 13 | private static final Log LOG = null; // init logger with factory 14 | 15 | public void sendWelcomeEmailToUsers(Set users) { 16 | // send email 17 | if (LOG.isDebugEnabled()) { 18 | LOG.debug("Emails have been sent for users: " + users); 19 | } 20 | } 21 | 22 | interface Log { 23 | void debug(String message); 24 | 25 | boolean isDebugEnabled(); 26 | } 27 | } 28 | 29 | @Good 30 | static class PassLambdaToLazyCalculateValueForLogMessage { 31 | private static final Log LOG = null; // init logger with factory 32 | 33 | public void sendWelcomeEmailToUsers(Set users) { 34 | // send email 35 | LOG.debug(() -> "Emails have been sent for users: " + users); 36 | } 37 | 38 | interface Log { 39 | void debug(String message); 40 | 41 | boolean isDebugEnabled(); 42 | 43 | default void debug(Supplier message) { 44 | if (isDebugEnabled()) { 45 | debug(message.get()); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/collections/EmulateMultimap.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda.collections; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.*; 8 | 9 | public class EmulateMultimap { 10 | private final Map> usersByRole = new HashMap<>(); 11 | 12 | @Ugly 13 | class ManuallyInsertSetOnFirstValueForTheKey { 14 | public void addUser(User user) { 15 | user.getRoles().forEach(r -> { 16 | Set usersInRole = usersByRole.get(r.getName()); 17 | if (usersInRole == null) { 18 | usersInRole = new HashSet<>(); 19 | usersByRole.put(r.getName(), usersInRole); 20 | } 21 | usersInRole.add(user); 22 | }); 23 | } 24 | 25 | public Set getUsersInRole(String role) { 26 | Set users = usersByRole.get(role); 27 | return users == null ? Collections.emptySet() : users; 28 | } 29 | } 30 | 31 | @Good 32 | class ComputeEmptySetIfKeyIsAbsent { 33 | public void addUser(User user) { 34 | user.getRoles().forEach(r -> usersByRole 35 | .computeIfAbsent(r.getName(), k -> new HashSet<>()) 36 | .add(user)); 37 | } 38 | 39 | public Set getUsersInRole(String role) { 40 | return usersByRole.getOrDefault(role, Collections.emptySet()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/collections/ListSorting.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda.collections; 2 | 3 | import com.xpinjection.java8.misused.User; 4 | import com.xpinjection.java8.misused.Annotations.Good; 5 | import com.xpinjection.java8.misused.Annotations.Ugly; 6 | 7 | import java.util.List; 8 | 9 | import static java.util.Comparator.comparing; 10 | 11 | public class ListSorting { 12 | @Ugly 13 | class UsingCustomComparator { 14 | public void sortUsersById(List users) { 15 | users.sort((x, y) -> Long.compare(x.getId(), y.getId())); 16 | } 17 | } 18 | 19 | @Good 20 | class UsingExistingPredefinedComparator { 21 | public void sortUsersById(List users) { 22 | users.sort(comparing(User::getId)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/collections/MapIterating.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda.collections; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import static java.util.stream.Collectors.toMap; 11 | 12 | public class MapIterating { 13 | @Ugly 14 | class UsingOldGoodEntrySet { 15 | public Map getUserNames(Map users) { 16 | Map userNames = new HashMap<>(); 17 | users.entrySet().forEach(user -> 18 | userNames.put(user.getKey(), user.getValue().getName())); 19 | return userNames; 20 | } 21 | } 22 | 23 | @Good 24 | class UsingMapForEach { 25 | public Map getUserNames(Map users) { 26 | Map userNames = new HashMap<>(); 27 | users.forEach((key, value) -> userNames.put(key, value.getName())); 28 | return userNames; 29 | } 30 | } 31 | 32 | @Good 33 | class UsingMapTransform { 34 | public Map getUserNames(Map users) { 35 | return users.entrySet().stream() 36 | .collect(toMap(Map.Entry::getKey, 37 | entry -> entry.getValue().getName())); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/lambda/collections/RemoveElementWithIterator.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.lambda.collections; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Permission; 6 | import com.xpinjection.java8.misused.User; 7 | 8 | import java.util.HashSet; 9 | import java.util.Iterator; 10 | import java.util.Set; 11 | 12 | public class RemoveElementWithIterator { 13 | private final Set users = new HashSet<>(); 14 | 15 | @Ugly 16 | class ManuallyRemoveElementWithIteratorRemove { 17 | public void removeUsersWithPermission(Permission permission) { 18 | Iterator iterator = users.iterator(); 19 | while (iterator.hasNext()) { 20 | User user = iterator.next(); 21 | if (user.getRoles().stream() 22 | .anyMatch(r -> r.getPermissions().contains(permission))) { 23 | iterator.remove(); 24 | } 25 | } 26 | } 27 | } 28 | 29 | @Good 30 | class RemoveWithPredicate { 31 | public void removeUsersWithPermission(Permission permission) { 32 | users.removeIf(user -> user.getRoles().stream() 33 | .anyMatch(r -> r.getPermissions().contains(permission))); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/HundredAndOneApproach.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Bad; 4 | import com.xpinjection.java8.misused.Annotations.Good; 5 | import com.xpinjection.java8.misused.Annotations.Ugly; 6 | 7 | import java.util.Optional; 8 | 9 | import static java.util.Optional.empty; 10 | import static java.util.Optional.ofNullable; 11 | 12 | public class HundredAndOneApproach { 13 | @Ugly 14 | class SameOldImperativeStyle { 15 | public String getPersonCarInsuranceName(Person person) { 16 | String name = "Unknown"; 17 | if (ofNullable(person).isPresent()) { 18 | if (person.getCar().isPresent()) { 19 | if (person.getCar().get().getInsurance().isPresent()) { 20 | name = person.getCar().get().getInsurance().get().getName(); 21 | } 22 | } 23 | } 24 | return name; 25 | } 26 | } 27 | 28 | @Ugly 29 | class UsingIfPresentInSameImperativeWayWithDirtyHack { 30 | public String getPersonCarInsuranceName(Person person) { 31 | final StringBuilder builder = new StringBuilder(); 32 | ofNullable(person).ifPresent( 33 | p -> p.getCar().ifPresent( 34 | c -> c.getInsurance().ifPresent( 35 | i -> builder.append(i.getName()) 36 | ) 37 | ) 38 | ); 39 | return builder.toString(); 40 | } 41 | } 42 | 43 | @Bad 44 | class UsingMapWithUncheckedGet { 45 | public String getPersonCarInsuranceName(Person person) { 46 | return ofNullable(person) 47 | .map(Person::getCar) 48 | .map(car -> car.get().getInsurance()) 49 | .map(insurance -> insurance.get().getName()) 50 | .orElse("Unknown"); 51 | } 52 | } 53 | 54 | @Ugly 55 | class UsingMapWithOrElseEmptyObjectToFixUncheckedGet { 56 | public String getPersonCarInsuranceName(Person person) { 57 | return ofNullable(person) 58 | .map(Person::getCar) 59 | .map(car -> car.orElseGet(Car::new).getInsurance()) 60 | .map(insurance -> insurance.orElseGet(Insurance::new).getName()) 61 | .orElse("Unknown"); 62 | } 63 | } 64 | 65 | @Good 66 | class UsingFlatMap { 67 | public String getCarInsuranceNameFromPersonUsingFlatMap(Person person) { 68 | return ofNullable(person) 69 | .flatMap(Person::getCar) 70 | .flatMap(Car::getInsurance) 71 | .map(Insurance::getName) 72 | .orElse("Unknown"); 73 | } 74 | } 75 | 76 | class Person { 77 | Optional getCar() { 78 | return empty(); //stub 79 | } 80 | } 81 | 82 | class Car { 83 | Optional getInsurance() { 84 | return empty(); //stub 85 | } 86 | } 87 | 88 | class Insurance { 89 | String getName() { 90 | return ""; //stub 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/IfStatementIsNotAlwaysBadThing.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.util.Optional; 7 | import java.util.stream.Stream; 8 | 9 | public class IfStatementIsNotAlwaysBadThing { 10 | @Ugly 11 | class CombineSomeOptionalsInCleverWay { 12 | public Optional sum(Optional first, Optional second) { 13 | return Stream.of(first, second) 14 | .filter(Optional::isPresent) 15 | .map(Optional::get) 16 | .reduce(Integer::sum); 17 | } 18 | } 19 | 20 | @Ugly 21 | class PlayMapGameInEvenMoreCleverWay { 22 | public Optional sum(Optional first, Optional second) { 23 | return first.map(b -> second.map(a -> b + a).orElse(b)) 24 | .map(Optional::of) 25 | .orElse(second); 26 | } 27 | } 28 | 29 | @Good 30 | class OldSchoolButTotallyClearCode { 31 | public Optional sum(Optional first, Optional second) { 32 | if (!first.isPresent() && !second.isPresent()) { 33 | return Optional.empty(); 34 | } 35 | return Optional.of(first.orElse(0) + second.orElse(0)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/OptionalElvis.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import static java.util.Optional.ofNullable; 8 | 9 | public class OptionalElvis { 10 | @Ugly 11 | class BeforeJava8 { 12 | public String getUserName(User user) { 13 | return (user != null && user.getName() != null) ? user.getName() : "default"; 14 | } 15 | } 16 | 17 | @Ugly 18 | class UsingOptionalIsPresent { 19 | public String getUserName(User user) { 20 | if (ofNullable(user).isPresent()) { 21 | if (ofNullable(user.getName()).isPresent()) { 22 | return user.getName(); 23 | } 24 | } 25 | return "default"; 26 | } 27 | } 28 | 29 | @Good 30 | class UsingOrElse { 31 | String getUserName(User user) { 32 | return ofNullable(user) 33 | .map(User::getName) 34 | .orElse("default"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/OptionalOverEngineering.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Role; 6 | 7 | import java.util.Optional; 8 | 9 | public class OptionalOverEngineering { 10 | @Ugly 11 | class NullProtectionOverEngineering { 12 | public Role copyRole(Role role) { 13 | Role copy = new Role(); 14 | 15 | Optional.ofNullable(role.getName()) 16 | .ifPresent(copy::setName); 17 | copy.setPermissions(role.getPermissions()); 18 | return copy; 19 | } 20 | } 21 | 22 | @Good 23 | class SimpleConditionalCopying { 24 | public Role copyRole(Role role) { 25 | Role copy = new Role(); 26 | 27 | if (role.getName() != null) { 28 | copy.setName(role.getName()); 29 | } 30 | copy.setPermissions(role.getPermissions()); 31 | return copy; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/StrictCheckOfValuePresence.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.Optional; 8 | 9 | public class StrictCheckOfValuePresence { 10 | @Ugly 11 | class ManualCheckForPresenceToThrowException { 12 | public String getUserName(Long userId) { 13 | Optional user = findById(userId); 14 | if (user.isPresent()) { 15 | return user.get().getName(); 16 | } 17 | throw new IllegalStateException("User not found"); 18 | } 19 | 20 | public void deleteUser(Long userId) { 21 | Optional user = findById(userId); 22 | if (user.isPresent()) { 23 | delete(user.get()); 24 | } 25 | } 26 | 27 | private void delete(User user) { 28 | //delete from DB 29 | } 30 | } 31 | 32 | @Good 33 | class OrElseThrowUsage { 34 | public String getUserName(Long userId) { 35 | return findById(userId) 36 | .orElseThrow(() -> new IllegalStateException("User not found")) 37 | .getName(); 38 | } 39 | 40 | public void deleteUser(Long userId) { 41 | findById(userId).ifPresent(this::delete); 42 | } 43 | 44 | private void delete(User user) { 45 | //delete from DB 46 | } 47 | } 48 | 49 | private Optional findById(Long userId) { 50 | //search in DB 51 | return Optional.of(new User(5L, "Mikalai", 33)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/usage/InternalOptionalUsage.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional.usage; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.Optional; 8 | 9 | public class InternalOptionalUsage { 10 | @Ugly 11 | class UnclearOptionalDependencyWithCheckForNull { 12 | private Printer printer; 13 | 14 | public void process(User user) { 15 | //some processing 16 | if (printer != null) { 17 | printer.print(user); 18 | } 19 | } 20 | 21 | public void setPrinter(Printer printer) { 22 | this.printer = printer; 23 | } 24 | } 25 | 26 | @Good 27 | class ValidInternalOptionalDependency { 28 | private Optional printer = Optional.empty(); 29 | 30 | public void process(User user) { 31 | //some processing 32 | printer.ifPresent(p -> p.print(user)); 33 | } 34 | 35 | public void setPrinter(Printer printer) { 36 | this.printer = Optional.ofNullable(printer); 37 | } 38 | } 39 | 40 | interface Printer { 41 | void print(User user); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/usage/OptionalConstructorParameters.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional.usage; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.io.Serializable; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public class OptionalConstructorParameters { 12 | @Ugly 13 | class OptionalLeaksOutsideClass { 14 | public List create() { 15 | Email noAttachment = new Email("First!", "No attachment", Optional.empty()); 16 | Attachment attachment = new Attachment("/mnt/files/image.png", 370); 17 | Email withAttachment = new Email("Second!", "With attachment", Optional.of(attachment)); 18 | return Arrays.asList(noAttachment, withAttachment); 19 | } 20 | 21 | class Email implements Serializable { 22 | private final String subject; 23 | private final String body; 24 | private final Optional attachment; 25 | 26 | Email(String subject, String body, Optional attachment) { 27 | this.subject = subject; 28 | this.body = body; 29 | this.attachment = attachment; 30 | } 31 | 32 | String getSubject() { 33 | return subject; 34 | } 35 | 36 | String getBody() { 37 | return body; 38 | } 39 | 40 | Optional getAttachment() { 41 | return attachment; 42 | } 43 | } 44 | } 45 | 46 | @Good 47 | class OverloadedConstructors { 48 | public List create() { 49 | Email noAttachment = new Email("First!", "No attachment"); 50 | Attachment attachment = new Attachment("/mnt/files/image.png", 370); 51 | Email withAttachment = new Email("Second!", "With attachment", attachment); 52 | return Arrays.asList(noAttachment, withAttachment); 53 | } 54 | 55 | class Email implements Serializable { 56 | private final String subject; 57 | private final String body; 58 | private final Attachment attachment; 59 | 60 | Email(String subject, String body, Attachment attachment) { 61 | this.subject = subject; 62 | this.body = body; 63 | this.attachment = attachment; 64 | } 65 | 66 | Email(String subject, String body) { 67 | this(subject, body, null); 68 | } 69 | 70 | String getSubject() { 71 | return subject; 72 | } 73 | 74 | String getBody() { 75 | return body; 76 | } 77 | 78 | boolean hasAttachment() { 79 | return attachment != null; 80 | } 81 | 82 | Attachment getAttachment() { 83 | return attachment; 84 | } 85 | } 86 | } 87 | 88 | class Attachment { 89 | private final String path; 90 | private final int size; 91 | 92 | Attachment(String path, int size) { 93 | this.path = path; 94 | this.size = size; 95 | } 96 | 97 | String getPath() { 98 | return path; 99 | } 100 | 101 | int getSize() { 102 | return size; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/optional/usage/OptionalForCollections.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.optional.usage; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public class OptionalForCollections { 12 | private static final String ADMIN_ROLE = "admin"; 13 | 14 | @Ugly 15 | class TooVerbose { 16 | public User findAnyAdmin() { 17 | Optional> users = findUsersByRole(ADMIN_ROLE); 18 | if (users.isPresent() && !users.get().isEmpty()) { 19 | return users.get().get(0); 20 | } 21 | throw new IllegalStateException("No admins found"); 22 | } 23 | 24 | private Optional> findUsersByRole(String role) { 25 | //real search in DB 26 | return Optional.empty(); 27 | } 28 | } 29 | 30 | @Good 31 | class NiceAndClean { 32 | public User findAnyAdmin() { 33 | return findUsersByRole(ADMIN_ROLE).stream() 34 | .findAny() 35 | .orElseThrow(() -> new IllegalStateException("No admins found")); 36 | } 37 | 38 | private List findUsersByRole(String role) { 39 | //real search in DB 40 | return Collections.emptyList(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/CreationOptions.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Permission; 6 | import com.xpinjection.java8.misused.Role; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.stream.IntStream; 11 | import java.util.stream.Stream; 12 | 13 | public class CreationOptions { 14 | @Ugly 15 | public Stream getStreamFromList() { 16 | return Arrays.asList(Permission.ADD, Permission.DELETE).stream(); 17 | } 18 | 19 | @Good 20 | public Stream getStreamFromElements() { 21 | return Stream.of(Permission.ADD, Permission.DELETE); 22 | } 23 | 24 | @Ugly 25 | public Stream generateStreamByMappingCopies(int n) { 26 | return Collections.nCopies(n, "ignored").stream() 27 | .map(s -> new Role()); 28 | } 29 | 30 | @Ugly 31 | public Stream generateStreamFromRange(int n) { 32 | return IntStream.range(0, n).mapToObj(i -> new Role()); 33 | } 34 | 35 | @Good 36 | public Stream generateStreamFromSupplierWithLimit(int n) { 37 | return Stream.generate(Role::new).limit(n); 38 | } 39 | 40 | @Ugly 41 | public Stream generateStreamFromArrayWithRange(Role[] roles, int max) { 42 | int to = Integer.min(roles.length, max); 43 | return IntStream.range(0, to).mapToObj(i -> roles[i]); 44 | } 45 | 46 | @Good 47 | public Stream generateStreamFromArrayWithLimit(Role[] roles, int max) { 48 | return Stream.of(roles).limit(max); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/DoNotNeglectDataStructures.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.util.*; 7 | 8 | import static java.util.stream.Collectors.toList; 9 | 10 | public class DoNotNeglectDataStructures { 11 | @Ugly 12 | class UnnecessaryUseOfNestedStreamOperations { 13 | public List filterOrdersByStatuses(List orders, Set appropriateStatuses) { 14 | return orders.stream() 15 | .filter(order -> 16 | appropriateStatuses.stream().anyMatch(order.getStatus()::equals)) 17 | .collect(toList()); 18 | } 19 | } 20 | 21 | @Good 22 | class UseOfDataStructure { 23 | public List filterOrdersByStatuses(List orders, Set appropriateStatuses) { 24 | return orders.stream() 25 | .filter(order -> appropriateStatuses.contains(order.getStatus())) 26 | .collect(toList()); 27 | } 28 | } 29 | 30 | @Ugly 31 | class StateIsStoredInBadDataStructure { 32 | private final List orders = new ArrayList<>(); 33 | 34 | public void placeOrder(Order order) { 35 | orders.add(order); 36 | } 37 | 38 | public List getOrdersInStatus(Status status) { 39 | return orders.stream() 40 | .filter(order -> order.getStatus() == status) 41 | .collect(toList()); 42 | } 43 | } 44 | 45 | @Good 46 | class InternalDataStructureMayBeOptimizedForAccessMethods { 47 | //Use multimap instead from external collections like Guava 48 | private final Map> orders = new EnumMap<>(Status.class); 49 | 50 | public void placeOrder(Order order) { 51 | orders.computeIfAbsent(order.getStatus(), status -> new ArrayList<>()).add(order); 52 | } 53 | 54 | public List getOrdersInStatus(Status status) { 55 | return orders.get(status); 56 | } 57 | } 58 | 59 | class Order { 60 | private Status status = Status.ACTIVE; 61 | 62 | Status getStatus() { 63 | return status; 64 | } 65 | 66 | void setStatus(Status status) { 67 | this.status = status; 68 | } 69 | } 70 | 71 | enum Status { 72 | ACTIVE, SUSPENDED, CLOSED 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/ImperativeCodeMix.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Permission; 6 | import com.xpinjection.java8.misused.Role; 7 | import com.xpinjection.java8.misused.User; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | public class ImperativeCodeMix { 14 | private static final String ADMIN_ROLE = "admin"; 15 | 16 | private final List users = new ArrayList<>(); 17 | 18 | @Ugly 19 | class TooVerboseMixOfStreamOperationsAndImperativeCode { 20 | public boolean hasAdmin() { 21 | return users.stream() 22 | .map(u -> { 23 | if (u == null) { 24 | throw new NullPointerException(); 25 | } 26 | return u; 27 | }) 28 | .flatMap(u -> u.getRoles().stream()) 29 | .map(Role::getName) 30 | .anyMatch(name -> ADMIN_ROLE.equals(name)); 31 | } 32 | } 33 | 34 | @Good 35 | class NiceAndCleanStreamOperationsChain { 36 | public boolean hasAdmin(Permission permission) { 37 | return users.stream() 38 | .map(Objects::requireNonNull) 39 | .flatMap(u -> u.getRoles().stream()) 40 | .map(Role::getName) 41 | .anyMatch(ADMIN_ROLE::equals); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/MatchElementInFunctionalStyle.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Permission; 6 | import com.xpinjection.java8.misused.User; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | 12 | public class MatchElementInFunctionalStyle { 13 | private final Set users = new HashSet<>(); 14 | 15 | @Ugly 16 | class UseOldSchoolIterationsWithForEachAndExternalBoolean { 17 | public boolean checkPermission(Permission permission) { 18 | AtomicBoolean found = new AtomicBoolean(); 19 | users.forEach( 20 | u -> u.getRoles().forEach( 21 | r -> { 22 | if (r.getPermissions().contains(permission)) { 23 | found.set(true); 24 | } 25 | } 26 | ) 27 | ); 28 | return found.get(); 29 | } 30 | } 31 | 32 | @Ugly 33 | class TryToUseFunctionalStyleWithStreamFilter { 34 | public boolean checkPermission(Permission permission) { 35 | return users.stream().filter( 36 | u -> u.getRoles().stream() 37 | .filter(r -> r.getPermissions().contains(permission)) 38 | .count() > 0) 39 | .findFirst().isPresent(); 40 | } 41 | } 42 | 43 | @Ugly 44 | class TryToUseStreamMatching { 45 | public boolean checkPermission(Permission permission) { 46 | return users.stream() 47 | .anyMatch(u -> u.getRoles().stream() 48 | .anyMatch(r -> r.getPermissions().contains(permission))); 49 | } 50 | } 51 | 52 | @Good 53 | class UseFlatMapForSubCollections { 54 | public boolean checkPermission(Permission permission) { 55 | return users.stream() 56 | .flatMap(u -> u.getRoles().stream()) 57 | .anyMatch(r -> r.getPermissions().contains(permission)); 58 | } 59 | } 60 | 61 | @Good 62 | class UseFlatMapWithMethodReferencesForSubCollections { 63 | public boolean checkPermission(Permission permission) { 64 | return users.stream() 65 | .map(User::getRoles) 66 | .flatMap(Set::stream) 67 | .anyMatch(r -> r.getPermissions().contains(permission)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/NestedForEach.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | import static java.util.stream.Collectors.toSet; 12 | 13 | public class NestedForEach { 14 | @Ugly 15 | class NestedForEachWithExternalCollection { 16 | public Set retrievePromoRuleNames(List transactions) { 17 | Set ruleNamesWithPromo = new HashSet<>(); 18 | transactions.forEach(transaction -> transaction.getRules().stream() 19 | .filter(BusinessRule::isPromotion) 20 | .forEach(rule -> ruleNamesWithPromo.add(rule.getRuleName()))); 21 | return ruleNamesWithPromo; 22 | } 23 | } 24 | 25 | @Good 26 | class StreamOperationsChain { 27 | public Set retrievePromoRuleNames(List transactions) { 28 | return transactions.stream() 29 | .flatMap(t -> t.getRules().stream()) 30 | .filter(BusinessRule::isPromotion) 31 | .map(BusinessRule::getRuleName) 32 | .collect(toSet()); 33 | } 34 | } 35 | 36 | class BusinessTransaction { 37 | List getRules() { 38 | return new ArrayList<>(); //stub 39 | } 40 | } 41 | 42 | class BusinessRule { 43 | String getRuleName() { 44 | return ""; //stub 45 | } 46 | 47 | boolean isPromotion() { 48 | return false; //stub 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/PreferSpecializedStreams.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class PreferSpecializedStreams { 13 | private final Set users = new HashSet<>(); 14 | 15 | @Ugly 16 | class GeneralStreamUsage { 17 | public int getTotalAge() { 18 | return users.stream() 19 | .map(User::getAge) 20 | .reduce(0, Integer::sum); 21 | } 22 | } 23 | 24 | @Good 25 | class SpecializedStreamUsage { 26 | public int getTotalAge() { 27 | return users.stream() 28 | .mapToInt(User::getAge) 29 | .sum(); 30 | } 31 | } 32 | 33 | @Ugly 34 | class FlatMapToCountElementsInAllCollections { 35 | public int countEmployees(Map> departments) { 36 | return (int) departments.values().stream() 37 | .flatMap(List::stream) 38 | .count(); 39 | } 40 | } 41 | 42 | @Good 43 | class MapToIntToSimplifyCalculation { 44 | public long countEmployees(Map> departments) { 45 | return departments.values().stream() 46 | .mapToInt(List::size) 47 | .sum(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/RichDomainModel.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Role; 6 | import com.xpinjection.java8.misused.User; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class RichDomainModel { 12 | @Ugly 13 | class PoorDomainModelCausesComplexDataAccessCode { 14 | private final List users = new ArrayList<>(); 15 | 16 | public User findUserInRole(String roleName) { 17 | for (User user : users) { 18 | for (Role role : user.getRoles()) { 19 | if (roleName.equals(role.getName())) { 20 | return user; 21 | } 22 | } 23 | } 24 | return null; 25 | } 26 | } 27 | 28 | @Ugly 29 | class StreamVersionLooksNotMuchBetter { 30 | private final List users = new ArrayList<>(); 31 | 32 | public User findUserInRole(String roleName) { 33 | return users.stream().filter(user -> user.getRoles().stream() 34 | .map(Role::getName) 35 | .anyMatch(roleName::equals)) 36 | .findAny() 37 | .orElse(null); 38 | } 39 | } 40 | 41 | @Good 42 | class RichDomainModelCouldSimplifyAccessCode { 43 | private final List users = new ArrayList<>(); 44 | 45 | public User findUserInRole(String roleName) { 46 | return users.stream() 47 | .filter(user -> user.hasRole(roleName)) 48 | .findAny() 49 | .orElse(null); 50 | } 51 | 52 | class BetterUser extends User { 53 | BetterUser(long id, String name, int age) { 54 | super(id, name, age); 55 | } 56 | 57 | boolean hasRole(String roleName) { 58 | return getRoles().stream() 59 | .map(Role::getName) 60 | .anyMatch(roleName::equals); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/SameOldCodeStyleWithNewConstructs.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.User; 4 | import com.xpinjection.java8.misused.Annotations.Good; 5 | import com.xpinjection.java8.misused.Annotations.Ugly; 6 | 7 | import java.util.Collection; 8 | import java.util.Objects; 9 | 10 | import static java.util.Optional.ofNullable; 11 | 12 | public class SameOldCodeStyleWithNewConstructs { 13 | @Ugly 14 | class NoMoreThanSameOldLoopWithIf { 15 | public void registerUsers(Collection users) { 16 | users.stream().forEach(user -> 17 | ofNullable(user).ifPresent(u -> { 18 | //register user 19 | }) 20 | ); 21 | } 22 | } 23 | 24 | @Good 25 | class NewStreamStyleWithMethodReference { 26 | public void registerUsers(Collection users) { 27 | users.stream() 28 | .filter(Objects::nonNull) 29 | .forEach(this::registerUser); 30 | } 31 | 32 | private void registerUser(User user){ 33 | //register user 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/SkipAndLimitOnListIsWaste.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.User; 4 | 5 | import java.util.List; 6 | 7 | import static com.xpinjection.java8.misused.Annotations.Good; 8 | import static com.xpinjection.java8.misused.Annotations.Ugly; 9 | 10 | public class SkipAndLimitOnListIsWaste { 11 | @Ugly 12 | class SkipSomeElementsAndThenTakeSomeForProcessing { 13 | public void registerUsers(List users) { 14 | users.stream().skip(5).limit(10) 15 | .forEach(SkipAndLimitOnListIsWaste.this::registerUser); 16 | } 17 | } 18 | 19 | @Good 20 | class SublistDoNotWasteProcessingTime { 21 | public void registerUsers(List users) { 22 | users.subList(5, 15) 23 | .forEach(SkipAndLimitOnListIsWaste.this::registerUser); 24 | } 25 | } 26 | 27 | private void registerUser(User user) { 28 | //register user 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/UntypedStreamsCouldBeConverted.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.util.List; 7 | 8 | public class UntypedStreamsCouldBeConverted { 9 | @Ugly 10 | class ProcessOnlyValuesOfSpecialType { 11 | public int countDoubleNaNs(List numbers) { 12 | int count = 0; 13 | for (Object e : numbers) { 14 | if (e instanceof Double) { 15 | Double d = (Double) e; 16 | if (d.isNaN()) { 17 | count++; 18 | } 19 | } 20 | } 21 | return count; 22 | } 23 | } 24 | 25 | @Good 26 | class TypeOfStreamCouldBeChanged { 27 | public int countDoubleNaNs(List numbers) { 28 | return (int) numbers.stream() 29 | .filter(Double.class::isInstance) 30 | .mapToDouble(Double.class::cast) 31 | .filter(Double::isNaN) 32 | .count(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/WantToUseStreamsEverywhere.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.util.AbstractMap; 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.stream.Stream; 11 | 12 | import static java.util.stream.Collectors.collectingAndThen; 13 | import static java.util.stream.Collectors.toMap; 14 | 15 | public class WantToUseStreamsEverywhere { 16 | @Ugly 17 | class UseStreamToBuildMap { 18 | public Map getJpaProperties() { 19 | return Stream.of( 20 | new AbstractMap.SimpleEntry<>("hibernate.show_sql", "true"), 21 | new AbstractMap.SimpleEntry<>("hibernate.format_sql", "true") 22 | ).collect(collectingAndThen( 23 | toMap(Map.Entry::getKey, Map.Entry::getValue), 24 | Collections::unmodifiableMap) 25 | ); 26 | } 27 | } 28 | 29 | @Good 30 | class UseOldPlainMap { 31 | public Map getJpaProperties() { 32 | Map properties = new HashMap<>(); 33 | properties.put("hibernate.show_sql", "true"); 34 | properties.put("hibernate.format_sql", "true"); 35 | return Collections.unmodifiableMap(properties); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/collectors/AvoidLoopsInStreams.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.collectors; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | public class AvoidLoopsInStreams { 12 | private final Set users = new HashSet<>(); 13 | 14 | @Ugly 15 | class UseExternalCounter { 16 | public double countAverageRolesPerUser() { 17 | if (users.isEmpty()) { 18 | return 0; 19 | } 20 | AtomicInteger totalCount = new AtomicInteger(); 21 | users.forEach(u -> totalCount.addAndGet(u.getRoles().size())); 22 | return totalCount.doubleValue() / users.size(); 23 | } 24 | } 25 | 26 | @Good 27 | class ApplyMappingsToTargetType { 28 | public double countAverageRolesPerUser() { 29 | return users.stream() 30 | .mapToDouble(u -> u.getRoles().size()) 31 | .average() 32 | .orElse(0); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/collectors/CollectorsChain.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.collectors; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static java.util.Comparator.comparing; 11 | import static java.util.stream.Collectors.*; 12 | 13 | public class CollectorsChain { 14 | @Ugly 15 | class GroupByAndTransformResultingMap { 16 | public Map getMaxAgeByUserName(List users) { 17 | return users.stream() 18 | .collect(groupingBy(User::getName)) 19 | .entrySet().stream() 20 | .collect(toMap( 21 | Map.Entry::getKey, 22 | e -> e.getValue().stream() 23 | .map(User::getAge) 24 | .reduce(0, Integer::max) 25 | )); 26 | } 27 | } 28 | 29 | @Ugly 30 | class GroupByWithMaxCollectorUnwrappingOptionalWithFinisher { 31 | public Map getMaxAgeByUserName(List users) { 32 | return users.stream().collect(groupingBy(User::getName, 33 | collectingAndThen(maxBy(comparing(User::getAge)), 34 | user -> user.get().getAge()))); 35 | } 36 | } 37 | 38 | @Good 39 | class CollectToMapWithMergeFunction { 40 | public Map getMaxAgeByUserName(List users) { 41 | return users.stream() 42 | .collect(toMap(User::getName, 43 | User::getAge, 44 | Integer::max)); 45 | } 46 | } 47 | 48 | @Good 49 | class ApplyReduceCollectorAsDownstream { 50 | public Map getMaxAgeByUserName(List users) { 51 | return users.stream() 52 | .collect(groupingBy(User::getName, 53 | mapping(User::getAge, 54 | reducing(0, Integer::max)))); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/collectors/ExternalCollectionForGrouping.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.collectors; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.Permission; 6 | import com.xpinjection.java8.misused.User; 7 | 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import static java.util.stream.Collectors.*; 14 | 15 | public class ExternalCollectionForGrouping { 16 | private final Set users = new HashSet<>(); 17 | 18 | @Ugly 19 | class ExternalStateIsUsedForStreamOperations { 20 | public Map> findEditors() { 21 | Map> editors = new HashMap<>(); 22 | users.forEach(u -> u.getRoles().stream() 23 | .filter(r -> r.getPermissions().contains(Permission.EDIT)) 24 | .forEach(r -> { 25 | //is it better to use Multiset and avoid this complex code 26 | Set usersInRole = editors.get(r.getName()); 27 | if (usersInRole == null) { 28 | usersInRole = new HashSet<>(); 29 | editors.put(r.getName(), usersInRole); 30 | } 31 | usersInRole.add(u); 32 | }) 33 | ); 34 | return editors; 35 | } 36 | } 37 | 38 | @Good 39 | class TuplesAreUsedWhenStateIsNeededOnLaterPhase { 40 | public Map> findEditors() { 41 | return users.stream() 42 | .flatMap(u -> u.getRoles().stream() 43 | .filter(r -> r.getPermissions().contains(Permission.EDIT)) 44 | .map(r -> new Pair<>(r, u)) 45 | ).collect(groupingBy(p -> p.getKey().getName(), 46 | mapping(Pair::getValue, toSet()))); 47 | } 48 | } 49 | 50 | //any tuple implementation from 3rd party libraries 51 | class Pair { 52 | private final K key; 53 | private final V value; 54 | 55 | Pair(K key, V value) { 56 | this.key = key; 57 | this.value = value; 58 | } 59 | 60 | K getKey() { 61 | return key; 62 | } 63 | 64 | V getValue() { 65 | return value; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/collectors/StatisticsCalculation.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.collectors; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.IntSummaryStatistics; 8 | import java.util.List; 9 | import java.util.stream.IntStream; 10 | 11 | import static java.util.stream.Collectors.summarizingInt; 12 | 13 | public class StatisticsCalculation { 14 | @Ugly 15 | class IterateThroughValuesSeveralTimes { 16 | public void printNameStats(List users) { 17 | getNameLengthStream(users) 18 | .max() 19 | .ifPresent(max -> System.out.println("MAX: " + max)); 20 | getNameLengthStream(users) 21 | .min() 22 | .ifPresent(min -> System.out.println("MIN: " + min)); 23 | } 24 | 25 | private IntStream getNameLengthStream(List users) { 26 | return users.stream() 27 | .mapToInt(user -> user.getName().length()); 28 | } 29 | } 30 | 31 | @Good 32 | class CalculateStatisticsInSingleRunWithCollector { 33 | public void registerUsers(List users) { 34 | IntSummaryStatistics statistics = users.stream() 35 | .collect(summarizingInt(user -> user.getName().length())); 36 | System.out.println("MAX: " + statistics.getMax()); 37 | System.out.println("MIN: " + statistics.getMin()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/collectors/StreamMayBeConvertedToArray.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.collectors; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class StreamMayBeConvertedToArray { 11 | @Ugly 12 | class ConvertToArrayViaList { 13 | public String[] getUserNames(List users) { 14 | List names = users.stream() 15 | .map(User::getName) 16 | .collect(Collectors.toList()); 17 | return names.toArray(new String[names.size()]); 18 | } 19 | } 20 | 21 | @Good 22 | class ConvertToArrayDirectly { 23 | public String[] getUserNames(List users) { 24 | return users.stream() 25 | .map(User::getName) 26 | .toArray(String[]::new); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/collectors/TrueFunctionalApproach.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.collectors; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | import com.xpinjection.java8.misused.User; 6 | 7 | import java.util.List; 8 | 9 | import static java.util.Comparator.comparingInt; 10 | 11 | public class TrueFunctionalApproach { 12 | @Ugly 13 | class BeforeJava8 { 14 | public User findUsersWithMostRoles(List users) { 15 | if (users.isEmpty()) { 16 | return null; 17 | } 18 | User mostPowerful = users.iterator().next(); 19 | for (User user : users) { 20 | if (user.getRoles().size() > mostPowerful.getRoles().size()) { 21 | mostPowerful = user; 22 | } 23 | } 24 | return mostPowerful; 25 | } 26 | } 27 | 28 | @Ugly 29 | class NaiveStreamsApproach { 30 | public User findUsersWithMostRoles(List users) { 31 | return users.stream() 32 | .sorted(comparingInt(u -> u.getRoles().size())) 33 | .findFirst() 34 | .orElse(null); 35 | } 36 | } 37 | 38 | @Ugly 39 | class StreamsWithReduction { 40 | public User findUsersWithMostRoles(List users) { 41 | return users.stream() 42 | .reduce((u1, u2) -> 43 | u1.getRoles().size() > u2.getRoles().size() ? u1 : u2) 44 | .orElse(null); 45 | } 46 | } 47 | 48 | @Good 49 | class MaxWithComparator { 50 | public User findUsersWithMostRoles(List users) { 51 | return users.stream() 52 | .max(comparingInt(u -> u.getRoles().size())) 53 | .orElse(null); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/incorrect/ForgotTerminalOperation.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.incorrect; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Bad; 4 | 5 | import java.util.stream.IntStream; 6 | 7 | public class ForgotTerminalOperation { 8 | @Bad 9 | public void willDoNothingInReality() { 10 | IntStream.range(1, 5) 11 | .peek(System.out::println) 12 | .peek(i -> { 13 | if (i == 5) 14 | throw new RuntimeException("bang"); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/incorrect/InfiniteStreams.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.incorrect; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Bad; 4 | import com.xpinjection.java8.misused.Annotations.Good; 5 | 6 | import java.util.stream.IntStream; 7 | 8 | public class InfiniteStreams { 9 | @Bad 10 | public void infinite(){ 11 | IntStream.iterate(0, i -> i + 1) 12 | .forEach(System.out::println); 13 | } 14 | 15 | @Good 16 | public void validOne(){ 17 | IntStream.iterate(0, i -> i + 1) 18 | .limit(10) 19 | .forEach(System.out::println); 20 | } 21 | 22 | @Bad 23 | public void stillInfinite(){ 24 | IntStream.iterate(0, i -> ( i + 1 ) % 2) 25 | .distinct() 26 | .limit(10) 27 | .forEach(System.out::println); 28 | } 29 | 30 | @Good 31 | public void butThisOneIfFine(){ 32 | IntStream.iterate(0, i -> ( i + 1 ) % 2) 33 | .limit(10) 34 | .distinct() 35 | .forEach(System.out::println); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/stream/incorrect/UseStreamMoreThanOnce.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.stream.incorrect; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Bad; 4 | 5 | import java.util.Arrays; 6 | import java.util.stream.IntStream; 7 | 8 | public class UseStreamMoreThanOnce { 9 | @Bad 10 | public void streamIsClosedAfterTerminalOperation() { 11 | int[] array = new int[]{1, 2}; 12 | IntStream stream = Arrays.stream(array); 13 | stream.forEach(System.out::println); 14 | array[0] = 2; 15 | stream.forEach(System.out::println); 16 | //IllegalStateException: stream has already been operated upon or closed 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/xpinjection/java8/misused/time/TimeApiIgnorance.java: -------------------------------------------------------------------------------- 1 | package com.xpinjection.java8.misused.time; 2 | 3 | import com.xpinjection.java8.misused.Annotations.Good; 4 | import com.xpinjection.java8.misused.Annotations.Ugly; 5 | 6 | import java.time.LocalDate; 7 | import java.util.Calendar; 8 | import java.util.Date; 9 | 10 | import static java.time.temporal.ChronoUnit.DAYS; 11 | 12 | public class TimeApiIgnorance { 13 | @Ugly 14 | class AddDayInPreJava8Style { 15 | public Date tomorrow() { 16 | Calendar now = Calendar.getInstance(); 17 | now.add(Calendar.DAY_OF_MONTH, 1); 18 | return now.getTime(); 19 | } 20 | } 21 | 22 | @Ugly 23 | class AddDayInefficient { 24 | public LocalDate tomorrow() { 25 | return LocalDate.now().plus(1, DAYS); 26 | } 27 | } 28 | 29 | @Good 30 | class AddDayInJava8Style { 31 | public LocalDate tomorrow() { 32 | return LocalDate.now().plusDays(1); 33 | } 34 | } 35 | } --------------------------------------------------------------------------------