Java8函数式编程
Posted 编程小栈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8函数式编程相关的知识,希望对你有一定的参考价值。
-
Lambda 表达式
-
函数式接口
-
方法引用与构造器引用
-
Stream API
-
接口中的默认方法与静态方法
-
新时间日期API
-
其他新特性
速度更快
代码更少(增加了新的语法 Lambda 表达式)
强大的 Stream API
便于并行
最大化减少空指针异常 Optional
其中最为核心的为 Lambda 表达式与Stream API
1、Lambda表达式
1.1什么是Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数,它主要关注的是对数据进行了什么操作。
1.2从匿名类到 Lambda 的转换
我们在创建线程并启动时可以使用匿名内部类的写法:
new Thread(new Runnable()
@Override
public void run()
System.out.println("lambda111");
).start();
可以使用Lambda的格式对其进行修改。修改后如下:
new Thread(()->
System.out.println("lambda222");
).start();
1.3Lambda表达式语法
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。
它将 Lambda 分为两个部分:
**左侧:**指定了 Lambda 表达式需要的所有参数
**右侧:**指定了 Lambda 体,即 Lambda 表达式要执行的功能
(参数列表)->代码
类型推断
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,
这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。
省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
练习
1.现有方法定义如下,其中IntBinaryOperator是一个接口。先使用匿名内部类的写法调用该方法。
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写法:
public static void main(String[] args)
int i = calculateNum((int left, int right)->
return left + right;
);
System.out.println(i);
2.现有方法定义如下,其中IntPredicate是一个接口。先使用匿名内部类的写法调用该方法。
public static void printNum(IntPredicate predicate)
int[] arr = 1,2,3,4,5,6,7,8,9,10;
for (int i : arr)
if(predicate.test(i))
System.out.println(i);
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;
);
3.现有方法定义如下,其中Function是一个接口。先使用匿名内部类的写法调用该方法。
public static <R> R typeConver(Function<String,R> function)
String str = "1235";
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);
Lambda写法:
Integer result = typeConver((String s)->
return Integer.valueOf(s);
);
System.out.println(result);
4.现有方法定义如下,其中IntConsumer是一个接口。先使用匿名内部类的写法调用该方法。
public static void foreachArr(IntConsumer consumer)
int[] arr = 1,2,3,4,5,6,7,8,9,10;
for (int i : arr)
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);
);
2、函数式接口
2.1什么是函数式接口
只包含一个抽象方法的接口,称为函数式接口。 你可以通过 Lambda 表达式来创建该接口的对象。
(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
我们可以在任意函数式接口上使用 @FunctionalInterface 注解,
这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
2.2自定义函数式接口
2.3内置核心函数式接口
2.4接口中常用的默认方法
-
and
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件
例如:
打印作家中年龄大于17并且姓名的长度大于1的作家。
List<Author> authors = getAuthors(); Stream<Author> authorStream = authors.stream(); authorStream.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(author -> System.out.println(author));
-
or
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用||来拼接两个判断条件。
例如:
打印作家中年龄大于17或者姓名的长度小于2的作家。
// 打印作家中年龄大于17或者姓名的长度小于2的作家。 List<Author> authors = getAuthors(); 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(author -> System.out.println(author.getName()));
-
negate
Predicate接口中的方法。negate方法相当于是在判断添加前面加了个! 表示取反
例如:
打印作家中年龄不大于17的作家。
// 打印作家中年龄不大于17的作家。 List<Author> authors = getAuthors(); authors.stream() .filter(new Predicate<Author>() @Override public boolean test(Author author) return author.getAge()>17; .negate()).forEach(author -> System.out.println(author.getAge()));
3、方法引用与构造器引用
3.1 推荐用法
我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
3.2 基本格式
类名或者对象名::方法名
3.3 语法详解(了解)
3.3.1引用类的静态方法
其实就是引用类的静态方法
格式
类名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
例如:
如下代码就可以用方法引用进行简化
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
.map(age->String.valueOf(age));
注意,如果我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则。
优化后如下:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
.map(String::valueOf);
3.3.2 引用对象的实例方法
格式
对象名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
例如:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
.forEach(name->sb.append(name));
优化后:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
.forEach(sb::append);
3.3.4 引用类的实例方法
格式
类名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
例如:
interface UseString
String use(String str,int start,int length);
public static String subAuthorName(String str, UseString useString)
int start = 0;
int length = 1;
return useString.use(str,start,length);
public static void main(String[] args)
subAuthorName("666", new UseString()
@Override
public String use(String str, int start, int length)
return str.substring(start,length);
);
优化后如下:
public static void main(String[] args)
subAuthorName("666", String::substring);
3.3.5 构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
格式
类名::new
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
例如:
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
.map(name->new StringBuilder(name))
.map(sb->sb.append("-666").toString())
.forEach(str-> System.out.println(str));
优化后:
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
.map(StringBuilder::new)
.map(sb->sb.append("-666").toString())
.forEach(str-> System.out.println(str));
4、Stream API
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。
4.1什么是Stream
**流(Stream) 到底是什么呢?**是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
4.2Stream操作的三个步骤
数据中准备
@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
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book
//id
private Long id;
//书名
private String name;
//分类
private String category;
//评分
private Integer score;
//简介
private String intro;
private static List《Java8实战》读书笔记12:函数式编程