介绍
- 优点
- 速度更快
- 代码更少(Lambda表达式)
- 强大的Stream API
- 便于并行
- 最大化减少控制异常Optional
- 缺点
- 链式编程,使得代码可读性和逻辑性不明显
常用方法
- Lambda表达式(常用循环遍历、排序)
- 新的日期API
- 函数式接口
- 方法引用与构造器引用
- Stream API流式计算(对集合元素进行增加、删除、获取元素、遍历)
-
应用
Lambda表达式
概念
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” ,该操作符被称 为 Lambda 操作符或剪头操作符。它将 Lambda 分为 两个部分:
左侧:指定了 Lambda 表达式需要的所有参数
右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。使用
public class Test {
public static void main(String[] args) {
Consumer<String> consumer = (x) -> System.out.println(x);
consumer.accept("我在学习Lambda表达式");
String[] players_1 = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"};
// 1.2 使用 lambda expression 排序 players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players_1, sortByName);
System.out.println(players_1);
System.out.println("------------1----------------");
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer", "Roger Federer",
"Andy Murray", "Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
// 以前的循环方式
for (String player : players) {
System.out.print(player + "; ");
}
System.out.println("-------------2---------------");
// 使用 lambda 表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));
System.out.println("-------------3---------------");
//在里面写方法,刚开始以为只是一个赋值
//左侧是所有参数,右侧要执行的功能
players.forEach(player -> {
System.out.print(player + ";=== ");
});
System.out.println("-------------4---------------");
// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);
}
}
Stream API流式计算
概念
Stream流的出现,主要是用在集合的操作上。在我们日常的工作中,经常需要对集合中的元素进行相关操作。诸如:增加、删除、获取元素、遍历。 Stream流式思想类似于工厂车间的”生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream 可以看做是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。[1] (图片更直观) 参考一
举例
public class Test2 {
public static void main(String[] args) {
List<String> listLevel = new ArrayList<String>();
listLevel.add("5");
listLevel.add("1");
listLevel.add("2");
listLevel.add("9");
listLevel.add("7");
//比较出最小的
String levelMin = listLevel.stream().min((Comparator.comparing((a) -> a))).get();
System.out.println(levelMin);
//比较出最大的
String levelMax = listLevel.stream().max((Comparator.comparing((a) -> a))).get();
System.out.println(levelMax);
//进入工厂
listLevel.stream()
.filter(level-> (Integer.parseInt(level)>5)) //第一道工序
.filter(level-> (Integer.parseInt(level)<10)) //第二道工序
.forEach(level -> { //第三道工序
System.out.println("我是"+level); //开始使用
});
}
}
使用
java.util.stream.Stream 是 JDK8 新加入的流接口。
- 所有的 Collection 集合都可以通过 .stream() 方法来获取流;
使用 Stream 接口的 .of() 静态方法,可以获取流。
//方式1:根据Collection获取流
//Collection接口中有一个默认的方法:default Stream<E> stream()
//1.List获取流
List<String> list = new ArrayList<>();
Stream<String> stream01 = list.stream();
//2.Set获取流
Set<String> set = new HashSet<>();
Stream<String> stream02 = set.stream();
//3.Map获取流
//Map 并没有继承自 Collection 接口,所有无法通过该 map.stream()获取流。
//但是可用通过如下三种方式获取:
Map<String,String> map = new HashMap<>();
Stream<String> stream03 = map.keySet().stream();
Stream<String> stream04 = map.values().stream();
Stream<Map.Entry<String, String>> stream05 = map.entrySet().stream();
//方式2:Stream中的静态方法of获取流
// static<T> Stream<T> of(T... values)
// T... values:可变参数,实际原理就是可变数组(传递String数组进去)
//1.字符串获取流
Stream<String> stream06 = Stream.of("aa", "bb", "cc");
//2.数组类型(基本类型除外)
String[] strs = {"aa","bb","cc"};
Stream<String> stream07 = Stream.of(strs);
//3.基本数据类型的数组
int[] arr = {1,2,3,4};
//返回值是 int[],
//这是 Stream流把整个数组看做一个元素来操作,而不是操作数组中的int元素
Stream<int[]> stream08 = Stream.of(arr);
常用方法
| 方法名 | 作用 | 返回值类型 | 方法种类 | | —- | —- | —- | —- | | count | 统计个数 | long | 终结 | | forEach | 遍历(逐一处理) | void | 终结 | | filter | 过滤 | Stream | 函数拼接 | | limit | 取用前几个 | Stream | 函数拼接 | | skip | 跳过前几个 | Stream | 函数拼接 | | map | 映射 | Stream | 函数拼接 | | concat | 组合 | Stream | 函数拼接 |
// count() 方法,用来统计集合中的元素个数,是一个终结方法。
long count = listLevel.stream().count();
System.out.println("元素个数为:"+count);
// limit() 方法,用来对 Stream 流中的数据进行截取,只取用前 n 个,是一个非终结方法。
// 参数是一个 long 型,如果集合当前长度大于参数则进行截取,否则不进行操作。
// 因为 limit() 是一个非终结方法,所以必须调用终止方法。
listLevel.stream().limit(3).forEach(System.out::println);
//如果希望跳过前几个元素,去取后面的元素,则可以使用 skip()方法,
//获取一个截取之后的新流,它是一个非终结方法。
// 参数是一个 long 类型,如果 Stream 流的当前长度大于 n,则跳过前 n 个,
//否则将会得到一个长度为 0 的空流。
// 因为 limit() 是一个非终结方法,所以必须调用终止方法。
listLevel.stream().skip(3).forEach(System.out::println);
//可以使用limit()方法和skip()实现分页
//一页2条 分页操作
//第一页
listLevel.stream().skip(0).limit(2).forEach(System.out::println);
//第二页
listLevel.stream().skip(2).limit(2).forEach(System.out::println);
//第三页
listLevel.stream().skip(4).limit(2).forEach(System.out::println);
//Stream 转成 List
Stream<String> list = listLevel.stream().skip(0).limit(2);
//Convert a Stream to List
List<String> result = list.collect(Collectors.toList());
result.forEach(System.out::println);
//sorted() 方法,可以用来对 Stream 流中的数据进行排序.
//因为 sorted() 方法是一个非终结方法,所以必须调用终止方法。
//sorted() 方法:按照自然规律,默认为升序排序。
//sorted(Comparator comparator) 方法,按照指定的比较器规则排序
Stream<Integer> stream = Stream.of(66,33,11,55);
stream.sorted().forEach(System.out::println);
新的日期API
概念
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
- Local(本地) − 简化了日期时间的处理,没有时区的问题。
Zoned(时区) − 通过指定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。使用
```java public class Test3 { public static void main(String[] args) {
//指定格式
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss ");
LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);
System.out.println(strDate);
String strDate2 = ldt.format(dtf2);
System.out.println(strDate2);
LocalDateTime newLdt = ldt.parse(strDate, dtf);
System.out.println(newLdt);
// 获取当前的日期时间
// LocalDateTime ldt = LocalDateTime.now();
System.out.println("当前时间: " + ldt);
LocalDateTime ldt2 = LocalDateTime.of(2019, 11, 21, 10, 10, 10);
System.out.println("使用指定数据: " + ldt2);
LocalDateTime ldt3 = ldt2.plusYears(20);
System.out.println("加20年: " + ldt3);
LocalDateTime ldt4 = ldt2.minusMonths(2);
System.out.println("减2个月: " + ldt4);
System.out.println("年: " + ldt.getYear());
System.out.println("月: " + ldt.getMonthValue());
System.out.println("日: " + ldt.getDayOfMonth());
System.out.println("时: " + ldt.getHour());
System.out.println("分: " + ldt.getMinute());
System.out.println("秒: " + ldt.getSecond());
// 默认使用 UTC 时区(0时区)
Instant ins = Instant.now();
System.out.println(ins);
// 偏移8个时区
OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);
// 获取纳秒
System.out.println(ins.getNano());
// 在 1970年1月1日 00:00:00 加5秒
Instant ins2 = Instant.ofEpochSecond(5);
System.out.println(ins2);
}
}
<a name="l472J"></a>
### Optional.ofNullable()
```java
Object val = Optional.ofNullable(val).orElse(" ");
//如果list集合不为空,将list集合赋值给newList;
//如果list集合为空创建一个空对象集合赋值给newList,
//保证list集合永远不为空,也就避免了空指针异常。
List<String> list = null;
List<String> newList = Optional.ofNullable(list).orElse(Lists.newArrayList());
newList.forEach(x -> System.out.println(x));
默认排序是归并排序
这是因为 Java 做为一个平台型语言,对于稳定性要求较高!归并有一个快排没有的优点,就是归并排序是稳定的。
因为合并排序比较稳定,比快排稳定,快排有可能时间复杂度达到 O(n ^ 2),但是合并排序就相对趋于 O(nlogn),但是合并排序的一个缺点就是要用到两个临时的数组内存!
很多人可能不理解什么是稳定?给你举个例子吧。[1,2,1,3] 排序过后成为 [1, 1, 2, 3]。使用快速排序后,这两个 1 可能就换了个位置。这里用单纯的数字,你可能看不出来效果。但是如果是用对象比较的话,假如两个对象的 compare 值是对等的,但是排序过后相互顺序却变了,在内存上是有意义的。
另外,快排是不是最快的排序算法。
在 JDK8 中,如果你看过源码就会知道,其实针对不同的情况使用了不同的排序算法,简单罗列下:
- 如果是简单对象数据,例如int,double,且数组长度在一定阀值内,则使用快排
- 如果在阀值外,则用归并;
- 如果是复杂对象数组,则如果数组长度在一定阀值以内,则使用折半插入排序,如果长度在阀值外,则使用归并法,但是如果归并二分后小于阀值了,则在内部还是会使用折半插入排序。
那么为什么复杂对象不使用快速排序呢?因为对于一个 hashcode 计算复杂的对象来说,移动的成本远低于比较的成本。
归并排序在最坏情况下的键值比较次数十分接近基于比较的排序算法在理论上能够达到的最少次数。当n很大时,归并排序算法比较的次数是要小于0.25n次的,因此效率也属于θ(nlogn)。
相比于快速排序和堆排序,归并算法的一个显著优点在于其稳定性,主要缺点在于需要线性的额外空间。归并算法也可以做到原地排序,使空间复杂度降至 O(1)。
归并排序有两类主要的变化形式:
- 算法自底向上合并数组的一个个元素对,然后再合并有些有序对,依次类推。这种方式是迭代式的,因此避免了使用堆栈处理递归调用时的空间和时间开销。
- 算法把数组划分为待排序的多个部分,再对它们递归排序,最后将其合并在一起。这个方案尤其适合对存放在二级存储空间(内存-外存)的文件进行排序,也被称为多路归并排序(multiway mergesort)。