├── Collectors提供的归约方法.png
├── Option的一些方法.png
├── README.md
└── README.md~7c0b1bcaf98012cba72a270a62a46268e54a08be
/Collectors提供的归约方法.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CL0610/Java8-learning/476e0ae091ccc3a8cd330f09886c4de55159860b/Collectors提供的归约方法.png
--------------------------------------------------------------------------------
/Option的一些方法.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CL0610/Java8-learning/476e0ae091ccc3a8cd330f09886c4de55159860b/Option的一些方法.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 对于Java开发者来说,Java8的版本显然是一个具有里程碑意义的版本,蕴含了许多令人激动的新特性,如果能利用好这些新特性,能够大大提升我们的开发效率。Java8的函数式编程能够大大减少代码量和便于维护,同时,还有一些跟并发相关的功能。开发中常用到的新特性如下:
2 |
3 | [接口的默认方法和静态方法](#1)
4 |
5 | [函数式接口FunctionInterface与lambda表达式](#2)
6 |
7 | [方法引用](#3)
8 |
9 | [Stream](#4)
10 |
11 | [Optional](#5)
12 |
13 | [Date/time API的改进](#6)
14 |
15 | [其他改进](#7)
16 |
17 |
18 |
19 |
20 |
21 | # 1. 接口的默认方法和静态方法
22 |
23 |
24 | 在Java8之前,接口中**只能**包含抽象方法。那么这有什么样弊端呢?比如,想再Collection接口中添加一个spliterator抽象方法,那么也就意味着之前所有实现Collection接口的实现类,都要重新实现spliterator这个方法才行。而接口的默认方法就是**为了解决接口的修改与接口实现类不兼容的问题,作为代码向前兼容的一个方法**。
25 |
26 | 那么如何在接口中定义一个默认方法呢?来看下JDK中Collection中如何定义spliterator方法的:
27 |
28 | default Spliterator spliterator() {
29 | return Spliterators.spliterator(this, 0);
30 | }
31 |
32 | 可以看到定义接口的默认方法是通过**default**关键字。因此,在Java8中接口能够包含抽象方法外还能够包含若干个默认方法(即有完整逻辑的实例方法)。
33 |
34 |
35 | public interface IAnimal {
36 | default void breath(){
37 | System.out.println("breath!");
38 | };
39 | }
40 |
41 |
42 | public class DefaultMethodTest implements IAnimal {
43 | public static void main(String[] args) {
44 | DefaultMethodTest defaultMethod = new DefaultMethodTest();
45 | defaultMethod.breath();
46 | }
47 |
48 | }
49 |
50 |
51 | 输出结果为:breath!
52 |
53 | 可以看出**IAnimal**接口中有由default定义的默认方法后,那么其实现类DefaultMethodTest也同样能够拥有实例方法**breath**。但是如果一个类继承多个接口,多个接口中有相同的方法就会产生冲突该如何解决?实际上默认方法的改进,使得java类能够拥有类似多继承的能力,即一个对象实例,将拥有多个接口的实例方法,自然而然也会存在方法重复冲突的问题。
54 |
55 | 下面来看一个例子:
56 |
57 | public interface IDonkey{
58 | default void run() {
59 | System.out.println("IDonkey run");
60 | }
61 | }
62 |
63 | public interface IHorse {
64 |
65 | default void run(){
66 | System.out.println("Horse run");
67 | }
68 |
69 | }
70 |
71 | public class DefaultMethodTest implements IDonkey,IHorse {
72 | public static void main(String[] args) {
73 | DefaultMethodTest defaultMethod = new DefaultMethodTest();
74 | defaultMethod.breath();
75 | }
76 |
77 |
78 |
79 | }
80 |
81 | 定义两个接口:IDonkey和IHorse,这两个接口中都有相同的run方法。DefaultMethodTest实现了这两个接口,由于这两个接口有相同的方法,因此就会产生冲突,不知道以哪个接口中的run方法为准,编译会出错:`inherits unrelated defaults for run.....`
82 |
83 |
84 |
85 | > 解决方法
86 |
87 | 针对由默认方法引起的方法冲突问题,**只有通过重写冲突方法,并方法绑定的方式,指定以哪个接口中的默认方法为准**。
88 |
89 | public class DefaultMethodTest implements IAnimal,IDonkey,IHorse {
90 | public static void main(String[] args) {
91 | DefaultMethodTest defaultMethod = new DefaultMethodTest();
92 | defaultMethod.run();
93 | }
94 |
95 | @Override
96 | public void run() {
97 | IHorse.super.run();
98 | }
99 | }
100 |
101 | DefaultMethodTest重写了run方法,并通过 `IHorse.super.run();`指定以IHorse中的run方法为准。
102 |
103 | **静态方法**
104 |
105 | 在Java8中还有一个特性就是,接口中还可以声明静态方法,如下例:
106 |
107 | public interface IAnimal {
108 | default void breath(){
109 | System.out.println("breath!");
110 | }
111 | static void run(){}
112 | }
113 |
114 |
115 |
116 | # 2.函数式接口FunctionInterface与lambda表达式 #
117 |
118 | > 函数式接口
119 |
120 | Java8最大的变化是引入**了函数式思想,也就是说函数可以作为另一个函数的参数**。函数式接口,要求接口中**有且仅有一个抽象方法**,因此经常使用的Runnable,Callable接口就是典型的函数式接口。可以使用`@FunctionalInterface`注解,声明一个接口是函数式接口。如果一个接口满足函数式接口的定义,会默认转换成函数式接口。但是,最好是使用`@FunctionalInterface`注解显式声明。这是因为函数式接口比较脆弱,如果开发人员无意间新增了其他方法,就破坏了函数式接口的要求,如果使用注解`@FunctionalInterface`,开发人员就会知道当前接口是函数式接口,就不会无意间破坏该接口。下面举一个例子:
121 |
122 | @java.lang.FunctionalInterface
123 | public interface FunctionalInterface {
124 | void handle();
125 | }
126 |
127 | 该接口只有一个抽象方法,并且使用注解显式声明。但是,函数式接口要求只有一个抽象方法却可以拥有若干个默认方法的(实例方法),比如下例:
128 |
129 | @java.lang.FunctionalInterface
130 | public interface FunctionalInterface {
131 | void handle();
132 |
133 | default void run() {
134 | System.out.println("run");
135 | }
136 | }
137 |
138 | 该接口中,除了有抽象方法handle外,还有默认方法(实例方法)run。另外,**任何被Object实现的方法都不能当做是抽象方法**。
139 |
140 | > lambda表达式
141 |
142 | lambda表达式是函数式编程的核心,lambda表达式即匿名函数,是一段没有函数名的函数体,可以作为参数直接传递给相关的调用者。lambda表达式极大的增加了Java语言的表达能力。lambda的语法结构为:
143 |
144 |
145 | (parameters) -> expression
146 | 或
147 | (parameters) ->{ statements; }
148 |
149 |
150 |
151 | - **可选类型声明**:不需要声明参数类型,编译器可以统一识别参数值。
152 |
153 |
154 | - **可选的参数圆括号**:一个参数无需定义圆括号,但多个参数需要定义圆括号。
155 |
156 |
157 | - **可选的大括号**:如果主体包含了一个语句,就不需要使用大括号。
158 |
159 |
160 | - **可选的返回关键字**:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
161 |
162 | 完整示例为(摘自[菜鸟](http://www.runoob.com/java/java8-lambda-expressions.html))
163 |
164 |
165 | public class Java8Tester {
166 | public static void main(String args[]){
167 | Java8Tester tester = new Java8Tester();
168 |
169 | // 类型声明
170 | MathOperation addition = (int a, int b) -> a + b;
171 |
172 | // 不用类型声明
173 | MathOperation subtraction = (a, b) -> a - b;
174 |
175 | // 大括号中的返回语句
176 | MathOperation multiplication = (int a, int b) -> { return a * b; };
177 |
178 | // 没有大括号及返回语句
179 | MathOperation division = (int a, int b) -> a / b;
180 |
181 | System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
182 | System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
183 | System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
184 | System.out.println("10 / 5 = " + tester.operate(10, 5, division));
185 |
186 | // 不用括号
187 | GreetingService greetService1 = message ->
188 | System.out.println("Hello " + message);
189 |
190 | // 用括号
191 | GreetingService greetService2 = (message) ->
192 | System.out.println("Hello " + message);
193 |
194 | greetService1.sayMessage("Runoob");
195 | greetService2.sayMessage("Google");
196 | }
197 |
198 | interface MathOperation {
199 | int operation(int a, int b);
200 | }
201 |
202 | interface GreetingService {
203 | void sayMessage(String message);
204 | }
205 |
206 | private int operate(int a, int b, MathOperation mathOperation){
207 | return mathOperation.operation(a, b);
208 | }
209 | }
210 |
211 | 另外,lambda还可以访问外部局部变量,如下例所示:
212 |
213 | int adder = 5;
214 | Arrays.asList(1, 2, 3, 4, 5).forEach(e -> System.out.println(e + adder));
215 |
216 | 实际上在lambda中访问类的成员变量或者局部变量时,**会隐式转换成final类型变量**,所以上例实际上等价于:
217 |
218 | final int adder = 5;
219 | Arrays.asList(1, 2, 3, 4, 5).forEach(e -> System.out.println(e + adder));
220 |
221 |
222 |
223 | # 3. 方法引用 #
224 |
225 | 方法引用是为了进一步简化lambda表达式,通过类**名或者实例名与方法名的组合来直接访问到类或者实例已经存在的方法或者构造方法**。方法引用使用**::**来定义,**::**的前半部分表示类名或者实例名,后半部分表示方法名,如果是构造方法就使用`NEW`来表示。
226 |
227 | 方法引用在Java8中使用方式相当灵活,总的来说,一共有以下几种形式:
228 |
229 | - 静态方法引用:ClassName::methodName;
230 | - 实例上的实例方法引用:instanceName::methodName;
231 | - 超类上的实例方法引用:supper::methodName;
232 | - 类的实例方法引用:ClassName:methodName;
233 | - 构造方法引用Class:new;
234 | - 数组构造方法引用::TypeName[]::new
235 |
236 | 下面来看一个例子:
237 |
238 |
239 | public class MethodReferenceTest {
240 |
241 | public static void main(String[] args) {
242 | ArrayList cars = new ArrayList<>();
243 | for (int i = 0; i < 5; i++) {
244 | Car car = Car.create(Car::new);
245 | cars.add(car);
246 | }
247 | cars.forEach(Car::showCar);
248 |
249 | }
250 |
251 | @FunctionalInterface
252 | interface Factory {
253 | T create();
254 | }
255 |
256 | static class Car {
257 | public void showCar() {
258 | System.out.println(this.toString());
259 | }
260 |
261 | public static Car create(Factory factory) {
262 | return factory.create();
263 | }
264 | }
265 | }
266 |
267 |
268 | 输出结果:
269 |
270 | learn.MethodReferenceTest$Car@769c9116
271 | learn.MethodReferenceTest$Car@6aceb1a5
272 | learn.MethodReferenceTest$Car@2d6d8735
273 | learn.MethodReferenceTest$Car@ba4d54
274 | learn.MethodReferenceTest$Car@12bc6874
275 |
276 | 在上面的例子中使用了`Car::new`,即通过构造方法的方法引用的方式进一步简化了lambda的表达式,`Car::showCar`,即表示实例方法引用。
277 |
278 |
279 | # 4. Stream #
280 | Java8中有一种新的数据处理方式,那就是流Stream,结合lambda表达式能够更加简洁高效的处理数据。Stream使用一种类似于SQL语句从数据库查询数据的直观方式,对数据进行如筛选、排序以及聚合等多种操作。
281 |
282 | ## 4.1 什么是流Stream ##
283 |
284 | Stream是一个来自数据源的元素队列并支持聚合操作,更像是一个更高版本的Iterator,原始版本的Iterator,只能一个个遍历元素并完成相应操作。而使用Stream,只需要指定什么操作,如“过滤长度大于10的字符串”等操作,Stream会内部遍历并完成指定操作。
285 |
286 | Stream中的元素在管道中经过中间操作(intermediate operation)的处理后,最后由最终操作(terminal operation)得到最终的结果。
287 |
288 | - 数据源:是Stream的来源,可以是集合、数组、I/O channel等转换而成的Stream;
289 | - 基本操作:类似于SQL语句一样的操作,比如filter,map,reduce,find,match,sort等操作。
290 |
291 | 当我们操作一个流时,实际上会包含这样的执行过程:
292 |
293 | **获取数据源-->转换成Stream-->执行操作,返回一个新的Stream-->再以新的Stream继续执行操作--->直至最后操作输出最终结果**。
294 |
295 |
296 | ## 4.2 生成Stream的方式 ##
297 |
298 | 生成Stream的方式主要有这样几种:
299 |
300 | 1. 从接口Collection中和Arrays:
301 |
302 | - Collection.stream();
303 | - Collection.parallelStream(); //相较于串行流,并行流能够大大提升执行效率
304 | - Arrays.stream(T array);
305 | 2. Stream中的静态方法:
306 |
307 | - Stream.of();
308 | - generate(Supplier s);
309 | - iterate(T seed, UnaryOperator f);
310 | - empty();
311 |
312 | 3. 其他方法
313 | - Random.ints()
314 | - BitSet.stream()
315 | - Pattern.splitAsStream(java.lang.CharSequence)
316 | - JarFile.stream()
317 | - BufferedReader.lines()
318 |
319 | 下面对前面常见的两种方式给出示例:
320 |
321 | public class StreamTest {
322 |
323 |
324 | public static void main(String[] args) {
325 | //1.使用Collection中的方法和Arrays
326 | String[] strArr = new String[]{"a", "b", "c"};
327 | List list = Arrays.asList(strArr);
328 | Stream stream = list.stream();
329 | Stream stream1 = Arrays.stream(strArr);
330 |
331 | //2. 使用Stream中提供的静态方法
332 | Stream stream2 = Stream.of(strArr);
333 | Stream stream3 = Stream.generate(Math::random);
334 | Stream