什么是Supplier

supplier接口是JAVA8以后配合lambda表达式和函数式接口编程(FunctionInterface,以下简称FI)组合使用的一个接口,对外表现为双冒号"::",顺便说下"->"符号对应的是Function接口中的Reply方法例如:

Supplier<Person> persionSupplier = Person::new;
Arrays.asList("a","b","c").forEach(e->System.out.println(e));

其中的双冒号点击进去就是这个Supplier接口:

package java.util.function;

/**
 * Represents a supplier of results.
 *
 * <p>There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #get()}.
 *
 * @param <T> the type of results supplied by this supplier
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

/**
 * Gets a result.
 *
 * @return a result
 */
T get();
}

可以看到这份代码中,有一个比较奇特的注解@FunctionalInterface,这是一个函数式接口的声明。该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

Supplier的使用

我们可以从源代码中看出来,Supplier接口中只有一个方法,就是一个get方法且无需参数,返回的是泛型T。一般的类强制转换为一个接口编译器(eclipse)不会报错但是运行时会报错。假设现在我们新建一个普通类Person:

public class Person{
    Integer id;
    String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    public Person() {
        this.name="mike";
                    System.out.println("创建了一个对象");
    }
    public Person(Integer x,String y) {
        // TODO Auto-generated constructor stub
        this.id = x;
        this.name=y;
    }
//        public String toString() {
//            return this.name;
//        }
    
}

Person类中只有基本的属性与get/set方法,最最常见的对象创建方法就是直接一个new,这是正确的我们也恰好使用的是这个特性

Person::new

在编译器中这句代码相当于声明一个Person类型的Supplier,返回一个Supplier<Person>接口,接下来是重点:使用get方法会执行Person类的无参构造方法,每一次使用get方法都会新建一个对象,所以不是Supplier保存了Person对象,而是需要使用时创建一个,在main中运行以下代码:

    Supplier<Person> persionSupplier = Person::new;
    System.out.println("-------------分割线-------------");
    System.out.println("supplier中的实体类地址为:"+persionSupplier.get().toString());
    persionSupplier.get().setName("coda");
    System.out.println("更改后supplier中的实体类地址为:"+persionSupplier.get().toString());

而输出的结果为:
-------------分割线-------------
创建了一个对象
supplier中的实体类地址为:Person@87aac27
创建了一个对象
创建了一个对象
更改后supplier中的实体类地址为:Person@3e3abc88

可以得知:
第一句代码并没有调用构造方法
第三句代码调用了构造方法所以给出了地址 87aac27
第四句代码也调用了构造方法所以他其实已经不是上面的那个地址为87aac27的Person对象而是一个新的对象了
第五句代码也调用了构造方法所以他是一个全新的对象,地址为:3e3abc88

lambda表达式与Supplier的组合使用

看了以上的同学可能会问,至于吗,我只是想新建一个对象而已,我一个new就能解决的事需要搞这么多花里花俏的?
冷静老哥。JAVA8说了是扩展对象的用法,而不是替代传统的new方式,以下贴代码,注释不需要管,但是也是个学习的东西所以顺便贴上去了,可以解除注释试一下输出:

List<Person> list = new ArrayList();  
list.add(new Person(1, "haha"));  
list.add(new Person(2, "rere"));  
list.add(new Person(3, "fefe"));   
//Map<Integer, Person> mapp = list.stream().collect(Collectors.toMap(Person::getId, Function.identity())); 
//Map<Integer, Person> mapp = list.stream().collect(Collectors.toMap(x -> x.getId(), x->x));
//System.out.println(mapp);
Map<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName)); 
//Map<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName,(x1,x2)->x1));  

以下的功能很简单,把list中的三个Person对象变成一个Map。Map是一个K-V形式的集合,他要求key不可以重复,所以当我们把Person中的构造方法的123改成相同的那就会出现报错,注意下面那句注释(x1,x2)->x1意味着当第一个和第二个参数的key一致时选用第一个元素,这样即算有两个相同的KEY也不会影响到代码的运行,->是什么我会在另外一篇博文中详细介绍,现在我们只关心::,也就是Supplier在这段代码中起什么作用。Person::getId, Person::getName这句话到底是什么意思,这个我们直接看map的输出就好:
{1=haha, 2=rere, 3=fefe}
看到这个结果我们就知道了Person::getId指的是使用Person对象中的ID作为键,使用Person::getName作为Map中元素的值,getId,getName指的是list中的元素使用的方法名字。虽然可能这段代码还是太过于复杂但是这是我觉得比较适合用来诠释JAVA8的操作拓展改变的所以想了一下还是用的这个例子。

总结

Supplier单独的使用并没有什么意义,但是如果要使用JAVA8的其他新特性例如lambda表达式,Function,stream,Collectors等则需要花点时间看一下,否则会发现同样是JAVA为啥他写的像外星文,我写的像是幼儿园的这种闹剧。

标签: none

仅有一条评论

  1. 程序员小羊 程序员小羊

    学到了!!!

添加新评论