12、函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
函数式编程:Lambda表达式:
将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。
常用函数式接口
1、Supplier接口(生产型接口)
java.util.function.Supplier< T > 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。
//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
Public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
//调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
String s = getString(()->{
//生产一个字符串,并返回
return "胡歌";
});
System.out.println(s);
//优化Lambda表达式
String s2 = getString(()->"胡歌");
System.out.println(s2);
}
2、Consumer接口(消费型接口)
java.util.function.Consumer< T > 接口不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。接口中包含抽象方法 void accept(T t) ,泛型执行什么类型,就可以使用accept方法消费什么类型的数据。
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
Consumer接口有一个默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。
//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
public static void printInfo(String[] arr, Consumer<String> con1,Consumer<String> con2){
//遍历字符串数组
for (String message : arr) {
//使用andThen方法连接两个Consumer接口,消费字符串
con1.andThen(con2).accept(message);
}
}
public static void main(String[] args) {
//定义一个字符串类型的数组
String[] arr = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
//调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
printInfo(arr,(message)->{
//消费方式:对message进行切割,获取姓名,按照指定的格式输出
String name = message.split(",")[0];
System.out.print("姓名: "+name);
},(message)->{
//消费方式:对message进行切割,获取年龄,按照指定的格式输出
String age = message.split(",")[1];
System.out.println("。年龄: "+age+"。");
});
}
3、Predicate接口(判断型接口)
java.util.function.Predicate< T > 接口包含一个抽象方法: boolean test(T t)。用于条件判断的场景,从而得到一个boolean值结果。
public static boolean checkString(String s, Predicate<String> pre){
return pre.test(s);
}
public static void main(String[] args) {
//定义一个字符串
String s = "abcdef";
//调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
/*boolean b = checkString(s,(String str)->{
return str.length()>5;
//判断参数传递的字符串的长度是否大于5,并返回结果
});*/
//优化Lambda表达式
boolean b = checkString(s,str->str.length()>5);
System.out.println(b);
}
Predicate接口的默认方法:and
将两个 Predicate 条件使用"与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and
Predicate接口的默认方法:or
与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”
Predicate接口的默认方法:negate
默认方法 negate 实现逻辑关系中的““非”(取反)”
//and
private static void method(Predicate<String> one, Predicate<String> two) {
//使用实现类a.and(实现类b)
boolean isValid = one.and(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
//判断一个字符串既要包含大写“H”,又要包含大写“W”
}
}
//or
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
//判断字符串包含大写H或者包含大写W
}
//negate
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5);
//判断字符串长度是否大于5
}
4、 Function接口(功能型接口)
ava.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据。
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用的场景例如:将String类型转换为Integer类型。
Function接口的默认方法:andThen
用来进行组合操作
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("qqq",12));
list.add(new Person("www",13));
list.add(new Person("eee",69));
//将封装为List的Person对象中的年龄后加一个0;
ArrayList<String> list1 = P2(list, (Person person)-> {
return person.getAge();
//将Person类型对象转为int类型年龄
}, (Integer integer)-> {
return integer + "0";
//将int类型年龄转为String类型拼接
});
System.out.println(list1);
}
private static <P,T,X> ArrayList<X> P2 (ArrayList<P> al,
Function<P,T> fun1,Function<T,X> fun2){
//P代表Person类型,T代表int类型,X代表String类型
ArrayList<X> newList = new ArrayList<>();
for (P p : al) {
//调用apply默认方法,传入Person类型对象,再使用andThen拼接需要进行的两步操作
X apply = fun1.andThen(fun2).apply(p);
newList.add(apply);
}
return newList;
}
13.1 Stream流
两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
java.util.stream.Stream 是Java 8新加入的最常用的流接口。
所有的 Collection 集合都可以通过 stream 默认方法获取流;
Stream 接口的静态方法 of 可以获取数组对应的流。
//ArrayList
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
//Map
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
//数组
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream<String> stream = Stream.of(array);
Stream流的常用方法:
延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法。
1、逐一处理:forEach
void forEach(Consumer<? super T> action);
**Consumer接口中包含抽象方法void accept(T t)
Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(name‐> System.out.println(name));
2、过滤:filter
Stream< T > filter(Predicate<? super T> predicate);
** Predicate接口中包含抽象方法boolean test(T t);
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
//会将original中的以张开头的字符串全部去除,把剩下的赋值给result
3、映射:map
将流中的元素映射到另一个流中
< R > Stream< R > map(Function<? super T, ? extends R>mapper);
** Function接口中包含抽象方法R apply(T t);
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
//将origninal中字符串转为Integer类型赋值给result
4、取用前几个:limit
Stream< T > limit(long maxSize);
5、跳过前几个:skip
Stream< T > skip(long n);
6、组合:concat
static < T > Stream< T > concat(Stream<? extends T> a, Stream<? extends T> b)
Stream<String> result = Stream.concat(streamA, streamB);
//这是Stream类的静态方法
7、统计个数:count
long count();
Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
Stream<String> stream2 = Stream.of("美羊羊","喜洋洋","懒洋洋","灰太狼");
Stream.concat(stream1.limit(2),stream2.skip(2))
.forEach(s -> System.out.println(s));
//stream1只要前两个,stream2不要前两个,然后合并为一个流,再遍历输出
13.2 方法引用
Lambda
@FunctionalInterface
public interface Printable {
void print(String str);
}
public class Demo01PrintSimple {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(s ‐> System.out.println(s));
//拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。
}
}
使用方法引用:
public class Demo02PrintRef {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(System.out::println);
//直接让 System.out 中的 println 方法来取代Lambda
}
}
"::"这种写法即为方法引用:
如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者
1、通过对象名引用成员方法
//函数式接口
@FunctionalInterface
public interface Printable {
void print(String str);
}
//一个类中已经存在了一个成员方法
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
}
//当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的Lambda的时候,已经具有了MethodRefObject 类的对象实例,则可以通过对象名引用成员方法
public class Demo04MethodRef {
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase);
}
}
//使用了与预期效果(变大写)一样的MethodRefObject()的对象的printUpperCase方法作为方法引用,
//通过直接使用MethodRefObject()的成员方法printUpperCase,实现了少写了一次实现语句(str.toUpperCase())
2、通过类名称引用静态方法
@FunctionalInterface
public interface Calcable {
int calc(int num);
}
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
这个例子中,
Lambda表达式: n -> Math.abs(n)
方法引用: Math::abs
两种写法是等效的
3、通过super引用成员方法
@FunctionalInterface
public interface Greetable {
void greet();
}
//父类
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
//子类
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
//使用lambda方法:
public void show(){
//调用method方法,使用Lambda表达式
//method(()‐>{
//创建Human对象,调用sayHello方法
// new Human().sayHello();
//});
//简化Lambda
//method(()‐>new Human().sayHello());
//使用super关键字代替父类对象
method(()‐>super.sayHello());
}
}
//使用方法引用
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
}
在这个例子中,下面两种写法是等效的:
Lambda表达式: () -> super.sayHello()
方法引用: super::sayHello
this引用成员方法同理
4、类的构造器引用
public interface PersonBuilder {
Person buildPerson(String name);
}
//lambda
public class Demo09Lambda {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("赵丽颖", name ‐> new Person(name));
}
}
//方法引用
public class Demo10ConstructorRef {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("赵丽颖", Person::new);
}
}
在这个例子中,下面两种写法是等效的:
Lambda表达式: name -> new Person(name)
方法引用: Person::new
5、数组的构造器引用
@FunctionalInterface
public interface ArrayBuilder {
int[] buildArray(int length);
}
//lambda
public class Demo11ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, length ‐> new int[length]);
}
}
//方法引用
public class Demo12ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, int[]::new);
}
}
在这个例子中,下面两种写法是等效的:
Lambda表达式: length -> new int[length]
方法引用: int[]::new