这里写目录标题
1.Stream流
Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。
入门案例:
public class StreamDemo {
public static void main(String[] args) {
// System.out.println(getAuthors());
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.filter(author -> author.getAge()<18)
.forEach(author -> System.out.println(author.getName())); //必须要有终结操作
}
idea 对debug对stream流的直观体现:
创建流
单列集合: 集合对象.stream()
数组:Arrays.stream(数组)
或者使用Stream.of(数组)
来创建
双列集合:转换成单列集合后再创建
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作
-
filter 流中的元素进行条件过滤
-
map 可以把对流中的元素进行计算或转换。
这个Map是映射的意思,映射成其他的元素
public void t1(){
getAuthors().stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}).forEach(name-> System.out.println(name));
}
public void t1(){
getAuthors().stream()
.map(new Function<Author, Author>() {
@Override
public Author apply(Author author) {
author.setAge(author.getAge()+10);
return author;
}
}).forEach(author-> System.out.println(author.getAge()));
}
- distinct 可以去除流中的重复元素。
distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法
-
sorted 可以对流中的元素进行排序。
对象需要实现comparable接口 或者 传一个Comparator的匿名内部类
//author对象实现了omparable接口
@Override
public int compareTo(Author o) {
//返回负整数、零或正整数,因为此对象小于、等于或大于指定对象
return o.getAge()-this.getAge();
}
public void t2(){
getAuthors().stream()
.sorted()
.forEach(author -> System.out.println(author.getName()+author.getAge()));
}
@Test
public void t2(){
getAuthors().stream()
.sorted((o1, o2) -> o1.getAge()-o2.getAge()) //o1 应该是当前项目
.forEach(author -> System.out.println(author.getName()+author.getAge()));
}
- limit
可以设置流的最大长度,超出的部分将被抛弃。 limit 获取前N个元素
public void t3(){
getAuthors().stream()
//年龄最小的前2个
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.limit(2)
.forEach(author -> System.out.println(author.getName()+author.getAge()));
}
- skip 跳过流中的前n个元素,返回剩下的元素
@Test
public void t4(){
getAuthors().stream()
//跳过前两个
.skip(2)
.forEach(author -> System.out.println(author.getName()+author.getAge()));
}
-
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
public void t5(){ getAuthors().stream() .flatMap(new Function<Author, Stream<?>>() { @Override public Stream<?> apply(Author author) { return author.getBooks().stream(); } }) .forEach(book -> System.out.println(book)); } public void t5(){ getAuthors().stream() .flatMap(author -> author.getBooks().stream()) .forEach(book -> System.out.println(book)); }
终结操作
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作。
count
可以用来获取当前流中元素的个数。
@Test
public void t7(){
List<Author> authors = getAuthors();
long count = authors.stream()
.distinct()
.count();
System.out.println(count);
}
max&min
可以用来或者流中的最值。
public void t7(){
// 分别获取这些作家的所出书籍的最高分和最低分并打印。
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max((o1, o2) -> o1 - o2);
Integer integer = max.get();
System.out.println(integer);
}
collect
把当前流转换成一个集合。
List<Author> authors = getAuthors();
List<String> list = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList()); //使用 java.util.stream.Collectors; 这个工具类
System.out.println(list);
// set 一模一样
map 需要注意的是 key 数据一定不能重复(下面用了distinct 其实是对author对象euaqls 去重,也没实际是对name去重)
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
System.out.println(map);
查找与匹配
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。
public void t10() {
List<Author> authors = getAuthors();
boolean b = authors.stream()
.anyMatch(author -> author.getAge() > 18);
System.out.println(b);
}
public void t10() {
List<Author> authors = getAuthors();
boolean b = authors.stream()
.allMatch(author -> author.getAge() > 18);
System.out.println(b);
}
noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
// 判断作家是否都没有超过100岁的。
List<Author> authors = getAuthors();
boolean b = authors.stream()
.noneMatch(author -> author.getAge() > 100);
System.out.println(b); //true 就是都没有
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
例子:
获取任意一个年龄大于18的作家,如果存在就输出他的名字
List<Author> authors = getAuthors();
Optional<Author> optionalAuthor = authors.stream()
.filter(author -> author.getAge()>18)
.findAny();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
findFirst
获取流中的第一个元素。
例子:
获取一个年龄最小的作家,并输出他的姓名。
// 获取一个年龄最小的作家,并输出他的姓名。
List<Author> authors = getAuthors();
Optional<Author> first = authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.findFirst();
first.ifPresent(author -> System.out.println(author.getName()));
它俩区别的注意点~~
返回的元素是不确定的,对于同一个列表多次调用findAny()有可能会返回不同的值。使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,如果是并行的情况,那就不能确保是第一个。
所以 你测试的时候可能发现 他俩结果一样 可能是因为数据量小
reduce归并
内部操作:
发现 返回的值 是和元素的类型一样的 所以一般先进行map操作 连起来叫 map-reduce操作
@Test
public void t12() {
// 年龄和
List<Author> authors = getAuthors();
Integer sum = authors.stream()
.map(author -> author.getAge())
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer re, Integer elem) {
return re + elem;
}
});
System.out.println(sum);
}
min 和 max 操作其实就是reduce
使用reduce求所有作者中年龄的最大值
// 使用reduce求所有作者中年龄的最大值
List<Author> authors = getAuthors();
Integer max = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
System.out.println(max);
使用reduce求所有作者中年龄的最小值
// 使用reduce求所有作者中年龄的最小值
List<Author> authors = getAuthors();
Integer min = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
System.out.println(min);
注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
特殊:影响原数据
List<Author> authors = getAuthors();
authors.stream().forEach(author -> System.out.println("原数据"+author.getAge()));
authors.stream()
.map(new Function<Author, Author>() {
@Override
public Author apply(Author author) {
author.setAge(author.getAge() + 10);
return author;
}
})
.forEach(author -> System.out.println(author.getAge()));
authors.stream().forEach(author -> System.out.println("原数据"+author.getAge()));
//原数据都改变了
原数据33
原数据15
原数据14
原数据14
43
25
24
24
原数据43
原数据25
原数据24
原数据24
Process finished with exit code 0
@Test
public void t13() {
List<Author> authors = getAuthors();
authors.stream().forEach(author -> System.out.println("原数据"+author.getAge()));
authors.stream()
.map( author -> author.getAge()+10)
.forEach(age -> System.out.println(age));
authors.stream().forEach(author -> System.out.println("原数据"+author.getAge()));
}
原数据33
原数据15
原数据14
原数据14
43
25
24
24
原数据33
原数据15
原数据14
原数据14
Process finished with exit code 0
也就是不会影响集合内元素项的类型、个数、顺序。但由于值传递,可能改变元素项的属性
2.Optional
在JDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常。(比如mapper 查某个数据)
在实际开发中我们的数据很多是从数据库获取的。Mybatis从3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
并且在很多函数式编程相关的API中也都用到了Optional。
使用
创建
Optional就好像是包装类,可以把我们的具体数据封装Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
@Test
public void t1() {
Optional<Author> authorOptional = getAuthorOptional();
authorOptional.ifPresent(author -> System.out.println(author)); //如果不是null (ifPresent)才去消费
}
private Optional<Author> getAuthorOptional() {
Author author = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);
return Optional.ofNullable(author);//ofNullable创建
}
- 不经常用的两个创建方法:
如果你确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象。
Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);
但是一定要注意,如果使用of的时候传入的参数必须不为null。(尝试下传入null会出现什么结果)
如果一个方法的返回值类型是Optional类型。而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。这时则可以使用Optional的静态方法empty来进行封装。
Optional.empty()
- 更多使用 更方便的应该是
ofNullable
安全消费值
我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent方法对来消费其中的值。
这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安全了。
例如,以下写法就优雅的避免了空指针异常。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.ifPresent(author -> System.out.println(author.getName()));
获取值
如果我们想获取值自己进行处理可以使用get方法获取,但是不推荐。因为当Optional内部的数据为空的时候会出现异常。
@Test
public void t2() {
Optional<Author> authorOptional = getAuthorOptional();
Author author = authorOptional.get(); //如果下面 return Optional.ofNullable(null); 那么这一行会报错
System.out.println(author);
//Author(id=3, name=易, age=14, intro=是这个世界在限制他的思维, books=null)
}
private Optional<Author> getAuthorOptional() {
Author author = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);
return Optional.ofNullable(author);
}
安全获取值
如果我们期望安全的获取值。我们不推荐使用get方法,而是使用Optional提供的以下方法。
-
orElseGet
获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。
public void t2() { Optional<Author> authorOptional = getAuthorOptional(); Author author = authorOptional.orElseGet(() -> new Author());//否则获取 System.out.println(author); //Author(id=null, name=null, age=null, intro=null, books=null) } private Optional<Author> getAuthorOptional() { Author author = new Author(3L, "易", 14, "是这个世界在限制他的思维", null); return Optional.ofNullable(null); }
-
orElseThrow
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
@Test public void t3() { Optional<Author> authorOptional = getAuthorOptional(); Author author = null; try { author = authorOptional.orElseThrow(() -> new RuntimeException("数据为空")); } catch (Throwable e) { e.printStackTrace(); } System.out.println(author); } private Optional<Author> getAuthorOptional() { Author author = new Author(3L, "易", 14, "是这个世界在限制他的思维", null); return Optional.ofNullable(null); }
过滤
我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(author -> author.getAge()>100).ifPresent(author -> System.out.println(author.getName()));
判断
我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现Optional的好处,更推荐使用ifPresent方法。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent()) {
System.out.println(authorOptional.get().getName());
}
数据转换
Optional还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全。
例如我们想获取作家的书籍集合。
public void t4() {
Optional<Author> authorOptional = getAuthorOptional();
Optional<List<Book>> books = authorOptional.map(author -> author.getBooks());
books.ifPresent(books1 -> System.out.println(books1));
//[Book(id=1, name=刀的两侧是光明与黑暗, category=哲学,爱情, score=88, intro=用一把刀划分了爱恨), Book(id=2, name=一个人不能死在同一把刀下, category=个人成长,爱情, score=99, intro=讲述如何从失败中明悟真理)]
}
private Optional<Author> getAuthorOptional() {
Author author = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);
List<Book> books1 = new ArrayList<>();
books1.add(new Book(1L, "刀的两侧是光明与黑暗", "哲学,爱情", 88, "用一把刀划分了爱恨"));
books1.add(new Book(2L, "一个人不能死在同一把刀下", "个人成长,爱情", 99, "讲述如何从失败中明悟真理"));
author.setBooks(books1);
return Optional.ofNullable(author);
}