JDK1.8新特性
jdk经典版本
1 JDK1.0
2 JDK1.2 集合等框架
3 JDK1.5
(1)可变参数
(2)枚举
(3)注解
(4)包装类自动装箱与拆箱
(5)foreach
(6)泛型
...
5 JDK1.8
从Java9开始,JDK的版本更新周期变为半年一次,Java之前的都是长期支持版本,Java9之后不是所有版本都是长期支持版本,有的是临时版本或试用版本,Java11,Java17是长期支持版本。
Java8新特性:
(1)接口中增加了 静态方法和默认方法
(2)日期时间API的第三代 java.time及其子包
(3)Lambda表达式 --> 函数式编程思想引入到Java8中 方法引用和构造器引用
(4)StreamAPI --> 在内存中对数据进行处理
(5)Optional工具类 --> NullPointException异常
1 lambda表达式
强调做什么,而不是以什么形式做。
foreach为了简化迭代,是迭代的“语法糖”;
lambda可以简化匿名内部类的声明。
lambda表达式是引入了函数式编程思想,所谓函数式编程思想就是把函数堪称第一位的,可以把函数当成“参数”或者“返回值”,不想面向对象,过分强调“对象”的形式。
lambda表达式的演示:
public class exam {
public static void main(String[] args){
System.out.println("------------线程--------------");
// 匿名内部类实现一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello innerclass");
}
}).start();
// lambda方式
new Thread(()-> System.out.println("hello lambda")).start();
}
}
public class exam {
public static void main(String[] args){
// 忽略大小写进行排序
String[] str1 = {"Haha","new","birth","flOWER"};
String[] str2 = {"Haha","new","birth","flOWER"};
// 匿名内部类
Arrays.sort(str1, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
for (String s : str1) {
System.out.print(s+" ");
}
System.out.println();
//lambda
Arrays.sort(str2,((o1, o2) -> o1.compareToIgnoreCase(o2)));
for (String s : str2) {
System.out.print(s+" ");
}
}
}
(1)是不是所有的Java的语法都可以使用lambda表达式进行简化?
不是。
Java仍然是面向对象的编程语言,以面向对象为主。
(2)哪些情况下,可以使用lambda表达式?
lambda表达式其实就是实现SAM接口的语法糖。
(3)什么是SAM接口?
SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包括其他非抽象方法。
分析下列接口是否属于SAM接口。
Ⅰjava.lang.Runnable接口:是的,有单一抽象方法void run
Ⅱ java.lang.Cloneable接口:不是,他没有抽象方法
Ⅲ java.io.Serializable接口:不是,他没有抽象方法。
Ⅳ java.lang.Comparable接口:是,有单一抽象方法int compareTo(T t)方法。至于它的Equals是推荐重写的方法,因为比如说,有两个学生都是100分,我们通过compare认为这两个学生是“相等的”,排序时通过这个条件来排序,但是在逻辑上我们不认为是同一个学生(对象),所以期望实现一个equals抽象方法来分析“是不是同一个对象”。
Ⅴ java.util.Comparator接口:是,有单一抽象方法int compare(T t)方法,至于它的Equals是推荐重写的方法,因为比如说,有两个学生都是100分,我们通过compare认为这两个学生是“相等的”,排序时通过这个条件来排序,但是在逻辑上我们不认为是同一个学生(对象),所以期望实现一个equals抽象方法来分析“是不是同一个对象”。
Ⅵ java.lang.Iterable接口:是,有单一抽象方法Iterator iterator()
Ⅶ java.util.Iterator接口:不是
Ⅷ java.util.Collection,List,Set,Map<k,v>接口:不是
Ⅸ java.io.FileFilter接口:是,有单一抽象方法boolean accept(File pathname)
Ⅹ java.io.Externalizable接口:不是
Java8版本,在java.util.function 包中给我们引入了大量的函数式接口(Functional Interface)
(4)新的注解@FunctionalInterface?
Java8引入了函数式接口的概念之后,建议我们SAM接口,即函数式接口,在声明时加@FunctionalInterface注解标记。
Lambda表达式针对标记了@FunctionalInterface注解的接口进行使用。如果没有加@FunctionalInterface标记的接口(就算它符合SAM特征,也尽量不要用)
上面的接口中,符合SAM接口特征的,哪些增加了@FunctionalInterface注解标记。
(1)java.lang.Runnable接口: void run()
(5)java.util.Comparator接口:int compare(T t1, T t2)
(9)java.io.FileFilter接口:boolean accept(File pathname)
(5)函数式接口(SAM)
在java.util.function包中新增的函数式接口(有很多),我们可以分为四大类。
①消费型接口
经典代表:Cunsumer 抽象方法:void accept(T t)。
抽象方法的特点是:有参无返回值。(消费行为,有去无回)。
变形:
| 接口名 | 抽象方法 | 参数列表和返回值 |
|---|---|---|
| BiConsumer<T,U> | void accept(T t, U u) | 接收两个对象用于完成功能 |
| DoubleConsumer | void accept(double value) | 接收一个double值 |
| IntConsumer | void accept(int value) | 接收一个int值 |
| LongConsumer | void accept(long value) | 接收一个long值 |
| ObjDoubleConsumer | void accept(T t, double value) | 接收一个对象和一个double值 |
| ObjIntConsumer | void accept(T t, int value) | 接收一个对象和一个int值 |
| ObjLongConsumer | void accept(T t, long value) | 接收一个对象和一个long值 |
②供给型接口
经典代表:Supplier 抽象方法:T get().
抽象方法的特点是,无参有返回值。(空手套白狼)
变形:
| 接口名 | 抽象方法 | 参数列表和返回值 |
|---|---|---|
| BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
| DoubleSupplier | double getAsDouble() | 返回一个double值 |
| IntSupplier | int getAsInt() | 返回一个int值 |
| LongSupplier | long getAsLong() | 返回一个long值 |
③判断型接口
经典代表:Predicate 抽象方法:Boolean test(T t).
抽象方法的特点是:有参有返回值。但是返回值是Boolean。用来判断参数是否符合某一条件。
| 接口名 | 抽象方法 | 参数列表和返回值 |
|---|---|---|
| BiPredicate<T,U> | boolean test(T t, U u) | 接收两个对象 |
| DoublePredicate | boolean test(double value) | 接收一个double值 |
| IntPredicate | boolean test(int value) | 接收一个int值 |
| LongPredicate | boolean test(long value) | 接收一个long值 |
④功能型接口(函数型接口)
经典代表:Function<T,R> 抽象方法:R apply(T t)
抽象方法的特点是:有参有返回值。(有来有往,参数和返回值的类型可能不同)。
| 接口名 | 抽象方法 | 参数列表和返回值 |
|---|---|---|
| UnaryOperator | T apply(T t) | 接收一个T类型对象,返回一个T类型对象结果 |
| DoubleFunction | R apply(double value) | 接收一个double值,返回一个R类型对象 |
| IntFunction | R apply(int value) | 接收一个int值,返回一个R类型对象 |
| LongFunction | R apply(long value) | 接收一个long值,返回一个R类型对象 |
| ToDoubleFunction | double applyAsDouble(T value) | 接收一个T类型对象,返回一个double |
| ToIntFunction | int applyAsInt(T value) | 接收一个T类型对象,返回一个int |
| ToLongFunction | long applyAsLong(T value) | 接收一个T类型对象,返回一个long |
| DoubleToIntFunction | int applyAsInt(double value) | 接收一个double值,返回一个int结果 |
| DoubleToLongFunction | long applyAsLong(double value) | 接收一个double值,返回一个long结果 |
| IntToDoubleFunction | double applyAsDouble(int value) | 接收一个int值,返回一个double结果 |
| IntToLongFunction | long applyAsLong(int value) | 接收一个int值,返回一个long结果 |
| LongToDoubleFunction | double applyAsDouble(long value) | 接收一个long值,返回一个double结果 |
| LongToIntFunction | int applyAsInt(long value) | 接收一个long值,返回一个int结果 |
| DoubleUnaryOperator | double applyAsDouble(double operand) | 接收一个double值,返回一个double |
| IntUnaryOperator | int applyAsInt(int operand) | 接收一个int值,返回一个int结果 |
| LongUnaryOperator | long applyAsLong(long operand) | 接收一个long值,返回一个long结果 |
| BiFunction<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
| BinaryOperator | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
| ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
| ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
| ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | 接收一个T类型和一个U类型对象,返回一个long |
| DoubleBinaryOperator | double applyAsDouble(double left, double right) | 接收两个double值,返回一个double结果 |
| IntBinaryOperator | int applyAsInt(int left, int right) | 接收两个int值,返回一个int结果 |
| LongBinaryOperator | long applyAsLong(long left, long right) | 接收两个long值,返回一个long结果 |
Bi:二元 binary
Unary:一元
(6)Lambda表达式语法
ⅠLambda表达式是用来给“函数式接口”的变量或者参数赋值用的。
Ⅱ 语法格式
(形参列表)->{
lambda体
}
说明:
- ->:新的运算符,是lambda表达式,也称为Lambda操作符,又称为箭头符号,中间不要有空格
- (形参列表):这个形参列表是函数的参数列表,它不是程序员随便定义的,它是当前lambda表达式对应的函数式接口的抽象方法的(形参列表)。
- {lambda体}:这个lambda体其实就是实现当前Lambda表达式对应的函数式接口的抽象方法
总的来说:
lambda表达式就是实现 函数式接口 的抽象方法的代码。
只不过我们此时只关系(形参列表)和{方法体/lambda体}。
为什么?
(形参列表)决定了我们要给他传入的xx数据;
{lambda体}决定了它能够完成什么功能。
(7)lambda可以简化为:
①当{lambda体},只有一个语句时,我们可以省略{}以及语句后面的;(分号)。
②如果{lambda体}的唯一的语句时return语句,我们在省略{}和;的同时,要把return也省略掉。
③当lambda表达式的(形参列表)的类型是一致的或者是可以推断的,那么形参列表的类型可以省略。
④当lambda表达式的(形参列表)是空参,()不能省略。
⑤当lambda表达式的(形参列表)只有一个形参,它的数据类型已经省略的情况下,()可以省略。
public class TestLambdaGrammer2 {
public static void main(String[] args) {
System.out.println("------------第一个例子---------------");
//下面用匿名内部类的方式实现Runnable接口的方式启动一个线程,这个线程负责打印一句话:hello lambda
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello lambda");
}
}).start();
System.out.println("---------------------------");
/* new Thread(Runnable target).start();
new Thread()构造器的形参是Runnable接口,它是函数式接口,它有唯一的抽象方法void run(),所以可以使用Lambda表达式给他赋值
抽象方法:public void run()
形参列表:()
实现run()的方法体: { System.out.println("hello lambda"); }
对应的Lambda表达式:() -> { System.out.println("hello lambda"); }
*/
new Thread(() -> { System.out.println("hello lambda"); }).start();
//(1)当{Lambda体},只有一个语句时,我们可以省略Lambda体的{}以及语句后面的;
new Thread(() -> System.out.println("hello lambda")).start();
System.out.println("------------第二个例子---------------");
String[] arr = {"Lala","hello","HI","Joke"};
System.out.println("------------匿名内部类的方式---------------");
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
/*
我们调用的是Arrays类中public static <T> void sort(T[] a, Comparator<? super T> c)方法进行排序
这个方法的第二个形参类型,Comparator<? super T> ,它是一个函数式接口,他的抽象方法: int compare(T o1, T o2)
就可以使用Lambda表达式给形参 Comparator<? super T> c赋值。
抽象方法:int compare(T o1, T o2)
形参列表:(T o1, T o2)
方法体:{ return o1.compareToIgnoreCase(o2);}
Lambda表达式:(T o1, T o2) -> { return o1.compareToIgnoreCase(o2);}
这个T,在当前例子中,是代表String类型
Lambda表达式:(String o1, String o2) -> { return o1.compareToIgnoreCase(o2);}
*/
Arrays.sort(arr, (String o1, String o2) -> { return o1.compareToIgnoreCase(o2);});
//(1)当{Lambda体},只有一个语句时,我们可以省略Lambda体的{}以及语句后面的;
//(2)如果{Lambda体}的唯一的语句是一个return语句,我们在省略{}和;的同时,要把return也省略了。
Arrays.sort(arr, (String o1, String o2) -> o1.compareToIgnoreCase(o2));
//(3)当Lambda表达式的(形参列表)的类型是已知的或者是可以推断的,那么形参列表的类型可以省略
Arrays.sort(arr, (o1, o2) -> o1.compareToIgnoreCase(o2));
System.out.println("------------第三个例子---------------");
//Iterable接口,实现它就可以支持foreach循环
//我们看java.lang.Iterable<E>接口中,Java8增加了一个默认方法
//default void forEach(Consumer<? super T> action)
//Iterable接口增加的方法,它的子接口也拥有,它的子接口有Collection<E>,List<E>,Set<E>等
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
/*
调用list集合的 default void forEach(Consumer<? super T> action),这个方法的作用是遍历集合的元素
这个方法的形参是 Consumer<T>接口,是消费型接口的代表, 它的抽象方法是 void accept(T t)
它是函数式接口,就可以用Lambda表达式赋值
抽象方法:void accept(T t)
形参列表:(T t)
方法体:{打印集合中的元素}
Lambda表达式:(T t) -> {打印集合中的元素}
Lambda表达式:(T t) -> {System.out.println(元素);}
Lambda表达式:(T t) -> {System.out.println(t);}
这里的T的类型就是list集合的元素类型<String>
*/
list.forEach((String t) -> {System.out.println(t);});
//(1)当{Lambda体},只有一个语句时,我们可以省略{}以及语句后面的;
list.forEach((String t) -> System.out.println(t));
//(3)当Lambda表达式的(形参列表)的类型是已知的或者是可以推断的,那么形参列表的类型可以省略
list.forEach((t) -> System.out.println(t));
//(5)当Lambda表达式的(形参列表)只有一个形参,它的数据类型已经省略的情况下,()可以省略
list.forEach(t -> System.out.println(t));
}
}
(8)练习
①自定义函数式接口
package com.atguigu.lambda;
/*
1、自定义函数式接口
要求:只有一个抽象方法,使用@FunctionalInterface注解标记
举例:
*/
public class TestDefineFunctionalInterface {
public static void main(String[] args) {
// Call call = 接口的实现类对象;
//因为Call是函数式接口,可以使用Lambda表达式赋值
//Call接口的抽象方法 void shout()
Call call = () -> System.out.println("妈妈喊你回家吃饭");
//可以调用call对象的shout方法
call.shout();
}
}
//自己定义的接口
//如果接口中只有一个抽象方法,可以标记为函数式接口,用@FunctionalInterface注解标记
@FunctionalInterface
interface Call{
void shout();//唯一的抽象方法
}
②消费型接口
/*
练习2:消费型接口
1、经典代表:Consumer<T>
抽象方法:void accept(T t)
例子:Iterable<T>中forEach方法
default void forEach(Consumer<? super T> action)
2、
JDK1.8中的Map接口,增加了一些方法,其中有一个:
default void forEach(BiConsumer<? super K,? super V> action)
*/
public class TestConsumer {
@Test
public void test03(){
HashMap<Integer,String> map = new HashMap<>();
//添加元素,用put
map.put(1, "java");
map.put(2, "c");
map.put(3, "python");
/*
回忆之前遍历map:
Set<Map.Entry<K,V>> entrySet()
Set<K> keySet()
Collection<V> values();
今天遍历集合用新的forEach方法
void forEach(BiConsumer<? super K,? super V> action)
forEach方法的形参是BiConsumer,是函数式接口,是Consumer接口的变形,可以使用Lambda表达式赋值
BiConsumer<T,U>接口的抽象方法, void accept(T t, U u)
我们forEach方法的形参类型BiConsumer<? super K,? super V>对应BiConsumer<T,U>
T就是K
U就是V
抽象方法的类型不能随便改,抽象方法的形参名字,可以自己修改。
原来抽象方法的形参名分别是t和u,我可以修改为key,value
*/
map.forEach((key, value) -> System.out.println("key:" + key + ",value=" +value));
}
@Test
public void test02(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
/*
list.forEach(Consumer<? super T> action)遍历集合
forEach方法的形参Consumer类型,是函数式接口,可以使用Lambda表达式
Consumer接口的抽象方法void accept(T t)
Lambda表达式: (T t) -> {遍历集合的元素要做的事情}
*/
list.forEach(t -> System.out.println(t));
}
@Test
public void test01(){
ArrayList list = new ArrayList();
list.add("hello");
list.add(1);
/*
list.forEach(Consumer<? super T> action)遍历集合
forEach方法的形参Consumer类型,是函数式接口,可以使用Lambda表达式
Consumer接口的抽象方法void accept(T t)
Lambda表达式: (T t) -> {遍历集合的元素要做的事情}
*/
list.forEach(t -> System.out.println(t));
}
}
③功能型接口
/*
练习4:功能型接口
经典代表:Function<T,R> R:return 返回值类型
抽象方法:R apply(T t)
例子:在JDK1.8时Map接口增加了很多方法,
public default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
变形:BiFunction<T,U,R>
抽象方法:R apply(T t, U u)
*/
public class TestFunction {
@Test
public void test02(){
HashMap<Integer,String> map = new HashMap<>();
//添加元素,用put
map.put(1, "java");
map.put(2, "c");
map.put(3, "python");
//需求:把里面的value,取首字母存储,即原来是java,变成j
/*
调用map的void replaceAll(BiFunction<? super K,? super V,? extends V> function)方法
replaceAll方法的形参是BiFunction<? super K,? super V,? extends V>是函数式接口,可以使用Lambda表达式
BiFunction<T,U,R>接口的抽象方法R apply(T t, U u)
看这个BiFunction<? super K,? super V,? extends V>来判断T,U,R分别代表:
T:key的类型
U:value的类型,原来value的类型
R:value的类型,替换之后value的类型
Lambda表达式:(T t, U u) -> {...}
Lambda表达式:(K key, V value) -> {把value转换为大写}
*/
map.replaceAll((key,value) -> value.charAt(0)+"");
//遍历结果
map.forEach((key,value) -> System.out.println(key+","+value));
}
@Test
public void test01(){
HashMap<Integer,String> map = new HashMap<>();
//添加元素,用put
map.put(1, "java");
map.put(2, "c");
map.put(3, "python");
//需求:把里面的value,转换为大写的形式
/*
调用map的void replaceAll(BiFunction<? super K,? super V,? extends V> function)方法
replaceAll方法的形参是BiFunction<? super K,? super V,? extends V>是函数式接口,可以使用Lambda表达式
BiFunction<T,U,R>接口的抽象方法R apply(T t, U u)
看这个BiFunction<? super K,? super V,? extends V>来判断T,U,R分别代表:
T:key的类型
U:value的类型,原来value的类型
R:value的类型,替换之后value的类型
Lambda表达式:(T t, U u) -> {...}
Lambda表达式:(K key, V value) -> {把value转换为大写}
*/
map.replaceAll((key,value) -> value.toUpperCase());
//遍历结果
map.forEach((key,value) -> System.out.println(key+","+value));
}
}
④供给型接口
/*
练习4:供给型接口
经典代表:Supplier<T>
抽象方法:T get()
例如:
在JDK1.8中增加了StreamAPI,java.util.stream.Stream<T>是一个数据流。
static <T> Stream<T> generate(Supplier<T> s),它是静态方法,通过“类型名.方法"
*/
public class TestSupplier {
@Test
public void test01(){
/*使用java.util.stream.Stream<T>接口的静态方法
static <T> Stream<T> generate(Supplier<T> s),创建一个数据流
generate方法的形参是 Supplier类型,它是函数式接口,可以使用Lambda表达式
Supplier接口的抽象方法 T get()
Lambda表达式: () -> {...}
Lambda体的功能是产生数据,这里我调用Math.random()方法,随机产生一个数字
*/
Stream<Double> stream = Stream.generate(() -> Math.random());
//查看Stream流中的元素
stream.forEach(t-> System.out.println(t));
}
}
2 方法引用和构造器引用
(1)方法引用与构造器引用的作用是干什么的?
作用:简化Lambda表达式
就像原来的匿名内部类不一定都可以使用Lambda表达式进行简化一样,Lambda表达式也不是所有情况都可以使用法引用与构造器引用进行简化的。
(2)什么情况下才可以使用方法引用进行简化呢?
①Lambda体只有一个语句,并且该语句是调用现有的类或对象的一个方法来完成
②Lambda体中所调用的方法,使用的实参,都是来自于 Lambda表达式的形参列表,而没有其他额外的参数
(3)什么情况下才可以使用构造器引用进行简化呢?
①Lambda体只有一个语句,并且该语句是通过new一个对象来完成的
②Lambda体中所调用的构造器,使用的实参,都是来自于 Lambda表达式的形参列表,而没有其他额外的参数
(4)形式
①类名或对象名 :: 方法名 方法引用
②类名/数组类型::new 构造器引用
例子:
public class TestFunctionReference {
@Test
public void test07(){
//调用这个createArray方法,得到一个对象数组
//createArray方法的第一个形参是Function,它是函数式接口,可以使用lambda表达式
//Function<T,R>接口的抽象方法: R apply(T t)
//Function<Integer,String[]> fun;
// createArray( (Integer t) -> {return new String[t];},10);
String[] array = createArray(String[]::new, 10);
System.out.println(array.length);//16
}
/*
下面的方法,可以创建一个对象数组,并且该对象数组的长度是与指定的length最接近的2的幂次方
*/
public <R> R[] createArray(Function<Integer,R[]> fun, int length){
int n = length - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
length = n < 0 ? 1 : n + 1;//上面这段代码的作用就是把length处理成2的幂次方
/*
假设length一开始是30
int n= length-1=29; 29的二进制 0000 0000 0000 0000 0000 0000 0001 1101
n |= n >>> 1; n = n | (n>>>1) n 29的二进制 0000 0000 0000 0000 0000 0000 0001 1101
n>>>1的二进制 0000 0000 0000 0000 0000 0000 0000 1110
| 0000 0000 0000 0000 0000 0000 0001 1111
n |= n >>> 2; n = n | (n>>>2) n 0000 0000 0000 0000 0000 0000 0001 1111
n>>>2 0000 0000 0000 0000 0000 0000 0000 0111
| 0000 0000 0000 0000 0000 0000 0001 1111
....
发现>>>然后又|,结果就是 一个特殊的二进制,右边全是1,左边全是0
length = n < 0 ? 1 : n + 1; 如果n不是小于0,length=n+1,就得到了2的幂次方
*/
return fun.apply(length);
}
@Test
public void test06(){
// Supplier<String> supplier = () -> new String();
Supplier<String> supplier = String::new;
}
@Test
public void test05(){
String[] arr = {"hello","HI","chai","lala"};
// Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));
Arrays.sort(arr, String::compareToIgnoreCase);
}
@Test
public void test04(){
Stream.generate(Math::random).forEach(System.out::println);
}
@Test
public void test03(){
Stream<Double> stream = Stream.generate(() -> Math.random());
//遍历流中的数据
stream.forEach(t-> System.out.println(t));
}
@Test
public void test02(){
new Thread(() -> System.out.println("hello lambda")); //无法使用方法引用进行简化
new Thread(System.out::println);//打印一个空行
}
@Test
public void test01(){
//集合遍历
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("atguigu");
//集合遍历的forEach
list.forEach(t -> System.out.println(t));
//进行简化
//这里Lambda体是调用 System.out对象的println方法来完成
list.forEach(System.out::println);
}
}
3 StreamAPI-->内存中的输入输出
(1)概述
Lambda表达式能够在原来的API中使用的不多,因为原来的API中满足函数式接口的情况不多。
新的Stream API中有大量的可以使用Lambda表达式的地方,因为它很多方法的参数类型都是函数式接口类型。
Stream API ( java.util.stream) 的作用是希望可以像sql一样,可以对“容器”中的数据进行处理,处理包括:筛选,统计,排序等等。
StreamAPI如何工作的?
(1)先根据某个“容器”,得到一个“数据流”
(2)调用StreamAPI中的方法,对数据进行“处理”。可以是处理0步,也可以处理1步,还可以处理n步 比喻成加工
(3)最终得到结果
StreamAPI的特点:
(1)处理期间,每一次都会得到一个新的数据流,不会影响原来的数据。
(2)StreamAPI的中间处理操作是一个延迟操作,即如果没有最后一步拿结果的代码,我所有中间的操作都不执行
(3)Stream本身是不负责存储数据,最终要存储结果啥的还得依靠“容器”
(2)创建Stream
①方式一:通过集合创建Stream
JDK1.8之后,集合类型增加了一些方法
例如:Collection系列的集合增加了:default Stream stream()
②方式二:通过数组创建Stream
JDK1.8之后,在数组工具类Arrays中增加了一些方法,可以利用某个数组,创建Stream
③方式三:Stream接口里面有一些静态方法可以创建Stream流对象
static Stream generate(Supplier s) :创建无限流
static Stream iterate(T seed, UnaryOperator f):创建无限流
static Stream of(T... values) :创建有限流
static Stream of(T t) :创建有限流
public class TestCreateStream {
@Test
public void test06(){
Stream<String> stream = Stream.of("hello", "world", "java");
}
@Test
public void test05(){
/*
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
iterate方法的第二个形参是UnaryOperator<T>,它是Function接口的子接口,是Function接口的变形。
interface UnaryOperator<T> extends Function<T,T>
Function<T,R> 抽象方法 R apply(T t)
UnaryOperator<T> 抽象方法 T apply(T t)
*/
Stream<Integer> stream = Stream.iterate(1, t -> t + 2);
stream.forEach(System.out::println);
}
@Test
public void test04(){
Stream<Double> generate = Stream.generate(Math::random);
}
@Test
public void test03(){
String[] arr = {"hello","world","java"};
Stream<String> stream = Arrays.stream(arr);
}
@Test
public void test02(){
HashMap<Integer,String> map = new HashMap<>();
map.put(1,"java");
map.put(2,"c++");
map.put(3,"python");
Stream<Map.Entry<Integer, String>> stream = map.entrySet().stream();
}
@Test
public void test01(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
Stream<String> stream = list.stream();
//只有拿结果时,上面创建才有意义
//比如:遍历结果,也是表示结果
stream.forEach(System.out::println);
/*
这里先演示创建stream,还未演示加工处理,所以看起来和直接遍历集合没有区别
*/
}
}
(3)中间操作
Stream的中间加工处理操作:可以0~n步 (1)Stream filter(Predicate<? super T> predicate):过滤
(2)Stream distinct() :去重
(3)Stream limit(long maxSize) :限制maxSize个元素
(4)Stream skip(long n) :跳过n个
(5)Stream peek(Consumer<? super T> action)
(6)Stream sorted():默认使用元素的Comparable接口的compareTo方法进行排序 Stream sorted(Comparator<? super T> comparator) :使用Comparator接口的compare方法进行排序
(7) Stream map(Function<? super T,? extends R> mapper) :映射操作,对流中的每一个元素执行xx操作,得到的结果构成新的流
(8) Stream flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
观察:上面所有方法的返回值类型都是Stream类型, 因为方法的返回值是Stream类型的结果,因此可以再对结果进行处理操作。即可以再调用Stream的其他方法。
public class TestStreamMiddleHandler {
@Test
public void test11() {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("Lily");
list.add("HanMeiMei");
list.add("LiLei");
list.stream().map(t-> Stream.of(t.split("|"))).forEach(System.out::println);
}
@Test
public void test10() {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("Lily");
list.add("HanMeiMei");
list.add("LiLei");
/*
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
flatMap方法的形参类型是:Function接口,是函数式接口,它的抽象方法:R apply(T t)
对比一下:
<R> Stream<R> map(Function<? super T,? extends R> mapper)
Function<T,R>接口 ==> map方法的形参Function,Function<? super T,? extends R>
T -> ? super T
R -> ? extends R
==>flatMap方法的形参Function, Function<? super T,? extends Stream<? extends R>>
T -> ? super T
R -> ? extends Stream<? extends R>
意思是对流中的元素t处理,处理完的结果是一个Stream
例如:现在对流中的元素 “hello"加工处理完,要得到一个Stream的流对象
*/
//需求:把每一个字符串打散,变成 字符数组,例如:hello-> h,e,l,l,o
//t.split("|")得到的是String[] "hello" ==> {"h","e","l","l","o"}
list.stream().flatMap(t-> Stream.of(t.split("|"))).forEach(System.out::println);
}
@Test
public void test09() {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("Lily");
list.add("HanMeiMei");
list.add("LiLei");
/*
<R> Stream<R> map(Function<? super T,? extends R> mapper)
map方法的形参是 Function接口类型,它是函数式接口,它的抽象方法:R apply(T t)
*/
//需求:把它们都转为大写
list.stream().map(String::toUpperCase).forEach(System.out::println);
}
@Test
public void test08() {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("Lily");
list.add("HanMeiMei");
list.add("LiLei");
//加工处理, 首先要取字符串长度超过4的字符串,按照字母顺序排序,不区分大小写
list.stream()
.filter(s->s.length()>4)
.sorted(String::compareToIgnoreCase)
.forEach(System.out::println);
}
@Test
public void test07() {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
/*
Stream<T> peek(Consumer<? super T> action)
形参是Consumer接口,是消费性的函数式接口,可以使用Lambda表达式
*/
long count = list.stream().peek(System.out::println).count();
System.out.println("count = " + count);
}
@Test
public void test06() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 3, 4, 5);
list.stream().skip(2).forEach(System.out::println);
}
@Test
public void test05() {
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
@Test
public void test04() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 3, 4, 5);
list.stream().limit(2).forEach(System.out::println);
}
@Test
public void test03(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 3, 4, 5);
/* //创建Stream
Stream<Integer> stream = list.stream();
//加工处理
stream = stream.distinct();
//看结果
stream.forEach(System.out::println);*/
list.stream().distinct().forEach(System.out::println);
}
@Test
public void test02(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
//创建Stream,加工处理,取结果合起来写
list.stream() //创建
.filter(t-> t.length()>4) //加工
.forEach(System.out::println);//结果
}
@Test
public void test01(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
//创建Stream
Stream<String> stream = list.stream();
//加工处理
/*
Stream<T> filter(Predicate<? super T> predicate)
(1)filter的形参是Predicate类型,是函数式接口,它的抽象方法 boolean test(T t)
(2)filter方法的返回值类型是Stream类型,需要接收
返回值类型是Stream,意味着可以再次调用Stream的方法
*/
//需求:只要流中长度>4字符串
stream = stream.filter(t-> t.length()>4);
//看结果
stream.forEach(System.out::println);
}
}
(4)终结操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。
流进行了终止操作后,不能再次使用。
意思:StreamAPI中的方法,只要返回值类型不是Stream类型都属于Stream的终结操作。
(1)void forEach(Consumer<? super T> action):遍历流中的数据
(2)long count() :统计流中的元素的个数
(3)boolean allMatch(Predicate<? super T> predicate):流中的所有元素是否都匹配xx条件
boolean anyMatch(Predicate<? super T> predicate):流中的元素是否有一些匹配xx条件
boolean noneMatch(Predicate<? super T> predicate)::流中的所有元素是否都不匹配xx条件
(4)Optional findFirst() :取出流的第一个
Optional findAny() :取出流的任意一个,如果当前流是一个固定的流,那么默认也返回第一个
这两个方法的返回值结果用Optional容器包装起来的
(5)Optional max(Comparator<? super T> comparator)
Optional min(Comparator<? super T> comparator)
(6)<R,A> R collect(Collector<? super T,A,R> collector)
collect方法的形参Collector是一个接口,又不是函数式接口,无法直接使用Lambda表达式,
看collect方法的API文档中给了我们一下样例:
API Note:
以下将将字符串累加到ArrayList中:
List asList = stringStream.collect(Collectors.toList()); 以下将按城市分类Person对象:
Map<String, List> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity)); 以下将按国家和城市对Person对象进行分类,将两个Collector组合在一起:
Map<String, Map<String, List>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
发现Collector接口需要配合Collectors工具类使用。
(7)Optional reduce(BinaryOperator accumulator)
T reduce(T identity, BinaryOperator accumulator)
public class TestStreamEndding {
@Test
public void test09(){
//Stream.of(1, 7, 3, 9, 5, 2, 6)把流中的元素累加起来,求它们的和
/*
Optional<T> reduce(BinaryOperator<T> accumulator)
reduce方法的形参是BinaryOperator类型,它是一个函数式接口,
BinaryOperator<T>它继承了BiFunction<T,T,T>
BiFunction<T,U,R>:R apply(T t, U u)
BinaryOperator<T>:T apply(T t1, T t2)
*/
Optional<Integer> result = Stream.of(1, 7, 3, 9, 5, 2, 6).reduce((t1, t2) -> t1 + t2);
System.out.println("result = " + result);
}
@Test
public void test08(){
List<Integer> collect = Stream.of(1, 7, 3, 9, 5, 2, 6).sorted().collect(Collectors.toList());
Collections.reverse(collect);
System.out.println(collect.stream().skip(2).findFirst());
}
@Test
public void test07(){
//取出流中排第三的数字,从大到小
//1,2,3,5,6,7,9
Optional<Integer> middle = Stream.of(1, 7, 3, 9, 5, 2, 6 ).sorted().skip(4).findFirst();
System.out.println(middle);
}
@Test
public void test06(){
/*
Optional<T> max(Comparator<? super T> comparator)
max方法的形参类型是Comparator类型,是函数式接口,可以使用Lambda表达式
*/
Optional<Integer> max = Stream.of(1, 7, 3, 9, 5).max(Integer::compareTo);
System.out.println("max = " + max);
}
@Test
public void test05(){
Optional<Integer> first = Stream.of(1, 7, 3, 9, 5).filter(t -> t % 2 == 0).findFirst();
System.out.println(first);
Optional<Integer> result = Stream.of(1, 2, 3, 4, 5).filter(t -> t % 2 == 0).findFirst();
System.out.println("result = " + result);
}
@Test
public void test04(){
System.out.println(Stream.of(1, 7, 3, 9, 5).anyMatch(t -> t % 2 == 0));//流中是否有偶数 false
System.out.println(Stream.of(1, 7, 3, 9, 5).noneMatch(t -> t % 2 == 0));//流中是否没有偶数 true
System.out.println(Stream.of(1, 7, 3, 9, 5).allMatch(t -> t % 2 == 0));//流中是否全是偶数 false
}
@Test
public void test03(){
System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(t -> t % 2 == 0));//流中是否有偶数 true
System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(t -> t % 2 == 0));//流中是否没有偶数 false
System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(t -> t % 2 == 0));//流中是否全是偶数 false
}
@Test
public void test02(){
long count = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(t -> t % 2 == 0).count();
System.out.println("count = " + count);//统计流中偶数的个数
System.out.println("----------------");
System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(t -> t % 2 == 0).count());
}
@Test
public void test01(){
Stream.of("hello","java","world").forEach(System.out::println);
}
}
4 Optional -->解决NullPointerException
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。
Java8为了避免空指针异常,做了很多的工作。
(1)ArrayList以前“new”ArrayList时,内部默认创建长度为10的数组。
因为很多方法的返回值类型就是ArrayList,有些程序员为了节省内存,当方法的结果没有元素时,就返回null处理,会导致调用这些方法的地方,另一些程序忘了判断结果是否为null,直接遍历集合导致空指针异常。Java8再次对ArrayList进行处理,“new”ArrayList时,内部默认创建一个长度为0的空数组,既节约的内存,又避免得到一个null结果。
(2)Optional类
也是用来解决很多方法的返回值类型可能返回null的问题。
例如:StreamAPI中的findFirst方法,它原来返回值应该是一个T ,即一个对象,但是有可能这个流中现在没有对象 Stream.of(1, 7, 3, 9, 5).filter(t -> t % 2 == 0).findFirst() 里面因为没有偶数,结果是null如果调用者并没有考虑到null的情况,直接用对象,很容易造成空指针异常。为了避免返回null,findFirst方法把返回值类型设置为Optional类型。至少会得到一个Optional对象,肯定不是null.
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。即Optional这个容器要么保存一个对象,要么什么都没有的空容器。
①如何用Optional包装对象
of(t):仅适用于包装非空对象
ofNullable(t):包装一个可能为空也可能非空的对象
empty():一定是空的元素
②如何从Optional中取出数据使用
T get() -->配合of方法用的
T orElse(T other) -->配合 ofNullable(t)方法使用
如果Optional容器中是非空,返回实际的元素,否则返回other替换元素
T orElseGet(Supplier<? extends T> other) -->配合 ofNullable(t)方法使用
如果Optional容器中是非空,返回实际的元素,否则返回Supplier供给型接口提供的对象
③其他方法
boolean isPresent() :判断Optional容器是否存在元素
void ifPresent(Consumer<? super T> consumer) :如果Optional容器有元素,就执行xx操作,否则就不执行
public class TestOptional {
@Test
public void test10(){
// String str = "hello";
String str = null;
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
opt.ifPresent(System.out::println);
}
@Test
public void test09(){
// String str = "hello";
String str = null;
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
System.out.println(opt.orElseGet(()->new String()));
}
@Test
public void test08(){
String str = "hello";
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
System.out.println(opt.orElse("world"));
}
@Test
public void test07(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
System.out.println(opt.orElse("world"));
}
@Test
public void test06(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
System.out.println(opt.get());//NoSuchElementException: No value present
}
@Test
public void test05(){
String str = "hello";
Optional<String> opt = Optional.of(str);//一定非空
System.out.println(opt.get());//取出里面的元素
}
@Test
public void test04(){
Optional<Object> opt = Optional.empty();//明确包装的是空的
}
@Test
public void test03(){
String str = "hello";
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
System.out.println(opt);
}
@Test
public void test02(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
System.out.println(opt);
}
@Test
public void test01(){
String str = "hello";
Optional<String> opt = Optional.of(str);//一定非空
System.out.println(opt);
}
}