一、函数式编程思想:主要关注对数据进行什么操作,易于并发编程
二、Lambda表达式:是函数式编程的体现。
例:替换匿名内部类
//匿名内部类
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("test");
}
}).start();
//函数式编程 -> 只关注方法实现本省,去掉冗余代码
new Thread(()->{
System.out.println("test");
}).start();
省略规则:
参数可省略;方法体中只有一句代码时大括号return和唯一一句代码的分号可以省略;方法只有一个参数时小括号可以省略
三、Stream流:java8的Stream使用的是函数式编程模式,被用来对集合或数组进行链状流式操作。
创建流:
public class Author {
private String id;
private String name;
private int age;
private String intro;
private List<Book> book;
public Author(List<Book> book) {this.book = book;}
public void setBook(List<Book> book) {this.book = book;}
public List<Book> getBook() {return book;}
public String getId() {return id;}
public void setId(String id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public String getIntro() {return intro;}
public void setIntro(String intro) {this.intro = intro;}
}
public class Book {
private String id;
private String name;
private String category;
private String score;
private String intro;
public String getId() {return id;}
public void setId(String id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getCategory() {return category;}
public void setCategory(String category) {this.category = category;}
public String getScore() {return score;}
public void setScore(String score) {this.score = score;}
public String getIntro() {return intro;}
public void setIntro(String intro) {this.intro = intro;}
}
//单列集合
ArrayList<Author> authors = new ArrayList<>();
Stream<Author> stream = authors.stream();
//数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream1 = Arrays.stream(arr);
//双列集合
Map<String, Integer> map = new HashMap<>();
map.put("1",1);
map.put("2",2);
map.put("3",3);
Stream<Map.Entry<String, Integer>> stream2 = map.entrySet().stream();
中间操作:
/**
* filter:对流的元素进行条件过滤,符合过滤条件的才能继续留在流中
* map:对流中的元素进行计算或转换
* distinct:去重,注意:dis方法依赖Object方法来判断是否是相同的对象的,所以需要注意重写equals方法
* sorted:排序,注意:如果调用空参的sorted方法,需要流中的元素是实现了Comparable
* limit:可以设置流的最大长度,超出的部分将被抛弃
* skip:跳过流中的前n个元素,返回剩下的元素
* flatMap:(类似于partition by)map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
*/
authors.stream()
.distinct()
.filter(bean->bean.getAge()<18)
.forEach(bean-> System.out.println(bean.getName()));
authors.stream()
.map(bean->bean.getAge()+10)
.forEach(x-> System.out.println(x));
authors.stream()
.distinct()
.forEach(bean-> System.out.println(bean.getName()));
authors.stream()
.sorted(Comparator.comparing(bean->bean.getAge()))
.forEach(bean-> System.out.println(bean.getAge()));
authors.stream()
.distinct()
.sorted(Comparator.comparing(bean->bean.getAge()))
.limit(2)
.forEach(bean->bean.getName());
authors.stream()
.distinct()
.sorted(Comparator.comparing(bean->bean.getAge()))
.skip(1)
.forEach(bean->bean.getName());
authors.stream()
.flatMap(bean->bean.getBook().stream())
.forEach(book->book.getName());
authors.stream()//打印数据所有分类,要求对分类进行去重,按照“,”分割开
.flatMap(bean->bean.getBook().stream())
.distinct()
.flatMap(book->Arrays.stream(book.getCategory().split(","))) //把数字转换成流
.distinct();
终结操作:
/**
* 终结操作
* forEach:对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作
* count:可以用来获取当前流中元素的个数
* max&min(封装好的reduce):可以用来获取流中的最值
* collect:把当前流转换成一个集合
* 查找与匹配:
* anyMatch:可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
* allMatch:可以用来判断是否都符合匹配的条件,结果为boolean类型,如果都符合结果为true,否则结果为false
* noneMatch:可以判断流中的元素是否都不符合匹配条件,如果都不符合结果为true,否则结果为false
* findAny:获取流中任意一个元素,该方法没有办法保证获取的一定是流中的第一个元素
* findFirst:获取流中的第一个元素
*
* /**************reduce归并******************
* 对流中的数据按照你制定的计算方式计算出一个结果
* reduce的作用是把stream中的元素给结合起来,我们可以传入一个初始值,他会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面的元素计算
* 他的内部计算方式如下:
* T result = identity;
* for(T element : this stream)
* result = accumulator.apply(result,element)
* return result;
* 类似于:
* int resul = 0;
* for(int i : arr){
* result += result+i;
* }
* 其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
*
* 注意事项:
* *惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
* *流是一次性的(一旦一个流对象经过一个终结操作之后,这个流就不能再被使用)
* *不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。)
*/
authors.stream()
.map(bean->bean.getName())
.distinct()
.forEach(name-> System.out.println(name));
authors.stream()
.flatMap(bean->bean.getBook().stream())
.distinct()
.count();
Optional<Integer> max = authors.stream()
.flatMap(bean->bean.getBook().stream())
.map(x->x.getScore())
.max((score1,score2) -> score1 - score2);
authors.stream()
.map(bean->bean.getName())
.collect(Collectors.toList());
authors.stream()
.flatMap(bean->bean.getBook().stream())
.collect(Collectors.toSet());
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.getBook();
}
}));
Map<String, List<Book>> collect = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBook()));
//判断是否有年龄在28以上的作家
authors.stream()
.anyMatch(bean->bean.getAge()>28);
//判断是否所有的作家都是成年
authors.stream()
.allMatch(bean->bean.getAge() >= 18);
//判断所有的作家都没有超过100
authors.stream()
.noneMatch(bean->bean.getAge()>100);
//获取任意一个大于18的作家,如果存在就输出
authors.stream()
.filter(bean->bean.getAge()>18)
.findAny().ifPresent(bean-> System.out.println(bean.getName())); //如果不为null,则输出名字,为null则不输出
//获取一个年龄最小的作家,并输出他的名字
authors.stream()
.sorted(Comparator.comparing(bean->bean.getAge()))
.findFirst().ifPresent(bean->bean.getName());
//使用reduce求所有作者年龄的和
authors.stream()
.map(bean->bean.getAge())
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result+element;
}
});
authors.stream()
.map(bean->bean.getAge())
.reduce(0, (result,element) -> result + element);
//使用reduce求所有作者中年龄最大的
authors.stream()
.map(bean->bean.getAge())
.reduce(Integer.MIN_VALUE,(result,element) -> result< element?element:result);
//使用reduce求所有作者中年龄最小的
authors.stream()
.map(bean->bean.getAge())
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result > element ? element : result;
}
}).ifPresent(age-> System.out.println(age));
authors.stream()
.map(Author::getAge)
.reduce((result, element) -> result > element ? element : result);
Optional空值处理:
/**
* Optional:
* 我们在编写代码的时候出现最多的就是空指针异常,所以很多时候需要判断非空,java8中引入了Optional
* 养成Optional的习惯后你可以写出更优雅的代码来避免空指针异常。Mybatis3.5已经支持optional了
* 使用:
* 创建对象
* 我们一般使用Optional的静态方法ofNullable来把数据数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题
* 安全消费值:
* 使用ifPresent方法来消费
* Author author = getAuthor();
* Optional<Author> authorOptional = optional.ofNullable(author);
* authorOptional.ifPresent(author->sout(author.getName()));
* 安全的获取值:
* orElseGet:获取数据并且设置为空时的默认值。如果数据不为空就能获取到该数据,如果为空则根据传入的参数来创建对象作为默认的返回
* Optional<Author> authorOptional = optional.ofNullable(author);
* Author author = authorOptional.orElseGet(()->new Author());
* orElseThrow:获取数据,如果数据不为空就能获取到该数据。如果为空则根据传入的参数来创建异常
* Optional<Author> authorOptional = optional.ofNullable(author);
* try{
* Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author is null"));
* sout(author.getName());
* } catch (Throwable throwable){
* throwable.printStackTrace();
* }
* 过滤:
* 使用filter方法对数据进行过滤,如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象
* Optional<Author> authorOptional = optional.ofNullable(author);
* authorOptional.filter(author -> author.getAge()>100).ifPersent(author -> sout(author.getName()));
* 判断:
* 使用isPresent方法进行是否存在数据的判断,如果为空返回false,不为空返回true
* 数据转换:
* optional还提供map对数据进行转换。
* Optional<Author> authorOptional = optional.ofNullable(author);
* Optional<List<Book>> books = authorOptional.map(author -> author.getBooks());
* books.ifPresent(new Consumer<Lits<book>>(){
* public void accept(Lits<book> books){
* book.forEach(book->sout(book.getName();))
* }
* });
*/
函数式接口
/**
* 函数式接口
* 概述:只有一个抽象方法的接口我们称之为函数接口。
* JDK的函数式接口都加上@FunctionalInterface注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口
* 常见的函数式接口:
* Consumer消费接口:根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
* @FunctionalInterface
* public interface Consumer<T>{
* void accept(T t);
* }
* Function计算转换接口:
* @FunctionalInterface
* public interface Function<T,R>{
* R accept(T t);
* }
* Predicate判断接口
* @FunctionalInterface
* public interface Predicate<T>{
* Boolean test(T t);
* }
* Supplier生产型接口
* @FunctionalInterface
* public interface Supplier<T>{
* T get(T t);
* }
* 常用的默认方法
* and:我们在使用Predicate接口时候可能需要进行判断条件的拼接,而and方法相当于是使用&&来拼接两个判断条件
* or:使用||来拼接条件判断
* eg:打印作家中年龄大于17并且姓名长度大于1的作家
* 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()>1;
* }
* })).forEach(bean-> System.out.println(bean));
*/
方法引用
/**
* 方法引用
* 概述:我们在使用lambda时,如果方法中只有一个方法的调用的话,我们可以用方法引用进一步简化代码
* 基本格式:类名或对象名::方法名
* 引用类的静态方法:
* 使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是《调用了某个类的静态方法》,并且我们把要重写的抽象方法中所有的参数都按照顺序传入这个静态方法中,这个时候我们就可以引用类的静态方法。
* authors.stream()
* .map(author -> author.getAge())
* .map(age->String.valueOf(age)); ==> .map(String::valueOf);
* 引用对象的实例方法:
* StringBuilder sb = new StringBuilder();
* authors.stream()
* .map(author -> author.getName())
* .forEach(name->sb.append(name)); ==> .forEach(sb.append);
* 引用类的实例方法:
* 使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是《调用了第一个参数的成员方法》,并且把重写的抽象方法中剩余的所有参数都按照顺序传入了这个成员方法中
* public class Demo{
* interface UseString{
* String use(String str, int start, int length);
* }
* public static String subAuthorName(String str, UseString useString){
* int start = 1;
* int length = 1;
* return useString.use(str,start,length);
* }
* public static void main(String[] args){
* subAuthorName("xuyu", new UseString() {
* @Override
* public String use(String str, int start, int length) {
* return str.substring(start,length);
* }
* });
*
* ==>
*
* subAuthorName("xuyu", String::substring);
* }
* }
* 构造器引用:
* 基本格式:类名::new
* 使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是《调用了某个类的构造方法》,并且把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中
* authors.stream()
* .map(author -> author.getName())
* .map(name->new StringBuilder(name)) ==> .map(StringBuilder::new)
* .map(sb->sb.append("xuyu").toString())
* .forEach(str-> System.out.println(str));
*/
高级用法
/**
* 高级用法
* 概述:我们之前用到很多stream的方法都是用了泛型,所以涉及到的参数和返回值都是引用类型,即使我们操作的都是整数小数,
* 但是实际都是他们的包装类,JDK5中引入的自动装箱和拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便,但是这
* 中间存在大量时间消耗,需要优化,故stream提供了很多针对基本数据类型的方法:
* 方法: mapToInt,mapToLong,mapToDouble,flatMapToInt,flatToDouble等
* eg:
* authors.stream()
* .mapToInt(author->author.getAge())
* .map(age->age+10)
* .filter(age->age>18)
* .map(age->age+2)
* .forEach(System.out::println);
*/
并行流
/**
* 并行流
* 概述:当流中有大量元素时,我门可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去执行。如果我们自己去用代码
* 实现的话会非常复杂,而使用Stream,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现。
* Stream<Integer> tempStream = Stream.of(1,2,3,4,5,6,7,8,9);
* Integer sum = tempStream.parallel() //.parallel() 串行流转并行流
* .peek(new Consumer<Integer>() { //打印当前在那个线程执行
* @Override
* public void accept(Integer num) {
* System.out.println(num+Thread.currentThread().getName());
* }
* })
* .filter(num -> num > 5)
* .reduce((result, ele) -> result + ele)
* .get();
*/