├── .gitignore ├── README.md ├── account-data ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ ├── data │ └── Accounts.java │ └── model │ ├── Account.java │ ├── CreditAccount.java │ └── Sex.java ├── crazy-lambdas ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ └── CrazyLambdas.java │ └── test │ └── java │ └── com │ └── bobocode │ └── CrazyLambdasTest.java ├── crazy-optionals ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ ├── CrazyOptionals.java │ │ ├── exception │ │ └── AccountNotFoundException.java │ │ └── function │ │ ├── AccountProvider.java │ │ ├── AccountService.java │ │ └── CreditAccountProvider.java │ └── test │ └── java │ └── com │ └── CrazyOptionalsTest.java ├── crazy-streams ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com.bobocode │ │ ├── CrazyStreams.java │ │ └── exception │ │ └── EntityNotFoundException.java │ └── test │ └── java │ └── com │ └── bobocode │ └── CrazyStreamsTest.java ├── declarative-sum-of-squares ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ ├── SumOfSquares.java │ │ └── exception │ │ └── InvalidRangeException.java │ └── test │ └── java │ └── com │ └── bobocode │ └── SumOfSquareTest.java ├── file-reader ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ └── FileReaders.java │ └── test │ ├── java │ └── com │ │ └── bobocode │ │ └── FileReadersTest.java │ └── resources │ ├── empty.txt │ ├── lines.txt │ └── simple.txt ├── file-stats ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ ├── FileStats.java │ │ └── FileStatsException.java │ └── test │ ├── java │ └── com │ │ └── bobocode │ │ └── FileStatsTest.java │ └── resources │ ├── scosb.txt │ └── sotl.txt ├── lambda-math-functions ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com.bobocode │ │ ├── FunctionMap.java │ │ ├── Functions.java │ │ └── InvalidFunctionNameException.java │ └── test │ └── java │ └── com │ └── bobocode │ └── FunctionsTest.java ├── linked-list ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ ├── LinkedList.java │ │ └── List.java │ └── test │ └── java │ └── com │ └── bobocode │ └── LinkedListTest.java ├── linked-queue ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ ├── LinkedQueue.java │ │ └── Queue.java │ └── test │ └── java │ └── com │ └── bobocode │ └── QueueTest.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/*.iml 3 | **/target 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Core exercises 2 | The list of exercises dedicated to training your Java SE skills 3 | 4 | ### No pain, No gain :heavy_exclamation_mark: 5 | 6 | > Skill is only developed by hours and hours and hours of beating on your craft 7 | 8 | Working on real problems, you're focused on finding a solution. Learning new things, you're trying to understand how it works. 9 | It is important to have a different type of activities, which purpose is improving your skill 10 | 11 | ***An exercise** is a predefined task that you continuously implement to improve a certain skill* :muscle: 12 | ## 13 | * [Linked queue](https://github.com/bobocode-projects/java-core-exercises/tree/master/linked-queue) 14 | * [Linked List](https://github.com/bobocode-projects/java-core-exercises/tree/master/linked-list) 15 | * [File Stats](https://github.com/bobocode-projects/java-core-exercises/tree/master/file-stats) 16 | * [File Reader](https://github.com/bobocode-projects/java-core-exercises/tree/master/file-reader) 17 | 18 | ### Related information 19 | * [Java: The Complete Reference, 9th Edition](https://www.amazon.com/Java-Complete-Reference-Herbert-Schildt/dp/0071808558/ref=sr_1_1?ie=UTF8&qid=1540376597&sr=8-1&keywords=java+complete+reference+9th+edition) :orange_book: 20 | * **Classes** 21 | * Nested and Inner classes - **pp. 149 - 151** 22 | 23 | * **Generics** 24 | * Bounded types - **pp. 346 - 349** 25 | * Wildcards - **pp. 349 - 356** 26 | * Type erasure - **p. 373** 27 | * Generic method - **pp. 356 - 359** 28 | * Generic interfaces - **pp. 360 - 362** :heavy_exclamation_mark: 29 | * Restrictions on static members - **p. 377** :heavy_exclamation_mark: 30 | -------------------------------------------------------------------------------- /account-data/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | account-data 13 | 14 | 15 | 16 | io.codearte.jfairy 17 | jfairy 18 | 0.5.9 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /account-data/src/main/java/com/bobocode/data/Accounts.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.data; 2 | 3 | import com.bobocode.model.Account; 4 | import com.bobocode.model.CreditAccount; 5 | import com.bobocode.model.Sex; 6 | import io.codearte.jfairy.Fairy; 7 | import io.codearte.jfairy.producer.person.Person; 8 | 9 | import java.math.BigDecimal; 10 | import java.time.LocalDate; 11 | import java.util.List; 12 | import java.util.concurrent.ThreadLocalRandom; 13 | import java.util.stream.Stream; 14 | 15 | import static java.util.stream.Collectors.toList; 16 | 17 | public class Accounts { 18 | public static int MAX_BALANCE_VALUE = 200_000; 19 | 20 | public static Account generateAccount() { 21 | Person person = generatePerson(); 22 | Account account = convertToAccount(person); 23 | fillCommonRandomFields(account); 24 | return account; 25 | } 26 | 27 | public static CreditAccount generateCreditAccount() { 28 | Person person = generatePerson(); 29 | CreditAccount account = convertToCreditAccount(person); 30 | fillCommonRandomFields(account); 31 | account.setCreditBalance(randomBigDecimal(MAX_BALANCE_VALUE)); 32 | return account; 33 | } 34 | 35 | public static List generateAccountList(int size) { 36 | return Stream.generate(Accounts::generateAccount) 37 | .limit(size) 38 | .collect(toList()); 39 | } 40 | 41 | public static List generateCreditAccountList(int size) { 42 | return Stream.generate(Accounts::generateCreditAccount) 43 | .limit(size) 44 | .collect(toList()); 45 | } 46 | 47 | private static Person generatePerson() { 48 | Fairy fairy = Fairy.create(); 49 | return fairy.person(); 50 | } 51 | 52 | private static Account convertToAccount(Person person) { 53 | Account account = new Account(); 54 | fillAccount(account, person); 55 | return account; 56 | } 57 | 58 | private static CreditAccount convertToCreditAccount(Person person) { 59 | CreditAccount account = new CreditAccount(); 60 | fillAccount(account, person); 61 | return account; 62 | } 63 | 64 | private static void fillAccount(Account account, Person person) { 65 | account.setFirstName(person.getFirstName()); 66 | account.setLastName(person.getLastName()); 67 | account.setEmail(person.getEmail()); 68 | account.setBirthday(LocalDate.of( 69 | person.getDateOfBirth().getYear(), 70 | person.getDateOfBirth().getMonthOfYear(), 71 | person.getDateOfBirth().getDayOfMonth())); 72 | account.setSex(Sex.valueOf(person.getSex().name())); 73 | } 74 | 75 | private static void fillCommonRandomFields(Account account) { 76 | BigDecimal balance = randomBigDecimal(MAX_BALANCE_VALUE); 77 | account.setBalance(balance); 78 | account.setCreationDate(LocalDate.now()); 79 | } 80 | 81 | private static BigDecimal randomBigDecimal(int max) { 82 | return BigDecimal.valueOf(ThreadLocalRandom.current().nextInt(max)); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /account-data/src/main/java/com/bobocode/model/Account.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.*; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor(access = AccessLevel.PUBLIC) 11 | @Getter 12 | @Setter 13 | @ToString 14 | @EqualsAndHashCode(of = "email") 15 | public class Account { 16 | private Long id; 17 | private String firstName; 18 | private String lastName; 19 | private String email; 20 | private LocalDate birthday; 21 | private Sex sex; 22 | private LocalDate creationDate; 23 | private BigDecimal balance = BigDecimal.ZERO; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /account-data/src/main/java/com/bobocode/model/CreditAccount.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.Optional; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor(access = AccessLevel.PUBLIC) 13 | @Setter 14 | public class CreditAccount extends Account { 15 | private BigDecimal creditBalance; 16 | 17 | public Optional getCreditBalance() { 18 | return Optional.ofNullable(creditBalance); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /account-data/src/main/java/com/bobocode/model/Sex.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | public enum Sex { 4 | MALE, 5 | FEMALE 6 | } 7 | -------------------------------------------------------------------------------- /crazy-lambdas/README.md: -------------------------------------------------------------------------------- 1 | # Crazy lambda exercise :muscle: 2 | Improve your lambda skills 3 | ### Task 4 | `CrazyLambdas` class consists of static methods that return various functions, operations and predicates. 5 | Your job is to implement the *todo* section of that class using **Lambda expressions** and **method reference**. 6 | To verify your implementation, run `CrazyLambdasTest.java` 7 | 8 | ### Pre-conditions :heavy_exclamation_mark: 9 | You're supposed to be familiar with Java 8 10 | 11 | ### How to start :question: 12 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 13 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 14 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 15 | 16 | ### Related materials :information_source: 17 | * [Lambda tutorial](https://github.com/bobocode-projects/java-8-tutorial/tree/master/lambdas) 18 | * [State of lambda (JSR 335)](http://htmlpreview.github.io/?https://github.com/bobocode-projects/resources/blob/master/java8/lambda/sotl.html) 19 | 20 | -------------------------------------------------------------------------------- /crazy-lambdas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | crazy-lambdas 13 | 14 | 15 | -------------------------------------------------------------------------------- /crazy-lambdas/src/main/java/com/bobocode/CrazyLambdas.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Map; 5 | import java.util.function.*; 6 | 7 | public class CrazyLambdas { 8 | 9 | /** 10 | * Returns {@link Supplier} that always supply "Hello" 11 | * 12 | * @return a string supplier 13 | */ 14 | public static Supplier helloSupplier() { 15 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 16 | } 17 | 18 | /** 19 | * Returns a {@link Predicate} of string that checks if string is empty 20 | * 21 | * @return a string predicate 22 | */ 23 | public static Predicate isEmptyPredicate() { 24 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 25 | } 26 | 27 | /** 28 | * Return a {@link Function} that accepts {@link String} and returns that string repeated n time, where n is passed 29 | * as function argument 30 | * 31 | * @return function that repeats Strings 32 | */ 33 | public static BiFunction stringMultiplier() { 34 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 35 | } 36 | 37 | /** 38 | * Returns a {@link Function} that converts a {@link BigDecimal} number into a {@link String} that start with 39 | * a dollar sign and then gets a value 40 | * 41 | * @return function that converts adds dollar sign 42 | */ 43 | public static Function toDollarStringFunction() { 44 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 45 | } 46 | 47 | /** 48 | * Receives two parameter that represent a range and returns a {@link Predicate} that verifies if string 49 | * length is in the specified range. E.g. min <= length < max 50 | * 51 | * @param min min length 52 | * @param max max length 53 | * @return a string predicate 54 | */ 55 | public static Predicate lengthInRangePredicate(int min, int max) { 56 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 57 | } 58 | 59 | /** 60 | * Returns a {@link Supplier} of random integers 61 | * 62 | * @return int supplier 63 | */ 64 | public static IntSupplier randomIntSupplier() { 65 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 66 | } 67 | 68 | 69 | /** 70 | * Returns an {@link IntUnaryOperator} that receives an int as a bound parameter, and returns a random int 71 | * 72 | * @return int operation 73 | */ 74 | public static IntUnaryOperator boundedRandomIntSupplier() { 75 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 76 | } 77 | 78 | /** 79 | * Returns {@link IntUnaryOperator} that calculates an integer square 80 | * 81 | * @return square operation 82 | */ 83 | public static IntUnaryOperator intSquareOperation() { 84 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 85 | } 86 | 87 | /** 88 | * Returns a {@link LongBinaryOperator} sum operation. 89 | * 90 | * @return binary sum operation 91 | */ 92 | public static LongBinaryOperator longSumOperation() { 93 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 94 | } 95 | 96 | /** 97 | * Returns a {@link ToIntFunction} that converts string to integer. 98 | * 99 | * @return string to int converter 100 | */ 101 | public static ToIntFunction stringToIntConverter() { 102 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 103 | } 104 | 105 | /** 106 | * Receives int parameter n, and returns a {@link Supplier} that supplies {@link IntUnaryOperator} 107 | * that is a function f(x) = n * x 108 | * 109 | * @param n a multiplier 110 | * @return a function supplier 111 | */ 112 | public static Supplier nMultiplyFunctionSupplier(int n) { 113 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 114 | } 115 | 116 | /** 117 | * Returns a {@link UnaryOperator} that accepts str to str function and returns the same function composed with trim 118 | * 119 | * @return function that composes functions with trim() function 120 | */ 121 | public static UnaryOperator> composeWithTrimFunction() { 122 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 123 | } 124 | 125 | /** 126 | * Receives a {@link Runnable} parameter, and returns a {@link Supplier}. The thread will be started only 127 | * when you call supplier method {@link Supplier#get()} 128 | * 129 | * @param runnable the code you want to tun in new thread 130 | * @return a thread supplier 131 | */ 132 | public static Supplier runningThreadSupplier(Runnable runnable) { 133 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 134 | } 135 | 136 | /** 137 | * Returns a {@link Consumer} that accepts {@link Runnable} as a parameter and runs in in a new thread. 138 | * 139 | * @return a runnable consumer 140 | */ 141 | public static Consumer newThreadRunnableConsumer() { 142 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 143 | } 144 | 145 | /** 146 | * Returns a {@link Function} that accepts an instance of {@link Runnable} and returns a {@link Supplier} of a 147 | * started {@link Thread} that is created from a given {@link Runnable} 148 | * 149 | * @return a function that transforms runnable into a thread supplier 150 | */ 151 | public static Function> runnableToThreadSupplierFunction() { 152 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 153 | } 154 | 155 | /** 156 | * Returns a {@link BiFunction} that has two parameters. First is {@link IntUnaryOperator} which is some integer function. 157 | * Second is {@link IntPredicate} which is some integer condition. And the third is {@link IntUnaryOperator} which is 158 | * a new composed function that uses provided predicate (second parameter of binary function) to verify its input 159 | * parameter. If predicate returns {@code true} it applies a provided integer function 160 | * (first parameter of binary function) and returns a result value, otherwise it returns an element itself. 161 | * 162 | * @return a binary function that receiver predicate and function and compose them to create a new function 163 | */ 164 | public static BiFunction functionToConditionalFunction() { 165 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 166 | } 167 | 168 | /** 169 | * Returns a {@link BiFunction} which first parameter is a {@link Map} where key is a function name, and value is some 170 | * {@link IntUnaryOperator}, and second parameter is a {@link String} which is a function name. If the map contains a 171 | * function by a given name then it is returned by high order function otherwise an identity() is returned. 172 | * 173 | * @return a high-order function that fetches a function from a function map by a given name or returns identity() 174 | */ 175 | public static BiFunction, String, IntUnaryOperator> functionLoader() { 176 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 177 | } 178 | 179 | /** 180 | * Returns {@link Supplier} of {@link Supplier} of {@link Supplier} of {@link String} "WELL DONE". 181 | * 182 | * @return a supplier instance 183 | */ 184 | public static Supplier>> trickyWellDoneSupplier() { 185 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 186 | } 187 | } 188 | 189 | -------------------------------------------------------------------------------- /crazy-lambdas/src/test/java/com/bobocode/CrazyLambdasTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import org.junit.jupiter.api.MethodOrderer; 4 | import org.junit.jupiter.api.Order; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestMethodOrder; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Queue; 12 | import java.util.concurrent.ConcurrentLinkedQueue; 13 | import java.util.function.BiFunction; 14 | import java.util.function.Consumer; 15 | import java.util.function.Function; 16 | import java.util.function.IntPredicate; 17 | import java.util.function.IntSupplier; 18 | import java.util.function.IntUnaryOperator; 19 | import java.util.function.LongBinaryOperator; 20 | import java.util.function.Predicate; 21 | import java.util.function.Supplier; 22 | import java.util.function.ToIntFunction; 23 | import java.util.function.UnaryOperator; 24 | 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertFalse; 27 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | @TestMethodOrder( MethodOrderer.OrderAnnotation.class) 31 | public class CrazyLambdasTest { 32 | 33 | @Test 34 | @Order(1) 35 | void testHelloSupplier() { 36 | Supplier helloSupplier = CrazyLambdas.helloSupplier(); 37 | 38 | assertEquals("Hello", helloSupplier.get()); 39 | } 40 | 41 | 42 | @Test 43 | @Order(2) 44 | void testIsEmptyPredicate() { 45 | Predicate isEmptyPredicate = CrazyLambdas.isEmptyPredicate(); 46 | 47 | boolean nonEmptyStringResult = isEmptyPredicate.test("fasdfa"); 48 | boolean emptyStringResult = isEmptyPredicate.test(""); 49 | 50 | assertFalse(nonEmptyStringResult); 51 | assertTrue(emptyStringResult); 52 | } 53 | 54 | @Test 55 | @Order(3) 56 | void testStringMultiplier() { 57 | BiFunction stringMultiplier = CrazyLambdas.stringMultiplier(); 58 | 59 | String threeTimesHi = stringMultiplier.apply("Hi", 3); 60 | String twoTimesHello = stringMultiplier.apply("Hello", 2); 61 | 62 | assertEquals("HiHiHi", threeTimesHi); 63 | assertEquals("HelloHello", twoTimesHello); 64 | } 65 | 66 | @Test 67 | @Order(4) 68 | void testToDollarStringFunction() { 69 | Function toDollarStringFunction = CrazyLambdas.toDollarStringFunction(); 70 | String tenDollarStr = toDollarStringFunction.apply(BigDecimal.TEN.setScale(2)); 71 | 72 | assertEquals("$10.00", tenDollarStr); 73 | } 74 | 75 | @Test 76 | @Order(5) 77 | void testLengthInRangePredicate() { 78 | Predicate lengthInRangePredicate = CrazyLambdas.lengthInRangePredicate(4, 10); 79 | 80 | boolean twoLetterStringResult = lengthInRangePredicate.test("Hi"); 81 | boolean fourLetterStringResult = lengthInRangePredicate.test("Hola"); 82 | boolean fiveLetterStringResult = lengthInRangePredicate.test("Amigo"); 83 | boolean eightLetterStringResult = lengthInRangePredicate.test("Lalaland"); 84 | boolean thirteenLetterStringResult = lengthInRangePredicate.test("Lambda rocks!"); 85 | 86 | assertFalse(twoLetterStringResult); 87 | assertTrue(fourLetterStringResult); 88 | assertTrue(fiveLetterStringResult); 89 | assertTrue(eightLetterStringResult); 90 | assertFalse(thirteenLetterStringResult); 91 | } 92 | 93 | @Test 94 | @Order(6) 95 | void testRandomIntSupplier() { 96 | IntSupplier randomIntSupplier = CrazyLambdas.randomIntSupplier(); 97 | 98 | int firstValue = randomIntSupplier.getAsInt(); 99 | int secondValue = randomIntSupplier.getAsInt(); 100 | 101 | assertNotEquals(firstValue, secondValue); 102 | } 103 | 104 | @Test 105 | @Order(7) 106 | void testBoundedRandomIntSupplier() { 107 | IntUnaryOperator boundedRandomIntSupplier = CrazyLambdas.boundedRandomIntSupplier(); 108 | 109 | int randomIntLessThan10 = boundedRandomIntSupplier.applyAsInt(10); 110 | int randomIntLessThan100 = boundedRandomIntSupplier.applyAsInt(100); 111 | int randomIntLessThan1000 = boundedRandomIntSupplier.applyAsInt(1000); 112 | int randomIntLessThan10000 = boundedRandomIntSupplier.applyAsInt(1000); 113 | 114 | assertTrue(randomIntLessThan10 < 10); 115 | assertTrue(randomIntLessThan100 < 100); 116 | assertTrue(randomIntLessThan1000 < 1000); 117 | assertTrue(randomIntLessThan10000 < 10000); 118 | } 119 | 120 | @Test 121 | @Order(8) 122 | void testIntSquareOperation() { 123 | IntUnaryOperator squareOperation = CrazyLambdas.intSquareOperation(); 124 | 125 | int squareOfFour = squareOperation.applyAsInt(4); 126 | int squareOfZero = squareOperation.applyAsInt(0); 127 | 128 | assertEquals(16, squareOfFour); 129 | assertEquals(0, squareOfZero); 130 | } 131 | 132 | @Test 133 | @Order(9) 134 | void testLongSumOperation() { 135 | LongBinaryOperator sumOperation = CrazyLambdas.longSumOperation(); 136 | 137 | 138 | long sumOfSevenAndEight = sumOperation.applyAsLong(7, 8); 139 | long sumOfTenAndZero = sumOperation.applyAsLong(10, 0); 140 | long sumOfFiveAndMinusTen = sumOperation.applyAsLong(5, -10); 141 | 142 | assertEquals(15, sumOfSevenAndEight); 143 | assertEquals(10, sumOfTenAndZero); 144 | assertEquals(-5, sumOfFiveAndMinusTen); 145 | } 146 | 147 | @Test 148 | @Order(10) 149 | void testStringToIntConverter() { 150 | ToIntFunction stringToIntConverter = CrazyLambdas.stringToIntConverter(); 151 | 152 | int num = stringToIntConverter.applyAsInt("234"); 153 | int negativeNum = stringToIntConverter.applyAsInt("-122"); 154 | 155 | assertEquals(234, num); 156 | assertEquals(-122, negativeNum); 157 | } 158 | 159 | @Test 160 | @Order(11) 161 | void testNMultiplyFunctionSupplier() { 162 | Supplier fiveMultiplyFunctionSupplier = CrazyLambdas.nMultiplyFunctionSupplier(5); 163 | 164 | IntUnaryOperator multiplyByFiveOperation = fiveMultiplyFunctionSupplier.get(); 165 | int result = multiplyByFiveOperation.applyAsInt(11); // 11 * 5 = 55 166 | 167 | assertEquals(55, result); 168 | } 169 | 170 | @Test 171 | @Order(12) 172 | void testComposeWithTrimFunction() { 173 | UnaryOperator> composeWithTrimFunction = CrazyLambdas.composeWithTrimFunction(); 174 | Function toLowerWithTrim = composeWithTrimFunction.apply(String::toLowerCase); 175 | Function threeTimesRepeatWithTrim = composeWithTrimFunction.apply(s -> s.repeat(3)); 176 | 177 | String hey = toLowerWithTrim.apply(" Hey "); 178 | String threeTimesHi = threeTimesRepeatWithTrim.apply(" Hi "); 179 | 180 | assertEquals("hey", hey); 181 | assertEquals("HiHiHi", threeTimesHi); 182 | } 183 | 184 | @Test 185 | @Order(13) 186 | void testRunningThreadSupplier() throws InterruptedException { 187 | Queue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); 188 | Supplier runningThreadSupplier = CrazyLambdas.runningThreadSupplier(() -> concurrentLinkedQueue.add(25)); 189 | 190 | // supplier does not create and start a thread before you call get() 191 | assertEquals(0, concurrentLinkedQueue.size()); 192 | 193 | Thread runningThread = runningThreadSupplier.get(); // new thread has been started 194 | runningThread.join(); 195 | 196 | assertEquals(1, concurrentLinkedQueue.size()); 197 | assertEquals(25, concurrentLinkedQueue.element().intValue()); 198 | } 199 | 200 | @Test 201 | @Order(14) 202 | void testNewThreadRunnableConsumer() throws InterruptedException { 203 | Consumer newThreadRunnableConsumer = CrazyLambdas.newThreadRunnableConsumer(); 204 | 205 | Queue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); 206 | newThreadRunnableConsumer.accept(() -> concurrentLinkedQueue.add(50)); 207 | 208 | Thread.sleep(500); // don't do that in real code 209 | 210 | assertEquals(1, concurrentLinkedQueue.size()); 211 | assertEquals(50, concurrentLinkedQueue.element().intValue()); 212 | } 213 | 214 | @Test 215 | @Order(15) 216 | void testRunnableToThreadSupplierFunction() throws InterruptedException { 217 | Function> runnableSupplierFunction = CrazyLambdas.runnableToThreadSupplierFunction(); 218 | Queue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); 219 | 220 | Supplier threadSupplier = runnableSupplierFunction.apply(() -> concurrentLinkedQueue.add(200)); 221 | 222 | assertEquals(0, concurrentLinkedQueue.size()); // supplier does not create and start a thread before you call get() 223 | 224 | Thread thread = threadSupplier.get();// new thread has been started 225 | thread.join(); 226 | 227 | assertEquals(1, concurrentLinkedQueue.size()); 228 | assertEquals(200, concurrentLinkedQueue.element().intValue()); 229 | } 230 | 231 | @Test 232 | @Order(16) 233 | void testFunctionToConditionalFunction() { 234 | BiFunction intFunctionToConditionalIntFunction 235 | = CrazyLambdas.functionToConditionalFunction(); 236 | 237 | IntUnaryOperator abs = intFunctionToConditionalIntFunction.apply(a -> -a, a -> a < 0); 238 | 239 | assertEquals(5, abs.applyAsInt(-5)); 240 | assertEquals(0, abs.applyAsInt(0)); 241 | assertEquals(5, abs.applyAsInt(5)); 242 | } 243 | 244 | @Test 245 | @Order(17) 246 | void testFunctionLoader() { 247 | BiFunction, String, IntUnaryOperator> functionLoader = CrazyLambdas.functionLoader(); 248 | Map functionMap = new HashMap<>(); 249 | functionMap.put("increment", x -> x + 1); 250 | functionMap.put("square", x -> x * x); 251 | 252 | IntUnaryOperator incrementFunction = functionLoader.apply(functionMap, "increment"); 253 | IntUnaryOperator squareFunction = functionLoader.apply(functionMap, "square"); 254 | IntUnaryOperator identityFunction = functionLoader.apply(functionMap, "none"); 255 | 256 | assertEquals(5, incrementFunction.applyAsInt(4)); 257 | assertEquals(9, squareFunction.applyAsInt(3)); 258 | assertEquals(10, identityFunction.applyAsInt(10)); 259 | } 260 | 261 | @Test 262 | @Order(18) 263 | void testTrickyWellDoneSupplier() { 264 | Supplier>> wellDoneSupplier = CrazyLambdas.trickyWellDoneSupplier(); 265 | 266 | String wellDoneStr = wellDoneSupplier.get().get().get(); 267 | 268 | assertEquals("WELL DONE!", wellDoneStr); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /crazy-optionals/README.md: -------------------------------------------------------------------------------- 1 | # Crazy Optionals exercise :muscle: 2 | Improve your Optional API skills 3 | ### Task 4 | `CrazyOptionals` class consists of static methods that require implementation. 5 | Your job is to implement the *todo* section of that class using **Optional API**. 6 | To verify your implementation, run `CrazyOptionalsTest.java` 7 | 8 | ### Pre-conditions :heavy_exclamation_mark: 9 | You're supposed to be familiar with Java 8 10 | 11 | ### How to start :question: 12 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 13 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 14 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the **implementation with explanation** 15 | 16 | ### Related materials :information_source: 17 | * [Optional у Java 9 ](https://youtu.be/YSZVyOHLbjk) 18 | * [Stream API. Optional. Курс Enterprise Java ](https://youtu.be/OD0hIs1cGmY) 19 | 20 | -------------------------------------------------------------------------------- /crazy-optionals/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | crazy-optionals 13 | 14 | 15 | 16 | com.bobocode 17 | account-data 18 | 1.0-SNAPSHOT 19 | 20 | 21 | -------------------------------------------------------------------------------- /crazy-optionals/src/main/java/com/bobocode/CrazyOptionals.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.data.Accounts; 4 | import com.bobocode.exception.AccountNotFoundException; 5 | import com.bobocode.function.AccountProvider; 6 | import com.bobocode.function.AccountService; 7 | import com.bobocode.function.CreditAccountProvider; 8 | import com.bobocode.model.Account; 9 | import com.bobocode.model.CreditAccount; 10 | 11 | import javax.annotation.Nonnull; 12 | import javax.annotation.Nullable; 13 | import java.math.BigDecimal; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.OptionalDouble; 17 | 18 | public class CrazyOptionals { 19 | 20 | /** 21 | * Creates an instance of {@link Optional} using a text parameter 22 | * 23 | * @param text 24 | * @return optional object that holds text 25 | */ 26 | public static Optional optionalOfString(@Nullable String text) { 27 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 28 | } 29 | 30 | /** 31 | * Adds a provided amount of money to the balance of an optional account. 32 | * 33 | * @param accountProvider 34 | * @param amount money to deposit 35 | */ 36 | public static void deposit(AccountProvider accountProvider, BigDecimal amount) { 37 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 38 | } 39 | 40 | /** 41 | * Creates an instance of {@link Optional} using an account parameter 42 | * 43 | * @param account 44 | * @return optional object that holds account 45 | */ 46 | public static Optional optionalOfAccount(@Nonnull Account account) { 47 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 48 | } 49 | 50 | /** 51 | * Returns the {@link Account} got from {@link AccountProvider}. If account provider does not provide an account, 52 | * returns a defaultAccount 53 | * 54 | * @param accountProvider 55 | * @param defaultAccount 56 | * @return account from provider or defaultAccount 57 | */ 58 | public static Account getAccount(AccountProvider accountProvider, Account defaultAccount) { 59 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 60 | } 61 | 62 | /** 63 | * Passes account to {@link AccountService#processAccount(Account)} when account is provided. 64 | * Otherwise calls {@link AccountService#processWithNoAccount()} 65 | * 66 | * @param accountProvider 67 | * @param accountService 68 | */ 69 | public static void processAccount(AccountProvider accountProvider, AccountService accountService) { 70 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 71 | } 72 | 73 | /** 74 | * Returns account provided by {@link AccountProvider}. If no account is provided it generates one using {@link Accounts} 75 | * Please note that additional account should not be generated if {@link AccountProvider} returned one. 76 | * 77 | * @param accountProvider 78 | * @return provided or generated account 79 | */ 80 | public static Account getOrGenerateAccount(AccountProvider accountProvider) { 81 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 82 | } 83 | 84 | /** 85 | * Retrieves a {@link BigDecimal} balance using null-safe mappings. 86 | * 87 | * @param accountProvider 88 | * @return optional balance 89 | */ 90 | public static Optional retrieveBalance(AccountProvider accountProvider) { 91 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 92 | } 93 | 94 | /** 95 | * Returns an {@link Account} provided by {@link AccountProvider}. If no account is provided, 96 | * throws {@link AccountNotFoundException} with a message "No Account provided!" 97 | * 98 | * @param accountProvider 99 | * @return provided account 100 | */ 101 | public static Account getAccount(AccountProvider accountProvider) { 102 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 103 | } 104 | 105 | /** 106 | * Retrieves a {@link BigDecimal} credit balance using null-safe mappings. 107 | * 108 | * @param accountProvider 109 | * @return optional credit balance 110 | */ 111 | public static Optional retrieveCreditBalance(CreditAccountProvider accountProvider) { 112 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 113 | } 114 | 115 | 116 | /** 117 | * Retrieves an {@link Account} with gmail email using {@link AccountProvider}. If no account is provided or it gets 118 | * not gmail then returns {@link Optional#empty()} 119 | * 120 | * @param accountProvider 121 | * @return optional gmail account 122 | */ 123 | public static Optional retrieveAccountGmail(AccountProvider accountProvider) { 124 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 125 | } 126 | 127 | /** 128 | * Retrieves account using {@link AccountProvider} and fallbackProvider. In case main provider does not provide an 129 | * {@link Account} then account should ge retrieved from fallbackProvider. In case both provider returns no account 130 | * then {@link java.util.NoSuchElementException} should be thrown. 131 | * 132 | * @param accountProvider 133 | * @param fallbackProvider 134 | * @return account got from either accountProvider or fallbackProvider 135 | */ 136 | public static Account getAccountWithFallback(AccountProvider accountProvider, AccountProvider fallbackProvider) { 137 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 138 | } 139 | 140 | /** 141 | * Returns an {@link Accounts} with the highest balance. Throws {@link java.util.NoSuchElementException} if input 142 | * list is empty 143 | * 144 | * @param accounts 145 | * @return account with the highest balance 146 | */ 147 | public static Account getAccountWithMaxBalance(List accounts) { 148 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 149 | } 150 | 151 | /** 152 | * Returns the lowest balance values or empty if accounts list is empty 153 | * 154 | * @param accounts 155 | * @return the lowest balance values 156 | */ 157 | public static OptionalDouble findMinBalanceValue(List accounts) { 158 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 159 | } 160 | 161 | /** 162 | * Finds an {@link Account} with max balance and processes it using {@link AccountService#processAccount(Account)} 163 | * 164 | * @param accounts 165 | * @param accountService 166 | */ 167 | public static void processAccountWithMaxBalance(List accounts, AccountService accountService) { 168 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 169 | } 170 | 171 | /** 172 | * Calculates a sum of {@link CreditAccount#getCreditBalance()} of all accounts 173 | * 174 | * @param accounts 175 | * @return total credit balance 176 | */ 177 | public static double calculateTotalCreditBalance(List accounts) { 178 | throw new UnsupportedOperationException("Some people say that method does not work until you implement it"); 179 | } 180 | } 181 | 182 | -------------------------------------------------------------------------------- /crazy-optionals/src/main/java/com/bobocode/exception/AccountNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class AccountNotFoundException extends RuntimeException { 4 | public AccountNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /crazy-optionals/src/main/java/com/bobocode/function/AccountProvider.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.function; 2 | 3 | import com.bobocode.model.Account; 4 | 5 | import java.util.Optional; 6 | 7 | @FunctionalInterface 8 | public interface AccountProvider { 9 | Optional getAccount(); 10 | } 11 | -------------------------------------------------------------------------------- /crazy-optionals/src/main/java/com/bobocode/function/AccountService.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.function; 2 | 3 | import com.bobocode.model.Account; 4 | 5 | @FunctionalInterface 6 | public interface AccountService { 7 | void processAccount(Account account); 8 | 9 | default void processWithNoAccount(){ 10 | /* No operation */ 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /crazy-optionals/src/main/java/com/bobocode/function/CreditAccountProvider.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.function; 2 | 3 | import com.bobocode.model.CreditAccount; 4 | 5 | import java.util.Optional; 6 | 7 | @FunctionalInterface 8 | public interface CreditAccountProvider { 9 | Optional getAccount(); 10 | } 11 | -------------------------------------------------------------------------------- /crazy-optionals/src/test/java/com/CrazyOptionalsTest.java: -------------------------------------------------------------------------------- 1 | package com; 2 | 3 | 4 | import com.bobocode.CrazyOptionals; 5 | import com.bobocode.data.Accounts; 6 | import com.bobocode.exception.AccountNotFoundException; 7 | import com.bobocode.function.AccountService; 8 | import com.bobocode.model.Account; 9 | import com.bobocode.model.CreditAccount; 10 | import org.junit.jupiter.api.MethodOrderer; 11 | import org.junit.jupiter.api.Order; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.TestMethodOrder; 14 | 15 | import java.math.BigDecimal; 16 | import java.util.Collections; 17 | import java.util.Comparator; 18 | import java.util.List; 19 | import java.util.NoSuchElementException; 20 | import java.util.Optional; 21 | import java.util.OptionalDouble; 22 | 23 | import static java.util.Comparator.comparing; 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | import static org.junit.jupiter.api.Assertions.assertFalse; 26 | import static org.junit.jupiter.api.Assertions.assertNotNull; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | import static org.mockito.ArgumentMatchers.any; 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.never; 31 | import static org.mockito.Mockito.spy; 32 | import static org.mockito.Mockito.times; 33 | import static org.mockito.Mockito.verify; 34 | 35 | @TestMethodOrder( MethodOrderer.OrderAnnotation.class ) 36 | class CrazyOptionalsTest { 37 | 38 | @Test 39 | @Order (1) 40 | void optionalOfStringShouldAcceptNull() { 41 | Optional optionalString = CrazyOptionals.optionalOfString(null); 42 | 43 | assertEquals(Optional.empty(), optionalString); 44 | } 45 | 46 | @Test 47 | @Order(2) 48 | void optionalOfStringShouldHoldSting() { 49 | Optional optionalString = CrazyOptionals.optionalOfString("Hello"); 50 | 51 | assertEquals("Hello", optionalString.get()); 52 | } 53 | 54 | @Test 55 | @Order(3) 56 | void depositWhenAccountExistsShouldUpdateBalance() { 57 | Account account = Accounts.generateAccount(); 58 | BigDecimal balanceBeforeUpdate = account.getBalance(); 59 | BigDecimal amountToAdd = BigDecimal.valueOf(1000); 60 | 61 | CrazyOptionals.deposit(() -> Optional.of(account), amountToAdd); 62 | 63 | assertEquals(balanceBeforeUpdate.add(amountToAdd), account.getBalance()); 64 | } 65 | 66 | @Test 67 | @Order(4) 68 | void depositWhenOptionalIsEmptyShouldDoNothing() { 69 | CrazyOptionals.deposit(Optional::empty, BigDecimal.ZERO); 70 | } 71 | 72 | @Test 73 | @Order(5) 74 | void optionalOfAccountShouldNotAcceptNull() { 75 | assertThrows(NullPointerException.class, () -> CrazyOptionals.optionalOfAccount(null)); 76 | } 77 | 78 | @Test 79 | @Order(6) 80 | void optionalOfAccountShouldHoldAccount() { 81 | Account account = Accounts.generateAccount(); 82 | Optional optionalAccount = CrazyOptionals.optionalOfAccount(account); 83 | 84 | assertEquals(account, optionalAccount.get()); 85 | } 86 | 87 | @Test 88 | @Order(7) 89 | void getAccountShouldReturnAccountGotFromProvider() { 90 | Account providedAccount = Accounts.generateAccount(); 91 | Account defaultAccount = Accounts.generateAccount(); 92 | 93 | Account receivedAccount = CrazyOptionals.getAccount(() -> Optional.of(providedAccount), defaultAccount); 94 | 95 | assertEquals(providedAccount, receivedAccount); 96 | } 97 | 98 | @Test 99 | @Order(8) 100 | void getAccountWhenNoAccountIsProvidedShouldReturnDefaultAccount() { 101 | Account defaultAccount = Accounts.generateAccount(); 102 | 103 | Account receivedAccount = CrazyOptionals.getAccount(Optional::empty, defaultAccount); 104 | 105 | assertEquals(defaultAccount, receivedAccount); 106 | } 107 | 108 | @Test 109 | @Order(9) 110 | void processAccountWhenAccountIsProvidedShouldPassAccountToService() { 111 | Account account = Accounts.generateAccount(); 112 | BigDecimal initialBalance = account.getBalance(); 113 | BigDecimal bonus = BigDecimal.valueOf(1000); 114 | 115 | CrazyOptionals.processAccount(() -> Optional.of(account), a -> a.setBalance(account.getBalance().add(bonus))); 116 | 117 | assertEquals(initialBalance.add(bonus), account.getBalance()); 118 | } 119 | 120 | @Test 121 | @Order(10) 122 | void processAccountWhenNoAccountProvidedShouldProcessWithNoAccount() { 123 | Optional optionalAccountSpy = spy(Optional.empty()); 124 | AccountService accountService = mock(AccountService.class); 125 | 126 | CrazyOptionals.processAccount(() -> optionalAccountSpy, accountService); 127 | 128 | verify(accountService, times(1)).processWithNoAccount(); 129 | verify(accountService, never()).processAccount(any()); 130 | 131 | } 132 | 133 | @Test 134 | @Order(11) 135 | void processAccountShouldUseNullSafeDeclarativeIfStatement() { 136 | Optional optionalAccountSpy = spy(Optional.empty()); 137 | 138 | CrazyOptionals.processAccount(() -> optionalAccountSpy, account -> { 139 | }); 140 | 141 | verify(optionalAccountSpy, times(1)).ifPresentOrElse(any(), any()); 142 | } 143 | 144 | @Test 145 | @Order(12) 146 | void getOrGenerateAccountShouldReturnAccountGotFromProvider() { 147 | Account providedAccount = Accounts.generateAccount(); 148 | 149 | Account receivedAccount = CrazyOptionals.getOrGenerateAccount(() -> Optional.of(providedAccount)); 150 | 151 | assertEquals(providedAccount, receivedAccount); 152 | } 153 | 154 | @Test 155 | @Order(13) 156 | void getOrGenerateAccountWhenNoAccountIsProvidedShouldGenerateAccount() { 157 | Account receivedAccount = CrazyOptionals.getOrGenerateAccount(Optional::empty); 158 | 159 | assertNotNull(receivedAccount); 160 | } 161 | 162 | @Test 163 | @Order(14) 164 | void getOrGenerateAccountWhenNoAccountIsProvidedShouldUseLazyInitialization() { 165 | Optional optionalAccountSpy = spy(Optional.empty()); 166 | 167 | CrazyOptionals.getOrGenerateAccount(() -> optionalAccountSpy); 168 | 169 | verify(optionalAccountSpy, never()).orElse(any()); 170 | verify(optionalAccountSpy, never()).get(); 171 | verify(optionalAccountSpy, times(1)).orElseGet(any()); 172 | } 173 | 174 | @Test 175 | @Order(15) 176 | void retrieveBalanceWhenAccountIsNotProvidedShouldReturnEmptyOptional() { 177 | Optional balance = CrazyOptionals.retrieveBalance(Optional::empty); 178 | 179 | assertFalse(balance.isPresent()); 180 | } 181 | 182 | @Test 183 | @Order(16) 184 | void retrieveBalanceWhenBalanceIsNullShouldReturnEmptyOptional() { 185 | Account account = Accounts.generateAccount(); 186 | account.setBalance(null); 187 | 188 | Optional balance = CrazyOptionals.retrieveBalance(() -> Optional.of(account)); 189 | 190 | assertFalse(balance.isPresent()); 191 | } 192 | 193 | @Test 194 | @Order(17) 195 | void retrieveBalanceShouldReturnOptionalBalance() { 196 | Account account = Accounts.generateAccount(); 197 | 198 | Optional balance = CrazyOptionals.retrieveBalance(() -> Optional.of(account)); 199 | 200 | assertEquals(account.getBalance(), balance.get()); 201 | } 202 | 203 | @Test 204 | @Order(18) 205 | void retrieveBalanceShouldUseNullSafeMapping() { 206 | Account account = Accounts.generateAccount(); 207 | Optional optionalAccountSpy = spy(Optional.of(account)); 208 | 209 | CrazyOptionals.retrieveBalance(() -> optionalAccountSpy); 210 | 211 | verify(optionalAccountSpy, never()).get(); 212 | verify(optionalAccountSpy, never()).isEmpty(); 213 | verify(optionalAccountSpy, times(1)).isPresent(); 214 | verify(optionalAccountSpy, never()).orElse(any()); 215 | verify(optionalAccountSpy, never()).orElseGet(any()); 216 | verify(optionalAccountSpy, times(1)).map(any()); 217 | } 218 | 219 | @Test 220 | @Order(19) 221 | void getAccountShouldReturnProvidedAccount() { 222 | Account account = Accounts.generateAccount(); 223 | 224 | Account receivedAccount = CrazyOptionals.getAccount(() -> Optional.of(account)); 225 | 226 | assertEquals(account, receivedAccount); 227 | } 228 | 229 | @Test 230 | @Order(20) 231 | void getAccountWhenNoAccountIsProvidedShouldThrowAccountNotFoundException() { 232 | AccountNotFoundException exception = assertThrows(AccountNotFoundException.class, 233 | () -> CrazyOptionals.getAccount(Optional::empty)); 234 | assertEquals("No Account provided!", exception.getMessage()); 235 | } 236 | 237 | @Test 238 | @Order(21) 239 | void retrieveCreditBalanceWhenAccountIsNotProvidedShouldReturnEmptyOptional() { 240 | Optional creditBalance = CrazyOptionals.retrieveCreditBalance(Optional::empty); 241 | 242 | assertFalse(creditBalance.isPresent()); 243 | } 244 | 245 | @Test 246 | @Order(22) 247 | void retrieveCreditBalanceWhenBalanceIsNullShouldReturnEmptyOptional() { 248 | CreditAccount account = Accounts.generateCreditAccount(); 249 | account.setCreditBalance(null); 250 | 251 | Optional creditBalance = CrazyOptionals.retrieveCreditBalance(() -> Optional.of(account)); 252 | 253 | assertFalse(creditBalance.isPresent()); 254 | } 255 | 256 | @Test 257 | @Order(23) 258 | void retrieveCreditBalanceShouldReturnOptionalBalance() { 259 | CreditAccount account = Accounts.generateCreditAccount(); 260 | 261 | Optional creditBalance = CrazyOptionals.retrieveCreditBalance(() -> Optional.of(account)); 262 | 263 | assertEquals(account.getCreditBalance(), creditBalance); 264 | } 265 | 266 | @Test 267 | @Order(24) 268 | void retrieveCreditBalanceShouldUseNullSafeMapping() { 269 | CreditAccount creditAccount = Accounts.generateCreditAccount(); 270 | Optional optionalCreditAccountSpy = spy(Optional.of(creditAccount)); 271 | 272 | CrazyOptionals.retrieveCreditBalance(() -> optionalCreditAccountSpy); 273 | 274 | verify(optionalCreditAccountSpy, never()).get(); 275 | verify(optionalCreditAccountSpy, never()).isEmpty(); 276 | verify(optionalCreditAccountSpy, times(1)).isPresent(); 277 | verify(optionalCreditAccountSpy, never()).orElse(any()); 278 | verify(optionalCreditAccountSpy, never()).orElseGet(any()); 279 | verify(optionalCreditAccountSpy, times(1)).flatMap(any()); 280 | } 281 | 282 | @Test 283 | @Order(25) 284 | void retrieveAccountGmailWhenNoAccountProvidedShouldReturnEmptyOptional() { 285 | Optional optionalGmailAccount = CrazyOptionals.retrieveAccountGmail(Optional::empty); 286 | 287 | assertEquals(Optional.empty(), optionalGmailAccount); 288 | } 289 | 290 | @Test 291 | @Order(26) 292 | void retrieveAccountGmailWhenAccountEmailIsNotGmailShouldReturnEmptyOptional() { 293 | Account account = Accounts.generateCreditAccount(); 294 | account.setEmail("bobby@yahoo.com"); 295 | 296 | Optional optionalGmailAccount = CrazyOptionals.retrieveAccountGmail(() -> Optional.of(account)); 297 | 298 | assertEquals(Optional.empty(), optionalGmailAccount); 299 | } 300 | 301 | @Test 302 | @Order(27) 303 | void retrieveAccountGmailWhenEmailIsGmailShouldReturnEmail() { 304 | Account account = Accounts.generateCreditAccount(); 305 | account.setEmail("johnny@gmail.com"); 306 | 307 | Optional optionalGmailAccount = CrazyOptionals.retrieveAccountGmail(() -> Optional.of(account)); 308 | 309 | assertEquals(account, optionalGmailAccount.get()); 310 | } 311 | 312 | @Test 313 | @Order(28) 314 | void retrieveAccountGmailShouldUseNullSafeFiltering() { 315 | Account account = Accounts.generateAccount(); 316 | account.setEmail("johnny@gmail.com"); 317 | Optional optionalAccountSpy = spy(Optional.of(account)); 318 | 319 | CrazyOptionals.retrieveAccountGmail(() -> optionalAccountSpy); 320 | 321 | verify(optionalAccountSpy, never()).get(); 322 | verify(optionalAccountSpy, times(1)).isPresent(); 323 | verify(optionalAccountSpy, never()).isEmpty(); 324 | verify(optionalAccountSpy, never()).orElse(any()); 325 | verify(optionalAccountSpy, times(1)).filter(any()); 326 | } 327 | 328 | @Test 329 | @Order(29) 330 | void getAccountWithFallbackShouldBeRetrievedFromMainProvider() { 331 | Account account = Accounts.generateAccount(); 332 | Account fallbackAccount = Accounts.generateAccount(); 333 | 334 | Account retrievedAccount = CrazyOptionals.getAccountWithFallback(() -> Optional.of(account), () -> Optional.of(fallbackAccount)); 335 | 336 | assertEquals(account, retrievedAccount); 337 | } 338 | 339 | @Test 340 | @Order(30) 341 | void getAccountWithFallbackWhenNoAccountIsProvidedByMainProviderShouldUseFallback() { 342 | Account fallbackAccount = Accounts.generateAccount(); 343 | 344 | Account retrievedAccount = CrazyOptionals.getAccountWithFallback(Optional::empty, () -> Optional.of(fallbackAccount)); 345 | 346 | assertEquals(fallbackAccount, retrievedAccount); 347 | } 348 | 349 | @Test 350 | @Order(31) 351 | void getAccountWithFallbackWhenNoAccountShouldThrowException() { 352 | assertThrows(NoSuchElementException.class, 353 | () -> CrazyOptionals.getAccountWithFallback(Optional::empty, Optional::empty)); 354 | } 355 | 356 | @Test 357 | @Order(32) 358 | void getAccountWithFallbackShouldUseNullSafeFallbackStrategy() { 359 | Optional optionalAccountSpy = spy(Optional.empty()); 360 | 361 | CrazyOptionals.getAccountWithFallback(() -> optionalAccountSpy, () -> Optional.of(Accounts.generateAccount())); 362 | 363 | verify(optionalAccountSpy, times(1)).isPresent(); 364 | verify(optionalAccountSpy, never()).isEmpty(); 365 | verify(optionalAccountSpy, never()).get(); 366 | verify(optionalAccountSpy, never()).orElse(any()); 367 | verify(optionalAccountSpy, never()).orElseGet(any()); 368 | verify(optionalAccountSpy, times(1)).or(any()); 369 | } 370 | 371 | @Test 372 | @Order(33) 373 | void getAccountWithMaxBalance() { 374 | List accounts = Accounts.generateAccountList(5); 375 | Account richestAccount = getMaxAccount(accounts, comparing(Account::getBalance)); 376 | 377 | Account receivedAccount = CrazyOptionals.getAccountWithMaxBalance(accounts); 378 | 379 | assertEquals(richestAccount, receivedAccount); 380 | } 381 | 382 | @Test 383 | @Order(34) 384 | void getAccountWithMaxBalanceWhenListIsEmptyShouldThrowException() { 385 | assertThrows(NoSuchElementException.class, 386 | () -> CrazyOptionals.getAccountWithMaxBalance(Collections.emptyList())); 387 | } 388 | 389 | @Test 390 | @Order(35) 391 | void findMinBalanceValueShouldReturnCorrectDoubleValue() { 392 | List accounts = Accounts.generateAccountList(5); 393 | Account accountWithLowestBalance = getMaxAccount(accounts, comparing(Account::getBalance).reversed()); 394 | double expectedBalance = accountWithLowestBalance.getBalance().doubleValue(); 395 | 396 | OptionalDouble optionalMinBalance = CrazyOptionals.findMinBalanceValue(accounts); 397 | 398 | assertEquals(expectedBalance, optionalMinBalance.getAsDouble(), 0.001); 399 | } 400 | 401 | @Test 402 | @Order(36) 403 | void findMinBalanceValueWhenListIsEmptyShouldReturnOptionalEmpty() { 404 | OptionalDouble optionalMinBalance = CrazyOptionals.findMinBalanceValue(Collections.emptyList()); 405 | 406 | assertEquals(OptionalDouble.empty(), optionalMinBalance); 407 | } 408 | 409 | @Test 410 | @Order(37) 411 | void processAccountWithMaxBalance() { 412 | List accounts = Accounts.generateAccountList(5); 413 | Account richestAccount = getMaxAccount(accounts, comparing(Account::getBalance)); 414 | AccountService accountServiceSpy = spy(AccountService.class); 415 | 416 | CrazyOptionals.processAccountWithMaxBalance(accounts, accountServiceSpy); 417 | 418 | verify(accountServiceSpy, times(1)).processAccount(richestAccount); 419 | } 420 | 421 | @Test 422 | @Order(38) 423 | void calculateTotalCreditBalanceShouldCalculateCorrectTotal() { 424 | List accounts = Accounts.generateCreditAccountList(5); 425 | double expectedTotal = calculateTotalCreditBalance(accounts); 426 | 427 | double calculatedTotal = CrazyOptionals.calculateTotalCreditBalance(accounts); 428 | 429 | assertEquals(expectedTotal, calculatedTotal, 0.001); 430 | } 431 | 432 | @Test 433 | @Order(39) 434 | void calculateTotalCreditBalanceWhenListIsEmptyShouldReturnZero() { 435 | double calculatedTotal = CrazyOptionals.calculateTotalCreditBalance(Collections.emptyList()); 436 | 437 | assertEquals(0.0, calculatedTotal, 0.001); 438 | } 439 | 440 | private Account getMaxAccount(List accounts, Comparator comparator) { 441 | return accounts.stream() 442 | .max(comparator) 443 | .orElseThrow(); 444 | } 445 | 446 | private double calculateTotalCreditBalance(List accounts) { 447 | return accounts.stream() 448 | .map(CreditAccount::getCreditBalance) 449 | .flatMap(Optional::stream) 450 | .mapToDouble(BigDecimal::doubleValue) 451 | .sum(); 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /crazy-streams/README.MD: -------------------------------------------------------------------------------- 1 | # Account analytics exercise :muscle: 2 | Improve your Stream API skills 3 | ### Task 4 | `AccountAnalytics` provides an API with a couple statistic methods for a list of accounts. Your job is to implement the *todo* section of that class using **Stream API**. 5 | To verify your implementation, run `AccountAnalyticsTest.java` 6 | 7 | ### Pre-conditions :heavy_exclamation_mark: 8 | You're supposed to be familiar with Java 8 9 | 10 | ### How to start :question: 11 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 12 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 13 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 14 | 15 | ### Related materials :information_source: 16 | * [Stream API tutorial](https://github.com/bobocode-projects/java-functional-features-tutorial/blob/master/stream-api/README.MD) 17 | * [State of lambda (JSR 335)](http://htmlpreview.github.io/?https://github.com/bobocode-projects/resources/blob/master/java8/lambda/sotl.html) 18 | 19 | -------------------------------------------------------------------------------- /crazy-streams/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | crazy-streams 13 | 14 | 15 | 16 | com.bobocode 17 | account-data 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /crazy-streams/src/main/java/com.bobocode/CrazyStreams.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.EntityNotFoundException; 4 | import com.bobocode.model.Account; 5 | 6 | import java.math.BigDecimal; 7 | import java.time.Month; 8 | import java.util.Collection; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | 14 | /** 15 | * Implement methods using Stream API 16 | */ 17 | public class CrazyStreams { 18 | private Collection accounts; 19 | 20 | public static CrazyStreams of(Collection accounts) { 21 | return new CrazyStreams(accounts); 22 | } 23 | 24 | private CrazyStreams(Collection accounts) { 25 | this.accounts = accounts; 26 | } 27 | 28 | /** 29 | * Returns {@link Optional} that contains an {@link Account} with the max value of balance 30 | * 31 | * @return account with max balance wrapped with optional 32 | */ 33 | public Optional findRichestPerson() { 34 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 35 | } 36 | 37 | /** 38 | * Returns a {@link List} of {@link Account} that have a birthday month equal to provided. 39 | * 40 | * @param birthdayMonth a month of birth 41 | * @return a list of accounts 42 | */ 43 | public List findAccountsByBirthdayMonth(Month birthdayMonth) { 44 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 45 | } 46 | 47 | /** 48 | * Returns a map that separates all accounts into two lists - male and female. Map has two keys {@code true} indicates 49 | * male list, and {@code false} indicates female list. 50 | * 51 | * @return a map where key is true or false, and value is list of male, and female accounts 52 | */ 53 | public Map> partitionMaleAccounts() { 54 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 55 | } 56 | 57 | /** 58 | * Returns a {@link Map} that stores accounts grouped by its email domain. A map key is {@link String} which is an 59 | * email domain like "gmail.com". And the value is a {@link List} of {@link Account} objects with a specific email domain. 60 | * 61 | * @return a map where key is an email domain and value is a list of all account with such email 62 | */ 63 | public Map> groupAccountsByEmailDomain() { 64 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 65 | } 66 | 67 | /** 68 | * Returns a number of letters in all first and last names. 69 | * 70 | * @return total number of letters of first and last names of all accounts 71 | */ 72 | public int getNumOfLettersInFirstAndLastNames() { 73 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 74 | } 75 | 76 | /** 77 | * Returns a total balance of all accounts. 78 | * 79 | * @return total balance of all accounts 80 | */ 81 | public BigDecimal calculateTotalBalance() { 82 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 83 | } 84 | 85 | /** 86 | * Returns a {@link List} of {@link Account} objects sorted by first and last names. 87 | * 88 | * @return list of accounts sorted by first and last names 89 | */ 90 | public List sortByFirstAndLastNames() { 91 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 92 | } 93 | 94 | /** 95 | * Checks if there is at least one account with provided email domain. 96 | * 97 | * @param emailDomain 98 | * @return true if there is an account that has an email with provided domain 99 | */ 100 | public boolean containsAccountWithEmailDomain(String emailDomain) { 101 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 102 | } 103 | 104 | /** 105 | * Returns account balance by its email. Throws {@link EntityNotFoundException} with message 106 | * "Cannot find Account by email={email}" if account is not found. 107 | * 108 | * @param email account email 109 | * @return account balance 110 | */ 111 | public BigDecimal getBalanceByEmail(String email) { 112 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 113 | } 114 | 115 | /** 116 | * Collects all existing accounts into a {@link Map} where a key is account id, and the value is {@link Account} instance 117 | * 118 | * @return map of accounts by its ids 119 | */ 120 | public Map collectAccountsById() { 121 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 122 | } 123 | 124 | /** 125 | * Filters accounts by the year when an account was created. Collects account balances by its emails into a {@link Map}. 126 | * The key is {@link Account#email} and the value is {@link Account#balance} 127 | * 128 | * @param year the year of account creation 129 | * @return map of account by its ids the were created in a particular year 130 | */ 131 | public Map collectBalancesByEmailForAccountsCreatedOn(int year) { 132 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 133 | } 134 | 135 | /** 136 | * Returns a {@link Map} where key is {@link Account#lastName} and values is a {@link Set} that contains first names 137 | * of all accounts with a specific last name. 138 | * 139 | * @return a map where key is a last name and value is a set of first names 140 | */ 141 | public Map> groupFirstNamesByLastNames() { 142 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 143 | } 144 | 145 | /** 146 | * Returns a {@link Map} where key is a birthday month, and value is a {@link String} that stores comma and space 147 | * -separated first names (e.g. "Polly, Dylan, Clark"), of all accounts that have the same birthday month. 148 | * 149 | * @return a map where a key is a birthday month and value is comma-separated first names 150 | */ 151 | public Map groupCommaSeparatedFirstNamesByBirthdayMonth() { 152 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 153 | } 154 | 155 | /** 156 | * Returns a {@link Map} where key is a {@link Month} of {@link Account#creationDate}, and value is total balance 157 | * of all accounts that have the same value creation month. 158 | * 159 | * @return a map where key is a creation month and value is total balance of all accounts created in that month 160 | */ 161 | public Map groupTotalBalanceByCreationMonth() { 162 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 163 | } 164 | 165 | /** 166 | * Returns a {@link Map} where key is a letter {@link Character}, and value is a number of its occurrences in 167 | * {@link Account#firstName}. 168 | * 169 | * @return a map where key is a letter and value is its count in all first names 170 | */ 171 | public Map getCharacterFrequencyInFirstNames() { 172 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 173 | } 174 | 175 | /** 176 | * Returns a {@link Map} where key is a letter {@link Character}, and value is a number of its occurrences ignoring 177 | * case, in all {@link Account#firstName} and {@link Account#lastName}. All letters should stored in lower case. 178 | * 179 | * @return a map where key is a letter and value is its count ignoring case in all first and last names 180 | */ 181 | public Map getCharacterFrequencyIgnoreCaseInFirstAndLastNames() { 182 | throw new UnsupportedOperationException("It's your job to implement this method"); // todo 183 | } 184 | 185 | } 186 | 187 | -------------------------------------------------------------------------------- /crazy-streams/src/main/java/com.bobocode/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /crazy-streams/src/test/java/com/bobocode/CrazyStreamsTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.EntityNotFoundException; 4 | import com.bobocode.model.Account; 5 | import com.bobocode.model.Sex; 6 | import org.junit.jupiter.api.*; 7 | 8 | import java.math.BigDecimal; 9 | import java.time.LocalDate; 10 | import java.time.Month; 11 | import java.util.Arrays; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.Set; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertFalse; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | import static org.junit.jupiter.api.Assertions.fail; 22 | 23 | /** 24 | * The helper method of this test class do not use Stream API intentionally. You should try to find a stream-based 25 | * solutions for {@link CrazyStreams} by yourself. 26 | */ 27 | @TestMethodOrder( MethodOrderer.OrderAnnotation.class) 28 | public class CrazyStreamsTest { 29 | 30 | private CrazyStreams streams; 31 | private List accounts; 32 | 33 | @BeforeEach 34 | void setUp() { 35 | accounts = Arrays.asList( 36 | new Account(1L, "Justin", "Butler", "justin.butler@gmail.com", 37 | LocalDate.parse("2003-04-17"), Sex.MALE, LocalDate.parse("2016-06-13"), BigDecimal.valueOf(172966)), 38 | new Account(2L, "Olivia", "Cardenas", "cardenas@mail.com", 39 | LocalDate.parse("1930-01-19"), Sex.FEMALE, LocalDate.parse("2014-06-21"), BigDecimal.valueOf(38029)), 40 | new Account(3L, "Nolan", "Donovan", "nolandonovan@gmail.com", 41 | LocalDate.parse("1925-04-19"), Sex.MALE, LocalDate.parse("2011-03-10"), BigDecimal.valueOf(13889)), 42 | new Account(4L, "Lucas", "Lynn", "lucas.lynn@yahoo.com", 43 | LocalDate.parse("1987-05-25"), Sex.MALE, LocalDate.parse("2009-03-05"), BigDecimal.valueOf(16980)) 44 | ); 45 | streams = CrazyStreams.of(accounts); 46 | } 47 | 48 | @Test 49 | @Order (1) 50 | void testFindRichestPerson() { 51 | Optional expectedPerson = Optional.of(accounts.get(0)); 52 | Optional actualRichestPerson = streams.findRichestPerson(); 53 | 54 | assertEquals(expectedPerson, actualRichestPerson); 55 | } 56 | 57 | @Test 58 | @Order (2) 59 | void testFindAccountsByBirthdayMonth() { 60 | List expectedList = getExpectedList(); 61 | List aprilAccounts = streams.findAccountsByBirthdayMonth(Month.APRIL); 62 | 63 | assertEquals(expectedList, aprilAccounts); 64 | } 65 | 66 | @Test 67 | @Order (3) 68 | void testSeparateMaleAccounts() { 69 | Map> expectedAccountMap = getExpectedMaleMap(); 70 | Map> maleToAccountsMap = streams.partitionMaleAccounts(); 71 | 72 | assertEquals(expectedAccountMap, maleToAccountsMap); 73 | } 74 | 75 | private Map> getExpectedMaleMap() { 76 | Map> expectedMap = new HashMap<>(2); 77 | expectedMap.put(Boolean.TRUE, Arrays.asList(accounts.get(0), accounts.get(2), accounts.get(3))); 78 | expectedMap.put(Boolean.FALSE, Arrays.asList(accounts.get(1))); 79 | return expectedMap; 80 | } 81 | 82 | private List getExpectedList() { 83 | return Arrays.asList(accounts.get(0), accounts.get(2)); 84 | } 85 | 86 | @Test 87 | @Order (4) 88 | void testGroupAccountsByEmailDomain() { 89 | Map> expectedEmailMap = getExpectedEmailMap(); 90 | Map> emailDomainToAccountsMap = streams.groupAccountsByEmailDomain(); 91 | 92 | assertEquals(expectedEmailMap, emailDomainToAccountsMap); 93 | } 94 | 95 | private Map> getExpectedEmailMap() { 96 | Map> expectedEmailMap = new HashMap<>(); 97 | expectedEmailMap.put("gmail.com", Arrays.asList(accounts.get(0), accounts.get(2))); 98 | expectedEmailMap.put("mail.com", Arrays.asList(accounts.get(1))); 99 | expectedEmailMap.put("yahoo.com", Arrays.asList(accounts.get(3))); 100 | 101 | return expectedEmailMap; 102 | } 103 | 104 | @Test 105 | @Order (5) 106 | void testGetNumOfLettersInFirstAndLastNames() { 107 | int numOfLettersInFirstAndLastNames = streams.getNumOfLettersInFirstAndLastNames(); 108 | 109 | assertEquals(47, numOfLettersInFirstAndLastNames); 110 | } 111 | 112 | @Test 113 | @Order (6) 114 | void testCalculateTotalBalance() { 115 | BigDecimal totalBalance = streams.calculateTotalBalance(); 116 | 117 | assertEquals(BigDecimal.valueOf(241864), totalBalance); 118 | } 119 | 120 | 121 | @Test 122 | @Order (7) 123 | void testSortByFirstAndLastNames() { 124 | List sortedList = streams.sortByFirstAndLastNames(); 125 | 126 | assertEquals(1L, sortedList.get(0).getId().longValue()); 127 | assertEquals(4L, sortedList.get(1).getId().longValue()); 128 | assertEquals(3L, sortedList.get(2).getId().longValue()); 129 | assertEquals(2L, sortedList.get(3).getId().longValue()); 130 | 131 | } 132 | 133 | @Test 134 | @Order (8) 135 | void testContainsAccountWithEmailDomain() { 136 | assertTrue(streams.containsAccountWithEmailDomain("gmail.com")); 137 | assertTrue(streams.containsAccountWithEmailDomain("yahoo.com")); 138 | assertFalse(streams.containsAccountWithEmailDomain("ukr.net")); 139 | } 140 | 141 | @Test 142 | @Order (9) 143 | void testGetBalanceByEmail() { 144 | Account account = accounts.get(1); 145 | BigDecimal balance = streams.getBalanceByEmail(account.getEmail()); 146 | 147 | assertEquals(account.getBalance(), balance); 148 | } 149 | 150 | @Test 151 | @Order (10) 152 | void testGetBalanceByEmailThrowsException() { 153 | String fakeEmail = "fake@mail.com"; 154 | try { 155 | streams.getBalanceByEmail(fakeEmail); 156 | fail("Should throw exception"); 157 | } catch (Exception e) { 158 | assertTrue(e instanceof EntityNotFoundException); 159 | assertEquals(String.format("Cannot find Account by email=%s", fakeEmail), e.getMessage()); 160 | } 161 | } 162 | 163 | @Test 164 | @Order (11) 165 | void testCollectAccountsById() { 166 | Map idToAccountMap = streams.collectAccountsById(); 167 | 168 | assertEquals(accounts.get(0), idToAccountMap.get(1L)); 169 | assertEquals(accounts.get(1), idToAccountMap.get(2L)); 170 | assertEquals(accounts.get(2), idToAccountMap.get(3L)); 171 | assertEquals(accounts.get(3), idToAccountMap.get(4L)); 172 | } 173 | 174 | @Test 175 | @Order (12) 176 | void testCollectBalancesByEmailForAccountsCreatedOn() { 177 | Account account = accounts.get(3); 178 | 179 | Map emailToBalanceMap = streams.collectBalancesByEmailForAccountsCreatedOn(account.getCreationDate().getYear()); 180 | 181 | assertEquals(Map.of(account.getEmail(), account.getBalance()), emailToBalanceMap); 182 | } 183 | 184 | @Test 185 | @Order (13) 186 | void testGroupFirstNamesByLastNames() { 187 | Map> lastToFirstNamesMap = streams.groupFirstNamesByLastNames(); 188 | 189 | assertEquals(4, lastToFirstNamesMap.size()); 190 | assertEquals(Set.of("Justin"), lastToFirstNamesMap.get("Butler")); 191 | assertEquals(Set.of("Olivia"), lastToFirstNamesMap.get("Cardenas")); 192 | assertEquals(Set.of("Nolan"), lastToFirstNamesMap.get("Donovan")); 193 | assertEquals(Set.of("Lucas"), lastToFirstNamesMap.get("Lynn")); 194 | } 195 | 196 | @Test 197 | @Order (14) 198 | void testGroupCommaSeparatedFirstNamesByBirthdayMonth() { 199 | Map birthdayMonthToFirstNamesMap = streams.groupCommaSeparatedFirstNamesByBirthdayMonth(); 200 | 201 | assertEquals(3, birthdayMonthToFirstNamesMap.size()); 202 | assertEquals("Olivia", birthdayMonthToFirstNamesMap.get(Month.JANUARY)); 203 | assertEquals("Justin, Nolan", birthdayMonthToFirstNamesMap.get(Month.APRIL)); 204 | assertEquals("Lucas", birthdayMonthToFirstNamesMap.get(Month.MAY)); 205 | } 206 | 207 | @Test 208 | @Order (15) 209 | void testGroupTotalBalanceByCreationMonth() { 210 | Map totalBalanceByAccountCreationMonth = streams.groupTotalBalanceByCreationMonth(); 211 | 212 | assertEquals(2, totalBalanceByAccountCreationMonth.size()); 213 | assertEquals(BigDecimal.valueOf(210995), totalBalanceByAccountCreationMonth.get(Month.JUNE)); 214 | assertEquals(BigDecimal.valueOf(30869), totalBalanceByAccountCreationMonth.get(Month.MARCH)); 215 | } 216 | 217 | @Test 218 | @Order (16) 219 | void testGetCharacterFrequencyInFirstNames() { 220 | Map characterFrequencyInFirstAndLastNames = streams.getCharacterFrequencyInFirstNames(); 221 | 222 | assertEquals(3, characterFrequencyInFirstAndLastNames.get('a').longValue()); 223 | assertEquals(1, characterFrequencyInFirstAndLastNames.get('c').longValue()); 224 | assertEquals(3, characterFrequencyInFirstAndLastNames.get('i').longValue()); 225 | assertEquals(1, characterFrequencyInFirstAndLastNames.get('J').longValue()); 226 | assertEquals(1, characterFrequencyInFirstAndLastNames.get('L').longValue()); 227 | assertEquals(2, characterFrequencyInFirstAndLastNames.get('l').longValue()); 228 | assertEquals(2, characterFrequencyInFirstAndLastNames.get('u').longValue()); 229 | } 230 | 231 | @Test 232 | @Order (17) 233 | void testGetCharacterFrequencyIgnoreCaseInFirstAndLastNames() { 234 | Map characterFrequencyInFirstAndLastNames = streams.getCharacterFrequencyIgnoreCaseInFirstAndLastNames(); 235 | 236 | assertEquals(6, characterFrequencyInFirstAndLastNames.get('a').longValue()); 237 | assertEquals(1, characterFrequencyInFirstAndLastNames.get('b').longValue()); 238 | assertEquals(2, characterFrequencyInFirstAndLastNames.get('c').longValue()); 239 | assertEquals(5, characterFrequencyInFirstAndLastNames.get('l').longValue()); 240 | assertEquals(8, characterFrequencyInFirstAndLastNames.get('n').longValue()); 241 | assertEquals(3, characterFrequencyInFirstAndLastNames.get('u').longValue()); 242 | assertEquals(1, characterFrequencyInFirstAndLastNames.get('y').longValue()); 243 | } 244 | } 245 | 246 | 247 | -------------------------------------------------------------------------------- /declarative-sum-of-squares/README.MD: -------------------------------------------------------------------------------- 1 | # Sum of squares exercise :muscle: 2 | Improve your functional programming skills 3 | ### Task 4 | `SumOfSquares` is a class that allows you to calculate a sum of squares in a provided range. It is implemented using 5 | OO approach. Your job is to **refactor the todo section using functional approach.** So the **implementation will not use 6 | mutable variables**, and all test will pass. 7 | 8 | ### Pre-conditions :heavy_exclamation_mark: 9 | You're supposed to be familiar with Java 8 10 | 11 | ### How to start :question: 12 | * Just clone the repository and start working on the **todo** section, verify your changes by running tests 13 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 14 | * Don't worry if you got stuck, checkout the branch **exercise/completed** and see the final implementation 15 | 16 | ### Related materials :information_source: 17 | * [Functional programming tutorial](https://github.com/bobocode-projects/java-functional-features-tutorial/tree/master/functional-programming-basics) 18 | 19 | -------------------------------------------------------------------------------- /declarative-sum-of-squares/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | declarative-sum-of-squares 13 | 14 | 15 | -------------------------------------------------------------------------------- /declarative-sum-of-squares/src/main/java/com/bobocode/SumOfSquares.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | 4 | import com.bobocode.exception.InvalidRangeException; 5 | 6 | 7 | /** 8 | * This class allow to calculate a sum of squares of integer number in a certain range. It was implemented using 9 | * OO approach. Your job is to refactor it using functional approach. E.g. avoid using mutable variables 10 | */ 11 | public class SumOfSquares { 12 | public static void main(String[] args) { 13 | System.out.println("Sum of squares from 5 to 10 is " + calculateSumOfSquaresInRange(5, 10)); 14 | } 15 | 16 | /** 17 | * This method calculates the sum of squares of integer in the range 18 | * 19 | * @param startInclusive first element in range 20 | * @param endInclusive last element in range 21 | * @return the sum of squares of each element in the range 22 | */ 23 | static int calculateSumOfSquaresInRange(int startInclusive, int endInclusive) { 24 | if (endInclusive < startInclusive) { 25 | throw new InvalidRangeException(); 26 | } 27 | 28 | // todo: refactor using functional approach 29 | int sumOfSquares = 0; 30 | for (int i = startInclusive; i <= endInclusive; i++) { 31 | sumOfSquares += i * i; 32 | } 33 | return sumOfSquares; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /declarative-sum-of-squares/src/main/java/com/bobocode/exception/InvalidRangeException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class InvalidRangeException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /declarative-sum-of-squares/src/test/java/com/bobocode/SumOfSquareTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.InvalidRangeException; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertThrows; 8 | 9 | public class SumOfSquareTest { 10 | 11 | @Test 12 | void testCalculateSumOfSquaresOfZero() { 13 | int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(0, 0); 14 | 15 | assertEquals(0, sumOfSquares); 16 | } 17 | 18 | @Test 19 | void testCalculateSumOfSquaresOfOne() { 20 | int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(0, 1); 21 | 22 | assertEquals(1, sumOfSquares); 23 | } 24 | 25 | @Test 26 | void testCalculateSumOfSquares() { 27 | int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(1, 5); // 1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 55 28 | 29 | assertEquals(55, sumOfSquares); 30 | } 31 | 32 | @Test 33 | void testCalculateSumOfSquaresOnNegative() { 34 | int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(-4, -2); // -4*(-4) + -3*(-3) + -2*(-2) = 29 35 | 36 | assertEquals(29, sumOfSquares); 37 | } 38 | 39 | @Test 40 | void testWithInvalidRange() { 41 | assertThrows(InvalidRangeException.class, () -> SumOfSquares.calculateSumOfSquaresInRange(4, 1)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /file-reader/README.MD: -------------------------------------------------------------------------------- 1 | # File Reader exercise :muscle: 2 | Improve your Java SE and Clean Code skills 3 | ### Task 4 | `FileReaders` is an API that allows you to read whole file content into a `String`. Your job is to 5 | implement the *todo* section of that class. 6 | 7 | ### Refactoring Task 8 | There is an dirty implementation of class `FileReaders` that **contains bad practices and code smells**. If you want to 9 | practice in **refactoring** and improve your **clean code skills** you can start from branch **exercise/dirty** instead of 10 | **master** and try to refactor existing implementation. 11 | 12 | To verify your implementation, run `FileReadersTest.java` 13 | 14 | ### Pre-conditions :heavy_exclamation_mark: 15 | You're supposed to know how to work with text files and be able to write Java code 16 | 17 | ### How to start :question: 18 | * Just clone the repository and create a branch **exercise/your_username** if you want your code to be reviewed 19 | * Start implementing the **todo** section and verify your changes by running tests 20 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 21 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 22 | 23 | ### Related materials :information_source: 24 | * [Stream API tutorial](https://github.com/bobocode-projects/java-functional-features-tutorial/tree/master/stream-api) 25 | 26 | -------------------------------------------------------------------------------- /file-reader/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | file-reader 13 | 14 | 15 | -------------------------------------------------------------------------------- /file-reader/src/main/java/com/bobocode/FileReaders.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | /** 4 | * {@link FileReaders} privides an API that allow to read whole file into a {@link String} by file name. 5 | */ 6 | public class FileReaders { 7 | 8 | /** 9 | * Returns a {@link String} that contains whole text from the file specified by name. 10 | * 11 | * @param fileName a name of a text file 12 | * @return string that holds whole file content 13 | */ 14 | public static String readWholeFile(String fileName) { 15 | throw new UnsupportedOperationException("It's your job to make it work!"); //todo 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /file-reader/src/test/java/com/bobocode/FileReadersTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | 8 | public class FileReadersTest { 9 | 10 | @Test 11 | void testReadWholeFileOnEmptyFile() { 12 | String fileContent = FileReaders.readWholeFile("empty.txt"); 13 | 14 | assertEquals("", fileContent); 15 | 16 | } 17 | 18 | @Test 19 | void testReadWholeFileOnFileWithEmptyLines() { 20 | String fileContent = FileReaders.readWholeFile("lines.txt"); 21 | 22 | assertEquals("Hey!\n" + 23 | "\n" + 24 | "What's up?\n" + 25 | "\n" + 26 | "Hi!", fileContent); 27 | } 28 | 29 | @Test 30 | void testReadWholeFile() { 31 | String fileContent = FileReaders.readWholeFile("simple.txt"); 32 | 33 | assertEquals("Hello!\n" + "It's a test file.", fileContent); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /file-reader/src/test/resources/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobocode-projects/java-core-exercises/82165b3cec5aaa5a4beaa4a3ac4ac788628368fa/file-reader/src/test/resources/empty.txt -------------------------------------------------------------------------------- /file-reader/src/test/resources/lines.txt: -------------------------------------------------------------------------------- 1 | Hey! 2 | 3 | What's up? 4 | 5 | Hi! -------------------------------------------------------------------------------- /file-reader/src/test/resources/simple.txt: -------------------------------------------------------------------------------- 1 | Hello! 2 | It's a test file. -------------------------------------------------------------------------------- /file-stats/README.MD: -------------------------------------------------------------------------------- 1 | # Files Stats exercise :muscle: 2 | Improve your Java SE skills 3 | ### Task 4 | `FileStats` is an API that allows you to get character statistics based on text file. Your job is to 5 | implement the *todo* section of that class. Please note, that each object should be immutable and should not allow to 6 | reload or refresh input data. 7 | 8 | To verify your implementation, run `FileStatsTest.java` 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to know how to work with text files and be able to write Java code 12 | 13 | ### How to start :question: 14 | * Just clone the repository and create a branch **exercise/your_username** if you want your code to be reviewed 15 | * Start implementing the **todo** section and verify your changes by running tests 16 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 17 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 18 | 19 | ### Related materials :information_source: 20 | * [Stream API tutorial](https://github.com/bobocode-projects/java-functional-features-tutorial/tree/master/stream-api) 21 | 22 | -------------------------------------------------------------------------------- /file-stats/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | file-stats 13 | 14 | 15 | -------------------------------------------------------------------------------- /file-stats/src/main/java/com/bobocode/FileStats.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | /** 4 | * {@link FileStats} provides an API that allow to get character statistic based on text file. All whitespace characters 5 | * are ignored. 6 | */ 7 | public class FileStats { 8 | /** 9 | * Creates a new immutable {@link FileStats} objects using data from text file received as a parameter. 10 | * 11 | * @param fileName input text file name 12 | * @return new FileStats object created from text file 13 | */ 14 | public static FileStats from(String fileName) { 15 | throw new UnsupportedOperationException("It's your job to make it work!"); //todo 16 | } 17 | 18 | /** 19 | * Returns a number of occurrences of the particular character. 20 | * 21 | * @param character a specific character 22 | * @return a number that shows how many times this character appeared in a text file 23 | */ 24 | public int getCharCount(char character) { 25 | throw new UnsupportedOperationException("It's your job to make it work!"); //todo 26 | } 27 | 28 | /** 29 | * Returns a character that appeared most often in the text. 30 | * 31 | * @return the most frequently appeared character 32 | */ 33 | public char getMostPopularCharacter() { 34 | throw new UnsupportedOperationException("It's your job to make it work!"); //todo 35 | } 36 | 37 | /** 38 | * Returns {@code true} if this character has appeared in the text, and {@code false} otherwise 39 | * 40 | * @param character a specific character to check 41 | * @return {@code true} if this character has appeared in the text, and {@code false} otherwise 42 | */ 43 | public boolean containsCharacter(char character) { 44 | throw new UnsupportedOperationException("It's your job to make it work!"); //todo 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /file-stats/src/main/java/com/bobocode/FileStatsException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | public class FileStatsException extends RuntimeException{ 4 | public FileStatsException(String message) { 5 | super(message); 6 | } 7 | 8 | public FileStatsException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /file-stats/src/test/java/com/bobocode/FileStatsTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | public class FileStatsTest { 11 | 12 | @Test 13 | void testCreateFileStatsFromExistingFile() { 14 | FileStats fileStats = FileStats.from("sotl.txt"); 15 | } 16 | 17 | @Test 18 | void testCreateFileStatsFromNonExistingFile() { 19 | Assertions.assertThrows(FileStatsException.class, () -> FileStats.from("blahblah.txt")); 20 | } 21 | 22 | @Test 23 | void testGetCharCount() { 24 | FileStats lambdaArticleFileStats = FileStats.from("sotl.txt"); 25 | FileStats springCloudArticleFileStats = FileStats.from("scosb.txt"); 26 | 27 | int aCharCountInLambdaArticle = lambdaArticleFileStats.getCharCount('a'); 28 | int bCharCountInSpringArticle = springCloudArticleFileStats.getCharCount('b'); 29 | 30 | assertEquals(2345, aCharCountInLambdaArticle); 31 | assertEquals(4, bCharCountInSpringArticle); 32 | } 33 | 34 | @Test 35 | void testGetMostPopularCharacter() { 36 | FileStats lambdaArticleFileStats = FileStats.from("sotl.txt"); 37 | FileStats springCloudArticleFileStats = FileStats.from("scosb.txt"); 38 | 39 | char mostPopularCharacterInLambdaArticle = lambdaArticleFileStats.getMostPopularCharacter(); 40 | char mostPopularCharacterInSpringArticle = springCloudArticleFileStats.getMostPopularCharacter(); 41 | 42 | System.out.println(mostPopularCharacterInSpringArticle); 43 | 44 | assertEquals('e', mostPopularCharacterInLambdaArticle); 45 | assertEquals('e', mostPopularCharacterInSpringArticle); 46 | } 47 | 48 | @Test 49 | void testContainsCharacter() { 50 | FileStats lambdaArticleFileStats = FileStats.from("sotl.txt"); 51 | FileStats springCloudArticleFileStats = FileStats.from("scosb.txt"); 52 | 53 | boolean lambdaArticleContainsExistingCharacter = lambdaArticleFileStats.containsCharacter('a'); 54 | boolean lambdaArticleContainsWhitespace = lambdaArticleFileStats.containsCharacter(' '); 55 | boolean springArticleContainsExistingCharacter = springCloudArticleFileStats.containsCharacter('b'); 56 | boolean springArticleContainsWhitespace = springCloudArticleFileStats.containsCharacter(' '); 57 | 58 | assertTrue(lambdaArticleContainsExistingCharacter); 59 | assertFalse(lambdaArticleContainsWhitespace); 60 | assertTrue(springArticleContainsExistingCharacter); 61 | assertFalse(springArticleContainsWhitespace); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /file-stats/src/test/resources/scosb.txt: -------------------------------------------------------------------------------- 1 | We’re pleased to announce that the 2.0.1 release of Spring Cloud Open Service Broker is now available. This release resolves a few issues that were raised since the 2.0.0 release. Thank you to the community for your interest and feedback! 2 | 3 | Spring Cloud Open Service Broker is a framework for building Spring Boot applications that implement the Open Service Broker API. The Open Service Broker API project allows developers to deliver services to applications running within cloud native platforms such as Cloud Foundry, Kubernetes, and OpenShift. -------------------------------------------------------------------------------- /file-stats/src/test/resources/sotl.txt: -------------------------------------------------------------------------------- 1 | State of the Lambda 2 | September 2013 3 | Java SE 8 Edition 4 | This is an informal overview of the enhancements to the Java programming language specified by JSR 335 and implemented in the OpenJDK Lambda Project. It refines the previous iteration posted in December 2011. A formal description of some of the language changes may be found in the Early Draft Specification for the JSR; an OpenJDK Developer Preview is also available. Additional historical design documents can be found at the OpenJDK project page. There is also a companion document, State of the Lambda, Libraries Edition, describing the library enhancements added as part of JSR 335. 5 | 6 | The high-level goal of Project Lambda is to enable programming patterns that require modeling code as data to be convenient and idiomatic in Java. The principal new language features include: 7 | 8 | Lambda expressions (informally, "closures" or "anonymous methods") 9 | Method and constructor references 10 | Expanded target typing and type inference 11 | Default and static methods in interfaces 12 | These are described and illustrated below. 13 | 14 | 1. Background 15 | Java is, primarily, an object-oriented programming language. In both object-oriented and functional languages, basic values can dynamically encapsulate program behavior: object-oriented languages have objects with methods, and functional languages have functions. This similarity may not be obvious, however, because Java objects tend to be relatively heavyweight: instantiations of separately-declared classes wrapping a handful of fields and many methods. 16 | 17 | Yet it is common for some objects to essentially encode nothing more than a function. In a typical use case, a Java API defines an interface, sometimes described as a "callback interface," expecting the user to provide an instance of the interface when invoking the API. For example: 18 | 19 | public interface ActionListener { 20 | void actionPerformed(ActionEvent e); 21 | } 22 | Rather than declaring a class that implements ActionListener for the sole purpose of allocating it once at an invocation site, a user typically instantiates the implementing class inline, anonymously: 23 | 24 | button.addActionListener(new ActionListener() { 25 | public void actionPerformed(ActionEvent e) { 26 | ui.dazzle(e.getModifiers()); 27 | } 28 | }); 29 | Many useful libraries rely on this pattern. It is particularly important for parallel APIs, in which the code to execute must be expressed independently of the thread in which it will run. The parallel-programming domain is of special interest, because as Moore's Law continues to give us more cores but not faster cores, serial APIs are limited to a shrinking fraction of available processing power. 30 | 31 | Given the increasing relevance of callbacks and other functional-style idioms, it is important that modeling code as data in Java be as lightweight as possible. In this respect, anonymous inner classes are imperfect for a number of reasons, primarily: 32 | 33 | Bulky syntax 34 | Confusion surrounding the meaning of names and this 35 | Inflexible class-loading and instance-creation semantics 36 | Inability to capture non-final local variables 37 | Inability to abstract over control flow 38 | This project addresses many of these issues. It eliminates (1) and (2) by introducing new, much more concise expression forms with local scoping rules, sidesteps (3) by defining the semantics of the new expressions in a more flexible, optimization-friendly manner, and ameliorates (4) by allowing the compiler to infer finality (allowing capture of effectively final local variables). 39 | 40 | However, it is not a goal of this project to address all the problems of inner classes. Neither arbitrary capture of mutable variables (4) nor nonlocal control flow (5) are within this project's scope (though such features may be revisited in a future iteration of the language.) 41 | 42 | 2. Functional interfaces 43 | The anonymous inner class approach, despite its limitations, has the nice property of fitting very cleanly into Java's type system: a function value with an interface type. This is convenient for a number of reasons: interfaces are already an intrinsic part of the type system; they naturally have a runtime representation; and they carry with them informal contracts expressed by Javadoc comments, such as an assertion that an operation is commutative. 44 | 45 | The interface ActionListener, used above, has just one method. Many common callback interfaces have this property, such as Runnable and Comparator. We'll give all interfaces that have just one method a name: functional interfaces. (These were previously called SAM Types, which stood for "Single Abstract Method".) 46 | 47 | Nothing special needs to be done to declare an interface as functional; the compiler identifies it as such based on its structure. (This identification process is a little more than just counting method declarations; an interface might redundantly declare a method that is automatically provided by the class Object, such as toString(), or might declare static or default methods, none of which count against the one-method limit.) However, API authors may additionally capture the design intent that an interface be functional (as opposed to accidentally having only one method) with the @FunctionalInterface annotation, in which case the compiler will validate that the interface meets the structural requirements to be a functional interface. 48 | 49 | An alternative (or complementary) approach to function types, suggested by some early proposals, would have been to introduce a new, structural function type, sometimes called arrow types. A type like "function from a String and an Object to an int" might be expressed as (String,Object)->int. This idea was considered and rejected, at least for now, due to several disadvantages: 50 | 51 | It would add complexity to the type system and further mix structural and nominal types (Java is almost entirely nominally typed). 52 | It would lead to a divergence of library styles -- some libraries would continue to use callback interfaces, while others would use structural function types. 53 | The syntax could be unwieldy, especially when checked exceptions were included. 54 | It is unlikely that there would be a runtime representation for each distinct function type, meaning developers would be further exposed to and limited by erasure. For example, it would not be possible (perhaps surprisingly) to overload methods m(T->U) and m(X->Y). 55 | So, we have instead followed the path of "use what you know" -- since existing libraries use functional interfaces extensively, we codify and leverage this pattern. This enables existing libraries to be used with lambda expressions. 56 | 57 | To illustrate, here is a sampling of some of the functional interfaces already in Java SE 7 that are well-suited for being used with the new language features; the examples that follow illustrate the use of a few of them. 58 | 59 | java.lang.Runnable 60 | java.util.concurrent.Callable 61 | java.security.PrivilegedAction 62 | java.util.Comparator 63 | java.io.FileFilter 64 | java.beans.PropertyChangeListener 65 | In addition, Java SE 8 adds a new package, java.util.function, which contains functional interfaces that are expected to be commonly used, such as: 66 | 67 | Predicate -- a boolean-valued property of an object 68 | Consumer -- an action to be performed on an object 69 | Function -- a function transforming a T to a R 70 | Supplier -- provide an instance of a T (such as a factory) 71 | UnaryOperator -- a function from T to T 72 | BinaryOperator -- a function from (T, T) to T 73 | In addition to these basic "shapes", there are also primitive specializations such as IntSupplier or LongBinaryOperator. (Rather than provide the full complement of primitive specializations, we provide only specializations for int, long, and double; the other primitive types can be accomodated through conversions.) Similarly, there are some specializations for multiple arities, such as BiFunction, which represents a function from (T,U) to R. 74 | 75 | 3. Lambda expressions 76 | The biggest pain point for anonymous classes is bulkiness. They have what we might call a "vertical problem": the ActionListener instance from section 1 uses five lines of source code to encapsulate a single aspect of behavior. 77 | 78 | Lambda expressions are anonymous methods, aimed at addressing the "vertical problem" by replacing the machinery of anonymous inner classes with a lighter-weight mechanism. 79 | 80 | Here are some examples of lambda expressions: 81 | 82 | (int x, int y) -> x + y 83 | 84 | () -> 42 85 | 86 | (String s) -> { System.out.println(s); } 87 | The first expression takes two integer arguments, named x and y, and returns their sum. The second takes no arguments and returns the integer 42. The third takes a string and prints it to the console, returning nothing. 88 | 89 | The general syntax consists of an argument list, the arrow token ->, and a body. The body can either be a single expression, or a statement block. In the expression form, the body is simply evaluated and returned. In the block form, the body is evaluated like a method body -- a return statement returns control to the caller of the anonymous method; break and continue are illegal at the top level, but are of course permitted within loops; and if the body produces a result, every control path must return something or throw an exception. 90 | 91 | The syntax is optimized for the common case in which a lambda expression is quite small, as illustrated above. For example, the expression-body form eliminates the need for a return keyword, which could otherwise represent a substantial syntactic overhead relative to the size of the expression. 92 | 93 | It is also expected that lambda expressions will frequently appear in nested contexts, such as the argument to a method invocation or the result of another lambda expression. To minimize noise in these cases, unnecessary delimiters are avoided. However, for situations in which it is useful to set the entire expression apart, it can be surrounded with parentheses, just like any other expression. 94 | 95 | Here are some examples of lambda expressions appearing in statements: 96 | 97 | FileFilter java = (File f) -> f.getName().endsWith(".java"); 98 | 99 | String user = doPrivileged(() -> System.getProperty("user.name")); 100 | 101 | new Thread(() -> { 102 | connectToService(); 103 | sendNotification(); 104 | }).start(); 105 | 4. Target typing 106 | Note that the name of a functional interface is not part of the lambda expression syntax. So what kind of object does a lambda expression represent? Its type is inferred from the surrounding context. For example, the following lambda expression is an ActionListener: 107 | 108 | ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers()); 109 | An implication of this approach is that the same lambda expression can have different types in different contexts: 110 | 111 | Callable c = () -> "done"; 112 | 113 | PrivilegedAction a = () -> "done"; 114 | In the first case, the lambda expression () -> "done" represents an instance of Callable. In the second case, the same expression represents an instance of PrivilegedAction. 115 | 116 | The compiler is responsible for inferring the type of each lambda expression. It uses the type expected in the context in which the expression appears; this type is called the target type. A lambda expression can only appear in a context whose target type is a functional interface. 117 | 118 | Of course, no lambda expression will be compatible with every possible target type. The compiler checks that the types used by the lambda expression are consistent with the target type's method signature. That is, a lambda expression can be assigned to a target type T if all of the following conditions hold: 119 | 120 | T is a functional interface type 121 | The lambda expression has the same number of parameters as T's method, and those parameters' types are the same 122 | Each expression returned by the lambda body is compatible with T's method's return type 123 | Each exception thrown by the lambda body is allowed by T's method's throws clause 124 | Since a functional interface target type already "knows" what types the lambda expression's formal parameters should have, it is often unnecessary to repeat them. The use of target typing enables the lambda parameters' types to be inferred: 125 | 126 | Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2); 127 | Here, the compiler infers that the type of s1 and s2 is String. In addition, when there is just one parameter whose type is inferred (a very common case), the parentheses surrounding a single parameter name are optional: 128 | 129 | FileFilter java = f -> f.getName().endsWith(".java"); 130 | 131 | button.addActionListener(e -> ui.dazzle(e.getModifiers())); 132 | These enhancements further a desirable design goal: "Don't turn a vertical problem into a horizontal problem." We want the reader of the code to have to wade through as little syntax as possible before arriving at the "meat" of the lambda expression. 133 | 134 | Lambda expressions are not the first Java expressions to have context-dependent types: generic method invocations and "diamond" constructor invocations, for example, are similarly type-checked based on an assignment's target type. 135 | 136 | List ls = Collections.emptyList(); 137 | List li = Collections.emptyList(); 138 | 139 | Map m1 = new HashMap<>(); 140 | Map m2 = new HashMap<>(); 141 | 5. Contexts for target typing 142 | We stated earlier that lambda expressions can only appear in contexts that have target types. The following contexts have target types: 143 | 144 | Variable declarations 145 | Assignments 146 | Return statements 147 | Array initializers 148 | Method or constructor arguments 149 | Lambda expression bodies 150 | Conditional expressions (?:) 151 | Cast expressions 152 | In the first three cases, the target type is simply the type being assigned to or returned. 153 | 154 | Comparator c; 155 | c = (String s1, String s2) -> s1.compareToIgnoreCase(s2); 156 | 157 | public Runnable toDoLater() { 158 | return () -> { 159 | System.out.println("later"); 160 | }; 161 | } 162 | Array initializer contexts are like assignments, except that the "variable" is an array component and its type is derived from the array's type. 163 | 164 | filterFiles(new FileFilter[] { 165 | f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("q") 166 | }); 167 | In the method argument case, things are more complicated: target type determination interacts with two other language features, overload resolution and type argument inference. 168 | 169 | Overload resolution involves finding the best method declaration for a particular method invocation. Since different declarations have different signatures, this can impact the target type of a lambda expression used as an argument. The compiler will use what it knows about the lambda expression to make this choice. If a lambda expression is explicitly typed (specifies the types of its parameters), the compiler will know not only the parameter types but also the type of all return expressions in its body. If the lambda is implicitly typed (inferred parameter types), overload resolution will ignore the lambda body and only use the number of lambda parameters. 170 | 171 | If the choice of a best method declaration is ambiguous, casts or explicit lambdas can provide additional type information for the compiler to disambiguate. If the return type targeted by a lambda expression depends on type argument inference, then the lambda body may provide information to the compiler to help infer the type arguments. 172 | 173 | List ps = ... 174 | String names = ps.stream().map(p -> p.getName()); 175 | Here, ps is a List, so ps.stream() is a Stream. The map() method is generic in R, where the parameter of map() is a Function, where T is the stream element type. (T is known to be Person at this point.) Once the overload is selected and the lambda's target type is known, we need to infer R; we do this by type-checking the lambda body, and discovering that its return type is String, and hence R is String, and therefore the map() expression has a type of Stream. Most of the time, the compiler just figures this all out, but if it gets stuck, we can provide additional type information via an explicit lambda (give the argument p an explicit type), casting the lambda to an explicit target type such as Function, or providing an explicit type witness for the generic parameter R (.map(p -> p.getName())). 176 | 177 | Lambda expressions themselves provide target types for their bodies, in this case by deriving that type from the outer target type. This makes it convenient to write functions that return other functions: 178 | 179 | Supplier c = () -> () -> { System.out.println("hi"); }; 180 | Similarly, conditional expressions can "pass down" a target type from the surrounding context: 181 | 182 | Callable c = flag ? (() -> 23) : (() -> 42); 183 | Finally, cast expressions provide a mechanism to explicitly provide a lambda expression's type if none can be conveniently inferred from context: 184 | 185 | // Illegal: Object o = () -> { System.out.println("hi"); }; 186 | Object o = (Runnable) () -> { System.out.println("hi"); }; 187 | Casts are also useful to help resolve ambiguity when a method declaration is overloaded with unrelated functional interface types. 188 | 189 | The expanded role of target typing in the compiler is not limited to lambda expressions: generic method invocations and "diamond" constructor invocations can also take advantage of target types wherever they are available. The following declarations are illegal in Java SE 7 but valid in Java SE 8: 190 | 191 | List ls = 192 | Collections.checkedList(new ArrayList<>(), String.class); 193 | 194 | Set si = flag ? Collections.singleton(23) 195 | : Collections.emptySet(); 196 | 6. Lexical scoping 197 | Determining the meaning of names (and this) in inner classes is significantly more difficult and error-prone than when classes are limited to the top level. Inherited members -- including methods of class Object -- can accidentally shadow outer declarations, and unqualified references to this always refer to the inner class itself. 198 | 199 | Lambda expressions are much simpler: they do not inherit any names from a supertype, nor do they introduce a new level of scoping. Instead, they are lexically scoped, meaning names in the body are interpreted just as they are in the enclosing environment (with the addition of new names for the lambda expression's formal parameters). As a natural extension, the this keyword and references to its members have the same meaning as they would immediately outside the lambda expression. 200 | 201 | To illustrate, the following program prints "Hello, world!" twice to the console: 202 | 203 | public class Hello { 204 | Runnable r1 = () -> { System.out.println(this); } 205 | Runnable r2 = () -> { System.out.println(toString()); } 206 | 207 | public String toString() { return "Hello, world!"; } 208 | 209 | public static void main(String... args) { 210 | new Hello().r1.run(); 211 | new Hello().r2.run(); 212 | } 213 | } 214 | The equivalent using anonymous inner classes would instead, perhaps to the programmer's surprise, print something like Hello$1@5b89a773 and Hello$2@537a7706. 215 | 216 | Consistent with the lexical-scoping approach, and following the pattern set by other local parameterized constructs like for loops and catch clauses, the parameters of a lambda expression must not shadow any local variables in the enclosing context. 217 | 218 | 7. Variable capture 219 | The compiler check for references to local variables of enclosing contexts in inner classes (captured variables) is quite restrictive in Java SE 7: an error occurs if the captured variable is not declared final. We relax this restriction -- for both lambda expressions and inner classes -- by also allowing the capture of effectively final local variables. 220 | 221 | Informally, a local variable is effectively final if its initial value is never changed -- in other words, declaring it final would not cause a compilation failure. 222 | 223 | Callable helloCallable(String name) { 224 | String hello = "Hello"; 225 | return () -> (hello + ", " + name); 226 | } 227 | References to this -- including implicit references through unqualified field references or method invocations -- are, essentially, references to a final local variable. Lambda bodies that contain such references capture the appropriate instance of this. In other cases, no reference to this is retained by the object. 228 | 229 | This has a beneficial implication for memory management: while inner class instances always hold a strong reference to their enclosing instance, lambdas that do not capture members from the enclosing instance do not hold a reference to it. This characteristic of inner class instances can often be a source of memory leaks. 230 | 231 | While we relax the syntactic restrictions on captured values, we still prohibit capture of mutable local variables. The reason is that idioms like this: 232 | 233 | int sum = 0; 234 | list.forEach(e -> { sum += e.size(); }); // ERROR 235 | are fundamentally serial; it is quite difficult to write lambda bodies like this that do not have race conditions. Unless we are willing to enforce -- preferably at compile time -- that such a function cannot escape its capturing thread, this feature may well cause more trouble than it solves. Lambda expressions close over values, not variables. 236 | 237 | Another reason to not support capture of mutable variables is that there's a better way to address accumulation problems without mutation, and instead treat this problem as a reduction. The java.util.stream package provides both general and specialized (such as sum, min, and max) reductions on collections and other data structures. For example, instead of using forEach and mutation, we could do a reduction which is safe both sequentially or in parallel: 238 | 239 | int sum = list.stream() 240 | .mapToInt(e -> e.size()) 241 | .sum(); 242 | The sum() method is provided for convenience, but is equivalent to the more general form of reduction: 243 | 244 | int sum = list.stream() 245 | .mapToInt(e -> e.size()) 246 | .reduce(0, (x,y) -> x+y); 247 | Reduction takes a base value (in case the input is empty) and an operator (here, addition), and computes the following expression: 248 | 249 | 0 + list[0] + list[1] + list[2] + ... 250 | Reduction can be done with other operations as well, such as minimum, maximum, product, etc, and if the operator is associative, is easily and safely parallelized. So, rather than supporting an idiom that is fundamentally sequential and prone to data races (mutable accumulators), we instead choose to provide library support to express accumulations in a more parallelizable and less error-prone way. 251 | 252 | 8. Method references 253 | Lambda expressions allow us to define an anonymous method and treat it as an instance of a functional interface. It is often desirable to do the same with an existing method. 254 | 255 | Method references are expressions which have the same treatment as lambda expressions (i.e., they require a target type and encode functional interface instances), but instead of providing a method body, they refer an existing method by name. 256 | 257 | For example, consider a Person class that can be sorted by name or by age. 258 | 259 | class Person { 260 | private final String name; 261 | private final int age; 262 | 263 | public int getAge() { return age; } 264 | public String getName() { return name; } 265 | ... 266 | } 267 | 268 | Person[] people = ... 269 | Comparator byName = Comparator.comparing(p -> p.getName()); 270 | Arrays.sort(people, byName); 271 | We can rewrite this to use a method reference to Person.getName() instead: 272 | 273 | Comparator byName = Comparator.comparing(Person::getName); 274 | Here, the expression Person::getName can be considered shorthand for a lambda expression which simply invokes the named method with its arguments, and returns the result. While the method reference may not (in this case) be any more syntactically compact, it is clearer -- the method that we want to call has a name, and so we can refer to it directly by name. 275 | 276 | Because the functional interface method's parameter types act as arguments in an implicit method invocation, the referenced method signature is allowed to manipulate the parameters -- via widening, boxing, grouping as a variable-arity array, etc. -- just like a method invocation. 277 | 278 | Consumer b1 = System::exit; // void exit(int status) 279 | Consumer b2 = Arrays::sort; // void sort(Object[] a) 280 | Consumer b3 = MyProgram::main; // void main(String... args) 281 | Runnable r = MyProgram::main; // void main(String... args) 282 | 9. Kinds of method references 283 | There are several different kinds of method references, each with slightly different syntax: 284 | 285 | A static method (ClassName::methName) 286 | An instance method of a particular object (instanceRef::methName) 287 | A super method of a particular object (super::methName) 288 | An instance method of an arbitrary object of a particular type (ClassName::methName) 289 | A class constructor reference (ClassName::new) 290 | An array constructor reference (TypeName[]::new) 291 | For a static method reference, the class to which the method belongs precedes the :: delimiter, such as in Integer::sum. 292 | 293 | For a reference to an instance method of a particular object, an expression evaluating to an object reference precedes the delimiter: 294 | 295 | Set knownNames = ... 296 | Predicate isKnown = knownNames::contains; 297 | Here, the implicit lambda expression would capture the String object referred to by knownNames, and the body would invoke Set.contains using that object as the receiver. 298 | 299 | The ability to reference the method of a specific object provides a convenient way to convert between different functional interface types: 300 | 301 | Callable c = ... 302 | PrivilegedAction a = c::call; 303 | For a reference to an instance method of an arbitrary object, the type to which the method belongs precedes the delimiter, and the invocation's receiver is the first parameter of the functional interface method: 304 | 305 | Function upperfier = String::toUpperCase; 306 | Here, the implicit lambda expression has one parameter, the string to be converted to upper case, which becomes the receiver of the invocation of the toUpperCase() method. 307 | 308 | If the class of the instance method is generic, its type parameters can be provided before the :: delimiter or, in most cases, inferred from the target type. 309 | 310 | Note that the syntax for a static method reference might also be interpreted as a reference to an instance method of a class. The compiler determines which is intended by attempting to identify an applicable method of each kind (noting that the instance method has one less argument). 311 | 312 | For all forms of method references, method type arguments are inferred as necessary, or they can be explicitly provided following the :: delimiter. 313 | 314 | Constructors can be referenced in much the same was as static methods by using the name new: 315 | 316 | SocketImplFactory factory = MySocketImpl::new; 317 | If a class has multiple constructors, the target type's method signature is used to select the best match in the same way that a constructor invocation is resolved. 318 | 319 | For inner classes, no syntax supports explicitly providing an enclosing instance parameter at the site of the constructor reference. 320 | 321 | If the class to instantiate is generic, type arguments can be provided after the class name, or they are inferred as for a "diamond" constructor invocation. 322 | 323 | There is a special syntactic form of constructor references for arrays, which treats arrays as if they had a constructor that accepts an int parameter. For example: 324 | 325 | IntFunction arrayMaker = int[]::new; 326 | int[] array = arrayMaker.apply(10); // creates an int[10] 327 | 10. Default and static interface methods 328 | Lambda expressions and method references add a lot of expressiveness to the Java language, but the key to really achieving our goal of making code-as-data patterns convenient and idiomatic is to complement these new features with libraries tailored to take advantage of them. 329 | 330 | Adding new functionality to existing libraries is somewhat difficult in Java SE 7. In particular, interfaces are essentially set in stone once they are published; unless one can update all possible implementations of an interface simultaneously, adding a new method to an interface can cause existing implementations to break. The purpose of default methods (previously referred to as virtual extension methods or defender methods) is to enable interfaces to be evolved in a compatible manner after their initial publication. 331 | 332 | To illustrate, the standard collections API obviously ought to provide new lambda-friendly operations. For example, the removeAll method could be generalized to remove any of a collection's elements for which an arbitrary property held, where the property was expressed as an instance of a functional interface Predicate. But where would this new method be defined? We can't add an abstract method to the Collection interface -- many existing implementations wouldn't know about the change. We could make it a static method in the Collections utility class, but that would relegate these new operations to a sort of second-class status. 333 | 334 | Default methods provide a more object-oriented way to add concrete behavior to an interface. These are a new kind of method: interface method can either be abstract or default. Default methods have an implementation that is inherited by classes that do not override it (see the next section for the details). Default methods in a functional interface don't count against its limit of one abstract method. For example, we could have (though did not) add a skip method to Iterator, as follows: 335 | 336 | interface Iterator { 337 | boolean hasNext(); 338 | E next(); 339 | void remove(); 340 | 341 | default void skip(int i) { 342 | for (; i > 0 && hasNext(); i--) next(); 343 | } 344 | } 345 | Given the above definition of Iterator, all classes that implement Iterator would inherit a skip method. From a client's perspective, skip is just another virtual method provided by the interface. Invoking skip on an instance of a subclass of Iterator that does not provide a body for skip has the effect of invoking the default implementation: calling hasNext and next up to a certain number of times. If a class wants to override skip with a better implementation -- by advancing a private cursor directly, for example, or incorporating an atomicity guarantee -- it is free to do so. 346 | 347 | When one interface extends another, it can add a default to an inherited abstract method, provide a new default for an inherited default method, or reabstract a default method by redeclaring the method as abstract. 348 | 349 | In addition to allowing code in interfaces in the form of default methods, Java SE 8 also introduces the ability to place static methods in interfaces as well. This allows helper methods that are specific to an interface to live with the interface, rather than in a side class (which is often named for the plural of the interface). For example, Comparator acquired static helper methods for making comparators, which takes a function that extracts a Comparable sort key and produces a Comparator: 350 | 351 | public static > 352 | Comparator comparing(Function keyExtractor) { 353 | return (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); 354 | } 355 | 11. Inheritance of default methods 356 | Default methods are inherited just like other methods; in most cases, the behavior is just as one would expect. However, when a class's or interface's supertypes provide multiple methods with the same signature, the inheritance rules attempt to resolve the conflict. Two basic principles drive these rules: 357 | 358 | Class method declarations are preferred to interface defaults. This is true whether the class method is concrete or abstract. (Hence the default keyword: default methods are a fallback if the class hierarchy doesn't say anything.) 359 | 360 | Methods that are already overridden by other candidates are ignored. This circumstance can arise when supertypes share a common ancestor. 361 | 362 | As an example of how the second rule comes into play, say the Collection and List interfaces provided different defaults for removeAll, and Queue inherits the default method from Collection; in the following implements clause, the List declaration would have priority over the Collection declaration inherited by Queue: 363 | 364 | class LinkedList implements List, Queue { ... } 365 | In the event that two independently-defined defaults conflict, or a default method conflicts with an abstract method, it is a compilation error. In this case, the programmer must explicitly override the supertype methods. Often, this amounts to picking the preferred default, and declaring a body that invokes the preferred default. An enhanced syntax for super supports the invocation of a particular superinterface's default implementation: 366 | 367 | interface Robot implements Artist, Gun { 368 | default void draw() { Artist.super.draw(); } 369 | } 370 | The name preceding super must refer to a direct superinterface that defines or inherits a default for the invoked method. This form of method invocation is not restricted to simple disambiguation -- it can be used just like any other invocation, in both classes and interfaces. 371 | 372 | In no case does the order in which interfaces are declared in an inherits or extends clause, or which interface was implemented "first" or "more recently", affect inheritance. 373 | 374 | 12. Putting it together 375 | The language and library features for Project Lambda were designed to work together. To illustrate, we'll consider the task of sorting a list of people by last name. 376 | 377 | Today we write: 378 | 379 | List people = ... 380 | Collections.sort(people, new Comparator() { 381 | public int compare(Person x, Person y) { 382 | return x.getLastName().compareTo(y.getLastName()); 383 | } 384 | }); 385 | This is a very verbose way to write "sort people by last name"! 386 | 387 | With lambda expressions, we can make this expression more concise: 388 | 389 | Collections.sort(people, 390 | (Person x, Person y) -> x.getLastName().compareTo(y.getLastName())); 391 | However, while more concise, it is not any more abstract; it still burdens the programmer with the need to do the actual comparison (which is even worse when the sort key is a primitive). Small changes to the libraries can help here, such the static comparing method added to Comparator: 392 | 393 | Collections.sort(people, Comparator.comparing((Person p) -> p.getLastName())); 394 | This can be shortened by allowing the compiler to infer the type of the lambda parameter, and importing the comparing method via a static import: 395 | 396 | Collections.sort(people, comparing(p -> p.getLastName())); 397 | The lambda in the above expression is simply a forwarder for the existing method getLastName. We can use method references to reuse the existing method in place of the lambda expression: 398 | 399 | Collections.sort(people, comparing(Person::getLastName)); 400 | Finally, the use of an ancillary method like Collections.sort is undesirable for many reasons: it is more verbose; it can't be specialized for each data structure that implements List; and it undermines the value of the List interface since users can't easily discover the static sort method when inspecting the documentation for List. 401 | 402 | Default methods provide a more object-oriented solution for this problem, where we've added a sort() method to List: 403 | 404 | people.sort(comparing(Person::getLastName)); 405 | Which also reads much more like to the problem statement in the first place: sort the people list by last name. 406 | 407 | If we add a default method reversed() to Comparator, which produces a Comparator that uses the same sort key but in reverse order, we can just as easily express a descending sort: 408 | 409 | people.sort(comparing(Person::getLastName).reversed()); 410 | 13. Summary 411 | Java SE 8 adds a relatively small number of new language features -- lambda expressions, method references, default and static methods in interfaces, and more widespread use of type inference. Taken together, though, they enable programmers to express their intent more clearly and concisely with less boilerplate, and enable the development of more powerful, parallel-friendly libraries. -------------------------------------------------------------------------------- /lambda-math-functions/README.MD: -------------------------------------------------------------------------------- 1 | # Math functions exercise :muscle: 2 | Improve your lambda and method reference skills 3 | ### Task 4 | **FunctionMap** is an API that allows you to store and retrieve math functions by string name. Your job is to implement the *todo* section of `Functions.java` class using **Lambda expressions and method reference**, so all tests in `FunctionsTest.java` should pass 5 | 6 | ### Pre-conditions :heavy_exclamation_mark: 7 | You're supposed to be familiar with Java 8 8 | 9 | ### How to start :question: 10 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 11 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 12 | * Don't worry if you got stuck, checkout the [exercise/completed](https://github.com/bobocode-projects/tdd-exercises/tree/exercise/completed/binary-search-tree) branch and see the final implementation 13 | 14 | ### Related materials :information_source: 15 | * [Lambda tutorial](https://github.com/bobocode-projects/java-8-tutorial/tree/master/lambdas) 16 | * [State of lambda (JSR 335)](http://htmlpreview.github.io/?https://github.com/bobocode-projects/resources/blob/master/java8/lambda/sotl.html) 17 | 18 | -------------------------------------------------------------------------------- /lambda-math-functions/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lambda-math-functions 13 | 14 | -------------------------------------------------------------------------------- /lambda-math-functions/src/main/java/com.bobocode/FunctionMap.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.function.Function; 6 | 7 | /** 8 | * FunctionMap is an API that allows you to store and retrieve functions by string name. FunctionMap are stored in a 9 | * HashMap, where the key is a function name, and the value is a Function instance 10 | */ 11 | public class FunctionMap { 12 | private Map> functionMap; 13 | 14 | FunctionMap() { 15 | functionMap = new HashMap<>(); 16 | } 17 | 18 | public void addFunction(String name, Function function) { 19 | functionMap.put(name, function); 20 | } 21 | 22 | public Function getFunction(String name) { 23 | if (functionMap.containsKey(name)) { 24 | return functionMap.get(name); 25 | } else { 26 | throw new InvalidFunctionNameException(name); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /lambda-math-functions/src/main/java/com.bobocode/Functions.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | public class Functions { 4 | /** 5 | * A static factory method that creates an integer function map with basic functions: 6 | * - abs (absolute value) 7 | * - sgn (signum function) 8 | * - increment 9 | * - decrement 10 | * - square 11 | * 12 | * @return an instance of {@link FunctionMap} that contains all listed functions 13 | */ 14 | public static FunctionMap intFunctionMap() { 15 | FunctionMap intFunctionMap = new FunctionMap<>(); 16 | 17 | // todo: add simple functions to the function map (abs, sgn, increment, decrement, square) 18 | 19 | return intFunctionMap; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lambda-math-functions/src/main/java/com.bobocode/InvalidFunctionNameException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | public class InvalidFunctionNameException extends RuntimeException { 4 | public InvalidFunctionNameException(String functionName) { 5 | super("Function " + functionName + " doesn't exist."); 6 | } 7 | } -------------------------------------------------------------------------------- /lambda-math-functions/src/test/java/com/bobocode/FunctionsTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import org.junit.jupiter.api.*; 4 | 5 | import java.util.function.Function; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | 10 | 11 | @TestMethodOrder( MethodOrderer.OrderAnnotation.class) 12 | public class FunctionsTest { 13 | private FunctionMap integerFunctionMap; 14 | 15 | @BeforeEach 16 | public void init() { 17 | integerFunctionMap = Functions.intFunctionMap(); 18 | } 19 | 20 | @Test 21 | @Order(7) 22 | void testSquareFunction() { 23 | Function squareFunction = integerFunctionMap.getFunction("square"); 24 | 25 | int actualResult = squareFunction.apply(5); 26 | 27 | assertEquals(25, actualResult); 28 | } 29 | 30 | @Test 31 | @Order(1) 32 | void testAbsFunction() { 33 | Function absFunction = integerFunctionMap.getFunction("abs"); 34 | 35 | int actualResult = absFunction.apply(-192); 36 | 37 | assertEquals(192, actualResult); 38 | } 39 | 40 | @Test 41 | @Order(5) 42 | void testIncrementFunction() { 43 | Function incrementFunction = integerFunctionMap.getFunction("increment"); 44 | 45 | int actualResult = incrementFunction.apply(399); 46 | 47 | assertEquals(400, actualResult); 48 | } 49 | 50 | @Test 51 | @Order(6) 52 | void testDecrementFunction() { 53 | Function decrementFunction = integerFunctionMap.getFunction("decrement"); 54 | 55 | int actualResult = decrementFunction.apply(800); 56 | 57 | assertEquals(799, actualResult); 58 | } 59 | 60 | @Test 61 | @Order(2) 62 | void testSignFunctionOnNegativeValue() { 63 | Function signFunction = integerFunctionMap.getFunction("sgn"); 64 | 65 | int actualResult = signFunction.apply(-123); 66 | 67 | assertEquals(-1, actualResult); 68 | } 69 | 70 | @Test 71 | @Order(3) 72 | void testSignFunctionOnPositiveValue() { 73 | Function signFunction = integerFunctionMap.getFunction("sgn"); 74 | 75 | int actualResult = signFunction.apply(23); 76 | 77 | assertEquals(1, actualResult); 78 | } 79 | 80 | @Test 81 | @Order(4) 82 | void testSignFunctionOnZero() { 83 | Function signFunction = integerFunctionMap.getFunction("sgn"); 84 | 85 | int actualResult = signFunction.apply(0); 86 | 87 | assertEquals(0, actualResult); 88 | } 89 | 90 | @Test 91 | @Order(8) 92 | void testGetUnknownFunction() { 93 | assertThrows(InvalidFunctionNameException.class, () -> integerFunctionMap.getFunction("sqrt")); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /linked-list/README.MD: -------------------------------------------------------------------------------- 1 | # Linked List exercise :muscle: 2 | Improve your Java SE skills 3 | ### Task 4 | `List` is an API that represents a well-known data structure. Your job is to 5 | implement the *todo* section of the class `LinkedList`. Please note, that your implementation should be based on **singly 6 | liked nodes.** It means that you should create your own class `Node` that will hold list elements. 7 | 8 | To verify your implementation, run `LinkedListTest.java` 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to be familiar Linked List data structure, and be able to write Java code 12 | 13 | ### How to start :question: 14 | * Just clone the repository and create a branch **exercise/your_username** if you want your code to be reviewed 15 | * Start implementing the **todo** section and verify your changes by running tests 16 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 17 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 18 | 19 | ### Related materials :information_source: 20 | * [Linked Lists](https://www.cs.cmu.edu/~adamchik/15-121/lectures/Linked%20Lists/linked%20lists.html) 21 | 22 | -------------------------------------------------------------------------------- /linked-list/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | linked-list 13 | 14 | 15 | -------------------------------------------------------------------------------- /linked-list/src/main/java/com/bobocode/LinkedList.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | /** 4 | * {@link LinkedList} is a list implementation that is based on singly linked generic nodes. A node is implemented as 5 | * inner static class {@link Node}. In order to keep track on nodes, {@link LinkedList} keeps a reference to a head node. 6 | * 7 | * @param generic type parameter 8 | */ 9 | public class LinkedList implements List { 10 | 11 | /** 12 | * This method creates a list of provided elements 13 | * 14 | * @param elements elements to add 15 | * @param generic type 16 | * @return a new list of elements the were passed as method parameters 17 | */ 18 | public static List of(T... elements) { 19 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 20 | } 21 | 22 | /** 23 | * Adds an element to the end of the list 24 | * 25 | * @param element element to add 26 | */ 27 | @Override 28 | public void add(T element) { 29 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 30 | } 31 | 32 | /** 33 | * Adds a new element to the specific position in the list. In case provided index in out of the list bounds it 34 | * throws {@link IndexOutOfBoundsException} 35 | * 36 | * @param index an index of new element 37 | * @param element element to add 38 | */ 39 | @Override 40 | public void add(int index, T element) { 41 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 42 | } 43 | 44 | /** 45 | * Changes the value of an list element at specific position. In case provided index in out of the list bounds it 46 | * throws {@link IndexOutOfBoundsException} 47 | * 48 | * @param index an position of element to change 49 | * @param element a new element value 50 | */ 51 | @Override 52 | public void set(int index, T element) { 53 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 54 | } 55 | 56 | /** 57 | * Retrieves an elements by its position index. In case provided index in out of the list bounds it 58 | * throws {@link IndexOutOfBoundsException} 59 | * 60 | * @param index element index 61 | * @return an element value 62 | */ 63 | @Override 64 | public T get(int index) { 65 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 66 | } 67 | 68 | /** 69 | * Removes an elements by its position index. In case provided index in out of the list bounds it 70 | * throws {@link IndexOutOfBoundsException} 71 | * 72 | * @param index element index 73 | */ 74 | @Override 75 | public void remove(int index) { 76 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 77 | } 78 | 79 | 80 | /** 81 | * Checks if a specific exists in he list 82 | * 83 | * @return {@code true} if element exist, {@code false} otherwise 84 | */ 85 | @Override 86 | public boolean contains(T element) { 87 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 88 | } 89 | 90 | /** 91 | * Checks if a list is empty 92 | * 93 | * @return {@code true} if list is empty, {@code false} otherwise 94 | */ 95 | @Override 96 | public boolean isEmpty() { 97 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 98 | } 99 | 100 | /** 101 | * Returns the number of elements in the list 102 | * 103 | * @return number of elements 104 | */ 105 | @Override 106 | public int size() { 107 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 108 | } 109 | 110 | /** 111 | * Removes all list elements 112 | */ 113 | @Override 114 | public void clear() { 115 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /linked-list/src/main/java/com/bobocode/List.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | 4 | public interface List { 5 | void add(T element); 6 | 7 | void add(int index, T element); 8 | 9 | void set(int index, T element); 10 | 11 | T get(int index); 12 | 13 | void remove(int index); 14 | 15 | boolean contains(T element); 16 | 17 | boolean isEmpty(); 18 | 19 | int size(); 20 | 21 | void clear(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /linked-list/src/test/java/com/bobocode/LinkedListTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | 4 | import org.junit.jupiter.api.MethodOrderer; 5 | import org.junit.jupiter.api.Order; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.TestMethodOrder; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | @TestMethodOrder( MethodOrderer.OrderAnnotation.class) 15 | public class LinkedListTest { 16 | 17 | private List intList = new LinkedList<>(); 18 | 19 | @Test 20 | @Order(1) 21 | void testAddIntoEmptyList() { 22 | intList.add(41); 23 | 24 | assertEquals(1, intList.size()); 25 | assertEquals(41, intList.get(0).intValue()); 26 | } 27 | 28 | @Test 29 | @Order(2) 30 | void testGetFirstElementFromSingleElementList() { 31 | intList.add(25); 32 | 33 | int element = intList.get(0); 34 | 35 | assertEquals(25, element); 36 | } 37 | 38 | @Test 39 | @Order(4) 40 | void testAddElements() { 41 | intList = LinkedList.of(43, 233, 54); 42 | 43 | assertEquals(3, intList.size()); 44 | assertEquals(43, intList.get(0).intValue()); 45 | assertEquals(233, intList.get(1).intValue()); 46 | assertEquals(54, intList.get(2).intValue()); 47 | } 48 | 49 | 50 | @Test 51 | @Order(5) 52 | void testGetElements() { 53 | intList = LinkedList.of(25, 87, 45); 54 | 55 | int firstElement = intList.get(0); 56 | int secondElement = intList.get(1); 57 | int thirdElement = intList.get(2); 58 | 59 | assertEquals(25, firstElement); 60 | assertEquals(87, secondElement); 61 | assertEquals(45, thirdElement); 62 | } 63 | 64 | @Test 65 | @Order(16) 66 | void testGetFirstElementFromEmptyList() { 67 | assertThrows(IndexOutOfBoundsException.class, () -> intList.get(0)); 68 | } 69 | 70 | @Test 71 | @Order(17) 72 | void testGetElementByNegativeIndex() { 73 | assertThrows(IndexOutOfBoundsException.class, () -> intList.get(-1)); 74 | } 75 | 76 | @Test 77 | @Order(18) 78 | void testGetElementByIndexEqualsToListSize() { 79 | intList = LinkedList.of(33, 46, 25, 87, 45); 80 | assertThrows(IndexOutOfBoundsException.class, () -> intList.get(5)); 81 | } 82 | 83 | @Test 84 | @Order(6) 85 | void testAddElementByZeroIndexIntoEmptyList() { 86 | intList.add(0, 45); 87 | 88 | assertEquals(1, intList.size()); 89 | assertEquals(45, intList.get(0).intValue()); 90 | } 91 | 92 | @Test 93 | @Order(7) 94 | void testAddElementByIndexToTheEndOfList() { 95 | intList = LinkedList.of(98, 64, 23, 1, 3, 4); 96 | 97 | int newElementIndex = intList.size(); 98 | intList.add(newElementIndex, 44); 99 | 100 | assertEquals(44, intList.get(newElementIndex).intValue()); 101 | assertEquals(7, intList.size()); 102 | } 103 | 104 | @Test 105 | @Order(8) 106 | void testAddElementToTheHeadOfNonEmptyList() { 107 | intList = LinkedList.of(4, 6, 8, 9, 0, 2); 108 | 109 | intList.add(0, 53); 110 | 111 | assertEquals(53, intList.get(0).intValue()); 112 | assertEquals(4, intList.get(1).intValue()); 113 | assertEquals(7, intList.size()); 114 | } 115 | 116 | @Test 117 | @Order(9) 118 | void testAddElementByIndex() { 119 | intList = LinkedList.of(43, 5, 6, 8); 120 | 121 | int newElementIdx = 2; 122 | intList.add(newElementIdx, 66); 123 | 124 | assertEquals(66, intList.get(newElementIdx).intValue()); 125 | assertEquals(43, intList.get(0).intValue()); 126 | assertEquals(5, intList.get(1).intValue()); 127 | assertEquals(6, intList.get(3).intValue()); 128 | assertEquals(8, intList.get(4).intValue()); 129 | assertEquals(5, intList.size()); 130 | } 131 | 132 | @Test 133 | @Order(10) 134 | void testAddElementByNegativeIndex() { 135 | assertThrows(IndexOutOfBoundsException.class, () -> intList.add(-1, 66)); 136 | 137 | } 138 | 139 | @Test 140 | @Order(11) 141 | void testAddElementByIndexLargerThanListSize() { 142 | intList = LinkedList.of(4, 6, 11, 9); 143 | 144 | int newElementIdx = 5; 145 | assertThrows(IndexOutOfBoundsException.class, () -> intList.add(newElementIdx, 88)); 146 | } 147 | 148 | @Test 149 | @Order(12) 150 | void testAddElementByIndexEqualToSize() { 151 | intList = LinkedList.of(1, 2, 3, 4, 5); // size = 5 152 | 153 | intList.add(5, 111); 154 | 155 | assertEquals(6, intList.size()); 156 | assertEquals(111, intList.get(5).intValue()); 157 | } 158 | 159 | @Test 160 | @Order(13) 161 | void testSetFirstElementOnEmptyTree() { 162 | assertThrows(IndexOutOfBoundsException.class, () -> intList.set(0, 34)); 163 | } 164 | 165 | @Test 166 | @Order(14) 167 | void testSetElementByIndexEqualToSize() { 168 | intList = LinkedList.of(2, 3, 4); // size = 3 169 | 170 | assertThrows(IndexOutOfBoundsException.class, () -> intList.set(3, 222)); 171 | } 172 | 173 | @Test 174 | @Order(15) 175 | void testSetElementByIndex() { 176 | intList = LinkedList.of(34, 78, 9, 8); 177 | 178 | int index = 2; //element = 78 179 | intList.set(index, 99); 180 | 181 | assertEquals(99, intList.get(index).intValue()); 182 | assertEquals(34, intList.get(0).intValue()); 183 | assertEquals(78, intList.get(1).intValue()); 184 | assertEquals(8, intList.get(3).intValue()); 185 | assertEquals(4, intList.size()); 186 | 187 | } 188 | 189 | @Test 190 | @Order(19) 191 | void testRemoveElementFromEmptyList() { 192 | assertThrows(IndexOutOfBoundsException.class, () -> intList.remove(234)); 193 | } 194 | 195 | @Test 196 | @Order(20) 197 | void testRemoveFirstElement() { 198 | intList = LinkedList.of(4, 6, 8, 9); 199 | 200 | intList.remove(0); 201 | 202 | assertEquals(6, intList.get(0).intValue()); 203 | assertEquals(3, intList.size()); 204 | } 205 | 206 | @Test 207 | @Order(21) 208 | void testRemoveLastElement() { 209 | intList = LinkedList.of(4, 6, 8, 9); 210 | 211 | intList.remove(intList.size() - 1); 212 | 213 | assertEquals(8, intList.get(intList.size() - 1).intValue()); 214 | assertEquals(3, intList.size()); 215 | } 216 | 217 | @Test 218 | @Order(22) 219 | void testRemoveElement() { 220 | intList = LinkedList.of(1, 2, 3, 4, 5); 221 | 222 | int elementIndex = 2; 223 | intList.remove(elementIndex); // element = 3 224 | 225 | assertEquals(4, intList.get(elementIndex).intValue()); 226 | assertEquals(4, intList.size()); 227 | } 228 | 229 | @Test 230 | @Order(23) 231 | void testContainsOnEmptyList() { 232 | boolean contains = intList.contains(34); 233 | 234 | assertFalse(contains); 235 | } 236 | 237 | @Test 238 | @Order(24) 239 | void testContains() { 240 | intList = LinkedList.of(45, 6, 3, 6); 241 | 242 | boolean containsExistingElement = intList.contains(3); 243 | boolean containsNotExistingElement = intList.contains(54); 244 | 245 | assertTrue(containsExistingElement); 246 | assertFalse(containsNotExistingElement); 247 | } 248 | 249 | @Test 250 | @Order(25) 251 | void testIsEmptyOnEmptyList() { 252 | boolean empty = intList.isEmpty(); 253 | 254 | assertTrue(empty); 255 | } 256 | 257 | @Test 258 | @Order(26) 259 | void testIsEmpty() { 260 | intList = LinkedList.of(34, 5, 6); 261 | 262 | boolean empty = intList.isEmpty(); 263 | 264 | assertFalse(empty); 265 | } 266 | 267 | @Test 268 | @Order(27) 269 | void testSizeOnEmptyList() { 270 | int size = intList.size(); 271 | 272 | assertEquals(0, size); 273 | } 274 | 275 | @Test 276 | @Order(3) 277 | void testSize() { 278 | intList = LinkedList.of(4, 7, 9, 0, 7); 279 | 280 | int size = intList.size(); 281 | 282 | assertEquals(5, size); 283 | } 284 | 285 | @Test 286 | @Order(28) 287 | void testClearOnEmptyList() { 288 | intList.clear(); 289 | 290 | assertEquals(0, intList.size()); 291 | } 292 | 293 | @Test 294 | @Order(29) 295 | void testClearChangesTheSize() { 296 | intList = LinkedList.of(4, 5, 6); 297 | 298 | intList.clear(); 299 | 300 | assertEquals(0, intList.size()); 301 | } 302 | 303 | @Test 304 | @Order(30) 305 | void testClearRemovesElements() { 306 | intList = LinkedList.of(4, 5, 6); 307 | 308 | intList.clear(); 309 | assertThrows(IndexOutOfBoundsException.class, () -> intList.get(0)); 310 | } 311 | 312 | } 313 | -------------------------------------------------------------------------------- /linked-queue/README.MD: -------------------------------------------------------------------------------- 1 | # Linked Queue exercise :muscle: 2 | Improve your Java SE skills 3 | ### Task 4 | `Queue` is an API that represents queue data structure that follows "first in, first out" rule (FIFO). Your job is to 5 | implement the *todo* section of the class `LinkedQueue`. Please note, that your implementation should be based on liked 6 | queue, e.g. linked nodes. It means that you should create your own class `Node` that will hold queue elements. 7 | 8 | To verify your implementation, run `QueueTest.java` 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to be familiar Queue data structure, and be able to write Java code 12 | 13 | ### How to start :question: 14 | * Just clone the repository and create a branch **exercise/your_username** if you want your code to be reviewed 15 | * Start implementing the **todo** section and verify your changes by running tests 16 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 17 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 18 | 19 | ### Related materials :information_source: 20 | * [Queue (abstract data type)](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) 21 | 22 | -------------------------------------------------------------------------------- /linked-queue/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-core-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | linked-queue 13 | 14 | 15 | -------------------------------------------------------------------------------- /linked-queue/src/main/java/com/bobocode/LinkedQueue.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | /** 4 | * {@link LinkedQueue} implements FIFO {@link Queue}, using singly linked nodes. Nodes are stores in instances of nested 5 | * class Node. In order to perform operations {@link LinkedQueue#add(Object)} and {@link LinkedQueue#poll()} 6 | * in a constant time, it keeps to references to the head and tail of the queue. 7 | * 8 | * @param a generic parameter 9 | */ 10 | public class LinkedQueue implements Queue { 11 | 12 | /** 13 | * Adds an element to the end of the queue. 14 | * 15 | * @param element the element to add 16 | */ 17 | public void add(T element) { 18 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 19 | } 20 | 21 | /** 22 | * Retrieves and removes queue head. 23 | * 24 | * @return an element that was retrieved from the head or null if queue is empty 25 | */ 26 | public T poll() { 27 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 28 | } 29 | 30 | /** 31 | * Returns a size of the queue. 32 | * 33 | * @return an integer value that is a size of queue 34 | */ 35 | public int size() { 36 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 37 | } 38 | 39 | /** 40 | * Checks if the queue is empty. 41 | * 42 | * @return {@code true} if the queue is empty, returns {@code false} if it's not 43 | */ 44 | public boolean isEmpty() { 45 | throw new UnsupportedOperationException("This method is not implemented yet"); // todo: implement this method 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /linked-queue/src/main/java/com/bobocode/Queue.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | /** 4 | * Queue is a data structure that follows "first in, first out" rule (FIFO). Operations {@link Queue#add(Object)} and 5 | * {@link Queue#poll()} are performed in constant time O(1) 6 | */ 7 | public interface Queue { 8 | /** 9 | * Adds an element to the end of the queue. 10 | * 11 | * @param element the element to add 12 | */ 13 | void add(T element); 14 | 15 | /** 16 | * Retrieves and removes queue head. 17 | * 18 | * @return an element that was retrieved from the head or null if queue is empty 19 | */ 20 | T poll(); 21 | 22 | /** 23 | * Returns a size of the queue. 24 | * 25 | * @return an integer value that is a size of queue 26 | */ 27 | int size(); 28 | 29 | /** 30 | * Checks if the queue is empty. 31 | * 32 | * @return {@code true} if the queue is empty, returns {@code false} if it's not 33 | */ 34 | boolean isEmpty(); 35 | } 36 | -------------------------------------------------------------------------------- /linked-queue/src/test/java/com/bobocode/QueueTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import org.junit.jupiter.api.MethodOrderer; 4 | import org.junit.jupiter.api.Order; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestMethodOrder; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertFalse; 10 | import static org.junit.jupiter.api.Assertions.assertNull; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | @TestMethodOrder( MethodOrderer.OrderAnnotation.class) 14 | public class QueueTest { 15 | 16 | private Queue integerQueue = new LinkedQueue<>(); 17 | 18 | @Test 19 | @Order(3) 20 | void testAddElementIntoEmptyQueue() { 21 | integerQueue.add(1312); 22 | 23 | assertEquals(1, integerQueue.size()); 24 | assertEquals(1312, integerQueue.poll().intValue()); 25 | } 26 | 27 | @Test 28 | @Order(5) 29 | void testPollElementFromEmptyQueue() { 30 | assertNull(integerQueue.poll()); 31 | } 32 | 33 | @Test 34 | @Order(1) 35 | void testSizeOfEmptyQueue() { 36 | assertEquals(0, integerQueue.size()); 37 | } 38 | 39 | @Test 40 | void testIsEmptyOnEmptyQueue() { 41 | assertTrue(integerQueue.isEmpty()); 42 | } 43 | 44 | @Test 45 | @Order(4) 46 | void testAddElement() { 47 | integerQueue.add(324); 48 | integerQueue.add(23); 49 | integerQueue.add(5); 50 | 51 | assertEquals(3, integerQueue.size()); 52 | assertEquals(324, integerQueue.poll().intValue()); 53 | } 54 | 55 | @Test 56 | @Order(6) 57 | void testPollElement() { 58 | integerQueue.add(33); 59 | integerQueue.add(123); 60 | integerQueue.add(222); 61 | integerQueue.add(444); 62 | 63 | integerQueue.poll(); // should poll 33 64 | 65 | assertEquals(123, integerQueue.poll().intValue()); 66 | assertEquals(2, integerQueue.size()); 67 | } 68 | 69 | @Test 70 | @Order(2) 71 | void testSize() { 72 | integerQueue.add(98); 73 | integerQueue.add(9); 74 | integerQueue.add(5); 75 | integerQueue.add(6); 76 | 77 | assertEquals(4, integerQueue.size()); 78 | } 79 | 80 | @Test 81 | @Order(8) 82 | void testIsEmpty() { 83 | integerQueue.add(3); 84 | integerQueue.add(9); 85 | 86 | assertFalse(integerQueue.isEmpty()); 87 | 88 | } 89 | 90 | @Test 91 | @Order(7) 92 | void testPollLastElement() { 93 | integerQueue.add(8); 94 | integerQueue.add(123); 95 | integerQueue.add(99); 96 | integerQueue.add(46); 97 | 98 | integerQueue.poll(); // should poll 8 99 | integerQueue.poll(); // should poll 123 100 | integerQueue.poll(); // should poll 99 101 | 102 | assertEquals(46, integerQueue.poll().intValue()); 103 | assertEquals(0, integerQueue.size()); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.bobocode 8 | java-core-exercises 9 | 1.0-SNAPSHOT 10 | 11 | linked-queue 12 | linked-list 13 | file-stats 14 | file-reader 15 | lambda-math-functions 16 | crazy-streams 17 | account-data 18 | declarative-sum-of-squares 19 | crazy-lambdas 20 | crazy-optionals 21 | 22 | pom 23 | 24 | 25 | 11 26 | 11 27 | UTF-8 28 | 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.8 35 | 36 | 37 | org.slf4j 38 | slf4j-simple 39 | 1.7.26 40 | 41 | 42 | org.junit.jupiter 43 | junit-jupiter-engine 44 | 5.5.1 45 | 46 | 47 | com.google.code.findbugs 48 | jsr305 49 | 3.0.0 50 | 51 | 52 | org.mockito 53 | mockito-core 54 | 3.0.0 55 | test 56 | 57 | 58 | org.mockito 59 | mockito-inline 60 | 3.0.0 61 | test 62 | 63 | 64 | 65 | --------------------------------------------------------------------------------