面向函数式编程思想
面向过程编程思想 : 凡事必躬亲
面向对象编程思想 : 自己的事情别人做 (懒人思维)
面向函数式编程思想 : 忽略对象,忽略对象的行为声明,更关注对象怎么做
函数式接口
函数式接口 : 普通接口
要求 : 接口中有且仅有一个抽象方法的接口 --> 函数式接口 (自定义常量,默认方法,静态方法,私有方法 --> 这些成员的数量在函数式接口中没有要求)
之前就接触到的函数式接口 :
Runnable 接口 : 线程的任务接口 -> 抽象方法 :void run()
Callable<T> 接口 : 线程带结果的任务接口 -> 抽象方法: V call()
Comparable<E> 接口 : 绑定比较器接口 -> 抽象方法 : int compareTo(E o)
Comparator<E> 接口 : 独立比较器接口 -> 抽象方法 : int compare(E o1,E o2)
FileFilter 接口 : 过滤器接口 -> 抽象方法 : boolean accept(File pathname)
注解 : 约束函数式接口的格式
@FunctionalInterface
Lambda
Lambda : 面向函数式编程的体现
Lambda : 对匿名内部类格式的简化书写
Lambda表达式前提 : 被作用的必须是函数式接口
格式 :
(方法的形参) -> {
重写后的方法体;
}
Lambda的简化格式 :
1. 重写的方法体有且仅有一句话的时候,可以省略重写方法体的 大括号,语句结尾的分号,return //要省略就一起省略
2. 所有重写方法的形参类型可以省略
3. 如果重写方法的形参数量有且仅有一个,可以省略形参的小括号
方法引用
方法引用: 它是对Lambda表达式的简写
方法引用能够使用的条件:
1. 必须满足Lambda表达式的条件(必须是函数式接口)
2. 必须要求重写方法内方法体有且仅有一句代码 --> 严苛
3. 必须要求重写方法内方法体的这句代码: -> 严苛至极
a. 对象调方法
b. 类名调静态方法
c. 创建对象
d. 创建数组
4. 形参要在以上功能中使用
标志 : ::
Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。当Lambda表达式满足一些特殊的情况时,还可以再简化:
方法引用
方法引用的语法格式:
(1)对象名::实例方法 --> 对象调方法
(2)类名::静态方法 --> 类名调用静态方法
说明:
-
::称为方法引用操作符
-
Lambda表达式的形参列表,全部在Lambda体中使用上了,要么是作为调用方法的对象,要么是作为方法的实参。
-
在整个Lambda体中没有额外的数据。
构造器引用
(1)当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表。
(2) 当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度
构造器引用的语法格式:
-
类名::new
-
数组类型名::new
使用说明
方法引用的前提:
1. 必须可以用Lambda
2. 重写方法内的方法体有且仅有一句
3. 要求这一句必须是
对象调方法 : 对象名::方法名;
类名调静态方法 : 类名::静态方法名;
创建对象/创建数组 : 类名::new / 数组类型::new
能用匿名内部类的不一定能用Lambda,能用Lambda的不一定能用方法引用;反之都可以
案例
public class MethodDemo {
public static void main(String[] args) {
//匿名内部类的写法
InterA ia1 = new InterA(){
@Override
public void print(String str) {
System.out.println(str);
}
};
//Lambda表达式的写法
InterA ia2 = str -> System.out.println(str);
// System.out-> 对象
// println(str) -> 方法
//方法引用的写法 :
// 基于Lambda做的简化,要求重写方法内有且仅有一句代码且这句代码是
//要么对象调方法
//要么类名调用静态方法
//要么是创建对象
//要么是创建一个数组
//对象调方法: 格式 对象::方法名
InterA ia3 = System.out::println;
//类名调用静态方法 : 格式 类名::方法名
InterB ib = Integer::parseInt;
//创建对象 : 格式 类型::new
//InterC ic = () -> new Date();
InterC ic = Date::new;
//创建InterD的实现类对象
//InterD id = length -> new int[length];
//创建数组对象 格式: 数组类型::new
InterD id = int[]::new;
}
}
@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);
}
Stream流
Stream流 : 关于集合的简化使用方式 -> 流式编程 (流水线)
Stream流 : 对Lambda表达式和方法引用的推广 (Stream流中大量使用了Lambda表达式)
Stream流初体验
案例需求
按照下面的要求完成集合的创建和遍历
- 创建一个集合,存储多个字符串元素
- 把集合中所有以"张"开头的元素存储到一个新的集合
- 把"张"开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
进工厂方法(进流方法)
1. 集合进流
单列集合 : Collection<E>接口中有方法
-> Stream<E> stream()
双列集合 : 不能直接进流 -> 先把双列变单列再进流
a. Set<K> keySet()
b. Collection<V> values()
c. Set<Map.Entry<K,V>> entrySet()
2. 数组进流 :
Arrays 数组操作的工具类
static Stream<T> stream(任意类型的数组)
3. 同一类型的一组数据进流
Stream<T> 接口中的静态方法 :
static <T> Stream<T> of(任意个同类型的数据)
车间方法(中间方法)
当流对象调用完方法后,得到的还是一个流对象 --> 中间方法
过滤车间
Stream<T> filter(Predicate<? super T> predicate)
Predicate<? super T> : 判断性接口 -> 函数式接口
有且仅有一个抽象方法 : boolean test(T t)
重写test方法就是设定 过滤的条件
截取车间和跳过车间
截取车间 : Stream<T> limit(long maxSize)
: 只保留老流中前maxSize个元素,并生成新的流对象
跳过车间 : Stream<T> skip(long n)
: 跳过(不要)老流中前n个元素,并生成新的流对象
去重车间
去重车间 : Stream<T> distinct() -> 把流中的重复元素直接剔除,保留不重复的元素到新流中
合并车间
Stream 接口的静态方法:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
转换车间
<R> Stream<R> map(Function<? super T,? extends R> mapper) : 把老流中元素的类型从T类型变成R类型,返回的是元素类型是R类型的新流
Function<T,R> : 转换接口 -> 函数式接口
抽象方法 : R apply(T t)
//重写apply方法的逻辑就是如何把 t 元素转换成 r 元素的逻辑
出工厂方法(出流方法)
当流对象调用完方法后,得到的不是一个流对象 --> 终结方法
遍历出厂
void forEach(Consumer<? super T> action) : 遍历流中的元素并使用
Consumer<? super T> : 消费型接口 -> 函数式接口
抽象方法 : void accept(T t)
//重写accept方法的方法体就是在使用 流中的元素
统计出厂
long count() : 统计流中有多少个元素
收集出厂
<R,A> R collect(Collector<? super T,A,R> collector)
Collector : 收集器接口
-> 要启动collect方法,就需要传入一个Collector的实现类对象
-> 程序员自己不会实现Collector接口
-> 程序员借助 Collectors 工具类 :
收集成List集合
static <T> Collector<T,?,List<T>> toList() //Collectors.toList()
收集成Set集合
static <T> Collector<T,?,Set<T>> toSet() //Collectors.toSet()
收集成Map集合
static Collector<T,?,Map<K,U>> toMap(Function<T,K> key, Function<T,V> value)
//Collectors.toMap(如何把流中数据变成键,如何把流中数据变成值)
Stream流综合案例
案例需求
现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
- 男演员只要名字为3个字的前三人
- 女演员只要姓林的,并且不要第一个
- 把过滤后的男演员姓名和女演员姓名合并到一起
- 把上一步操作后的元素作为构造方法的参数创建演员对象 -> String 转换 Actor
- 把转换后的流收集成List集合并遍历List集合
演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法
//创建集合
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("王祖贤");
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 + '\'' +
'}';
}
}