2

几个使用场景

上面几个,是否像我们调用一个方法,调用方法的时候,有这几种形式:

  • 传参数,有返回值:Predicate,Function
  • 传参数,无返回值:Consumer
  • 不传参数,有返回值:Supplier

所以我们调用匿名类的时候,基本都可以用上面几种内置的函数式接口,而不用自己特意去为了一个匿名类定义接口。

其他函数式接口

java.util.function包中,我们可以看到IntPredicateIntFunctionIntSupplierIntConsumerLongConsumerLongFunctionLongPredicateLongSupplier等,是为了在输入和输出都是原始类型时,避免自动装箱的操作。
java的自动装箱机制虽然可以让我们在原始类型和引用类型之间的装箱和拆箱操作是自动完成的,但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
比如下面这个,是没有装箱的:

IntPredicate evenNumbers = (int i) -> i % 2 == 0;
evenNumbers.test(1000);

下面这个,是有装箱的:

Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1;
oddNumbers.test(1000);

方法引用

在java8中,方法引用是调用特定方法的Lambda的一种快捷写法,格式是类名+::+方法,注意后面没有括号,表示没有实际调用这个方法。我们下面看看例子:

静态方法引用

把一组字符串转为数字,并求和

先解析一下,把字符串转数字,就是输入字符串,返回数字,所以我们用的是Function这个内置函数式接口,所以我们是这样写的:

public class StaticMethod {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("1", "2", "3", "4");
        // 方法1
        sum(list, (String str) -> Integer.valueOf(str));
        // 方法2
        sum(list, Integer::parseInt);
    }

    public static <T, R> void sum(List<T> list, Function<T, R> function) {
        Integer sum = 0;
        for (T t : list) {
            sum += (Integer) function.apply(t);
        }
        System.out.println(sum);
    }
}

方法1代码中,我们可以看到Integer.valueOf()是静态方法,接收的参数就是传递过来的参数,所以可以直接用方法2来简写,说明引用的是Integer的valueOf方法。

任意类型实例方法的方法引用

在前面的Function中,我们演示了通过字符串,返回对应的长度这个例子,里面有个Lambda表达式
t -> t.length(),这个t是String类型的,调用的是length()方法,所以我们可以直接这样写:

public class StaticMethod2 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("1", "2", "3", "11", "22", "33");
        getLength(list, String::length);
    }

    public static <T, R> void getLength(List<T> list, Function<T, R> function) {
        Map<T, R> map = new HashMap<>();
        for (T t : list) {
            map.put(t, function.apply(t));
        }
        System.out.println(map);
    }
}

String::length来替换t -> t.length(),说明引用的是String的length方法。这个与静态方法引用不同的是,静态方法引用中,调用的是其他对象的静态方法,而这里是调用的是Lambda参数的方法(比如这个例子的参数类型是string,调用的的length方法)。

现有对象的实例方法的方法引用

Supplier中,我们看了获取字符串的例子,我们把这个稍微改一下,获取字符串的长度。

public class StaticMethod3 {
    public static void main(String[] args) {
        String str = "hello wolrd";
        // 第一个方法
        System.out.println(getStrLength(() -> str.length()));
        // 第二个方法
        System.out.println(getStrLength(str::length));
    }
    public static <T> T getStrLength(Supplier<T> supplier) {
        return supplier.get();
    }
}

第一个方法,是之前的写法,我们可以看到调用的是str的length
方法,于是我们可以直接简写str::length,同样是调用字符串的length方法,上面例子是String::length,这边是str::length,区别在于这边是调用已经存在的外部对象(str)中的方法。

构造函数引用

先定义两个class,一个有参构造,一个无参构造:

class ArgBean {
    Integer arg;

    public ArgBean(Integer arg) {
        this.arg = arg;
    }

    @Override
    public String toString() {
        return "ArgBean{" +
                "arg=" + arg +
                '}';
    }
}

无参构造

无参构造,很明显我们用Supplier,在第一种方法到第二种方法的简化,直接是类+::+new。

public class Constructor {
    public static void main(String[] args) {
        // 第一种方法
        Supplier<Bean> supplier1 = () -> new Bean();
        System.out.println(supplier1.get());
        // 第二种方法
        Supplier<Bean> supplier2 = Bean::new;
        System.out.println(supplier2.get());

    }
}

有参构造

有参构造,通过一个对象返回另外一个对象,所以我们用Function。方式跟上面一样,直接是类+::+new,不同的是需要通过apply传递参数

public class Constructor {
    public static void main(String[] args) {
        // 第一种方法
        Function<Integer, ArgBean> function = (arg) -> new ArgBean(arg);
        System.out.println(function.apply(1));
        // 第二种方法
        Function<Integer, ArgBean> function2 = ArgBean::new;
        System.out.println(function2.apply(2));
    }
}

大军
847 声望183 粉丝

学而不思则罔,思而不学则殆