在我看来 lambda 表达式就是简化了之前的一大堆繁琐的操做,让咱们代码看起来更加简洁,让之前五六行甚至更多行的代码只须要两三行就能解决,可是对于 Java 初学者可能不是特别友好,可能一会儿理解不过来该代码想表达什么。dom
lambda 表达式是一段能够传递的代码,所以它能够被执行一次或屡次ide
咱们先来看看老版本的排序字符串的办法,这里咱们不按照字典序,而按照字符串的大小来排序函数
Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(),o2.length()); } }; List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,comparator);// [a, aa, aaa, aaaa, aaaaa]
老版本的排序咱们会先建立一个自定义比较器,而后按照比较器的规则进行排序。
如今咱们来看看 lambda 表达式如何来实现的
List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,(String o1,String o2)->{ return Integer.compare(o1.length(),o2.length()); });//[a, aa, aaa, aaaa, aaaaa]
能够看到,代码浓缩了很多,可是可读性没有原来好,原来须要先建立一个比较器而后将比较器传到 Collections 工具类进行排序,典型的面向对象编程,可是 lambda 表达式确是将代码传进去而后直接进行比较。若是你觉得这样就是最简便的,那你就错了,有更简便的。
List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,(String o1,String o2) ->Integer.compare(o1.length(),o2.length()));
若是返回值只有一行,能够省略大括号和 return 关键字。
List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,(o1,o2)->Integer.compare(o1.length(),o2.length()));
若是是带泛型的容器,参数的类型能够省略,JVM 会本身进行上下文判断出类型。
咱们能够看到从原来的那么多行代码浓缩成了一行,看着清爽了不少,可是可读性却没有原来那么友好了。
访问局部变量
能够在 lambda 表达式中访问外部的局部变量
int number = 10; Converter<String,Integer> converter = num->Integer.valueOf(num + number); System.out.println(converter.convert("123"));//12310
在匿名内部类中外部的局部变量必须声明为 final。而咱们这里不须要。
可是要注意的是这的 number 不能被后面的代码修改,不然编译不经过,也就是具备隐性的 final 语义。
访问 字段和静态变量
咱们对 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); }; } }
没法在 lambda 表达式中访问默认接口方法。
在 Java 中有许多已有的接口都选哦封装成代码块,好比 Runnable 或者 Comparator 。 lambda 表达式与这些接口是像后兼容的。
对于只包含一个抽象方法的接口,可是能够有多个非抽象方法,(非抽象方法也是 java 8 新特性,咱们后面会讲到),咱们能够经过 lambda 表达式来建立该接口的对象。这种接口被称为函数式接口。
Java 8 新增长了一种特殊的注解 @FunctionalInterface,该接口会自动判断你的接口中是否只有一个抽象方法,若是多于一个抽象方法就会报错。
如今咱们来自定义一个函数式接口:
@FunctionalInterface interface Converter<F,T>{ T convert(F num); }
// 将数字形式的字符串转化成整型 Converter<String,Integer> converter = (num -> Integer.valueOf(num)); System.out.println(converter.convert("123").getClass());//class java.lang.Integer
如今来解释下该代码,在该代码中咱们的函数式接口中定义了一个方法,该方法可以实现传入一个 F 类型的参数,咱们能够对这个类型的参数进行各类处理,最后返回一个 T 类型的结果。在这里我只是简单的将传进来的 string 转成了 integer。这里的 F 与 T 都是泛型类型,能够为任何实体类。
java 8 帮咱们实现了不少函数式接口,大部分都不须要咱们本身写,这些接口在 java.util.function 包 里,能够自行进行查阅。
上面的代码能够写的更加简单:
Converter<String,Integer> converter = Integer::valueOf; System.out.println(converter.convert("123").getClass());//class java.lang.Integer
java 8 能够经过 ** : : **来传递方法或者构造函数的引用。上面的演示了若是引用静态方法,引用对象方法也相差不大,只是须要声明一个对象:
class Demo{ public Integer demo(String num){ return Integer.valueOf(num); } } public class Main { public static void main(String[] args) { Demo demo = new Demo(); Converter<String,Integer> converter = demo::demo; System.out.println(converter.convert("123").getClass()); //class java.lang.Integer } }
Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其余复杂的逻辑(好比:与,或,非)
@FunctionalInterface public interface Predicate<T> { // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断. boolean test(T t); .... }
Function 接口接受一个参数并生成结果。默认方法可用于将多个函数连接在一块儿(compose, andThen)
@FunctionalInterface public interface Function<T, R> { //将Function对象应用到输入的参数上,而后返回计算结果。 R apply(T t); ... }
Supplier 接口产生给定泛型类型的结果。 与 Function 接口不一样,Supplier 接口不接受参数。
Consumer 接口表示要对单个输入参数执行的操做。
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法
前面已经有写地方提到了接口的默认方法,这里对其作下介绍。接口的默认方法也是 java 8 新出的功能。可以经过使用 default
关键字向接口添加非抽象方法实现。
interface Formula{ double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
Formula 接口中除了抽象方法计算接口公式还定义了默认方法 sqrt
。 实现该接口的类只须要实现抽象方法 calculate
。 默认方法sqrt
能够直接使用。固然你也能够直接经过接口建立对象,而后实现接口中的默认方法就能够了,咱们经过代码演示一下这种方式。
public class Main { public static void main(String[] args) { // TODO 经过匿名内部类方式访问接口 Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; System.out.println(formula.calculate(100)); // 100.0 System.out.println(formula.sqrt(16)); // 4.0 } }
formula 是做为匿名对象实现的。该代码很是容易理解,6行代码实现了计算 sqrt(a * 100)
。
不论是抽象类仍是接口,均可以经过匿名内部类的方式访问。不能经过抽象类或者接口直接建立对象。对于上面经过匿名内部类方式访问接口,咱们能够这样理解:一个内部类实现了接口里的抽象方法而且返回一个内部类对象,以后咱们让接口的引用来指向这个对象。
Stream 是在 java.util 下的。Stream 表示能应用在一组元素上一次执行的操做序列。Stream 操做分为中间操做或者最终操做两种,最终操做返回一特定类型的计算结果,而中间操做返回 Stream 自己,这样咱们就能够将多个操做依次串起来。Stream 的建立须要指定一个数据源,好比java.util.Collection
的子类:List 或者 Set。Map 不支持。Stream 的操做能够串行执行或者并行执行。
当咱们使用 Stream 时,咱们将经过三个阶段来创建一个操做流水线。
在这以后 stream 就不会再被使用了。
经过 Java 8 在 Collection 接口中新提娜佳的 stram 方法,能够将任何集合转化为一个 Stream。若是咱们面对的是一个数组,也能够用静态的 Stream.of 方法将其转化为一个 Stream。
@Test public void test1(){ List<String> stringList = new ArrayList<>(); stringList.add("ddd2"); stringList.add("aaa2"); stringList.add("bbb1"); stringList.add("aaa1"); stringList.add("bbb3"); stringList.add("ccc"); stringList.add("bbb2"); stringList.add("ddd1"); Stream<String> stream = stringList.stream(); //Stream<String> stringStream = stringList.parallelStream(); }
咱们能够经过 Collection.stream() 或者 Collection.parallelStream() 来建立一个Stream。
过滤经过一个predicate接口来过滤并只保留符合条件的元素,该操做属于中间操做,因此咱们能够在过滤后的结果来应用其余Stream操做。(好比forEach)。forEach须要一个函数来对过滤后的元素依次执行。forEach是一个最终操做,因此咱们不能在forEach以后来执行其余Stream操做。
stringList .stream() .filter(s->s.startsWith("a")) .forEach(System.out::println);
forEach 是为 Lambda 而设计的,保持了最紧凑的风格。并且 Lambda 表达式自己是能够重用的,很是方便。
排序是一个中间操做,返回的是排序好的 Stream 。若是咱们不指定一个自定义的 Comparator 则会使用默认排序。
stringList .stream() .sorted((o1,o2)->Integer.compare(o1.length(),o2.length())) .forEach(System.out::println);
须要注意的是,排序只建立了一个排列好后的Stream,而不会影响原有的数据源,排序以后原数据stringCollection是不会被修改的。
中间操做 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
map返回的 Stream 类型是根据咱们 map 传递进去的函数的返回值决定的。
stringList .stream() .map(String::toUpperCase) .sorted((o1,o2)->Integer.compare(o1.length(),o2.length())) .forEach(System.out::println);
Stream提供了多种匹配操做,容许检测指定的Predicate是否匹配整个Stream。全部的匹配操做都是 最终操做 ,并返回一个 boolean 类型的值。
boolean anyStartsWithA = stringList .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringList .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringList .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
计数是一个 最终操做,返回Stream中元素的个数,返回值类型是 long。
long count = stringList .stream() .map(String::toUpperCase) .sorted((o1, o2) -> Integer.compare(o1.length(), o2.length())) .count(); System.out.println(count);
Stream有串行和并行两种,串行Stream上的操做是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
下面使用串行流和并行流为一个大容器进行排序,比较二者性能。
串行排序
@Test public void test1(){ int max = 1000000; List<String> list = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); list.add(uuid.toString()); } long startTime = System.nanoTime(); long count = list.stream().sorted().count(); System.out.println(count); long endTime = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(endTime-startTime); System.out.println(millis); //1000000 //877 }
并行排序
@Test public void test2(){ int max = 1000000; List<String> list = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); list.add(uuid.toString()); } long startTime = System.nanoTime(); long count = list.parallelStream().sorted().count(); System.out.println(count); long endTime = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(endTime-startTime); System.out.println(millis); } //1000000 //512
能够明显看出在大数据量的状况下并行排序比串行来的快。可是小数据量的话倒是串行排序比较快,缘由是并行须要涉及到上下文切换。
Collector 是专门用来做为 Stream 的 collect 方法的参数的。而 Collectors 是做为生产具体 Collector 的工具类。
toList():将流构形成 list
List<String> collect = list.stream().collect(Collectors.toList());
toSet():将流构形成set
Set<String> set = list.stream().collect(Collectors.toSet()); Set<String> treeSet = list.stream().collect(Collectors.toCollection(TreeSet::new));
joining():拼接流中全部字符串
String collect = list.stream().collect(Collectors.joining()); String collect = list.stream().collect(Collectors.joining(";"));
toMap():将流转成 map
Map<String, String> collect = list .stream() .collect(Collectors .toMap(e -> "key" + e, e -> "v" + e,(a,b)->b,HashMap::new));
上面的 e -> "key" + e 定义了 map 的 key 的生成规则,e -> "v" + e 定义了 map 的 value 的生成规则,(a,b)->b 表示冲突的解决方案,若是键 a 和 键 b 冲突了则该键键值取 b 的,HashMap::new 定义了生成的 map 为 hashmap。
Map 虽然不支持 Stream 可是咱们能够经过 map.keySet().stream()
,map.values().stream()
和map.entrySet().stream()
来经过过去键、值的集合再转换成流进行处理。
Java 8 中 map 新方法:
putIfAbsent(key, value)//有则不加,无则加
map.forEach((key, value) -> System.out.println(value));//循环打印
map.computeIfPresent(3, (num, val) -> val + num);//当key 存在则执行后面方法
map.computeIfAbsent(23, num -> "val" + num);//当key 不存在时执行后面方法
map.getOrDefault(42, 1);//有则获取,无则置 1
map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); //若是键名不存在则插入,不然则对原键对应的值作合并操做并从新插入到map中。
LocalTime(本地时间)
LocalTime 定义了一个没有时区信息的时间
方法 | 描述 |
---|---|
now,of | 这些静态方法能够根据当前时间或指定的年、月、日来建立一个 LocalTime 对象 |
getHour,getMinute,getSecond,getNano | 得到当前 LocalTime 的小时、分钟、秒钟及微妙值 |
isBefore,isAfter | 比较两个LocalTime |
LocalDate(本地日期)
LocalDate 表示了一个确切的日期,好比 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。
方法 | 描述 |
---|---|
now,of | 这些静态方法能够根据当前时间或指定的年、月、日来建立一个LocalDate对象 |
getDayOfMonth | 获取月份天数(在 1~ 31 之间) |
getDayOfYear | 获取年份天数(在1~366之间) |
getMonth,getMonthValue | 得到月份,或者为一个 Month 枚举的值,或者是 1 ~ 12 之间的一个数字 |
getYear | 获取年份 |
isBefore,isAfter | 比较两个LocalDate |
上面这些方法是比较经常使用的,其他的能够自行查阅。