函数式编程(一)
1. 前言
1.1 概念
优点:
- 能够读懂公司里面的代码
- 大量数据下处理集合的效率比较高
- 代码可读性大
- 消灭嵌套式的地狱
面向对象:需要关注的用什么东西完成了什么事情,函数式编程的思想实际上就是关注的就是对于数据进行了什么操作。
2. Lambda 表达式
2.1 概述
它主要可以对于一些匿名内部类的写法进行简化,它是函数式编程思想的一个重要的体现,让我们不在关注什么是对象。从而更加清楚的关注对于数据进行了什么操作【返回值】【参数】。
2.2 基本的格式
(临时的变量) -> {执行代码的任务块}
2.3 触发条件
1.8版本以上有效
@FunctionalInterface 语法严格要求当下的接口,(有且只能有一个)尚未完成的缺省属性是public abstract 修饰的方法。
函数的接口一般用于方法的增强,直接作为方法的参数,实现插件式编程。
@FunctionalInterface
interface Test{
void test();
}
2.4 Lambda表达式
Lambda中,可以将接口的匿名内部类转换成为lambda表达式
2.4.1 无参无返回值
接口
@FunctionalInterface
interface Test{
/**
* 缺省属性是public abstract
*/
void test();
}
方法的设计
/**
* 检测lambda表达式
*
* @param test 参数是接口
*/
public static void testLambda(Test test){
System.out.println("检测lambda表达式");
}
代码检验
匿名内部类的形式
public class Demo1 { public static void main(String[] args) { /** * 采用的是匿名内部类的方式将接口作为形参传入 */ testLambda(new Test() { @Override public void test() { System.out.println("匿名内部类的实现"); } }); } }
采用lambda表达式的方式实现匿名内部类
/** * 采用lambda表达式的分析 * :void方法没有返回值操作 * * 接口方法返回值是:void * 接口参数是无参数的 * * 所以lambda表达式是: * () -> {大括号中无需返回值类型} * */ testLambda(()->{ System.out.println("匿名内部类的方式转换成为第一种lambda的表达式"); });
对于只有一行的lambda表达式可以将大括号进行省略操作
testLambda(()-> System.out.println("匿名内部类的方式转换成为第一种lambda的表达式"));
2.4.2 有参无返回值
接口
@FunctionalInterface
interface Demo<T>{
/**
* 采用的是有参无返回值的操作(又称为数据最终处理接口、数据处理方法终止接口,因为当前方法中数据有来无回)
*
* @param t 使用泛型的目的是为了保证数据的多样性,方法中也是用同样的泛型,
* 保证数据的一致性
*/
void test(T t);
}
方法的设计
/**
* 方法的设计:对于字符串进行处理操作,但是没有返回值
*
* @param str 指定的字符串
* @param demo 已经约束为String类型的接口处理器
*/
public static void testLambda(String str,Demo<String> demo){
demo.test(str);
}
实现操作
匿名内部类的使用方式
/** * 1.采用的是匿名内部类的操作的方式进行实现 */ testLambda("指定的执行的字符串", new Demo<String>() { @Override public void test(String s) { System.out.println(s+"进行处理操作。(有参数无返回值的操作)"); } });
转换成为lambda表达式的方法
/** * 2. 转换成为lambda表达式的方式 * 分析: * 针对于接口中的方法:void test(String str) * 接口方法中 有string类型的参数 * 返回值:没有返回值 * * * 所以lambda的表达式可以写成 * (s) -> {无返回值的代码} */ testLambda("lambda表达式的操作",s ->{ System.out.println(s+"进行lambda表达式的操作"); });
lambda表达式的优化操作
testLambda("lambda表达式的操作",s -> System.out.println(s+"进行lambda表达式的操作"));
2.4.3 无参数有返回值
接口
@FunctionalInterface
interface TestA<T>{
/**
* 没有参数,但是有返回值操作,泛型约束是接口对应的返回值接口数据类型,要求按照泛型约束
* 返回对应的数据内容
*
* @return 返回一个数据,符合泛型约束
*/
T get();
}
方法设计
/**
* 作用:当前方法需要返回一个字符串类型的数据
*
* @param test 表示TestA String 数据提供接口对应的参数
* @return 字符串数据
*/
public static String testLambda(TestA<String> test){
return test.get();
}
实现
public class Demo3 {
public static void main(String[] args) {
/**
* 匿名内部类的使用:匿名内部类的实现是介于方法,接口作为参数的格式,而且接口没有
* 所谓意义上的抽象类实现,则可以通过匿名内部类实现。
*/
String s = testLambda(new TestA<String>() {
@Override
public String get() {
return "作为一个字符串输出操作";
}
});
/**
* 因为返回值类型是String类型,所以通过S进行接收,然后进行输出操作
*/
System.out.println(s);
/**
* 改进的方案,将匿名内部类转换成为lambda表达式
*/
/**
* 匿名内部类的使用规则:
* 方法分析:T get(); 现在给定的类型是String类型
* 有返回值:T 表示的是返回值的类型是泛型
* 无参数
*
* Lambda 格式
* () -> {必须返回一个 String 类型}
* return 关键字出马
*/
String s1 = testLambda(() -> {return "lambda表达式操作";});
/**
*更为精简的模式:对于只有一句的返回值,我们可以将return进行清除操作
*/
String s1 = testLambda(() -> "lambda表达式操作");
}
2.4.4 有参有返回值【重点】
2.4.4.1 比较器接口
比较器的接口经常用来实现数据的排序操作或者升序或者降序,其中返回的值如果是小于0 的值则左边的大。0表示两者一样大(> return -1 = return 0;)
接口设计
/**
* 比较器接口,参数是泛型,用户指定类型
*/
@FunctionalInterface
interface Comparator<T>{
/**
* 比较器接口的方法,参数是泛型类型,用户指定类型
*
* @param o1 用户在使用接口时约束的泛型对应具体数据类型
* @param o2 用户在使用接口时约束的泛型对应具体数据类型
* @return 如果返回0表示的是,两者相同,否则两者不同
*/
int comparator(T o1,T o2);
}
方法设计
/**
* 使用的是排序的操作,排序的数组是person 类型,排序的规则使用自定义Comparator 接口实现
* 接口约束 person 类型
*
* @param arr Person类型的数据
* @param com 针对于Person类型数组的Comparator 比较器接口实现
*/
public static void comparatorArray(Person[] arr,Comparator<Person> com){
/**
* 采用的核心算法是选择排序的算法操作
*/
for (int i = 0; i < arr.length - 1; i++) {
int index = i;
/**
* 对于比较器中的泛型操作,目前约束的类型是 Person 类型
* 当前数组中存储的类型就是Person类型对象,可以作为参数,
* 同时利用compare 方法 返回值返回int类型的数据,作为
* 排序算法的规则限制。
*/
for (int j = i + 1; j < arr.length; j++) {
if (com.compare(arr[index],arr[j] ) > 0 ){
index = j;
}
}
if (index != i) {
Person temp = arr[index];
arr[index] = arr[i];
arr[i] = temp;
}
}
}
实现
public class Demo {
public static void main(String[] args) {
/**
* 数据的声明赋值部分
*/
Person[] arr = new Person[5];
for (int i = 0; i < arr.length; i++) {
int age = (int) (Math.random()*50);
arr[i] = new Person("小八嘎",age,"日本");
}
/**
* 匿名内部类比较实现
*/
comparatorArray(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
}
lambda表达式的操作
/**
* lambda分析,
* 首先方法:
* int compare(T o1,T o2);
* 获取泛型之后,泛型的类型是person类型
* 返回值的数据类型是int类型
*
* 方法的参数:2个参数
* 参数的类型都是Person类型
* 最终:
* (o1,o2) -> {所欲要的最重的返回值}
*/
comparatorArray(arr, (o1, o2) -> o1.getAge() - o2.getAge());
2.4.4.2 过滤器接口
接口的设计
@FunctionalInterface
interface Predicate<T>{
/**
* 过滤器接口约束的方法,使用方法参数是用户使用时约束泛型对应具体数据参数
* 返回的数据的类型是boolean类型,作用是对条件进行判断,结果反馈
*
* @param t 方法参数是用户使用时约束泛型对应具体数据参数
* @return 判断条件,结果反馈
*/
boolean test(T t);
}
方法的设计
/**
* 方法的功能:将传入的参数进行过滤筛查,限定数组的内容,将满足条件的数组,传给一个新的数组
* 将新的数组返回。
*
*
* @param people 传入的类型数组
* @param predicate 过滤器参数
* @return 返回过滤后的新的数组
*/
public static Person[] filterArrayUsing(Person[] people,Predicate<Person> predicate){
// 采用的是尾插法操作。
//声明一个新的数组
Person[] temp = new Person[people.length];
int count = 0;
for (int i = 0; i < people.length; i++) {
/**
* 过滤对象提供的方法是test方法,
* 通过调用当前的对象,判断当前对象是否满足要求,如果满足存储到新的数组中
*
* 但是现在的比较条件没有确定,通过重写方法来确定
*/
if (predicate.test(people[i])){
temp[count++] = people[i];
}
}
return temp;
}
匿名内部类实现操作
public static void main(String[] args) {
/**
* 赋值条件
*/
Person[] array = new Person[5];
for (int i = 0; i < array.length; i++) {
int age = (int) (Math.random() * 50);
array[i] = new Person( "小八嘎", age, "小日本");
}
/**
* 使用内名内部类,重写接口中的方法,将接口中的方法的返回值进行处理
*/
filterArrayUsing(array, new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.getAge() > 10;
}
});
}
lambda表达式实现操作
/*
Lambda 分析
boolean test(T t); ==> 泛型约束为 Person 类型 ==> boolean test(Person t);
方法返回值是 boolean
方法参数
1. 1 个
2. Person 类型
Lambda 格式
p -> {要求必须返回一个 boolean}
*/
filterArrayUsing(array, person -> person.getAge() > 10);
2.4.4.3 类转换器接口
接口设计
// 类型转换器接口
//其中的T类型表示的是 当前的数据类型, 其中的R表示的是期望转换成为的类型
@FunctionalInterface
interface Function<T, R> {
R apply(T t);
}
方法设计
/**
* 功能:将传入的字符串数据进行处理操作导入的数据是String 类型的数据,导出的int类型的数据
*
* @param str String类型的数据
* @param function 类型转换器接口作为参数导入
* @return int 类型的数据
*/
public static int lambdaTest(String str,Function<String,Integer> function){
return function.apply(str);
}
实现
public static void main(String[] args) {
String str = "开封有个包青天";
int i = lambdaTest(str, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
});
System.out.println(i);
int j = lambdaTest(str, s -> s.length());
System.out.println(j);
}
2.4.5 总结省略规则
- 小括号内部的参数类型能够省略
- 方法体中只有一个返回语句的时候,无论是否存在返回值,其中的return关键字、 大括号、 语句分号 都可以删除,如上所示2.4.3
- 方法只有一个参数的时候,小括号也可以省略
3. Stream 流
3.1 概述
从i d k 1.8 之后stream使用的是函数式的编程模式,如同它的名字一样,他可以被用来对
集合或者数组
进行流水线式的操作,可以方便我们对集合或者数组的操作。 stream流操作涉及到数据筛选,排序,转换类型,限制个数,最终处理…并且在处理数据的过程中,对于数据的原始空间没有任何的修改,不影响原始数据。
3.2 Stream中常用的方法
创建Stream对象
// 创建Stream流对象
// 集合对象
Stream<T> stream();
集合对象调用可以直接获取对应当前集合存储元素的 Stream 流对象。
// 数组
Stream<T> Arrays.Stream(T[] t);
利用 Arrays 工具类对当前需要按照 Stream 流方式操作的数据进行转换操作,根据当前数组的数据类型和数据存储情况,返回一个对应的 Stream 流对象
Stream中的中间处理方法
Stream<T> skip(long n);
限制跳过当前 Stream 流对应元素的个数,【掐头】
Stream<T> limit(long n);
限制当前 Stream 对应的元素总个数,【去尾】
Stream<T> sorted();
对当前 Stream 流存储的进行排序操作,要求元素有自然顺序或者遵从 Comparable 接口,默认【升序】
Stream<T> sorted(Comparator<? super T> com);
对当前 Stream 流存储的进行排序操作,排序规则由 Comparator 函数式接口规范
Stream<T> filter(Predicate<? super T> pre);
判断过滤当前 Stream 流可以保存的数据条件,满足条件保留,不满足条件移除,过滤规则由 Predicate 接口约束
Stream<T> distinct();
当前 Stream 流中对应的所有元素去重擦操作
Stream<R> map(Function<T, R> fun);
当前 Stream 存储的数据情况转换为 Function 函数式接口要求的返回值类型,完成类型转换操作。
终止方法
终止方法:Stream流自动关闭,对于Stream流的内存自动被JVM回收
long count();
返回当前 Stream 流对应的数据元素个数,为终止方法。
void forEach(Consumer<? super T> con);
针对于当前 Stream 存储元素的处置方法,为终止方法。
<R, A> R collect(Collector<? super T, A, R> collector);
Stream 流对应的元素存储内容,转换为用户要求的 集合对象。终止方法
常用:
Collectors.toList() 目标存储集合类型为 List 集合
Collectors.toSet() 目标存储集合类型为 Set 集合
Object[] toArray();
Stream 流存储的元素内容转换为 Object 类型数组返回
使用方法
ArrayList<String> list = new ArrayList<>();
list.add("娃哈哈AD钙奶");
list.add("不二家棒棒糖");
list.add("波力海苔");
list.add("大白兔奶糖");
list.add("双汇王中王中王");
list.add("牛肉干");
System.out.println();
list.stream()
.skip(2)
.limit(6)
.filter(s -> s.length() > 4)
.sorted(Comparator.comparing(String::length))
.forEach(System.out::println);
3.3 常用的操作
3.3.1 创建流
单列集合
集合的对象.Stream()
Stream<String> stream = list.stream();
Stream<T> stream();
集合对象调用可以直接获取对应当前集合存储元素的 Stream 流对象。
数组
Arrays.stream(数组) 或者使用Stream.of来创建
arrays.stream或Stream.of将 Arrays Array into a Stream.
- 双列集合
转换成为单列集合再进行创建
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("蜡笔小白",22);
map.put("日向象养",33);
Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();
3.4 常用的操作
在Stream流中的操作,在不知道如何快速的书写的时候,可以通过使用内名内部类加上IDEA的快速生成lambda表达式的方式实现。
skip
中间操作
表示的是跳过当前的前n个元素,返回剩下的元素
list.stream()
.skip(2) // 跳过当前 Stream 流对应元素的前两个元素
.forEach(System.out::println);
limit
中间操作
表示的是可以设置流的最大的长度,超出的部分将会被抛弃
strings.stream()
.skip(2)
.limit(8)// 表示的是从跳过去的第二个开始,一直到八个位置结束
count
count 是一种处理数据的最终的方法或者是
终止的方法
,调用该方法,Stream流会自动关闭,对应的Stream流的空间会被JVM自动回收。可以用来获取当前流中的元素的个数
注意
:需要进行接收得到的数据,然后输出才能够得到所需的元素。
// stream流的声明操作
Stream<String> strings = list.stream();
long count = strings.stream()
.skip(2)
.limit(8)
.filter(s -> s.length() > 2)
.count();
System.out.println(count);
distinct
中间操作
可以去除流中的重复的元素
注意
该方法依赖的是Object的equals方法来判断是否相同的对象,所以需要注意重写equals方法。
Stream<String> stream = strings.stream();
long count = stream
.skip(2)
.limit(8)
.distinct()
.filter(s -> s.length() > 2)
.count();
strings.forEach(System.out::println);
System.out.println(count);
注意
:
在count方法下面仍然进行调用的操作,会出现异常信息
异常信息
stream has already been operated upon or closed
提示告知当前 Stream 已经被关闭!
sorted
中间操作
对于流中的数据元素进行排序操作,如果要调用空参的sort方法,需要流中的元素实现了Comparable接口,重写compareTo方法。
Stream<T> sorted(Comparator<? super T> comparator);
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
//当前方法排序所需的条件,排序规则是通过 Comparator 接口提供,数据类型支持数据排序效果,更好!!!
Stream sorted();
list.stream().sorted().forEach(System.out::println);
//排序方法局限性很大,只能用于已经有自然顺序或者遵从 Comparable 接口的数据类型。
//如果是针对于对象Person而言,很有可能会出现类型转换异常的情况,如果非要调用只能去实现compareable接口,也就是需要重写compareTo方法,也就是提前将person的数据进行一定的处理。
filter
中间操作
可以对于流中间的元素进行条件筛选,符合条件的语句才能够被保留在流中
Stream<T> filter(Predicate<? super T> predicate);
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
检验
//表示的含义是将长度大于2 的保留下来
strings.stream()
.filter(s -> s.length() > 2)
map
中间操作
可以对于流中的数据进行条件筛选过滤,符合筛选条件的才能够保留在流中
//采用内名内部类的方式进行实现操作 stream .skip(2) .limit(8) .distinct() .filter(s -> s.length() > 2) .map(new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } }) .forEach(s -> System.out.print(s)); strings.forEach(System.out::println); //采用lambda表达式进行操作 .map(s -> s.length())
Stream<Person> stream = list.stream();
Stream<String> stringStream = stream.map(s -> s.toString());
stringStream.forEach(System.out::println);
stream.map(person -> person.getAge())
.map(age -> age+10)
.forEach(System.out::println);
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体的操作。
终止操作
Stream<String> stream = strings.stream();
stream
.skip(2)
.limit(8)
.distinct()
.filter(s -> s.length() > 2)
.forEach(s -> System.out.println(s));
strings.forEach(System.out::println);
4. 函数式接口
4.1 概述
只有一个抽象方法的接口
我们成为函数接口。JDK的函数式接口上都加上了@FunctionalInterface 注解进行标注,但是无论是否加上该注解,只要接口中只有一个抽象方法,都是函数式接口。
4.2 常见的函数式接口
-
Consumer 消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对于传入的参数进行消费(实际上接口中的方法就是一种有参 无返回值的模式)
@FunctionalInterface public interface Consumer<T>{ // t 表示的是与接口约束的泛型一致 void accept(T t); }
-
Function计算转换接口
采用的是有参数有返回值的操作,函数型接口遵从的是礼尚往来,对于传入的数据进行一个转换处理。
// 类型转换器接口 @FunctionalInterface interface Function<T, R> { R apply(T t); }
-
Suppier 供给型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对于传入的参数进行生产(实际上接口中的方法就是一种无参数 有返回值的模式)
@FunctionalInterface public interface Suppier{ // t 表示的是与接口约束的泛型一致 T get(); }
-
断定型接口 Predicate
根据其中的参数的类型,能够看出,传入参数,返回的是boolean类型的数据,经常用于条件的判断
// 过滤器接口,判断器接口,条件接口 @FunctionalInterface interface Predicate<T> { /** * 过滤器接口约束的方法,方法参数是用户使用时约束泛型对应具体数据参数 * 返回值类型是 boolean 类型,用于条件判断,数据过来 * * @param t 用户约束泛型对应的具体数据类型参数 * @return boolean 数据,判断结果反馈 */ boolean test(T t); }
5. 方法引用和构造器引用
方法引用
: 若lambda表达式中的内容有方法已经实现了,我们可以使用方法引用,可以理解为方法引用是lambda表达式的另外的一种书写的格式。使用场景
:如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码只是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。格式: 对象:: 实例方法名称 类 :: 静态方法名称 类 :: 实例化方法
构造器的引用
格式: 类名:: new
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照一定的顺序传入了这个构造方法中,这个时候就可以引用构造器
------未完结见下章{Stream和Optional}