提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
1.函数式编程
只包含一个抽象方法的接口,称为函数式接口。
你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式 抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽 象方法上进行声明)。
我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检 查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个 接口是一个函数式接口。
在java.util.function包下定义了Java 8 的丰富的函数式接口
函数式编程思想:
- 对面向对象思想的简化
- 忽略对象的创建,和对象功能的声明,重点关注 此行为是如何实现的(方法体)
已经学过的函数式接口:
Runnable接口 : 线程对象的任务接口 -> void run(); Comparable<E>接口 : 绑定比较器接口 -> int compareTo(E o) Comparator<E>接口 : 独立比较器接口 -> int compare(E o1,E o2) Callable<V>接口 : 线程对象的有结果的任务接口 -> E call() FileFilter接口 : 文件过滤器接口 -> boolean accept(File fileName)
2.Lambda表达式
概述: Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以 传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更 灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了 提升。实质上是对匿名内部类在作用于函数式接口时的格式简化也可称为 箭头函数
使用场景: 只能作用在函数式接口上,对匿名内部类的简化书写格式。
格式:
(参数列表) -> {
方法体;
};
Lambda表达式简化条件:
- 如果重写方法内的代码,有且仅有一句,可以省略 包裹方法体的大括号,语句结尾的分号,(如果方法内有return 也可以省略return) 要省略都省略,不能只省略其中一个 。
- Lambda表达式重写方法的形参列表中的形参类型都可以省略 。
- 如果重写方法的形参 有且仅有一个 那么可以省略形参的小括号 。
- 形参名可以修改,方法体内的形参使用也要改名字 。//一般不弄
代码示例:
public class Demo01 {
public static void main(String[] args) {
// 1.无返回值类型,无参数列表
// a、使用匿名对象,实例化实现类
InterA interA_a = new InterA() {
@Override
public void method() {
System.out.println("无返回值类型,无参数列表--->匿名对象");
}
};
interA_a.method();
// b、使用Lambda表达式替换匿名对象实例化实现类
InterA interA_b = () -> System.out.println("无返回值类型,无参数列表--->Lambda表达式");
interA_b.method();
System.out.println("----------------------------");
//2. 无返回值类型,有参数列表
// a、使用匿名对象,实例化实现类
InterB interB_a = new InterB() {
@Override
public void method(String str) {
System.out.println(str);
}
};
interB_a.method("返回值类型,有参数列表--->匿名对象");
// b、使用Lambda表达式替换匿名对象实例化实现类
InterB interB_b = str -> System.out.println(str);
interB_b.method("返回值类型,有参数列表--->Lambda表达式");
System.out.println("----------------------------");
//3. 有返回值类型,无参数列表
// a、使用匿名对象,实例化实现类
InterC interC_a = new InterC() {
@Override
public String method() {
return "有返回值类型,无参数列表--->匿名对象";
}
};
System.out.println("interC_a.method() = " + interC_a.method());
// b、使用Lambda表达式替换匿名对象实例化实现类
InterC interC_b = () -> "有返回值类型,无参数列表--->Lambda表达式";
System.out.println("interC_b.method() = " + interC_b.method());
System.out.println("----------------------------");
//4. 有返回值类型,有形参
// a、使用匿名对象,实例化实现类
InterD interD_a = new InterD() {
@Override
public String method(String str) {
return str;
}
};
System.out.println("interD_a.method() = " + interD_a.method("有返回值类型,有参数列表--->匿名对象"));
// b、使用Lambda表达式替换匿名对象实例化实现类
InterD interD_b = (str) -> str;
System.out.println("interD_b.method() = " + interD_b.method("有返回值类型,有参数列表--->Lambda表达式"));
System.out.println("----------------------------");
}
}
// 无返回值类型,无参数列表
@FunctionalInterface
interface InterA{
public abstract void method();
}
// 无返回值类型,有参数列表
@FunctionalInterface
interface InterB{
public abstract void method(String str);
}
// 有返回值类型,无参数列表
@FunctionalInterface
interface InterC{
public abstract String method();
}
// 有返回值类型,有形参
@FunctionalInterface
interface InterD{
public abstract String method(String str);
}
3.方法引用(Method References)了解
3.1 概述
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就 是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向 一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的 方法的参数列表和返回值类型保持一致!
实质:方法的引用实质式对Lambda表达式的简化
使用条件:
1. 必须满足Lambda表达式的前置条件(必须是函数式接口) 2. 必须要求重写方法内方法体有且仅有一句代码 --> 严苛 3. 必须要求重写方法内方法体的这句代码: -> 严苛至极 a. 对象调方法 b. 类名调静态方法 c. 创建对象 d. 创建数组 4. 形参要在以上功能中使用//使用条件
标志:::
Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化 Lambda表达式的。当Lambda表达式满足一些特殊的情况时,还可以再简化:
3.2 使用格式
方法的引用:
(1)对象调方法 --> 对象名::实例方法
(2)类名调用静态方法 --> 类名::静态方法
注意事项 :
1.::称为方法引用操作符
2.Lambda表达式的形参列表,全部在Lambda体中使用上了,要么是作为调用方法的对象,要么是作为方法的实参。
3.在整个Lambda体中没有额外的数据。
构造器引用:
(1)当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造 器的实参列表。
(2) 当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组 对象的长度
类名::new
数组类型名::new
代码演示:
public class Demo01 {
public static void main(String[] args) {
// 匿名对象写法
InterA interA = new InterA() {
@Override
public void print(String str) {
System.out.println(str);
}
};
interA.print("只展示一次匿名对象写法");
System.out.println("---------------------------------");
/**
* 方法引用的条件
* 基于Lambda做的简化,要求重写方法内有且仅有一句代码且这句代码是
* 要么对象调方法
* 要么类名调用静态方法
* 要么是创建对象
* 要么是创建一个数组
*/
// 1.InterA 对象的调用 对象名::方法名称
InterA interA1 = System.out :: println;
interA1.print("类名::方法名称");
// 2.InterB 静态方法的调用 类名::方法名
InterB interB = Integer :: parseInt;
interB.change("123");
// 3.InterC 构造使用 对象名::new
InterC interC = Date :: new;
interC.get();
//4.InterD 数组 类型[] :: new
InterD interD = int[] :: new ;
interD.get(10);
}
}
@FunctionalInterface
interface InterA{
public abstract void print(String str);
}
@FunctionalInterface
interface InterB{
public abstract int change(String str);
}
@FunctionalInterface
interface InterC{
public abstract Date get();
}
@FunctionalInterface
interface InterD{
public abstract int[] get(int length);
}
4.Stream API
4.1 概述
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则 是 Stream API。
Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程 序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进 行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种 高效且易于使用的处理数据的方式。
为什么要使用Stream API
- 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数 据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要 Java层面去处理。
- Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据 结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中, 后者主要是面向 CPU,通过 CPU 实现计算。
Stream 的操作三个步骤
- 创建 Stream : 一个数据源(如:集合、数组),获取一个流
- 中间操作 : 一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作) : 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
Stream 操作流程图
4.2 创建Stream流
常用的方法:
-- 1.单集合创建Stream流
default Stream<E> stream() : 返回以此集合作为源的顺序 Stream 。
Stream<E> 流对象 = 集合.stream();
-- 2.双列集合创建Stream对象,双列集合不能直接创建Stream,而是要转换成单列集合在转Stream
a. 先获取键集 : Set<K> keySet() , 键集进流
b. 先获取值集 : Collection<V> values() , 值集进流
c. 先获取键值对集合 : Set<Map.Entry<K,V>> entrySet() ,键值对集合进流 -> 推荐
-- 3.数组创建Stream对象(Arrays工具类)
static <T>Stream stream(<T>[] array) : 返回顺序<T>Stream与指定的数组作为源
<T>Stream 流对象 = Arrays.stream(<T>[] array);
-- 4. 一组同类型的数据
static <T> Stream<T> of(T... values) : 返回其元素是指定值的顺序排序流。
Stream<T> 流对象 = Stream.of(一组相同数据类型的数据);
代码演示:
public class Demo01 {
public static void main(String[] args) {
listStream();
System.out.println("---------------------");
mapStream();
System.out.println("---------------------");
arrayStream();
System.out.println("----------------------");
dataStream();
}
/**
* 1.单集合创建Stream流
* default Stream<E> stream() : 返回以此集合作为源的顺序 Stream 。
* Stream<E> 流对象 = 集合.stream();
*/
public static void listStream(){
// 1.创建集合
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"aaa","bbb","ccc");
// 2.创建流对象
Stream<String> stream = list.stream();
// 3.查看Stream
stream.forEach(System.out :: println);
}
/**
* 2.双列集合创建Stream对象,双列集合不能直接创建Stream,而是要转换成单列集合在转Stream
* a. 先获取键集 : Set<K> keySet() , 键集进流
* b. 先获取值集 : Collection<V> values() , 值集进流
* c. 先获取键值对集合 : Set<Map.Entry<K,V>> entrySet() ,键值对集合进流 -> 推荐
*/
public static void mapStream(){
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa",1);
map.put("bbb",2);
map.put("ccc",3);
// 将双列集合转成单列集合
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 创建Stream对象
Stream<Map.Entry<String, Integer>> stream = entries.stream();
// 输出Stream流
stream.forEach(System.out :: println);
}
/**
* 3.数组创建Stream对象(Arrays工具类)
* static <T>Stream stream(<T>[] array) : 返回顺序<T>Stream与指定的数组作为源
* <T>Stream 流对象 = Arrays.stream(<T>[] array);
*/
public static void arrayStream(){
int[] arr = {1,2,3,4,5,6,7,8};
// 创建Stream流对象
IntStream stream = Arrays.stream(arr);
// 输出流
stream.forEach(System.out :: println);
}
/**
* 4. 一组同类型的数据
* static <T> Stream<T> of(T... values) : 返回其元素是指定值的顺序排序流。
* Stream<T> 流对象 = Stream.of(一组相同数据类型的数据);
*/
public static void dataStream(){
// 创建Stream流
Stream<Double> doubleStream = Stream.of(1.1, 1.2, 1.3);
// 输出Stream
doubleStream.forEach(System.out::println);
}
}
4.3 Stream流中间操作方法
常用的方法:
-- 过滤操作
Stream<T> filter(Predicate<? super T> predicate) : 返回由与此给定谓词匹配的此流的元素组成的流。
// 该方法实现过滤的操作,过滤操作是由Predicate接口中test方法实现的。所以要重写test方法,在方法中写过滤操作
-- 截取操作
Stream<T> limit(long maxSize) : 返回由此流的元素组成的流,截短长度不能超过 maxSize 。
-- 跳过操作
Stream<T> skip(long n) : 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
-- 去重操作
Stream<T> distinct() : 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
-- 排序操作
Stream<T> sorted() : 返回由此流的元素组成的流,根据自然顺序排序。
Stream<T> sorted(Comparator<? super T> comparator) : 返回由该流的元素组成的流,根据提供的 Comparator进行排序。
-- 合并操作
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) : 创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素
-- 转换操作
<R> Stream<R> map(Function<? super T,? extends R> mapper) : 返回由给定函数应用于此流的元素的结果组成的流。
代码演示:
public class Demo02 {
public static void main(String[] args) {
filterTest();
System.out.println("------------------------");
limitTest();
System.out.println("------------------------");
skipTest();
System.out.println("------------------------");
distinctTest();
System.out.println("------------------------");
sortTest();
System.out.println("------------------------");
concatTest();
System.out.println("------------------------");
mapTest();
System.out.println("------------------------");
}
/**
* 过滤操作
* Stream<T> filter(Predicate<? super T> predicate) : 返回由与此给定谓词匹配的此流的元素组成的流。
* 该方法实现过滤的操作,过滤操作是由Predicate接口中test方法实现的。所以要重写test方法,在方法中写过滤操作
*/
public static void filterTest(){
// 创建流Stream对象
Stream<String> stream = Stream.of( "张三丰", "李四", "王五","张思", "赵六","张三");
// 获取所有姓张的名字
/*
Stream<String> newStream = stream.filter(new Predicate<String>() {
@Override
public boolean test(String name) {
return name.startsWith("张");
}
});
*/
Stream<String> newStream = stream.filter(name -> name.startsWith("张"));
// 输出流
newStream.forEach(System.out :: println);
}
/**
* 截取操作
* Stream<T> limit(long maxSize) : 返回由此流的元素组成的流,截短长度不能超过 maxSize 。
*/
public static void limitTest(){
// 创建流Stream对象
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
// 保留5之前的数字
Stream<Integer> limit = stream.limit(5);
// 输出流
limit.forEach(System.out :: println);
}
/**
* 跳过操作
* Stream<T> skip(long n) : 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
*/
public static void skipTest(){
// 创建流Stream对象
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
// 跳过4之前的数
Stream<Integer> skip = stream.skip(4);
skip.forEach(System.out :: println);
}
/**
* 去重操作
* Stream<T> distinct() : 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
*/
public static void distinctTest(){
// 创建流Stream对象
Stream<Integer> stream = Stream.of(1,1,3,2,5,5,8,8,9,10);
// 去除重复的数字
Stream<Integer> distinct = stream.distinct();
distinct.forEach(System.out :: println);
}
/**
* 排序操作
* Stream<T> sorted() : 返回由此流的元素组成的流,根据自然顺序排序。
* Stream<T> sorted(Comparator<? super T> comparator) : 返回由该流的元素组成的流,根据提供的 Comparator进行排序。
*/
public static void sortTest(){
// 创建流Stream对象
Stream<Integer> stream = Stream.of(1,4,3,2,6,5,8,7,9,10);
// 排序
Stream<Integer> sorted = stream.sorted((o1,o2) -> o1 - o2 );
sorted.forEach(System.out :: println);
}
/**
* 合并操作
* static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) : 创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素
*/
public static void concatTest(){
Stream<Integer> stream1 = Stream.of(1,4,3,2);
Stream<Integer> stream2 = Stream.of(5,8,7,9,10);
Stream<Integer> concat = Stream.concat(stream1, stream2);
concat.forEach(System.out :: println);
}
/**
* 转换操作
* <R> Stream<R> map(Function<? super T,? extends R> mapper) : 返回由给定函数应用于此流的元素的结果组成的流。
*/
public static void mapTest(){
Stream<Integer> stream = Stream.of(1,4,3,2);
Stream<String> newStream = stream.map(Object::toString);
newStream.forEach(System.out :: println);
}
}
4.4 Stream流的终止操作
常用的方法:
1.遍历终结
void forEach(Consumer<? super T> action) : 对此流的每个元素执行操作
//foreach方法可以获取流中的所有数据,拿到元素后如何使用由重写的accept方法决定
2.统计终结
long count() : 返回此流中的元素数。
3.收集终结
<R,A> R collect(Collector<? super T,A,R> collector)
要调用collect方法需要传递Collector的实现类对象,但是Collector实现类对象自己不会写;
需要借助工具类 : Collectors -> 可以快速的生成Collector对象
Collectors工具类中的方法 :
static <T> Collector<T,?,List<T>> toList() : 把流中元素收集成List集合
static <T> Collector<T,?,Set<T>> toSet() : 把流中元素收集成Set集合
//Set集合中的元素可以去重 -> 去重原理 : hashCode 和 equals
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K>keyMapper, Function<? super T,? extends U> valueMapper)
简化 :
static Collector<T,?,Map<K,U>> toMap(Function<T,K> key, Function<T,U> value)
代码演示:
public class Demo03 {
public static void main(String[] args) {
forEachTest();
System.out.println("---------------");
countTest();
System.out.println("---------------");
collectTest();
System.out.println("---------------");
}
/**
* 1.遍历终结
* void forEach(Consumer<? super T> action) : 对此流的每个元素执行操作
* //foreach方法可以获取流中的所有数据,拿到元素后如何使用由重写的accept方法决定
*/
public static void forEachTest(){
// 创建流Stream对象
Stream<String> stream = Stream.of( "张三丰", "李四", "王五","张思", "赵六","张三");
stream.forEach(System.out :: println);
}
/**
* 2.统计终结
* long count() : 返回此流中的元素数。
*/
public static void countTest(){
// 创建流Stream对象
Stream<String> stream = Stream.of( "张三丰", "李四", "王五","张思", "赵六","张三");
// 统计个数
long count = stream.count();
System.out.println("count = " + count);
}
/**
* 3.收集终结
* <R,A> R collect(Collector<? super T,A,R> collector)
* 要调用collect方法需要传递Collector的实现类对象,但是Collector实现类对象自己不会写;
* 需要借助工具类 : Collectors -> 可以快速的生成Collector对象
* Collectors工具类中的方法 :
* static <T> Collector<T,?,List<T>> toList() : 把流中元素收集成List集合
* static <T> Collector<T,?,Set<T>> toSet() : 把流中元素收集成Set集合
* //Set集合中的元素可以去重 -> 去重原理 : hashCode 和 equals
* static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K>keyMapper, Function<? super T,? extends U> valueMapper)
* 简化 :
* static Collector<T,?,Map<K,U>> toMap(Function<T,K> key, Function<T,U> value)
*/
public static void collectTest(){
// 创建流Stream对象
Stream<String> stream = Stream.of( "张三丰", "李四", "王五","张思", "赵六","张三");
List<String> collect = stream.collect(Collectors.toList());
for (String s : collect) {
System.out.println("s = " + s);
}
}
}
4.5 综合案例
案例描述:
现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
- 男演员只要名字为3个字的前三人
- 女演员只要姓林的,并且不要第一个
- 把过滤后的男演员姓名和女演员姓名合并到一起
- 把上一步操作后的元素作为构造方法的参数创建演员对象 -> String 转换 Actor
- 把转换后的流收集成List集合并遍历List集合
public class Actor implements Serializable {
private static final long serialVersionUID = 5215563480733681684L;
private String name;
public Actor() {
}
public Actor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Actor actor = (Actor) o;
return Objects.equals(name, actor.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Actor{" +
"name='" + name + '\'' +
'}';
}
}
代码演示:
public class Demo01 {
public static void main(String[] args) {
//创建集合
ArrayList<String> manList = new ArrayList<String>();
manList.add("周润发");
manList.add("成龙");
manList.add("刘德华");
manList.add("吴京");
manList.add("周星驰");
manList.add("李连杰");
ArrayList<String> womanList = new ArrayList<String>();
womanList.add("林心如");
womanList.add("张曼玉");
womanList.add("林青霞");
womanList.add("柳岩");
womanList.add("林志玲");
womanList.add("王祖贤");
// 创建Stream流对象
Stream<String> manStream1 = manList.stream();
Stream<String> womanStream1 = womanList.stream();
// 1.男演员只要名字为3个字的前三人
Stream<String> manStream2 = manStream1.filter( name-> name.length() == 3 );
Stream<String> manStream3 = manStream2.limit(3);
// 2. 女演员只要姓林的,并且不要第一个
Stream<String> womanStream2 = womanStream1.filter(name -> name.startsWith("林"));
Stream<String> womanStream3 = womanStream2.skip(1);
// 3.把过滤后的男演员姓名和女演员姓名合并到一起
Stream<String> concatStream = Stream.concat(manStream3, womanStream3);
// 4.把上一步操作后的元素作为构造方法的参数创建演员对象 -> String 转换 Actor
Stream<Actor> actorStream = concatStream.map( Actor :: new);
// 5.把转换后的流收集成List集合并遍历List集合
List<Actor> collectList = actorStream.collect(Collectors.toList());
System.out.println("collectList = " + collectList);
}
}
优化上述代码:
public class Demo02 {
public static void main(String[] args) {
//创建集合
ArrayList<String> manList = new ArrayList<String>();
manList.add("周润发");
manList.add("成龙");
manList.add("刘德华");
manList.add("吴京");
manList.add("周星驰");
manList.add("李连杰");
ArrayList<String> womanList = new ArrayList<String>();
womanList.add("林心如");
womanList.add("张曼玉");
womanList.add("林青霞");
womanList.add("柳岩");
womanList.add("林志玲");
womanList.add("王祖贤");
List<Actor> collectList =
// 3.把过滤后的男演员姓名和女演员姓名合并到一起
Stream.concat(
// 1.男演员只要名字为3个字的前三人
manList.stream().filter( name-> name.length() == 3 ),
// 2. 女演员只要姓林的,并且不要第一个
womanList.stream().filter(name -> name.startsWith("林")).skip(1))
// 4.把上一步操作后的元素作为构造方法的参数创建演员对象 -> String 转换 Actor
.map( Actor :: new)
// 5.把转换后的流收集成List集合并遍历List集合
.collect(Collectors.toList());
System.out.println("collectList = " + collectList);
}
}