JDK8
Lambda表达式
// 1. 不需要参数,返回值为 5 {}只有一行代码,可以省略
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值,()只有一个参数可以省略
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
我们常用的一些接口Callable、Runnable、Comparator等在JDK8中都添加了@FunctionalInterface注解
通过JDK8源码javadoc,可以知道这个注解有以下特点:
1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
语法:
Interface var = (x,y) -> {}
该接口只能有一个需要被实现的方法,小括号中参数取决于Interface 的接口方法的参数,没有参数则为空,{}中为方法的实现内容,如果内容只有一行代码,{}可以省略。实际上就是匿名函数。
Runnable run = new Runnable(){
@Override
publicvoidrun(){
System.out.println("常规写法");
}
};
Runnable run1 = () -> {System.out.println("lambda");};//{}中只有一条语句时,{}可以省略
//匿名函数的访问权限可以省略(跟接收变量的作用域保持一致,返回值和参数类型都可以编译器自动判断。)
只有一个抽象方法需要被实现的接口,称为“函数式接口”,为了避免后续被人在该接口中添加方法,导致规则被破坏,可以在该接口上加一个声明@FunctionalInterface,这样该接口就无法添加新的接口函数了。在JDK8以后接口里面可以有实现的方法,只是实现的那个方法要加上default修饰。
变量作用域
lambda 表达式只能引用final 类型的外层局部变量,就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。与匿名函数同理
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)。
下面的num虽然没有被finale修饰,但是在lamda表达式中被使用了,它默认就已经加上final语义了,如果在lamda中修改这个值会被语义拦截报错,但是是可以访问的。
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//报错信息:Local variable num defined in an enclosing scope must be final or effectively final
//在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
方法引用
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”,可以理解为方法引用是lambda表达式的另外一种表达形式
主要有三种语法格式:
- 对象 :: 实例方法名
- 类 :: 静态方法名
- 类 :: 实例方法名
被引用的方法的参数和返回值必须和要实现的抽象方法的参数和返回值一致
静态方法引用:
//格式:Classname :: staticMethodName 和静态方法调用相比,只是把 . 换为 ::
String::valueOf 等价于lambda表达式 (s) -> String.valueOf(s)
Math::pow 等价于lambda表达式 (x, y) -> Math.pow(x, y);
实例对象方法引用
//格式:instanceReference::methodName
class ComparisonProvider{
public int compareByName(Person a, Person b){
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b){
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
超类上的实例方法引用
//格式:super::methodName
//还可以使用this
泛型类和泛型方法引用
public interface MyFunc<T> {
int func(T[] als, T v);
}
public class MyArrayOps {
public static <T> int countMatching(T[] vals, T v) {
int count = 0;
for (int i = 0; i < vals.length; i++) {
if (vals[i] == v) count++;
}
return count;
}
}
public class GenericMethodRefDemo {
public static <T> int myOp(MyFunc<T> f, T[] vals, T v) {
return f.func(vals, v);
}
public static void main(String[] args){
Integer[] vals = {1, 2, 3, 4, 2, 3, 4, 4, 5};
String[] strs = {"One", "Two", "Three", "Two"};
int count;
count=myOp(MyArrayOps::<Integer>countMatching, vals, 4);
System.out.println("vals contains "+count+" 4s");
count=myOp(MyArrayOps::<String>countMatching, strs, "Two");
System.out.println("strs contains "+count+" Twos");
}
}
构造器引用
通过函数式接口实例化类时可以用构造器引用,引用到的是方法参数个数和类型匹配的构造器
//格式:ClassName :: new,调用默认构造器。
//lambda方式
Supplier<Passenger> supplier1 = () -> new Passenger();
//构造器引用:通过类型推断,引用无参构造器
Supplier<Passenger> supplier2 = Passenger::new;
//lambda方式
BiFunction<String, String, Passenger> function1 = (x, y) -> new Passenger(x, y);
//构造器引用:通过类型推断,引用有两个String参数的构造器
BiFunction<String, String, Passenger> function2 = Passenger::new;
数组引用
//lambda方式
Function<Integer, String[]> fun1 = (x) -> new String[x];
String[] strs1 = fun1.apply(10);
//数组引用
Function<Integer, String[]> fun2 = String[]::new;
String[] strs2 = fun2.apply(10);
Stream
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现
1、Stream是元素的集合,这点让Stream看起来用些类似Iterator;
2、可以支持顺序和并行的对原Stream进行汇聚的操作;
特性:
- 不存储数据
- 不改变源数据
- 延迟执行
使用步骤:
- 创建Stream数据源;
- 数据处理,转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);
- 对Stream进行聚合(Reduce)操作,获取想要的结果;
创建数据源:
1、Collection.stream(); 从集合获取流。
2、Collection.parallelStream(); 从集合获取并行流。
3、Arrays.stream(T array) or Stream.of(); 从数组获取流。
4、BufferedReader.lines(); 从输入流中获取流。
5、IntStream.of() ; 从静态方法中获取流。
6、Stream.generate(); 自己生成流
@Test
public void createStream() throws FileNotFoundException {
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"};
// 集合获取 Stream 流
Stream<String> nameListStream = nameList.stream();
// 集合获取并行 Stream 流
Stream<String> nameListStream2 = nameList.parallelStream();
// 数组获取 Stream 流
Stream<String> nameArrStream = Stream.of(nameArr);
// 数组获取 Stream 流
Stream<String> nameArrStream1 = Arrays.stream(nameArr);
// 文件流获取 Stream 流
BufferedReader bufferedReader = new BufferedReader(new FileReader("README.md"));
Stream<String> linesStream = bufferedReader.lines();
// 从静态方法获取流操作
IntStream rangeStream = IntStream.range(1, 10);
rangeStream.limit(10).forEach(num -> System.out.print(num+","));
System.out.println();
IntStream intStream = IntStream.of(1, 2, 3, 3, 4);
intStream.forEach(num -> System.out.print(num+","));
}
数据处理转换:
中间操作,可以有多个,返回的是一个新的stream对象,惰性计算,只有在开始收集结果时中间操作才会执行。
filter:数据筛选,相当于if判断
它的参数是个Predicate 接口实现类,我们在使用的时候可以使用lambda表达式。我们先来看下这个Predicate接口。
Predicate接口
- JDK8 提供的函数式接口
- 提供一个抽象方法test, 接受一个参数, 根据这个参数进行一些判断, 返回判断结果 true / false
- 提供几个默认的default方法, and, or, negate 用于进行组合判断
- 在流中被广泛使用
/**
* 接收一个参数, 判断这个参数是否匹配某种规则, 匹配成功返回true, 匹配失败则返回false
我们在使用filter的时候使用lamda表达式其实就是重写的这个方法
*/
boolean test(T t);
/**
* default方法, 接收另外一个Predicate<T>类型参数进行逻辑与操作
* 返回一个新的Predicate
* Predicate<T> newPredicate = (t) -> this.test(t) && other.test(t);
* 如果传入的Predicate为空, 会抛出空指针异常
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* default方法, 接收另外一个Predicate<T>类型参数进行逻辑或操作
* 返回一个新的Predicate
* Predicate<T> newPredicate = (t) -> this.test(t) || other.test(t);
* 如果传入的Predicate为空, 会抛出空指针异常
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* default方法, 返回当前Predicate取反操作之后的Predicate
* Predicate<T> newPredicate = (t) -> !test(t);
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* 接收一个Object targetRef, 返回一个Predicate<T>类型
* 返回的Predicate的test方法是用来判断传入的参数是否等于targetRef
* 如Predicate<T> predicate = Predicate.isEqual("七夜雪");
* 等同于Predicate<T> predicate = t -> "七夜雪".equals(t);
* 个人感觉这个方法没多大用处
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
使用案例:
/**
* @author 七夜雪
* @date 2019-01-12 10:53
*/
public class PredicateTest {
// 辅助打印方法
private static void print(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) {
Predicate<String> predicate = item -> "七夜雪".equals(item);
// 1. test 方法测试
System.out.println("1---> " + predicate.test("七夜雪"));
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// 2. Predicate 返回一个List中的偶数
// list.stream(), 表示将List作为流进行处理, filter()方法接收一个Predicate, toArray是将流转换成数组
Object[] result = list.stream().filter(t -> t % 2 == 0).toArray();
print("2---> " + Arrays.toString(result));
// 3. 测试Predicate的and方法, 打印list中大于3, 小于6的数字
Predicate<Integer> predicate1 = t -> t > 3;
predicate1 = predicate1.and(t -> t < 6);
result = list.stream().filter(predicate1).toArray();
print("3---> " + Arrays.toString(result));
// 4. 测试Predicate的or方法, 打印list中小于3或大于5的数字
predicate1 = t -> t < 3;
predicate1 = predicate1.or(t -> t > 5);
result = list.stream().filter(predicate1).toArray();
print("4---> " + Arrays.toString(result));
// 5. 测试Predicate的negate方法, 返回list中大于等于3,小于等于5的数字, 即对场景4取反
result = list.stream().filter(predicate1.negate()).toArray();
print("5---> " + Arrays.toString(result));
// 6. 测试静态方法isEqual方法, 个人感觉这个方法没啥用处
predicate = Predicate.isEqual("七夜雪");
print("6---> " + predicate.test("七夜雪"));
print("6---> " + predicate.test("七夜雪1"));
}
}
map:把对象映射成另一种对象
这个操作可以对输入的数据进行一对一映射然后返回。看两个例子感受下。
// map 操作
List<Integer> integers = Arrays.asList(2, 3, 4, 6, 22, 55);
List<Integer> collect = integers.stream().map(t -> t * t).collect(Collectors.toList());
System.out.println("map操作: "+ collect.toString());
List<String> java_second = Arrays.asList("java second", "c++ first", "python third");
List<String[]> collect1 = java_second.stream().map(s -> s.split(" ")).collect(Collectors.toList());
System.out.println("map操作: "+collect1);
从收集的返回值以及输出的结果可以看出,map会把流中的每个元素进行map中的方法操作,每个操作完成的返回值add到一个list中最终返回。原始流中有多少个元素,最终collect的集合中就有多少个元素,只是元素的类型可能发生变化,就是一对一映射。
flatMap: 有一对一映射,也就有一对多映射。我们先来看下例子感受下。
// map 操作
List<Integer> integers = Arrays.asList(2, 3, 4, 6, 22, 55);
List<Integer> collect = integers.stream().map(t -> t * t).collect(Collectors.toList());
System.out.println("map操作: "+ collect.toString());
List<String> java_second = Arrays.asList("java second", "c++ first", "python third");
List<String[]> collect1 = java_second.stream().map(s -> s.split(" ")).collect(Collectors.toList());
System.out.println("map操作: "+collect1);
// flatMap 操作
List<String> collect2 = java_second.stream().flatMap(t -> Arrays.stream(t.split(" "))).collect(Collectors.toList());
System.out.println("flatMap 操作: " +collect2);
接着上面的例子对比看,从flatMap中参数知道,flatMap对每个元素处理完之后要返回一个stream,从结果看就知道,最终collect到的结果是个String集合,而且是源数据中的每个元素进行flatMap中的方法操作后得到的集合。
也就是说flatMap不是把每个元素进行一对一映射,而是把每个元素操作的结果汇成一个新的流。
看个例子加深下理解:https://www.jianshu.com/p/7fbd347eb107
sorted,把过滤得到数据进行排序
peek ,获取元素
limit:获取前n个元素
skip:丢弃前n个元素
@Test
public void limitOrSkipTest() {
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
ageList.stream()
.limit(3)
.forEach(age -> System.out.print(age+","));、//11,22,13
System.out.println();
ageList.stream()
.skip(3)
.forEach(age -> System.out.print(age+","));//14,25,26
}
reduce累加器,也是很有用的方法,可以参考这篇比较详细介绍的文章:https://blog.csdn.net/iteye_19045/article/details/106672718
其它的方法:
collect
min
max
count
anyMatch
allMatch
noneMatch
findFirst
findAny
iterator
Statistics:统计
@Test
public void mathTest() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("最小值:" + stats.getMin());
System.out.println("最大值:" + stats.getMax());
System.out.println("个数:" + stats.getCount());
System.out.println("和:" + stats.getSum());
System.out.println("平均数:" + stats.getAverage());
}
// 输出结果
// 最小值:1
// 最大值:6
// 个数:6
// 和:21
// 平均数:3.5
groupingBy:分组聚合,相当于mysql的group by
@Test
public void groupByTest() {
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
Map<String, List<Integer>> ageGrouyByMap = ageList.stream()
.collect(Collectors.groupingBy(age -> String.valueOf(age / 10)));
ageGrouyByMap.forEach((k, v) -> {
System.out.println("年龄" + k + "0多岁的有:" + v);
});
}
// 输出结果
// 年龄10多岁的有:[11, 13, 14]
// 年龄20多岁的有:[22, 25, 26]
partitioningBy:按条件分组
@Test
public void partitioningByTest() {
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
Map<Boolean, List<Integer>> ageMap = ageList.stream()
.collect(Collectors.partitioningBy(age -> age > 18));
System.out.println("未成年人:" + ageMap.get(false));
System.out.println("成年人:" + ageMap.get(true));
}
// 输出结果
// 未成年人:[11, 13, 14]
// 成年人:[22, 25, 26]
自己生成流:有些并不能直接得到流的。
@Test
public void generateTest(){
// 生成自己的随机数流
Random random = new Random();
Stream<Integer> generateRandom = Stream.generate(random::nextInt);
generateRandom.limit(5).forEach(System.out::println);// 如果不加limit 是个无限流
// 生成自己的 UUID 流
Stream<UUID> generate = Stream.generate(UUID::randomUUID);
generate.limit(5).forEach(System.out::println);
}
//使用limit进行短路
short-circuiting
有一种 Stream 操作被称作 short-circuiting
,它是指当 Stream 流无限大但是需要返回的 Stream 流是有限的时候,而又希望它能在有限的时间内计算出结果,那么这个操作就被称为short-circuiting
。例如 findFirst
操作。
findFirst:找出stream中第一个元素
@Test
public void findFirstTest(){
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional<Integer> firstNumber = numberList.stream()
.findFirst();
System.out.println(firstNumber.orElse(-1));
}
//找出第一个元素后就会停止遍历,相当于短路操作
解决终端操作只能一个的问题
Supplier<Stream<String>> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok 每次 get都会得到一个新的流
streamSupplier.get().noneMatch(s -> true); // ok
并行迭代器
//tryAdvance 相当于普通迭代器iterator 串行处理
public void iterator(){
AtomicInteger num = new AtomicInteger(0);
while(true){
boolean flag = spliterator.tryAdvance((i) ->{
num.addAndGet((int)i);
System.out.println(i);
});
if(!flag){
break;
}
}
System.out.println(num);
}
//trySplit将list分段,每段单独处理,为并行提供可能
public void spliterator(){
AtomicInteger num = new AtomicInteger(0);
Spliterator s1 = spliterator.trySplit();
Spliterator s2 = spliterator.trySplit();
spliterator.forEachRemaining((i) ->{
num.addAndGet((int)i);
System.out.println("spliterator:"+i);
});
s1.forEachRemaining((i) ->{
num.addAndGet((int)i);
System.out.println("s1:"+i);
});
s2.forEachRemaining((i) ->{
num.addAndGet((int)i);
System.out.println("s2:"+i);
});
System.out.println("最终结果:"+num);
}
//利用分段,开启多线程处理
public void spliterator2() throws InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
run(spliterator.trySplit());
return "future1 finished!";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
run(spliterator.trySplit());
return "future2 finished!";
});
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
run(spliterator);
return "future3 finished!";
});
CompletableFuture<Void> combindFuture = CompletableFuture.allOf(future1, future2);
try {
combindFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("future1: " + future1.isDone() + " future2: " + future2.isDone());
System.out.println("最终结果为:" + count);
}
public void run(Spliterator s1) {
final String threadName = Thread.currentThread().getName();
System.out.println("线程" + threadName + "开始运行-----");
s1.forEachRemaining(new Consumer() {
@Override
public void accept(Object o) {
count.addAndGet((Integer)o);
}
});
System.out.println("线程" + threadName + "运行结束-----");
}
HashMap
JDK8优化了HashMap的实现, 主要优化点包括:
- 将链表方式修改成链表或者红黑树的形式
- 修改resize的过程,解决JDK7在resize在并发场景下死锁的隐患
- JDK1.7存储使用Entry数组, JDK8使用Node或者TreeNode数组存储
当链表长度大于8是链表的存储结构会被修改成红黑树的形式。
查询效率从O(N)提升到O(logN)。链表长度小于6时,红黑树的方式退化成链表。
JDK7链表插入是从链表头部插入, 在resize的时候会将原来的链表逆序。
JDK8插入从链表尾部插入, 因此在resize的时候仍然保持原来的顺序。
其他
新增JVM工具:jdeps提供了用于分析类文件的命令行工具。
使用metaSpace代替永久区
新增NMT(Native Memeory Trace)本地内存跟踪器
日期和时间api:
老版本的Date类有两个,java.util.Date和java.sql.Date线程不安全