1. 概述
函数式编程学习目的:
- 能够看懂公司里的代码
- 大数据量下处理集合效率更高
- 代码可读性高
- 消灭嵌套地狱
函数式编程思想:
面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数。他主要关注的是对数据进行了什么操作
优点:
- 代码简洁,快速开发
- 接近自然语言,易于理解
- 易于“并发编程”
2. Lambad表达式
概念:Lambda是JDK中的语法糖,它可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象,而是更关注我们对数据进行了什么操作。
核心原则:可推导则可省略
基本格式:(参数列表) -> {代码}
通过练习来学习Lambda表达式
例一:在创建一个线程时,可以使用以下匿名内部类的方式进行创建
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程启动...");
}
}).start();
}
而这个匿名内部类就可以用Lambda进行简化。
什么样的匿名内部类可以被简化呢?
当匿名内部类实现的是一个接口,且该接口中只有一个方法时,就可以进行简化
public static void main(String[] args) {
new Thread(() -> System.out.println("子线程启动...")).start();
}
例二:现有方法定义如下,其中IntBinaryOperator是一个接口。
public static int calculateNum(IntBinaryOperator intBinaryOperator) {
int a = 10;
int b = 20;
return intBinaryOperator.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表达式来调用该方法:
public static void main(String[] args) {
int i = calculateNum((int a, int b) -> {
return a + b;
});
System.out.println(i);
}
例三:现有方法定义如下,其中IntPredicate是一个接口。
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value % 2 == 0;
}
});
使用匿名内部类的方式调用该方法:
public static void main(String[] args) {
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value % 2 == 0;
}
});
}
使用Lambda表达式来调用该方法:
public static void main(String[] args) {
printNum((int value) -> {
return value % 2 == 0;
});
}
从三个例子中可以看出,Lambda表达式只关注参数列表与函数体,其他的都不重要
例四:现有方法定义如下,其中Function是一个接口。
public static <R> R typeConver(Function<String, R> function) {
String str = "1234";
R result = function.apply(str);
return result;
}
先使用匿名内部类的写法调用该方法
public static void main(String[] args) {
Integer result = typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
System.out.println(result);
String result1 = typeConver(new Function<String, String>() {
@Override
public String apply(String s) {
return s + "哈哈";
}
});
System.out.println(result1);
}
使用Lambda表达式来调用该方法:
public static void main(String[] args) {
Integer result = typeConver((String s) -> {
return Integer.valueOf(s);
});
System.out.println(result);
String result1 = typeConver((String str) -> {
return str + "哈哈";
});
System.out.println(result1);
}
例五:现有方法定义如下,其中IntConstumer是一个接口。
public static void foreachArr(IntConsumer consumer){
int[] array = {1,2,3,4,5,6,7,8,9,10};
for (int i : array) {
consumer.accept(i);
}
}
先使用匿名内部类的写法调用该方法
public static void main(String[] args) {
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
}
使用Lambda表达式来调用该方法:
public static void main(String[] args) {
foreachArr((int value) -> {
System.out.println(value);
});
}
Lambda表达式还可以继续优化省略:
-
参数类型可以省略
如:
foreachArr((int value) -> { System.out.println(value); }); //省略后 foreachArr(value -> System.out.println(value));
-
方法体只有一句代码时,大括号return和唯一一句代码的分号可以省略
-
方法只有一个参数时小括号可以省略
如:
printNum((int value) -> { return value % 2 == 0; }); //省略后 printNum(value -> value % 2 == 0);
3. Stream流
Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以方便让我们对集合或数组操作。
3.1 案例数据准备
-
导入Lombok依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
-
两个实体类
@Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode //后期的去重使用 public class Author { /** * id */ private Long id; /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; /** * 简介 */ private String intro; /** * 作品 */ private List<Book>books; }
@Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode //后期的去重使用 public class Book { /** * id */ private Long id; /** * 书名 */ private String name; /** * 分类 */ private String category; /** * 评分 */ private Integer score; /** * 简介 */ private String intro; }
-
一个静态方法
private static List<Author> getAuthors() { //数据初始化 Author author1 = new Author(1L, "诸葛亮", 25, "鞠躬尽瘁", null); Author author2 = new Author(2L, "王昭君", 25, "出嫁", null); Author author3 = new Author(3L, "嫦娥", 25, "奔月", null); Author author4 = new Author(4L, "刘备", 25, "三顾茅庐", null); //数据列表 List<Book> books1 = new ArrayList<>(); List<Book> books2 = new ArrayList<>(); List<Book> books3 = new ArrayList<>(); books1.add(new Book(1L, "Spring详解", "java", 105, "学习新技术!")); books1.add(new Book(2L, "SpringBoot入门", "计算机", 998, "做技术大牛")); books2.add(new Book(3L, "JavaWeb", "计算机", 998, "做技术大牛")); books2.add(new Book(3L, "JavaSE", "计算机", 998, "做技术大牛")); books2.add(new Book(3L, "MySQL", "计算机", 998, "做技术大牛")); books3.add(new Book(2L, "SpringCloud", "计算机", 998, "做技术大牛")); books3.add(new Book(2L, "Juc并发编程", "计算机", 998, "做技术大牛")); books3.add(new Book(2L, "JVM虚拟机", "计算机", 998, "做技术大牛")); author1.setBooks(books1); author2.setBooks(books2); author3.setBooks(books3); List<Author> authorList = new ArrayList<>(Arrays.asList(author1, author2, author3, author4)); return authorList; }
3.2 快速入门
-
需求:可以通过getAuthors获取作家集合。要求打印所有年龄小于18的作家名字,并且去重。
-
实现
public static void main(String[] args) { List<Author> authors = getAuthors(); authors.stream() //把集合转换成流 .distinct() //去重 .filter(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge() < 18; } }) //过滤年龄小于18的 .forEach(new Consumer<Author>() { @Override public void accept(Author author) { System.out.println(author.getName()); } }); }
简化:
public static void main(String[] args) { List<Author> authors = getAuthors(); //把集合转换成流 authors.stream() // 去重 .distinct() //过滤年龄小于18的 .filter(author -> author.getAge() < 18) //遍历输出符合条件的 .forEach(author -> System.out.println(author.getName())); }
可以通过Debug模式来查看流的工作过程:
3.3 创建流
单列集合:集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
数组:Arrays.stream(数组)
或使用Stream.of
来创建
Integer[] array = {1,2,3,4,5,6,7,8,9};
Stream<Integer> stream1 = Arrays.stream(array);
Stream<Integer> stream2 = Stream.of(array);
双列集合:转换成单列集合后再创建
Map<String, Integer> map = new HashMap<>();
map.put("诸葛亮",19);
map.put("王昭君",18);
map.put("嫦娥",22);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Stream<Map.Entry<String, Integer>> stream1 = entries.stream();
3.4 中间操作
-
filter
可以对流中的元素进行条件过滤,符合条件的才能继续留在流中+
List<Author> authors = getAuthors(); //Stream<Author> stream = authors.stream(); authors.stream() //打印id序号大于2的作家姓名 .filter(author -> author.getId()>2) .forEach(author -> System.out.println(author.getName()));
-
map
把流中的元素进行计算或转换
authors.stream() //输出所有作家的姓名和年龄 //类型本来是Author,转换为String类型 /*.map(new Function<Author,String>() { @Override public String apply(Author author) { return author.getName()+" "+author.getAge(); } })*/ .map(author -> author.getName()+" "+author.getAge()) .forEach(s -> System.out.println(s));
-
distinct
可以去除流中的重复元素
distinct方法是依赖Object的equals方法来判断是否是相同对象的,所以需要重写equals方法。
-
sorted
可以对流中的元素进行排序
authors.stream() /*.sorted(new Comparator<Author>() { @Override public int compare(Author o1, Author o2) { return o1.getAge() - o2.getAge(); } })*/ .sorted((o1, o2) -> o1.getAge() - o2.getAge()) .forEach(author -> System.out.println(author.getName()));
或实体类实现Comparable接口,重写方法compareTo,并添加排序条件
public class Author implements Comparable<Author> { ... @Override public int compareTo(Author o) { return this.getAge() - o.getAge(); } }
authors.stream() /*.sorted(new Comparator<Author>() { @Override public int compare(Author o1, Author o2) { return o1.getAge() - o2.getAge(); } })*/ .sorted() .forEach(author -> System.out.println(author.getName()));
-
limit
可以设置流的最大长度,超出的部分将被抛弃
authors.stream() .distinct() .sorted() //限制输出前两个 .limit(2) .forEach(author -> System.out.println(author.getName()));
-
skip
跳过流中的前n个元素,返回剩下的元素
-
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素
authors.stream() /*.flatMap(new Function<Author, Stream<Book>>() { @Override public Stream<Book> apply(Author author) { return author.getBooks().stream(); } })*/ .flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream()) .distinct() /*.forEach(new Consumer<Book>() { @Override public void accept(Book book) { System.out.println(book.getName()); } });*/ .forEach(book -> System.out.println(book.getName()));
3.5 终结操作
一个流最终必须要有终结操作结束流。
-
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历的元素进行什么具体的操作
-
count
可以用来获取当前流中元素的个数
-
max&min
可以用来获取流中的最值
Optional<Integer> max = authors.stream() .flatMap(author -> author.getBooks().stream()) .map(book -> book.getScore()) /*.max(new Comparator<Integer>() { @Override public int compare(Integer score1, Integer score2) { return score1 - score2; } });*/ .max((score1, score2) -> score1 - score2); System.out.println(max.get());
-
collect
把当前流转换成一个集合
转换为List集合
List<String> collect = authors.stream() .map(author -> author.getName()) //获取一个存放所有作者名字的List集合 .collect(Collectors.toList()); System.out.println(collect);
转换为Set集合
Set<String> collect = authors.stream() //获取一个所有书名的Set集合 .flatMap(author -> author.getBooks().stream()) .map(book -> book.getName()) .collect(Collectors.toSet()); System.out.println(collect);
转换为Map集合
Map<String, List<Book>> map = 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(); } }));*/ .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks())); System.out.println(map);
-
查找与匹配
-
anyMatch
可以判断是否有任意符合条件的元素,结果为boolean类型
boolean result = authors.stream() /*.anyMatch(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge() < 15; } });*/ //判断是否存在年龄小于15的作家 .anyMatch(author -> author.getAge() < 15); System.out.println(result);
-
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则为false
boolean result = authors.stream() //判断是否所有作家都已经成年 .allMatch(author -> author.getAge() > 18); System.out.println(result);
-
noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合返回true,否则返回false
-
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素
Optional<Author> optionalAuthor = authors.stream() //获取任意一个年龄大于18的作家,并输出他的名字 .filter(author -> author.getAge() > 18) .findAny(); optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
-
findFirst
获取流中的第一个元素
Optional<Author> optionalAuthor = authors.stream() //获取第一个年龄大于18的作家,并输出他的名字 .filter(author -> author.getAge() > 18) .findFirst(); optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
-
-
reduce归并
对流中的数据按照指定的计算方式计算出一个结果(缩减操作)
reduce的作用是把stream的元素组合起来,我们可以传入一个初始值,他会按照我们的计算方式依次拿流中的元素和初始值进行计算,计算结果再和后面的元素计算
内部的计算方式如下:
T result = identify; for (T element : this stream){ result = accumulator.apply(result,element) } return result;
其中identify就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的
Integer result = authors.stream() //计算所有作家的年龄总值 .map(author -> author.getAge()) //给定初始值 调用apply方法 /*.reduce(0, new BinaryOperator<Integer>() { @Override public Integer apply(Integer integer, Integer integer2) { return integer + integer2; } });*/ .reduce(0, (integer, integer2) -> integer + integer2); System.out.println(result);
Integer result = authors.stream() //使用reduce求所有作者年龄的最大值 .map(author -> author.getAge()) .reduce(Integer.MIN_VALUE, (integer, integer2) -> { return integer < integer2 ? integer2 : integer; }); System.out.println(result);
一个参数的重载形式内部计算
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<Integer> result = authors.stream() //使用reduce求所有作者年龄的最小值 .map(author -> author.getAge()) .reduce((integer, integer2) -> { return integer < integer2 ? integer : integer2; }); result.ifPresent(age -> System.out.println("age = " + age));
注意事项
- 惰性求值:如果没有终结操作,没有中间操作是不会得到执行的
- 流是一次性的:一旦一个流对象经过一个终结操作后,这个流就不能再使用
- 不会影响原数据:我们在流中可以对数据进行很多处理,而不影响原来集合中的元素
4. Optional
我们在编写代码的时候出现最多的就是空指针异常。所以在很多情况下我们需要做各种非空判断。
例如:
public static void main(String[] args) {
Author author = getAuthor();
if (author!= null){
System.out.println(author.getName());
}
}
public static Author getAuthor(){
Author author = new Author(1L, "诸葛亮", 25, "鞠躬尽瘁", null);
return null;
}
尤其是对象中的属性还是一个对象的情况下,这种判断就会更多。
而过多的判断语句会使得我们的代码臃肿不堪,不够优雅。
因此在JDK8中引入了Optional,养成习惯使用Optional后可以写出更优雅的代码来避免空指针异常。
并且在很多函数式编程相关的API中都用到了Optional,如果不学习Optional也会对函数式编程的学习造成影响
实际上,Optional就是将一个数据封装成了Optional的一个属性,直接调用Optional的方法,方法会进行相应的非空判断。
4.1 创建对象
Optional就像是包装类,可以把我们的具体数据封装到Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅地避免空指针异常。
我们一般使用Optional地静态方法ofNullable
来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。
Author author = getAuthor();
Optional<Author> optionalAuthor = Optional.ofNullable(author);
//如果author存在,进行消费
optionalAuthor.ifPresent(author1 -> System.out.println(author1.getName()));
那么这样使用Optional会让人感觉到封装数据更麻烦了。但是如果改造getAuthor方法,让其返回值就是封装好的Optional的话,在使用的时候就会方便很多。
public static void main(String[] args) {
Optional<Author> authorOptional = getAuthor();
//如果author存在,进行消费
authorOptional.ifPresent(author1 -> System.out.println(author1.getName()));
}
public static Optional<Author> getAuthor(){
Author author = new Author(1L, "诸葛亮", 25, "鞠躬尽瘁", null);
return Optional.ofNullable(author);
}
在实际开发中,我们的数据很多都是从数据库获取的。MyBatis从3.5版本就已经支持Optional了。我们可以直接dao方法的返回值类型定义为Optional类型,MyBatis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
如果确定一个对象肯定不是空的,则可以使用Option的静态方法of来把数据封装成Option对象
Author author = getAuthor();
Optional<Author> optionalAuthor = Optional.of(author);
但是一定要注意,使用of的时候传入的参数必须不为null。因此并不推荐使用这种方法封装数据。
如果一个方法的返回值类型是Optional类型。而如果我们判断发现某次计算得到的返回值为null,这个时候就需要把null封装为Optional对象返回,这时则可以使用Optional的静态方法empty来进行封装。
public static Optional<Author> getAuthor() {
Author author = new Author(1L, "诸葛亮", 25, "鞠躬尽瘁", null);
return author == null ? Optional.empty() : Optional.of(author);
}
4.2 安全消费值
我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent
方法来消费其中的值。这个方法会判断其内部封装的数据是否为空,不为空时在会执行具体的消费代码。这样使用起来就更加安全了。
Optional<Author> authorOptional = getAuthor();
//如果author存在,进行消费
authorOptional.ifPresent(author -> System.out.println(author.getName()));
4.3 安全获取值
如果我们想获取值自己进行处理可以使用get方法获取,但是并不推荐。因为当Optional内部数据为空的时候会出现异常。如果我们期望安全地获取值,推荐使用Optional提供的以下方法。
-
orElseGet
获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据传入的参数来创建对象作为默认值返回。
Optional<Author> authorOptional = getAuthor(); //如果author存在,进行消费 Author author = authorOptional.orElseGet(() -> new Author(2L, "王昭君", 25, "出嫁", null));
-
orElseThrow
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
Optional<Author> authorOptional = getAuthor(); //如果author存在,进行消费 try { /*Author author = authorOptional.orElseThrow(new Supplier<Throwable>() { @Override public Throwable get() { return new RuntimeException("Author为空"); } });*/ Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("Author为空")); System.out.println(author.getName()); } catch (Throwable e) { e.printStackTrace(); }
4.4 过滤数据
我们可以使用filter
方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
Optional<Author> authorOptional = getAuthor();
authorOptional.filter(author -> author.getAge() > 100)
.ifPresent(author -> System.out.println(author.getName()));
4.5 判断
我们可以使用isParent
方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现出Optional的好处,更推荐使用ifPresent
方法。
4.6 数据转换
Optional还提供了map可以让我们对数据进行转换,并且转换得到的数据也还是被Option包装好的,保证了我们的使用数据安全。
Optional<Author> authorOptional = getAuthor();
Optional<List<Book>> books = authorOptional.map(author -> author.getBooks());
books.ifPresent(books1 -> books1.forEach(book -> System.out.println(book.getName())));
5. 函数式接口
只有一个抽象方法的接口我们称之为函数接口
JDK的函数式接口都加上了@FuncationalInterface
注释进行了标识。但是无论是否加上该注释只要接口中只有一个抽象方法,都是函数式接口。
常见的函数式接口:
-
Consumer消费接口
在方法中对传入的参数进行消费
-
Function计算转换接口
在方法中对传入的参数计算或者转换,把结果返回
-
Predicate判断接口
在方法中对传入的参数条件判断,返回判断结果
-
Supplier生产型接口
在方法中创建对象,把创建好的对象返回
常用的默认方法:
-
and
我们在使用Predicate接口的时候可能需要进行判断条件的拼接。而add方法相当于是使用&&来拼接两个判断条件
-
or
我们在使用Predicate接口的时候可能需要进行判断条件的拼接,而or方法相当于是使用||来拼接两个判断条件
-
negate
negate方法像短语实在判断添加前面加了一个!表示取反
6. 方法引用
在我们使用Lambda表达式时,如果方法体只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
我们在使用Lambda时不需要考虑什么时候用方法引用,用那种方法引用,引用的格式是什么。我们只需要在写完Lambda方法发现方法体只有一行代码,并且是方法的调用时,使用快捷键转换成方法引用即可。
基本格式:类名或者对象名::方法名
7. 高级用法
基本数据类型优化数据
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很小。但是在大量的数据不断的重复装箱拆箱的时候,就不能无视这个时间损耗了。
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge())
//再次进行map操作时,本来就是Integer类型,又进行了转换,拆箱装箱,可以进行优化
.map(age -> age + 10)
.filter(age -> age >18)
.map(age -> age +2)
.forEach(System.out::println);
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专针对基本数据类型的方法。例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble
并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
标签:函数,author,编程,System,public,println,new,Optional,Java8 From: https://www.cnblogs.com/hackertyper/p/16862365.html