Java 8 JDK 1.8
一、Lambda 表达式
Lambda 将函数式编程引入了 Java。Lambda 允许把函数作为一个方法的参数,或者把代码看成数据。
一个 Lambda 表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:
Arrays.asList( “p”, “k”, “u”,“f”, “o”, “r”,“k”).forEach( e -> System.out.println( e ) );
为了使现有函数更好的支持 Lambda 表达式,Java 8 引入了函数式接口的概念。函数式接口就是只有一个方法的普通接口。java.lang.Runnable 与 java.util.concurrent.Callable 是函数式接口最典型的例子。为此,Java 8 增加了一种特殊的注解@FunctionalInterface
二、接口的默认方法与静态方法
public interface DefaultFunctionInterface {/*** default 在接口中定义默认方法,也可以提供默认的实现。*/default String defaultFunction() {return "default function";}/*** static 在接口中定义静态方法,也可以提供实现。* static 方法不能被继承,也不能被实现类调用,只能被自身调用。*/static String staticFunction() {return "static function";}
}
接口的默认方法和静态方法的引入,其实可以认为引入了C++ 中抽象类的理念,以后再也不用在每个实现类中都写重复的代码了。
三、方法引用(含构造方法引用)
通常与 Lambda 表达式联合使用,可以直接引用已有 Java 类或对象的方法。一般有四种不同的方法引用:
构造器引用。语法是Class::new,或者更一般的Class< T >::new,要求构造器方法是没有参数;
静态方法引用。语法是Class::static_method,要求接受一个Class类型的参数;
特定类的任意对象方法引用。它的语法是Class::method。要求方法是没有参数的;
特定对象的方法引用,它的语法是instance::method。要求方法接受一个参数,与3不同的地方在于,3是在列表元素上分别调用方法,而4是在某个对象上调用方法,将列表元素作为参数传入;
四、重复注解
在 Java 5 中使用注解有一个限制,即相同的注解在同一位置只能声明一次。Java 8 引入重复注解,这样相同的注解在同一地方也可以声明多次。重复注解机制本身需要用 @Repeatable 注解。Java 8 在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。
五、扩展注解的支持(类型注解)
Java 8 扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。
private @NotNull String name;
六、Optional
Java 8 引入 Optional 类来防止空指针异常,Optional 类最先是由 Google 的 Guava 项目引入的。Optional 类实际上是个容器:它可以保存类型T的值,或者保存 null。使用 Optional 类就不用显式进行空指针检查了。
七、Stream
Stream API 是把真正的函数式编程风格引入到 Java 中。其实简单来说可以把 Stream 理解为 MapReduce,当然 Google 的 MapReduce的 灵感也是来自函数式编程。她其实是一连串支持连续、并行聚集操作的元素。从语法上看,也很像 linux 的管道、或者链式编程,代码写起来简洁明了,非常酷帅!
八、Date/Time API (JSR 310)
Java 8 新的 Date-Time API (JSR 310) 受 Joda-Time 的影响,提供了新的 java.time 包,可以用来替代 java.util.Date 和 java.util.Calendar。一般会用到 Clock、LocaleDate、LocalTime、LocaleDateTime、ZonedDateTime、Duration 这些类,对于时间日期的改进还是非常不错的。
九、JavaScript 引擎 Nashorn
Nashorn 允许在 JVM 上开发运行 JavaScript 应用,允许 Java 与 JavaScript 相互调用。
十、Base64
在Java 8 中,Base64 编码成为了 Java 类库的标准。Base64 类同时还提供了对URL、MIME友好的编码器与解码器。
更好的类型推测机制
Java 8 在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。
编译器优化
Java 8 将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用 -parameters 参数。
并行(parallel)数组
支持对数组进行并行处理,主要是 parallelSort() 方法,它可以在多核机器上极大提高数组排序的速度。
并发(Concurrency)
在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。
Nashorn 引擎 jjs
基于 Nashorn 引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。
类依赖分析器 jdeps
可以显示Java类的包级别或类级别的依赖。
JVM 的 PermGen 空间被移除
取代它的是Metaspace(JEP 122)。
————————————————
二、Lambda 表达式
Lambda表达式可以看成是匿名内部类,使用Lambda表达式时,接口必须是函数式接口
基本语法:
<函数式接口> <变量名> = (参数1,参数2...) -> {//方法体}
1
2
3
说明:
(参数1,参数2…)表示参数列表;->表示连接符;{}内部是方法体
1、=右边的类型会根据左边的函数式接口类型自动推断;
2、如果形参列表为空,只需保留();
3、如果形参只有1个,()可以省略,只需要参数的名称即可;
4、如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句;
5、形参列表的数据类型会自动推断;
6、lambda不会生成一个单独的内部类文件;
7、lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错;
示例代码:
public interface LambdaTest {
abstract void print();
}
public interface LambdaTest2 {
abstract void print(String a);
}
public interface DefalutTest {
static int a =5;
default void defaultMethod(){System.out.println("DefalutTest defalut 方法");
}int sub(int a,int b);static void staticMethod() {System.out.println("DefalutTest static 方法");
}
}
public class Main {
public static void main(String[] args) {//匿名内部类--java8之前的实现方式DefalutTest dt = new DefalutTest(){@Overridepublic int sub(int a, int b) {// TODO Auto-generated method stubreturn a-b;}};//lambda表达式--实现方式1DefalutTest dt2 =(a,b)->{return a-b;};System.out.println(dt2.sub(2, 1));//lambda表达式--实现方式2,省略花括号DefalutTest dt3 =(a,b)->a-b;System.out.println(dt3.sub(5, 6));//测试finalint c = 5;DefalutTest dt4 =(a,b)->a-c;System.out.println(dt4.sub(5, 6));//无参方法,并且执行语句只有1条LambdaTest lt = ()-> System.out.println("测试无参");lt.print();//只有一个参数方法LambdaTest2 lt1 = s-> System.out.println(s);lt1.print("有一个参数");
}
}
1
局部变量修改报错如图:
若是强行修改也无法编译通过
Lambda表达式其他特性:
1、引用实例方法:
语法:
<函数式接口> <变量名> = <实例>::<实例方法名>
//调用
<变量名>.接口方法([实际参数...])
将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;
示例代码:
如我们引用PrintStream类中的println方法。我们知道System类中有一个PrintStream的实例为out,引用该实例方法:System.out::println:
public class Main {
public static void main(String[] args) {LambdaTest2 lt1 = s-> System.out.println(s);lt1.print("有一个参数");//改写为:LambdaTest2 lt2 = System.out::println;lt2.print("实例引用方式调用");
}
}
将lt2调用时的实际参数传递给了PrintStream类中的println方法,并调用该方法
2、引用类方法:
语法:
<函数式接口> <变量名> = <类>::<类方法名称>
//调用
<变量名>.接口方法([实际参数...])
将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;
示例代码:
我们可以以数组排序方式为例
public interface LambdaTest3 {
abstract void sort(List<Integer> list,Comparator<Integer> c);
}
public class Main {
public static void main(String[] args) {List<Integer> list = new ArrayList<Integer>();list.add(50);list.add(18);list.add(6);list.add(99);list.add(32);System.out.println(list.toString()+"排序之前");LambdaTest3 lt3 = Collections::sort;lt3.sort(list, (a,b) -> {return a-b;});System.out.println(list.toString()+"排序之后");
}
}
执行结果:
[50, 18, 6, 99, 32]排序之前
[6, 18, 32, 50, 99]排序之后
再来看Comparator接口,它属于函数式接口,所以我们在Comparator入参时,也采取了lambda表达式写法。
@FunctionalInterface
public interface Comparator {
…
…
…
}
3、引用类的实例方法:
定义、调用接口时,需要多传递一个参数,并且参数的类型与引用实例的类型一致
语法:
//定义接口
interface <函数式接口>{<返回值> <方法名>(<类><类名称>,[其他参数...]);
}
<函数式接口> <变量名> = <类>::<类实例方法名>
//调用
<变量名>.接口方法(类的实例,[实际参数...])
将调用方法时的传递的实际参数,从第二个参数开始(第一个参数指定的类的实例),全部传递给引用的方法,执行引用的方法;
示例代码:
public class LambdaClassTest {
public int add(int a, int b){System.out.println("LambdaClassTest类的add方法");return a+b;
}
}
public interface LambdaTest4 {
abstract int add(LambdaClassTest lt,int a,int b);
}
public class Main {
public static void main(String[] args) {LambdaTest4 lt4 = LambdaClassTest::add;LambdaClassTest lct = new LambdaClassTest();System.out.println(lt4.add(lct, 5, 8));
}
}
4、引用构造器方法:
语法:
<函数式接口> <变量名> = <类>::<new>
//调用
<变量名>.接口方法([实际参数...])
1
2
3
把方法的所有参数全部传递给引用的构造器,根据参数类型自动推断调用的构造器方法;
示例代码:
public interface LambdaTest5 {
abstract String creatString(char[] c);
}
public class Main {
public static void main(String[] args) {LambdaTest5 lt5 = String::new;System.out.println(lt5.creatString(new char[]{'1','2','3','a'}));
}
}
根据传入的参数类型,自动匹配构造函数
三、函数式接口
如果一个接口只有一个抽象方法,则该接口称之为函数式接口,因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
函数式接口可以使用Lambda表达式,lambda表达式会被匹配到这个抽象方法上
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的
示例代码:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert(“123”);
System.out.println(converted); // 123
五、Lambda 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
六、访问局部变量
我们可以直接在lambda表达式中访问外层的局部变量,但是该局部变量必须是final的,即使没有加final关键字,之后我们无论在哪(lambda表达式内部或外部)修改该变量,均报错。
七、访问对象字段与静态变量
lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的;
示例代码:
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
八、访问接口的默认方法
Predicate接口
Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
public static void main(String[] args) {Predicate<String> predicate = (s) -> s.length() > 0;System.out.println(predicate.test("foo")); // trueSystem.out.println(predicate.negate().test("foo")); // falsePredicate<Boolean> nonNull = Objects::nonNull;Predicate<Boolean> isNull = Objects::isNull;Predicate<String> isEmpty = String::isEmpty;Predicate<String> isNotEmpty = isEmpty.negate();System.out.println(nonNull.test(null));System.out.println(isNull.test(null));System.out.println(isEmpty.test("sss"));System.out.println(isNotEmpty.test(""));
}
运行结果:
true
false
false
true
false
false
Function 接口
Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):
Function<String, Integer> toInteger = Integer::valueOf;System.out.println(toInteger.apply("123").getClass());Function<String, Object> toInteger2 = toInteger.andThen(String::valueOf);System.out.println(toInteger2.apply("123").getClass());
输出:
class java.lang.Integer
class java.lang.String
Supplier 接口
Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数
Supplier personSupplier = Person::new;
personSupplier.get(); // new Person
Consumer 接口
Consumer 接口表示执行在单个参数上的操作。接口只有一个参数,且无返回值
Supplier<LambdaClassTest> personSupplier = LambdaClassTest::new;Consumer<LambdaClassTest> greeter = (lt) -> System.out.println("Hello, " + lt.getTest());greeter.accept(personSupplier.get());
Comparator 接口
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person(“John”, “Doe”);
Person p2 = new Person(“Alice”, “Wonderland”);
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Optional 接口
Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:
Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
Optional optional = Optional.of(“bam”);
optional.isPresent(); // true
optional.get(); // “bam”
optional.orElse(“fallback”); // “bam”
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // “b”
Stream 接口 重要!!!
创建stream–通过of方法
Stream integerStream = Stream.of(1, 2, 3, 5);
Stream stringStream = Stream.of(“taobao”);
1
2
创建stream–通过generator方法
生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)
Stream.generate(new Supplier() {
@Overridepublic Double get() {return Math.random();}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。
创建stream–通过iterate方法
也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。
通过Collection子类获取Stream
public interface Collection extends Iterable {
//其他方法省略default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}
}
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。
Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能:
首先我们创建一个没有重复元素的大表:
int max = 1000000;
List values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
然后我们计算一下排序这个Stream要耗时多久,
串行排序:
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format(“sequential sort took: %d ms”, millis));
// 串行耗时: 899 ms
并行排序:
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format(“parallel sort took: %d ms”, millis));
6
// 并行排序耗时: 472 ms
上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream();
stream的其他应用:
1、count()、max()、min()方法
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {List<Integer> collection = new ArrayList<Integer>();collection.add(14);collection.add(5);collection.add(43);collection.add(89);collection.add(64);collection.add(112);collection.add(55);collection.add(55);collection.add(58);//list长度System.out.println(collection.parallelStream().count());//求最大值,返回Option,通过Option.get()获取值System.out.println(collection.parallelStream().max((a,b)->{return a-b;}).get());//求最小值,返回Option,通过Option.get()获取值System.out.println(collection.parallelStream().min((a,b)->{return a-b;}).get());}
}
2、Filter 过滤方法
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {List<Integer> collection = new ArrayList<Integer>();collection.add(14);collection.add(5);collection.add(43);collection.add(89);collection.add(64);collection.add(112);collection.add(55);collection.add(55);collection.add(58);Long count =collection.stream().filter(num -> num!=null).filter(num -> num.intValue()>50).count();System.out.println(count);
}
}
3、distinct方法
去除重复
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {List<Integer> collection = new ArrayList<Integer>();collection.add(14);collection.add(5);collection.add(43);collection.add(89);collection.add(64);collection.add(112);collection.add(55);collection.add(55);collection.add(58);collection.stream().distinct().forEach(System.out::println);;
}
}
4、Sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith(“a”))
.forEach(System.out::println);
// “aaa1”, “aaa2”
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
5、Map 映射
对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {List<String> collection = new ArrayList<String>();collection.add("14");collection.add("5");collection.add("43");collection.add("89");collection.add("64");collection.add("112");collection.add("55");collection.add("55");collection.add("58");//将String转化为Integer类型collection.stream().mapToInt(Integer::valueOf).forEach(System.out::println);//或collection.stream().mapToInt(a->Integer.parseInt(a)).forEach(System.out::println);
}
}
也可以这样用:
List nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println(“sum is:”+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num * 2).
peek(System.out::println).skip(2).limit(4).sum());
27
7、limit:
对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
8、skip:
返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
9、Match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith(“a”));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith(“a”));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith(“z”));
System.out.println(noneStartsWithZ); // true
10、Count 计数
计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith(“b”))
.count();
System.out.println(startsWithB); // 3
11、Reduce 规约
这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:
Optional reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + “#” + s2);
reduced.ifPresent(System.out::println);
// “aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2”
Map
前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, “val” + i);
}
map.forEach((id, val) -> System.out.println(val));
以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。
下面的例子展示了map上的其他有用的函数:
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> “val” + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> “bam”);
map.get(3); // val33
接下来展示如何在Map里删除一个键值全都匹配的项:
map.remove(3, “val3”);
map.get(3); // val33
map.remove(3, “val33”);
map.get(3); // null
另外一个有用的方法:
map.getOrDefault(42, “not found”); // not found
对Map的元素做合并也变得很容易了:
map.merge(9, “val9”, (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, “concat”, (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。
steam在实际项目中使用的代码片段:
//1、有list集合生成以productId为key值得map集合
Map<String, List> cartManagerGroup =
carts.stream().collect(
Collectors.groupingBy(CartManager::getProductId)
);
//2、取得购物车中数量之和
IntStream is = list.stream().mapToInt((CartManager c)->c.getQuantity());
is.sum();//数量之和
//3、所有订单中商品数量*订单金额求和
orderDetailsNew.parallelStream()
.mapToDouble(orderDetailMid -> orderDetailMid.getQuantity()*orderDetailMid.getFinalPrice()).sum()
//4、过滤出指定类型的订单,并生成新的集合
orderDetails.stream().
filter(orderDetail -> StringUtil.isEmpty(orderDetail.getPromotionsType())|| !orderDetail.getPromotionsType().equals(PromotionTypeEnum.ORDERGIFTPROMOTION.getType())).collect(Collectors.toList());
//5、过滤购物车未被选中商品并生成新的list
carts.stream().filter(cart -> cart.getSelectFlag()==1).collect(Collectors.toList());
//6、将list以商品促销委key转化为map
Map<String,List> map =
promotionsGiftProducts.stream().collect( Collectors.groupingBy(PromotionsGiftProduct::getPromotionId));
//7、从list中分离出只存储productId的列表list
List productIds = needUpdate.parallelStream()
.map(CartManager::getProductId)
.collect(Collectors.toList());
九、Date API
Java 8 在包java.time下包含了一组全新的时间日期API。
Clock 时钟
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
1
2
3
4
Timezones 时区
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of(“Europe/Berlin”);
ZoneId zone2 = ZoneId.of(“Brazil/East”);
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
1
2
3
4
5
6
7
8
LocalTime 本地时间
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse(“13:37”, germanFormatter);
System.out.println(leetTime); // 13:37
LocalDate 本地日期
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
从字符串解析一个LocalDate类型和解析LocalTime一样简单:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse(“24.12.2014”, germanFormatter);
System.out.println(xmas); // 2014-12-24
LocalDateTime 本地日期时间
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern(“MMM dd, yyyy - HH:mm”);
LocalDateTime parsed = LocalDateTime.parse(“Nov 03, 2014 - 07:13”, formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
关于时间日期格式的详细信息:
http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html
十、Annotation 注解
在Java 8中支持多重注解了
————————————————
一、Lambda表达式
1、Lambda表达式是什么?
Lambda是一个匿名函数,我们可以将Lambda表达式理解为一段可以传递的代码(将代码像数据一样传递)。使用它可以写出简洁、灵活的代码。作为一种更紧凑的代码风格,使java语言表达能力得到提升。
2、从匿名类到Lambda转换
package com.chen.test.JAVA8Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Demo01 {
private static Logger log = LoggerFactory.getLogger(Demo01.class);
public static void main(String[] args) {
Runnable t1 =new Runnable(){
@Override
public void run(){
log.info(“我是没有使用Lambda表达式:不简洁”);
}
};
Runnable t2 = () -> log.info("我是使用Lambda表达式:简洁、灵活");t1.run();t2.run();}
}
Run result
19:43:39.303 [main] INFO com.chen.test.JAVA8Features.Demo01 - 我是没有使用Lambda表达式:不简洁、代码多
19:43:39.303 [main] INFO com.chen.test.JAVA8Features.Demo01 - 我是使用Lambda表达式:简洁、灵活
Process finished with exit code 0
3、Lambda表达式语法
Lambda表达式在java语言中引入了一种新的语法元素和操作。这种操作符号为“->”,Lambda操作符或箭头操作符,它将Lambda表达式分割为两部分。 左边:指Lambda表达式的所有参数 右边:指Lambda体,即表示Lambda表达式需要执行的功能。
六种语法格式:
1、语法格式一:无参数、无返回值,只需要一个Lambda体
package com.chen.test.JAVA8Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Demo02 {
private static Logger log = LoggerFactory.getLogger(Demo02.class);
public static void main(String[] args) {
Runnable t1 = ()-> log.info(“Lambda表达式:简洁、灵活,优雅永不过时”);
t1.run();
}
}
run result
22:22:39.125 [main] INFO com.chen.test.JAVA8Features.Demo02 - Lambda表达式:简洁、灵活,优雅永不过时
Process finished with exit code 0
2、语法格式二:lambda有一个参数、无返回值
package com.chen.test.JAVA8Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Consumer;
public class Demo03 {
private static Logger log = LoggerFactory.getLogger(Demo03.class);
public static void main(String[] args) {
Consumer consumer = new Consumer() {
@Override
public void accept(String s) {
log.info(s);
}
};
consumer.accept(“爱与被爱的区别”);
Consumer consumer1 = (s) -> log.info(s);
consumer1.accept(“接受爱不一定爱对方,爱一定付出真心爱”);
}
}
run result
23:03:08.992 [main] INFO com.chen.test.JAVA8Features.Demo03 - 爱与被爱的区别
23:03:09.142 [main] INFO com.chen.test.JAVA8Features.Demo03 - 接受爱不一定爱对方,爱一定付出真心爱
Process finished with exit code 0
3、语法格式三:Lambda只有一个参数时,可以省略()
package com.chen.test.JAVA8Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Consumer;
public class Demo04 {
private static Logger log = LoggerFactory.getLogger(Demo04.class);
public static void main(String[] args) {
Consumer consumer = s -> log.info(s);
consumer.accept(“Lambda只有一个参数时,可以省略()”);
}
}
run result
23:08:27.295 [main] INFO com.chen.test.JAVA8Features.Demo04 - Lambda只有一个参数时,可以省略()
Process finished with exit code 0
4、语法格式四:Lambda有两个参数时,并且有返回值
package com.chen.test.JAVA8Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
public class Demo05 {
private static Logger log = LoggerFactory.getLogger(Demo05.class);
public static void main(String[] args) {
CompareOldMethod(12,10);
findMaxValue(12,10);
findMinValue(12,10);
}
// 没有使用Lambda表达式比较大小
public static void CompareOldMethod(int num1,int num2){
Comparator comparator = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
log.info(“o1:{}”,o1);
log.info(“o2:{}”,o2);
return o1 < o2 ? o2 : o1;
}
};
log.info(“OldFindMaxValue:{}”,comparator.compare(num1,num2));
}
// 使用lambda表达式
public static void findMaxValue(int num1,int num2){
Comparator comparatorMax = (o1, o2) ->{
log.info(“o1:{}”,o1);
log.info(“o2:{}”,o2);
return (o1<o2)? o2 :(o1);
};
log.info(“findMaxValue:{}”,(comparatorMax.compare(num1,num2)));
}
public static void findMinValue(int num1,int num2){
Comparator comparatorMin = (o1, o2) -> {
log.info(“o1:{}”,o1);
log.info(“o2:{}”,o2);
return (o1 < o2) ? o1 : o2;
};
log.info(“FindMinValue:{}”,comparatorMin.compare(num1,num2));
}
}
run result
00:17:10.206 [main] INFO com.chen.test.JAVA8Features.Demo05 - o1:12
00:17:10.206 [main] INFO com.chen.test.JAVA8Features.Demo05 - o2:10
00:17:10.206 [main] INFO com.chen.test.JAVA8Features.Demo05 - OldFindMaxValue:12
00:17:10.315 [main] INFO com.chen.test.JAVA8Features.Demo05 - o1:12
00:17:10.315 [main] INFO com.chen.test.JAVA8Features.Demo05 - o2:10
00:17:10.315 [main] INFO com.chen.test.JAVA8Features.Demo05 - findMaxValue:12
00:17:10.315 [main] INFO com.chen.test.JAVA8Features.Demo05 - o1:12
00:17:10.315 [main] INFO com.chen.test.JAVA8Features.Demo05 - o2:10
00:17:10.315 [main] INFO com.chen.test.JAVA8Features.Demo05 - FindMinValue:10
Process finished with exit code 0
5、语法格式五:当Lambda体只有一条语句的时候,return和{}可以省略掉
package com.chen.test.JAVA8Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
public class Demo05 {
private static Logger log = LoggerFactory.getLogger(Demo05.class);
public static void main(String[] args) {
findMaxValue(12,10);
findMinValue(12,10);
}
// 使用lambda表达式
public static void findMaxValue(int num1,int num2){
Comparator comparatorMax = (o1, o2) ->{
log.info(“o1:{}”,o1);
log.info(“o2:{}”,o2);
return (o1<o2)? o2 :(o1);
};
log.info(“findMaxValue:{}”,(comparatorMax.compare(num1,num2)));
}
public static void findMinValue(int num1,int num2){
Comparator comparatorMin = (o1, o2) -> (o1 < o2) ? o1 : o2;
log.info(“FindMinValue:{}”,comparatorMin.compare(num1,num2));
}
}
run result
00:22:31.059 [main] INFO com.chen.test.JAVA8Features.Demo05 - o1:12
00:22:31.075 [main] INFO com.chen.test.JAVA8Features.Demo05 - o2:10
00:22:31.075 [main] INFO com.chen.test.JAVA8Features.Demo05 - findMaxValue:12
00:22:31.075 [main] INFO com.chen.test.JAVA8Features.Demo05 - FindMinValue:10
Process finished with exit code 0
6、语法格式六:类型推断:数据类型可以省略,因为编译器可以推断得出,成为“类型推断”
package com.chen.test.JAVA8Features;
import com.mysql.cj.callback.MysqlCallbackHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
public class Demo07 {
private static Logger log = LoggerFactory.getLogger(Demo07.class);
public static void main(String[] args) {
dateType();
}
public static void dateType(){
Consumer consumer = (String s) -> log.info(s);
consumer.accept(“Hello World !”);
Consumer consumer1 = (s) -> log.info(s);
consumer1.accept(“Hello don’t date type !”);
}
}
二、函数式接口
1、什么是函数式接口?
函数式接口:只包含一个抽象方法的接口,称为函数式接口,并且可以使用lambda表达式来创建该接口的对象,可以在任意函数式接口上使用@FunctionalInterface注解,来检测它是否是符合函数式接口。同时javac也会包含一条声明,说明这个接口是否符合函数式接口。
2、自定义函数式接口
package com.chen.test.JAVA8Features;
@FunctionalInterface
public interface FunctionDemo1 {
public void fun();
}
3、泛型函数式接口
package com.chen.test.JAVA8Features;
@FunctionalInterface
public interface FunctionGeneric {
public void fun(T t);
}
4、java内置函数式接口
(Function、Consumer、Supplier、Predicate) java.util.function
Function (函数型接口)
函数型接口:有输入参数,也有返回值。
- @param the type of the input to the function
- @param the type of the result of the function
- @since 1.8
/
@FunctionalInterface
public interface Function<T, R> {
/*- Applies this function to the given argument.
- @param t the function argument
- @return the function result
*/
R apply(T t);
其中T表示输入参数,R为返回值
代码展示:
public void functionTest(){
// Function function = new Function<String,String>(){
// @Override
// public String apply(String s) {
// return s;
// }
// };
// log.info(“函数型接口 :{}”,function.apply(“没有使用Lambda表达式”));
Function function = s -> s;
log.info(“函数型接口:{}”,function.apply(“Function Demo”));
}
Consumer(消费型接口)
消费型接口:有入参,没有会有返回值
- @param the type of the input to the operation
- @since 1.8
/
@FunctionalInterface
public interface Consumer {
/*- Performs this operation on the given argument.
- @param t the input argument
*/
void accept(T t);
代码展示:
public void consumerTest(){
// 非Lambda表达式
// Consumer consumer = new Consumer() {
// @Override
// public void accept(String s) {
// log.info(s);
// }
// };
// consumer.accept(“消费型函数:没有使用Lambda表达式”);
// 使用Lambda表达式
Consumer consumer = s -> log.info(s);
consumer.accept(“消费型函数:Consumer Demo”);
}
Supplier(供给型接口)
供给型接口:没有输入参数,有返回值
- @param the type of results supplied by this supplier
- @since 1.8
/
@FunctionalInterface
public interface Supplier {
/*- Gets a result.
- @return a result
*/
T get();
}
代码展示:
public void supplierTest(){
// 非Lambda表达式
// Supplier supplier = new Supplier(){
// @Override
// public String get() {
// return “供给型接口:没有使用Lambda表达式”;
// }
// };
// log.info(String.valueOf(supplier.get()));
Supplier supplier = () -> “供给型接口:Supplier Demo”;
log.info(String.valueOf(supplier.get()));
}
Predicate(断定型接口)
断言型接口:既有输入参数也有返回值,返回类型是boolean类型
- @param the type of the input to the predicate
- @since 1.8
/
@FunctionalInterface
public interface Predicate {
/*- Evaluates this predicate on the given argument.
- @param t the input argument
- @return {@code true} if the input argument matches the predicate,
- otherwise {@code false}
*/
boolean test(T t);
展示代码:
public void predicateTest() {
// Predicate predicate = new Predicate() {
// @Override
// public boolean test(String s) {
// return s.equals(“Predicate Demo”);
// }
// };
// log.info(“断言型接口:{}”,predicate.test(“没有使用Lambda表达式”));
Predicate predicate = s -> s.equals(“Predicate Demo”);
log.info(“断言型接口:{}”,predicate.test(“Predicate Demo”));
}
java内置四种大函数式接口,可以使用Lambda表达式
package com.chen.test.JAVA8Features;
import com.google.common.base.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class FunDemo01 {
private static Logger log = LoggerFactory.getLogger(FunDemo01.class);
public static void main(String[] args) {
FunDemo01 demo01 = new FunDemo01();
demo01.functionTest();
demo01.consumerTest();
demo01.supplierTest();
demo01.predicateTest();
}
public void functionTest(){
// 非Lambda表达式
// Function function = new Function<String,String>(){
// @Override
// public String apply(String s) {
// return s;
// }
// };
// log.info(“函数型接口 :{}”,function.apply(“没有使用Lambda表达式”));
Function function = s -> s;
log.info(“函数型接口:{}”,function.apply(“Function Demo”));
}
public void consumerTest(){
// 非Lambda表达式
// Consumer consumer = new Consumer() {
// @Override
// public void accept(String s) {
// log.info(s);
// }
// };
// consumer.accept(“消费型函数:没有使用Lambda表达式”);
// 使用Lambda表达式
Consumer consumer = s -> log.info(s);
consumer.accept(“消费型函数:Consumer Demo”);
}
public void supplierTest(){
// 非Lambda表达式
// Supplier supplier = new Supplier(){
// @Override
// public String get() {
// return “供给型接口:没有使用Lambda表达式”;
// }
// };
// log.info(String.valueOf(supplier.get()));
Supplier supplier = () -> “供给型接口:Supplier Demo”;
log.info(String.valueOf(supplier.get()));
}
public void predicateTest() {
// Predicate predicate = new Predicate() {
// @Override
// public boolean test(String s) {
// return s.equals(“Predicate Demo”);
// }
// };
// log.info(“断言型接口:{}”,predicate.test(“没有使用Lambda表达式”));
Predicate predicate = s -> s.equals(“Predicate Demo”);
log.info(“断言型接口:{}”,predicate.test(“Predicate Demo”));
}
}
三、方法引用和构造器引用
1、方法引用
当要传递给Lambda体的操作已经有实现方法,可以直接使用方法引用(实现抽象方法的列表,必须要和方法引用的方法参数列表一致)
方法引用:使用操作符“::”将方法名和(类或者对象)分割开来。
有下列三种情况:
对象::实例方法
类::实例方法
类::静态方法
代码展示:
package com.chen.test.JAVA8Features;
public class MethodRefDemo {
public static void main(String[] args) {
FunctionGeneric strName = s -> System.out.println(s);
strName.fun(“Lambda表达式没有使用方法引用”);
//方法引用FunctionGeneric<String> strName2 = System.out::println;strName2.fun("使用方法引用");
}
}
2、构造器引用
本质上:构造器引用和方法引用相识,只是使用了一个new方法
使用说明:函数式接口参数列表和构造器参数列表要一致,该接口返回值类型也是构造器返回值类型
格式:ClassName :: new
代码展示:
package com.chen.test.JAVA8Features;
import java.util.function.Function;
public class MethodRefDemo {
public static void main(String[] args) {
//构造器引用
Function<String, Integer> fun1 = (num) -> new Integer(num);
Function<String, Integer> fun2 = Integer::new;
//数组引用
Function<Integer,Integer[]> fun3 = (num) ->new Integer[num];
Function<Integer,Integer[]> fun4 = Integer[]::new;
}
}
四、强大的Stream API
1、什么是Stream?
Java8中两个最为重要特性:第一个的是Lambda表达式,另一个是Stream API。
StreamAPI它位于java.util.stream包中,StreamAPI帮助我们更好地对数据进行集合操作,它本质就是对数据的操作进行流水线式处理,也可以理解为一个更加高级的迭代器,主要作用是遍历其中每一个元素。简而言之,StreamAP提供了一种高效且易于使用的处理数据方式。
2、Stream特点:
1、Stream自己不会存储数据。
2、Stream不会改变源对象。相反,它们会返回一个持有结果的新Stream对象
3、Stream操作时延迟执行的。这就意味着它们等到有结果时候才会执行。
和list不同,Stream代表的是任意Java对象的序列,且stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。它可以“存储”有限个或无限个元素。
例如:我们想表示一个全体自然数的集合,使用list是不可能写出来的,因为自然数是无线的,不管内存多大也没法放到list中,但是使用Sream就可以
3、Stream操作的三个步骤?
1、创建Stream:一个数据源(例如:set 、list),获取一个流
2、中间操作:一个中间操作连接,对数据源的数据进行处理
3、终止操作:一个终止操作,执行中间操作连,产生结果。
1、创建流
创建流方式有多种:
第一种:通过集合
对于Collection接口(List 、Set、Queue等)直接调用Stream()方法可以获取Stream
List<String> list = new ArrayList<>();Stream<String> stringStream = list.stream(); //返回一个顺序流Stream<String> parallelStream = list.parallelStream(); //返回一个并行流(可多线程)
第二种:通过数组
把数组变成Stream使用Arrays.stream()方法
Stream<String> stream1 = Arrays.stream(new String[]{"CBB", "YJJ", "CB", "CJJ"});
第三种:Stream.of()静态方法直接手动生成一个Stream
Stream<String> stream = Stream.of("A", "B", "C", "D");
第四种:创建无限流
//迭代//遍历10个奇数Stream.iterate(1,t->t+2).limit(10).forEach(System.out::println);
//生成
//生成10个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
第五种:自己构建
第六种:其他等等
2、中间操作
一个流可以后面跟随着0个或者多个中间操作,其目的是打开流,做出某种程度的数据过滤、去重、排序、映射、跳过等。然后返回一个新的流,交给下一个使用,仅仅是调用这个方法,没有真正开始遍历。
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
3、终止操作:一个终止操作,执行中间操作连,产生结果。
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
package com.chen.test.JAVA8Features.Stream;
import java.util.*;
import java.util.stream.Collectors;
public class StreamDemo01 {
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
//map
List collect = list.stream().map(n -> n * 2).collect(Collectors.toCollection(ArrayList::new));
collect.forEach(System.out::println);
//filer 过滤
List list1 = list.stream().filter(n -> n % 2 == 0).collect(Collectors.toList());
list1.forEach(System.out::println);
//distinct 去重
List list2 = list.stream().distinct().collect(Collectors.toList());
list2.forEach(System.out::println);
//skip 跳过
List list3 = list.stream().skip(3).collect(Collectors.toList());
list3.forEach(System.out::println);
//limit 截取
Set set = list.stream().limit(3).collect(Collectors.toSet());
set.forEach(System.out::println);
//skip and limit 组合使用
List list4 = list.stream().skip(3).limit(5).collect(Collectors.toList());
list4.forEach(System.out::println);
}
}
六、新时间日期接口
七、Optional类
optional类是一个容器,代表一个值存在或者不存在,原来使用null表示一个值存不存在,现在使用optional可以更好的表达这个概念,并且可以避免空指针异常。
Optional常用的方法:
Optional.of(T t) : 创建一个Optional实例;
Optional.empty() : 创建一个空的Optional实例;
Optional.ofNullable(T t) :若t不为空创建一个Optional实例否则创建一个空实例;
isPresent() : 判断是否包含值;
orElse(T t) :如果调用对象包含值,返回该值,否则返回t;
orElseGet(Supplier s) : 如果调用对象包含值,返回该值,否则返回s获取的值;
map(Function f) : 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();
flatMap(Function mapper) : 与map类似,要求返回值必须是Optional。
————————————————
Java函数式设计
实现方法:
@FunctionalInterface接口
Lambda语法
方法引用
接口default方法实现
一、lambda表达式
lambda表达式为匿名内部类的简写,类似于匿名内部类的语法糖;但又区别于匿名内部类(后文会讲解)。
匿名内部类特点:
基于多态(多数基于接口编程)
实现类无需名称
允许多个抽象方法
Lambda的语法简洁,没有面向对象复杂的束缚。
特点:
使用Lambda必须有接口,并且接口中有且仅有一个抽象方法。
只有当接口中的抽象方法存在且唯一时,才可以使用Lambda,但排除接口默认方法以及声明中覆盖Object的公开方法。
使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
标准格式由三部分组成:
一些参数
一个箭头
一段代码
格式:
(参数列表)->{一些重要方法的代码};
():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数用逗号分隔。
->:传递:把参数传递给方法体{}
{}:重写接口的抽象方法的方法体
箭头操作符的左侧对应接口中参数列表(lambda表达式的参数列表),
箭头右侧为该抽象方法的实现(lambda表达式所需执行的功能)。
lambda优化写法:
可以推导的,都可以省略(凡是能根据上下文推导出来的内容,都可以省略不写):
(参数列表):括号中参数列表的数据类型,可以省略不写
(参数列表):括号中的参数只有一个,那么类型和()都可以省略
{一些代码} :如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},return,分号 必须一起省略
public class MyLambda {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+“新线程创建了”);
}
}).start();
//使用Lambdanew Thread(()->{System.out.println(Thread.currentThread().getName()+"新线程创建了");}).start();//优化lambdanew Thread(()->System.out.println(Thread.currentThread().getName()+"新线程创建了")).start();
}
}
1.1无参数,无返回
()->System.out.println(“hello lambda”)
Cook:
public interface Cook {
public abstract void makeFood();
}
Demo01Cook:
public class Demo01Cook {
public static void main(String[] args) {invokeCook(new Cook() {public void makeFood() {System.out.println("做饭。。。");}});//使用LambdainvokeCook(()->{System.out.println("做饭。。。");});//优化lambdainvokeCook(()-> System.out.println("做饭。。。"));
}public static void invokeCook(Cook cook){cook.makeFood();
}
}
1.2 有参数,无返回
x->System.out.println(“hello lambda”)
import java.util.function.Consumer;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class Demo2 {public static void main(String[] args) {
Consumer consumer = x-> System.out.println(x);
consumer.accept(“有参数无返回”);
}
}
1.3 有参数,有返回
(Person p1,Person p2)->{ return p1.getAge()-p2.getAge(); }
package com.hcx.lambda;
import java.util.Arrays;
import java.util.Comparator;
/**
-
Created by hongcaixia on 2019/10/26.
*/
public class MyArrays {public static void main(String[] args) {
Person[] arr = {new Person(“陈奕迅”,40),
new Person(“钟汉良”,39),
new Person(“杨千嬅”,38)};//对年龄进行排序Arrays.sort(arr, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge()-o2.getAge();}});//使用lambdaArrays.sort(arr,(Person p1,Person p2)->{return p1.getAge()-p2.getAge();});//优化lambdaArrays.sort(arr,(p1,p2)->p1.getAge()-p2.getAge());Arrays.sort(arr,Comparator.comparing(Person::getAge));for (Person p:arr){System.out.println(p);}
}
}
二、函数式接口
2.1概念
函数式接口:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
2.2格式
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
抽象方法的 public abstract 是可以省略的:
public interface MyFunctionalInterface {
void myMethod();
}
2.3@FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。用于函数式接口类型声明的信息注解类型,这些接口的实例被Lambda表达式、方法引用或构造器引用创建。函数式接口只能有一个抽象方法,但排除接口默认方法以及声明中覆盖Object的公开方法:
@FunctionalInterface注解源码注释:
源码注释:该注解是定义一个lambda表达式的基础, 即是否是函数式接口可以标注也可以不标注。
函数式接口必须有一个精确的抽象方法,但排除以下两种:
①java8的default,他们不属于抽象方法。
②如果该接口声明的一个抽象方法覆盖了任意一个object的方法,也排除掉。
@FunctionalInterface
public interface MyFunctionInterface {
//唯一个抽象方法
void method();
//排除用default修饰的方法
default void method1(){
}
//排除Ojbect下的方法
int hashCode();
}
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
注意:@FuncationlInterface不能标注在注解、类以及枚举上。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
MyFunctionalInterface:
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法
public abstract void method();
}
MyFunctionalInterfaceImpl:
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
@Override
public void method() {
}
}
Demo:
/*
函数式接口的使用:一般可以作为方法的参数和返回值类型
*/
public class Demo {
//定义一个方法,参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface myInter){
myInter.method();
}
public static void main(String[] args) {//调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象show(new MyFunctionalInterfaceImpl());//调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类show(new MyFunctionalInterface() {@Overridepublic void method() {System.out.println("使用匿名内部类重写接口中的抽象方法");}});//调用show方法,方法的参数是一个函数式接口,所以我们可以Lambda表达式show(()->{System.out.println("使用Lambda表达式重写接口中的抽象方法");});//简化Lambda表达式show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}
}
案例:
分析:函数式接口和普通方法的差异
import java.util.function.Supplier;
/**
-
Created by hongcaixia on 2019/11/3.
*/
public class MyTest {
public static void main(String[] args) {
print1(“hello,world”);
print2(()->“hello world”);
}public static void print1(String message){
System.out.println(message);
}public static void print2(Supplier message){
System.out.println(message.get());
}
}
以上代码会得到同样的结果,但使用了函数式接口相当于把数据进行了延迟加载。使用函数式接口,数据并没有完全确定,等到真正调用的时候才确定,类似推模型。
Demo01Logger:
public class Demo01Logger {
//定义一个根据日志的级别,显示日志信息的方法
public static void showLog(int level, String message){
//对日志的等级进行判断,如果是1级别,那么输出日志信息
if(level==1){
System.out.println(message);
}
}
public static void main(String[] args) {//定义三个日志信息String msg1 = "Hello";String msg2 = "World";String msg3 = "Java";//调用showLog方法,传递日志级别和日志信息showLog(2,msg1+msg2+msg3);}
}
Demo02Lambda:
/*
使用Lambda优化日志案例
Lambda的特点:延迟加载
Lambda的使用前提,必须存在函数式接口
*/
public class Demo02Lambda {
//定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口
public static void showLog(int level, MessageBuilder mb){
//对日志的等级进行判断,如果是1级,则调用MessageBuilder接口中的builderMessage方法
if(level==1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {//定义三个日志信息String msg1 = "Hello";String msg2 = "World";String msg3 = "Java";//调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以传递Lambda表达式/*showLog(2,()->{//返回一个拼接好的字符串return msg1+msg2+msg3;});*/showLog(1,()->{System.out.println("不满足条件不执行");//返回一个拼接好的字符串return msg1+msg2+msg3;});
}
}
MessageBuilder:
@FunctionalInterface
public interface MessageBuilder {
//定义一个拼接消息的抽象方法,返回被拼接的消息
public abstract String builderMessage();
}
分析:使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中,只有满足条件,日志的等级是1级才会调用接口MessageBuilder中的方法builderMessage,才会进行字符串的拼接;
如果条件不满足,日志的等级不是1级,那么MessageBuilder接口中的方法builderMessage也不会执行,所以拼接字符串的代码也不会执行,所以不会存在性能的浪费
2.4使用函数式接口作为方法的参数
Demo01Runnable:
public class Demo01Runnable {
//定义一个方法startThread,方法的参数使用函数式接口Runnable
public static void startThread(Runnable run){
//开启多线程
new Thread(run).start();
}
public static void main(String[] args) {//调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类startThread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");}});//调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式startThread(()->{System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");});//优化Lambda表达式startThread(()->System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了"));
}
}
2.5使用函数式接口作为方法的返回值
Demo02Comparator:
import java.util.Arrays;
import java.util.Comparator;
/*
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。
当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取。
*/
public class Demo02Comparator {
//定义一个方法,方法的返回值类型使用函数式接口Comparator
public static Comparator getComparator(){
//方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
/return new Comparator() {
@Override
public int compare(String o1, String o2) {
//按照字符串的降序排序
return o2.length()-o1.length();
}
};/
//方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式/*return (String o1, String o2)->{//按照字符串的降序排序return o2.length()-o1.length();};*///继续优化Lambda表达式return (o1, o2)->o2.length()-o1.length();
}public static void main(String[] args) {//创建一个字符串数组String[] arr = {"a","bb","ccc","dddd"};//输出排序前的数组System.out.println(Arrays.toString(arr));//调用Arrays中的sort方法,对字符串数组进行排序Arrays.sort(arr,getComparator());//输出排序后的数组System.out.println(Arrays.toString(arr));
}
}
每次都要声明一个接口,写一个抽象方法,然后再用这个接口作为参数去用lambda实现。。。当果不需要!这个新特性就是为了我们使用简单的呀,所以java已经内置了一堆函数式接口了。
先来个表格整体概览一下常用的一些:
函数式接口 参数类型 返回类型 用途
Supplier 供给型 无 T 返回类型为T的对象,方法:T get()
Consumer消费型 T void 对类型为T的对象应用操作,方法:void accept(T t)
Predicate 断定型 T boolean 确定类型为T的对象是否满足某种约束,返回布尔值,方法:boolean test(T t)
Function<T,R>函数型 T R 对类型为T的对象应用操作,并返回R类型的对象,方法:R apply(T,t)
2.6常用函数式接口
①提供类型:Supplier接口
特点:只出不进,作为方法/构造参数、方法返回值
java.util.function.Supplier接口仅包含一个无参的方法:T get()。
用来获取一个泛型参数指定类型的对象数据。
Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
import java.util.function.Supplier;
/**
-
Created by hongcaixia on 2019/10/29.
*/
public class MySupplier {public static String getString(Supplier supplier){
return supplier.get();
}public static void main(String[] args) {
getString(new Supplier() {
@Override
public String get() {
return null;
}
});String s = getString(()->"Eason");System.out.println(s);
}
}
GetMax:
import java.util.function.Supplier;
/**
-
Created by hongcaixia on 2019/10/29.
*/
public class GetMax {public static int getMaxNum(Supplier supplier){
return supplier.get();
}public static void main(String[] args) {
int[] arr = {-1,0,1,2,3};
int maxValue = getMaxNum(()->{
int max = arr[0];
for(int i=0;i<arr.length;i++){
if(arr[i]>max){
max = arr[i];
}
}
return max;
});
System.out.println(“数组元素的最大值是:”+maxValue);
}
}
②消费类型:Consumer接口
特点:只进不出,作为方法/构造参数
java.util.function.Consumer接口则正好与Supplier接口相反,
它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用),需要自定义
import java.util.function.Consumer;
/**
-
Created by hongcaixia on 2019/10/29.
*/
public class MyConsumer {public static void method(String name, Consumer consumer){
consumer.accept(name);
}public static void main(String[] args) {
method(“小哇”, new Consumer() {
@Override
public void accept(String s) {
System.out.println(“s是:”+s);
}
});method("小哇",(name)->{String s = new StringBuffer(name).reverse().toString();System.out.println("s是:"+s);});
}
}
andThen:
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
import java.util.function.Consumer;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class AndThen {public static void method(String s, Consumer consumer1,Consumer consumer2){
// consumer1.accept(s);
// consumer2.accept(s);
//使用andThen方法,把两个Consumer接口连接到一起,在消费数据
//con1连接con2,先执行con1消费数据,在执行con2消费数据
consumer1.andThen(consumer2).accept(s);
}public static void main(String[] args) {
method(“Hello”,
(t)-> System.out.println(t.toUpperCase()), //消费方式:把字符串转换为大写输出
(t)-> System.out.println(t.toLowerCase()));//消费方式:把字符串转换为小写输出method("Hello", new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s.toUpperCase());}},new Consumer<String>() {@Overridepublic void accept(String s1) {System.out.println(s1.toUpperCase());}});
}
}
按照格式“姓名:XX。性别:XX。”的格式将信息打印
要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,
将打印性别的动作作为第二个Consumer接口的Lambda实例,
将两个Consumer接口按照顺序“拼接”到一起。
public class DemoTest {
//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
public static void printInfo(String[] arr, Consumer con1,Consumer con2){
//遍历字符串数组
for (String message : arr) {
//使用andThen方法连接两个Consumer接口,消费字符串
con1.andThen(con2).accept(message);
}
}
public static void main(String[] args) {//定义一个字符串类型的数组String[] arr = { "陈奕迅,男", "钟汉良,男", "胡歌,男" };//调用printInfo方法,传递一个字符串数组,和两个Lambda表达式printInfo(arr,(message)->{//消费方式:对message进行切割,获取姓名,按照指定的格式输出String name = message.split(",")[0];System.out.print("姓名: "+name);},(message)->{//消费方式:对message进行切割,获取年龄,按照指定的格式输出String age = message.split(",")[1];System.out.println(";年龄: "+age+"。");});
}
}
③断定类型:Predicate接口
特点:boolean类型判断,作为方法/构造参数
java.util.function.Predicate接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果:
符合条件,返回true
不符合条件,返回false
import java.util.function.Predicate;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class MyPredicate1 {public static boolean validateStr(String str, Predicate predicate){
return predicate.test(str);
}public static void main(String[] args) {
String str = “abcdef”;
boolean b = validateStr(str,string->str.length()>5);
System.out.println(b);boolean b1 = validateStr(str, new Predicate<String>() {@Overridepublic boolean test(String s) {return s.length()>5;}});System.out.println(b1);
}
}
and方法:
Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
default Predicate and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> this.test(t) && other.test(t);
}
MyPredicateAnd :
import java.util.function.Predicate;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class MyPredicateAnd {public static boolean validateStr(String str, Predicate pre1,Predicate pre2){
// return pre1.test(str) && pre2.test(str);
return pre1.and(pre2).test(str);
}public static void main(String[] args) {
String s = “abcdef”;
boolean b = validateStr(s,str->str.length()>5,str->str.contains(“a”));
System.out.println(b);
}
}
or方法:
Predicate接口中有一个方法or,表示或者关系,也可以用于连接两个判断条件default Predicate or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
MyPredicateOr:
package com.hcx.lambda;
import java.util.function.Predicate;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class MyPredicateOr {
public static boolean validateStr(String s, Predicate pre1,Predicate pre2){
// return pre1.test(s) || pre2.test(s);
return pre1.or(pre2).test(s);
}public static void main(String[] args) {
String s = “acdef”;
boolean b = validateStr(s,str->str.length()>5,str->str.contains(“a”));validateStr(s, new Predicate<String>() {@Overridepublic boolean test(String str) {return s.length()>5;}}, new Predicate<String>() {@Overridepublic boolean test(String str) {return s.contains("a");}});System.out.println(b);
}
}
negate方法:
Predicate接口中有一个方法negate,也表示取反的意思
default Predicate<T> negate() {return (t) -> !test(t);
}
MyPredicateNegate:
import java.util.function.Predicate;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class MyPredicateNegate {public static boolean validateStr(String s, Predicate pre){
// return !pre.test(s);
return pre.negate().test(s);
}public static void main(String[] args) {
String s = “acde”;
boolean b = validateStr(s,str->str.length()>5);System.out.println(b);
}
}
MyTest:
package com.hcx.lambda;
import java.util.ArrayList;
import java.util.function.Predicate;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class MyTest {public static ArrayList filter(String[] arr, Predicate pre1, Predicate pre2) {
ArrayList list = new ArrayList<>();
for (String s : arr) {
boolean b = pre1.and(pre2).test(s);
if (b) {
list.add(s);
}
}
return list;
}public static void main(String[] args) {
String[] array = {“迪丽热巴,女”, “古力娜扎,女”, “佟丽娅,女”, “赵丽颖,女”};
ArrayList list = filter(array,
s -> s.split(“,”)[1].equals(“女”),
s -> s.split(“,”)[0].length() == 4);
for(String s : list){
System.out.println(s);
}
}
}
④转换类型:Function接口
特点:有输入,有输出
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用的场景例如:将String类型转换为Integer类型。
package com.hcx.lambda;
import java.util.function.Function;
/**
-
Created by hongcaixia on 2019/10/30.
*/
public class MyFunction {public static void change(String str, Function<String,Integer> function){
// Integer i = function.apply(str);
//自动拆箱 Integer自动转为int
int i = function.apply(str);
System.out.println(i);
}public static void main(String[] args) {
String s = “1234”;
change(s,str->Integer.parseInt(str));int i = Integer.parseInt(s);System.out.println(i);
}
}
andThen方法:
package com.hcx.lambda;
import java.util.function.Function;
/**
-
1.String转Integer,加10
-
Function<String,Integer> fun1 :Integer i = fun1.apply("123")+10;
-
2.Interger转String
-
Function<Integer,String> fun2 :String s = fun2.apply(i);
-
Created by hongcaixia on 2019/10/31.
*/
public class MyFunctionTest {public static void change(String str, Function<String,Integer> fun1,Function<Integer,String> fun2){
String string = fun1.andThen(fun2).apply(str);
System.out.println(string);
}public static void main(String[] args) {
change(“123”,str->Integer.parseInt(str)+10,i->i+“”);
}
}
自定义函数模型拼接Demo:
package com.hcx.lambda;
import java.util.function.Function;
/**
-
String str = “赵丽颖,20”;
-
1.将字符串截取数字年龄部分,得到字符串;
-
2.将上一步的字符串转换成为int类型的数字;
-
3.将上一步的int数字累加100,得到结果int数字。
-
Created by hongcaixia on 2019/10/31.
*/
public class MyFunctionTest2 {public static int change(String str, Function<String,String> fun1,Function<String,Integer> fun2,
Function<Integer,Integer> fun3){
return fun1.andThen(fun2).andThen(fun3).apply(str);
}public static void main(String[] args) {
int num = change(“赵丽颖,32”,str->str.split(“,”)[1],
str->Integer.parseInt(str),
i->i+100);
System.out.println(num);
}
}
注意:使用匿名内部类会编译后会多产生一个类,而使用lambda,底层是invokedynamic指令,不会有多余的类
三、方法引用
若lambda体中的内容,有方法已经实现了,则可以使用方法引用。方法引用是对lambda的简化
MyPrintable:
public interface MyPrintable {
void print(String str);
}
DemoPrint:
public class DemoPrint {
private static void printString(MyPrintable data){data.print("Hello,World");
}public static void main(String[] args) {printString(s->System.out.println(s));printString(new MyPrintable() {@Overridepublic void print(String str) {System.out.println(str);}});
}
}
改进:
public class DemoPrint {
private static void printString(MyPrintable data){data.print("Hello,World");
}public static void main(String[] args) {//printString(s->System.out.println(s));printString(System.out::println);
}
}
方法引用
双冒号::为引用运算符,而它所在的表达式被称为方法引用。
如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
三种格式:
对象::实例方法名
类::静态方法名
类::实例方法名
分析
Lambda表达式写法: s -> System.out.println(s);
方法引用写法: System.out::println
以上两种写法完全等效:
第一种:拿到参数之后通过Lambda传递给 System.out.println 方法去处理。
第二种:直接让 System.out 中的 println 方法来取代Lambda。
注意:lambda体中调用的方法的参数列表和返回值类型要与函数式接口的抽象方法的参数列表与返回值类型一致。
Lambda 中 传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
3.1 通过对象名引用成员方法
@Test
public void test(){Person person = new Person();Supplier<String> supplier =() -> person.getName();System.out.println(supplier.get());Supplier<Integer> supplier1 = person::getAge;System.out.println(supplier1.get());
}
MyPrintable:
public interface MyPrintable {
void print(String str);
}
MethodReadObj:
public class MethodReadObj {
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
MethodReference1:
public class MethodReference1 {
public static void printString(MyPrintable p){p.print("hello");
}public static void main(String[] args) {printString(str-> {MethodReadObj methodReadObj = new MethodReadObj();methodReadObj.printUpperCaseString(str);});/*** 使用方法引用:* 1.MethodReadObj对象已经存在* 2.成员方法printUpperCaseString已经存在* 所以可以使用对象名引用成员方法*/MethodReadObj methodReadObj = new MethodReadObj();printString(methodReadObj::printUpperCaseString);}
}
3.2 通过类名称引用静态方法
类已经存在,静态方法已经存在,则可以通过类名直接引用静态成员方法
@Test
public void test1(){
Comparator comparator = (x,y)->Integer.compare(x,y);
Comparator comparator1 = Integer::compare;
}
MyCalc:
public interface MyCalc {
int calc(int num);
}
MethodRerference2:
public class MethodRerference2 {
public static int method(int num,MyCalc c){return c.calc(num);
}public static void main(String[] args) {int number = method(-10, num -> Math.abs(num));int number1 = method(-10, Math::abs);System.out.println(number);System.out.println(number1);
}
}
通过类名引用实例方法
@Test
public void test2(){
BiPredicate<String,String> biPredicate = (x,y) -> x.equals(y);
BiPredicate<String,String> biPredicate1 = String::equals;
}
注意:以上这种情况,需要满足一定的条件:lambda表达式中第一个参数是lambda体中的调用者,第二个参数是lambda体中的参数
3.3 通过super引用成员方法
如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。
MyMeet :
@FunctionalInterface
public interface MyMeet {
void meet();
}
Parent:
public class Parent {
public void hello(){
System.out.println(“hello,I’m Parent”);
}
}
Child:
public class Child extends Parent{
@Override
public void hello() {
System.out.println(“hello,I’m Child”);
}
public void method(MyMeet myMeet){myMeet.meet();
}public void show(){method(()->{Parent parent = new Parent();parent.hello();});//使用super关键字调用父类method(()->super.hello());/*** 使用方法引用:使用super引用父类的成员方法:* 1.super已经存在* 2.父类的成员方法hello已经存在* 可以直接使用super引用父类的成员方法*/method(super::hello);
}public static void main(String[] args) {new Child().show();
}
}
3.4 通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用 this::成员方法的格式来使用方法引用
MyWallet:
@FunctionalInterface
public interface MyWallet {
void buy();
}
BuyThing:
public class BuyThing {
public void buyCar(){System.out.println("买了一辆别摸我");
}public void getSalary(MyWallet myWallet){myWallet.buy();
}public void method(){getSalary(()->this.buyCar());/*** 1.this已经存在* 2.本类的成员方法buyCar已经存在* 所以可以直接使用this引用本类的成员方法buyCar*/getSalary(this::buyCar);}public static void main(String[] args) {new BuyThing().method();
}
}
3.5 类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。
public void test3(){
Supplier personSupplier = ()->new Person();
//构造器引用 此处引用的是无参构造器 ,因为Supplier中的get方法没有参数
Supplier personSupplier1 = Person::new;
}
public void test4(){
Function<Integer,Person> personFunction = (x)->new Person(x);
//构造器引用 此处引用的是整型的一个参数的构造器 ,因为Function中的apply方法只有一个参数
Function<Integer,Person> personFunction1 = Person::new;
}
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表一致
Person:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
}
PersonBuilder:
@FunctionalInterface
public interface PersonBuilder {
//根据传递的姓名,创建Perosn对象
Person builderPerson(String name);
}
Demo:
public class Demo {
//传递姓名和PersonBuilder接口,通过姓名创建Person对象
public static void printName(String name,PersonBuilder personBuilder){Person person = personBuilder.builderPerson(name);System.out.println(person.getName());
}public static void main(String[] args) {printName("hongcaixia",str->new Person(str));/*** 使用方法引用:* 1.构造方法new Person(String name)已知* 2.创建对象已知* 可以使用Person引用new创建对象*/printName("hongcaixia",Person::new);
}
}
3.6 数组的构造器引用
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。
格式:Type[]::new
public void test5() {
Function<Integer, String[]> function = (x) -> new String[x];
String[] strings = function.apply(10);
Function<Integer,String[]> function1 = String[]::new;
String[] strings1 = function1.apply(10);
}
ArrayBuilder:
@FunctionalInterface
public interface ArrayBuilder {
//创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}
DemoArrayBuilder:
public class DemoArrayBuilder {
public static int[] createArray(int length,ArrayBuilder arrayBuilder){return arrayBuilder.builderArray(length);
}public static void main(String[] args) {int[] arr1 = createArray(5,length -> new int[length]);System.out.println(arr1.length);/*** 1.已知创建的是int[]数组* 2.创建的数组长度也是已知的* 使用方法引用,int[]引用new,根据参数传递的长度创建数组*/int[] arr2 = createArray(10,int[]::new);System.out.println(Arrays.toString(arr2));System.out.println(arr2.length);
}
}
四、StreamAPI
流是数据渠道,用于操作数据源(集合、数组等)生成的元素序列。
package com.hcx.stream;
import java.util.ArrayList;
import java.util.List;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class MyStream1 {public static void main(String[] args) {
List list = new ArrayList<>();
list.add(“陈奕迅”);
list.add(“陈小春”);
list.add(“钟汉良”);
list.add(“陈七”);
list.add(“陈伟霆”);
//筛选陈开头,名字是三个字的
List chenList = new ArrayList<>();
for(String item : list){
if(item.startsWith(“陈”)){
chenList.add(item);
}
}List<String> threeList = new ArrayList<>();for(String item : chenList){if(item.length()==3){threeList.add(item);}}//遍历输出符合条件的for(String item : threeList){System.out.println(item);}System.out.println("=====================");//使用Stream流list.stream().filter(str->str.startsWith("陈")).filter(str->str.length()==3).forEach(str-> System.out.println(str));
}
}
Stream(流)是一个来自数据源的元素队列
元素是特定类型的对象,形成一个队列。
数据源流的来源。 可以是集合,数组等。
Stream自己不会存储元素,而是按需计算。
Stream不会改变源对象,并且能返回一个持有结果的新流
Stream操作是延迟操作,意味着他们会等到需要结果的时候才执行
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent
style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
Stream操作的三个步骤:
创建Stream:一个数据源(如集合、数组),获取一个流
中间操作:一个操作链,对数据源的数据进行处理
终止操作:一个终止操作,执行中间操作链并产生结果
注意:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
import java.util.*;
import java.util.function.Predicate;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class MyPredicate {public static void main(String[] args) {
List nums = Arrays.asList(10,20,3,-5,-8);
Collection positiveNum = filter(nums,num->num>0);
Collection negativeNum = filter(nums,num->num<0);
System.out.println(positiveNum);
System.out.println(negativeNum);}
private static Collection filter(Collection source, Predicate predicate){
List list = new ArrayList<>(source);
Iterator iterator = list.iterator();
while (iterator.hasNext()){
E element = iterator.next();
if(!predicate.test(element)){
iterator.remove();
}
}
return Collections.unmodifiableList(list);
}
}
4.1获取流
java.util.stream.Stream是Java8新加入的常用的流接口。(不是函数式接口)
获取一个流有以下几种常用的方式:
①根据Collection获取流
java.util.Collection接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
stream():获取串行流
parallelStream():获取并行流
package com.hcx.stream;
import java.util.*;
import java.util.stream.Stream;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class GetStreamFromCollection {public static void main(String[] args) {
List<String> list = new ArrayList<>();Stream<String> stream1 = list.stream();Set<String> set = new HashSet<>();Stream<String> stream2 = set.stream();Vector<String> vector = new Vector<>();Stream<String> stream3 = vector.stream();
}
}
②根据Map获取流
java.util.Map 接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
package com.hcx.stream;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class GetStreamFromMap {public static void main(String[] args) {
Map<String,String> map = new HashMap<>();Stream<Map.Entry<String, String>> stream1 = map.entrySet().stream();Stream<String> stream2 = map.keySet().stream();Stream<String> stream3 = map.values().stream();
}
}
③根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of :
package com.hcx.stream;
import java.util.stream.Stream;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class GetStreamFromArray {public static void main(String[] args) {
String[] array = {“陈奕迅”,“钟汉良”,“杨千嬅”};
Stream stream = Stream.of(array);
}
}
④获取无限流
public void test6() {
Stream stream = Stream.iterate(0, x -> x + 2);
}
public void test7() {
Stream stream = Stream.generate(() -> Math.random());
}
注意:of 方法的参数是一个可变参数,所以支持数组。
总结:
所有的 Collection 集合都可以通过stream默认方法获取流;
Stream 接口的静态方法of可以获取数组对应的流
4.2 流常用方法
方法可以被分成两种:
延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
终结方法:返回值类型不再是 Stream接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。终结方法包括 count 和 forEach 方法。
①逐一处理:forEach
void forEach(Consumer<? super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。
Consumer是一个消费型的函数式接口,可传递lambda表达式,消费数据
package com.hcx.stream;
import java.util.stream.Stream;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class StreamForEach {public static void main(String[] args) {
Stream stream = Stream.of(“张三”, “李四”, “王五”);
stream.forEach(str-> System.out.println(str));
}
}
②过滤:filter
可以通过 filter 方法将一个流转换成另一个子集流。方法签名:
Stream filter(Predicate<? super T> predicate);
该接口接收一个Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
java.util.stream.Predicate函数式接口唯一的抽象方法为: boolean test(T t);
该方法将会产生一个boolean值结果,代表指定的条件是否满足:
如果结果为true,那么Stream流的filter方法将会留用元素;
如果结果为false,那么filter 方法将会舍弃元素。
public class StreamFilter {
public static void main(String[] args) {
Stream stream = Stream.of(“陈奕迅”, “陈伟霆”, “陈七”, “钟汉良”);
Stream stream1 = stream.filter(str -> str.startsWith(“陈”));
stream1.forEach(str-> System.out.println(str));
}
}
注意:Stream属于管道流,只能被消费一次
Stream流调用完毕方法,数据就回流到下一个Steam上,
而这时第一个Stream流已经使用完毕,就会关闭了,
所以第一个Stream流就不能再调用方法了。
③映射:map
接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。即将流中的元素映射到另一个流中。
方法签名:
Stream map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
java.util.stream.Function 函数式接口,其中唯一的抽象方法为: R apply(T t);
这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。
public class StreamMap {
public static void main(String[] args) {Stream<String> stream = Stream.of("1", "2", "3");Stream<Integer> integerStream = stream.map(str -> Integer.parseInt(str));integerStream.forEach(i-> System.out.println(i));
}
}
public void test8() {
Person person = new Person(“hcx”,24);
Person person1 = new Person(“hcx2”,24);
List list = new ArrayList<>();
list.add(person);
list.add(person1);
list.stream().map(Person::getName).forEach(System.out::println);
}
④flatMap
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。即对流扁平化处理,浅显一点解释就是把几个小的list转换到一个大的list
如:[[‘a’,‘b’],[‘c’,‘d’]] - > [‘a’,‘b’,‘c’,‘d’]
如果我们使用常用的map()方法获取的lowercaseWords数据结构为:[[‘a’,‘b’,‘c’],[‘m’,‘d’,‘w’],[‘k’,‘e’,‘t’]]。如果我们要得到如:[‘a’,‘b’,‘c’,‘m’,‘d’,‘w’,‘k’,‘e’,‘t’]这样数据结构的数据,就需要使用flatMap()方法。
public void test9() {
List list = Arrays.asList(“a”,“b”,“c”);
Stream<Stream> streamStream = list.stream().map(MethodReference::filterCharacter);
streamStream.forEach((stream)->stream.forEach(System.out::println));
//使用flatMap
Stream<Character> characterStream = list.stream().flatMap(MethodReference::filterCharacter);
characterStream.forEach(System.out::println);
}
public static Stream filterCharacter(String str){
List list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add©;
}
return list.stream();
}
⑤规约reduce
将流中元素反复结合起来,得到一个值
import java.util.stream.Stream;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class StreamReduce {public static void main(String[] args) {
sum(1,2,3,4,5);
}private static void sum(Integer… nums){
Stream.of(nums).reduce(Integer::sum).ifPresent(System.out::println);
}
}
@Test
public void test10() {
List list = Arrays.asList(1,2,3,4,5);
Integer sum = list.stream().reduce(0,(x,y)->x+y);
System.out.println(sum);
}
⑥统计个数:count
终结方法
流提供 count方法来数一数其中的元素个数
该方法返回一个long值代表元素个数:
package com.hcx.stream;
import java.util.ArrayList;
import java.util.stream.Stream;
/**
- Created by hongcaixia on 2019/10/31.
*/
public class StreamCount {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Stream stream = list.stream();
long count = stream.count();
System.out.println(count);
}
}
⑦取用前几个:limit
limit 方法可以对流进行截取,只取用前n个。方法签名:
Stream limit(long maxSize);
参数是一个 long型,如果集合当前长度大于参数则进行截取;否则不进行操作
延迟方法,只是对流中的元素进行截取,返回的是一个新的流,还可以继续调用Stream中的其他方法
public class StreamLimit {
public static void main(String[] args) {
String[] str = {“1”,“2”,“3”,“4”,“5”};
Stream stream = Stream.of(str);
Stream limitStream = stream.limit(3);
limitStream.forEach(string-> System.out.println(string));
}
}
⑧跳过前几个:skip
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
public class StreamSkip {
public static void main(String[] args) {
String[] str = {“1”,“2”,“3”,“4”,“5”};
Stream stream = Stream.of(str);
Stream skipStream = stream.skip(3);
skipStream.forEach(string-> System.out.println(string));
}
}
⑨组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
static Stream concat(Stream<? extends T> a, Stream<? extends T> b)
注意:这是一个静态方法,与 java.lang.String 当中的 concat 方法不同
public class StreamConcat {
public static void main(String[] args) {
Stream stream1 = Stream.of(“陈奕迅”, “陈伟霆”, “陈七”, “钟汉良”);
String[] arr = {“1”,“2”,“3”};
Stream stream2 = Stream.of(arr);
Stream concatStream = Stream.concat(stream1, stream2);
concatStream.forEach(str-> System.out.println(str));
}
}
⑩排序:sorted
sorted() 自然排序
sorted(Comparator com) 定制排序
allMatch 检查是否匹配所有元素
anyMatch 检查是否至少匹配一个元素
noneMatch 检查是否没有匹配所有元素
findFirst 返回第一个元素
findAny 返回当前流中的任意元素
count 返回流中元素的总个数
max 返回流中最大值
min 返回流中最小值
public class StreamSort {
public static void main(String[] args) {
Integer[] nums = {2,9,0,5,-10,90};
Stream numsStream = Stream.of(nums);
Stream sortedStram = numsStream.sorted();
sortedStram.forEach(num -> System.out.println(num));
}
}
⑪Collect
将流转换为其他形式。接收一个Collector的实现,用于给stream中元素做汇总的方法
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
-
Created by hongcaixia on 2019/10/31.
*/
public class StreamCollect {public static void main(String[] args) {
List list = Stream.of(1,2,3,4,5).collect(Collectors.toList());
List list1 = Stream.of(1,2,3,4,5).collect(LinkedList::new,List::add,List::addAll);
System.out.println(list.getClass());//class java.util.ArrayList
System.out.println(list1.getClass());//class java.util.LinkedList
}
}
@Test
public void test11() {
Person person = new Person(“hcx”,24);
Person person1 = new Person(“hcx2”,24);
List list = new ArrayList<>();
list.add(person);
list.add(person1);
List collect = list.stream().map(Person::getName).collect(Collectors.toList());
}
4.3 并行流和顺序流
并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
通过parallel()与sequential()可以实现并行流和顺序流之间的切换。
Fork/Join框架:在必要的情况下,将一个大任务进行拆分(fork)成若干小任务(拆到不可再拆时),再将一个个小任务运算的结果进行join汇总。
@Test
public void test12() {
//顺序流
LongStream.rangeClosed(0,100).reduce(0,Long::sum);
//并行流
long reduce = LongStream.rangeClosed(0, 100).parallel().reduce(0, Long::sum);
//5050
System.out.println(reduce);
}