├── 01-default-static-interface-methods.md ├── 02-lambdas.md ├── 03-streams.md ├── 04-collectors.md ├── 05-optionals.md ├── 06-map.md ├── 07-building-functional-programs.md ├── 08-date-time-api.md ├── 09-completable-futures.md ├── 10-nashorn.md ├── 11-tools.md ├── 12-annotations.md ├── LICENSE ├── README.md ├── ch10 └── lines.js ├── code ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── shekhargulati │ │ └── java8_tutorial │ │ ├── ch01 │ │ ├── App1.java │ │ ├── App2.java │ │ ├── App3.java │ │ ├── BasicCalculator.java │ │ ├── Calculator.java │ │ └── CalculatorFactory.java │ │ ├── ch02 │ │ ├── Example1_Lambda.java │ │ ├── Example2_Lambda.java │ │ ├── Example3_Functionalnterfaces.java │ │ ├── Example4_MethodReferences.java │ │ └── InvalidFunctionInterface.java │ │ ├── ch03 │ │ ├── Example1_Java7.java │ │ ├── Example1_Stream.java │ │ ├── Examples.java │ │ └── ParallelStreamExample.java │ │ ├── ch05 │ │ ├── TaskNotFoundException.java │ │ ├── TaskRepository.java │ │ └── domain │ │ │ ├── Task.java │ │ │ └── User.java │ │ ├── ch06 │ │ └── MapExample.java │ │ ├── ch09 │ │ └── CompletableFutureExample.java │ │ ├── ch10 │ │ ├── NashornExample1.java │ │ ├── NashornExample2.java │ │ └── NashornExample3.java │ │ ├── ch12 │ │ ├── CreateVm.java │ │ ├── MyAnnotation.java │ │ └── VmManager.java │ │ ├── domain │ │ ├── Task.java │ │ └── TaskType.java │ │ └── utils │ │ └── DataUtils.java │ └── test │ └── java │ └── com │ └── shekhargulati │ └── java8_tutorial │ ├── ch01 │ └── CalculatorTest.java │ └── ch06 │ └── MapExampleTest.java └── java8-slides.pdf /01-default-static-interface-methods.md: -------------------------------------------------------------------------------- 1 | 接口的默认和静态方法 2 | -------- 3 | 4 | > - 原文链接: [Default and Static Methods for Interfaces](https://github.com/shekhargulati/java8-the-missing-tutorial/blob/master/01-default-static-interface-methods.md) 5 | - 原文作者: [shekhargulati](https://github.com/shekhargulati) 6 | - 译者: leege100 7 | - 状态: 完成 8 | 9 | 众所周知,我们应该使用接口编程,接口使得在交互时不需要关注具体的实现细节,从而保持程序的松散耦合。在API的设计中,设计简约而清晰的接口非常重要。被称作固定定律的接口分离定律,其中有一条就讲到了应该设计更小的特定客户端接口而不是一个通用目的的接口。良好的接口设计是让应用程序和库的API保持简洁高效的关键。 10 | 11 | 如果你曾有过接口API设计的经验,那么有时候你会感觉到为API增加方法的必要。但是,如果API一旦发布了,几乎无法在保证已接口类的代码不变的情况下来增加新的方法。举个例子,假设你设计了一个简单的API Calculator,里面有add、subtract、devide和multiply函数。我们可以如下申明。简便起见,我们使用int类型。 12 | 13 | public interface Calculator { 14 | 15 | int add(int first, int second); 16 | 17 | int subtract(int first, int second); 18 | 19 | int divide(int number, int divisor); 20 | 21 | int multiply(int first, int second); 22 | } 23 | 24 | 为了实现Calculator这个接口,需要写如下一个BasicCalculator类 25 | 26 | public class BasicCalculator implements Calculator { 27 | 28 | @Override 29 | public int add(int first, int second) { 30 | return first + second; 31 | } 32 | 33 | @Override 34 | public int subtract(int first, int second) { 35 | return first - second; 36 | } 37 | 38 | @Override 39 | public int divide(int number, int divisor) { 40 | if (divisor == 0) { 41 | throw new IllegalArgumentException("divisor can't be zero."); 42 | } 43 | return number / divisor; 44 | } 45 | 46 | @Override 47 | public int multiply(int first, int second) { 48 | return first * second; 49 | } 50 | } 51 | 52 | 静态工厂方法 53 | 54 | 乍一看,Calculator这个API还是非常简单实用,其他开发者只需要创建一个BasicCalculator就可以使用这个API。比如下面这两段代码: 55 | 56 | Calculator calculator = new BasicCalculator(); 57 | int sum = calculator.add(1, 2); 58 | 59 | BasicCalculator cal = new BasicCalculator(); 60 | int difference = cal.subtract(3, 2); 61 | 62 | 然而,事实上给人的感觉却是此API的用户并不是面向这个接口进行编程,而是面向这个接口的实现类在编程。因为你的API并没有强制要求用户把BasicCalculator当着一个public类来对接口进行编程,如果你把BasicCalculator类变成protected类型你就需要提供一个静态的工厂类来谨慎的提供Calculator的实现,代码如下: 63 | 64 | class BasicCalculator implements Calculator { 65 | // rest remains same 66 | } 67 | 接下来,写一个工厂类来提供Calculator的实例,代码如下: 68 | 69 | public abstract class CalculatorFactory { 70 | 71 | public static Calculator getInstance() { 72 | return new BasicCalculator(); 73 | } 74 | } 75 | 76 | 这样,用户就被强制要求对Calculator接口进行编程,并且不需要关注接口的详细实现。 77 | 78 | 尽管我们通过上面的方式达到了我们想要的效果,但是我们却因增加了一个新的CalculaorFactory类而增加了API的表现层次。如果其他开发者要有效的利用这个Calculator API,就必须要先学会一个新的类的使用。但在java8之前,这是唯一的实现方式。 79 | 80 | java8允许用户在接口中定义静态的方法,允许API的设计者在接口的内部定义类似getInstance的静态工具方法,从而保持API简洁。静态的接口方法可以用来替代我们平时专为某一类型写的helper类。比如,Collections类是用来为Collection和相关接口提供各种helper方法的一个类。Collections类中定义的方法应该直接添加到Collection及其它相关子接口上。 81 | 82 | 下面的代码就是Java8中直接在Calculator接口中添加静态getInstance方法的例证: 83 | 84 | public interface Calculator { 85 | 86 | static Calculator getInstance() { 87 | return new BasicCalculator(); 88 | } 89 | 90 | int add(int first, int second); 91 | 92 | int subtract(int first, int second); 93 | 94 | int divide(int number, int divisor); 95 | 96 | int multiply(int first, int second); 97 | 98 | } 99 | 100 | 随着时间来演进API 101 | 102 | 有些用户打算通过新建一个Calculator子接口并在子接口中添加新方法或是通过重写一个继承自Calculator接口的实现类来增加类似remainder的方法。与他们沟通之后你会发现,他们可能仅仅是想向Calculator接口中添加一个类似remainder的方法。感觉这仅仅是一个非常简单的API变化,所以你加入了一个方法,代码如下: 103 | public interface Calculator { 104 | 105 | static Calculator getInstance() { 106 | return new BasicCalculator(); 107 | } 108 | 109 | int add(int first, int second); 110 | 111 | int subtract(int first, int second); 112 | 113 | int divide(int number, int divisor); 114 | 115 | int multiply(int first, int second); 116 | 117 | int remainder(int number, int divisor); // new method added to API 118 | } 119 | 在接口中增加方法会破坏代码的兼容性,这意味着那些实现Calculator接口的用户需要为remainder方法添加实现,否则,代码无法编译通过。这对API设计者来说非常麻烦,因为它让代码演进变得非常困难。Java8之前,开发者不能直接在API中实现方法,这也使得在一个API中添加一个或者多个接口定义变得十分困难。 120 | 121 | 为了让API的演进变得更加容易,Java8允许用户对定义在接口中的方法提供默认的实现。这就是通常的default或者defender方法。接口的实现类不需要再提供接口的实现方法。如果接口的实现类提供了方法,那么实现类的方法会被调用,否则接口中的方法会被调用。List接口有几个定义在接口中的默认方法,比如replaceAll,sort和splitIterator。 122 | 123 | default void replaceAll(UnaryOperator operator) { 124 | Objects.requireNonNull(operator); 125 | final ListIterator li = this.listIterator(); 126 | while (li.hasNext()) { 127 | li.set(operator.apply(li.next())); 128 | } 129 | } 130 | 131 | 我们可以向下面的代码一样定义一个默认的方法来解决API的问题。默认方法通常用在使用那些已经存在的方法,比如remainder是用来定义使用subtract,multiply和divice的一个方法。 132 | 133 | default int remainder(int number, int divisor) { 134 | return subtract(number, multiply(divisor, divide(number, divisor))); 135 | } 136 | 137 | 多重继承 138 | 139 | 一个类只能继承自一个父类,但可以实现多个接口。既然在接口中实现方法是可行的,接口方法的多实现也是可行的。之前,Java已经在类型上支持多重继承,如今也支持在表现阶段的多重继承。下面这三条规则决定了哪个方法会被调用。 140 | 141 | 规则一:定义在类中的方法优先于定义在接口的方法: 142 | 143 | interface A { 144 | default void doSth(){ 145 | System.out.println("inside A"); 146 | } 147 | } 148 | class App implements A{ 149 | 150 | @Override 151 | public void doSth() { 152 | System.out.println("inside App"); 153 | } 154 | 155 | public static void main(String[] args) { 156 | new App().doSth(); 157 | } 158 | } 159 | 160 | 运行结果:inside App。调用的是在类中定义的方法。 161 | 162 | 规则二:否则,调用定制最深的接口中的方法。 163 | 164 | interface A { 165 | default void doSth() { 166 | System.out.println("inside A"); 167 | } 168 | } 169 | interface B {} 170 | interface C extends A { 171 | default void doSth() { 172 | System.out.println("inside C"); 173 | } 174 | } 175 | class App implements C, B, A { 176 | 177 | public static void main(String[] args) { 178 | new App().doSth(); 179 | } 180 | } 181 | 182 | 运行结果:inside C 183 | 184 | 规则三:否则,直接调用指定接口的实现方法 185 | 186 | interface A { 187 | default void doSth() { 188 | System.out.println("inside A"); 189 | } 190 | } 191 | interface B { 192 | default void doSth() { 193 | System.out.println("inside B"); 194 | } 195 | } 196 | class App implements B, A { 197 | 198 | @Override 199 | public void doSth() { 200 | B.super.doSth(); 201 | } 202 | 203 | public static void main(String[] args) { 204 | new App().doSth(); 205 | } 206 | } 207 | 208 | 运行结果: inside B 209 | 210 | 水平有限,欢迎大家指点和建议,^-^ -------------------------------------------------------------------------------- /02-lambdas.md: -------------------------------------------------------------------------------- 1 | Lambda表达式 2 | ----- 3 | 4 | > - 原文链接: [Lambdas](https://github.com/shekhargulati/java8-the-missing-tutorial/blob/master/02-lambdas.md) 5 | - 原文作者: [shekhargulati](https://github.com/shekhargulati) 6 | - 译者: leege100 7 | - 状态: 完成 8 | 9 | lambda表达式是java8中最重要的特性之一,它让代码变得简洁并且允许你传递行为。曾几何时,Java总是因为代码冗长和缺少函数式编程的能力而饱受批评。随着函数式编程变得越来越受欢迎,Java也被迫开始拥抱函数式编程。否则,Java会被大家逐步抛弃。 10 | 11 | Java8是使得这个世界上最流行的编程语言采用函数式编程的一次大的跨越。一门编程语言要支持函数式编程,就必须把函数作为其一等公民。在Java8之前,只能通过匿名内部类来写出函数式编程的代码。而随着lambda表达式的引入,函数变成了一等公民,并且可以像其他变量一样传递。 12 | 13 | lambda表达式允许开发者定义一个不局限于定界符的匿名函数,你可以像使用编程语言的其他程序结构一样来使用它,比如变量申明。如果一门编程语言需要支持高阶函数,lambda表达式就派上用场了。高阶函数是指把函数作为参数或者返回结果是一个函数那些函数。 14 | 15 | > 这个章节的代码如下[ch02 package](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch02). 16 | 17 | 随着Java8中lambda表达式的引入,Java也支持高阶函数。接下来让我们来分析这个经典的lambda表达式示例--Java中Collections类的一个sort函数。sort函数有两种调用方式,一种需要一个List作为参数,另一种需要一个List参数和一个Comparator。第二种sort函数是一个接收lambda表达式的高阶函数的实例,如下: 18 | 19 | ```java 20 | List names = Arrays.asList("shekhar", "rahul", "sameer"); 21 | Collections.sort(names, (first, second) -> first.length() - second.length()); 22 | ``` 23 | 24 | 上面的代码是根据names的长度来进行排序,运行的结果如下: 25 | 26 | ``` 27 | [rahul, sameer, shekhar] 28 | ``` 29 | 30 | 上面代码片段中的`(first,second) -> first.length() - second.length()`表达式是一个`Comparator`的lambda表达式。 31 | 32 | * `(first,second)`是`Comparator` 中`compare`方法的参数。 33 | 34 | * `first.length() - second.length()`比较name字符串长度的函数体。 35 | 36 | * `->` 是用来把参数从函数体中分离出来的操作符。 37 | 38 | 在我们深入研究Java8中的lambda表达式之前,我们先来追溯一下他们的历史,了解它们为什么会存在。 39 | 40 | ## lambda表达式的历史 41 | 42 | lambda表达式源自于`λ演算`.[λ演算](https://en.wikipedia.org/wiki/Lambda_calculus)起源于用函数式来制定表达式计算概念的研究[Alonzo Church](https://en.wikipedia.org/wiki/Alonzo_Church)。`λ演算`是图灵完整的。图灵完整意味着你可以用lambda表达式来表达任何数学算式。 43 | 44 | `λ演算`后来成为了函数式编程语言强有力的理论基础。诸如 Hashkell、Lisp等著名的函数式编程语言都是基于`λ演算`.高阶函数的概念就来自于`λ演算`。 45 | 46 | `λ演算`中最主要的概念就是表达式,一个表达式可以用如下形式来表示: 47 | 48 | ``` 49 | := | | 50 | ``` 51 | 52 | * **variable** -- 一个variable就是一个类似用x、y、z来代表1、2、n等数值或者lambda函数式的占位符。 53 | 54 | * **function** -- 它是一个匿名函数定义,需要一个变量,并且生成另一个lambda表达式。例如,`λx.x*x`是一个求平方的函数。 55 | 56 | * **application** -- 把一个函数当成一个参数的行为。假设你想求10的平方,那么用λ演算的方式的话你需要写一个求平方的函数`λx.x*x`并把10应用到这个函数中去,这个函数程序就会返回`(λx.x*x) 10 = 10*10 = 100`。但是你不仅可以求10的平方,你可以把一个函数传给另一个函数然后生成另一个函数。比如,`(λx.x*x) (λz.z+10)` 会生成这样一个新的函数 `λz.(z+10)*(z+10)`。现在,你可以用这个函数来生成一个数加上10的平方。这就是一个高阶函数的实例。 57 | 58 | 现在,你已经理解了`λ演算`和它对函数式编程语言的影响。下面我们继续学习它们在java8中的实现。 59 | 60 | ## 在java8之前传递行为 61 | 62 | Java8之前,传递行为的唯一方法就是通过匿名内部类。假设你在用户完成注册后,需要在另外一个线程中发送一封邮件。在Java8之前,可以通过如下方式: 63 | 64 | ```java 65 | sendEmail(new Runnable() { 66 | @Override 67 | public void run() { 68 | System.out.println("Sending email..."); 69 | } 70 | }); 71 | ``` 72 | sendEmail方法定义如下: 73 | 74 | ```java 75 | public static void sendEmail(Runnable runnable) 76 | ``` 77 | 78 | 上面的代码的问题不仅仅在于我们需要把行为封装进去,比如`run`方法在一个对象里面;更糟糕的是,它容易混淆开发者真正的意图,比如把行为传递给`sendEmail`函数。如果你用过一些类似Guava的库,那么你就会切身感受到写匿名内部类的痛苦。下面是一个简单的例子,过滤所有标题中包含**lambda**字符串的task。 79 | 80 | 81 | ```java 82 | Iterable lambdaTasks = Iterables.filter(tasks, new Predicate() { 83 | @Override 84 | public boolean apply(Task task) { 85 | return input.getTitle().contains("lambda"); 86 | } 87 | }); 88 | ``` 89 | 90 | 使用Java8的Stream API,开发者不用太第三方库就可以写出上面的代码,我们将在下一章[chapter 3](./03-streams.md)讲述streams相关的知识。所以,继续往下阅读! 91 | 92 | ## Java 8 Lambda表达式 93 | 94 | 在Java8中,我们可以用lambda表达式写出如下代码,这段代码和上面提到的是同一个例子。 95 | 96 | ```java 97 | sendEmail(() -> System.out.println("Sending email...")); 98 | ``` 99 | 100 | 上面的代码非常简洁,并且能够清晰的传递编码者的意图。`()`用来表示无参函数,比如`Runnable`接口的中`run`方法不含任何参数,直接就可以用`()`来代替。`->`是将参数和函数体分开的lambda操作符,上例中,`->`后面是打印`Sending email`的相关代码。 101 | 102 | 下面再次通过Collections.sort这个例子来了解带参数的lambda表达式如何使用。要将names列表中的name按照字符串的长度排序,需要传递一个`Comparator`给sort函数。`Comparator`的定义如下 103 | 104 | ```java 105 | Comparator comparator = (first, second) -> first.length() - second.length(); 106 | ``` 107 | 108 | 上面写的lambda表达式相当于Comparator接口中的compare方法。`compare`方法的定义如下: 109 | 110 | ```java 111 | int compare(T o1, T o2); 112 | ``` 113 | 114 | `T`是传递给`Comparator`接口的参数类型,在本例中names列表是由`String`组成,所以`T`代表的是`String`。 115 | 116 | 在lambda表达式中,我们不需要明确指出参数类型,`javac`编译器会通过上下文自动推断参数的类型信息。由于我们是在对一个由`String`类型组成的List进行排序并且`compare`方法仅仅用一个T类型,所以Java编译器自动推断出两个参数都是`String`类型。根据上下文推断类型的行为称为**类型推断**。Java8提升了Java中已经存在的类型推断系统,使得对lambda表达式的支持变得更加强大。`javac`会寻找紧邻lambda表达式的一些信息通过这些信息来推断出参数的正确类型。 117 | 118 | > 在大多数情况下,`javac`会根据上下文自动推断类型。假设因为丢失了上下文信息或者上下文信息不完整而导致无法推断出类型,代码就不会编译通过。例如,下面的代码中我们将`String`类型从`Comparator`中移除,代码就会编译失败。 119 | 120 | 121 | ```java 122 | Comparator comparator = (first, second) -> first.length() - second.length(); // compilation error - Cannot resolve method 'length()' 123 | ``` 124 | 125 | ## Lambda表达式在Java8中的运行机制 126 | 127 | 你可能已经发现lambda表达式的类型是一些类似上例中Comparator的接口。但并不是每个接口都可以使用lambda表达式,***只有那些仅仅包含一个非实例化抽象方法的接口才能使用lambda表达式***。这样的接口被称着**函数式接口**并且它们能够被`@FunctionalInterface`注解注释。Runnable接口就是函数式接口的一个例子。 128 | 129 | ```java 130 | @FunctionalInterface 131 | public interface Runnable { 132 | public abstract void run(); 133 | } 134 | ``` 135 | 136 | `@FunctionalInterface`注解不是必须的,但是它能够让工具知道这一个接口是一个函数式接口并表现有意义的行为。例如,如果你试着这编译一个用`@FunctionalInterface`注释自己并且含有多个抽象方法的接口,编译就会报出这样一个错***Multiple non-overriding abstract methods found***。同样的,如果你给一个不含有任何方法的接口添加`@FunctionalInterface`注解,会得到如下错误信息,***No target method found***. 137 | 138 | 下面来回答一个你大脑里一个非常重大的疑问,***Java8的lambda表达式是否只是一个匿名内部类的语法糖或者函数式接口是如何被转换成字节码的?*** 139 | 140 | 答案是**NO**,Java8不采用匿名内部类的原因主要有两点: 141 | 142 | 1. **性能影响**: 如果lambda表达式是采用匿名内部类实现的,那么每一个lambda表达式都会在磁盘上生成一个class文件。当JVM启动时,这些class文件会被加载进来,因为所有的class文件都需要在启动时加载并且在使用前确认,从而会导致JVM的启动变慢。 143 | 144 | 2. **向后的扩展性**: 如果Java8的设计者从一开始就采用匿名内部类的方式,那么这将限制lambda表达式未来的使发展范围。 145 | 146 | ### 使用动态启用 147 | 148 | Java8的设计者决定采用在Java7中新增的`动态启用`来延迟在运行时的加载策略。当`javac`编译代码时,它会捕获代码中的lambda表达式并且生成一个`动态启用`的调用地址(称为lambda工厂)。当`动态启用`被调用时,就会向lambda表达式发生转换的地方返回一个函数式接口的实例。比如,在Collections.sort这个例子中,它的字节码如下: 149 | 150 | ``` 151 | public static void main(java.lang.String[]); 152 | Code: 153 | 0: iconst_3 154 | 1: anewarray #2 // class java/lang/String 155 | 4: dup 156 | 5: iconst_0 157 | 6: ldc #3 // String shekhar 158 | 8: aastore 159 | 9: dup 160 | 10: iconst_1 161 | 11: ldc #4 // String rahul 162 | 13: aastore 163 | 14: dup 164 | 15: iconst_2 165 | 16: ldc #5 // String sameer 166 | 18: aastore 167 | 19: invokestatic #6 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 168 | 22: astore_1 169 | 23: invokedynamic #7, 0 // InvokeDynamic #0:compare:()Ljava/util/Comparator; 170 | 28: astore_2 171 | 29: aload_1 172 | 30: aload_2 173 | 31: invokestatic #8 // Method java/util/Collections.sort:(Ljava/util/List;Ljava/util/Comparator;)V 174 | 34: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 175 | 37: aload_1 176 | 38: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 177 | 41: return 178 | } 179 | ``` 180 | 181 | 上面代码的关键部分位于第23行`23: invokedynamic #7, 0 // InvokeDynamic #0:compare:()Ljava/util/Comparator;`这里创建了一个`动态启用`的调用。 182 | 183 | 接下来是将lambda表达式的内容转换到一个将会通过`动态启用`来调用的方法中。在这一步中,JVM实现者有自由选择策略的权利。 184 | 185 | 这里我仅粗略的概括一下,具体的内部标准见这里 http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html. 186 | 187 | ## 匿名类 vs lambda表达式 188 | 189 | 下面我们对匿名类和lambda表达式做一个对比,以此来区分它们的不同。 190 | 191 | 1. 在匿名类中,`this` 指代的是匿名类本身;而在lambda表达式中,`this`指代的是lambda表达式所在的这个类。 192 | 193 | 2. 在匿名类中可以shadow包含此匿名类的变量,而在lambda表达式中就会报编译错误。 194 | 195 | 3. lambda表达式的类型是由上下文决定的,而匿名类中必须在创建实例的时候明确指定。 196 | 197 | ## 我需要自己去写函数式接口吗? 198 | 199 | Java8默认带有许多可以直接在代码中使用的函数式接口。它们位于`java.util.function`包中,下面简单介绍几个: 200 | 201 | ### java.util.function.Predicate 202 | 203 | 此函数式接口是用来定义对一些条件的检查,比如一个predicate。Predicate接口有一个叫`test`的方法,它需要一个`T`类型的值,返回值为布尔类型。例如,在一个`names`列表中找出所有以**s**开头的name就可以像如下代码这样使用predicate。 204 | 205 | ```java 206 | Predicate namesStartingWithS = name -> name.startsWith("s"); 207 | ``` 208 | 209 | ### java.util.function.Consumer 210 | 211 | 这个函数式接口用于表现那些不需要产生任何输出的行为。Consumer接口中有一个叫做`accept`的方法,它需要一个`T`类型的参数并且没有返回值。例如,用指定信息发送一封邮件: 212 | 213 | ```java 214 | Consumer messageConsumer = message -> System.out.println(message); 215 | ``` 216 | 217 | ### java.util.function.Function 218 | 219 | 这个函数式接口需要一个值并返回一个结果。例如,如果需要将所有`names`列表中的name转换为大写,可以像下面这样写一个Function: 220 | 221 | ```java 222 | Function toUpperCase = name -> name.toUpperCase(); 223 | ``` 224 | 225 | ### java.util.function.Supplier 226 | 227 | 这个函数式接口不需要传值,但是会返回一个值。它可以像下面这样,用来生成唯一的标识符 228 | 229 | ```java 230 | Supplier uuidGenerator= () -> UUID.randomUUID().toString(); 231 | ``` 232 | 233 | 在接下来的章节中,我们会学习更多的函数式接口。 234 | 235 | ## Method references 236 | 237 | 有时候,你需要为一个特定方法创建lambda表达式,比如`Function strToLength = str -> str.length();`,这个表达式仅仅在`String`对象上调用`length()`方法。可以这样来简化它,`Function strToLength = String::length;`。仅调用一个方法的lambda表达式,可以用缩写符号来表示。在`String::length`中,`String`是目标引用,`::`是定界符,`length`是目标引用要调用的方法。静态方法和实例方法都可以使用方法引用。 238 | 239 | ### Static method references 240 | 241 | 假设我们需要从一个数字列表中找出最大的一个数字,那我们可以像这样写一个方法引用`Function, Integer> maxFn = Collections::max`。`max`是一`Collections`里的一个静态方法,它需要传入一个`List`类型的参数。接下来你就可以这样调用它,`maxFn.apply(Arrays.asList(1, 10, 3, 5))`。上面的lambda表达式等价于`Function, Integer> maxFn = (numbers) -> Collections.max(numbers);`。 242 | 243 | ### Instance method references 244 | 245 | 在这样的情况下,方法引用用于一个实例方法,比如`String::toUpperCase`是在一个`String`引用上调用 `toUpperCase`方法。还可以使用带参数的方法引用,比如:`BiFunction concatFn = String::concat`。`concatFn`可以这样调用:`concatFn.apply("shekhar", "gulati")`。`String``concat`方法在一个String对象上调用并且传递一个类似`"shekhar".concat("gulati")`的参数。 246 | 247 | ## Exercise >> Lambdify me 248 | 249 | 下面通过一段代码,来应用所学到的。 250 | 251 | ```java 252 | public class Exercise_Lambdas { 253 | 254 | public static void main(String[] args) { 255 | List tasks = getTasks(); 256 | List titles = taskTitles(tasks); 257 | for (String title : titles) { 258 | System.out.println(title); 259 | } 260 | } 261 | 262 | public static List taskTitles(List tasks) { 263 | List readingTitles = new ArrayList<>(); 264 | for (Task task : tasks) { 265 | if (task.getType() == TaskType.READING) { 266 | readingTitles.add(task.getTitle()); 267 | } 268 | } 269 | return readingTitles; 270 | } 271 | 272 | } 273 | ``` 274 | 275 | 上面这段代码首先通过工具方法`getTasks`取得所有的Task,这里我们不去关心`getTasks`方法的具体实现,`getTasks`能够通过webservice或者数据库或者内存获取task。一旦得到了tasks,我们就过滤所有处于reading状态的task,并且从task中提取他们的标题,最后返回所有处于reading状态task的标题。 276 | 277 | 下面我们简单的重构下--在一个list上使用foreach和方法引用。 278 | 279 | ```java 280 | public class Exercise_Lambdas { 281 | 282 | public static void main(String[] args) { 283 | List tasks = getTasks(); 284 | List titles = taskTitles(tasks); 285 | titles.forEach(System.out::println); 286 | } 287 | 288 | public static List taskTitles(List tasks) { 289 | List readingTitles = new ArrayList<>(); 290 | for (Task task : tasks) { 291 | if (task.getType() == TaskType.READING) { 292 | readingTitles.add(task.getTitle()); 293 | } 294 | } 295 | return readingTitles; 296 | } 297 | 298 | } 299 | ``` 300 | 301 | 使用`Predicate`来过滤tasks 302 | 303 | ```java 304 | public class Exercise_Lambdas { 305 | 306 | public static void main(String[] args) { 307 | List tasks = getTasks(); 308 | List titles = taskTitles(tasks, task -> task.getType() == TaskType.READING); 309 | titles.forEach(System.out::println); 310 | } 311 | 312 | public static List taskTitles(List tasks, Predicate filterTasks) { 313 | List readingTitles = new ArrayList<>(); 314 | for (Task task : tasks) { 315 | if (filterTasks.test(task)) { 316 | readingTitles.add(task.getTitle()); 317 | } 318 | } 319 | return readingTitles; 320 | } 321 | 322 | } 323 | ``` 324 | 325 | 使用`Function`来将task中的title提取出来。 326 | 327 | ```java 328 | public class Exercise_Lambdas { 329 | 330 | public static void main(String[] args) { 331 | List tasks = getTasks(); 332 | List titles = taskTitles(tasks, task -> task.getType() == TaskType.READING, task -> task.getTitle()); 333 | titles.forEach(System.out::println); 334 | } 335 | 336 | public static List taskTitles(List tasks, Predicate filterTasks, Function extractor) { 337 | List readingTitles = new ArrayList<>(); 338 | for (Task task : tasks) { 339 | if (filterTasks.test(task)) { 340 | readingTitles.add(extractor.apply(task)); 341 | } 342 | } 343 | return readingTitles; 344 | } 345 | } 346 | ``` 347 | 348 | 把方法引用当着提取器来使用。 349 | 350 | 351 | 352 | ```java 353 | public static void main(String[] args) { 354 | List tasks = getTasks(); 355 | List titles = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Task::getTitle); 356 | titles.forEach(System.out::println); 357 | List createdOnDates = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Task::getCreatedOn); 358 | createdOnDates.forEach(System.out::println); 359 | List filteredTasks = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Function.identity()); 360 | filteredTasks.forEach(System.out::println); 361 | } 362 | ``` 363 | 364 | 我们也可以自己编写`函数式接口`,这样可以清晰的把开发者的意图传递给读者。我们可以写一个继承自`Function`接口的`TaskExtractor`接口。这个接口的输入类型是固定的`Task`类型,输出类型由实现的lambda表达式来决定。这样开发者就只需要关注输出结果的类型,因为输入的类型永远都是Task。 365 | 366 | ```java 367 | public class Exercise_Lambdas { 368 | 369 | public static void main(String[] args) { 370 | List tasks = getTasks(); 371 | List filteredTasks = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, TaskExtractor.identityOp()); 372 | filteredTasks.forEach(System.out::println); 373 | } 374 | 375 | public static List filterAndExtract(List tasks, Predicate filterTasks, TaskExtractor extractor) { 376 | List readingTitles = new ArrayList<>(); 377 | for (Task task : tasks) { 378 | if (filterTasks.test(task)) { 379 | readingTitles.add(extractor.apply(task)); 380 | } 381 | } 382 | return readingTitles; 383 | } 384 | 385 | } 386 | 387 | 388 | interface TaskExtractor extends Function { 389 | 390 | static TaskExtractor identityOp() { 391 | return t -> t; 392 | } 393 | } 394 | ``` 395 | -------------------------------------------------------------------------------- /03-streams.md: -------------------------------------------------------------------------------- 1 | Streams 2 | ------ 3 | 4 | > - 原文链接: [Streams](https://github.com/shekhargulati/java8-the-missing-tutorial/blob/master/03-streams.md) 5 | - 原文作者: [shekhargulati](https://github.com/shekhargulati) 6 | - 译者: leege100 7 | - 状态: 完成 8 | 9 | 10 | 在第二章中,我们学习到了lambda表达式允许我们在不创建新类的情况下传递行为,从而帮助我们写出干净简洁的代码。lambda表达式是一种简单的语法结构,它通过使用函数式接口来帮助开发者简单明了的传递意图。当采用lambda表达式的设计思维来设计API时,lambda表达式的强大就会得到体现,比如我们在第二节讨论的使用函数式接口编程的API[lambdas chapter](https://github.com/leege100/java8-the-missing-tutorial-zh/blob/master/02-lambdas.md#do-i-need-to-write-my-own-functional-interfaces)。 11 | 12 | Stream是java8引入的一个重度使用lambda表达式的API。Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。直观意味着开发者在写代码时只需关注他们想要的结果是什么而无需关注实现结果的具体方式。这一章节中,我们将介绍为什么我们需要一种新的数据处理API、Collection和Stream的不同之处以及如何将StreamAPI应用到我们的编码中。 13 | 14 | > 本节的代码见 [ch03 package](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch03). 15 | 16 | 17 | ## 为什么我们需要一种新的数据处理抽象概念? 18 | 19 | 在我看来,主要有两点: 20 | 21 | 1. Collection API 不能提供更高阶的结构来查询数据,因而开发者不得不为实现大多数琐碎的任务而写一大堆样板代码。 22 | 23 | 2、对集合数据的并行处理有一定的限制,如何使用Java语言的并发结构、如何高效的处理数据以及如何高效的并发都需要由程序员自己来思考和实现。 24 | 25 | ##Java 8之前的数据处理 26 | 27 | 阅读下面这一段代码,猜猜看它是拿来做什么的。 28 | 29 | ```java 30 | public class Example1_Java7 { 31 | 32 | public static void main(String[] args) { 33 | List tasks = getTasks(); 34 | 35 | List readingTasks = new ArrayList<>(); 36 | for (Task task : tasks) { 37 | if (task.getType() == TaskType.READING) { 38 | readingTasks.add(task); 39 | } 40 | } 41 | Collections.sort(readingTasks, new Comparator() { 42 | @Override 43 | public int compare(Task t1, Task t2) { 44 | return t1.getTitle().length() - t2.getTitle().length(); 45 | } 46 | }); 47 | for (Task readingTask : readingTasks) { 48 | System.out.println(readingTask.getTitle()); 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | 上面这段代码是用来按照字符串长度的排序打印所有READING类型的task的title。所有Java开发者每天都会写这样的代码,为了写出这样一个简单的程序,我们不得不写下15行Java代码。然而上面这段代码最大的问题不在于其代码长度,而在于不能清晰传达开发者的意图:过滤出所有READING的task、按照字符串的长度排序然后生成一个String类型的List。 55 | 56 | ##Java8中的数据处理 57 | 58 | 可以像下面这段代码这样,使用java8中的Stream API来实现与上面代码同等的效果。 59 | 60 | ```java 61 | public class Example1_Stream { 62 | 63 | public static void main(String[] args) { 64 | List tasks = getTasks(); 65 | 66 | List readingTasks = tasks.stream() 67 | .filter(task -> task.getType() == TaskType.READING) 68 | .sorted((t1, t2) -> t1.getTitle().length() - t2.getTitle().length()) 69 | .map(Task::getTitle) 70 | .collect(Collectors.toList()); 71 | 72 | readingTasks.forEach(System.out::println); 73 | } 74 | } 75 | ``` 76 | 77 | 78 | 上面这段代码中,形成了一个由多个stream操作组成的管道。 79 | 80 | * **stream()** - 通过在类似上面`tasks` `List`的集合源上调用 `stream()`方法来创建一个stream的管道。 81 | 82 | * **filter(Predicate)** - 这个操作用来提取stream中匹配predicate定义规则的元素。如果你有一个stream,你可以在它上面调用零次或者多次间断的操作。lambda表达式`task -> task.getType() == TaskType.READING`定义了一个用来过滤出所有READING的task的规则。 83 | 84 | * **sorted(Comparator)**: This operation returns a stream consisting of all the stream elements sorted by the Comparator defined by lambda expression i.e. in the example shown above.此操作返回一个stream,此stream由所有按照lambda表达式定义的Comparator来排序后的stream元素组成,在上面代码中排序的表达式是(t1, t2) -> t1.getTitle().length() - t2.getTitle().length(). 85 | 86 | * **map(Function)**: 此操作返回一个stream,该stream的每个元素来自原stream的每个元素通过Function处理后得到的结果。 87 | 88 | * **collect(toList())** -此操作把上面对stream进行各种操作后的结果装进一个list中。 89 | 90 | ###为什么说Java8更好 91 | 92 | In my opinion Java 8 code is better because of following reasons: 93 | 在我看来,Java8的代码更好主要有以下几点原因: 94 | 95 | 1. Java8代码能够清晰地表达开发者对数据过滤、排序等操作的意图。 96 | 97 | 2. 通过使用Stream API格式的更高抽象,开发者表达他们所想要的是什么而不是怎么去得到这些结果。 98 | 99 | 3. Stream API为数据处理提供一种统一的语言,使得开发者在谈论数据处理时有共同的词汇。当两个开发者讨论`filter`函数时,你都会明白他们都是在进行一个数据过滤操作。 100 | 101 | 4. 开发者不再需要为实现数据处理而写的各种样板代码,也不再需要为loop代码或者临时集合来储存数据的冗余代码,Stream API会处理这一切。 102 | 103 | 5. Stream不会修改潜在的集合,它是非交换的。 104 | 105 | ## Stream是什么 106 | 107 | Stream是一个在某些数据上的抽象视图。比如,Stream可以是一个list或者文件中的几行或者其他任意的一个元素序列的视图。Stream API提供可以顺序表现或者并行表现的操作总和。***开发者需要明白一点,Stream是一种更高阶的抽象概念,而不是一种数据结构。Stream不会储存数据***Stream天生就**很懒**,只有在被使用到时才会执行计算。它允许我们产生无限的数据流(stream of data)。在Java8中,你可以像下面这样,非常轻松的写出一个无限制生成特定标识符的代码: 108 | 109 | ``` 110 | public static void main(String[] args) { 111 | Stream uuidStream = Stream.generate(() -> UUID.randomUUID().toString()); 112 | } 113 | ``` 114 | 115 | 在Stream接口中有诸如`of`、`generate`、`iterate`等多种静态工厂方法可以用来创建stream实例。上面提到的`generate`方法带有一个`Supplier`,`Supplier`是一个可以用来描述一个不需要任何输入且会产生一个值的函数的函数式接口,我们向`generate`方法中传递一个supplier,当它被调用时会生成一个特定标识符。 116 | 117 | ```java 118 | Supplier uuids = () -> UUID.randomUUID().toString() 119 | ``` 120 | 121 | 运行上面这段代码,什么都不会发生,因为Stream是懒加载的,直到被使用时才会执行。如果我们改成如下这段代码,我们就会在控制台看到打印出来的UUID。这段程序会一直执行下去。 122 | 123 | 124 | ```java 125 | public static void main(String[] args) { 126 | Stream uuidStream = Stream.generate(() -> UUID.randomUUID().toString()); 127 | uuidStream.forEach(System.out::println); 128 | } 129 | ``` 130 | 131 | Java8运行开发者通过在一个Collection上调用`stream`方法来创建Stream。Stream支持数据处理操作,从而开发者可以使用更高阶的数据处理结构来表达运算。 132 | 133 | ## Collection vs Stream 134 | 135 | 下面这张表阐述了Collection和Stream的不同之处 136 | 137 | ![Collection vs Stream](https://whyjava.files.wordpress.com/2015/10/collection_vs_stream.png) 138 | 139 | 下面我们来探讨内迭代(internal iteration)和外迭代(external iteration)的区别,以及懒赋值的概念。 140 | 141 | ### 外迭代(External iteration) vs (内迭代)internal iterationvs 142 | 143 | 上面谈到的Java8 Stream API代码和Collection API代码的区别在于由谁来控制迭代,是迭代器本身还是开发者。Stream API仅仅提供他们想要实现的操作,然后迭代器把这些操作应用到潜在Collection的每个元素中去。当对潜在的Collection进行的迭代操作是由迭代器本身控制时,就叫着`内迭代`;反之,当迭代操作是由开发者控制时,就叫着`外迭代`。Collection API中`for-each`结构的使用就是一个`外迭代`的例子。 144 | 145 | 有人会说,在Collection API中我们也不需要对潜在的迭代器进行操作,因为`for-each`结构已经替我们处理得很好了,但是`for-each`结构其实不过是一种iterator API的语法糖罢了。`for-each`尽管很简单,但是它有一些缺点 -- 1)只有固有顺序 2)容易写出生硬的命令式代码(imperative code) 3)难以并行。 146 | 147 | ### Lazy evaluation懒加载 148 | 149 | stream表达式在被终极操作方法调用之前不会被赋值计算。Stream API中的大多数操作会返回一个Stream。这些操作不会做任何的执行操作,它们只会构建这个管道。看着下面这段代码,预测一下它的输出会是什么。 150 | 151 | ```java 152 | List numbers = Arrays.asList(1, 2, 3, 4, 5); 153 | Stream stream = numbers.stream().map(n -> n / 0).filter(n -> n % 2 == 0); 154 | ``` 155 | 156 | 上面这段代码中,我们将stream元素中的数字除以0,我们也许会认为这段代码在运行时会抛出`ArithmeticExceptin`异常,而事实上不会。因为stream表达式只有在有终极操作被调用时才会被执行运算。如果我们为上面的stream加上终极操作,stream就会被执行并抛出异常。 157 | 158 | ```java 159 | List numbers = Arrays.asList(1, 2, 3, 4, 5); 160 | Stream stream = numbers.stream().map(n -> n / 0).filter(n -> n % 2 == 0); 161 | stream.collect(toList()); 162 | ``` 163 | 164 | 我们会得到如下的stack trace: 165 | 166 | ``` 167 | Exception in thread "main" java.lang.ArithmeticException: / by zero 168 | at org._7dayswithx.java8.day2.EagerEvaluationExample.lambda$main$0(EagerEvaluationExample.java:13) 169 | at org._7dayswithx.java8.day2.EagerEvaluationExample$$Lambda$1/1915318863.apply(Unknown Source) 170 | at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 171 | at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) 172 | at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) 173 | at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) 174 | at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) 175 | at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 176 | at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) 177 | ``` 178 | 179 | ## 使用Stream API 180 | 181 | Stream API提供了一大堆开发者可以用来从集合中查询数据的操作,这些操作分为两种--过渡操作和终极操作。 182 | 183 | **过渡操作**从已存在的stream上产生另一个新的stream的函数,比如`filter`,`map`, `sorted`,等。 184 | 185 | **终极操作**从stream上产生一个非stream结果的函数,如`collect(toList())` , `forEach`, `count`等。 186 | 187 | 过渡操作允许开发者构建在调用终极操作时才执行的管道。下面是Stream API的部分函数列表: 188 | 189 | stream-api 190 | 191 | ### 示例类 192 | 193 | 在本教程中,我们将会用Task管理类来解释这些概念。例子中,有一个叫Task的类,它是一个由用户来表现的类,其定义如下: 194 | 195 | ```java 196 | import java.time.LocalDate; 197 | import java.util.*; 198 | 199 | public class Task { 200 | private final String id; 201 | private final String title; 202 | private final TaskType type; 203 | private final LocalDate createdOn; 204 | private boolean done = false; 205 | private Set tags = new HashSet<>(); 206 | private LocalDate dueOn; 207 | 208 | // removed constructor, getter, and setter for brevity 209 | } 210 | ``` 211 | 例子中的数据集如下,在整个Stream API例子中我们都会用到它。 212 | 213 | ```java 214 | Task task1 = new Task("Read Version Control with Git book", TaskType.READING, LocalDate.of(2015, Month.JULY, 1)).addTag("git").addTag("reading").addTag("books"); 215 | 216 | Task task2 = new Task("Read Java 8 Lambdas book", TaskType.READING, LocalDate.of(2015, Month.JULY, 2)).addTag("java8").addTag("reading").addTag("books"); 217 | 218 | Task task3 = new Task("Write a mobile application to store my tasks", TaskType.CODING, LocalDate.of(2015, Month.JULY, 3)).addTag("coding").addTag("mobile"); 219 | 220 | Task task4 = new Task("Write a blog on Java 8 Streams", TaskType.WRITING, LocalDate.of(2015, Month.JULY, 4)).addTag("blogging").addTag("writing").addTag("streams"); 221 | 222 | Task task5 = new Task("Read Domain Driven Design book", TaskType.READING, LocalDate.of(2015, Month.JULY, 5)).addTag("ddd").addTag("books").addTag("reading"); 223 | 224 | List tasks = Arrays.asList(task1, task2, task3, task4, task5); 225 | ``` 226 | > 本章节暂不讨论Java8的Data Time API,这里我们就把它当着一个普通的日期的API。 227 | 228 | ### Example 1: 找出所有READING Task的标题,并按照它们的创建时间排序。 229 | 230 | 第一个例子我们将要实现的是,从Task列表中找出所有正在阅读的任务的标题,并根据它们的创建时间排序。我们要做的操作如下: 231 | 232 | 233 | 1. 过滤出所有TaskType为READING的Task。 234 | 2. 按照创建时间对task进行排序。 235 | 3. 获取每个task的title。 236 | 4. 将得到的这些title装进一个List中。 237 | 238 | 上面的四个操作步骤可以非常简单的翻译成下面这段代码: 239 | 240 | ```java 241 | private static List allReadingTasks(List tasks) { 242 | List readingTaskTitles = tasks.stream(). 243 | filter(task -> task.getType() == TaskType.READING). 244 | sorted((t1, t2) -> t1.getCreatedOn().compareTo(t2.getCreatedOn())). 245 | map(task -> task.getTitle()). 246 | collect(Collectors.toList()); 247 | return readingTaskTitles; 248 | } 249 | ``` 250 | 251 | 在上面的代码中,我们使用了Stream API中如下的一些方法: 252 | 253 | * **filter**:允许开发者定义一个判断规则来从潜在的stream中提取符合此规则的部分元素。规则**task -> task.getType() == TaskType.READING**意为从stream中选取所有TaskType 为READING的元素。 254 | 255 | * **sorted**: 允许开发者定义一个比较器来排序stream。上例中,我们根据创建时间来排序,其中的lambda表达式**(t1, t2) -> t1.getCreatedOn().compareTo(t2.getCreatedOn())**就对函数式接口Comparator中的`compare`函数进行了实现。 256 | 257 | * **map**: 需要一个实现了能够将一个stream转换成另一个stream的`Function`的lambda表达式作为参数,Function接口能够将一个stream转换为另一个stream。lambda表达式**task -> task.getTitle()**将一个task转化为标题。 258 | 259 | * **collect(toList())** 这是一个终极操作,它将所有READING的Task的标题的装进一个list中。 260 | 261 | 我们可以通过使用`Comparator`接口的`comparing`方法和方法引用来将上面的代码简化成如下代码: 262 | 263 | ```java 264 | public List allReadingTasks(List tasks) { 265 | return tasks.stream(). 266 | filter(task -> task.getType() == TaskType.READING). 267 | sorted(Comparator.comparing(Task::getCreatedOn)). 268 | map(Task::getTitle). 269 | collect(Collectors.toList()); 270 | 271 | } 272 | ``` 273 | 274 | > 从Java8开始,接口可以含有通过静态和默认方法来实现方法,在[ch01](./01-default-static-interface-methods.md)已经介绍过了。 275 | > 引用方法`Task::getCreatedOn`是由`Function`而来的。 276 | 277 | 上面代码中,我们使用了`Comparator`接口中的静态帮助方法`comparing`,此方法需要接收一个用来提取`Comparable`的`Function`作为参数,返回一个通过key进行比较的`Comparator`。方法引用`Task::getCreatedOn` 是由 `Function`而来的. 278 | 279 | 我们可以像如下代码这样,使用函数组合,通过在Comparator上调用`reversed()`方法,来非常轻松的颠倒排序。 280 | 281 | ```java 282 | public List allReadingTasksSortedByCreatedOnDesc(List tasks) { 283 | return tasks.stream(). 284 | filter(task -> task.getType() == TaskType.READING). 285 | sorted(Comparator.comparing(Task::getCreatedOn).reversed()). 286 | map(Task::getTitle). 287 | collect(Collectors.toList()); 288 | } 289 | ``` 290 | 291 | ### Example 2: 去除重复的tasks 292 | 293 | 假设我们有一个有很多重复task的数据集,可以像如下代码这样通过调用`distinct`方法来轻松的去除stream中的重复的元素: 294 | 295 | ```java 296 | public List allDistinctTasks(List tasks) { 297 | return tasks.stream().distinct().collect(Collectors.toList()); 298 | } 299 | ``` 300 | 301 | `distinct()`方法把一个stream转换成一个不含重复元素的stream,它通过对象的`equals`方法来判断对象是否相等。根据对象相等方法的判定,如果两个对象相等就意味着有重复,它就会从结果stream中移除。 302 | 303 | ### Example 3: 根据创建时间排序,找出前5个处于reading状态的task 304 | 305 | `limit`方法可以用来把结果集限定在一个给定的数字。`limit`是一个短路操作,意味着它不会为了得到结果而去运算所有元素。 306 | 307 | ```java 308 | public List topN(List tasks, int n){ 309 | return tasks.stream(). 310 | filter(task -> task.getType() == TaskType.READING). 311 | sorted(comparing(Task::getCreatedOn)). 312 | map(Task::getTitle). 313 | limit(n). 314 | collect(toList()); 315 | } 316 | ``` 317 | 318 | 可以像如下代码这样,同时使用`skip`方法和`limit`方法来创建某一页。 319 | 320 | ```java 321 | // page starts from 0. So to view a second page `page` will be 1 and n will be 5. 322 | //page从0开始,所以要查看第二页的话,`page`应该为1,n应该为5 323 | List readingTaskTitles = tasks.stream(). 324 | filter(task -> task.getType() == TaskType.READING). 325 | sorted(comparing(Task::getCreatedOn).reversed()). 326 | map(Task::getTitle). 327 | skip(page * n). 328 | limit(n). 329 | collect(toList()); 330 | ``` 331 | 332 | ### Example 4:统计状态为reading的task的数量 333 | 334 | 要得到所有正处于reading的task的数量,我们可以在stream中使用`count`方法来获得,这个方法是一个终极方法。 335 | 336 | ```java 337 | public long countAllReadingTasks(List tasks) { 338 | return tasks.stream(). 339 | filter(task -> task.getType() == TaskType.READING). 340 | count(); 341 | } 342 | ``` 343 | 344 | ### Example 5: 非重复的列出所有task中的全部标签 345 | 346 | 要找出不重复的标签,我们需要下面几个步骤 347 | 1. 获取每个task中的标签。 348 | 2. 把所有的标签放到一个stream中。 349 | 3. 删除重复的标签。 350 | 4. 把最终结果装进一个列表中。 351 | 352 | 第一步和第二步可以通过在`stream`上调用`flatMap`来得到。`flatMap`操作把通过调用`task.getTags().stream`得到的各个stream合成到一个stream。一旦我们把所有的tag放到一个stream中,我们就可以通过调用`distinct`方法来得到非重复的tag。 353 | 354 | ```java 355 | private static List allDistinctTags(List tasks) { 356 | return tasks.stream().flatMap(task -> task.getTags().stream()).distinct().collect(toList()); 357 | } 358 | ``` 359 | 360 | ### Example 6: 检查是否所有reading的task都有`book`标签 361 | 362 | Stream API有一些可以用来检测数据集中是否含有某个给定属性的方法,`allMatch`,`anyMatch`,`noneMatch`,`findFirst`,`findAny`。要判断是否所有状态为reading的task的title中都包含`books`标签,可以用如下代码来实现: 363 | 364 | ```java 365 | public boolean isAllReadingTasksWithTagBooks(List tasks) { 366 | return tasks.stream(). 367 | filter(task -> task.getType() == TaskType.READING). 368 | allMatch(task -> task.getTags().contains("books")); 369 | } 370 | ``` 371 | 372 | 要判断所有reading的task中是否存在一个task包含`java8`标签,可以通过`anyMatch`来实现,代码如下: 373 | 374 | ```java 375 | public boolean isAnyReadingTasksWithTagJava8(List tasks) { 376 | return tasks.stream(). 377 | filter(task -> task.getType() == TaskType.READING). 378 | anyMatch(task -> task.getTags().contains("java8")); 379 | } 380 | ``` 381 | 382 | ### Example 7: 创建一个所有title的总览 383 | 384 | 当你想要创建一个所有title的总览时就可以使用`reduce`操作,`reduce`能够把stream变成成一个值。`reduce`函数接受一个可以用来连接stream中所有元素的lambda表达式。 385 | 386 | ```java 387 | public String joinAllTaskTitles(List tasks) { 388 | return tasks.stream(). 389 | map(Task::getTitle). 390 | reduce((first, second) -> first + " *** " + second). 391 | get(); 392 | } 393 | ``` 394 | 395 | ### Example 8: 基本类型stream的操作 396 | 397 | 除了常见的基于对象的stream,Java8对诸如int,long,double等基本类型也提供了特定的stream。下面一起来看一些基本类型的stream的例子。 398 | 399 | 要创建一个值区间,可以调用`range`方法。`range`方法创建一个值为0到9的stream,不包含10。 400 | 401 | ```java 402 | IntStream.range(0, 10).forEach(System.out::println); 403 | ``` 404 | 405 | `rangeClosed`方法允许我们创建一个包含上限值的stream。因此,下面的代码会产生一个从1到10的stream。 406 | 407 | ```java 408 | IntStream.rangeClosed(1, 10).forEach(System.out::println); 409 | ``` 410 | 还可以像下面这样,通过在基本类型的stream上使用`iterate`方法来创建无限的stream: 411 | 412 | ```java 413 | LongStream infiniteStream = LongStream.iterate(1, el -> el + 1); 414 | ``` 415 | 要从一个无限的stream中过滤出所有偶数,可以用如下代码来实现: 416 | 417 | ```java 418 | infiniteStream.filter(el -> el % 2 == 0).forEach(System.out::println); 419 | ``` 420 | 可以通过使用`limit`操作来现在结果stream的个数,代码如下: 421 | We can limit the resulting stream by using the `limit` operation as shown below. 422 | 423 | ```java 424 | infiniteStream.filter(el -> el % 2 == 0).limit(100).forEach(System.out::println); 425 | ``` 426 | 427 | ### Example 9: 为数组创建stream 428 | 429 | 可以像如下代码这样,通过调用`Arrays`类的静态方法`stream`来把为数组建立stream: 430 | 431 | ```java 432 | String[] tags = {"java", "git", "lambdas", "machine-learning"}; 433 | Arrays.stream(tags).map(String::toUpperCase).forEach(System.out::println); 434 | ``` 435 | 436 | 还可以像如下这样,根据数组中特定起始下标和结束下标来创建stream。这里的起始下标包括在内,而结束下标不包含在内。 437 | 438 | ```java 439 | Arrays.stream(tags, 1, 3).map(String::toUpperCase).forEach(System.out::println); 440 | ``` 441 | 442 | ## Parallel Streams并发的stream 443 | 444 | 445 | 使用Stream有一个优势在于,由于stream采用内部迭代,所以java库能够有效的管理处理并发。可以在一个stream上调用`parallel`方法来使一个stream处于并行。`parallel`方法的底层实现基于JDK7中引入的`fork-join`API。默认情况下,它会产生与机器CPU数量相等的线程。下面的代码中,我们根据处理它们的线程来对将数字分组。在第4节中将学习`collect`和`groupingBy`函数,现在暂时理解为它可以根据一个key来对元素进行分组。 446 | 447 | ```java 448 | public class ParallelStreamExample { 449 | 450 | public static void main(String[] args) { 451 | Map> numbersPerThread = IntStream.rangeClosed(1, 160) 452 | .parallel() 453 | .boxed() 454 | .collect(groupingBy(i -> Thread.currentThread().getName())); 455 | 456 | numbersPerThread.forEach((k, v) -> System.out.println(String.format("%s >> %s", k, v))); 457 | } 458 | } 459 | ``` 460 | 461 | 在我的机器上,打印的结果如下: 462 | ``` 463 | ForkJoinPool.commonPool-worker-7 >> [46, 47, 48, 49, 50] 464 | ForkJoinPool.commonPool-worker-1 >> [41, 42, 43, 44, 45, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130] 465 | ForkJoinPool.commonPool-worker-2 >> [146, 147, 148, 149, 150] 466 | main >> [106, 107, 108, 109, 110] 467 | ForkJoinPool.commonPool-worker-5 >> [71, 72, 73, 74, 75] 468 | ForkJoinPool.commonPool-worker-6 >> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160] 469 | ForkJoinPool.commonPool-worker-3 >> [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 76, 77, 78, 79, 80] 470 | ForkJoinPool.commonPool-worker-4 >> [91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145] 471 | ``` 472 | 473 | 并不是每个工作的线程都处理相等数量的数字,可以通过更改系统属性来控制fork-join线程池的数量`System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "2")`。 474 | 475 | 另外一个会用到`parallel`操作的例子是,当你像下面这样要处理一个URL的列表时: 476 | 477 | ```java 478 | String[] urls = {"https://www.google.co.in/", "https://twitter.com/", "http://www.facebook.com/"}; 479 | Arrays.stream(urls).parallel().map(url -> getUrlContent(url)).forEach(System.out::println); 480 | ``` 481 | 482 | 如果你想更好的掌握什么时候应该使用并发的stream,推荐你阅读由Doug Lea和其他几位Java大牛写的文章[http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html)。 483 | -------------------------------------------------------------------------------- /04-collectors.md: -------------------------------------------------------------------------------- 1 | 收集器(Collectors) 2 | ------ 3 | 4 | > - 原文链接: [Collectors](https://github.com/shekhargulati/java8-the-missing-tutorial/blob/master/04-collectors.md) 5 | - 原文作者: [shekhargulati](https://github.com/shekhargulati) 6 | - 译者: leege100 7 | - 状态: 完成 8 | 9 | 在前一节中,我们已经了解到StreamAPI能够帮助我们用更直观简洁的方式来处理集合。现在我们来看一下`collect`方法,它是一个能够把stream管道中的结果集装进一个`List`集合的终极操作。 `collect`是一个把stream规约成一个value的规约操作,这里的value可以是一个Collection、Map或者一个value对象。在下面这几种情况下,可以使用`collect`操作。 10 | 11 | 1. **把stream规约到一个单独的值** stream的执行结果可以规约成一个单独的值,这个单独的值可以是`Collection`或者数值型的值如int、double等,还可以是一个自定义的值对象。 12 | 13 | 2. **在stream中对元素进行分组** 对stream中的所有task按照TaskType分组。这会生成一个一个`Map`,其中的每个entry都包含一个TaskType和与它相关联的Task。也可以使用其它任何的Collection来替代List。如果不需要把所有的task对应到一个TaskType,也可以生成一个`Map`。 14 | 3. **分离stream中的元素** 可以把一个stream分离到两个组中--正在进行中的和已经完成的task。 15 | 16 | ## Collector in Action 17 | 18 | 下面我们通过这个根据type来对task进行分组的例子,来体验`Collector`的作用。在java8中,我们可以像下面这样来实现根据TaskType分组。**请参考博客[day 2](http://shekhargulati.com/2015/07/26/day-2-lets-learn-about-streams/)** 19 | 20 | ```java 21 | private static Map> groupTasksByType(List tasks) { 22 | return tasks.stream().collect(Collectors.groupingBy(task -> task.getType())); 23 | } 24 | ``` 25 | 26 | 上面的代码使用了`Collectors`工具类中定义的`groupingBy` `Collector`方法。它创建一个map,其中key为`TaskType`、value为所有具有相同`TaskType`的task组成的一个list列表。在java7中要实现相同的功能,需要写如下的代码。 27 | 28 | ```java 29 | public static void main(String[] args) { 30 | List tasks = getTasks(); 31 | Map> allTasksByType = new HashMap<>(); 32 | for (Task task : tasks) { 33 | List existingTasksByType = allTasksByType.get(task.getType()); 34 | if (existingTasksByType == null) { 35 | List tasksByType = new ArrayList<>(); 36 | tasksByType.add(task); 37 | allTasksByType.put(task.getType(), tasksByType); 38 | } else { 39 | existingTasksByType.add(task); 40 | } 41 | } 42 | for (Map.Entry> entry : allTasksByType.entrySet()) { 43 | System.out.println(String.format("%s =>> %s", entry.getKey(), entry.getValue())); 44 | } 45 | } 46 | ``` 47 | 48 | ## 收集器(Collectors):常用规约操作 49 | 50 | `Collectors` 工具类提供了许多静态工具方法来为大多数常用的用户用例创建收集器,比如将元素装进一个集合中、将元素分组、根据不同标准对元素进行汇总等。本文中将覆盖大多数常见的`收集器(Collector)`。 51 | 52 | 53 | ## 规约到一个单独的值 54 | 55 | 如上面所说,收集器(collector)可以用来把stream收集到一个collection中或者产生一个单独的值。 56 | 57 | ### 把数据装进一个list列表中 58 | 59 | 下面我们给出第一个测试用例--将给定的任务列表的所有标题收集到一个List列表中。 60 | 61 | 62 | ```java 63 | import static java.util.stream.Collectors.toList; 64 | 65 | public class Example2_ReduceValue { 66 | public List allTitles(List tasks) { 67 | return tasks.stream().map(Task::getTitle).collect(toList()); 68 | } 69 | } 70 | ``` 71 | 72 | `toList`收集器通过使用List的`add`方法将元素添加到一个结果List列表中,`toList`收集器使用`ArrayList`作为List的实现。 73 | 74 | 75 | ### 将数据收集到一个Set中 76 | 77 | 如果要保证所收集的title不重复并且我们对数据的排序没有要求的话,可以采用`toSet`收集器。 78 | 79 | ```java 80 | import static java.util.stream.Collectors.toSet; 81 | 82 | public Set uniqueTitles(List tasks) { 83 | return tasks.stream().map(Task::getTitle).collect(toSet()); 84 | } 85 | ``` 86 | 87 | `toSet` 方法采用`HashSet`作为Set的实现来储存结果集。 88 | 89 | ### 把数据收集到一个Map中 90 | 91 | 可以使用`toMap`收集器将一个stream转换成一个Map。`toMap`收集器需要两个集合函数来提取map中的key和value。下面的代码中,`Task::getTitle`需要一个task并产生一个仅有一个标题的key。**task -> task**是一个用来返回自己的lambda表达式,上例中返回一个task。 92 | 93 | ```java 94 | private static Map taskMap(List tasks) { 95 | return tasks.stream().collect(toMap(Task::getTitle, task -> task)); 96 | } 97 | ``` 98 | 99 | 可以使用`Function`接口中的默认方法`identity`来让上面的代码代码变得更简洁明了、传递开发者意图时更加直接,下面是采用identity函数的代码。 100 | 101 | ```java 102 | import static java.util.function.Function.identity; 103 | 104 | private static Map taskMap(List tasks) { 105 | return tasks.stream().collect(toMap(Task::getTitle, identity())); 106 | } 107 | ``` 108 | 109 | 代码创建了一个Map,当出现相同的key时就会抛出如下的异常。 110 | 111 | ``` 112 | Exception in thread "main" java.lang.IllegalStateException: Duplicate key Task{title='Read Version Control with Git book', type=READING} 113 | at java.util.stream.Collectors.lambda$throwingMerger$105(Collectors.java:133) 114 | ``` 115 | 116 | `toMap`还有一个可以指定合并函数的变体,我们可以采用它来处理重复的副本。合并函数允许开发者指定一个解决同一个key冲突的规则。在下面的代码中,我们简单地使用最后一个value,当然你也可以写更加智能的算法来处理冲突。 117 | 118 | ```java 119 | private static Map taskMap_duplicates(List tasks) { 120 | return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2)); 121 | } 122 | ``` 123 | 124 | 我们还可以使用`toMap`的第三种变体方法来使用任何其它的Map实现,这需要指定`Map` 和`Supplier`来存放结果。 125 | 126 | ``` 127 | public Map collectToMap(List tasks) { 128 | return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2, LinkedHashMap::new)); 129 | } 130 | ``` 131 | 132 | 与`toMap`收集器类似,`toConcurrentMap`收集器可以产生`ConcurrntMap`来替代`HashMap`。 133 | 134 | ### Using other collections 使用其它的集合 135 | 136 | `toList`和`toSet`等特定的收集器不支持指定潜在的list或set的实现,当你想要像下面这样这样把结果聚合到其它类型的集合时可以采用`toCollection`收集器。 137 | 138 | ``` 139 | private static LinkedHashSet collectToLinkedHaskSet(List tasks) { 140 | return tasks.stream().collect(toCollection(LinkedHashSet::new)); 141 | } 142 | ``` 143 | 144 | ### 找出标题最长的task 145 | 146 | ```java 147 | public Task taskWithLongestTitle(List tasks) { 148 | return tasks.stream().collect(collectingAndThen(maxBy((t1, t2) -> t1.getTitle().length() - t2.getTitle().length()), Optional::get)); 149 | } 150 | ``` 151 | 152 | ### 统计tags的总数 153 | 154 | ```java 155 | public int totalTagCount(List tasks) { 156 | return tasks.stream().collect(summingInt(task -> task.getTags().size())); 157 | } 158 | ``` 159 | 160 | ### 生成task标题的汇总 161 | 162 | ```java 163 | public String titleSummary(List tasks) { 164 | return tasks.stream().map(Task::getTitle).collect(joining(";")); 165 | } 166 | ``` 167 | 168 | ## 将元素分组 169 | 170 | Collector收集器一个最常见的用户用例就是对元素进行分组,下面我们通过几个示例来理解我们可以如何来分组。 171 | 172 | ### Example 1: 根据type对tasks分组 173 | 174 | 下面这个例子,我们根据`TaskType`对task进行分组。通过使用`Collectors`工具类的`groupingBy`收集器,我们可以非常简单的完成这个功能。可以使用方法引用和静态引入来让代码变得更加简洁。 175 | 176 | ```java 177 | import static java.util.stream.Collectors.groupingBy; 178 | private static Map> groupTasksByType(List tasks) { 179 | return tasks.stream().collect(groupingBy(Task::getType)); 180 | } 181 | ``` 182 | 会产生如下的输出: 183 | ``` 184 | {CODING=[Task{title='Write a mobile application to store my tasks', type=CODING, createdOn=2015-07-03}], WRITING=[Task{title='Write a blog on Java 8 Streams', type=WRITING, createdOn=2015-07-04}], READING=[Task{title='Read Version Control with Git book', type=READING, createdOn=2015-07-01}, Task{title='Read Java 8 Lambdas book', type=READING, createdOn=2015-07-02}, Task{title='Read Domain Driven Design book', type=READING, createdOn=2015-07-05}]} 185 | ``` 186 | 187 | ### Example 2: 根据tags分组 188 | 189 | ```java 190 | private static Map> groupingByTag(List tasks) { 191 | return tasks.stream(). 192 | flatMap(task -> task.getTags().stream().map(tag -> new TaskTag(tag, task))). 193 | collect(groupingBy(TaskTag::getTag, mapping(TaskTag::getTask,toList()))); 194 | } 195 | 196 | private static class TaskTag { 197 | final String tag; 198 | final Task task; 199 | 200 | public TaskTag(String tag, Task task) { 201 | this.tag = tag; 202 | this.task = task; 203 | } 204 | 205 | public String getTag() { 206 | return tag; 207 | } 208 | 209 | public Task getTask() { 210 | return task; 211 | } 212 | } 213 | ``` 214 | 215 | ### Example 3: 根据tag和tag的个数分组 216 | 217 | ```java 218 | private static Map tagsAndCount(List tasks) { 219 | return tasks.stream(). 220 | flatMap(task -> task.getTags().stream().map(tag -> new TaskTag(tag, task))). 221 | collect(groupingBy(TaskTag::getTag, counting())); 222 | } 223 | ``` 224 | 225 | ### Example 4: 根据TaskType和createdOn分组 226 | 227 | ```java 228 | private static Map>> groupTasksByTypeAndCreationDate(List tasks) { 229 | return tasks.stream().collect(groupingBy(Task::getType, groupingBy(Task::getCreatedOn))); 230 | } 231 | ``` 232 | 233 | ## 分割 234 | 235 | 有时候,你需要根据一定的规则将一个数据集分成两个数据集。比如,我们可以定义一个分割函数,根据规则`进行时间早于今天和进行时间晚于今天`将task分成两组。 236 | 237 | ```java 238 | private static Map> partitionOldAndFutureTasks(List tasks) { 239 | return tasks.stream().collect(partitioningBy(task -> task.getDueOn().isAfter(LocalDate.now()))); 240 | } 241 | ``` 242 | 243 | ## 生成统计 244 | 245 | 另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。 246 | ```java 247 | IntSummaryStatistics summaryStatistics = tasks.stream().map(Task::getTitle).collect(summarizingInt(String::length)); 248 | System.out.println(summaryStatistics.getAverage()); //32.4 249 | System.out.println(summaryStatistics.getCount()); //5 250 | System.out.println(summaryStatistics.getMax()); //44 251 | System.out.println(summaryStatistics.getMin()); //24 252 | System.out.println(summaryStatistics.getSum()); //162 253 | ``` 254 | 255 | 还有一些其它的基本类型的变体,比如`LongSummaryStatistics` 和 `DoubleSummaryStatistics` 256 | 257 | 还可以用`combine`操作来把两个`IntSummaryStatistics`结合到一起。 258 | 259 | ```java 260 | firstSummaryStatistics.combine(secondSummaryStatistics); 261 | System.out.println(firstSummaryStatistics) 262 | ``` 263 | 264 | ## 把所有的titles连在一起 265 | 266 | ```java 267 | private static String allTitles(List tasks) { 268 | return tasks.stream().map(Task::getTitle).collect(joining(", ")); 269 | } 270 | ``` 271 | 272 | ## 编写一个自定义的收集器 273 | 274 | ```java 275 | import com.google.common.collect.HashMultiset; 276 | import com.google.common.collect.Multiset; 277 | 278 | import java.util.Collections; 279 | import java.util.EnumSet; 280 | import java.util.Set; 281 | import java.util.function.BiConsumer; 282 | import java.util.function.BinaryOperator; 283 | import java.util.function.Function; 284 | import java.util.function.Supplier; 285 | import java.util.stream.Collector; 286 | 287 | public class MultisetCollector implements Collector, Multiset> { 288 | 289 | @Override 290 | public Supplier> supplier() { 291 | return HashMultiset::create; 292 | } 293 | 294 | @Override 295 | public BiConsumer, T> accumulator() { 296 | return (set, e) -> set.add(e, 1); 297 | } 298 | 299 | @Override 300 | public BinaryOperator> combiner() { 301 | return (set1, set2) -> { 302 | set1.addAll(set2); 303 | return set1; 304 | }; 305 | } 306 | 307 | @Override 308 | public Function, Multiset> finisher() { 309 | return Function.identity(); 310 | } 311 | 312 | @Override 313 | public Set characteristics() { 314 | return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH)); 315 | } 316 | } 317 | ``` 318 | 319 | ```java 320 | import com.google.common.collect.Multiset; 321 | 322 | import java.util.Arrays; 323 | import java.util.List; 324 | 325 | public class MultisetCollectorExample { 326 | 327 | public static void main(String[] args) { 328 | List names = Arrays.asList("shekhar", "rahul", "shekhar"); 329 | Multiset set = names.stream().collect(new MultisetCollector<>()); 330 | 331 | set.forEach(str -> System.out.println(str + ":" + set.count(str))); 332 | 333 | } 334 | } 335 | ``` 336 | 337 | ## Word Count in Java 8 Java8中的单词统计 338 | 339 | 下面,我们通过java8中的Streams和Collectors编写一个非常著名的单词统计示例来结束本文。 340 | 341 | ```java 342 | public static void wordCount(Path path) throws IOException { 343 | Map wordCount = Files.lines(path) 344 | .parallel() 345 | .flatMap(line -> Arrays.stream(line.trim().split("\\s"))) 346 | .map(word -> word.replaceAll("[^a-zA-Z]", "").toLowerCase().trim()) 347 | .filter(word -> word.length() > 0) 348 | .map(word -> new SimpleEntry<>(word, 1)) 349 | .collect(groupingBy(SimpleEntry::getKey, counting())); 350 | wordCount.forEach((k, v) -> System.out.println(String.format("%s ==>> %d", k, v))); 351 | } 352 | ``` 353 | -------------------------------------------------------------------------------- /05-optionals.md: -------------------------------------------------------------------------------- 1 | Optionals 2 | ---- 3 | 4 | Every Java developer whether beginner, novice, or seasoned has in his/her lifetime experienced `NullPointerException`. This is a true fact that no Java developer can deny. We all have wasted or spent many hours trying to fix bugs caused by `NullPointerException`. According to `NullPointerException` JavaDoc, ***NullPointerException is thrown when an application attempts to use null in a case where an object is required.***. This means if we invoke a method or try to access a property on ***null*** reference then our code will explode and `NullPointerException` is thrown. In this chapter, you will learn how to write null free code using Java 8 `Optional`. 5 | 6 | > On a lighter note, if you look at the JavaDoc of NullPointerException you will find that author of this exception is ***unascribed***. If the author is unknown unascribed is used(nobody wants to take ownership of NullPointerException ;)) 7 | 8 | ## What are null references? 9 | 10 | In 2009 at QCon conference ***[Sir Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)*** stated that he invented null reference type while designing ***ALGOL W*** programming language. null was designed to signify absence of a value. He called *null references* as a *billion-dollar mistake*. You can watch the full video of his presentation on Infoq http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare. 11 | 12 | Most of the programming languages like C, C++, C#, Java, Scala, etc. has nullable type as part of their type system which allows the value to be set to a special value **Null** instead of other possible data type values. 13 | 14 | ## Why null references are a bad thing? 15 | 16 | Let's look at the example Task management domain classes shown below. Our domain model is very simple with only two classes -- Task and User. A task can be assigned to a user. 17 | 18 | > Code for this section is inside [ch05 package](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch05). 19 | 20 | ```java 21 | public class Task { 22 | private final String id; 23 | private final String title; 24 | private final TaskType type; 25 | private User assignedTo; 26 | 27 | public Task(String id, String title, TaskType type) { 28 | this.id = id; 29 | this.title = title; 30 | this.type = type; 31 | } 32 | 33 | public Task(String title, TaskType type) { 34 | this.id = UUID.randomUUID().toString(); 35 | this.title = title; 36 | this.type = type; 37 | } 38 | 39 | public String getTitle() { 40 | return title; 41 | } 42 | 43 | public TaskType getType() { 44 | return type; 45 | } 46 | 47 | public User getAssignedTo() { 48 | return assignedTo; 49 | } 50 | 51 | public void setAssignedTo(User assignedTo) { 52 | this.assignedTo = assignedTo; 53 | } 54 | } 55 | 56 | public class User { 57 | 58 | private final String username; 59 | private String address; 60 | 61 | public User(String username) { 62 | this.username = username; 63 | } 64 | 65 | public String getUsername() { 66 | return username; 67 | } 68 | 69 | public String getAddress() { 70 | return address; 71 | } 72 | 73 | public void setAddress(String address) { 74 | this.address = address; 75 | } 76 | } 77 | ``` 78 | 79 | Given the above domain model, if we have to find the User who is assigned a task with id `taskId` then we would write code as shown below. 80 | 81 | ```java 82 | public String taskAssignedTo(String taskId) { 83 | return taskRepository.find(taskId).getAssignedTo().getUsername(); 84 | } 85 | ``` 86 | 87 | The biggest problem with the code shown above is that absence of the value is not visible in the API i.e. if the `task` is not assigned to any user then the code will throw `NullPointerException` when `getAssignedTo` is called. The `taskRepository.find(taskId)` and `taskRepository.find(taskId).getAssignedTo()` could return `null`. This forces clients of the API to program defensively and check for null checks as shown below. 88 | 89 | ```java 90 | public String taskAssignedTo(String taskId) throws TaskNotFoundException { 91 | Task task = taskRepository.find(taskId); 92 | if (task != null) { 93 | User assignedTo = task.getAssignedTo(); 94 | if (assignedTo != null) 95 | return assignedTo.getUsername(); 96 | return "NotAssigned"; 97 | } 98 | throw new TaskNotFoundException(String.format("No task exist with id '%s'", taskId)); 99 | } 100 | ``` 101 | 102 | The code shown above misses developer intent and bloats client code with `if-null` checks. The developer somehow wanted to use optional data type but he was forced to write `if-null` checks. I am sure you would have written this kind of code in your day to day programming. 103 | 104 | ## Null Object pattern 105 | 106 | A common solution to working with `null` references is to use [Null Object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern). The idea behind this pattern is very simple instead of returning null you should return a null object that implements your interface or class. So, you can create a `NullUser` as shown below. 107 | 108 | ```java 109 | public class NullUser extends User { 110 | 111 | public NullUser(String username) { 112 | super("NotAssigned"); 113 | } 114 | } 115 | ``` 116 | 117 | So now we could return a `NullUser` when no user is assigned a task. We can change the `getAssignedTo` method to return `NullUser` when no user is assigned a task. 118 | 119 | ```java 120 | public User getAssignedTo() { 121 | return assignedTo == null ? NullUser.getInstance() : assignedTo; 122 | } 123 | ``` 124 | 125 | Now client code can be simplified to not use null check for user as shown below. In this example, it does not make sense to use Null Object pattern for `Task` because non-existence of task in the repository is an exception situation. Also, by adding `TaskNotFoundException` in the throws clause we have made it explicit for the client that this code can throw exception. 126 | 127 | ```java 128 | public String taskAssignedTo(String taskId) throws TaskNotFoundException { 129 | Task task = taskRepository.find(taskId); 130 | if (task != null) { 131 | return task.getAssignedTo().getUsername(); 132 | } 133 | throw new TaskNotFoundException(String.format("No task exist with id '%s'", taskId)); 134 | } 135 | ``` 136 | 137 | ## Java 8 -- Introduction of Optional data type 138 | 139 | Java 8 introduced a new data type ***java.util.Optional*** which encapsulates an empty value. It makes intent of the API clear. If a function returns a value of type Optional then it tells the clients that value might not be present. Use of `Optional` data type makes it explicit to the API client when it should expect an optional value. When you use Optional type then you as a developer makes it visible via the type system that value may not be present and client can cleanly work with it. The purpose of using `Optional` type is to help API designers design APIs that makes it visible to their clients by looking at the method signature whether they should expect optional value or not. 140 | 141 | Let's update our domain model to reflect optional values. 142 | 143 | ```java 144 | import java.util.Optional; 145 | 146 | public class Task { 147 | private final String title; 148 | private final Optional assignedTo; 149 | private final String id; 150 | 151 | public Task(String id, String title) { 152 | this.id = id; 153 | this.title = title; 154 | assignedTo = Optional.empty(); 155 | } 156 | 157 | public Task(String id, String title, User assignedTo) { 158 | this.id = id; 159 | this.title = title; 160 | this.assignedTo = Optional.ofNullable(assignedTo); 161 | } 162 | 163 | public String getId() { 164 | return id; 165 | } 166 | 167 | public String getTitle() { 168 | return title; 169 | } 170 | 171 | public Optional getAssignedTo() { 172 | return assignedTo; 173 | } 174 | } 175 | 176 | import java.util.Optional; 177 | 178 | public class User { 179 | 180 | private final String username; 181 | private final Optional address; 182 | 183 | public User(String username) { 184 | this.username = username; 185 | this.address = Optional.empty(); 186 | } 187 | 188 | public User(String username, String address) { 189 | this.username = username; 190 | this.address = Optional.ofNullable(address); 191 | } 192 | 193 | public String getUsername() { 194 | return username; 195 | } 196 | 197 | public Optional getAddress() { 198 | return address; 199 | } 200 | } 201 | ``` 202 | 203 | Use of `Optional` data type in the data model makes it explicit that `Task` refers to an ***Optional*** and ***User*** has an **Optional** username. Now whoever tries to work with `assignedTo` User would know that it might not be present and they can handle it in a declarative way. We will talk about `Optional.empty` and `Optional.of` methods in the next section. 204 | 205 | ## Working with creational methods in the java.util.Optional API 206 | 207 | In the domain model shown above, we used couple of creational methods of the Optional class but I didn't talked about them. Let's now discuss three creational methods which are part of the `Optional` API. 208 | 209 | * **Optional.empty**: This is used to create an Optional when value is not present like we did above `this.assignedTo = Optional.empty();` in the constructor. 210 | 211 | * **Optional.of(T value)**: This is used to create an Optional from a non-null value. It throws `NullPointerException` when value is null. We used it in the code shown above `this.address = Optional.of(address);`. 212 | 213 | * **Optional.ofNullable(T value)**: This static factory method works for both null and non-null values. For null values it will create an empty Optional and for non-null value it will create Optional using the value. 214 | 215 | 216 | Below is a simple example of how you can write API using Optional. 217 | 218 | ```java 219 | public class TaskRepository { 220 | 221 | private static final Map TASK_STORE = new ConcurrentHashMap<>(); 222 | 223 | public Optional find(String taskId) { 224 | return Optional.ofNullable(TASK_STORE.get(taskId)); 225 | } 226 | 227 | public void add(Task task) { 228 | TASK_STORE.put(task.getId(), task); 229 | } 230 | } 231 | ``` 232 | 233 | ## Using Optional values 234 | 235 | Optional can be thought as a Stream with one element. It has methods similar to Stream API like map, filter, flatMap that we can use to work with values contained in the `Optional`. 236 | 237 | ### Getting title for a Task 238 | 239 | To read the value of title for a Task we would write code as shown below. The `map` function was used to transform from ***Optional*** to ***Optional***. The `orElseThrow` method is used to throw a custom business exception when no Task is found. 240 | 241 | ```java 242 | public String taskTitle(String taskId) { 243 | return taskRepository. 244 | find(taskId). 245 | map(Task::getTitle). 246 | orElseThrow(() -> new TaskNotFoundException(String.format("No task exist for id '%s'",taskId))); 247 | } 248 | ``` 249 | 250 | There are three variants of `orElse*` method: 251 | 252 | 1. **orElse(T t)**: This is used to return a value when exists or returns the value passed as parameter like `Optional.ofNullable(null).orElse("NoValue")`. This will return `NoValue` as no value exist. 253 | 254 | 2. **orElseGet**: This will return the value if present otherwise invokes the `Supplier`s `get` method to produce a new value. For example, `Optional.ofNullable(null).orElseGet(() -> UUID.randomUUID().toString()` could be used to lazily produce value only when no value is present. 255 | 256 | 3. **orElseThrow**: This allow clients to throw their own custom exception when value is not present. 257 | 258 | The find method shown above returns an `Optional` that the client can use to get the value. Suppose we want to get the task's title from the Optional, we can do that by using the map function as shown below. 259 | 260 | ### Getting username of the assigned user 261 | 262 | To get the username of the user who is assigned a task we can use the `flatMap` method as shown below. 263 | 264 | ```java 265 | public String taskAssignedTo(String taskId) { 266 | return taskRepository. 267 | find(taskId). 268 | flatMap(task -> task.getAssignedTo().map(user -> user.getUsername())). 269 | orElse("NotAssigned"); 270 | } 271 | ``` 272 | 273 | ### Filtering with Optional 274 | 275 | The third Stream API like operation supported by Optional is filter, which allows you to filter an Optional based on property as shown in example below. 276 | 277 | ```java 278 | public boolean isTaskDueToday(Optional task) { 279 | return task.flatMap(Task::getDueOn).filter(d -> d.isEqual(LocalDate.now())).isPresent(); 280 | } 281 | ``` 282 | -------------------------------------------------------------------------------- /06-map.md: -------------------------------------------------------------------------------- 1 | Map Improvements 2 | --------- 3 | -------------------------------------------------------------------------------- /07-building-functional-programs.md: -------------------------------------------------------------------------------- 1 | Building Functional Programs 2 | -------- 3 | -------------------------------------------------------------------------------- /08-date-time-api.md: -------------------------------------------------------------------------------- 1 | Date Time API 2 | ------- 3 | 4 | So far in this [book](https://github.com/shekhargulati/java8-the-missing-tutorial) we have focussed on [functional](./02-lambdas.md) [aspects](03-streams.md) of Java 8 and looked at how to design better API's using [Optional](05-optionals.md) and [default and static methods in Interfaces](./01-default-static-interface-methods.md). In this chapter, we will learn about another new API that will change the way we work with dates -- Date Time API. Almost all Java developers will agree that date and time support prior to Java 8 is far from ideal and most of the time we had to use third party libraries like [Joda-Time](http://www.joda.org/joda-time/) in our applications. The new Date Time API is heavily influenced by Joda-Time API and if you have used it then you will feel home. 5 | 6 | ## What's wrong with existing Date API? 7 | 8 | Before we learn about new Date Time API let's understand why existing Date API sucks. Look at the code shown below and try to answer what it will print. 9 | 10 | ```java 11 | import java.util.Date; 12 | 13 | public class DateSucks { 14 | 15 | public static void main(String[] args) { 16 | Date date = new Date(12, 12, 12); 17 | System.out.println(date); 18 | } 19 | } 20 | ``` 21 | 22 | Can you answer what above code prints? Most Java developers will expect the program to print `0012-12-12` but the above code prints `Sun Jan 12 00:00:00 IST 1913`. My first reaction when I learnt that program prints `Sun Jan 12 00:00:00 IST 1913` was WTF??? 23 | 24 | The code shown above has following issues: 25 | 26 | 1. What each 12 means? Is it month, year, date or date, month, year or any other combination. 27 | 28 | 2. Date API month index starts at 0. So, December is actually 11. 29 | 30 | 3. Date API rolls over i.e. 12 will become January. 31 | 32 | 4. Year starts with 1900. And because month also roll over so year becomes `1900 + 12 + 1 == 1913`. Go figure!! 33 | 34 | 5. Who asked for time? I just asked for date but program prints time as well. 35 | 36 | 6. Why is there time zone? Who asked for it? The time zone is JVM's default time zone, IST, Indian Standard Time in this example. 37 | 38 | > Date API is close to 20 years old introduced with JDK 1.0. One of the original authors of Date API is none other than James Gosling himself -- Father of Java Programming Language. 39 | 40 | There are many other design issues with Date API like mutability, separate class hierarchy for SQL, etc. In JDK1.1 effort was made to provide a better API i.e. `Calendar` but it was also plagued with similar issues of mutability and index starting at 0. 41 | 42 | ## Java 8 Date Time API 43 | 44 | Java 8 Date Time API was developed as part of JSR-310 and reside insides `java.time` package. The API applies **domain-driven design** principles with domain classes like LocalDate, LocalTime applicable to solve problems related to their specific domains of date and time. This makes API intent clear and easy to understand. The other design principle applied is the **immutability**. All the core classes in the `java.time` are immutable hence avoiding thread-safety issues. 45 | 46 | ## Getting Started with Java 8 Date Time API 47 | 48 | The three classes that you will encounter most in the new API are `LocalDate`, `LocalTime`, and `LocalDateTime`. Their description is like their name suggests: 49 | 50 | * **LocalDate**: It represents a date with no time or timezone. 51 | 52 | * **LocalTime**: It represents time with no date or timezone 53 | 54 | * **LocalDateTime**: It is the combination of LocalDate and LocalTime i.e. date with time without time zone. 55 | 56 | > We will use JUnit to drive our examples. We will first write a JUnit case that will explain what we are trying to do and then we will write code to make the test pass. Examples will be based on great Indian president -- [A.P.J Abdul Kalam](https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam). 57 | 58 | ### Kalam was born on 15th October 1931 59 | 60 | ```java 61 | import org.junit.Test; 62 | import java.time.LocalDate; 63 | import static org.hamcrest.CoreMatchers.equalTo; 64 | import static org.junit.Assert.assertThat; 65 | 66 | public class DateTimeExamplesTest { 67 | private AbdulKalam kalam = new AbdulKalam(); 68 | @Test 69 | public void kalamWasBornOn15October1931() throws Exception { 70 | LocalDate dateOfBirth = kalam.dateOfBirth(); 71 | assertThat(dateOfBirth.toString(), equalTo("1931-10-15")); 72 | } 73 | } 74 | ``` 75 | 76 | `LocalDate` has a static factory method `of` that takes year, month, and date and gives you a `LocalDate`. To make this test pass, we will write `dateOfBirth` method in `AbdulKalam` class using `of` method as shown below. 77 | 78 | ```java 79 | import java.time.LocalDate; 80 | import java.time.Month; 81 | 82 | public class AbdulKalam { 83 | public LocalDate dateOfBirth() { 84 | return LocalDate.of(1931, Month.OCTOBER, 15); 85 | } 86 | } 87 | ``` 88 | 89 | There is an overloaded `of` method that takes month as integer instead of `Month` enum. I recommend using `Month` enum as it is more readable and clear. There are two other static factory methods to create `LocalDate` instances -- `ofYearDay` and `ofEpochDay`. 90 | 91 | The `ofYearDay` creates LocalDate instance from the year and day of year for example March 31st 2015 is the 90th day in 2015 so we can create LocalDate using `LocalDate.ofYearDay(2015, 90)`. 92 | 93 | ```java 94 | LocalDate january_21st = LocalDate.ofYearDay(2015, 21); 95 | System.out.println(january_21st); // 2015-01-21 96 | LocalDate march_31st = LocalDate.ofYearDay(2015, 90); 97 | System.out.println(march_31st); // 2015-03-31 98 | ``` 99 | 100 | The `ofEpochDay` creates LocalDate instance using the epoch day count. The starting value of epoch is `1970-01-01`. So, `LocalDate.ofEpochDay(1)` will give `1970-01-02`. 101 | 102 | LocalDate instance provide many accessor methods to access different fields like year, month, dayOfWeek, etc. 103 | 104 | ```java 105 | @Test 106 | public void kalamWasBornOn15October1931() throws Exception { 107 | LocalDate dateOfBirth = kalam.dateOfBirth(); 108 | assertThat(dateOfBirth.getMonth(), is(equalTo(Month.OCTOBER))); 109 | assertThat(dateOfBirth.getYear(), is(equalTo(1931))); 110 | assertThat(dateOfBirth.getDayOfMonth(), is(equalTo(15))); 111 | assertThat(dateOfBirth.getDayOfYear(), is(equalTo(288))); 112 | } 113 | ``` 114 | 115 | You can create current date from the system clock using `now` static factory method. 116 | 117 | ```java 118 | LocalDate.now() 119 | ``` 120 | 121 | ### Kalam was born at 01:15am 122 | 123 | ```java 124 | @Test 125 | public void kalamWasBornAt0115() throws Exception { 126 | LocalTime timeOfBirth = kalam.timeOfBirth(); 127 | assertThat(timeOfBirth.toString(), is(equalTo("01:15"))); 128 | } 129 | ``` 130 | 131 | `LocalTime` class is used to work with time. Just like `LocalDate`, it also provides static factory methods for creating its instances. We will use the `of` static factory method giving it hour and minute and it will return LocalTime as shown below. 132 | 133 | ```java 134 | public LocalTime timeOfBirth() { 135 | return LocalTime.of(1, 15); 136 | } 137 | ``` 138 | 139 | There are other overloaded variants of `of` method that can take second and nanosecond. 140 | 141 | > LocalTime is represented to nanosecond precision. 142 | 143 | You can print the current time of the system clock using `now` method as shown below. 144 | 145 | ```java 146 | LocalTime.now() 147 | ``` 148 | 149 | You can also create instances of `LocalTime` from seconds of day or nanosecond of day using `ofSecondOfDay` and `ofNanoOfDay` static factory methods. 150 | 151 | Similar to `LocalDate` `LocalTime` also provide accessor for its field as shown below. 152 | 153 | ```java 154 | @Test 155 | public void kalamWasBornAt0115() throws Exception { 156 | LocalTime timeOfBirth = kalam.timeOfBirth(); 157 | assertThat(timeOfBirth.getHour(), is(equalTo(1))); 158 | assertThat(timeOfBirth.getMinute(), is(equalTo(15))); 159 | assertThat(timeOfBirth.getSecond(), is(equalTo(0))); 160 | } 161 | ``` 162 | 163 | ### Kalam was born on 15 October at 01:15 am 164 | 165 | When you want to represent both date and time together then you can use `LocalDateTime`. LocalDateTime also provides many static factory methods to create its instances. We can use `of` factory method that takes a `LocalDate` and `LocalTime` and gives `LocalDateTime` instance as shown below. 166 | 167 | ```java 168 | public LocalDateTime dateOfBirthAndTime() { 169 | return LocalDateTime.of(dateOfBirth(), timeOfBirth()); 170 | } 171 | ``` 172 | 173 | There are many overloaded variants of `of` method which as arguments take year, month, day, hour, min, secondOfDay, nanosecondOfDay. 174 | 175 | ![LocalDateTime Of Methods](https://whyjava.files.wordpress.com/2015/10/localdatetime_of.png) 176 | 177 | To create current date and time using system clock you can use `now` factory method. 178 | 179 | ```java 180 | LocalDateTime.now() 181 | ``` 182 | 183 | ## Manipulating dates 184 | 185 | Now that we know how to create instances of `LocalDate`, `LocalTime`, and `LocalDateTime` let's learn how we can manipulate them. 186 | 187 | > LocalDate, LocalTime, and LocalDateTime are immutable so each time you perform a manipulation operation you get a new instance. 188 | 189 | ### Kalam 50th birthday was on Thursday 190 | 191 | ```java 192 | @Test 193 | public void kalam50thBirthDayWasOnThursday() throws Exception { 194 | DayOfWeek dayOfWeek = kalam.dayOfBirthAtAge(50); 195 | assertThat(dayOfWeek, is(equalTo(DayOfWeek.THURSDAY))); 196 | } 197 | ``` 198 | 199 | We can use `dateOfBirth` method that we wrote earlier with `plusYears` on `LocalDate` instance to achieve this as shown below. 200 | 201 | ```java 202 | public DayOfWeek dayOfBirthAtAge(final int age) { 203 | return dateOfBirth().plusYears(age).getDayOfWeek(); 204 | } 205 | ``` 206 | 207 | There are similar `plus*` variants for adding days, months, weeks to the value. 208 | 209 | Similar to `plus` methods there are `minus` methods that allow you minus year, days, months from a `LocalDate` instance. 210 | 211 | ```java 212 | LocalDate today = LocalDate.now(); 213 | LocalDate yesterday = today.minusDays(1); 214 | ``` 215 | 216 | > Just like LocalDate LocalTime and LocalDateTime also provide similar `plus*` and `minus*` methods. 217 | 218 | ### List all Kalam's birthdate DayOfWeek 219 | 220 | For this use-case, we will create an infinite stream of `LocalDate` starting from the Kalam's date of birth using the `Stream.iterate` method. `Stream.iterate` method takes a starting value and a function that allows you to work on the initial seed value and return another value. We just incremented the year by 1 and return next year birthdate. Then we transformed `LocalDate` to `DayOfWeek` to get the desired output value. Finally, we limited our result set to the provided limit and collected Stream result into a List. 221 | 222 | ```java 223 | public List allBirthDateDayOfWeeks(int limit) { 224 | return Stream.iterate(dateOfBirth(), db -> db.plusYears(1)) 225 | .map(LocalDate::getDayOfWeek) 226 | .limit(limit) 227 | .collect(toList()); 228 | } 229 | ``` 230 | 231 | ## Duration and Period 232 | 233 | `Duration` and `Period` classes represents quantity or amount of time. 234 | 235 | **Duration** represents quantity or amount of time in seconds, nano-seconds, or days like 10 seconds. 236 | 237 | **Period** represents amount or quantity of time in years, months, and days. 238 | 239 | ### Calculate number of days kalam lived 240 | 241 | ```java 242 | @Test 243 | public void kalamLived30601Days() throws Exception { 244 | long daysLived = kalam.numberOfDaysLived(); 245 | assertThat(daysLived, is(equalTo(30601L))); 246 | } 247 | ``` 248 | 249 | To calculate number of days kalam lived we can use `Duration` class. `Duration` has a factory method that takes two `LocalTime`, or `LocalDateTime` or `Instant` and gives a duration. The duration can then be converted to days, hours, seconds, etc. 250 | 251 | ```java 252 | public Duration kalamLifeDuration() { 253 | LocalDateTime deathDateAndTime = LocalDateTime.of(LocalDate.of(2015, Month.JULY, 27), LocalTime.of(19, 0)); 254 | return Duration.between(dateOfBirthAndTime(), deathDateAndTime); 255 | } 256 | 257 | public long numberOfDaysLived() { 258 | return kalamLifeDuration().toDays(); 259 | } 260 | ``` 261 | 262 | ### Kalam lived 83 years 9 months and 12 days 263 | 264 | ```java 265 | @Test 266 | public void kalamLifePeriod() throws Exception { 267 | Period kalamLifePeriod = kalam.kalamLifePeriod(); 268 | assertThat(kalamLifePeriod.getYears(), is(equalTo(83))); 269 | assertThat(kalamLifePeriod.getMonths(), is(equalTo(9))); 270 | assertThat(kalamLifePeriod.getDays(), is(equalTo(12))); 271 | } 272 | ``` 273 | 274 | We can use `Period` class to calculate number of years, months, and days kalam lived as shown below. Period's `between` method works with `LocalDate` only. 275 | 276 | ```java 277 | public Period kalamLifePeriod() { 278 | LocalDate deathDate = LocalDate.of(2015, Month.JULY, 27); 279 | return Period.between(dateOfBirth(), deathDate); 280 | } 281 | ``` 282 | 283 | ## Printing and Parsing dates 284 | 285 | In our day-to-day applications a lot of times we have to parse a text format to a date or time or we have to print a date or time in a specific format. Printing and parsing are very common use cases when working with date or time. Java 8 provides a class `DateTimeFormatter` which is the main class for formatting and printing. All the classes and interfaces relevant to them resides inside the `java.time.format` package. 286 | 287 | ### Print Kalam birthdate in Indian date format 288 | 289 | In India, `dd-MM-YYYY` is the predominant date format that is used in all the government documents like passport application form. You can read more about Date and time notation in India on the [wikipedia](https://en.wikipedia.org/wiki/Date_and_time_notation_in_India). 290 | 291 | ```java 292 | @Test 293 | public void kalamDateOfBirthFormattedInIndianDateFormat() throws Exception { 294 | final String indianDateFormat = "dd-MM-YYYY"; 295 | String dateOfBirth = kalam.formatDateOfBirth(indianDateFormat); 296 | assertThat(dateOfBirth, is(equalTo("15-10-1931"))); 297 | } 298 | ``` 299 | 300 | The `formatDateofBirth` method uses `DateTimeFormatter` `ofPattern` method to create a new formatter using the specified pattern. All the main main date-time classes provide two methods - one for formatting, `format(DateTimeFormatter formatter)`, and one for parsing, `parse(CharSequence text, DateTimeFormatter formatter)`. 301 | 302 | ```java 303 | public String formatDateOfBirth(final String pattern) { 304 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); 305 | return dateOfBirth().format(formatter); 306 | } 307 | ``` 308 | 309 | For the common use cases, `DateTimeFormatter` class provides formatters as static constants. There are predefined constants for `BASIC_ISO_DATE` i.e **20111203** or `ISO_DATE` i.e. **2011-12-03**, etc that developers can easily use in their code. In the code shown below, you can see how to use these predefined formats. 310 | 311 | ```java 312 | @Test 313 | public void kalamDateOfBirthInDifferentDateFormats() throws Exception { 314 | LocalDate kalamDateOfBirth = LocalDate.of(1931, Month.OCTOBER, 15); 315 | assertThat(kalamDateOfBirth.format(DateTimeFormatter.BASIC_ISO_DATE), is(equalTo("19311015"))); 316 | assertThat(kalamDateOfBirth.format(DateTimeFormatter.ISO_LOCAL_DATE), is(equalTo("1931-10-15"))); 317 | assertThat(kalamDateOfBirth.format(DateTimeFormatter.ISO_ORDINAL_DATE), is(equalTo("1931-288"))); 318 | } 319 | ``` 320 | 321 | ### Parsing text to LocalDateTime 322 | 323 | Let's suppose we have to parse `15 Oct 1931 01:15 AM` to a LocalDateTime instance as shown in code below. 324 | 325 | ```java 326 | @Test 327 | public void shouldParseKalamDateOfBirthAndTimeToLocalDateTime() throws Exception { 328 | final String input = "15 Oct 1931 01:15 AM"; 329 | LocalDateTime dateOfBirthAndTime = kalam.parseDateOfBirthAndTime(input); 330 | assertThat(dateOfBirthAndTime.toString(), is(equalTo("1931-10-15T01:15"))); 331 | } 332 | ``` 333 | 334 | We will again use `DateTimeFormatter` `ofPattern` method to create a new `DateTimeFormatter` and then use the `parse` method of `LocalDateTime` to create a new instance of `LocalDateTime` as shown below. 335 | 336 | ```java 337 | public LocalDateTime parseDateOfBirthAndTime(String input) { 338 | return LocalDateTime.parse(input, DateTimeFormatter.ofPattern("dd MMM yyyy hh:mm a")); 339 | } 340 | ``` 341 | 342 | ## Advance date time manipulation with TemporalAdjusters 343 | 344 | In `Manipulating dates` section, we learnt how we can use `plus*` and `minus*` methods to manipulate dates. Those methods are suitable for simple manipulation operations like adding or subtracting days, months, or years. Sometimes, we need to perform advance date time manipulation such as adjusting date to first day of next month or adjusting date to next working day or adjusting date to next public holiday then we can use `TemporalAdjusters` to meet our needs. Java 8 comes bundled with many predefined temporal adjusters for common scenarios. These temporal adjusters are available as static factory methods inside the `TemporalAdjusters` class. 345 | 346 | ```java 347 | LocalDate date = LocalDate.of(2015, Month.OCTOBER, 25); 348 | System.out.println(date);// This will print 2015-10-25 349 | 350 | LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth()); 351 | System.out.println(firstDayOfMonth); // This will print 2015-10-01 352 | 353 | LocalDate firstDayOfNextMonth = date.with(TemporalAdjusters.firstDayOfNextMonth()); 354 | System.out.println(firstDayOfNextMonth);// This will print 2015-11-01 355 | 356 | LocalDate lastFridayOfMonth = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); 357 | System.out.println(lastFridayOfMonth); // This will print 2015-10-30 358 | ``` 359 | * **firstDayOfMonth** creates a new date set to first day of the current month. 360 | * **firstDayOfNextMonth** creates a new date set to first day of next month. 361 | * **lastInMonth** creates a new date in the same month with the last matching day-of-week. For example, last Friday in October. 362 | 363 | I have not covered all the temporal-adjusters please refer to the documentation for the same. 364 | ![TemporalAdjusters](https://whyjava.files.wordpress.com/2015/10/temporal-adjusters.png) 365 | 366 | ### Writing custom TemporalAdjuster 367 | 368 | You can write your own adjuster by implementing `TemporalAdjuster` functional interface. Let's suppose we have to write a TemporalAdjuster that adjusts today's date to next working date then we can use the `TemporalAdjusters` `ofDateAdjuster` method to adjust the current date to next working date as show below. 369 | 370 | ```java 371 | LocalDate today = LocalDate.now(); 372 | TemporalAdjuster nextWorkingDayAdjuster = TemporalAdjusters.ofDateAdjuster(localDate -> { 373 | DayOfWeek dayOfWeek = localDate.getDayOfWeek(); 374 | if (dayOfWeek == DayOfWeek.FRIDAY) { 375 | return localDate.plusDays(3); 376 | } else if (dayOfWeek == DayOfWeek.SATURDAY) { 377 | return localDate.plusDays(2); 378 | } 379 | return localDate.plusDays(1); 380 | }); 381 | System.out.println(today.with(nextWorkingDayAdjuster)); 382 | ``` 383 | -------------------------------------------------------------------------------- /09-completable-futures.md: -------------------------------------------------------------------------------- 1 | Completable Futures 2 | ------ 3 | -------------------------------------------------------------------------------- /10-nashorn.md: -------------------------------------------------------------------------------- 1 | Nashorn: Run JavaScript on the JVM 2 | ----- 3 | 4 | ![Nashorn](https://upload.wikimedia.org/wikipedia/commons/7/7a/Dortmunder_Nashorn_-_Hell_wieherte_der_Hippogryph.jpg) 5 | 6 | Nashorn is a high-performance JavaScript runtime written in Java for the JVM. It allows developers to embed JavaScript code inside their Java applications and even use Java classes and methods from their JavaScript code. You can think it as an alternative to Google's V8 JavaScript engine. It is a successor to Rhino JavaScript runtime which came bundled with earlier JDK versions. Nashorn is written from scratch using new language features like JSR 292(Supporting Dynamically Typed Languages) and `invokedynamic`. 7 | 8 | From the Nashorn documentation: 9 | > Nashorn uses invokedynamic to implement all of its invocations. If an invocation has a Java object receiver, Nashorn attempts to bind the call to an appropriate Java method instead of a JavaScript function. Nashorn has full discretion about how it resolves methods. As an example, if it can't find a field in the receiver, it looks for an equivalent Java Bean method. The result is completely transparent for calls from JavaScript to Java. 10 | 11 | Currently, Nashorn supports [ECMAScript 5.1 specification](http://www.ecma-international.org/ecma-262/5.1/) and work is in progress to support [ECMAScript 6](http://www.ecma-international.org/ecma-262/6.0/) as well. Few ECMAScript 6 features like `let` and `const` are available in latest JDK 8 updates(40 and above) and we will cover them later in this chapter. 12 | 13 | In this chapter, we will cover the following: 14 | 15 | * Working with Nashorn command-line 16 | * Accessing Java classes and methods 17 | * Using external JavaScript libraries 18 | * Writing scripts 19 | * Using Nashorn from Java code 20 | * Using Java 8 features like Streams and Lambdas inside JavaScript code 21 | * Turning off Java language access 22 | 23 | 24 | ## Working with Nashorn command-line 25 | 26 | JDK 8 comes bundled with two command-line tools that can be used to work with Nashorn engine. These two command-line tools are `jrunscript` and `jjs`. `jjs` is recommended to be used when working with Nashorn so we will only discuss it. To use `jjs`, you have to add `jjs` to the path. On *nix machines, you can do that adding a symbolic link as shown below. 27 | 28 | ```bash 29 | $ cd /usr/bin 30 | $ ln -s $JAVA_HOME/bin/jjs jjs 31 | ``` 32 | Windows users can add `$JAVA_HOME/bin` to the path for easy access. 33 | 34 | Once you have set the symbolic link you can access `jjs` from your terminal. To check version of `jjs`, run the following command. 35 | 36 | ```bash 37 | $ jjs -v 38 | nashorn 1.8.0_60 39 | jjs> 40 | ``` 41 | 42 | It will render the version and then show `jjs>` prompt. You can view the full version of `jjs` by using `jjs -fv` command. 43 | 44 | To quit the `jjs` shell, you can use `Ctrl-C`. 45 | 46 | Once you are inside `jjs`, you can execute any JavaScript code as shown below. 47 | 48 | ```bash 49 | jjs> print("learning about Nashorn") 50 | learning about Nashorn 51 | ``` 52 | 53 | You can define functions as shown below. 54 | 55 | ``` 56 | jjs> function add(a,b) {return a + b;} 57 | ``` 58 | 59 | You can call the add function as shown below. 60 | 61 | ``` 62 | jjs> add(5,10) 63 | 15 64 | ``` 65 | 66 | ## Accessing Java classes and methods 67 | 68 | It is very easy to access Java classes from within Nashorn. Assuming you are inside the `jjs` shell, you can create an instance of HashMap as shown below. 69 | 70 | ```bash 71 | jjs> var HashMap = Java.type("java.util.HashMap") 72 | jjs> var userAndAge = new HashMap() 73 | jjs> userAndAge.put("shekhar",32) 74 | null 75 | jjs> userAndAge.put("rahul",33) 76 | null 77 | jjs> userAndAge.get("shekhar") 78 | 32 79 | ``` 80 | 81 | In the code shown above we have used `Java` global object to create HashMap object. `Java` global object has `type` method that takes a string with the fully qualified Java class name, and returns the corresponding `JavaClass` function object. 82 | 83 | ```bash 84 | jjs> HashMap 85 | [JavaClass java.util.HashMap] 86 | ``` 87 | 88 | The `var userAndAge = new HashMap()` is used to instantiate `java.util.HashMap` class using the `new` keyword. 89 | 90 | You can access values by either calling the `get` method or using the `[]` notation as shown below. 91 | 92 | ```bash 93 | jjs> userAndAge["shekhar"] 94 | 32 95 | ``` 96 | 97 | Similarly, you can work with other Java collections. To use an `ArrayList` you will write code as shown below. 98 | 99 | ```bash 100 | jjs> var List = Java.type("java.util.ArrayList") 101 | jjs> var names = new List() 102 | jjs> names.add("shekhar") 103 | true 104 | jjs> names.add("rahul") 105 | true 106 | jjs> names.add("sameer") 107 | true 108 | jjs> names.get(0) 109 | shekhar 110 | jjs> names[1] 111 | rahul 112 | ``` 113 | 114 | ### Accessing static methods 115 | 116 | To access static methods you have to first get the Java type using `Java.type` method and then calling method on `JavaClass` function object. 117 | 118 | ```bash 119 | jjs> var UUID = Java.type("java.util.UUID") 120 | jjs> 121 | jjs> UUID.randomUUID().toString() 122 | e4242b89-0e94-458e-b501-2fc4344d5498 123 | ``` 124 | 125 | You can sort list using `Collections.sort` method as shown below. 126 | 127 | ```bash 128 | jjs> var Collections = Java.type("java.util.Collections") 129 | jjs> 130 | jjs> Collections.sort(names) 131 | jjs> names 132 | [rahul, sameer, shekhar] 133 | jjs> 134 | ``` 135 | 136 | ## Using external JavaScript libraries 137 | 138 | Let's suppose we want to use an external JavaScript library in our JavaScript code. Nashorn comes up with a built-in function -- `load` that loads and evaluates a script from a path, URL, or script object. To use `lodash` library we can write code as shown below. 139 | 140 | ``` 141 | jjs> load("https://raw.github.com/lodash/lodash/3.10.1/lodash.js") 142 | 143 | jjs> _.map([1, 2, 3], function(n) { return n * 3; }); 144 | 3,6,9 145 | ``` 146 | 147 | ## Writing scripts 148 | 149 | You can use Nashorn extensions that enable users to write scripts that can use Unix shell scripting features. To enable shell scripting features, you have to start `jjs` with `-scripting` option as shown below. 150 | 151 | ```bash 152 | jjs -scripting 153 | jjs> 154 | ``` 155 | 156 | Now you have access to Nashorn shell scripting global objects. 157 | 158 | **$ARG:** This global object can be used to access the arguments passed to the script 159 | ``` 160 | $ jjs -scripting -- hello hey 161 | jjs> 162 | jjs> $ARG 163 | hello,hey 164 | ``` 165 | 166 | **$ENV:** A map containing all the current environment variables 167 | 168 | ```bash 169 | jjs> $ENV["HOME"] 170 | /Users/shekhargulati 171 | 172 | jjs> $ENV.JAVA_HOME 173 | /Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home 174 | ``` 175 | 176 | **$EXEC:** launches processes to run commands 177 | 178 | ```bash 179 | jjs> $EXEC("pwd") 180 | /Users/shekhargulati/java8-the-missing-tutorial 181 | ``` 182 | 183 | ### Writing executable scripts 184 | 185 | You can use shebang(#!) at the beginning of the script to make a script file run as shell executable. Let's write a simple script that reads content of a file. We will use Java's `Files` and `Paths` API. 186 | 187 | ```javascript 188 | #!/usr/bin/jjs 189 | 190 | var Paths = Java.type("java.nio.file.Paths"); 191 | var Files = Java.type("java.nio.file.Files"); 192 | 193 | Files.lines(Paths.get($ARG[0])).forEach(function(line){print(line);}) 194 | ``` 195 | 196 | We will invoke it as 197 | 198 | ```bash 199 | $ jjs ch10/lines.js -- README.md 200 | ``` 201 | 202 | ## Using Nashorn from Java code 203 | 204 | To use Nashorn from inside Java code, you have to create an instance of ScriptEngine from `ScriptEngineManager` as shown below. Once you have `ScriptEngine` you can evaluate expressions. 205 | 206 | ```java 207 | import javax.script.ScriptEngine; 208 | import javax.script.ScriptEngineManager; 209 | import javax.script.ScriptException; 210 | 211 | public class NashornExample1 { 212 | 213 | public static void main(String[] args) throws ScriptException { 214 | ScriptEngineManager manager = new ScriptEngineManager(); 215 | ScriptEngine nashorn = manager.getEngineByName("nashorn"); 216 | Integer eval = (Integer) nashorn.eval("10 + 20"); 217 | System.out.println(eval); 218 | } 219 | } 220 | ``` 221 | 222 | 223 | Using bindings 224 | 225 | ```java 226 | import javax.script.*; 227 | import java.util.AbstractMap.SimpleEntry; 228 | import java.util.stream.Stream; 229 | 230 | import static java.util.stream.Collectors.toMap; 231 | 232 | public class NashornExample2 { 233 | 234 | public static void main(String[] args) throws ScriptException { 235 | ScriptEngineManager manager = new ScriptEngineManager(); 236 | ScriptEngine nashorn = manager.getEngineByName("nashorn"); 237 | 238 | Bindings bindings = new SimpleBindings(Stream.of( 239 | new SimpleEntry<>("a", 10), 240 | new SimpleEntry<>("b", 20)) 241 | .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue))); 242 | Double eval = (Double) nashorn.eval("a + b", bindings); 243 | System.out.println(eval); 244 | } 245 | } 246 | ``` 247 | 248 | ## Using Java 8 features like Streams and Lambdas inside JavaScript code 249 | 250 | Java 8 supports lambdas and many API in JDK make use of them. Every collection in Java has `forEach` method that accepts a consumer. Consumer is an interface with one method. In Java, you can write following: 251 | 252 | ```java 253 | Arrays.asList("shekhar","rahul","sameer").forEach(name -> System.out.println(name)); 254 | 255 | // shekhar 256 | // rahul 257 | // sameer 258 | ``` 259 | 260 | In Nashorn, you can use them same API but you will pass JavaScript function instead as shown below. 261 | 262 | ```javascript 263 | jjs> var Arrays = Java.type("java.util.Arrays") 264 | jjs> Arrays.asList("shekhar","rahul","sameer") 265 | [shekhar, rahul, sameer] 266 | jjs> var names = Arrays.asList("shekhar","rahul","sameer") 267 | jjs> names.forEach(function(name){print(name);}) 268 | shekhar 269 | rahul 270 | sameer 271 | ``` 272 | 273 | You can also use Stream API with Nashorn as shown below. 274 | 275 | ``` 276 | jjs> names 277 | .stream().filter(function(name){return name.startsWith("s");}) 278 | .forEach(function(name){print(name);}) 279 | 280 | shekhar 281 | sameer 282 | ``` 283 | 284 | ## Turning off Java language access 285 | 286 | In case you need to disallow Java usage, you can very easily turn off by passing `--no-java` option to `jjs` as shown below. 287 | 288 | ``` 289 | → jjs --no-java 290 | jjs> 291 | jjs> var HashMap = Java.type("java.util.HashMap") 292 | :1 TypeError: null has no such function "type" 293 | ``` 294 | 295 | Now when you will try to use `java.util.HashMap` you will get `TypeError`. 296 | 297 | You can do the same with Java code as well. 298 | 299 | ```java 300 | 301 | import jdk.nashorn.api.scripting.ClassFilter; 302 | import jdk.nashorn.api.scripting.NashornScriptEngineFactory; 303 | 304 | import javax.script.ScriptEngine; 305 | import javax.script.ScriptException; 306 | 307 | public class NashornExample3 { 308 | 309 | public static void main(String[] args) throws ScriptException { 310 | NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); 311 | ScriptEngine nashorn = factory.getScriptEngine(new NoJavaFilter()); 312 | Integer eval = (Integer) nashorn.eval("var HashMap = Java.type('java.util.HashMap')"); 313 | System.out.println(eval); 314 | } 315 | 316 | private static class NoJavaFilter implements ClassFilter{ 317 | 318 | @Override 319 | public boolean exposeToScripts(String s) { 320 | return false; 321 | } 322 | } 323 | } 324 | ``` 325 | 326 | You will get following exception when you run this program. 327 | 328 | ``` 329 | Caused by: java.lang.ClassNotFoundException: java.util.HashMap 330 | at jdk.nashorn.internal.runtime.Context.findClass(Context.java:1032) 331 | at jdk.nashorn.internal.objects.NativeJava.simpleType(NativeJava.java:493) 332 | at jdk.nashorn.internal.objects.NativeJava.type(NativeJava.java:322) 333 | at jdk.nashorn.internal.objects.NativeJava.type(NativeJava.java:314) 334 | at jdk.nashorn.internal.objects.NativeJava.type(NativeJava.java:310) 335 | at jdk.nashorn.internal.scripts.Script$\^eval\_.:program(:1) 336 | at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:640) 337 | at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:228) 338 | at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393) 339 | ``` 340 | -------------------------------------------------------------------------------- /11-tools.md: -------------------------------------------------------------------------------- 1 | Tools 2 | ----- 3 | -------------------------------------------------------------------------------- /12-annotations.md: -------------------------------------------------------------------------------- 1 | Annotation Improvements 2 | ------- 3 | 4 | There are couple of improvements made in the annotation mechanism in Java 8. These are: 5 | 6 | 1. Repeatable annotations 7 | 2. Type annotations 8 | 9 | > Code for this section is inside [ch12 package](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch12). 10 | 11 | ## Repeatable annotations 12 | 13 | Before Java 8, it was not possible to use same annotation twice at the same location i.e. you can't use annotation `@Foo` twice on a method. If you have used JPA then you would have use an annotation called `@JoinColumns` that allows wraps multiple `@JoinColumn` annotation as shown below. 14 | 15 | ```java 16 | @ManyToOne 17 | @JoinColumns({ 18 | @JoinColumn(name="ADDR_ID", referencedColumnName="ID"), 19 | @JoinColumn(name="ADDR_ZIP", referencedColumnName="ZIP") 20 | }) 21 | public Address getAddress() { return address; } 22 | ``` 23 | 24 | In Java 8, you can write the same as shown below because with Java 8 you can repeat an annotation multiple time at the same location. 25 | 26 | ```java 27 | @ManyToOne 28 | @JoinColumn(name="ADDR_ID", referencedColumnName="ID"), 29 | @JoinColumn(name="ADDR_ZIP", referencedColumnName="ZIP") 30 | public Address getAddress() { return address; } 31 | ``` 32 | 33 | 34 | ***With Java 8 you can use same annotation type multiple times possibly with different arguments at any location(class, method, field declaration) in your Java code.*** 35 | 36 | ### Writing your own repeatable Annotations 37 | 38 | To write your own repeatable annotation you have to do the following: 39 | 40 | **Step 1:** Create an annotation with `@Repeatable` annotation as shown below. `@Repeatable` annotation requires you to specify a mandatory value for the container type that will contain the annotation. We will create container annotation in step 2. 41 | 42 | ```java 43 | import java.lang.annotation.*; 44 | 45 | @Retention(RetentionPolicy.RUNTIME) 46 | @Target(ElementType.METHOD) 47 | @Repeatable(CreateVms.class) 48 | public @interface CreateVm { 49 | String name(); 50 | } 51 | ``` 52 | 53 | **Step 2:** Create a container annotation that holds the annotation. 54 | 55 | ```java 56 | @Retention(RetentionPolicy.RUNTIME) 57 | @Target(ElementType.METHOD) 58 | @interface CreateVms { 59 | CreateVm[] value(); 60 | } 61 | ``` 62 | 63 | Now you can use the annotation multiple times on any method as shown below. 64 | 65 | ```java 66 | @CreateVm(name = "vm1") 67 | @CreateVm(name = "vm2") 68 | public void manage() { 69 | System.out.println("Managing ...."); 70 | } 71 | ``` 72 | 73 | If you have to find all the repeatable annotations on a method then you can use `getAnnotationsByType` method that is now available on `java.lang.Class` and `java.lang.reflect.Method`. To print all the vm names, you can write code as shown below. 74 | 75 | ```java 76 | CreateVm[] createVms = VmManager.class.getMethod("manage").getAnnotationsByType(CreateVm.class); 77 | Stream.of(createVms).map(CreateVm::name).forEach(System.out::println); 78 | ``` 79 | 80 | ## Type annotations 81 | 82 | You can now apply annotations at two more target locations -- TYPE_PARAMETER and TYPE_USE. 83 | 84 | ```java 85 | @Retention(RetentionPolicy.RUNTIME) 86 | @Target(value = {ElementType.TYPE_PARAMETER}) 87 | public @interface MyAnnotation { 88 | } 89 | ``` 90 | 91 | You can use it like 92 | 93 | ```java 94 | class MyAnnotationUsage<@MyAnnotation T> { 95 | } 96 | ``` 97 | 98 | You can also use annotations at type usage as shown below. 99 | 100 | ```java 101 | @Retention(RetentionPolicy.RUNTIME) 102 | @Target(value = {ElementType.TYPE_USE}) 103 | public @interface MyAnnotation { 104 | } 105 | ``` 106 | 107 | Then you can use them as shown below. 108 | 109 | ```java 110 | public static void doSth() { 111 | List<@MyAnnotation String> names = Arrays.asList("shekhar"); 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Shekhar Gulati 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 那些年我们错过的Java8 2 | -------------- 3 | 4 | > - 原文链接: [Java 8: The Missing Tutorial](https://github.com/shekhargulati/java8-the-missing-tutorial) 5 | - 原文作者: [shekhargulati](https://github.com/shekhargulati) 6 | - 译者: leege100 7 | - 状态: 完成 8 | 9 | Java8已经不再是一个新的话题,市面上已经有很多基于它的图书出版。但是,我还是发现很多java开发者没有意识到它的强大,我写这系列文章的目的就是为大家介绍java8中最重要的特性,这些特性能够对开发者的日常编程有帮助。这些文章是基于我的[7 days with Java 8](http://shekhargulati.com/7-days-with-java-8/)博客系列。 10 | 11 | ## Table of Contents 12 | 13 | * [接口的默认和静态方法](./01-default-static-interface-methods.md) 14 | * [Lambda表达式](./02-lambdas.md) 15 | * [Streams](./03-streams.md) 16 | * [Collectors](./04-collectors.md) 17 | * [Optionals](./05-optionals.md) 18 | * [Map improvements](./06-map.md): Not written yet 19 | * [Building functional programs](./07-building-functional-programs.md): Not written yet 20 | * [Date Time API](./08-date-time-api.md) 21 | * [Completable Futures](./09-completable-futures.md): Not written yet 22 | * [Nashorn](./10-nashorn.md) 23 | * [Tools](./11-tools.md): Not written yet 24 | * [Annotation improvements](./12-annotations.md) 25 | 26 | ----------- 27 | 关注我的twitter[https://twitter.com/shekhargulati](https://twitter.com/shekhargulati)或者发邮件至。当然,也欢迎阅读我的系列博客[http://shekhargulati.com/](http://shekhargulati.com/)。 28 | -------------------------------------------------------------------------------- /ch10/lines.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/jjs 2 | 3 | var Paths = Java.type("java.nio.file.Paths"); 4 | var Files = Java.type("java.nio.file.Files"); 5 | 6 | Files.lines(Paths.get($ARG[0])).forEach(function(line){print(line);}) 7 | -------------------------------------------------------------------------------- /code/.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 3 | 4 | *.iml 5 | 6 | ## Directory-based project format: 7 | .idea/ 8 | # if you remove the above rule, at least ignore the following: 9 | 10 | # User-specific stuff: 11 | # .idea/workspace.xml 12 | # .idea/tasks.xml 13 | # .idea/dictionaries 14 | 15 | # Sensitive or high-churn files: 16 | # .idea/dataSources.ids 17 | # .idea/dataSources.xml 18 | # .idea/sqlDataSources.xml 19 | # .idea/dynamic.xml 20 | # .idea/uiDesigner.xml 21 | 22 | # Gradle: 23 | # .idea/gradle.xml 24 | # .idea/libraries 25 | 26 | # Mongo Explorer plugin: 27 | # .idea/mongoSettings.xml 28 | 29 | ## File-based project format: 30 | *.ipr 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | 49 | # Created by .ignore support plugin (hsz.mobi) 50 | ### Java template 51 | *.class 52 | 53 | # Mobile Tools for Java (J2ME) 54 | .mtj.tmp/ 55 | 56 | # Package Files # 57 | *.jar 58 | *.war 59 | *.ear 60 | 61 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 62 | hs_err_pid* 63 | ### Gradle template 64 | .gradle 65 | build/ 66 | 67 | # Ignore Gradle GUI config 68 | gradle-app.setting 69 | 70 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 71 | !gradle-wrapper.jar 72 | 73 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | Java 8: The Missing Tutorial Supporting Code 2 | ----- 3 | 4 | This gradle Java 8 project houses example code that is used in the tutorial. Please refer to Table of Content to go to their respective packages inside the Java project. 5 | 6 | ## Table of Contents 7 | 8 | * [Default and Static Methods for Interfaces](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch01) 9 | -------------------------------------------------------------------------------- /code/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.shekhargulati' 2 | version '1.0-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testCompile "org.hamcrest:hamcrest-all:1.3" 14 | testCompile group: 'junit', name: 'junit', version: '4.12' 15 | } 16 | -------------------------------------------------------------------------------- /code/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leege100/java8-the-missing-tutorial-zh/d185e1ac7a624cffb851616bce9db427ee76ba6b/code/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /code/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Nov 22 18:13:21 IST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-bin.zip 7 | -------------------------------------------------------------------------------- /code/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /code/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /code/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'java8-the-missing-tutorial' 2 | 3 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch01/App1.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | public class App1 implements A { 4 | @Override 5 | public void doSth() { 6 | System.out.println("inside App1"); 7 | } 8 | 9 | public static void main(String[] args) { 10 | new App1().doSth(); 11 | } 12 | } 13 | 14 | interface A { 15 | default void doSth() { 16 | System.out.println("inside A"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch01/App2.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | public class App2 implements B, D { 4 | public static void main(String[] args) { 5 | new App2().doSth(); 6 | } 7 | } 8 | 9 | interface B { 10 | default void doSth() { 11 | System.out.println("inside B"); 12 | } 13 | } 14 | 15 | interface D extends B { 16 | default void doSth() { 17 | System.out.println("inside D"); 18 | } 19 | } 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch01/App3.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | public class App3 implements E, F { 4 | @Override 5 | public void doSth() { 6 | F.super.doSth(); 7 | } 8 | 9 | public static void main(String[] args) { 10 | new App3().doSth(); 11 | } 12 | } 13 | 14 | interface E { 15 | default void doSth() { 16 | System.out.println("inside E"); 17 | } 18 | } 19 | 20 | interface F { 21 | default void doSth() { 22 | System.out.println("inside F"); 23 | } 24 | } -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch01/BasicCalculator.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | 4 | class BasicCalculator implements Calculator { 5 | 6 | @Override 7 | public int add(int first, int second) { 8 | return first + second; 9 | } 10 | 11 | @Override 12 | public int subtract(int first, int second) { 13 | return first - second; 14 | } 15 | 16 | @Override 17 | public int divide(int number, int divisor) { 18 | if (divisor == 0) { 19 | throw new IllegalArgumentException("divisor can't be zero."); 20 | } 21 | return number / divisor; 22 | } 23 | 24 | @Override 25 | public int multiply(int first, int second) { 26 | return first * second; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch01/Calculator.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | public interface Calculator { 4 | 5 | static Calculator getInstance() { 6 | return new BasicCalculator(); 7 | } 8 | 9 | int add(int first, int second); 10 | 11 | int subtract(int first, int second); 12 | 13 | int divide(int number, int divisor); 14 | 15 | int multiply(int first, int second); 16 | 17 | default int remainder(int number, int divisor) { 18 | return subtract(number, multiply(divisor, divide(number, divisor))); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch01/CalculatorFactory.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | public abstract class CalculatorFactory { 4 | 5 | public static Calculator getInstance() { 6 | return new BasicCalculator(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch02/Example1_Lambda.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch02; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | 8 | public class Example1_Lambda { 9 | 10 | public static void main(String[] args) { 11 | List names = Arrays.asList("shekhar", "rahul", "sameer"); 12 | // sort alphabetically 13 | Collections.sort(names); 14 | System.out.println("names sorted alphabetically >>"); 15 | System.out.println(names); 16 | System.out.println(); 17 | 18 | // using anonymous classes 19 | Collections.sort(names, new Comparator() { 20 | @Override 21 | public int compare(String o1, String o2) { 22 | return o1.length() - o2.length(); 23 | } 24 | }); 25 | System.out.println("names sorted by length >>"); 26 | System.out.println(names); 27 | System.out.println(); 28 | 29 | /** 30 | * Using lambda 31 | * Things to show >> 32 | * 1. return statement 33 | * 2. Without return statement 34 | * 3. Multiple lines 35 | * 4. Type inference 36 | */ 37 | 38 | Collections.sort(names, (String first, String second) -> second.length() - first.length()); 39 | System.out.println("names sorted by length(reversed) >>"); 40 | System.out.println(names); 41 | System.out.println(); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch02/Example2_Lambda.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch02; 2 | 3 | /** 4 | * 5 | * Example of compiler unable to detect lambda expression types 6 | */ 7 | public class Example2_Lambda { 8 | 9 | public static void main(String[] args) { 10 | 11 | // Comparator comparator = (first, second) -> first.length() - second.length(); 12 | 13 | 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch02/Example3_Functionalnterfaces.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch02; 2 | 3 | import java.util.UUID; 4 | import java.util.function.Consumer; 5 | import java.util.function.Function; 6 | import java.util.function.Predicate; 7 | import java.util.function.Supplier; 8 | 9 | public class Example3_Functionalnterfaces { 10 | 11 | public static void main(String[] args) { 12 | 13 | Predicate nameStartWithS = name -> name.startsWith("s"); 14 | 15 | Consumer sendEmail = message -> System.out.println("Sending email >> " + message); 16 | 17 | Function stringToLength = name -> name.length(); 18 | 19 | Supplier uuidSupplier = () -> UUID.randomUUID().toString(); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch02/Example4_MethodReferences.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch02; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.function.Function; 8 | 9 | import static java.util.Comparator.comparingInt; 10 | 11 | public class Example4_MethodReferences { 12 | 13 | public static void main(String[] args) { 14 | List names = Arrays.asList("shekhar", "rahul", "sameer"); 15 | 16 | List namesLength = transform(names, String::length); 17 | System.out.println(namesLength); 18 | 19 | List upperCaseNames = transform(names, String::toUpperCase); 20 | System.out.println(upperCaseNames); 21 | 22 | List numbers = transform(Arrays.asList("1", "2", "3"), Integer::parseInt); 23 | System.out.println(numbers); 24 | 25 | Collections.sort(names, comparingInt(String::length).reversed()); 26 | System.out.println(names); 27 | } 28 | 29 | private static List transform(List list, Function fx) { 30 | List result = new ArrayList<>(); 31 | for (T element : list) { 32 | result.add(fx.apply(element)); 33 | } 34 | return result; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch02/InvalidFunctionInterface.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch02; 2 | 3 | /** 4 | * Example of @FunctionalInterface 5 | */ 6 | 7 | @FunctionalInterface 8 | public interface InvalidFunctionInterface { 9 | 10 | public boolean test(); 11 | 12 | // public boolean test1(); 13 | 14 | 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch03/Example1_Java7.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch03; 2 | 3 | 4 | import com.shekhargulati.java8_tutorial.domain.Task; 5 | import com.shekhargulati.java8_tutorial.domain.TaskType; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.List; 11 | 12 | import static com.shekhargulati.java8_tutorial.utils.DataUtils.getTasks; 13 | 14 | 15 | public class Example1_Java7 { 16 | 17 | public static void main(String[] args) { 18 | List tasks = getTasks(); 19 | List readingTasks = new ArrayList<>(); 20 | for (Task task : tasks) { 21 | if (task.getType() == TaskType.READING) { 22 | readingTasks.add(task); 23 | } 24 | } 25 | Collections.sort(readingTasks, new Comparator() { 26 | @Override 27 | public int compare(Task t1, Task t2) { 28 | return t1.getTitle().length() - t2.getTitle().length(); 29 | } 30 | }); 31 | for (Task readingTask : readingTasks) { 32 | System.out.println(readingTask.getTitle()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch03/Example1_Stream.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch03; 2 | 3 | 4 | import com.shekhargulati.java8_tutorial.domain.Task; 5 | import com.shekhargulati.java8_tutorial.domain.TaskType; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | import static com.shekhargulati.java8_tutorial.utils.DataUtils.getTasks; 11 | 12 | 13 | public class Example1_Stream { 14 | 15 | public static void main(String[] args) { 16 | List tasks = getTasks(); 17 | 18 | List readingTasks = tasks.stream() 19 | .filter(task -> task.getType() == TaskType.READING) 20 | .sorted((t1, t2) -> t1.getTitle().length() - t2.getTitle().length()) 21 | .map(Task::getTitle) 22 | .collect(Collectors.toList()); 23 | 24 | readingTasks.forEach(System.out::println); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch03/Examples.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch03; 2 | 3 | import com.shekhargulati.java8_tutorial.domain.Task; 4 | import com.shekhargulati.java8_tutorial.domain.TaskType; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.Comparator.comparing; 9 | import static java.util.stream.Collectors.toList; 10 | 11 | public class Examples { 12 | 13 | public List allReadingTasks(List tasks) { 14 | return tasks.stream(). 15 | filter(task -> task.getType() == TaskType.READING). 16 | sorted(comparing(Task::getCreatedOn)). 17 | map(Task::getTitle). 18 | collect(toList()); 19 | 20 | } 21 | 22 | 23 | public List allReadingTasksSortedByCreatedOnDesc(List tasks) { 24 | return tasks.stream(). 25 | filter(task -> task.getType() == TaskType.READING). 26 | sorted(comparing(Task::getCreatedOn).reversed()). 27 | map(Task::getTitle). 28 | collect(toList()); 29 | 30 | } 31 | 32 | public List allDistinctTasks(List tasks) { 33 | return tasks.stream().distinct().collect(toList()); 34 | } 35 | 36 | public List topN(List tasks, int n) { 37 | return tasks.stream(). 38 | filter(task -> task.getType() == TaskType.READING). 39 | sorted(comparing(Task::getCreatedOn)). 40 | map(Task::getTitle). 41 | limit(n). 42 | collect(toList()); 43 | } 44 | 45 | public long countAllReadingTasks(List tasks) { 46 | return tasks.stream(). 47 | filter(task -> task.getType() == TaskType.READING). 48 | count(); 49 | } 50 | 51 | public List allDistinctTags(List tasks) { 52 | return tasks.stream().flatMap(task -> task.getTags().stream()).distinct().collect(toList()); 53 | } 54 | 55 | public boolean isAllReadingTasksWithTagBooks(List tasks) { 56 | return tasks.stream(). 57 | filter(task -> task.getType() == TaskType.READING). 58 | allMatch(task -> task.getTags().contains("books")); 59 | } 60 | 61 | public boolean isAnyReadingTasksWithTagJava8(List tasks) { 62 | return tasks.stream(). 63 | filter(task -> task.getType() == TaskType.READING). 64 | anyMatch(task -> task.getTags().contains("java8")); 65 | } 66 | 67 | public String joinAllTaskTitles(List tasks) { 68 | return tasks.stream(). 69 | map(Task::getTitle). 70 | reduce((first, second) -> first + " *** " + second). 71 | get(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch03/ParallelStreamExample.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch03; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.stream.IntStream; 6 | 7 | import static java.util.stream.Collectors.groupingBy; 8 | 9 | public class ParallelStreamExample { 10 | 11 | public static void main(String[] args) { 12 | Map> numbersPerThread = IntStream.rangeClosed(1, 160) 13 | .parallel() 14 | .boxed() 15 | .collect(groupingBy(i -> Thread.currentThread().getName())); 16 | 17 | numbersPerThread.forEach((k, v) -> System.out.println(String.format("%s >> %s", k, v))); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch05/TaskNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch05; 2 | 3 | public class TaskNotFoundException extends RuntimeException { 4 | public TaskNotFoundException(String id) { 5 | super("No task found for id: " + id); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch05/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch05; 2 | 3 | 4 | 5 | import com.shekhargulati.java8_tutorial.ch05.domain.Task; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | public class TaskRepository { 12 | 13 | private final Map db = new HashMap<>(); 14 | 15 | public void loadData() { 16 | db.put("1", new Task("1", "hello java 1")); 17 | db.put("2", new Task("2", "hello java 2")); 18 | db.put("3", new Task("3", "hello java 3")); 19 | db.put("4", new Task("4", "hello java 4")); 20 | db.put("5", new Task("5", "hello java 5")); 21 | } 22 | 23 | public Task find(String id) { 24 | return Optional.ofNullable(db.get(id)) 25 | .orElseThrow(() -> new TaskNotFoundException(id)); 26 | } 27 | 28 | public Optional taskAssignedTo(String id) { 29 | return Optional.ofNullable(find(id)) 30 | .flatMap(task -> task.getAssignedTo()) 31 | .map(user -> user.getUsername()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch05/domain/Task.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch05.domain; 2 | 3 | import java.util.Optional; 4 | 5 | public class Task { 6 | private final String title; 7 | private final Optional assignedTo; 8 | private final String id; 9 | 10 | public Task(String id, String title) { 11 | this.id = id; 12 | this.title = title; 13 | assignedTo = Optional.empty(); 14 | } 15 | 16 | public Task(String id, String title, User assignedTo) { 17 | this.id = id; 18 | this.title = title; 19 | this.assignedTo = Optional.ofNullable(assignedTo); 20 | } 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public String getTitle() { 27 | return title; 28 | } 29 | 30 | public Optional getAssignedTo() { 31 | return assignedTo; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch05/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch05.domain; 2 | 3 | import java.util.Optional; 4 | 5 | public class User { 6 | 7 | private final String username; 8 | private final Optional address; 9 | 10 | public User(String username) { 11 | this.username = username; 12 | this.address = Optional.empty(); 13 | } 14 | 15 | public User(String username, String address) { 16 | this.username = username; 17 | this.address = Optional.ofNullable(address); 18 | } 19 | 20 | public String getUsername() { 21 | return username; 22 | } 23 | 24 | public Optional getAddress() { 25 | return address; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch06/MapExample.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch06; 2 | 3 | import java.util.AbstractMap.SimpleEntry; 4 | import java.util.Map; 5 | import java.util.stream.Stream; 6 | 7 | import static java.util.stream.Collectors.toMap; 8 | 9 | public class MapExample { 10 | 11 | public static Map createMap(SimpleEntry... entries) { 12 | return Stream.of(entries).collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue)); 13 | } 14 | 15 | public static void doSth(Map map) { 16 | 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch09/CompletableFutureExample.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch09; 2 | 3 | import java.util.UUID; 4 | import java.util.concurrent.CompletableFuture; 5 | import java.util.concurrent.Executors; 6 | 7 | public class CompletableFutureExample { 8 | 9 | public static void main(String[] args) { 10 | CompletableFuture.completedFuture("hello"); 11 | CompletableFuture.runAsync(() -> System.out.println("hello")); 12 | CompletableFuture.runAsync(() -> System.out.println("hello"), Executors.newSingleThreadExecutor()); 13 | CompletableFuture.supplyAsync(() -> UUID.randomUUID().toString()); 14 | CompletableFuture.supplyAsync(() -> UUID.randomUUID().toString(), Executors.newSingleThreadExecutor()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch10/NashornExample1.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch10; 2 | 3 | import javax.script.ScriptEngine; 4 | import javax.script.ScriptEngineManager; 5 | import javax.script.ScriptException; 6 | 7 | public class NashornExample1 { 8 | 9 | public static void main(String[] args) throws ScriptException { 10 | ScriptEngineManager manager = new ScriptEngineManager(); 11 | ScriptEngine nashorn = manager.getEngineByName("nashorn"); 12 | Integer eval = (Integer) nashorn.eval("10 + 20"); 13 | System.out.println(eval); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch10/NashornExample2.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch10; 2 | 3 | import javax.script.*; 4 | import java.util.AbstractMap.SimpleEntry; 5 | import java.util.stream.Stream; 6 | 7 | import static java.util.stream.Collectors.toMap; 8 | 9 | public class NashornExample2 { 10 | 11 | public static void main(String[] args) throws ScriptException { 12 | ScriptEngineManager manager = new ScriptEngineManager(); 13 | ScriptEngine nashorn = manager.getEngineByName("nashorn"); 14 | 15 | Bindings bindings = new SimpleBindings(Stream.of( 16 | new SimpleEntry<>("a", 10), 17 | new SimpleEntry<>("b", 20)) 18 | .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue))); 19 | Double eval = (Double) nashorn.eval("a + b", bindings); 20 | System.out.println(eval); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch10/NashornExample3.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch10; 2 | 3 | import jdk.nashorn.api.scripting.ClassFilter; 4 | import jdk.nashorn.api.scripting.NashornScriptEngineFactory; 5 | 6 | import javax.script.ScriptEngine; 7 | import javax.script.ScriptException; 8 | 9 | public class NashornExample3 { 10 | 11 | public static void main(String[] args) throws ScriptException { 12 | NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); 13 | ScriptEngine nashorn = factory.getScriptEngine(new NoJavaFilter()); 14 | Integer eval = (Integer) nashorn.eval("var HashMap = Java.type('java.util.HashMap')"); 15 | System.out.println(eval); 16 | } 17 | 18 | private static class NoJavaFilter implements ClassFilter{ 19 | 20 | @Override 21 | public boolean exposeToScripts(String s) { 22 | return false; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch12/CreateVm.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch12; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Retention(RetentionPolicy.RUNTIME) 6 | @Target(ElementType.METHOD) 7 | @Repeatable(CreateVms.class) 8 | public @interface CreateVm { 9 | String name(); 10 | } 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.METHOD) 14 | @interface CreateVms { 15 | CreateVm[] value(); 16 | } -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch12/MyAnnotation.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch12; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(value = {ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) 12 | public @interface MyAnnotation { 13 | } 14 | 15 | 16 | class MyAnnotationUsage<@MyAnnotation T> { 17 | 18 | public static void main(String[] args) { 19 | 20 | } 21 | 22 | public static void doSth() { 23 | List<@MyAnnotation String> names = Arrays.asList("shekhar"); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/ch12/VmManager.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch12; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public class VmManager { 6 | 7 | @CreateVm(name = "vm1") 8 | @CreateVm(name = "vm2") 9 | public void manage() { 10 | System.out.println("Managing ...."); 11 | } 12 | 13 | public static void main(String[] args) throws Exception { 14 | CreateVm[] createVms = VmManager.class.getMethod("manage").getAnnotationsByType(CreateVm.class); 15 | Stream.of(createVms).map(CreateVm::name).forEach(System.out::println); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/domain/Task.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.domain; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.Objects; 7 | import java.util.Set; 8 | 9 | public class Task { 10 | 11 | private final String title; 12 | private final String description; 13 | private final TaskType type; 14 | private LocalDate createdOn; 15 | private Set tags = new HashSet<>(); 16 | 17 | public Task(final String title, final TaskType type) { 18 | this(title, title, type, LocalDate.now()); 19 | } 20 | 21 | public Task(final String title, final TaskType type, final LocalDate createdOn) { 22 | this(title, title, type, createdOn); 23 | } 24 | 25 | public Task(final String title, final String description, final TaskType type, final LocalDate createdOn) { 26 | this.title = title; 27 | this.description = description; 28 | this.type = type; 29 | this.createdOn = createdOn; 30 | } 31 | 32 | public String getTitle() { 33 | return title; 34 | } 35 | 36 | public String getDescription() { 37 | return description; 38 | } 39 | 40 | public TaskType getType() { 41 | return type; 42 | } 43 | 44 | public LocalDate getCreatedOn() { 45 | return createdOn; 46 | } 47 | 48 | public Task addTag(String tag) { 49 | this.tags.add(tag); 50 | return this; 51 | } 52 | 53 | public Set getTags() { 54 | return Collections.unmodifiableSet(tags); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "Task{" + 60 | "title='" + title + '\'' + 61 | ", type=" + type + 62 | '}'; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | Task task = (Task) o; 70 | return Objects.equals(title, task.title) && 71 | Objects.equals(type, task.type); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return Objects.hash(title, type); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/domain/TaskType.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.domain; 2 | 3 | public enum TaskType { 4 | 5 | READING, CODING, BLOGGING 6 | } 7 | -------------------------------------------------------------------------------- /code/src/main/java/com/shekhargulati/java8_tutorial/utils/DataUtils.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.utils; 2 | 3 | 4 | import com.shekhargulati.java8_tutorial.domain.Task; 5 | import com.shekhargulati.java8_tutorial.domain.TaskType; 6 | 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Paths; 10 | import java.time.LocalDate; 11 | import java.time.Month; 12 | import java.util.List; 13 | import java.util.stream.IntStream; 14 | import java.util.stream.Stream; 15 | 16 | import static java.util.stream.Collectors.toList; 17 | 18 | 19 | public class DataUtils { 20 | 21 | public static Stream lines() { 22 | return filePathToStream("src/main/resources/book.txt"); 23 | } 24 | 25 | public static Stream negativeWords() { 26 | return filePathToStream("src/main/resources/negative-words.txt"); 27 | } 28 | 29 | public static Stream filePathToStream(String path) { 30 | try { 31 | return Files.lines(Paths.get("training", path)); 32 | } catch (IOException e) { 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | 37 | public static IntStream range(int start, int end) { 38 | return IntStream.rangeClosed(start, end); 39 | } 40 | 41 | public static List getTasks() { 42 | Task task1 = new Task("Read Java 8 in action", TaskType.READING, LocalDate.of(2015, Month.SEPTEMBER, 20)).addTag("java").addTag("java8").addTag("books"); 43 | Task task2 = new Task("Write factorial program in Haskell", TaskType.CODING, LocalDate.of(2015, Month.SEPTEMBER, 20)).addTag("program").addTag("haskell").addTag("functional"); 44 | Task task3 = new Task("Read Effective Java", TaskType.READING, LocalDate.of(2015, Month.SEPTEMBER, 21)).addTag("java").addTag("books"); 45 | Task task4 = new Task("Write a blog on Stream API", TaskType.BLOGGING, LocalDate.of(2015, Month.SEPTEMBER, 21)).addTag("writing").addTag("stream").addTag("java8"); 46 | Task task5 = new Task("Write prime number program in Scala", TaskType.CODING, LocalDate.of(2015, Month.SEPTEMBER, 22)).addTag("scala").addTag("functional").addTag("program"); 47 | return Stream.of(task1, task2, task3, task4, task5).collect(toList()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /code/src/test/java/com/shekhargulati/java8_tutorial/ch01/CalculatorTest.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch01; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.equalTo; 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class CalculatorTest { 10 | 11 | Calculator calculator = Calculator.getInstance(); 12 | 13 | @Test 14 | public void shouldAddTwoNumbers() throws Exception { 15 | int sum = calculator.add(1, 2); 16 | assertThat(sum, is(equalTo(3))); 17 | } 18 | 19 | @Test 20 | public void shouldSubtractTwoNumbers() throws Exception { 21 | int difference = calculator.subtract(3, 2); 22 | assertThat(difference, is(equalTo(1))); 23 | } 24 | 25 | @Test 26 | public void shouldDivideTwoNumbers() throws Exception { 27 | int quotient = calculator.divide(4, 2); 28 | assertThat(quotient, is(equalTo(2))); 29 | } 30 | 31 | @Test(expected = IllegalArgumentException.class) 32 | public void shouldThrowIllegalArgumentExceptionWhenDivisorIsZero() throws Exception { 33 | calculator.divide(4, 0); 34 | } 35 | 36 | @Test 37 | public void shouldMultiplyTwoNumbers() throws Exception { 38 | int product = calculator.multiply(3, 4); 39 | assertThat(product, is(equalTo(12))); 40 | } 41 | 42 | @Test 43 | public void shouldFindRemainderOfTwoNumbers() throws Exception { 44 | int remainder = calculator.remainder(100, 19); 45 | assertThat(remainder, is(equalTo(5))); 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /code/src/test/java/com/shekhargulati/java8_tutorial/ch06/MapExampleTest.java: -------------------------------------------------------------------------------- 1 | package com.shekhargulati.java8_tutorial.ch06; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.AbstractMap.SimpleEntry; 6 | import java.util.Map; 7 | 8 | import static org.hamcrest.CoreMatchers.equalTo; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class MapExampleTest { 12 | 13 | @Test 14 | public void shouldCreateAHashMapUsingSimpleEntries() throws Exception { 15 | Map nameAndAge = MapExample.createMap(new SimpleEntry<>("shekhar", 32), new SimpleEntry<>("rahul", 33), new SimpleEntry<>("sameer", 33)); 16 | assertThat(nameAndAge.size(), equalTo(3)); 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /java8-slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leege100/java8-the-missing-tutorial-zh/d185e1ac7a624cffb851616bce9db427ee76ba6b/java8-slides.pdf --------------------------------------------------------------------------------