为什么要学
- 大数量下处理集合效率高
- 代码可读性高
- 消灭嵌套地狱
// 查询未成年作家的评分在70以上的书籍
List<Book> bookList = new ArrayList<>();
Set<Book> uniqueBookValues = new HashSet<>();
Set<Author> uniqueAuthorValues = new HashSet<>();
for (Author author : authors){
if (uniqueAuthorValues.add(author)){
if (author.getAge() < 18){
List<Book> books = author.getBooks();
for (Book book : boooks){
if (book.getScore > 70){
if (uniqueBookValues.add(book)){
bookList.add(book);
}
}
}
}
}
}
函数式编程思想
概念
面对对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于我们数学中的函数,它主要关注的是对数据进行了什么操作。
优点
- 代码简洁,开发快速。
- 接近自然语言,易于理解。
- 易于并发编程
Lambda表达式
概述
Lambda是JDK8中的一个语法糖,它可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象,而是更关注我们对数据进行了什么操作,更关注抽象方法的参数传的是什么,重写的抽象方法体内的代码如何编写,匿名内部类是一个接口,接口中只有一个抽象方法需要重写。
核心原则
可推导可省略
基本格式
(参数列表)->{代码}
例一
我们在创建线程并启动时可以使用匿名内部类的写法:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行了");
}
}).start();
使用Lambda表达式:
new Thread(() ->{
System.out.println("线程执行了");
}).start();
new Thread(()-> System.out.println("线程执行了")).start();
例二
public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a,b);
}
普通写法:
public static void main(String[] args) {
int i = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
System.out.println(i);
}
Lambda写法:
int i = calculateNum((left, right) -> left + right);
System.out.println(i);
int i = calculateNum(Integer::sum);
System.out.println(i);
例三
public static void printNum(IntPredicate predicate){
int[] arr = {1,2,3,4,5,6};
for (int i : arr) {
if (predicate.test(i)){
System.out.println(i);
}
}
}
普通写法:
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value > 3;
}
});
Lambda写法:
printNum((value -> {return value > 3;}));
printNum((value -> value > 3));
例四
public static <R> R typeConver (Function<String, R> function){
String str = "123";
R result = function.apply(str);
return result;
}
普通写法
Integer i = typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
Lambda写法:
Integer integer = typeConver((String s) -> {
return Integer.valueOf(s);
});
Integer integer = typeConver(Integer::valueOf);
String s = typeConver((t) -> t);
例五
public static void foreachArr(IntConsumer consumer){
int[] arr = {1,2,3,4,5,6};
for (int i : arr) {
consumer.accept(i);
}
}
普通写法:
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
Lambda写法:
foreachArr((int value) -> {
System.out.println(value);
});
foreachArr(System.out::println);
省略规则
- 参数类型可以省略,参数只有一个的时候,括号可以省略。
- 方法体中只有一句代码时,大括号、return和唯一一句代码的分号可以省略。
Stream流
概述
Java8的stream使用的函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。
注:首先需要创建流,然后经历中间一系列操作,但只有存在终结操作时流才会真正去执行。
快速入门
打印所有年龄小于18的作家的名字,并且要注意去重
package com.lmj;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @EqualsAndHashCode:用于后期去重
* @author 92144
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Author {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
}
package com.lmj;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Book {
private Long id;
private String name;
/**
* 分类
* “哲学,小说”
*/
private String category;
private Integer score;
private String intro;
}
package com.lmj;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* @author 92144
*/
public class Test01 {
public static void main(String[] args) {
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();//集合转换成Author的流
authors.stream()//集合转换成Author的流
.distinct()//去重重复的作家
.filter(new Predicate<Author>() {//满足条件的会被留下,留下年龄小于18的
@Override
public boolean test(Author author) {
return author.getAge() < 18;
}
})
.forEach(new Consumer<Author>() {//循环消费打印名字
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
}
public static List<Author> getAuthors(){
Author author = new Author(1L,"张三",17,"厉害的作者",null);
Author author2 = new Author(2L,"李四",18,"厉害的作者",null);
Author author3 = new Author(3L,"王五",15,"厉害的作者",null);
Author author4 = new Author(3L,"王五",15,"厉害的作者",null);
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
books1.add(new Book(1L,"安徒生","哲学,童话",88,null));
books1.add(new Book(2L,"钢铁怎样炼成","个人传记,励志",99,null));
books2.add(new Book(3L,"灰姑娘","爱情,童话",85,null));
books2.add(new Book(4L,"活着","个人传记",100,null));
books2.add(new Book(5L,"霍元甲","武侠,历史",80,null));
books3.add(new Book(6L,"风与剑","武侠",70,null));
books3.add(new Book(7L,"风与剑","武侠",70,null));
books3.add(new Book(8L,"西游记","虚幻,历史",95,null));
author.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);
List<Author> authorList = new ArrayList<>(Arrays.asList(author,author3,author2,author4));
return authorList;
}
}
常用操作
创建流
- 单列集合:
集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
- 数组:
Arrays.stream(数组)
或使用Stream.of
来创建
Integer[] arr = {1,2,3};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
/*
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
*/
- 双列集合:转换成单例集合后再创建
Map<String,Integer> map = new HashMap<>(3);
map.put("哆啦美",5);
map.put("大雄",7);
map.put("胖虎",8);
// 先转化成单列set,再转换成流
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Stream<Map.Entry<String, Integer>> stream1 = entrySet.stream();
stream1.filter(new Predicate<Map.Entry<String, Integer>>() {
@Override
public boolean test(Map.Entry<String, Integer> entry) {
return entry.getValue() > 5;
}
}).forEach(new Consumer<Map.Entry<String, Integer>>() {
@Override
public void accept(Map.Entry<String, Integer> entry) {
System.out.println(entry.getKey() + entry.getValue());
}
});
Stream<Map.Entry<String, Integer>> stream3 = map.entrySet().stream();
中间操作
filter(满足条件的留下)
打印所有姓名长度大于2的作家名字
public static void main(String[] args) {
List<Author> authors = getAuthors();
authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length() > 1;
}
})
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
}
简写
authors.stream()
.filter(author -> author.getName().length() >1)
.forEach(author -> System.out.println(author.getName()));
Predicate接口
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
map(对流中的元素进行计算或转换)
打印所有作家的名称
public static void main(String[] args) {
List<Author> authors = getAuthors();
authors.stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
简写代码
authors.stream()
.map(author -> author.getName())
.forEach(str -> System.out.println(str));
//-----------------------------------------------------------------
authors.stream()
.map(Author::getName)
.forEach(System.out::println);
打印所有作家年龄加10
authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer + 10;
}
})
.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
authors.stream()
.map(Author::getAge)
.map(age -> age + 10)
.forEach(System.out::println);
Function接口
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
distinct(去除流中重复元素)
注:distinct方法是依赖Object的equals方法来判断是否相同对象的(不重写,用Object原生equals方法的话,比较的就是内存地址相同,对象相同,即==),所以需要重写equals方法(属性相同,则对象相同)。
package com.lmj;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Objects;
/**
* @EqualsAndHashCode:用于后期去重
* @author 92144
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Author {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return Objects.equals(id, author.id)
&& Objects.equals(name, author.name)
&& Objects.equals(age, author.age)
&& Objects.equals(intro, author.intro)
&& Objects.equals(books, author.books);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, intro, books);
}
}
打印所有作家姓名,并且要求其中不能有重复元素
public static void main(String[] args) {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.forEach(System.out::println);
}
sorted(对流中元素排序)
对流中的元素按照年龄进行降序排序,并且要求不能有重复元素
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1, o2) -> Integer.compare(o2.getAge(),o1.getAge()))
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author);
}
});
简易写法
public interface Stream<T> extends BaseStream<T, Stream<T>> { .... Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator); .... }
方式一:
authors.stream()
.distinct()
.sorted(((o1, o2) -> Integer.compare(o2.getAge(), o1.getAge())))
.forEach(System.out::println);
方式二:
package com.lmj;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Objects;
/**
* @EqualsAndHashCode:用于后期去重
* @author 92144
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Author implements Comparable<Author>{
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
@Override
public int compareTo(Author o) {
return o.getAge() - this.getAge();
}
}
authors.stream()
.sorted()//需要流中的元素具有比较能力,即实现Comparable接口
.forEach(System.out::println);
注:直接用空参的sorted(),需要流中的元素Comparable,即实现Comparable接口
limit(可以设置流的最大程度,超出的部分将被抛弃)
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o2.getAge() - o1.getAge();
}
})
.limit(2)
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author);
}
});
简易写法
authors.stream()
.distinct()
.sorted(((o1, o2) -> o2.getAge() - o1.getAge()))
.limit(2)
.forEach(System.out::println);
skip(跳过流中的前n个元素,返回剩下的元素)
打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o2.getAge() - o1.getAge();
}
})
.skip(1)
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author);
}
});
简易写法
authors.stream()
.distinct()
.sorted(((o1, o2) -> o2.getAge() - o1.getAge()))
.skip(1)
.forEach(System.out::println);
flatMap(flatMap可以把一个对象转换成多个对象作为流中的元素,map只能吧一个对象转化成另外一个对象作为流中的元素)
打印所有书籍的名字,并对重复元素进行去重。
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println(book);
}
});
简易写法
authors.stream()
.flatMap(author -> author.getBooks().stream())// 将每个List<Book>转化成Stream<Book>,并将每个Stream<Book>拼接起来
.distinct()
.forEach(System.out::println);
打印现有数据的所有分类,要求分类进行去重,不能出现这种格式:哲学,爱情
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.flatMap(new Function<Book, Stream<String>>() {
@Override
public Stream<String> apply(Book book) {
return Arrays.stream(book.getCategory().split(","));
}
})
.distinct()
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
简易写法
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(System.out::println);
终结操作
forEach(对流中的元素进行遍历操作,我们通过传入参数去指定对遍历到元素进行什么具体操作)
输出所有作家的名字
authors.stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.distinct()
.forEach(new Consumer<String>() {
@Override
public void accept(String str) {
System.out.println("作家姓名:" + str);
}
});
简易写法
authors.stream()
.map(Author::getName)
.distinct()
.forEach(System.out::println);
count(可以用来获取当前流中元素的个数)
打印这些作家所出书籍的数目,注意删除重复元素
List<Author> authors = getAuthors();
long count = authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.count();
简易写法
long count1 = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
max & min()
获取这些作家的所处书籍的最高分
List<Author> authors = getAuthors();
Optional<Book> max = authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.max(new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
return o1.getScore() - o2.getScore();
}
});
简易写法
Optional<Book> max1 = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.max((Comparator.comparingInt(Book::getScore)));
获取这些作家的所有书籍的最低分
Optional<Book> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.min(Comparator.comparingInt(Book::getScore));
collect(把当前流转换成一个集合)
- 归集(toList / toSet / toMap)
获取一个存放所有作者名字的List集合
List<Author> authors = getAuthors();
List<String> collect = authors.stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.collect(Collectors.toList());
简易写法
List<String> collect2 = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
// ------------------------------------------------
List<String> collect3 = authors.stream()
.map(Author::getName)
.collect(Collectors.toList());
获取一个所有书名的set集合
Set<String> collect1 = authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {//先将作者流转为书流
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.map(new Function<Book, String>() {//再从书流中提取出书名
@Override
public String apply(Book book) {
return book.getName();
}
})
.collect(Collectors.toSet());// 收集成set集合
简易写法
List<String> collect4 = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.collect(Collectors.toList());
// ------------------------------------------------------------
List<String> collect5 = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(Book::getName)
.collect(Collectors.toList());
获取一个map集合,map的key为作者名,value为List
Map<String, List<Book>> collect6 = authors.stream()
.collect(Collectors.toMap(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}, new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
}));
简易写法
Map<String, List<Book>> collect7 = authors.stream()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
- 统计(count / averaging)
Collectors提供了一系列用于数据统计的静态方法。
计数:counting
平均值:averagingInt averagingLong averagingDouble
最值:maxBy minBy
求和:summingInt summingLong summingDouble
统计以上所有:summarizingInt summarizingLong summarizingDouble
// 求作家人数
Long sum = authors.stream()
.collect(Collectors.counting());
// 求作家平均年龄
Double collect8 = authors.stream()
.collect(Collectors.averagingInt(new ToIntFunction<Author>() {
@Override
public int applyAsInt(Author author) {
return author.getAge();
}
}));
Double collect11 = authors.stream()
.collect(Collectors.averagingInt(Author::getAge));
// 求作家最大年龄
Optional<Author> collect9 = authors.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Author::getAge)));
// 求作家年龄和
Integer collect10 = authors.stream()
.collect(Collectors.summingInt(new ToIntFunction<Author>() {
@Override
public int applyAsInt(Author author) {
return author.getAge();
}
}));
int sum1 = authors.stream().mapToInt(Author::getAge).sum();
IntSummaryStatistics collect12 = authors.stream()
.collect(Collectors.summarizingInt(Author::getAge));
System.out.println(collect12);
// IntSummaryStatistics{count=4, sum=65, min=15, average=16.250000, max=18}
- 分组(partitioningBy/groupingBy)
分区:将stream按条件分成两个map,比如员工按薪资是否高于8000分为两部分。
分组:将集合分为多个map,比如员工按性别分组,有单级分组和多级分组。
// 分区
Map<Boolean, List<Author>> collect6 = authors.stream()
.collect(Collectors.partitioningBy(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
}));
Map<Boolean, List<Author>> collect7 = authors.stream()
.collect(Collectors.partitioningBy(author -> author.getAge() > 18));
// 单次分组
Map<Integer, List<Author>> collect13 = authors.stream().collect(Collectors.groupingBy(Author::getAge));
// 多次分组
Map<Integer, Map<String, List<Author>>> collect12 = authors.stream()
.collect(Collectors.groupingBy(Author::getAge, Collectors.groupingBy(Author::getName)));
- 接合(joining)
joining可以将stream中的元素用特定的连接符连接成一个字符串,没有的话直接连接。
String name = authors.stream()
.map(Author::getName)
.collect(Collectors.joining("-"));
// output
张-李四-王五-王五
- 规约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义规约的支持。
查找与匹配(anyMatch、allMatch、nonMatch)
- anyMatch(可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型,true为有符合类型,false为没有符合类型)
判断是否有年龄在29以上的作家
List<Author> authors = getAuthors();
boolean b2 = authors.stream()
.anyMatch(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 29;
}
});
简易写法
boolean b3 = authors.stream()
.anyMatch(author -> author.getAge() > 29);
- allMatch(可以用来判断是否都符合匹配条件,结果为boolean类型,如果都符合为ture,否则为false)
判断是否所有作家都是成年人
boolean b = authors.stream()
.allMatch(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
});
简易写法
boolean b1 = authors.stream()
.allMatch(author -> author.getAge() > 18);
- noneMatch(可以判断流中的元素是否都不符合条件,如果都不符合结果为true,有一个符合,结果就为false)
判断作家是否都没有超过100岁
boolean b4 = authors.stream()
.noneMatch(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 100;
}
});
简易写法
boolean b5 = authors.stream()
.noneMatch(author -> author.getAge() > 100);
- findAny(获取流中的任意满足条件的一个元素,该方法没有办法保证获取的一定是流中的第一个元素)
获取任意一个大于18岁的作家,如果存在输出他的名字
List<Author> authors = getAuthors();
Optional<Author> optional = authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
})
.findAny();
optional.ifPresent(author -> System.out.println(author.getName()));
简易写法
Optional<Author> optional = authors.stream()
.filter(author -> author.getAge() > 17).findAny();
optional.ifPresent(author -> System.out.println(author.getName()));
- findFirst(获取流中符合条件的第一个元素)
获取一个年龄最小的作家,并输出他的名字
Optional<Author> first = authors.stream()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o1.getAge() - o2.getAge();
}
})
.findFirst();
first.ifPresent(author -> System.out.println(author.getName()));
简易写法
Optional<Author> first1 = authors.stream()
.sorted(Comparator.comparingInt(Author::getAge))
.findFirst();
first1.ifPresent(author -> System.out.println(author.getName()));
Reduce
使用reduce求所有作者中年龄最大值
List<Author> authors = getAuthors();
Integer reduce = authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer identity, Integer element) {
return element > identity ? element : identity;
}
});
简易写法
Integer reduce = authors.stream()
.map(Author::getAge)
.reduce(Integer.MIN_VALUE, (identity, element) -> element > identity ? element : identity);
使用reduce求出作者中的年龄最小值
Integer reduce = authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer identity, Integer element) {
return identity < element ? identity : element;
}
});
简易写法
Integer reduce = authors.stream()
.map(Author::getAge)
.reduce(Integer.MAX_VALUE, (identity, element) -> identity < element ? identity : element);
- reduce一个参数的重载形式的内部逻辑
boolean foundAny = false;
T result = null;
for (T element : this stream){
if (! foundAny){
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
会将流中的第一个元素赋为初始值,如果一个值没有,就返回空的optional。
Optional<Integer> min = authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result < element ? result : element;
}
});
min.ifPresent(System.out::println);
简易写法
Optional<Integer> reduce5 = authors.stream()
.map(Author::getAge)
.reduce((result, element) -> result < element ? result : element);
reduce5.ifPresent(System.out::println);
高级用法(基本数据类型优化)
我们之前用到的很多Stream的方法由于都使用了泛型,所以设计到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是我们实际用的都是他们的包装类,JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方法。但是你一定要知道装箱和拆箱肯定是要消耗时间的,虽然这个时间消耗较小,但是在大量数据不断的重复装箱拆箱,你就不能无视这个时间的损耗了。
所以我们为了优化这部分的时间,Stream还提供了很多专门针对基本数据类型的方法。
例如:mapToInt mapToLong mapToDouble flatMapToInt flatMapToDouble等
authors.stream()
.map(Author::getAge)
.map(age -> age +10)//这里会先把Integer拆成int进行运算,在自动装箱成Integer返回
.filter(age -> age > 18)//这里会先把Integer拆成int进行筛选
.forEach(System.out::println);
优化:
authors.stream()
.mapToInt(new ToIntFunction<Author>() {
@Override
public int applyAsInt(Author author) {
return author.getAge();
}
})//这里已经变成IntStream,后面都是基于int类型计算,免于装箱拆箱
.map(age -> age +10)
.filter(age -> age > 18)
.forEach(System.out::println);
并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率,其实并行流就是把任务分配个多个线程去完成,如果我们自己去用代码实现的话会很复杂,而使用Stream的话,只需要调用方法即可。
authors.stream().parallel().forEach(System.out::println);
authors.parallelStream().forEach(System.out::println);
authors.parallelStream().peek(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(Thread.currentThread().getName());
}
}).forEach(author -> System.out.println(author.getName()));
// OUTPUT
main
ForkJoinPool.commonPool-worker-2
王五
王五
ForkJoinPool.commonPool-worker-2
张
ForkJoinPool.commonPool-worker-1
李四
peek方法可以用来调试Stream。
注意事项
- 惰性求值(如果没有终结操作,中间操作是不会得到执行的)。
- 流是一次性的(一旦一个流对象经过一个终结操作后,这个流就不能再被使用,否则报异常)。
- 不会影响原数据(我们在流中可以多数据做很多处理,但是正常情况下不会影响原来集合中的元素,这往往也是我们期望的)。
Optional
概述
我们在编写代码的时候出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断。
例如:
Author author = getAuthor();
if(author != null){
System.out.println(author.getName());
}
尤其是对象中的属性还是一个对象的情况下,这种判断会更多。
而过多的判断语句会让代码十分臃肿。
所以在JDK8中引入了Optional,养成使用Optional的习惯后可以写出更优雅的代码来避免空指针异常。
并且在很多函数式编程相关的API中也都用到了Optional。
使用
创建对象
Optional就好像是包装类,可以把我们具体数据封装到Optional对象内部,然后我们使用Optional中封装好的方法操作封装进去的数据,就可以非常优雅的避免空指针异常。
我们一般使用Optional的静态方法Optional.ofNullable()
来把数据封装到一个Optional对象。无论传入的参数是否为null都不会出现问题。
Author author = getAuthor();
Optional<Author> optional = Optional.ofNullable(author);
源码
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
此时,代码可能会觉得麻烦,但如果改造一下getAuthor方法,让它的返回值就是封装好的Optional的话,后面使用就会方便很多。
public static void main(String[] args){
Optional<Author> optional = getAuthor();
optional.ifPresent(System.out::println);
}
public static Optional<Author> getAuthor(){
Author author = new Author(1L,"张三");
return Optional.ofNullable(author);
}
而且在实际开发中我们很多数据从数据库获取的,Mybatis从3.5版本已经支持Optional了,我们可以直接把dao方法的返回值类型定义成Optional类型,Mybatis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
如果你确定一个对象不是空,则可以使用Optional的静态方法Optional.of()来把数据封装成Optional对象。不建议使用
Author author = new Author();
Optional<Author> optional = Optional.of(author);
如果调用Optional.of(null)
,会在封装的时候报空指针异常。
如果计算得到的返回值是null,我们也要把null封装成optional返回,我们可以用Optional的静态方法进行封装。
Optional.empty()
源码
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
安全消费值
我们获取到一个Optional对象后肯定需要对其中数据进行使用,这个时候我们可以使用ifPresent方法来消费其中的值,这个方法会判断其内部封装的数据是否为空,不为空时才会执行具体的消费代码,这样使用更加安全。
optional.ifPresent(System.out::println);
源码
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
获取值
如果我们想获取值自己进行处理可以使用get方法获取,但是不推荐,因为当Optional内部数据为空会出现异常。
安全获取值
如果我们期望安全的获取值,我们不推荐使用get方法,而是使用optional提供的以下方法。
- orElseGet
获取数据并且设置数据为空时的默认值,如果数据不为空就能获取到该数据,如果为空则根据你传入的参数来创建对象作为默认值返回。
Author author = optional.orElseGet(() -> new Author());
- orElseThrow
获取数据,如果数据不为空就获取到该数据,如果为空则根据你传入的参数来创建异常抛出。
try{
Author author = optional.orElseThrow(Supplier<Throwalbe>) () -> new RuntimeException("author为空"));
} catch(Throwable throwable){
thorwable.printStackTrace();
}
过滤
我们可以使用filter方法对数据进行过滤,如果原本是有数据的,但是不符合判断,也会变成一个新的无数据的Optional对象,即Optional.empty();
optional.filter(author -> author.getAge() > 18).ifPresent(author -> System.out.println(author.getName()));
判断
我们可以使用isPresent方法来进行是否存在数据的判断,如果为空返回值为false,如果不为空,返回值为true,但这种方式并不能体现optional的好处。更推荐使用ifPresent方法。
if(optional.isPresent()){
// 此时使用get方法是安全的
System.out.println(optional.get().getName());
}
数据转换
optional还提供了map可以让我们对数据进行转换,并且转换得到的数据也还是被optional封装好的,保证了我们的使用安全。
authors.map(new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
}).ifPresent(new Consumer<List<Book>>() {
@Override
public void accept(List<Book> books) {
System.out.println(books);
}
});
简易写法
authors.map(Author::getBooks).ifPresent(System.out::println);
函数式接口
概述
只有一个抽象方法的接口我们称之为函数式接口。
jdk的函数式接口都加上了@FunctionalInterface注解进行校验标识。但无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
常见函数式接口
- Consumer消费接口
根据其中抽象方法的参数列表和返回值类型知道,需要传入参数,但没有返回值,我们可以在方法中对传入参数进行消费。
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
- Function计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回。
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
- Predicate判断接口,过滤filter用到
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入参数条件判断,返回判断结果(boolean)
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
- Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
常用默认方法
Predicate当中的默认方法举例。
- and
我们在使用Predicate接口时候可能需要进行判断条件的拼接,而and方法相当于使用&&
来拼接两个判断条件。
打印作家中年龄大于17并且姓名长度大于1的作家。
List<Author> authors = getAuthors();
authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 17;
}
}.and(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length() > 2;
}
}))
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
简易写法
authors.stream()
.filter(author -> author.getAge() > 17 && author.getName().length() > 2).forEach(System.out::println);
- or
我们在使用Predicate接口时候可能需要进行判断条件的拼接,而or方法相当于使用||
来拼接两个判断条件
打印作家中年龄大于17或姓名长度小于2的作家
authors.stream().filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 17;
}
}.or(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length() < 2;
}
})).forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
简易写法
authors.stream().filter(author -> author.getAge() > 17 || author.getName().length() < 2).forEach(System.out::println);
- negate
negate相当于是在判断前面添加去反!
打印作家中年龄不大于17的作家。
authors.stream().filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 17;
}
}.negate()).forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
简易写法
authors.stream().filter(author -> !(author.getAge() > 17)).forEach(System.out::println);
方法引用
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
基本格式
类名或对象名::方法名
语法详解
- 引用静态方法(引用类的静态方法)
格式:
类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码时调用了某个类的静态方法,并且我们要重写的抽象方法中所有的参数都按照顺序传入这个静态方法中,这个时候我们就可以引用类的静态方法。
authors.stream()
.map(author -> author.getAge())
.map(new Function<Integer, String>() {
@Override
public String apply(Integer integer) {
return String.valueOf(integer);
}
});
优化:
authors.stream()
.map(author -> author.getAge())
.map(String::valueOf);
注意:如果我们重写的方法是没有参数的,调用的方法也是没有参数的话也是符合以上规则的。
- 引用对象的实例方法
格式:
对象名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码时调用了某个类的成员方法,并且我们要重写的抽象方法中所有的参数都按照顺序传入这个成员方法中,这个时候我们就可以引用这个对象的实例方法。
authors.stream().map(author -> author.getName())
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
sb.append(s);
}
});
优化:
authors.stream()
.map(author -> author.getName())
.forEach(sb::append);
- 引用类的实例方法
格式:
类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码时调用了第一个参数的成员方法,并且我们要重写的抽象方法中剩余的所有参数都按照顺序传入这个成员方法中,这个时候我们就可以引用这个类的实例方法。
interface UseString{
String use(String str, int start, int lenght);
}
public static String subAuthorName(String str, UseString userString){
int start = 0;
int length = 1;
return useString.use(str, start, length);
}
public static void main(String[] args){
List<Author> author = getAuthors();
subAuthorName("lmj",new UseString(){
@Override
public String use(String str, int start, int lenght){
return str.substring(start, length);
})
}
优化:
- 构造器引用
如果方法体中的一行代码是构造器的话可以使用构造器引用
格式:
类名::new
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码时调用了某个类的构造方法,并且我们要重写的抽象方法中所有参数都按照顺序传入这个构造方法中,这个时候我们就可以引用构造器。
authors.stream().map(author -> author.getName())
.map(new Function<String, StringBuilder>() {
@Override
public StringBuilder apply(String s) {
return new StringBuilder(s);
}
}).forEach(System.out::println);
优化:
authors.stream()
.map(Author::getName)
.map(StringBuilder::new)
.forEach(System.out::println);