├── java11
├── src
│ └── test
│ │ ├── resources
│ │ └── HelloWorld.java
│ │ └── java
│ │ └── org
│ │ └── avb
│ │ └── whatsnew
│ │ └── java11
│ │ ├── optional
│ │ └── IsEmptyDemo.java
│ │ ├── varinlambdaparams
│ │ └── VarsInLambdasDemo.java
│ │ ├── collections
│ │ └── CollectionsMethodsDemo.java
│ │ ├── strings
│ │ └── StringsMethodsDemo.java
│ │ ├── launchsourcecode
│ │ └── LaunchSingleFileProgramDemo.java
│ │ └── httpclient
│ │ └── HttpClientDemo.java
└── pom.xml
├── common
├── src
│ └── main
│ │ └── resources
│ │ └── logback-test.xml
└── pom.xml
├── .gitignore
├── java10
├── src
│ └── test
│ │ └── java
│ │ └── org
│ │ └── avb
│ │ └── whatsnew
│ │ └── java10
│ │ ├── optional
│ │ └── OrElseThrowDemo.java
│ │ ├── adhocfields
│ │ └── AdHocMembersDemo.java
│ │ └── typeinference
│ │ └── LocalVariableTypeInferenceDemo.java
└── pom.xml
├── java8
├── pom.xml
└── src
│ └── test
│ └── java
│ └── org
│ └── avb
│ └── whatsnew
│ └── java8
│ └── methodrefs
│ └── MethodReferencesDemo.java
├── java12
├── pom.xml
└── src
│ └── test
│ └── java
│ └── org
│ └── avb
│ └── whatsnew
│ └── java12
│ └── superswitch
│ └── SwitchExpressionDemo.java
├── java13
├── pom.xml
└── src
│ └── test
│ └── java
│ └── org
│ └── avb
│ └── whatsnew
│ └── java13
│ ├── textblocks
│ └── TextBlockDemo.java
│ └── switchexpression
│ └── SwitchExpressionDemo.java
├── java9
├── pom.xml
└── src
│ └── test
│ └── java
│ └── org
│ └── avb
│ └── whatsnew
│ └── java9
│ ├── streams
│ └── NewStreamMethodsDemo.java
│ ├── optional
│ └── NewOptionalMethodsDemo.java
│ └── collections
│ └── CollectionUtilityMethodsDemo.java
├── README.md
└── pom.xml
/java11/src/test/resources/HelloWorld.java:
--------------------------------------------------------------------------------
1 | public class HelloWorld {
2 |
3 | public static void main(String[] args) {
4 | System.out.println("Hello, world!");
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/common/src/main/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36} %M - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .classpath
3 | .project
4 | .settings/
5 |
6 | # Intellij
7 | .idea/
8 | *.iml
9 | *.iws
10 |
11 | # Maven
12 | log/
13 | target/
14 |
15 | # Compiled class file
16 | *.class
17 |
18 | # Log file
19 | *.log
20 |
21 | # BlueJ files
22 | *.ctxt
23 |
24 | # Mobile Tools for Java (J2ME)
25 | .mtj.tmp/
26 |
27 | # Package Files #
28 | *.jar
29 | *.war
30 | *.nar
31 | *.ear
32 | *.zip
33 | *.tar.gz
34 | *.rar
35 |
36 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
37 | hs_err_pid*
38 |
--------------------------------------------------------------------------------
/common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | common
13 |
14 |
15 |
--------------------------------------------------------------------------------
/java10/src/test/java/org/avb/whatsnew/java10/optional/OrElseThrowDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java10.optional;
2 |
3 | import org.testng.annotations.Test;
4 |
5 | import java.util.NoSuchElementException;
6 | import java.util.Optional;
7 |
8 | /**
9 | * Since Java 10, the Optional class has orElseThrow that does not have an argument
10 | * and throws NoSuchElementException.
11 | *
12 | * It overloads an already existing method that takes an exception supplier.
13 | */
14 | public class OrElseThrowDemo {
15 |
16 | @Test(expectedExceptions = NoSuchElementException.class)
17 | public void testOrElseThrow() {
18 | Optional.empty()
19 | .orElseThrow();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/java8/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | java8
13 |
14 |
15 |
16 | org.avb.whatsnew
17 | common
18 |
19 |
20 |
--------------------------------------------------------------------------------
/java10/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | java10
13 |
14 |
15 |
16 | org.avb.whatsnew
17 | common
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/java11/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | java11
13 |
14 |
15 |
16 | org.avb.whatsnew
17 | common
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/java12/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | java12
13 |
14 |
15 |
16 | org.avb.whatsnew
17 | common
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/java13/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | java13
13 |
14 |
15 |
16 | org.avb.whatsnew
17 | common
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/java9/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | whats-new-in-java
7 | org.avb.whatsnew
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | java9
13 |
14 |
15 |
16 | org.avb.whatsnew
17 | common
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/java11/src/test/java/org/avb/whatsnew/java11/optional/IsEmptyDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java11.optional;
2 |
3 | import org.testng.annotations.Test;
4 |
5 | import java.util.Optional;
6 |
7 | import static org.testng.Assert.*;
8 |
9 | /**
10 | * In Java 11, the Optional class has a small improvement - a new method called isEmpty
11 | * that returns true If a value is not present, otherwise false.
12 | */
13 | public class IsEmptyDemo {
14 |
15 | @Test
16 | public void testIsEmptyTrue() {
17 | assertTrue(Optional.empty().isEmpty()); // ok
18 | assertTrue(Optional.ofNullable(null).isEmpty()); // ok
19 | }
20 |
21 | @Test
22 | public void testIsEmptyFalse() {
23 | assertFalse(Optional.of(123).isEmpty()); // ok
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/java11/src/test/java/org/avb/whatsnew/java11/varinlambdaparams/VarsInLambdasDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java11.varinlambdaparams;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.lang.annotation.*;
8 | import java.util.List;
9 |
10 | /**
11 | * Java 11 introduces Local-Variable syntax for lambda parameters.
12 | * It is used for one specific case like (@Annotation var param) -> ...
13 | */
14 | public class VarsInLambdasDemo {
15 | private static final Logger LOGGER = LoggerFactory.getLogger(VarsInLambdasDemo.class);
16 |
17 | @Test
18 | public void testVarInLambdas() {
19 | List.of("Java", "Kotlin", "Scala", "Closure").stream()
20 | .filter((@NotNull var lang) -> lang.startsWith("J"))
21 | .forEach(LOGGER::info); // prints "Java"
22 | }
23 | }
24 |
25 | @Target(ElementType.PARAMETER)
26 | @Inherited
27 | @Retention(RetentionPolicy.CLASS)
28 | @interface NotNull { }
--------------------------------------------------------------------------------
/java11/src/test/java/org/avb/whatsnew/java11/collections/CollectionsMethodsDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java11.collections;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.util.*;
8 |
9 | public class CollectionsMethodsDemo {
10 | private static final Logger LOGGER = LoggerFactory.getLogger(CollectionsMethodsDemo.class);
11 |
12 | @Test
13 | public void testToArrayWithGenerator() {
14 | var fibonacci = List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
15 | .toArray(Integer[]::new);
16 |
17 | Arrays.stream(fibonacci)
18 | .map(Object::toString)
19 | .forEach(LOGGER::info); // prints items in the same order as in the list
20 |
21 | var languages = Set.of("Java", "Kotlin", "Scala")
22 | .toArray(String[]::new);
23 |
24 | Arrays.stream(languages)
25 | .map(Object::toString)
26 | .forEach(LOGGER::info); // prints items in any order
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/java10/src/test/java/org/avb/whatsnew/java10/adhocfields/AdHocMembersDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java10.adhocfields;
2 |
3 | import org.avb.whatsnew.java10.typeinference.LocalVariableTypeInferenceDemo;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.testng.annotations.Test;
7 |
8 | /**
9 | * Since Java 10 it is possible to expand a class with new fields and methods
10 | * using anonymous classes together with var.
11 | *
12 | * That is not a good practice, but may be used for a reason
13 | */
14 | public class AdHocMembersDemo {
15 | private static final Logger LOGGER = LoggerFactory.getLogger(AdHocMembersDemo.class);
16 |
17 | @Test
18 | public void testAdHocMembers() {
19 | var intPair = new Object() {
20 | int first = 0;
21 | int second = 0;
22 |
23 | public int sum() {
24 | return first + second;
25 | }
26 | };
27 |
28 | intPair.first = 10;
29 | intPair.second = 20;
30 |
31 | LOGGER.info("sum of {} and {} is {}", intPair.first, intPair.second, intPair.sum());
32 | }
33 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What's new in Java
2 |
3 | This project demonstrates new features introduced in different versions of Java (SE). It focuses only on those features that are visible to application developers and may affect the style or approaches to programming in Java. (Re)moving of existing APIs is not considered here at all.
4 |
5 | The project is based on Maven and has a classic multi-modular structure. Each module focuses on a specific version of the language. All examples are located in **test** folders.
6 |
7 | It's in the phase of the active development now. You can help to add some new information with examples by doing a pull request from your fork.
8 |
9 | Follow the link depending on the verion of Java you are interested in:
10 | - [Java13](https://github.com/swsms/whats-new-in-java/tree/master/java13/src/test/java/org/avb/whatsnew/java13)
11 | - [Java12](https://github.com/swsms/whats-new-in-java/tree/master/java12/src/test/java/org/avb/whatsnew/java12)
12 | - [Java11](https://github.com/swsms/whats-new-in-java/tree/master/java11/src/test/java/org/avb/whatsnew/java11)
13 | - [Java10](https://github.com/swsms/whats-new-in-java/tree/master/java10/src/test/java/org/avb/whatsnew/java10)
14 | - [Java9](https://github.com/swsms/whats-new-in-java/tree/master/java9/src/test/java/org/avb/whatsnew/java9)
15 | - [Java8](https://github.com/swsms/whats-new-in-java/tree/master/java8/src/test/java/org/avb/whatsnew/java8)
16 |
--------------------------------------------------------------------------------
/java13/src/test/java/org/avb/whatsnew/java13/textblocks/TextBlockDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java13.textblocks;
2 |
3 | import org.testng.annotations.Test;
4 |
5 | import static org.testng.Assert.assertEquals;
6 |
7 | /**
8 | * Java 13 introduces text blocks that simplify the declaration of multi-line strings.
9 | * We do not use need to write newline characters (\n) and mark quotes with backslash (\")
10 | * inside such blocks.
11 | */
12 | public class TextBlockDemo {
13 |
14 | /* It looks ugly and unreadable! */
15 | private static final String rawStringHelloWorldProgram =
16 | "public class Main {\n" +
17 | " public static void main(String[] args) {\n" +
18 | " System.out.println(\"Hello, world!\");\n" +
19 | " }\n" +
20 | "}";
21 |
22 | @Test
23 | public void testTextBlockWithInternalQuotes() {
24 | String helloWorldProgram = """
25 | public class Main {
26 | public static void main(String[] args) {
27 | System.out.println("Hello, world!");
28 | }
29 | }""";
30 | assertEquals(helloWorldProgram, rawStringHelloWorldProgram);
31 | }
32 |
33 | @Test
34 | public void testTextBlockWithMultipleLineBreaks() {
35 | String textWithNumbers = """
36 | 1
37 |
38 | 2
39 |
40 | 3""";
41 | assertEquals(textWithNumbers, "1\n\n2\n\n3");
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/java9/src/test/java/org/avb/whatsnew/java9/streams/NewStreamMethodsDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java9.streams;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.util.List;
8 | import java.util.stream.Stream;
9 |
10 | public class NewStreamMethodsDemo {
11 | private static final Logger LOGGER = LoggerFactory.getLogger(NewStreamMethodsDemo.class);
12 |
13 | @Test
14 | public void testOfNullable() {
15 | List list = null;
16 | Stream.ofNullable(list) // produces Stream.empty()
17 | .map(Object::toString)
18 | .forEach(LOGGER::info);
19 | }
20 |
21 | @Test
22 | public void testIterateWhileTrue() {
23 | Stream.iterate(0, i -> i < 5, i -> i + 1) // condition: i < 5
24 | .map(Object::toString)
25 | .forEach(LOGGER::info); // 0 1 2 3 4
26 | }
27 |
28 | @Test
29 | public void testTakeWhile() {
30 | List numbers = List.of(2, 4, 6, 8, 9, 10, 12, 14);
31 | numbers.stream()
32 | .takeWhile(n -> n % 2 == 0)
33 | .forEach(n -> LOGGER.info("{}", n)); // 2 4 6 8
34 | }
35 |
36 | @Test
37 | public void testDropWhile() {
38 | List numbers = List.of(2, 4, 6, 8, 9, 10, 12, 14);
39 | numbers.stream()
40 | .dropWhile(n -> n % 2 == 0)
41 | .forEach(n -> LOGGER.info("{}", n)); // 9 10 12 14
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/java13/src/test/java/org/avb/whatsnew/java13/switchexpression/SwitchExpressionDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java13.switchexpression;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | /**
8 | * In Java 13, switch expression can use the yield keyword
9 | * to return values from multi-statement cases.
10 | * Breaks with values are not allowed now.
11 | */
12 | public class SwitchExpressionDemo {
13 | private static final Logger LOGGER = LoggerFactory.getLogger(SwitchExpressionDemo.class);
14 |
15 | @Test
16 | public void testSwitchExpressionWithYield() {
17 | var a = 10;
18 | var b = 20;
19 | var op = "+";
20 |
21 | var result = switch (op) {
22 | case "+" -> {
23 | var sum = a + b;
24 | LOGGER.info("Performing {} + {} ...", a, b);
25 | yield sum;
26 | }
27 | case "-" -> {
28 | var diff = a - b;
29 | LOGGER.info("Performing {} - {} ...", a, b);
30 | yield diff;
31 | }
32 | case "*" -> {
33 | var product = a * b;
34 | LOGGER.info("Performing {} * {} ...", a, b);
35 | yield product;
36 | }
37 | default -> {
38 | String errorMsg = String.format("An unknown operation: %s", op);
39 | throw new IllegalArgumentException(errorMsg);
40 | }
41 | };
42 |
43 | LOGGER.info("The result is {}", result);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/java11/src/test/java/org/avb/whatsnew/java11/strings/StringsMethodsDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java11.strings;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import static org.testng.Assert.*;
8 |
9 | public class StringsMethodsDemo {
10 | private static final Logger LOGGER = LoggerFactory.getLogger(StringsMethodsDemo.class);
11 |
12 | @Test
13 | public void testRepeatSmile() {
14 | var smiles = ":)".repeat(10); // ":):):):):):):):):):)"
15 | LOGGER.info("smiles: {}", smiles);
16 | }
17 |
18 | @Test
19 | public void testIsBlankString() {
20 | var emptyString = "";
21 | assertTrue(emptyString.isBlank());
22 |
23 | var stringWithSpaces = " \t\n";
24 | assertTrue(stringWithSpaces.isBlank());
25 |
26 | var stringWithLetters = " abc ";
27 | assertFalse(stringWithLetters.isBlank());
28 | }
29 |
30 | @Test
31 | public void testLines() {
32 | var languages = "java\nkotlin\nscala\nclosure";
33 | languages.lines()
34 | .forEach(LOGGER::info); // prints each word from a new line
35 | }
36 |
37 | @Test
38 | public void testStripMethods() {
39 | LOGGER.info("\n\t abc".stripLeading()); // "abc"
40 | LOGGER.info("def\n\t ".stripTrailing()); // "def"
41 | LOGGER.info(" \n\tqq\n\t ".strip()); // "qq"
42 |
43 | var stringWithMagicSpace = "\u205Fabc";
44 |
45 | LOGGER.info(stringWithMagicSpace.strip()); // "abc"
46 | LOGGER.info(stringWithMagicSpace.trim()); // " abc"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/java8/src/test/java/org/avb/whatsnew/java8/methodrefs/MethodReferencesDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java8.methodrefs;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.time.LocalDate;
8 | import java.util.function.Consumer;
9 | import java.util.function.Predicate;
10 | import java.util.function.Supplier;
11 | import java.util.function.UnaryOperator;
12 |
13 | public class MethodReferencesDemo {
14 | private static final Logger LOGGER = LoggerFactory.getLogger(MethodReferencesDemo.class);
15 |
16 | @Test
17 | public void testUnaryOperatorMethodReference() {
18 | UnaryOperator function = String::toUpperCase;
19 |
20 | LOGGER.info(function.apply("hi")); // HI
21 | }
22 |
23 | @Test
24 | public void testConsumerMethodReference() {
25 | Consumer infoLogger = LOGGER::info;
26 |
27 | infoLogger.accept("hi"); // hi
28 | }
29 |
30 | @Test
31 | public void testConstructorMethodReference() {
32 | Supplier emptyStringCreator = String::new;
33 |
34 | LOGGER.info(emptyStringCreator.get()); // ""
35 | }
36 |
37 | @Test
38 | public void testStaticMethodSupplierMethodReference() {
39 | Supplier emptyStringCreator = LocalDate::now;
40 |
41 | LOGGER.info("{}", emptyStringCreator.get()); // a date like 2020-06-11
42 | }
43 |
44 | @Test
45 | public void testPredicateMethodReference() {
46 | Predicate isUpper = Character::isUpperCase;
47 |
48 | LOGGER.info("{}", isUpper.test('Q')); // true
49 | LOGGER.info("{}", isUpper.test('a')); // false
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/java11/src/test/java/org/avb/whatsnew/java11/launchsourcecode/LaunchSingleFileProgramDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java11.launchsourcecode;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.io.IOException;
8 | import java.net.URISyntaxException;
9 | import java.nio.file.Paths;
10 |
11 | /**
12 | * Since Java 11, it is possible to launch a single source code file invoking the command:
13 | * java HelloWorld.java
14 | * The launcher will compile the source code it before the start.
15 | *
16 | * In this example, a new process for the launcher is created programmatically.
17 | */
18 | public class LaunchSingleFileProgramDemo {
19 | private static final Logger LOGGER = LoggerFactory.getLogger(LaunchSingleFileProgramDemo.class);
20 |
21 | private final static String FILE_NAME = "HelloWorld.java";
22 |
23 | @Test
24 | public void testLaunchHelloWorld() throws IOException, URISyntaxException, InterruptedException {
25 | var pathToSourceCode = getPathToSourceCodeFile();
26 | LOGGER.info("Loaded source code: {}", pathToSourceCode);
27 |
28 | var process = startProcess("java " + pathToSourceCode);
29 | int code = process.waitFor();
30 |
31 | LOGGER.info("Process code: {}", code);
32 | }
33 |
34 | private String getPathToSourceCodeFile() throws URISyntaxException {
35 | var uri = getClass().getClassLoader().getResource(FILE_NAME);
36 | return Paths.get(uri.toURI()).toString();
37 | }
38 |
39 | private Process startProcess(String commandWithOptions) throws IOException {
40 | return new ProcessBuilder()
41 | .command(commandWithOptions.split("\\s+"))
42 | .inheritIO()
43 | .start();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/java9/src/test/java/org/avb/whatsnew/java9/optional/NewOptionalMethodsDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java9.optional;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.util.Optional;
8 |
9 | public class NewOptionalMethodsDemo {
10 | private static final Logger LOGGER = LoggerFactory.getLogger(NewOptionalMethodsDemo.class);
11 |
12 | @Test
13 | public void testIfPresentOfElse() {
14 | User user1 = new User("stackman");
15 |
16 | /* Login: stackman */
17 | Optional.of(user1) // present
18 | .ifPresentOrElse(
19 | u -> LOGGER.info("Login: {}", u.login),
20 | () -> LOGGER.info("Not present"));
21 |
22 | User user2 = null;
23 |
24 | /* User not present */
25 | Optional.ofNullable(user2) // not present
26 | .ifPresentOrElse(
27 | u -> LOGGER.info("Login: {}", u.login),
28 | () -> LOGGER.info("User not present"));
29 | }
30 |
31 | @Test
32 | public void testOr() {
33 | User user1 = new User("stackman");
34 |
35 | /* Optional[User{login='stackman'}] */
36 | LOGGER.info("{}",
37 | Optional.of(user1)
38 | .or(() -> Optional.of(new User("default"))));
39 |
40 | User user2 = null;
41 |
42 | /* Optional[User{login='default'}] */
43 | LOGGER.info("{}",
44 | Optional.ofNullable(user2)
45 | .or(() -> Optional.of(new User("default"))));
46 | }
47 |
48 | @Test
49 | public void testToStream() {
50 | User user1 = new User("stackman");
51 |
52 | /* stackman */
53 | Optional.of(user1)
54 | .stream()
55 | .map(u -> u.login)
56 | .forEach(LOGGER::info);
57 |
58 | /* empty stream */
59 | long count = Optional.empty()
60 | .stream()
61 | .count();
62 |
63 | LOGGER.info("The number of elements: {}", count); // 0
64 | }
65 | }
66 |
67 | class User {
68 | final String login;
69 |
70 | User(String login) {
71 | this.login = login;
72 | }
73 |
74 | @Override
75 | public String toString() {
76 | return "User{" +
77 | "login='" + login + '\'' +
78 | '}';
79 | }
80 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.avb.whatsnew
8 | whats-new-in-java
9 | pom
10 | 1.0-SNAPSHOT
11 |
12 |
13 | common
14 | java13
15 | java12
16 | java11
17 | java10
18 | java9
19 | java8
20 |
21 |
22 |
23 | UTF-8
24 | 13
25 | 13
26 |
27 |
28 |
29 |
30 |
31 | org.avb.whatsnew
32 | common
33 | ${project.version}
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.testng
41 | testng
42 | 6.14.3
43 | test
44 |
45 |
46 | org.slf4j
47 | slf4j-api
48 | 1.7.25
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ch.qos.logback
58 | logback-classic
59 | 1.2.3
60 |
61 |
62 | ch.qos.logback
63 | logback-core
64 | 1.2.3
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | maven-compiler-plugin
73 |
74 |
75 | --enable-preview
76 |
77 |
78 |
79 |
80 | maven-surefire-plugin
81 |
82 | --enable-preview
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/java10/src/test/java/org/avb/whatsnew/java10/typeinference/LocalVariableTypeInferenceDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java10.typeinference;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.io.*;
8 | import java.time.LocalDate;
9 | import java.util.List;
10 | import java.util.TreeMap;
11 |
12 | import static java.util.stream.Collectors.*;
13 |
14 | /**
15 | * Java 10 extends type inference for local variables using var.
16 | * This feature totally reduces the number of boilerplate code.
17 | *
18 | * Important, this type inference is available only for local variables with the initializer.
19 | * It does not work for member variables, method parameters, return types.
20 | * It also does not work without initialization.
21 | */
22 | public class LocalVariableTypeInferenceDemo {
23 | private static final Logger LOGGER = LoggerFactory.getLogger(LocalVariableTypeInferenceDemo.class);
24 |
25 | @Test
26 | public void testTypeInferenceInSimpleCases() {
27 | var zero = 0; // int
28 | var one = 1L; // long
29 |
30 | LOGGER.info("zero is denoted as {}, one is denoted as {}", zero, one);
31 |
32 | var msg = "How are you today?"; // String
33 | var today = LocalDate.now(); // LocalDate
34 |
35 | LOGGER.info("{}: {}", today, msg);
36 | }
37 |
38 | @Test
39 | public void testTypeInferenceWithGenerics() {
40 | var persons = List.of( // var replaces List
41 | new Person("John", 34),
42 | new Person("Margo", 28),
43 | new Person("Alex", 18),
44 | new Person("John", 22),
45 | new Person("Katy", 34),
46 | new Person("Paul", 28)
47 | );
48 |
49 | LOGGER.info("Persons: {}", persons);
50 |
51 | var namesByAges = persons.stream() // var replaces Map>
52 | .collect(
53 | groupingBy(
54 | Person::getAge,
55 | TreeMap::new,
56 | mapping(Person::getName,
57 | toList())));
58 |
59 | namesByAges.forEach((age, names) -> LOGGER.info("{}: {}", age, names));
60 |
61 | /* Sorted output cause we use TreeMap
62 | * 18: [Alex]
63 | * 22: [John]
64 | * 28: [Margo, Paul]
65 | * 34: [John, Katy]
66 | */
67 | }
68 |
69 | @Test
70 | public void testTypeInferenceWithTryWithResources() throws IOException {
71 | var bytes = new byte[] { 0x01, 0x02, 0x03 };
72 |
73 | try (var output = new ByteArrayOutputStream();
74 | var input = new ByteArrayInputStream(bytes)) {
75 | LOGGER.info("Inside try-with-resources");
76 | input.transferTo(output);
77 | }
78 | }
79 | }
80 |
81 | class Person {
82 | private final String name;
83 | private final int age;
84 |
85 | Person(String name, int age) {
86 | this.name = name;
87 | this.age = age;
88 | }
89 |
90 | public String getName() {
91 | return name;
92 | }
93 |
94 | public int getAge() {
95 | return age;
96 | }
97 |
98 | @Override
99 | public String toString() {
100 | return "Person{" +
101 | "name='" + name + '\'' +
102 | ", age=" + age +
103 | '}';
104 | }
105 | }
--------------------------------------------------------------------------------
/java12/src/test/java/org/avb/whatsnew/java12/superswitch/SwitchExpressionDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java12.superswitch;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.Assert;
6 | import org.testng.annotations.Test;
7 |
8 | import static org.testng.Assert.*;
9 |
10 | /**
11 | * In Java 12, the old switch statement was drastically improved.
12 | * As you remember, the old form of this statement looked old fashioned (not like in Kotlin/Scala :))
13 | * and, more importantly, it could lead to errors due to the lack of breaks.
14 | * Now, it supports multiple values in cases, allows you do not use breaks,
15 | * can be used an expression and has a nice arrow lambda-like syntax (->) in addition to old colon (:).
16 | */
17 | public class SwitchExpressionDemo {
18 | private static final Logger LOGGER = LoggerFactory.getLogger(SwitchExpressionDemo.class);
19 |
20 | /** Just to avoid some warnings in examples */
21 | private int getDayNumber() {
22 | return 6;
23 | }
24 |
25 | /**
26 | * Here is an old switch statement which exists from beginning.
27 | * Just to remember.
28 | */
29 | @Test
30 | public void testTraditionalSwitchStatement() {
31 | String dayOfWeekName;
32 |
33 | switch (getDayNumber()) {
34 | case 1:
35 | dayOfWeekName = "Monday";
36 | break;
37 | case 2:
38 | dayOfWeekName = "Tuesday";
39 | break;
40 | case 3:
41 | dayOfWeekName = "Wednesday";
42 | break;
43 | case 4:
44 | dayOfWeekName = "Thursday";
45 | break;
46 | case 5:
47 | dayOfWeekName = "Friday";
48 | break;
49 | case 6:
50 | dayOfWeekName = "Saturday";
51 | break;
52 | case 7:
53 | dayOfWeekName = "Sunday";
54 | break;
55 | default:
56 | dayOfWeekName = "An unknown day";
57 | }
58 |
59 | LOGGER.info(dayOfWeekName);
60 | }
61 |
62 | /**
63 | * Here is a new switch statement with the nice arrow syntax.
64 | * Now it looks more compact and less boilerplate.
65 | * It's hard to make a mistake forgetting breaks.
66 | */
67 | @Test
68 | public void testModernSwitchStatementWithArrowSyntax() {
69 | String dayOfWeekName;
70 |
71 | switch (getDayNumber()) {
72 | case 1 -> dayOfWeekName = "Monday";
73 | case 2 -> dayOfWeekName = "Tuesday";
74 | case 3 -> dayOfWeekName = "Wednesday";
75 | case 4 -> dayOfWeekName = "Thursday";
76 | case 5 -> dayOfWeekName = "Friday";
77 | case 6 -> dayOfWeekName = "Saturday";
78 | case 7 -> dayOfWeekName = "Sunday";
79 | default -> dayOfWeekName = "An unknown day";
80 | }
81 |
82 | LOGGER.info(dayOfWeekName);
83 | }
84 |
85 | /**
86 | * Here is a switch expression which allows you to return the result from cases
87 | * and avoid duplicate assignments. It looks fantastic!
88 | */
89 | @Test
90 | public void testSwitchExpressionWithArrowSyntax() {
91 | var dayOfWeekName = switch (getDayNumber()) {
92 | case 1 -> "Monday";
93 | case 2 -> "Tuesday";
94 | case 3 -> "Wednesday";
95 | case 4 -> "Thursday";
96 | case 5 -> "Friday";
97 | case 6 -> "Saturday";
98 | case 7 -> "Sunday";
99 | default -> "An unknown day";
100 | };
101 |
102 | LOGGER.info(dayOfWeekName);
103 | }
104 |
105 | /**
106 | * Another interesting thing, that switch (both statement and expression)
107 | * supports multiple labels in cases.
108 | * See the calculate method below.
109 | */
110 | @Test
111 | public void testSwitchWithMultipleLabelsInCases() {
112 | assertEquals(calculate(3, 5, "+"), 8);
113 | assertEquals(calculate(4, 2, "plus"), 6);
114 | assertEquals(calculate(3, 3, "*"), 9);
115 | assertEquals(calculate(2, 3, "multiply"), 6);
116 | // and so on
117 | }
118 |
119 | private int calculate(int a, int b, String op) {
120 | return switch (op) {
121 | case "+", "plus" -> a + b;
122 | case "-", "minus" -> a - b;
123 | case "*", "mult", "multiply" -> a * b;
124 | default -> {
125 | String errorMsg = String.format("An unknown operation: %s", op);
126 | throw new IllegalArgumentException(errorMsg);
127 | }
128 | };
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/java9/src/test/java/org/avb/whatsnew/java9/collections/CollectionUtilityMethodsDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java9.collections;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Map.Entry;
10 | import java.util.Set;
11 |
12 | /**
13 | * Before Java 9 we could write Arrays.asList(...) to get a list with the specified elements.
14 | * We could also use methods like unmodifiableXXX, singletonXXX or emptyXXX to create unmodifiable lists, sets and maps.
15 | * But these methods are very tedious and verbose.
16 | *
17 | * Since Java 9 there is a set of convenient methods for creating immutable collections with the specified elements.
18 | * It returns an optimized implementation of a collection depending on the number of elements
19 | */
20 | public class CollectionUtilityMethodsDemo {
21 | private static final Logger LOGGER = LoggerFactory.getLogger(CollectionUtilityMethodsDemo.class);
22 |
23 | @Test
24 | public void testListOf() {
25 | List emptyList = List.of();
26 | LOGGER.info("{}", emptyList); // []
27 |
28 | List singletonList = List.of("Orange");
29 | LOGGER.info("{}", singletonList); // [Orange]
30 |
31 | List currencies = List.of("EUR", "USD", "CAD", "CNY");
32 | LOGGER.info("{}", currencies); // [EUR, USD, CAD, CNY]
33 | }
34 |
35 | @Test
36 | public void testSetOf() {
37 | Set emptySet = Set.of();
38 | LOGGER.info("{}", emptySet); // []
39 |
40 | Set singletonList = Set.of("Orange");
41 | LOGGER.info("{}", singletonList); // [Orange]
42 |
43 | Set currencies = Set.of("EUR", "USD", "CAD", "CNY");
44 | LOGGER.info("{}", currencies); // Order can be any: [USD, CAD, CNY, EUR]
45 | }
46 |
47 | @Test
48 | public void testMapOf() {
49 | Map emptyMap = Map.of();
50 | LOGGER.info("{}", emptyMap); // {}
51 |
52 | Map oneAddress = Map.of("google.com", "172.217.22.174");
53 | LOGGER.info("{}", oneAddress); // {google.com=172.217.22.174}
54 |
55 | /* The format is: key, value, key, value, ... */
56 | Map addresses = Map.of(
57 | "google.com", "172.217.22.174",
58 | "github.com", "192.30.253.112",
59 | "stackoverflow.com", "151.101.129.69"
60 | );
61 |
62 | /* {google.com=172.217.22.174, stackoverflow.com=151.101.129.69, github.com=192.30.253.112} */
63 | LOGGER.info("{}", addresses);
64 | }
65 |
66 | @Test(expectedExceptions = NullPointerException.class)
67 | public void testNullNotAllowed() {
68 | List digits = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null);
69 | }
70 |
71 | @Test(expectedExceptions = UnsupportedOperationException.class)
72 | public void testImmutability() {
73 | List digits = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
74 | digits.add(100); // it throws an exception
75 | }
76 |
77 | @Test
78 | public void testMapOfEntries() {
79 | Map emptyMap = Map.ofEntries();
80 | LOGGER.info("{}", emptyMap); // {}
81 |
82 | Entry nameToPhone1 = Map.entry("Darryl C. Boyd","1-651-643-4993");
83 | Entry nameToPhone2 = Map.entry("Irene R. Jenkins","1-651-643-4993");
84 | LOGGER.info("{}", nameToPhone2); // Irene R. Jenkins=1-651-643-4993
85 |
86 | Map nameToPhone = Map.ofEntries(nameToPhone1, nameToPhone2);
87 |
88 | /* {Darryl C. Boyd=1-651-643-4993, Irene R. Jenkins=1-651-643-4993} */
89 | LOGGER.info("{}", nameToPhone);
90 | }
91 |
92 | @Test
93 | public void testListOfSingleElementWithClass() {
94 | List singleLanguageList = List.of("Java");
95 |
96 | /* java.util.ImmutableCollections$List12: [Java] */
97 | LOGGER.info("{}: {}", singleLanguageList.getClass(), singleLanguageList);
98 | }
99 |
100 | @Test
101 | public void testListOfTwoElementsWithClass() {
102 | List twoLanguagesList = List.of("Java", "Kotlin");
103 |
104 | /* java.util.ImmutableCollections$List12: [Java, Kotlin] */
105 | LOGGER.info("{}: {}", twoLanguagesList.getClass(), twoLanguagesList);
106 | }
107 |
108 | @Test
109 | public void testListOfThreeElementsWithClass() {
110 | List threeLanguagesList = List.of("Java", "Kotlin", "Scala");
111 |
112 | /* java.util.ImmutableCollections$ListN: [Java, Kotlin, Scala] */
113 | LOGGER.info("{}: {}", threeLanguagesList.getClass(), threeLanguagesList);
114 | }
115 |
116 | @Test
117 | public void testListOfNElementsWithClass() {
118 | List fibSequencePart = List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89);
119 |
120 | /* java.util.ImmutableCollections$ListN: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] */
121 | LOGGER.info("{}: {}", fibSequencePart.getClass(), fibSequencePart);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/java11/src/test/java/org/avb/whatsnew/java11/httpclient/HttpClientDemo.java:
--------------------------------------------------------------------------------
1 | package org.avb.whatsnew.java11.httpclient;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.DataProvider;
6 | import org.testng.annotations.Test;
7 |
8 | import java.io.IOException;
9 | import java.net.Authenticator;
10 | import java.net.PasswordAuthentication;
11 | import java.net.URI;
12 | import java.net.http.HttpClient;
13 | import java.net.http.HttpRequest;
14 | import java.net.http.HttpResponse;
15 | import java.net.http.WebSocket;
16 | import java.time.Duration;
17 | import java.util.concurrent.*;
18 |
19 | /**
20 | * Java 11 standardizes a new http client introduced in Java 9.
21 | * It supports HTTP/1.1, HTTP/2, WebSockets, and both sync and async requests.
22 | *
23 | * By default the client will send requests using HTTP/2.
24 | * If the server does not support HTTP/2 yet, the request will be downgraded to HTTP/1.1.
25 | *
26 | * HTTP/2 key features:
27 | * - header compression,
28 | * - single connection for parallel requests,
29 | * - fully multiplexed,
30 | * - server push,
31 | * - more compact binary format.
32 | *
33 | * More about HTTP/2: https://http2.github.io/faq/#what-are-the-key-differences-to-http1x
34 | */
35 | public class HttpClientDemo {
36 | private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientDemo.class);
37 | private static final Duration REQUEST_DURATION = Duration.ofSeconds(60);
38 |
39 | @DataProvider(name = "httpResourcesProvider")
40 | public static Object[][] resources() {
41 | return new Object[][] {
42 | { "https://http2.github.io" },
43 | { "https://github.io" }
44 | };
45 | }
46 |
47 | @Test(dataProvider = "httpResourcesProvider")
48 | public void testSyncGetRequest(String url) throws InterruptedException {
49 | var request = HttpRequest.newBuilder()
50 | .uri(URI.create(url))
51 | .timeout(REQUEST_DURATION)
52 | .GET() // default
53 | .build();
54 |
55 | var httpClient = HttpClient.newHttpClient();
56 |
57 | try {
58 | var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
59 | printResponse(response, false);
60 | } catch (IOException e) {
61 | LOGGER.error(e.getMessage(), e);
62 | }
63 | }
64 |
65 | @Test
66 | public void testSyncPostRequest() throws InterruptedException {
67 | var fakePostService = URI.create("https://jsonplaceholder.typicode.com/posts");
68 |
69 | var request = HttpRequest.newBuilder()
70 | .uri(fakePostService)
71 | .timeout(REQUEST_DURATION)
72 | .header("Content-Type", "application/json")
73 | .POST(HttpRequest.BodyPublishers.ofString("{\"title\":\"foo\", \"body\":\"bar\"}"))
74 | .build();
75 |
76 | var httpClient = HttpClient.newHttpClient();
77 |
78 | try {
79 | var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
80 | printResponse(response, true);
81 | } catch (IOException e) {
82 | LOGGER.error(e.getMessage(), e);
83 | }
84 | }
85 |
86 | @Test(dataProvider = "httpResourcesProvider")
87 | public void testAsyncGetRequest(String url) {
88 | var request = HttpRequest.newBuilder()
89 | .uri(URI.create(url))
90 | .timeout(REQUEST_DURATION)
91 | .GET()
92 | .build();
93 |
94 | var httpClient = HttpClient.newHttpClient();
95 |
96 | httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
97 | .thenAccept(response -> printResponse(response, false))
98 | .join();
99 | }
100 |
101 | @Test
102 | public void testWebSocket() throws InterruptedException {
103 | var echoServerURI = URI.create("wss://echo.websocket.org");
104 | var executor = Executors.newSingleThreadExecutor();
105 | var httpClient = HttpClient.newBuilder()
106 | .executor(executor)
107 | .build();
108 |
109 | var webSocket = httpClient.newWebSocketBuilder().buildAsync(echoServerURI, new WebSocket.Listener() {
110 |
111 | @Override
112 | public void onOpen(WebSocket webSocket) {
113 | LOGGER.info("Successfully connected to {}", echoServerURI);
114 | webSocket.sendText("Hello!", true);
115 | WebSocket.Listener.super.onOpen(webSocket);
116 | }
117 |
118 | @Override
119 | public CompletionStage> onText(WebSocket webSocket, CharSequence data, boolean last) {
120 | LOGGER.info("received: {}", data);
121 | return WebSocket.Listener.super.onText(webSocket, data, last);
122 | }
123 |
124 | @Override
125 | public CompletionStage> onClose(WebSocket webSocket, int statusCode, String reason) {
126 | LOGGER.info("Closed with status " + statusCode + ", reason: " + reason);
127 | executor.shutdown();
128 | return WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
129 | }
130 | }).join();
131 |
132 | Thread.sleep(1000);
133 |
134 | webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok")
135 | .thenRun(() -> LOGGER.info("Sent close"));
136 | }
137 |
138 | @Test
139 | public void testBasicAuth() throws InterruptedException {
140 | var request = HttpRequest.newBuilder() // GET by default
141 | .uri(URI.create("https://postman-echo.com/basic-auth"))
142 | .timeout(REQUEST_DURATION)
143 | .build();
144 |
145 | var authClient = HttpClient
146 | .newBuilder()
147 | .authenticator(new Authenticator() {
148 | @Override
149 | protected PasswordAuthentication getPasswordAuthentication() {
150 | return new PasswordAuthentication("postman", "password".toCharArray());
151 | }
152 | })
153 | .build();
154 |
155 | try {
156 | var response = authClient.send(request, HttpResponse.BodyHandlers.ofString());
157 | printResponse(response, true);
158 | } catch (IOException e) {
159 | LOGGER.error(e.getMessage(), e);
160 | }
161 | }
162 |
163 |
164 | @Test
165 | public void testHandleBodyAsReactiveStream() throws InterruptedException {
166 | var request = HttpRequest.newBuilder()
167 | .uri(URI.create("http://emojitrack-gostreamer.herokuapp.com/subscribe/eps"))
168 | .timeout(REQUEST_DURATION)
169 | .GET()
170 | .build();
171 |
172 | var httpClient = HttpClient.newHttpClient();
173 |
174 | // TODO subscriber
175 | // TODO send async
176 | }
177 |
178 |
179 | private static void printResponse(HttpResponse response, boolean printBody) {
180 | LOGGER.info("Protocol version: {}", response.version());
181 | LOGGER.info("Status code: {}", response.statusCode());
182 | response.headers()
183 | .map()
184 | .forEach((name, values) -> LOGGER.info(name + ": " + values));
185 | LOGGER.info("Body length {}", response.body().length());
186 | if (printBody) {
187 | LOGGER.info("Body {}", response.body());
188 | }
189 | }
190 | }
--------------------------------------------------------------------------------