Skip to content

Common misuses of Java 8+ features

| java |

These notes are copy of xpinjection/java8-misuses repository.


Domain Model

All the cases are based on the next domain model:

package com.xpinjection.java8.misused;

public class Annotations {
    public @interface Good{}
    public @interface Bad{}
    public @interface Ugly{}
}
public enum Permission {ADD, EDIT, SEARCH, DELETE}
public class Role {
    private String name;
    private Set<Permission> permissions = EnumSet.noneOf(Permission.class);

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Set<Permission> getPermissions() { return permissions; }
    public void setPermissions(Set<Permission> permissions) { this.permissions = permissions; }

    @Override public boolean equals(Object o) { // ... the details are not important  }
    @Override public int hashCode() { return name.hashCode(); }
}
public class User {
    private Long id;
    private String name;
    private int age;
    private Set<Role> roles = new HashSet<>();

    public User(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public Set<Role> getRoles() { return roles; }
    public void setRoles(Set<Role> roles) { this.roles = roles; }
}
public class UserDto {
    private Long id;
    private String name;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

1 - Optional

  • Do not overuse Optional, never use it for parameters
  • Do not check for value presence, operate on it instead
  • Do not overuse Optional, don’t be too clever
  • Optional container should be short lived
  • Wrap nullable values in Optional to operate on them
  • Use chained methods
  • Carefully choose what to wrap

1.1 - Internal Optional Usage

package com.xpinjection.java8.misused.optional.usage;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.Optional;

public class InternalOptionalUsage {
    @Ugly
    class UnclearOptionalDependencyWithCheckForNull {
        private Printer printer;

        public void process(User user) {
            //some processing
            if (printer != null) {
                printer.print(user);
            }
        }

        public void setPrinter(Printer printer) {
            this.printer = printer;
        }
    }

    @Good
    class ValidInternalOptionalDependency {
        private Optional<Printer> printer = Optional.empty();

        public void process(User user) {
            //some processing
            printer.ifPresent(p -> p.print(user));
        }

        public void setPrinter(Printer printer) {
            this.printer = Optional.ofNullable(printer);
        }
    }

    interface Printer {
        void print(User user);
    }
}

1.2 - Optional Constructor Parameters

package com.xpinjection.java8.misused.optional.usage;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalConstructorParameters {
    @Ugly
    class OptionalLeaksOutsideClass {
        public List<Email> create() {
            Email noAttachment = new Email("First!", "No attachment", Optional.empty());
            Attachment attachment = new Attachment("/mnt/files/image.png", 370);
            Email withAttachment = new Email("Second!", "With attachment", Optional.of(attachment));
            return Arrays.asList(noAttachment, withAttachment);
        }

        class Email implements Serializable {
            private final String subject;
            private final String body;
            private final Optional<Attachment> attachment;

            Email(String subject, String body, Optional<Attachment> attachment) {
                this.subject = subject;
                this.body = body;
                this.attachment = attachment;
            }

            String getSubject() { return subject; }
            String getBody() { return body; }
            Optional<Attachment> getAttachment() { return attachment;}
        }
    }

    @Good
    class OverloadedConstructors {
        public List<Email> create() {
            Email noAttachment = new Email("First!", "No attachment");
            Attachment attachment = new Attachment("/mnt/files/image.png", 370);
            Email withAttachment = new Email("Second!", "With attachment", attachment);
            return Arrays.asList(noAttachment, withAttachment);
        }

        class Email implements Serializable {
            private final String subject;
            private final String body;
            private final Attachment attachment;

            Email(String subject, String body, Attachment attachment) {
                this.subject = subject;
                this.body = body;
                this.attachment = attachment;
            }

            Email(String subject, String body) {
                this(subject, body, null);
            }

            String getSubject() { return subject; }
            String getBody() { return body; }
            boolean hasAttachment() { return attachment != null; }
            Attachment getAttachment() { return attachment; }
        }
    }

    class Attachment {
        private final String path;
        private final int size;

        Attachment(String path, int size) {
            this.path = path;
            this.size = size;
        }

        String getPath() { return path; }
        int getSize() { return size; }
    }
}

1.3 - Optional for resulted (method returns) Collections

package com.xpinjection.java8.misused.optional.usage;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class OptionalForCollections {
    private static final String ADMIN_ROLE = "admin";

    @Ugly
    class TooVerbose {
        public User findAnyAdmin() {
            Optional<List<User>> users = findUsersByRole(ADMIN_ROLE);
            if (users.isPresent() && !users.get().isEmpty()) {
                return users.get().get(0);
            }
            throw new IllegalStateException("No admins found");
        }

        private Optional<List<User>> findUsersByRole(String role) {
            //real search in DB
            return Optional.empty();
        }
    }

    @Good
    class NiceAndClean {
        public User findAnyAdmin() {
            return findUsersByRole(ADMIN_ROLE).stream()
                    .findAny()
                    .orElseThrow(() -> new IllegalStateException("No admins found"));
        }

        private List<User> findUsersByRole(String role) {
            //real search in DB
            return Collections.emptyList();
        }
    }
}

1.4 - Use flatMap for getting nested Optional values

package com.xpinjection.java8.misused.optional;

import com.xpinjection.java8.misused.Annotations.Bad;
import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.Optional;

import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;

public class HundredAndOneApproach {
    @Ugly
    class SameOldImperativeStyle {
        public String getPersonCarInsuranceName(Person person) {
            String name = "Unknown";
            if (ofNullable(person).isPresent()) {
                if (person.getCar().isPresent()) {
                    if (person.getCar().get().getInsurance().isPresent()) {
                        name = person.getCar().get().getInsurance().get().getName();
                    }
                }
            }
            return name;
        }
    }

    @Ugly
    class UsingIfPresentInSameImperativeWayWithDirtyHack {
        public String getPersonCarInsuranceName(Person person) {
            final StringBuilder builder = new StringBuilder();
            ofNullable(person).ifPresent(
                    p -> p.getCar().ifPresent(
                            c -> c.getInsurance().ifPresent(
                                    i -> builder.append(i.getName())
                            )
                    )
            );
            return builder.toString();
        }
    }

    @Bad
    class UsingMapWithUncheckedGet {
        public String getPersonCarInsuranceName(Person person) {
            return ofNullable(person)
                    .map(Person::getCar)
                    .map(car -> car.get().getInsurance())
                    .map(insurance -> insurance.get().getName())
                    .orElse("Unknown");
        }
    }

    @Ugly
    class UsingMapWithOrElseEmptyObjectToFixUncheckedGet {
        public String getPersonCarInsuranceName(Person person) {
            return ofNullable(person)
                    .map(Person::getCar)
                    .map(car -> car.orElseGet(Car::new).getInsurance())
                    .map(insurance -> insurance.orElseGet(Insurance::new).getName())
                    .orElse("Unknown");
        }
    }

    @Good
    class UsingFlatMap {
        public String getCarInsuranceNameFromPersonUsingFlatMap(Person person) {
            return ofNullable(person)
                    .flatMap(Person::getCar)
                    .flatMap(Car::getInsurance)
                    .map(Insurance::getName)
                    .orElse("Unknown");
        }
    }

    class Person {
        Optional<Car> getCar() {
            return empty(); //stub
        }
    }

    class Car {
        Optional<Insurance> getInsurance() {
            return empty(); //stub
        }
    }

    class Insurance {
        String getName() {
            return ""; //stub
        }
    }
}

1.5 - if statement with Optional is not always bad thing

package com.xpinjection.java8.misused.optional;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.Optional;
import java.util.stream.Stream;

public class IfStatementIsNotAlwaysBadThing {
    @Ugly
    class CombineSomeOptionalsInCleverWay {
        public Optional<Integer> sum(Optional<Integer> first, Optional<Integer> second) {
            return Stream.of(first, second)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .reduce(Integer::sum);
        }
    }

    @Ugly
    class PlayMapGameInEvenMoreCleverWay {
        public Optional<Integer> sum(Optional<Integer> first, Optional<Integer> second) {
            return first.map(b -> second.map(a -> b + a).orElse(b))
                    .map(Optional::of)
                    .orElse(second);
        }
    }

    @Good
    class OldSchoolButTotallyClearCode {
        public Optional<Integer> sum(Optional<Integer> first, Optional<Integer> second) {
            if (!first.isPresent() && !second.isPresent()) {
                return Optional.empty();
            }
            return Optional.of(first.orElse(0) + second.orElse(0));
        }
    }
}

1.6 - Optional “elvis”

package com.xpinjection.java8.misused.optional;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import static java.util.Optional.ofNullable;

public class OptionalElvis {
    @Ugly
    class BeforeJava8 {
        public String getUserName(User user) {
            return (user != null && user.getName() != null) ? user.getName() : "default";
        }
    }

    @Ugly
    class UsingOptionalIsPresent {
        public String getUserName(User user) {
            if (ofNullable(user).isPresent()) {
                if (ofNullable(user.getName()).isPresent()) {
                    return user.getName();
                }
            }
            return "default";
        }
    }

    @Good
    class UsingOrElse {
        String getUserName(User user) {
            return ofNullable(user)
                    .map(User::getName)
                    .orElse("default");
        }
    }
}

1.7 - Optional Over Engineering (if not null might be simpler)

package com.xpinjection.java8.misused.optional;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Role;

import java.util.Optional;

public class OptionalOverEngineering {
    @Ugly
    class NullProtectionOverEngineering {
        public Role copyRole(Role role) {
            Role copy = new Role();

            Optional.ofNullable(role.getName())
                    .ifPresent(copy::setName);
            copy.setPermissions(role.getPermissions());
            return copy;
        }
    }

    @Good
    class SimpleConditionalCopying {
        public Role copyRole(Role role) {
            Role copy = new Role();

            if (role.getName() != null) {
                copy.setName(role.getName());
            }
            copy.setPermissions(role.getPermissions());
            return copy;
        }
    }
}

1.8 - Value presence strict check (chained ifPresent)

package com.xpinjection.java8.misused.optional;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.Optional;

public class StrictCheckOfValuePresence {
    @Ugly
    class ManualCheckForPresenceToThrowException {
        public String getUserName(Long userId) {
            Optional<User> user = findById(userId);
            if (user.isPresent()) {
                return user.get().getName();
            }
            throw new IllegalStateException("User not found");
        }

        public void deleteUser(Long userId) {
            Optional<User> user = findById(userId);
            if (user.isPresent()) {
                delete(user.get());
            }
        }

        private void delete(User user) {
            //delete from DB
        }
    }

    @Good
    class OrElseThrowUsage {
        public String getUserName(Long userId) {
            return findById(userId)
                    .orElseThrow(() -> new IllegalStateException("User not found"))
                    .getName();
        }

        public void deleteUser(Long userId) {
            findById(userId).ifPresent(this::delete);
        }

        private void delete(User user) {
            //delete from DB
        }
    }

    private Optional<User> findById(Long userId) {
        //search in DB
        return Optional.of(new User(5L, "Mikalai", 33));
    }
}

2 - Lambdas

  • Be concrete with functional interfaces
  • Avoid long or complex lambda expressions!
  • Prefer reusable method reference
  • Use specific methods on collections
  • Lazy calculation improve performance
  • Check popular API changes for lambda support

2.1 - Avoid Complex Lambdas

package com.xpinjection.java8.misused.lambda;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Permission;
import com.xpinjection.java8.misused.Role;
import com.xpinjection.java8.misused.User;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

import static java.util.stream.Collectors.toSet;

public class AvoidComplexLambdas {
    private final Set<User> users = new HashSet<>();

    @Ugly
    class UsingComplexLambdaInPlace {
        public Set<User> findEditors() {
            return users.stream()
                    .filter(u -> u.getRoles().stream()
                            .anyMatch(r -> r.getPermissions().contains(Permission.EDIT)))
                    .collect(toSet());
        }
    }

    @Good
    class ComplexityExtractedToMethodReference {
        public Set<User> checkPermission(Permission permission) {
            return users.stream()
                    //.filter(this::hasEditPermission)
                    .filter(hasPermission(Permission.EDIT))
                    .collect(toSet());
        }

        private Predicate<User> hasPermission(Permission permission) {
            return user -> user.getRoles().stream()
                    .map(Role::getPermissions)
                    .anyMatch(permissions -> permissions.contains(permission));
        }

        private boolean hasEditPermission(User user) {
            return hasPermission(Permission.EDIT).test(user);
        }
    }
}

2.2 - Avoid Long Lambdas

package com.xpinjection.java8.misused.lambda;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;
import com.xpinjection.java8.misused.UserDto;

import java.util.List;
import java.util.function.Function;

import static java.util.stream.Collectors.toList;

public class AvoidLongLambdas {
    @Ugly
    class LongLambdaInPlace {
        public List<UserDto> convertToDto(List<User> users){
            return users.stream()
                    .map(user -> {
                        UserDto dto = new UserDto();
                        dto.setId(user.getId());
                        dto.setName(user.getName());
                        //it happens to be much more fields 
                        //   and much more logic in terms of remapping these fields
                        return dto;
                    })
                    .collect(toList());
        }
    }

    @Good
    class MethodReferenceInsteadOfLambda {
        //particular toDto could be implemented as a separate class or as a lambda function
        private final Function<User, UserDto> toDto = this::convertToDto;

        public List<UserDto> convertToDto(List<User> users){
            return users.stream()
                    .map(toDto)
                    .collect(toList());
        }

        private UserDto convertToDto(User user){
            UserDto dto = new UserDto();
            dto.setId(user.getId());
            dto.setName(user.getName());
            return dto;
        }
    }
}

2.3 - Class Design (methods’ naming)

package com.xpinjection.java8.misused.lambda;

import com.xpinjection.java8.misused.Annotations.Bad;
import com.xpinjection.java8.misused.Annotations.Good;

import java.util.function.Function;
import java.util.function.UnaryOperator;

public class ClassDesign {
    @Bad
    static class AmbiguousOverloadedMethods {
        interface AmbiguousService<T> {
            <R> R process(Function<T, R> fn);

            T process(UnaryOperator<T> fn);
        }

        public void usage(AmbiguousService<String> service) {
            //which method you intended to call??? both are acceptable.
            service.process(String::toUpperCase);
        }
    }

    @Good
    static class SeparateSpecializedMethods {
        interface ClearService<T> {
            <R> R convert(Function<T, R> fn);

            T process(UnaryOperator<T> fn);
        }

        public void usage(ClearService<String> service) {
            //now it's clear which method will be called.
            service.convert(String::toUpperCase);
        }
    }
}

2.4 - Lambdas are not always the best option (method reference works as well)

package com.xpinjection.java8.misused.lambda;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.Optional;

public class LambdasAreNotAlwaysTheBestOption {
    @Ugly
    class UnneededLambdasUsage {
        public void processAndPrint(String name) {
            Optional.ofNullable(name)
                    //.filter(s -> !s.isEmpty())
                    .map(s -> s.toUpperCase())
                    .map(s -> doProcess(s))
                    .ifPresent(s -> System.out.print(s));
        }

        private String doProcess(String name) {
            return "MR. " + name;
        }
    }

    @Good
    class MethodReferenceUsage {
        public void processAndPrint(String name) {
            Optional.ofNullable(name)
                    //.filter(StringUtils::isNotEmpty) // replace with appropriate library method ref
                    .map(String::toUpperCase)
                    .map(this::doProcess)
                    .ifPresent(System.out::print);
        }

        private String doProcess(String name) {
            return "MR. " + name;
        }
    }
}

2.5 - Lazy calculations improve performance (log if logger.isDebugEnabled())

package com.xpinjection.java8.misused.lambda;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.Set;
import java.util.function.Supplier;

public class LazyCalculationsImprovePerformance {
    @Ugly
    static class LoggingWithAdditionalCheckToAvoidCalculations {
        private static final Log LOG = null; // init logger with factory

        public void sendWelcomeEmailToUsers(Set<User> users) {
            // send email
            if (LOG.isDebugEnabled()) {
                LOG.debug("Emails have been sent for users: " + users);
            }
        }

        interface Log {
            void debug(String message);

            boolean isDebugEnabled();
        }
    }

    @Good
    static class PassLambdaToLazyCalculateValueForLogMessage {
        private static final Log LOG = null; // init logger with factory

        public void sendWelcomeEmailToUsers(Set<User> users) {
            // send email
            LOG.debug(() -> "Emails have been sent for users: " + users);
        }

        interface Log {
            void debug(String message);

            boolean isDebugEnabled();

            default void debug(Supplier<String> message) {
                if (isDebugEnabled()) {
                    debug(message.get());
                }
            }
        }
    }
}

2.6 - Emulate Multimap

package com.xpinjection.java8.misused.lambda.collections;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.*;

public class EmulateMultimap {
    private final Map<String, Set<User>> usersByRole = new HashMap<>();

    @Ugly
    class ManuallyInsertSetOnFirstValueForTheKey {
        public void addUser(User user) {
            user.getRoles().forEach(r -> {
                Set<User> usersInRole = usersByRole.get(r.getName());
                if (usersInRole == null) {
                    usersInRole = new HashSet<>();
                    usersByRole.put(r.getName(), usersInRole);
                }
                usersInRole.add(user);
            });
        }

        public Set<User> getUsersInRole(String role) {
            Set<User> users = usersByRole.get(role);
            return users == null ? Collections.emptySet() : users;
        }
    }

    @Good
    class ComputeEmptySetIfKeyIsAbsent {
        public void addUser(User user) {
            user.getRoles().forEach(r -> usersByRole
                    .computeIfAbsent(r.getName(), k -> new HashSet<>())
                    .add(user));
        }

        public Set<User> getUsersInRole(String role) {
            return usersByRole.getOrDefault(role, Collections.emptySet());
        }
    }
}

2.7 - Sorting the list using existing predefined comparator

package com.xpinjection.java8.misused.lambda.collections;

import com.xpinjection.java8.misused.User;
import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.List;

import static java.util.Comparator.comparing;

public class ListSorting {
    @Ugly
    class UsingCustomComparator {
        public void sortUsersById(List<User> users) {
            users.sort((x, y) -> Long.compare(x.getId(), y.getId()));
        }
    }

    @Good
    class UsingExistingPredefinedComparator {
        public void sortUsersById(List<User> users) {
            users.sort(comparing(User::getId));
        }
    }
}

2.8 - Iterating the map (forEach and map transform)

package com.xpinjection.java8.misused.lambda.collections;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.HashMap;
import java.util.Map;

import static java.util.stream.Collectors.toMap;

public class MapIterating {
    @Ugly
    class UsingOldGoodEntrySet {
        public Map<String, String> getUserNames(Map<String, User> users) {
            Map<String, String> userNames = new HashMap<>();
            users.entrySet().forEach(user ->
                    userNames.put(user.getKey(), user.getValue().getName()));
            return userNames;
        }
    }

    @Good
    class UsingMapForEach {
        public Map<String, String> getUserNames(Map<String, User> users) {
            Map<String, String> userNames = new HashMap<>();
            users.forEach((key, value) -> userNames.put(key, value.getName()));
            return userNames;
        }
    }

    @Good
    class UsingMapTransform {
        public Map<String, String> getUserNames(Map<String, User> users) {
            return users.entrySet().stream()
                    .collect(toMap(Map.Entry::getKey,
                            entry -> entry.getValue().getName()));
        }
    }
}

2.9 - Remove with predicate

package com.xpinjection.java8.misused.lambda.collections;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Permission;
import com.xpinjection.java8.misused.User;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class RemoveElementWithIterator {
    private final Set<User> users = new HashSet<>();

    @Ugly
    class ManuallyRemoveElementWithIteratorRemove {
        public void removeUsersWithPermission(Permission permission) {
            Iterator<User> iterator = users.iterator();
            while (iterator.hasNext()) {
                User user = iterator.next();
                if (user.getRoles().stream()
                        .anyMatch(r -> r.getPermissions().contains(permission))) {
                    iterator.remove();
                }
            }
        }
    }

    @Good
    class RemoveWithPredicate {
        public void removeUsersWithPermission(Permission permission) {
            users.removeIf(user -> user.getRoles().stream()
                    .anyMatch(r -> r.getPermissions().contains(permission)));
        }
    }
}

2.10 - Avoid code-duplication with lambdas

// @Ugly
private void logUpper(String str) {
    //super interesting code
    System.out.println(str.toUpperCase());
    //even more interesting code
}
private void logLower(String str) {
    //super interesting code
    System.out.println(str.toLowerCase());
    //even more interesting code
}

// @Good
private void logUpper(String string) {
    doSuperCoolStuff(string, s -> s.toUpperCase());
}
private void logLower(String string) {
    doSuperCoolStuff(string, s -> s.toLowerCase());
}
private void doFoo(String str, Function<String, String> func) {
    //super interesting code
    System.out.println(func.apply(str));
    //even more interesting code
}

2.11 - List of optionals

beerLib.stream()
    .map(Beer::getDescription) //returns optional 

//java 8 style
beerLib.stream()
    .map(Beer::getDescription) //returns optional
    .filter(Optional::isPresent)
    .map(Optional::get)
    .forEach(System.out::println);

//java 8 flatMap
beerLib.stream()
    .map(Beer::getDescription) //returns optional
    .flatMap(o -> o.map(Stream::of).orElse(Stream.empty()))
    .forEach(System.out::println);

//java 9 flatMap
beerLib.stream()
    .map(Beer::getDescription) //returns optional
    .flatMap(Optional::stream)
    .forEach(System.out::println);

2.12 - Checked Exceptions & Lambda

public Beer doSomething(Beer beer) throws IsEmptyException { ... }

Function <Beer,Beer> fBeer = beer -> doSomething(beer) // Don't do this
// @Ugly
public Beer doSomething(Beer beer) throws IsEmptyException { ... }

beerLib.stream()
    .map(beer -> {
        try {
            return doSomething(beer);
        } catch (IsEmptyException e) {
            throw new RuntimeException(e);
        }
    };)
    .collect(Collectors.toList());

beerLib.stream()
    .map(this::wrappedDoSomeThing)
    .collect(Collectors.toList());

Exception Utility

// @Better
@FunctionalInterface
public interface CheckedFunction<T, R> {
    public R apply(T t) throws Exception;
}

public static <T, R> Function<T, R> wrap(CheckedFunction<T, R> function) {
    return t -> {
        try {
            return function.apply(t);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    };
};

beerLib.stream()
    .map(wrap(beer -> doSomething(beer)))
    .collect(Collectors.toList());

3 - Stream API

  • Don’t use external state with streams
  • Don’t mix streams and imperative code
  • Avoid complex nested streams
  • Avoid loop by design, think in pipeline
  • Follow true functional approach without side effects
  • Use typed streams for primitives
  • Take a look at extension like jool, StreamEx
  • Stream is not a data structure

Incorrect usage

3.1 - Forgotten termin operation

package com.xpinjection.java8.misused.stream.incorrect;

import com.xpinjection.java8.misused.Annotations.Bad;
import java.util.stream.IntStream;

public class ForgotTerminalOperation {
    @Bad
    public void willDoNothingInReality() {
        IntStream.range(1, 5)
                .peek(System.out::println)
                .peek(i -> {
                    if (i == 5)
                        throw new RuntimeException("bang");
                });
    }
}

3.2 - Infinite stream

package com.xpinjection.java8.misused.stream.incorrect;

import com.xpinjection.java8.misused.Annotations.Bad;
import com.xpinjection.java8.misused.Annotations.Good;

import java.util.stream.IntStream;

public class InfiniteStreams {
    @Bad
    public void infinite(){
        IntStream.iterate(0, i -> i + 1)
                .forEach(System.out::println);
    }

    @Good
    public void validOne(){
        IntStream.iterate(0, i -> i + 1)
                .limit(10)
                .forEach(System.out::println);
    }

    @Bad
    public void stillInfinite(){
        IntStream.iterate(0, i -> ( i + 1 ) % 2)
                .distinct()
                .limit(10)
                .forEach(System.out::println);
    }

    @Good
    public void butThisOneIfFine(){
        IntStream.iterate(0, i -> ( i + 1 ) % 2)
                .limit(10)
                .distinct()
                .forEach(System.out::println);
    }
}
  • Look closer at the order of operations
  • Only use infinite streams when absolute necessary

Solution:

IntStrean.range(0, 10);
IntStrean.rangeClosed(0, 10);
IntStrean.iterate(0, i -> i < 10, i -> i + 10); // Java 9 and up

3.3 - Use stream more than once

package com.xpinjection.java8.misused.stream.incorrect;

import com.xpinjection.java8.misused.Annotations.Bad;

import java.util.Arrays;
import java.util.stream.IntStream;

public class UseStreamMoreThanOnce {
    @Bad
    public void streamIsClosedAfterTerminalOperation() {
        int[] array = new int[]{1, 2};
        IntStream stream = Arrays.stream(array);
        stream.forEach(System.out::println);
        array[0] = 2;
        stream.forEach(System.out::println);
        //IllegalStateException: stream has already been operated upon or closed
    }
}

Collectors

3.4 - Avoid forEach and apply mapping to target type

package com.xpinjection.java8.misused.stream.collectors;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

public class AvoidLoopsInStreams {
    private final Set<User> users = new HashSet<>();

    @Ugly
    class UseExternalCounter {
        public double countAverageRolesPerUser() {
            if (users.isEmpty()) {
                return 0;
            }
            AtomicInteger totalCount = new AtomicInteger();
            users.forEach(u -> totalCount.addAndGet(u.getRoles().size()));
            return totalCount.doubleValue() / users.size();
        }
    }

    @Good
    class ApplyMappingsToTargetType {
        public double countAverageRolesPerUser() {
            return users.stream()
                    .mapToDouble(u -> u.getRoles().size())
                    .average()
                    .orElse(0);
        }
    }
}

3.5 - Collectors chain

package com.xpinjection.java8.misused.stream.collectors;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.List;
import java.util.Map;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.*;

public class CollectorsChain {
    @Ugly
    class GroupByAndTransformResultingMap {
        public Map<String, Integer> getMaxAgeByUserName(List<User> users) {
            return users.stream()
                    .collect(groupingBy(User::getName))
                    .entrySet().stream()
                    .collect(toMap(
                            Map.Entry::getKey,
                            e -> e.getValue().stream()
                                    .map(User::getAge)
                                    .reduce(0, Integer::max)
                    ));
        }
    }

    @Ugly
    class GroupByWithMaxCollectorUnwrappingOptionalWithFinisher {
        public Map<String, Integer> getMaxAgeByUserName(List<User> users) {
            return users.stream().collect(groupingBy(User::getName,
                    collectingAndThen(maxBy(comparing(User::getAge)),
                            user -> user.get().getAge())));
        }
    }

    @Good
    class CollectToMapWithMergeFunction {
        public Map<String, Integer> getMaxAgeByUserName(List<User> users) {
            return users.stream()
                    .collect(toMap(User::getName,
                            User::getAge,
                            Integer::max));
        }
    }

    @Good
    class ApplyReduceCollectorAsDownstream {
        public Map<String, Integer> getMaxAgeByUserName(List<User> users) {
            return users.stream()
                    .collect(groupingBy(User::getName,
                            mapping(User::getAge,
                            reducing(0, Integer::max))));
        }
    }
}

3.6 - Do not use external collection for grouping

package com.xpinjection.java8.misused.stream.collectors;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Permission;
import com.xpinjection.java8.misused.User;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static java.util.stream.Collectors.*;

public class ExternalCollectionForGrouping {
    private final Set<User> users = new HashSet<>();

    @Ugly
    class ExternalStateIsUsedForStreamOperations {
        public Map<String, Set<User>> findEditors() {
            Map<String, Set<User>> editors = new HashMap<>();
            users.forEach(u -> u.getRoles().stream()
                    .filter(r -> r.getPermissions().contains(Permission.EDIT))
                    .forEach(r -> {
                        //is it better to use Multiset and avoid this complex code
                        Set<User> usersInRole = editors.get(r.getName());
                        if (usersInRole == null) {
                            usersInRole = new HashSet<>();
                            editors.put(r.getName(), usersInRole);
                        }
                        usersInRole.add(u);
                    })
            );
            return editors;
        }
    }

    @Good
    class TuplesAreUsedWhenStateIsNeededOnLaterPhase {
        public Map<String, Set<User>> findEditors() {
            return users.stream()
                    .flatMap(u -> u.getRoles().stream()
                        .filter(r -> r.getPermissions().contains(Permission.EDIT))
                        .map(r -> new Pair<>(r, u))
                    ).collect(groupingBy(p -> p.getKey().getName(),
                            mapping(Pair::getValue, toSet())));
        }
    }

    //any tuple implementation from 3rd party libraries
    class Pair<K, V> {
        private final K key;
        private final V value;

        Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }

        K getKey() {
            return key;
        }

        V getValue() {
            return value;
        }
    }
}

3.7 - Calculate statistics in single run with collector

package com.xpinjection.java8.misused.stream.collectors;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.summarizingInt;

public class StatisticsCalculation {
    @Ugly
    class IterateThroughValuesSeveralTimes {
        public void printNameStats(List<User> users) {
            getNameLengthStream(users)
                    .max()
                    .ifPresent(max -> System.out.println("MAX: " + max));
            getNameLengthStream(users)
                    .min()
                    .ifPresent(min -> System.out.println("MIN: " + min));
        }

        private IntStream getNameLengthStream(List<User> users) {
            return users.stream()
                    .mapToInt(user -> user.getName().length());
        }
    }

    @Good
    class CalculateStatisticsInSingleRunWithCollector {
        public void registerUsers(List<User> users) {
            IntSummaryStatistics statistics = users.stream()
                    .collect(summarizingInt(user -> user.getName().length()));
            System.out.println("MAX: " + statistics.getMax());
            System.out.println("MIN: " + statistics.getMin());
        }
    }
}

3.8 - Convert stream to array

package com.xpinjection.java8.misused.stream.collectors;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.List;
import java.util.stream.Collectors;

public class StreamMayBeConvertedToArray {
    @Ugly
    class ConvertToArrayViaList {
        public String[] getUserNames(List<User> users) {
            List<String> names = users.stream()
                    .map(User::getName)
                    .collect(Collectors.toList());
            return names.toArray(new String[names.size()]);
        }
    }

    @Good
    class ConvertToArrayDirectly {
        public String[] getUserNames(List<User> users) {
            return users.stream()
                    .map(User::getName)
                    .toArray(String[]::new);
        }
    }
}

3.9 - Use functional approach when “map-reduce”

package com.xpinjection.java8.misused.stream.collectors;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.List;

import static java.util.Comparator.comparingInt;

public class TrueFunctionalApproach {
    @Ugly
    class BeforeJava8 {
        public User findUsersWithMostRoles(List<User> users) {
            if (users.isEmpty()) {
                return null;
            }
            User mostPowerful = users.iterator().next();
            for (User user : users) {
                if (user.getRoles().size() > mostPowerful.getRoles().size()) {
                    mostPowerful = user;
                }
            }
            return mostPowerful;
        }
    }

    @Ugly
    class NaiveStreamsApproach {
        public User findUsersWithMostRoles(List<User> users) {
            return users.stream()
                    .sorted(comparingInt(u -> u.getRoles().size()))
                    .findFirst()
                    .orElse(null);
        }
    }

    @Ugly
    class StreamsWithReduction {
        public User findUsersWithMostRoles(List<User> users) {
            return users.stream()
                    .reduce((u1, u2) ->
                            u1.getRoles().size() > u2.getRoles().size() ? u1 : u2)
                    .orElse(null);
        }
    }

    @Good
    class MaxWithComparator {
        public User findUsersWithMostRoles(List<User> users) {
            return users.stream()
                    .max(comparingInt(u -> u.getRoles().size()))
                    .orElse(null);
        }
    }
}

Misuses

3.10 - Stream generation

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Permission;
import com.xpinjection.java8.misused.Role;

import java.util.Arrays;
import java.util.Collections;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class CreationOptions {
    @Ugly
    public Stream<Permission> getStreamFromList() {
        return Arrays.asList(Permission.ADD, Permission.DELETE).stream();
    }

    @Good
    public Stream<Permission> getStreamFromElements() {
        return Stream.of(Permission.ADD, Permission.DELETE);
    }

    @Ugly
    public Stream<Role> generateStreamByMappingCopies(int n) {
        return Collections.nCopies(n, "ignored").stream()
                .map(s -> new Role());
    }

    @Ugly
    public Stream<Role> generateStreamFromRange(int n) {
        return IntStream.range(0, n).mapToObj(i -> new Role());
    }

    @Good
    public Stream<Role> generateStreamFromSupplierWithLimit(int n) {
        return Stream.generate(Role::new).limit(n);
    }

    @Ugly
    public Stream<Role> generateStreamFromArrayWithRange(Role[] roles, int max) {
        int to = Integer.min(roles.length, max);
        return IntStream.range(0, to).mapToObj(i -> roles[i]);
    }

    @Good
    public Stream<Role> generateStreamFromArrayWithLimit(Role[] roles, int max) {
        return Stream.of(roles).limit(max);
    }
}

3.11 - Use data structure features

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.*;

import static java.util.stream.Collectors.toList;

public class DoNotNeglectDataStructures {
    @Ugly
    class UnnecessaryUseOfNestedStreamOperations {
        public List<Order> filterOrdersByStatuses(List<Order> orders, Set<Status> appropriateStatuses) {
            return orders.stream()
                    .filter(order ->
                            appropriateStatuses.stream().anyMatch(order.getStatus()::equals))
                    .collect(toList());
        }
    }

    @Good
    class UseOfDataStructure {
        public List<Order> filterOrdersByStatuses(List<Order> orders, Set<Status> appropriateStatuses) {
            return orders.stream()
                    .filter(order -> appropriateStatuses.contains(order.getStatus()))
                    .collect(toList());
        }
    }

    @Ugly
    class StateIsStoredInBadDataStructure {
        private final List<Order> orders = new ArrayList<>();

        public void placeOrder(Order order) {
            orders.add(order);
        }

        public List<Order> getOrdersInStatus(Status status) {
            return orders.stream()
                    .filter(order -> order.getStatus() == status)
                    .collect(toList());
        }
    }

    @Good
    class InternalDataStructureMayBeOptimizedForAccessMethods {
        //Use multimap instead from external collections like Guava
        private final Map<Status, List<Order>> orders = new EnumMap<>(Status.class);

        public void placeOrder(Order order) {
            orders.computeIfAbsent(order.getStatus(), status -> new ArrayList<>()).add(order);
        }

        public List<Order> getOrdersInStatus(Status status) {
            return orders.get(status);
        }
    }

    class Order {
        private Status status = Status.ACTIVE;

        Status getStatus() {
            return status;
        }

        void setStatus(Status status) {
            this.status = status;
        }
    }

    enum Status {
        ACTIVE, SUSPENDED, CLOSED
    }
}

3.12 - Do not mix imperative code with streams

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Permission;
import com.xpinjection.java8.misused.Role;
import com.xpinjection.java8.misused.User;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class ImperativeCodeMix {
    private static final String ADMIN_ROLE = "admin";

    private final List<User> users = new ArrayList<>();

    @Ugly
    class TooVerboseMixOfStreamOperationsAndImperativeCode {
        public boolean hasAdmin() {
            return users.stream()
                    .map(u -> {
                        if (u == null) {
                            throw new NullPointerException();
                        }
                        return u;
                    })
                    .flatMap(u -> u.getRoles().stream())
                    .map(Role::getName)
                    .anyMatch(name -> ADMIN_ROLE.equals(name));
        }
    }

    @Good
    class NiceAndCleanStreamOperationsChain {
        public boolean hasAdmin(Permission permission) {
            return users.stream()
                    .map(Objects::requireNonNull)
                    .flatMap(u -> u.getRoles().stream())
                    .map(Role::getName)
                    .anyMatch(ADMIN_ROLE::equals);
        }
    }
}

3.13 - Match element in functional style

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Permission;
import com.xpinjection.java8.misused.User;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

public class MatchElementInFunctionalStyle {
    private final Set<User> users = new HashSet<>();

    @Ugly
    class UseOldSchoolIterationsWithForEachAndExternalBoolean {
        public boolean checkPermission(Permission permission) {
            AtomicBoolean found = new AtomicBoolean();
            users.forEach(
                    u -> u.getRoles().forEach(
                            r -> {
                                if (r.getPermissions().contains(permission)) {
                                    found.set(true);
                                }
                            }
                    )
            );
            return found.get();
        }
    }

    @Ugly
    class TryToUseFunctionalStyleWithStreamFilter {
        public boolean checkPermission(Permission permission) {
            return users.stream().filter(
                    u -> u.getRoles().stream()
                            .filter(r -> r.getPermissions().contains(permission))
                            .count() > 0)
                    .findFirst().isPresent();
        }
    }

    @Ugly
    class TryToUseStreamMatching {
        public boolean checkPermission(Permission permission) {
            return users.stream()
                    .anyMatch(u -> u.getRoles().stream()
                            .anyMatch(r -> r.getPermissions().contains(permission)));
        }
    }

    @Good
    class UseFlatMapForSubCollections {
        public boolean checkPermission(Permission permission) {
            return users.stream()
                    .flatMap(u -> u.getRoles().stream())
                    .anyMatch(r -> r.getPermissions().contains(permission));
        }
    }

    @Good
    class UseFlatMapWithMethodReferencesForSubCollections {
        public boolean checkPermission(Permission permission) {
            return users.stream()
                    .map(User::getRoles)
                    .flatMap(Set::stream)
                    .anyMatch(r -> r.getPermissions().contains(permission));
        }
    }
}

3.14 - Nested forEach is anti-pattern

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.toSet;

public class NestedForEach {
    @Ugly
    class NestedForEachWithExternalCollection {
        public Set<String> retrievePromoRuleNames(List<BusinessTransaction> transactions) {
            Set<String> ruleNamesWithPromo = new HashSet<>();
            transactions.forEach(transaction -> transaction.getRules().stream()
                    .filter(BusinessRule::isPromotion)
                    .forEach(rule -> ruleNamesWithPromo.add(rule.getRuleName())));
            return ruleNamesWithPromo;
        }
    }

    @Good
    class StreamOperationsChain {
        public Set<String> retrievePromoRuleNames(List<BusinessTransaction> transactions) {
            return transactions.stream()
                    .flatMap(t -> t.getRules().stream())
                    .filter(BusinessRule::isPromotion)
                    .map(BusinessRule::getRuleName)
                    .collect(toSet());
        }
    }

    class BusinessTransaction {
        List<BusinessRule> getRules() {
            return new ArrayList<>(); //stub
        }
    }

    class BusinessRule {
        String getRuleName() {
            return ""; //stub
        }

        boolean isPromotion() {
            return false; //stub
        }
    }
}

3.15 - Prefer specialized streams

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.User;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class PreferSpecializedStreams {
    private final Set<User> users = new HashSet<>();

    @Ugly
    class GeneralStreamUsage {
        public int getTotalAge() {
            return users.stream()
                    .map(User::getAge)
                    .reduce(0, Integer::sum);
        }
    }

    @Good
    class SpecializedStreamUsage {
        public int getTotalAge() {
            return users.stream()
                    .mapToInt(User::getAge)
                    .sum();
        }
    }

    @Ugly
    class FlatMapToCountElementsInAllCollections {
        public int countEmployees(Map<String, List<User>> departments) {
            return (int) departments.values().stream()
                    .flatMap(List::stream)
                    .count();
        }
    }

    @Good
    class MapToIntToSimplifyCalculation {
        public long countEmployees(Map<String, List<User>> departments) {
            return departments.values().stream()
                    .mapToInt(List::size)
                    .sum();
        }
    }
}

3.16 - Poor Domain Model causes complex Data Access code

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;
import com.xpinjection.java8.misused.Role;
import com.xpinjection.java8.misused.User;

import java.util.ArrayList;
import java.util.List;

public class RichDomainModel {
    @Ugly
    class PoorDomainModelCausesComplexDataAccessCode {
        private final List<User> users = new ArrayList<>();

        public User findUserInRole(String roleName) {
            for (User user : users) {
                for (Role role : user.getRoles()) {
                    if (roleName.equals(role.getName())) {
                        return user;
                    }
                }
            }
            return null;
        }
    }

    @Ugly
    class StreamVersionLooksNotMuchBetter {
        private final List<User> users = new ArrayList<>();

        public User findUserInRole(String roleName) {
            return users.stream().filter(user -> user.getRoles().stream()
                            .map(Role::getName)
                            .anyMatch(roleName::equals))
                    .findAny()
                    .orElse(null);
        }
    }

    @Good
    class RichDomainModelCouldSimplifyAccessCode {
        private final List<BetterUser> users = new ArrayList<>();

        public User findUserInRole(String roleName) {
            return users.stream()
                    .filter(user -> user.hasRole(roleName))
                    .findAny()
                    .orElse(null);
        }

        class BetterUser extends User {
            BetterUser(long id, String name, int age) {
                super(id, name, age);
            }

            boolean hasRole(String roleName) {
                return getRoles().stream()
                        .map(Role::getName)
                        .anyMatch(roleName::equals);
            }
        }
    }
}

3.17 - Do not use old-style code with new constructs

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.User;
import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.Collection;
import java.util.Objects;

import static java.util.Optional.ofNullable;

public class SameOldCodeStyleWithNewConstructs {
    @Ugly
    class NoMoreThanSameOldLoopWithIf {
        public void registerUsers(Collection<User> users) {
            users.stream().forEach(user ->
                    ofNullable(user).ifPresent(u -> {
                        //register user
                    })
            );
        }
    }

    @Good
    class NewStreamStyleWithMethodReference {
        public void registerUsers(Collection<User> users) {
            users.stream()
                    .filter(Objects::nonNull)
                    .forEach(this::registerUser);
        }

        private void registerUser(User user){
            //register user
        }
    }
}

3.18 - Know when to use skip and limit

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.User;

import java.util.List;

import static com.xpinjection.java8.misused.Annotations.Good;
import static com.xpinjection.java8.misused.Annotations.Ugly;

public class SkipAndLimitOnListIsWaste {
    @Ugly
    class SkipSomeElementsAndThenTakeSomeForProcessing {
        public void registerUsers(List<User> users) {
            users.stream().skip(5).limit(10)
                    .forEach(SkipAndLimitOnListIsWaste.this::registerUser);
        }
    }

    @Good
    class SublistDoNotWasteProcessingTime {
        public void registerUsers(List<User> users) {
            users.subList(5, 15)
                    .forEach(SkipAndLimitOnListIsWaste.this::registerUser);
        }
    }

    private void registerUser(User user) {
        //register user
    }
}

3.19 - Type of stream could be changed

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.List;

public class UntypedStreamsCouldBeConverted {
    @Ugly
    class ProcessOnlyValuesOfSpecialType {
        public int countDoubleNaNs(List numbers) {
            int count = 0;
            for (Object e : numbers) {
                if (e instanceof Double) {
                    Double d = (Double) e;
                    if (d.isNaN()) {
                        count++;
                    }
                }
            }
            return count;
        }
    }

    @Good
    class TypeOfStreamCouldBeChanged {
        public int countDoubleNaNs(List numbers) {
            return (int) numbers.stream()
                    .filter(Double.class::isInstance)
                    .mapToDouble(Double.class::cast)
                    .filter(Double::isNaN)
                    .count();
        }
    }
}

3.20 - Use stream to build map is over-complication

package com.xpinjection.java8.misused.stream;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toMap;

public class WantToUseStreamsEverywhere {
    @Ugly
    class UseStreamToBuildMap {
        public Map<String, Object> getJpaProperties() {
            return Stream.of(
                    new AbstractMap.SimpleEntry<>("hibernate.show_sql", "true"),
                    new AbstractMap.SimpleEntry<>("hibernate.format_sql", "true")
            ).collect(collectingAndThen(
                    toMap(Map.Entry::getKey, Map.Entry::getValue),
                    Collections::unmodifiableMap)
            );
        }
    }

    @Good
    class UseOldPlainMap {
        public Map<String, Object> getJpaProperties() {
            Map<String, Object> properties = new HashMap<>();
            properties.put("hibernate.show_sql", "true");
            properties.put("hibernate.format_sql", "true");
            return Collections.unmodifiableMap(properties);
        }
    }
}

4 - Time API

4.1 - Ignore Java 8 Time API

package com.xpinjection.java8.misused.time;

import com.xpinjection.java8.misused.Annotations.Good;
import com.xpinjection.java8.misused.Annotations.Ugly;

import java.time.LocalDate;
import java.util.Calendar;
import java.util.Date;

import static java.time.temporal.ChronoUnit.DAYS;

public class TimeApiIgnorance {
    @Ugly
    class AddDayInPreJava8Style {
        public Date tomorrow() {
            Calendar now = Calendar.getInstance();
            now.add(Calendar.DAY_OF_MONTH, 1);
            return now.getTime();
        }
    }

    @Ugly
    class AddDayInefficient {
        public LocalDate tomorrow() {
            return LocalDate.now().plus(1, DAYS);
        }
    }

    @Good
    class AddDayInJava8Style {
        public LocalDate tomorrow() {
            return LocalDate.now().plusDays(1);
        }
    }
}

References