一、Java8新特性
1、新特性
接口的变化
第三代日期时间API
HashMap底层变化
...
Lambda表达式
StreamAPI
Optional类
2、Lambda表达式是一种新的语法糖,它是让Java开始支持“函数式编程”的语法。
Java原来说自己是面向对象的编程语言,所有的操作几乎都是基于对象,“一切皆对象”,
如果某个变量或形参是引用数据类型,那么必须给他赋值一个对象。
在开发中,有时候我们并不关心这个对象,我们关心的是这个对象的某个方法的功能,方法体代码,其实就是函数的实现。
按照面向对象的编程思想,为了传递这段代码,还要new一个对象,就显得有点啰嗦。
函数式编程思想:把函数当成数据传递
public class TestJava8 {
@Test
public void test06(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//希望对这个数组进行排序,不区分大小写的排序
//调用Arrays工具类有sort方法
Arrays.sort(arr);//区分大小写的,因为这个方法是调用元素的类型String的compareTo方法排序
System.out.println(Arrays.toString(arr));//[Chai, Hi, Jock, atguigu, hello, world]
Arrays.sort(arr, String::compareToIgnoreCase);
System.out.println(Arrays.toString(arr));//[atguigu, Chai, hello, Hi, Jock, world]
}
@Test
public void test05(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//希望对这个数组进行排序,不区分大小写的排序
//调用Arrays工具类有sort方法
Arrays.sort(arr);//区分大小写的,因为这个方法是调用元素的类型String的compareTo方法排序
System.out.println(Arrays.toString(arr));//[Chai, Hi, Jock, atguigu, hello, world]
Arrays.sort(arr, (o1, o2) -> o1.compareToIgnoreCase(o2));
System.out.println(Arrays.toString(arr));//[atguigu, Chai, hello, Hi, Jock, world]
}
@Test
public void test04(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//希望对这个数组进行排序,不区分大小写的排序
//调用Arrays工具类有sort方法
Arrays.sort(arr);//区分大小写的,因为这个方法是调用元素的类型String的compareTo方法排序
System.out.println(Arrays.toString(arr));//[Chai, Hi, Jock, atguigu, hello, world]
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
System.out.println(Arrays.toString(arr));//[atguigu, Chai, hello, Hi, Jock, world]
}
@Test
public void test03(){
//需求:用实现Runnable接口的方式启动一个线程,打印“hello"
new Thread(() ->System.out.println("hello")).start();
}
@Test
public void test02(){
//需求:用实现Runnable接口的方式启动一个线程,打印“hello"
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}).start();
}
@Test
public void test01(){
//需求:用实现Runnable接口的方式启动一个线程,打印“hello"
MyRunnable my = new MyRunnable();
Thread t = new Thread(my);
t.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("hello");
}
}
二、函数式接口
Java中引入了函数式编程思想,但是只有部分情况才支持使用。
Lambda表达式只针对“函数式接口”使用。
1、函数式接口,是SAM接口(Single Abstract Method)有唯一的抽象方法的接口。
回忆之前学过的接口:
1)java.lang.Runnable:
抽象方法:public abstract void run();
(2)java.util.Comparator<T>
抽象方法: int compare(T o1, T o2);
从Comparator<T>接口中看有一个boolean equals(Object obj);,形式也像是抽象方法,
但是不要求我们实现Comparator<T>接口的类强制重写,因为实现类默认从Object继承了一个equals方法的实现。
但是它建议我们重写equals方法。希望equals方法的实现与compare方法的实现,逻辑一致。
例如:Student类 有重写equals方法,按照id来比较的,认为两个id相同的学生对象就是同一个学生对象,
Student类有一个Comparator的比较器StudentComparator,它实现Comparator<T>接口,重写compare方法,
这个方法中说两个学生的成绩score相等,就返回0,就代表他们是“相等”的对象。
如果说,我们把学生对象放到TreeSet中,有序的集合。
(3)java.lang.Comparable<T>
抽象方法:int compareTo(T o);
(4)java.lang.Iterable<T> :实现它,支持foreach遍历
抽象方法:Iterator<T> iterator();
(5)java.util.Iterator<T> 不是函数式接口,因为有多个抽象方法
抽象方法:
boolean hasNext();
E next();
(6)java.io.Serializable:不是函数式接口,因为没有抽象方法
(7)java.lang.Cloneable:不是函数式接口,因为没有抽象方法
(8)Collection,List,Set,Map都不是,因为有多个抽象方法
(9)java.io.FileFilter
抽象方法:
boolean accept(File pathname);
总结:之前学过的接口中,满足函数式接口的要求的
java.lang.Runnable、java.util.Comparator<T>、java.lang.Comparable<T>、java.lang.Iterable<T>、java.io.FileFilter
2、建议对满足函数式接口的特点的接口,并且接口上面标记了@FunctionalInterface注解的接口,再使用Lambda表达式。
上面满足函数式接口的特点的接口中,标记@FunctionalInterface注解的有:
java.lang.Runnable
java.util.Comparator<T>
java.io.FileFilter
java.lang.Comparable<T>接口,没有标记@FunctionalInterface注解,因为它的实现类通常是要比较大小的元素的类型,
例如:Student类本身实现这个接口,这个类里面信息非常丰富,一般不会只关注 compareTo这一个方法。
而我们使用Lambda表达式的场景,通常都是只关注接口的抽象方法的实现的常量。
java.lang.Iterable<T>接口,也没有标记@FunctionalInterface注解,因为它的实现类通常都是容器,ArrayList等,
它里面也不可能值关注Iterator<T> iterator();这一个方法。需要关注更多的add等方法。
总结:学过的接口中只有三个接口,可以使用Lambda表达式。其他的暂时不建议使用。
public class TestFunctionalInterface {
public static void main(String[] args) {
//TreeSet的特点:有序(按大小顺序),不可重复
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getScore() - o2.getScore();
}//错误的
/* @Override
public int compare(Student o1, Student o2) {
int result = o1.getScore() - o2.getScore();
return result == 0 ? o1.getId() - o2.getId(): result;
}*/
});
set.add(new Student(1,"张三",86));
set.add(new Student(1,"张三",86));
set.add(new Student(2,"李四",86));
System.out.println(set);//[Student{id=1, name='张三', score=86}]
}
}
class Student{
private int id;
private String name;
private int score;
public Student(int id, String name, int score) {
this.id = id;
this.name = name;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", score=" + score +
'}';
}
/*@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id; //为了突出问题,故意只选id
}
@Override
public int hashCode() {
return Objects.hash(id);
}*/
}
三、Lambda表达式
1、语法结构
(Lambda表达式的形参列表) -> {Lambda体}
说明:
(1)->:称为箭头操作符,或Lambda操作符
由减号和大于号组成,中间不能有空格
(2) (Lambda表达式的形参列表),它其实就是 你的lambda表达式要针对的函数式接口的抽象方法的形参列表
例如:Lambda表达式是针对java.lang.Runnable接口使用,Lambda表达式的形参列表就是()
因为java.lang.Runnable接口的抽象方法 void run()
Lambda表达式是针对java.util.Comparator<T>接口使用,Lambda表达式的形参列表就是(T t1, T t2)
因为java.util.Comparator<T>接口的抽象方法 int compare(T t1, T t2)
Lambda表达式是针对java.util.function.Consumer<T>接口使用,Lambda表达式的形参列表就是(T t)
因为 java.util.function.Consumer<T>接口的抽象方法void accept(T t)
(3) {Lambda体},它其实就是你实现这个抽象方法的方法体
2、Lambda表达式的作用是给函数式接口的变量/形参赋值的
3、Lambda表达式可以简化
(1)当{Lambda体}只有一个语句,可以省略{}和语句后面的;
(2)当{Lambda体}只有一个语句,可以省略{}和语句后面的;。如果此时是return语句,要把return也省略
(3)当(Lambda表达式的形参列表)的形参列表的形参类型是已知的,或者是可以根据泛型自动推断的,那么类型可以省略
(4)如果(Lambda表达式的形参列表)的形参列表的类型省略了,并且形参只有一个,那么()可以省略,
如果形参列表是多个参数,()不能省略
如果形参列表是空参(),那么()不能省略
public class TestLambda {
@Test
public void test04(){
//需求:列出"D:\to_student\尚硅谷_210728Java_柴林燕_JavaSE\预装软件"目录下的.exe文件
File dir = new File("D:\\to_student\\尚硅谷_210728Java_柴林燕_JavaSE\\预装软件");
/*
调用java.io.File类的public File[] listFiles(FileFilter filter)方法
这个方法的形参是 (FileFilter filter),FileFilter是函数式接口,可以使用Lambda表达式赋值
FileFilter接口的抽象方法 boolean accept(File pathname)
Lambda表达式 :(File pathname) -> { 过滤文件或目录的条件}
Lambda表达式 :(File pathname) -> { return pathname.getName().endsWith(".exe");}
*/
// File[] files = dir.listFiles((File pathname) -> { return pathname.getName().endsWith(".exe");});
//(2)当{Lambda体}只有一个语句,可以省略{}和语句后面的;。如果此时是return语句,要把return也省略
// File[] files = dir.listFiles((File pathname) ->pathname.getName().endsWith(".exe"));
//(3)当(Lambda表达式的形参列表)的形参列表的形参类型是已知的,或者是可以根据泛型自动推断的,那么类型可以省略
// File[] files = dir.listFiles((pathname) ->pathname.getName().endsWith(".exe"));
//(4)如果(Lambda表达式的形参列表)的形参列表的类型省略了,并且形参只有一个,那么()可以省略,
File[] files = dir.listFiles(pathname ->pathname.getName().endsWith(".exe"));
for (File file : files) {
System.out.println(file);
}
}
@Test
public void test03(){
//需求:列出"D:\to_student\尚硅谷_210728Java_柴林燕_JavaSE\预装软件"目录下的.exe文件
File dir = new File("D:\\to_student\\尚硅谷_210728Java_柴林燕_JavaSE\\预装软件");
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {//(File pathname)参数是dir这个目录的下一级文件或目录对象
return pathname.getName().endsWith(".exe");
}
});
for (File file : files) {
System.out.println(file);
}
}
@Test
public void test02(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//希望对这个数组进行排序,不区分大小写的排序
//调用Arrays工具类有public static <T> void sort(T[] a, Comparator<? super T> c)
//T[] a:接收一个对象数组,给哪个数组排序,就传哪个数组
//Comparator<? super T> c:需要一个Comparator接口的实现类对象
/*
这里不传Comparator接口的实现类对象,我们用Lambda表达式给他赋值。
Comparator接口的抽象方法 int compare(T t1, T t2)
Lambda表达式:
(T t1, T t2) -> {写t1,t2的排序规则}
T类型是什么? 因为Comparator接口的实现类对象,或者说,int compare(T t1, T t2)是给arr数组的元素排序,
这个数组是String[]类型,元素是String类型,这个T就是String类型
Lambda表达式:
(String t1, String t2) -> {写t1,t2的排序规则,区分大小写的排序}
(String t1, String t2) -> {t1.compareToIgnoreCase(t2);}
因为 int compare(T t1, T t2)有返回值类型,是int,所以在{}中需要return语句
Lambda表达式:
(String t1, String t2) -> {return t1.compareToIgnoreCase(t2);}
*/
// Arrays.sort(arr, (String t1, String t2) -> {return t1.compareToIgnoreCase(t2);});
//(2)当{Lambda体}只有一个语句,可以省略{}和语句后面的;。如果此时是return语句,要把return也省略
// Arrays.sort(arr, (String t1, String t2) -> t1.compareToIgnoreCase(t2));
//(3)当(Lambda表达式的形参列表)的形参列表的形参类型是已知的,或者是可以根据泛型自动推断的,那么类型可以省略
Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));
}
@Test
public void test01(){
//需求:用实现Runnable接口的方式启动一个线程,打印“hello"
//Thread(Runnable target),构造器需要一个Runnable接口的实现类对象,现在要传Lambda表达式
/*
使用Lambda表达式给(Runnable target)参数赋值。
java.lang.Runnable接口的抽象方法 void run()
Lambda表达式:
() -> {System.out.println("hell");}
*/
// new Thread(() -> {System.out.println("hello");}).start();
//当{Lambda体}只有一个语句,可以省略{}和语句后面的;
new Thread(() -> System.out.println("hello")).start();
}
}
4、演示Java8新增的函数式接口的Lambda表达式的应用
(1)消费型接口
Consumer<T> void accept(T t)
Java8不仅增加了函数式接口,还升级了集合框架的API。
在Java8版的java.lang.Iterable<T>接口中新增了一个默认方法:
default void forEach(Consumer<? super T> action):这个方法的作用是用来遍历/迭代 容器中的元素
观察这个方法的形参列表是 (Consumer<? super T> action),形参的类型Consumer是函数式接口,
所以调用这个方法,可以传入Lambda表达式。
因为Collection系列的集合继承/实现了Iterable<T>接口,所以Collection系列的集合都有了forEach方法。
(2)消费型接口
BiConsumer<T,U> 抽象方法 void accept(T t, U u)
Java8在java.util.Map<K,V>接口中也增加
default void forEach(BiConsumer<? super K,? super V> action)
forEach方法的形参:BiConsumer,它也是函数式接口,并且是Consumer的变形,也是消费性接口
(3)判断型接口
Predicate<T> 抽象方法 boolean test(T t)
Java8版本,对Collection接口增加了方法 default boolean removeIf(Predicate<? super E> filter)
这个方法的形参类型:Predicate,它是函数式接口,是判断型函数式接口
(4)功能型接口
BiFunction<T,U,R> 抽象方法 R apply(T t, U u)
在JDK1.8时Map接口增加方法:
default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
replaceAll方法的形参是 BiFunction,它是功能型接口
public class TestLambda2 {
@Test
public void test05(){
HashMap<Integer,String> map = new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"java");
//需求:把value的字符串中包含o字母的字符串,换成大写
//调用default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
/*
replaceAll方法的形参是 BiFunction,它是功能型接口,抽象方法 R apply(T t, U u)
Lambda表达式:
T:是map的key类型
U:是map的value类型
(Integer key, String value) -> {把value的字符串中包含o字母的字符串,换成大写}
(Integer key, String value) -> {
if(value.contains("o")){
return value.toUpperCase();//返回转为大写的value
}
return value;//返回原来的value
}
*/
map.replaceAll((Integer key, String value) -> {
if(value.contains("o")){
return value.toUpperCase();//返回转为大写的value
}
return value;//返回原来的value
});
//简化:
map.replaceAll((key, value) -> value.contains("o") ? value.toUpperCase() : value);
System.out.println(map);
}
@Test
public void test04(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world","mysql");
//需求,删除字符串中包含o字母的字符串
//调用default boolean removeIf(Predicate<? super E> filter)
/*
形参是Predicate,它是函数式接口,是判断型函数式接口,抽象方法boolean test(T t)
调用这个方法,需要传递参数,这个参数传递进去之后,在test方法肯定是用于条件判断,满足xx条件返回true,否则返回false。
Lambda表达式:(T t)->{判断t是否满足删除条件}
T类型就是集合元素的类型,String类型
Lambda表达式:(String t)->{ return t.contains("o");}
*/
// list.removeIf((String t)->{ return t.contains("o");});
//简化
// list.removeIf(t->t.contains("o"));
list.removeIf(element->element.contains("o")); //修改形参名,使得可读性更强
System.out.println(list);
}
@Test
public void test03(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world","mysql");
//需求,删除字符串中包含o字母的字符串
//原来Collection系列的集合,要根据条件删除,必须使用Iterator迭代器
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
if(str.contains("o")){
iterator.remove();
}
}
}
@Test
public void test02(){
HashMap<Integer,String> map = new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"java");
//调用map的default void forEach(BiConsumer<? super K,? super V> action)
/*
形参BiConsumer,它也是函数式接口,它的抽象方法void accept(T t, U u)
Lambda表达式: (T t, U u) 这个T和U其实就是Map的K,V
(Integer key, String value) -> {遍历键值对}
*/
// map.forEach((Integer key, String value) -> {System.out.println(key+":" + value);});
//简化
map.forEach((key, value) -> System.out.println(key+":" + value));
}
@Test
public void test01(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world","mysql");
//调用default void forEach(Consumer<? super T> action)方法
/*
形参是:Consumer类型,它是函数式接口,它的抽象方法 void accept(T t)
lambda表达式:
(T t) -> {遍历集合的元素}
T的类型,由集合的元素类型决定,是String类型
lambda表达式:
(String t) -> {System.out.println(t);}
*/
// list.forEach((String t) -> {System.out.println(t);} );
//简化
list.forEach(t -> System.out.println(t));
}
}
四、方法引用和构造器引用
1、Lambda表达式是用于给“函数式接口(SAM接口,只有一个抽象方法的接口)”的变量/形参赋值的一种语法。
可以简化/替代原来的匿名内部类的方式实现函数式接口的代码。
方法引用是对Lambda表达式的再次简化。
2、方法引用和构造器引用的语法格式:
方法引用: 类名/对象名 :: 方法名
构造器引用: 类名 :: new
3、说明
(1)当{lambda体}中只有一个语句,并且这个语句是调用现有的对象/类的方法来完成。
(2)Lambda表达式的(形参列表)中的形参,要么作为调用方法的对象,要么作为调用方法的实参,所有形参都用上了
(3)在整个{lambda体}中,没有使用到额外的数据/对象等
例如:Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));
(1){lambda体}中只有一个语句 return t1.compareToIgnoreCase(t2);
(2)Lambda表达式的(形参列表)是(t1, t2),t1是作为调用compareToIgnoreCase方法的对象,
t2是作为调用compareToIgnoreCase方法的实参。
例如:list.forEach(t -> System.out.println(t));
(1){lambda体}中只有一个语句 System.out.println(t)
(2)Lambda表达式的(形参列表)是(String t) , t作为println方法调用的实参。
例如:new Thread(()->System.out.println("hello")).start();
(1){lambda体}中只有一个语句 System.out.println("hello")
(2)Lambda表达式的(形参列表)是(),没有参数可以使用
(3)在整个{lambda体}中,没有使用到额外的数据/对象等 不满足,因为出现了"hello"
public class TestFunction {
@Test
public void test04(){
//供给型接口Supplier<T>,抽象方法T get()
//Lambda表达式可以直接给函数式接口的变量赋值
// Supplier<String> s = () -> new String();
//使用构造器引用进行简化
Supplier<String> s = String :: new;
}
@Test
public void test03(){
//需求:用实现Runnable接口的方式启动一个线程,打印“hello"
new Thread(()->System.out.println("hello")).start();//多线程打印了一个"hello"
//是否可以使用方法引用进行简化呢? 不能简化
new Thread(System.out::println).start(); //多线程只是打印了一个空行
}
@Test
public void test02(){
//集合遍历
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"hello","java","world");
//调用forEach方法进行遍历
//forEach的形参类型是Consumer<T>,抽象方法 void accept(T t)
// list.forEach(t -> System.out.println(t));
//使用方法引用进行简化
list.forEach(System.out::println);
}
@Test
public void test01(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//实现排序,不区分大小写的排序
// Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));
//使用方法引用来进行简化
Arrays.sort(arr, String::compareToIgnoreCase);
System.out.println(Arrays.toString(arr));
}
}
4、JDK1.8中新增加了很多函数式接口给我们使用,它们在java.util.function包。
四大代表:
(1)Consumer<T>,消费型接口 抽象方法:void accept(T t)
特点:有参无返回值 调用这个方法,需要给他传递实参,但是得不到返回值,“有去无回”
(2)Supplier<T>,供给型接口 抽象方法:T get()
特点:无参有返回值 调用者方法,不需要传递参数,但是可以得到一个返回值,“空手套白狼” 这个接口的抽象方法,相当于“奉献型”
(3)Predicate<T>,判断型接口,断定型接口 抽象方法:boolean test(T t)
特点:有参有返回值,但是返回值类型是boolean
调用这个方法,需要传递参数,这个参数传递进去之后,在test方法肯定是用于条件判断,满足xx条件返回true,否则返回false。
(4)Function<T,R>:功能型接口 抽象方法:R apply(T t)
特点:有参有返回值,参数的类型和返回值的类型不确定,并且可能不一样
调用这个方法,需要传递参数,也可以得到一个返回值,但是参数的类型和返回值的类型,需要在使用时才能确定,什么都有可能。所以这个方法相当于只是完成一个功能。
其他接口,都是上面这些接口的变形:
(1)Consumer<T>,消费型接口
bi:binary二元的,两个的
(2)Supplier<T>,供给型接口
(3)(3)Predicate<T>,判断型接口,断定型接口
(4)Function<T,R>:功能型接口
UnaryOperator:一元的,一个参数一个返回值,参数类型和返回值类型一样
BinaryOperator = Bi + UnaryOperator,有两个参数,一个返回值,参数和返回值的类型都是一样的
5、自定义函数式接口
(1)这个接口只能定义一个抽象方法
(2)这个接口加@FunctionalInterface注解
public class TestFunctionalInterface3 {
}
@FunctionalInterface
interface MyInterface{
void method();
}
//下面这个接口就没必要定义,因为java8的 java.util.function包下已经有相似的类型
//| IntConsumer | void accept(int value) | 接收一个int值
@FunctionalInterface
interface OtherInterface{
void method(int a);
}
五、Java8新增了一个工具类/容器:java.util.Optional<T>
1、Optional类的对象是一个容器,容器就是用来装对象的
可能里面是空的,可能里面有一个对象,
这个类通常用来当做一个方法的返回值来使用,避免返回null值。
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。
以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,
Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。
受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
例如:Stream的API中的一些方法返回值就是Optional类。
| **Optional<T>** **findFirst()** | 返回第一个元素 |
| **Optional<T>** **findAny()** | 返回当前流中的任意元素 | |
| **Optional<T>** **max(Comparator c)** | 返回流中最大值 |
| **Optional<T>** **min(Comparator c)** |返回流中的最小值
因为流中可能没有元素,那么这些方法就没有真正的元素返回,如果没有Optional,就需要返回null,
调用这些方法的地方,就可能发生空指针异常。
2、如何创建一个Optional类的对象
(1)创建一个空的容器对象 Optional.empty();
(2)创建一个容器对象,里面可能包含空值,可能包含具体值 Optional.ofNullable(x); x可能为空
(3)创建一个非空的容器对象, Optional.of(x); x一定是非空
3、如花使用Optional
(1)判断是否为空
boolean isPresent()
(2)获取里面的对象
T get() :取出Optional容器中的对象,要求里面必须有对象,否则抛出 NoSuchElementException
T orElse(T other) :如果Optional容器中有对象,那么直接返回里面 的对象,否则返回备胎
T orElseGet(Supplier<? extends T> other) :如果Optional容器中有对象,那么直接返回里面 的对象,否则返回Supplier供给型接口提供的备胎
(3)void ifPresent(Consumer<? super T> consumer)
public class TestOptional {
@Test
public void test01(){
Optional<Object> empty = Optional.empty();
System.out.println(empty);
}
@Test
public void test03(){
Optional<String> opt = findGirlFriend2("汪飞");
System.out.println(opt);
System.out.println("汪飞的女朋有是否存在: " + opt.isPresent());
System.out.println("女朋友:" + opt.get());
opt.ifPresent(System.out::println);//如果女朋友存在,就打印,否则就不打印
Optional<String> opt2 = findGirlFriend2("李天棋");
System.out.println("opt2 = " + opt2);// Optional.empty
System.out.println("李天棋的女朋有是否存在: " + opt2.isPresent());
// System.out.println("女朋友:" + opt2.get());
System.out.println("女朋友:" + opt2.orElse("石榴姐"));
System.out.println("女朋友:" + opt2.orElseGet(String::new));//空字符串
opt2.ifPresent(System.out::println);//如果女朋友存在,就打印,否则就不打印
}
public Optional<String> findGirlFriend2(String boyName){
HashMap<String,String > map = new HashMap<>();//这个集合后面是数据库中的数据
map.put("汪飞", "如花");
map.put("二虎", "翠花");
map.put("邱世玉", "似玉");
map.put("李天棋", null);
return Optional.ofNullable(map.get(boyName));
}
@Test
public void test02(){
String girl1 = findGirlFriend("汪飞");
System.out.println("girl1 = " + girl1);
System.out.println("姓:" + girl1.charAt(0));
String girl2 = findGirlFriend("李天棋");
System.out.println("girl2 = " + girl2);
System.out.println("姓:" + girl2.charAt(0));
}
public String findGirlFriend(String boyName){
HashMap<String,String > map = new HashMap<>();//这个集合后面是数据库中的数据
map.put("汪飞", "如花");
map.put("二虎", "翠花");
map.put("邱世玉", "似玉");
map.put("李天棋", null);
return map.get(boyName);
}
}
六、StreamAPI
1、Stream:流
我们之前在IO章节学习的 InputStream,OutputStream 和我们今天要学习的Stream不是一回事,没关系。
2、Java8新增的包,java.util.stream
作用:针对集合等容器中的数据进行操作的。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。
数据库:简单理解数据仓库,用来存储数据的。
数据库是一个管理系统,可以用来存储数据,并且管理数据(增删改查,备份...)
数据库分为:
文件系统的数据库(数据存储在硬盘上)
内存数据库(数据存储在内存中)
存在硬盘上的好处:永久性保存,就算系统断电了,数据是不会丢失的
内存数据库的好处:速度快
3、Stream的API的使用分为三步:
第一步:创建Stream
告诉Stream的数据来源是什么
第二步:中间操作,对数据进行处理
第三步:终结操作,看最后结果
4、Stream的API的特点
(1)Stream的对象是不可变对象,凡是修改会产生新Stream对象,必须重新接收
(2)Stream流的操作不会改变原数据
(3)中间操作可以是0~n步
(4)中间操作是一个延迟操作,在执行终结操作之前,中间不会执行,直到我们要执行终结操作,一口气执行
(5)一旦终结就结束了,除非创建新的Stream
(6)Stream不负责存储数据,只负责处理数据
public class TestStream {
@Test
public void test2() {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("张三",89));
list.add(new Student("李四",45));
list.add(new Student("王五",75));
//第一步:创建Stream
Stream<Student> stream = list.stream();
//第二步:中间操作,对数据进行处理
stream = stream.filter(s -> s.getScore()<60);//找出成绩不及格
// 第三步:终结操作,看最后结果
stream.forEach(System.out::println);
}
@Test
public void test(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"hello","world","java","hao");
//第一步:创建Stream
Stream<String> stream = list.stream();
//第二步:中间操作,对数据进行处理
stream = stream.filter(t -> t.contains("o"));//只留下包含“o”字母的单词
stream = stream.sorted();
// 第三步:终结操作,看最后结果
stream.forEach(System.out::println);
System.out.println(list);
}
}
class Student{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
System.out.println("getScore方法被调用了");
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
5、创建Stream的方式
(1)集合.stream()
Collection系列的集合,在Java8之后增加了一个方法:default Stream<E> stream()
Map系列的集合,要先转为Collection系列的集合再获取Stream。
(2)Arrays.stream(数组)
在数组工具类Arrays中增加了一批stream,可以根据数组来创建Stream
(3)Stream接口的静态方法,of方法,创建一个有限的Stream
Stream.of(T... args)
Stream.of(T t)
(4)Stream接口的静态方法,generate和iterate,创建无限的Stream
static <T> Stream<T> generate(Supplier<T> s)
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
public class TestCreateStream {
@Test
public void test11(){
/*
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
iterate方法的形参有两个:
第一个是T seed,种子
第二个是UnaryOperator<T>,它是今天学习的函数式接口的一种,
UnaryOperator<T>抽象方法T apply(T t) ,有参有返回值,参数和返回值的类型一样
需求:从种子(数字1)开始,不断的累加2
*/
//第一步创建Stream
Stream<Integer> stream = Stream.iterate(1, t -> t + 2);
//第三步:终结操作
stream.forEach(System.out::println);
}
@Test
public void test10(){
/*
static <T> Stream<T> generate(Supplier<T> s)
generate方法的形参类型是Supplier<T>,它是供给型函数式接口,抽象方法:T get()
Lambda表达式可以给 (Supplier<T> s)形参赋值
Lambda表达式:
() -> {有一个语句,可以产生数据}
*/
//第一步创建Stream
// Stream<Double> stream = Stream.generate(() -> {return Math.random();});
// Stream<Double> stream = Stream.generate(() -> Math.random());
//使用方法引用简化
Stream<Double> stream = Stream.generate(Math::random);
//第三步:终结操作
stream.forEach(System.out::println);
}
@Test
public void test9(){
Stream<String> stream = Stream.of("hello", "java", "world");
}
@Test
public void test8(){
Integer[] arr = {1,2,3,4};
Stream<Integer> stream = Arrays.stream(arr);
}
@Test
public void test4(){
int[] arr = {1,2,3,4};
IntStream stream = Arrays.stream(arr);
}
@Test
public void test3(){
String[] arr = {"hello","java","world"};
Stream<String> stream = Arrays.stream(arr);
}
@Test
public void test2(){
HashMap<Integer,String> map = new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"java");
Set<Integer> keys = map.keySet();
Stream<Integer> keyStream = keys.stream();
System.out.println("-------------------------");
Collection<String> values = map.values();
Stream<String> valueStream = values.stream();
System.out.println("-------------------------");
Set<Map.Entry<Integer, String>> entries = map.entrySet();
Stream<Map.Entry<Integer, String>> entryStream = entries.stream();
}
@Test
public void test(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"hello","world","java","hao");
Stream<String> stream = list.stream();
}
}
6、Stream的中间操作:这些方法的返回值类型都是Stream
(1)filter过滤
(2)distinct() 去重
(3)limit(long maxSize) 限制个数
(4)skip(long n)跳过前面的几个
(5)sorted():使用元素的自然排序,要求元素实现Comparable接口
sorted(Comparator com),使用定制排序,指定Comparator比较规则
(6)peek(Consumer action):对流中的元素做action指定的操作,但是不修改元素
(7)map(Function f):对流中的每一个元素映射xxx操作
(8)flatMap(Function f):把f函数式接口中的方法体得到的小的流的结果再合成大的Stream
public class TestMiddleStream {
@Test
public void test14(){
String[] arr = {"hello","world","java"};
//第一步:创建Stream
Stream<String> stream = Arrays.stream(arr);
//第二步:中间处理,把上面流中的每一个单词,拆开一个一个的字母
//t.split("|"):拆分字符串,得到一个一个的String[]数组,元素是单个的字母
//Stream.of(t.split("|"):把数组又构建成了一个小的Stream
//map:把Stream.of(t.split("|")得到Stream作为新的流的元素
Stream<Stream<String>> streamStream = stream.map(t -> Stream.of(t.split("|")));
//第三步:终结操作
streamStream.forEach(System.out::println);
}
@Test
public void test13(){
String str = "hello";
String[] strings = str.split("|");
System.out.println(Arrays.toString(strings));//[h, e, l, l, o]
}
@Test
public void test12(){
String[] arr = {"hello","world","java"};
//第一步:创建Stream
Stream<String> stream = Arrays.stream(arr);
//第二步:中间处理,把上面流中的每一个单词,拆开一个一个的字母
//t.split("|"):拆分字符串,得到一个一个的String[]数组,元素是单个的字母
//Stream.of(t.split("|"):把数组又构建成了一个小的Stream
//flatMap:把Stream.of(t.split("|")得到的一个一个的Stream,又合并成一个大的Stream
Stream<String> stringStream = stream.flatMap(t -> Stream.of(t.split("|")));
//第三步:终结操作
stringStream.forEach(System.out::println);
}
@Test
public void test10(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//取出每一个字符串的首字母
/*
map(Function f)方法的形参是Function<T,R>功能型接口,抽象方法:R apply(T t)
*/
Arrays.stream(arr).map(t-> t.charAt(0)).forEach(System.out::println);
/* //第一步:创建Stream
Stream<String> stream = Arrays.stream(arr);
//第二步:中间处理
Stream<Character> characterStream = stream.map(t -> t.charAt(0));
//第三步:终结
characterStream.forEach(System.out::println);*/
}
@Test
public void test09(){
//随机产生10个[0,100)的整数,然后取出前三名最大的数字
List<Integer> collect = Stream.generate(() -> (int) (Math.random() * 100))
.limit(10)
.peek(System.out::println)
.sorted((t1,t2)-> t2-t1)
.limit(3)
.collect(Collectors.toList());
System.out.println(collect);
}
@Test
public void test08(){
//随机产生10个[0,100)的整数,然后取出前三名最大的数字
List<Integer> collect = Stream.generate(() -> (int) (Math.random() * 100))
.limit(10)
.peek(System.out::println)
.sorted()
.skip(7)
.collect(Collectors.toList());
System.out.println(collect);
}
@Test
public void test07(){
//随机产生10个[0,100)的整数,然后取出前三名最大的数字
Stream.generate(() -> (int)(Math.random()*100)).limit(10).sorted().skip(7).forEach(System.out::println);
}
@Test
public void test06(){
//有一个字符串数组
String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
//按照字符串大小排序,不区分大小写
//方案一:Arrays.sort
//方案二:使用Stream
Arrays.stream(arr).sorted(String::compareToIgnoreCase).forEach(System.out::println);
}
@Test
public void test05() {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8);
//跳过前面3个,再取3个
list.stream().skip(3).limit(3).forEach(System.out::println);
}
@Test
public void test04(){
//使用Math.random()方法随机产生一些数据,取前10个
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
@Test
public void test03(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8);
//获取集合中不重复的元素,即去掉重复元素
//方案一:可以放到set中
//方案二:可以使用Stream
//三步连起来
list.stream().distinct().forEach(System.out::println);
}
@Test
public void test02(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8);
//获取list集合中的所有偶数
//三步连起来
list.stream().filter(t -> t % 2 == 0).forEach(System.out::println);
}
@Test
public void test01(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8);
//获取list集合中的所有偶数
//第一步:创建Stream
Stream<Integer> stream = list.stream();
//第二步:过滤,把偶数留下
/*
filter(Predicate p)方法的形参是判断型接口Predicate<T>,抽象方法 boolean test(T t)
*/
stream = stream.filter(t -> t % 2 == 0);
//第三步:终结操作,打印结果
stream.forEach(System.out::println);
}
}
7、Stream终结操作:当我们调用了Stream的某个方法,这个方法的返回值类型不是Stream类型,那么就说明这个操作是终结操作
(1)allMatch:判断流中的元素是否某匹配xx条件
(2)anyMatch:任意一个匹配
(3)noneMatch:没有一个匹配,都不匹配xx条件
(4)findFirst:返回第一个
findAny:返回任意一个 如果这个流的数据是固定,那么findAny和findFirst
如果流是一个无限流,或者流中元素不固定,findAny的结果就和findFirst不同
(5)long count()
(6)Optional<T> max(Comparator c)
Optional<T> min(Comparator c)
(7)void forEach(Consumer c)
(8)T reduce(T iden, BinaryOperator b)
U reduce(BinaryOperator b)
(9)R collect(Collector c):把流中剩下的元素收集到集合或其他的容器中
这个方法需要配合Collectors工具类
public class TestEndStream {
@Test
public void test11() {
//随机产生10个[0,10)之间的整数,打印10个整数,收集其中的偶数,不能重复
List<Integer> list = Stream.generate(() -> (int) (Math.random() * 10))
.limit(10)
.peek(System.out::println)
.filter(t->t%2==0)
.distinct()
.collect(Collectors.toList());
System.out.println("list = " + list);
}
@Test
public void test10() {
//随机产生10个[0,10)之间的整数,打印10个整数,收集其中的偶数,不能重复
Set<Integer> set = Stream.generate(() -> (int) (Math.random() * 10))
.limit(10)
.peek(System.out::println)
.filter(t -> t % 2 == 0)
.collect(Collectors.toSet());
System.out.println("set = " + set);
}
@Test
public void test09() {
//随机产生10个[0,10)之间的整数,打印10个整数,收集其中的偶数
List<Integer> list = Stream.generate(() -> (int) (Math.random() * 10))
.limit(10)
.peek(System.out::println)
.filter(t->t%2==0)
.collect(Collectors.toList());
System.out.println("list = " + list);
}
@Test
public void test08() {
//随机产生10个[0,10)之间的整数,打印10个整数,累加它们的和
Optional<Integer> result = Stream.generate(() -> (int) (Math.random() * 10))
.limit(10)
.peek(System.out::println)
.reduce((t1, t2) -> t1 + t2);
/*
reduce(BinaryOperator b)形参BinaryOperator<T>是函数式接口,抽象方法T apply(T t, T u)
*/
System.out.println("result = " + result);
}
@Test
public void test07() {
//随机产生10个[0,100)之间的整数,打印10个整数,并且返回最大值
Optional<Integer> max = Stream.generate(() -> (int) (Math.random() * 100))
.limit(10)
.peek(System.out::println)
.max((t1, t2) -> t1 - t2);
System.out.println("max = " + max);
}
@Test
public void test06() {
//随机产生10个[0,100)之间的整数,打印10个整数,并统计里面的偶数个数
ArrayList<Integer> list = new ArrayList<>();
int count = 0;
for(int i=0; i<10; i++){
int num = (int) (Math.random() * 100);
list.add(num);
if(num%2==0){
count++;
}
}
System.out.println(list);
System.out.println("偶数的个数count = " + count);
}
@Test
public void test05() {
//随机产生10个[0,100)之间的整数,打印10个整数,并统计里面的偶数个数
long count = Stream.generate(() -> (int) (Math.random() * 100))
.limit(10)
.peek(System.out::println)
.filter(t -> t % 2 == 0)
.count();
System.out.println("偶数的个数count = " + count);
}
@Test
public void test04() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//第一步:创建Stream
Stream<Integer> stream = list.stream();
//第二步:留下比5大的整数
stream = stream.filter(t -> t>5);
//第三步:findFirst
Optional<Integer> first = stream.findFirst(); //Optional是一个容器,它用来包装返回值结果
System.out.println("first = " + first);//Optional.empty
}
@Test
public void test03() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//第一步:创建Stream
Stream<Integer> stream = list.stream();
//第三步:findFirst
Optional<Integer> first = stream.findFirst(); //Optional是一个容器,它用来包装返回值结果
System.out.println("first = " + first);
}
@Test
public void test02(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//第一步:创建Stream
Stream<Integer> stream = list.stream();
//第二步:过滤
stream = stream.filter(t -> t % 2 == 0);//只留下偶数
//第三步:终结操作
boolean result = stream.allMatch(t -> t % 2 == 0);
System.out.println("result = " + result);
}
@Test
public void test01(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//第一步:创建Stream
Stream<Integer> stream = list.stream();
//第三步:终结操作
boolean result = stream.allMatch(t -> t % 2 == 0);
System.out.println("result = " + result);
}
}