Optionals and Streams

do more with less code

Created by Javi M. / @javiyt

Agenda

  • Optional
  • Streams

Optional

What is it?

  • Contains an object
  • Can't be serialized
  • Designed to be a return value

Optional

Important about them

      
      public class WrongWayToUseOptionals {
        private Optional<String> myOptionalProperty;

        public void getOptionalValues(Optional<String> myParameter) {
          // do some stuff
        }
      }
      
    

Optional

Creating objects

      
      Optional<String> myEmptyVar = Optional.empty();
      Optional<String> otherEmtpyVar = Optional.ofNullable(null);
      Optional<String> myVar = Optional.of("my value");
      Optional<String> otherVar = Optional.ofNullable("my value");
      
    

Optional

Goodbye NullPointerException

      
      Optional<String> myVar = Optional.of("testing optionals");
      if (myVar.isPresent()) {
        String testing = myVar.get();
      }
      
    

Optional

Goodbye NullPointerException (Better way)

      
      Optional<String> myVar = Optional.of("testing optionals");

      String testing = myVar.orElse("default value");
      
    

Optional: What can I do with them?

Check value presence

      
      public interface User {
        Optional<UUID> getId();
      }

      public interface UserRepository {
        User save(User user);
      }

      public class EditUserProfile {
        private final UserRepository repository;

        public void editUser(User user) throws ForbiddenException {
          if (user.getId().isPresent()) {
            repository.save(user);
          }
        }

        public void saveUser(User user) throws ForbiddenException {
          user.getId().ifPresent(user ->
            repository.save(user);
          );
        }
      }
      
    

Optional: What can I do with them?

Return default or execute Supplier

      
      public interface User {
        Optional<UUID> getId();
      }

      public class EditUserProfile {
        public void editUser(User user) throws ForbiddenException {
          UUID userId = user.getId().orElse(DEFAULT_USER_ID);
          UUID userId = user.getId().orElseGet(() -> UUID.randomUUID());

          // do stuff
        }
      }
      
    

Optional: What can I do with them?

Throw exceptions

      
      public interface User {
        Optional<UUID> getId();
      }

      public class EditUserProfile {
        public void editUser(User user) throws ForbiddenException {
          UUID userId = user.getId()
                      .orElseThrow(ForbiddenException::new);

          // do stuff
        }
      }
      
    

Optional: What can I do with them?

Transform values: Map

      
      public interface User {
        Optional<String> getId();
      }

      public class EditUserProfile {
        public void editUser(User user) throws ForbiddenException {
          UUID userId = user.getId()
                    .map(UUID::fromString)
                    .orElseThrow(ForbiddenException::new);

          // do stuff
        }
      }
      
    

Optional: What can I do with them?

Transform values: flatMap

      
      public interface UserProfile {
        String getUserName();
      }

      public interface User {
        Optional<UserProfile> getProfile();
      }

      public class GetUserProfile {
        private static final String DEFAULT_USERNAME = "default user";

        public String getUserName(User user) throws ForbiddenException {
          return Optional.ofNullable(user)
                      .flatMap(User::getProfile)
                      .map(UserProfile::getUserName)
                      .orElse(DEFAULT_USERNAME);
        }
      }
      
    

Optional: What can I do with them?

Transform values: flatMap vs Map

      
      Optional<User> user1 = Optional.of(user);
      Optional<Optional<UserProfile>> userProfile = user1.map(User::getProfile);
      Optional<UserProfile> userProfile1 = user1.flatMap(User::getProfile);
      
    

Optional: What can I do with them?

Transform values: flatMap vs Map

      
      public interface UserProfile {
        String getUserName();
      }

      public interface User {
        Optional<UserProfile> getProfile();
      }

      public class GetUserProfile {
        private static final String DEFAULT_USERNAME = "default user";

        public String getUserName(User user) throws ForbiddenException {
          return Optional.ofNullable(user)
                      .map(User::getProfile)
                      .map(u -> u.map(UserProfile::getUserName).orElse(DEFAULT_USERNAME))
                      .orElse(DEFAULT_USERNAME);
        }
      }
      
    

Optional: What can I do with them?

Transform values (without Optional)

      
      public interface UserProfile {
        String getUserName();
      }

      public interface User {
        UserProfile getProfile();
      }

      public class GetUserProfile {
        public String getUserName(User user) throws ForbiddenException {
          String userName = DEFAULT_USERNAME;

          if (
            user != null
            && user.getProfile() != null
            && user.getProfile().getUserName() != null
          ) {
            userName = user.getProfile().getUserName();
          }

          return userName;
        }
      }
      
    

Optional: What can I do with them?

Filter values

      
      public interface User {
        Optional<String> getId();
      }

      public class EditUserProfile {
        public void editUser(User user) throws ForbiddenException {
          String userId = user.getId()
                      .filter(id -> id.equals(FIXED_USER_ID))
                      .orElseThrow(ForbiddenException::new);
          // do stuff
        }
      }
      
    

Streams

What is it?

  • Sequence of elements
  • Lazily constructed
  • Can be computed
  • Automatic iteration

Streams

What is it?

  • Source: Collections, Arrays or I/O resources
  • Pipelined
  • Intermediate and terminal operations
  • Parallel (parallelStream) or sequential execution (stream)
  • Support: filter, map, limit, reduce, find, macth, ...

Streams

Filtering: without streams

        
        public List<Integer> filterList(List<Integer> integers) {
          List<Integer> filtered = new ArrayList<>();
          for (Integer value : integers) {
            if (value <= 2) {
              filtered.add(value);
            }
          }

          return filtered;
        }
        
      

Streams

Filtering: with streams

      
      public List<Integer> filterList(List<Integer> integers) {
        return integers.stream()
                          .filter(i -> i <= 2)
                          .collect(toList());
      }
      
    

Streams

Distinct: without streams

        
        public List<Integer> distinctList(List<Integer> integers) {
          List<Integer> filtered = new ArrayList<>();
          for (Integer value : integers) {
            if (!filtered.contains(value)) {
              filtered.add(value);
            }
          }

          return filtered;
        }
        
      

Streams

Distinct: with streams

      
      public List<Integer> distinctList(List<Integer> integers) {
        return integers.stream()
                            .distinct()
                            .collect(toList());
      }
      
    

Streams

Matching

      
          boolean match = integers.stream()
                                .anyMatch(i -> i == 2);

          boolean match = integers.stream()
                                .allMatch(i -> i == 2);

          boolean match = integers.stream()
                                .noneMatch(i -> i == 2);
      
    

Streams

Find

      
        Optional<Integer> any = integers.stream().findAny();
        Optional<Integer> first = integers.stream().findFirst();
      
    

Streams

Sorting: with comparator

      
      public interface User {
        Date getCreatedAt();
      }

      public class GetUsers {
        public List<User> getOrderedByCreatedAt(List<User> users) {
          Collections.sort(users, new Comparator<User>() {
            @Override
            public int compare(User u1, User u2) {
              return u1.getCreatedAt().compareTo(u2.getCreatedAt());
            }
          });

          return users;
        }
      }
      
    

Streams

Sorting: with streams

      
        public interface User {
          Date getCreatedAt();
        }

        public class GetUsers {
          public List<User> getOrderedByCreatedAt(List<User> users) {
            return users.stream()
                    .sorted(this::compareCreationDate)
                    .collect(toList());
          }

          private int compareCreationDate(User u1, User u2) {
            return u1.getCreatedAt().compareTo(u2.getCreatedAt());
          }
        }
      
    

Streams

Transforming: without streams

      
      public interface User {
        Date getCreatedAt();
      }

      public class GetUsers {
        public List<Date> getCreationDate(List<User> users) {
          List<Date> dates = new ArrayList<>();
          for (User user: users) {
            dates.add(user.getCreatedAt());
          }

          return dates;
        }
      }
      
    

Streams

Transforming: with streams

      
      public interface User {
        Date getCreatedAt();
      }

      public class GetUsers {
        public List<Date> getCreationDate(List<User> users) {
          return users.stream()
                  .map(User::getCreatedAt)
                  .collect(toList());
        }
      }
      
    

Streams

Transforming: without streams

      
      public interface User {
        User addLanguage(String language);

        List<String> getLanguages();
      }

      public class GetUsers {
        public List<String> getUsersLanguages(List<User> users) {
          List<String> languages = new ArrayList<>();
          for (User user: users) {
            for (String language: user.getLanguages()) {
              if (!languages.contains(language)) {
                languages.add(language);
              }
            }
          }

          return languages;
        }
      }
      
    

Streams

Transforming: with streams

      
      public interface User {
        User addLanguage(String language);

        List<String> getLanguages();
      }

      public class GetUsers {
        public List<String> getUsersLanguages(List<User> users) {
          return users.stream()
                  .map(User::getLanguages)
                  .flatMap(l -> l.stream())
                  .collect(toList());
        }
      }
      
    

Streams

Transforming: more...

  • mapToDouble
  • mapToInt
  • mapToLong

Streams

Reduction: without streams

      
      public interface User {
        Integer getPoints();
      }

      public class GetUsers {
        public long getPointsSum(List<User> users) {
          long points = 0;
          for (User user: users) {
            points += user.getPoints();
          }

          return points;
        }
      }
      
    

Streams

Reduction: with streams

      
      public interface User {
        Integer getPoints();
      }

      public class GetUsers {
        public Integer getPointsSum(List<User> users) {
          return users.stream()
            .map(user -> user.getPoints())
            .reduce(0, Integer::sum);
        }
      }
      
    

Streams

Reduction: more...

  • average
  • sum
  • min
  • max
  • count

Streams

Collecting

  • toList
  • toMap
  • toSet
  • joining
  • groupingBy

Streams

Collecting: joining example

      
      public interface User {
        String getUserName();
      }

      public class GetUsers {
        public String getUsersJoined(List<User> users) {
          String usersJoined = "";
          for (User user: users) {
            usersJoined += user.getUserName() + ", ";
          }

          return usersJoined;
        }
      }
      
    

Streams

Collecting: joining example

      
      public interface User {
        String getUserName();
      }

      public class GetUsers {
        public String getUsersJoined(List<User> users) {
          return users.stream()
                    .map(User::getUserName)
                    .collect(joining(", "));
        }
      }
      
    

Streams

Collecting: groupingBy example without streams

      
      public Map<Date,List<User>> getUsersByCreationDate(List<User> users) {
        Map<Date,List<User>> grouped = new HashMap<>();
        for (User user : users) {
          if (!grouped.containsKey(user.getCreatedAt())) {
            grouped.put(user.getCreatedAt(), new ArrayList<>() {
              {
                add(user);
              }
            });
          } else {
            grouped.get(user.getCreatedAt()).add(user);
          }
        }

        return grouped;
      }
      
    

Streams

Collecting: groupingBy example with streams

      
      public Map<Date,List<User>> getUsersByCreationDate(List<User> users) {
        return users.stream().collect(groupingBy(User::getCreatedAt));
      }
      
    

Streams: Example

Merging two Maps

      
      public interface User {
        UUID getId();

        String getUserName();

        Integer getPoints();
      }

      public class TopUsers {
        public Map<String, Integer> getTopUsers(Map<UUID, User> users1, Map<UUID, User> users2) {
          return Stream.of(users1, users2)
                  .map(Map::entrySet)
                  .flatMap(Collection::stream)
                  .collect(toMap(
                          userEntry -> userEntry.getValue().getUserName(),
                          userEntry -> userEntry.getValue().getPoints(),
                          Integer::max
                  ));
        }
      }
      
    

Questions