文章目录
一、方法引用
什么是方法引用
方法应用用来代替lambda的一种解决方案。
方法引用符:
符号表示 :::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景 : 如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中,那么则可以使用方法引用。
Lambda表达式写法:s -> System.out.println(s);
拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理。方法引用写法:System.out::println
直接让System.out中的println方法来取代Lambda。
####推导与省略 :
-
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形
式——它们都将被自动推导。 -
而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda的基础,而方法引用是Lambda的简化形式。
常见引用方式
方法引用的替代原则
-
正常情况:引用方法的形参、返回值类型必须同接口中抽象方法的形参、数据类型一致;
-
如果抽像方法有返回值,引用的方法必须也要有返回值;
如果抽象方法没有返回值,引用的方法可以有,也可以没有返回值;
-
被引用的方法的形参类型,要大于,等于抽象方法的形参类型;
被引用方法的返回值类型,要小于,等于抽象方法的返回值类型;
1. 对象名–引用成员方法
如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法。
补充:::符号是可以用来引用非静态的方法,但条件是所用的方法模版第一个参数是所引用方法的实例且后面的参数和返回类型符合引用的非静态发方法就可以,在调用时会用第一个参数这个实例去调用应用的方法。
Function<String, String> toUpperCase = String::toUpperCase;
String::toString
//等价于lambda表达式
(s) -> s.toString()
本质上还是用实例来引用非静态方法
public class DemoMethodRef {
public static void main(String[] args) {
String str = "hello";
printUP(str::toUpperCase);
}
public static void printUP(Supplier< String> sup ){
String apply =sup.get();
System.out.println(apply);
}
}
2. 类名–引用静态方法
由于在java.lang.Math 类中已经存在了静态方法random ,所以当我们需要通过Lambda来调用该方法时,可以使
用方法引用 , 写法是:
public class DemoMethodRef {
public static void main(String[] args) {
printRanNum(Math::random);
}
public static void printRanNum(Supplier<Double> sup) {
Double apply = sup.get();
System.out.println(apply);
}
}
下面两种写法是等效的:
-
Lambda表达式: n -> Math.abs(n)
-
方法引用: Math::abs
3. 类–构造引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new 的格式表示。
首先是一个简单的Person 类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
要使用这个函数式接口,可以通过方法引用传递:
public class Demo09Lambda {
public static void main(String[] args) {
String name = "tom";
Person person = createPerson(Person::new, name);
System.out.println(person);
}
public static Person createPerson(Function<String, Person> fun , String name){
Person p = fun.apply(name);
return p;
}
}
-
Lambda表达式:
name -> new Person(name)
-
方法引用:
Person::new
4. 数组–构造引用
数组也是Object
的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口:(数组构造必须带参) 格式: int[]::new
在应用该接口的时候,可以通过方法引用传递:
public class Demo11ArrayInitRef {
public static void main(String[] args) {
int[] array = createArray(int[]::new, 3);
System.out.println(array.length);
}
public static int[] createArray(Function<Integer , int[]> fun , int n){
int[] p = fun.apply(n);
return p;
}
}
下面两种写法是等效的:
-
Lambda表达式:
length -> new int[length]
-
方法引用:
int[]::new
方法引用只能"引用"已经存在的方法!
二、Stream流
流式思想概述
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的
弊端。
它在Java中是一个接口,它类似于“迭代器”,但比迭代器强大,它也是对集合元素的操作。
获取流方式
java.util.stream.Stream 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
- 所有的Collection 集合都可以通过stream 默认方法获取流;
- Stream 接口的静态方法of 可以获取数组对应的流。
- 根据Collection获取流
首先, java.util.Collection 接口中加入了default方法stream 用来获取流,所以其所有实现类均可获取流。
import java.util.*;
import java.util.stream.Stream;
public class Demo04GetStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
// ...
Stream<String> stream3 = vector.stream();
}
}
-
根据Map获取流
java.util.Map 接口不是Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流
需要分key、value或entry等情况:
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
// 获取所有键的流:
Stream<String> keyStream = map.keySet().stream();
//获取所有值的流:
Stream<String> valueStream = map.values().stream();
//获取所有键值对的流:
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
-
根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream 接口中提供了静态方法of。
-
引用类型数组:
Integer[] arr = {1,2,3,4,5}; Stream<Integer> intStream =Stream.of(arr);
-
基本类型数组:
int[] arr = {1,2,3,4,5}; IntStreamstream =IntStream.of(arr);
-
通过一些零散的数据获取流:
Stream<Integer> stream = Stream.of(1,2,3,4,5);
常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 终结方法:返回值类型不再是Stream 接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。终结方法包括count 和forEach 方法。
- 非终结方法:返回值类型仍然是Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
1. forEach : 逐一处理
该方法并不保证元素的逐一消费动作在流中是被有序执行的。
方法原型:
void forEach(Consumer<? super T> action); //接收一个Consumer 接口函数,会将每一个流元素交给该函数进行处理
方法实例:
import java.util.stream.Stream;
public class Demo12StreamForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(System.out::println);
}
}
2.count:统计个数
方法原型:
long count(); //该方法返回一个long值代表元素个数(不再像旧集合那样是int值)
方法实例:
public class Demo09StreamCount {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
System.out.println(result.count()); // 2
}
}
3.filter:过滤
可以通过filter 方法将一个流转换成另一个子集流。
方法原型:
Stream<T> filter(Predicate<? super T> predicate); //该接口接收一个Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
方法实例:
public class Demo09StreamCount {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("张"));
System.out.println(result.count()); // 2
}
}
4.limit:取用前几个
limit 方法可以对流进行截取,只取用前n个。
方法原型:
Stream<T> limit(long maxSize); //参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
方法实例:
import java.util.stream.Stream;
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
5.skip:跳过前几个
方法原型:
Stream<T> skip(long n); //如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
方法实例:
import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
7.map:映射
如果需要将流中的元素映射到另一个流中,可以使用map
方法。
方法原型:
<R> Stream<R> map(Function<? super T, ? extends R> mapper); //该接口需要一个`Function`函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
方法实例:
import java.util.stream.Stream;
public class Demo08StreamMap {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(Integer::parseInt);
}
}
这段代码中,map
方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer
类对象)。
8.concat:组合
如果有两个流,希望合并成为一个流,那么可以使用Stream 接口的静态方法concat :
方法原型:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) //这是一个静态方法,与`java.lang.String`当中的`concat`方法是不同的。
方法实例:
import java.util.stream.Stream;
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张无忌");
Stream<String> streamB = Stream.of("张翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
函数拼接与终结方法
-
拼接方法:方法返回的是一个Stream流,可以继续调用流的方法。
-
终结方法:方法返回的是一个最终结果,不能再继续调用流的方法。
方法名 方法作用 方法种类 是否支持链式调用 count 统计个数 终结 否 forEach 逐一处理 终结 否 filter 过滤 函数拼接 是 limit 取用前几个 函数拼接 是 skip 跳过前几个 函数拼接 是 map 映射 函数拼接 是 concat 组合 函数拼接 是
收集Stream结果
1. 收集到集合中
Stream流提供collect 方法,其参数需要一个java.util.stream.Collector<T,A, R> 接口对象来指定收集到哪种
集合中。幸运的是, java.util.stream.Collectors 类提供一些方法,可以作为Collector 接口的实例:
public static <T> Collector<T, ?, List<T>> toList() //转换为List集合。
public static <T> Collector<T, ?, Set<T>> toSet() //转换为Set集合。
实例:
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo15StreamCollect {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
}
}
2. 收集到数组中
Stream提供toArray
方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的:
Object[] toArray();
实例:
import java.util.stream.Stream;
public class Demo16StreamArray {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
Object[] objArray = stream.toArray();
}
}
补充
这是几个常用的函数式接口,可以在流中用到,它们主要在java.util.function
包中。
Supplier接口
java.util.function.Supplier<T>
接口,它意味着"供给" , 对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。
抽象方法 : get
仅包含一个无参的方法:T get()
。用来获取一个泛型参数指定类型的对象数据。
源码:
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
实例:求数组元素最大值
使用Supplier
接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer
类。
public class DemoIntArray {
public static void main(String[] args) {
int[] array = { 10, 20, 100, 30, 40, 50 };
printMax(() -> {
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
});
}
private static void printMax(Supplier<Integer> supplier) {
int max = supplier.get();
System.out.println(max);
}
}
Consumer接口
java.util.function.Consumer<T>
接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
抽象方法:accept
Consumer
接口中包含抽象方法void accept(T t)
,意为消费一个指定泛型的数据。
实例:
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function , String str) {
function.accept(str);
}
public static void main(String[] args) {
consumeString(s -> System.out.println(s));
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是Consumer
类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer
接口中的default方法andThen
。
实例:
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two,String str) {
one.andThen(two).accept(str);
}
public static void main(String[] args) {
consumeString(
s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase()),
"HeLLo");
}
}
源码:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
备注:
java.util.Objects
的requireNonNull
静态方法将会在参数为null时主动抛出NullPointerException
异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为“函数Function”。
抽象方法:apply
Function
接口中最主要的抽象方法为:R apply(T t)
,根据类型T的参数获取类型R的结果。使用的场景例如:将String
类型转换为Integer
类型。
实例:
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function, Str str) {
int num = function.apply(str);
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s -> Integer.parseInt(s) , "10");
}
}
默认方法:andThen
接口中有一个默认的
andThen方法,用来进行组合操作,和
Consumer中的
andThen`差不多。
实例:
public class Demo12FunctionAndThen {
private static void method(Function<String, Integer> one, Function<Integer, Integer> two, String str) {
int num = one.andThen(two).apply(str);
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s -> Integer.parseInt(s), i -> i *= 10, "10");
}
}
源码:
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
请注意,Function的前置条件泛型和后置条件泛型可以相同。
Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>
接口。
抽象方法:test
Predicate
接口中包含一个抽象方法:boolean test(T t)
。用于条件判断的场景。
实例:
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate,String str) {
boolean veryLong = predicate.test(str);
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s -> s.length() > 5, "HelloWorld");
}
}
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate
条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and
。
实例:
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two,String str) {
boolean isValid = one.and(two).test(str);
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s -> s.contains("H"), s -> s.contains("W"),"Helloworld");
}
}
默认方法:or
与and
的“与”类似,默认方法or
实现逻辑关系中的“或”。
实例:
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two,String str) {
boolean isValid = one.or(two).test(str);
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s -> s.contains("H"), s -> s.contains("W"),"Helloworld");
}
}
默认方法:negate
这是“非”,代表取反。
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two,String str) {
boolean isValid = one.or(two).test(str);
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s -> s.contains("H"), s -> s.contains("W"),"Helloworld");
}
}
源码:
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}