java 函数式接口例子 求解运行过程

看到别人写的一个工厂模式的demo代码,用到了Lambda表达式以及Consumer和Supplier.
但遇到一个地方看不太懂 求各位解答下


Builder.java

public interface Builder {
  void add(WeaponType name, Supplier<Weapon> supplier);
}

WeaponFactory.java

public interface WeaponFactory {

  /**
   * Creates an instance of the given type.
   * @param name representing enum of an object type to be created.
   * @return new instance of a requested class implementing {@link Weapon} interface.
   */
  Weapon create(WeaponType name);

  /**
   * Creates factory - placeholder for specified {@link Builder}s.
   * @param consumer for the new builder to the factory.
   * @return factory with specified {@link Builder}s
   */
  static WeaponFactory factory(Consumer<Builder> consumer) {
    Map<WeaponType, Supplier<Weapon>> map = new HashMap<>();
    consumer.accept(map::put);
    return name -> map.get(name).get();
  }
}

WeaponType.java

public enum WeaponType {
  SWORD, AXE, BOW, SPEAR
}

App.java

public class App {

    private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

    /**
     * Program entry point.
     *
     * @param args command line args
     */
    public static void main(String[] args) {
        WeaponFactory factory = WeaponFactory.factory(builder -> {
            builder.add(WeaponType.SWORD, Sword::new);
            builder.add(WeaponType.AXE, Axe::new);
            builder.add(WeaponType.SPEAR, Spear::new);
            builder.add(WeaponType.BOW, Bow::new);
        });
        Weapon axe = factory.create(WeaponType.AXE);
        LOGGER.info(axe.toString());
    }
}

想请教,在App.java中WeaponFactory.factory接受的是Consumer<Builder>类的函数式接口,那builder的add方法是如何实现的呢?并没有看到对add方法进行lambda表达式的实现?请问这是什么原理?

map又是如何接受builder中多个对象的呢?

阅读 3k
2 个回答

题主好,以下是我个人对于函数式接口的理解

以往我们在看一个方法时,看它的关键点其实就是这么几个

  1. 方法名
  2. 方法参数
  3. 方法体
  4. 方法返回类型

我们随便拿一个方法来举例:

clipboard.png

所以可以看到方法其实是以固定参数返回固定值的一个抽象过程,而这里的固定参数就是我们平常用的数据类型
因此至此可以说,方法和数据类型是分开的,两个没啥实际关联,是不同种类的东西,方法只是需要数据进行执行而已

举个实际例子,例如如何钓鱼这个方法,需要用鱼竿和诱饵这两个实际的东西配合钓鱼的方法才可以执行
不过钓鱼也可以是一个实际东西,比如我把如何用鱼竿和诱饵钓鱼的方法写在纸上,交给你
这个时候这个纸也成了一个实际东西,也就是说钓鱼这个方法也就成了一个实际东西了

函数式接口就是这么一个纸,把方法承载下来,当然它也是一个数据类型,也就是说你可以在方法里传递方法

函数式接口也是一个方法,那它也符合方法刚才那4个关键点,以Builder.java为例,我们写一个Builder的函数式接口的实例

Builder builder = (name, supplier) -> {
            name.toString();
            return;
        };

clipboard.png

所以可以看到只是写法不一样,由于这个Builder的函数式接口定义为void返回,所以可以写出return;也可以不用写,这么就可以不用大括号了

Builder builder = (name, supplier) -> name.toString();

这是那张纸。。。所以你得需要实际数据去执行,执行时,只用看函数式接口定义的方法名就可以了,为了方便举例,我们换个函数式接口来看,比如java.util.function.Function

@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);

可以看到这个Function的定义中,用了泛型,那就是说,只要是满足一个参数T,返回一个R的方法都可以叫Function,哈哈,所以这个很抽象的东西,只要符合都是Function的实例

比如:Object的方法toString

Function function = o -> o.toString();

假如你有个A类的实例a,想执行atoString方法

A a = new A();
a.toString();

哈哈哈,这只是弟弟的执行方式

你可以这么骚起来

A a = new A();
Function function = o -> o.toString();
function.apply(a);

所以要是你不知道函数式接口怎么执行的,比如上面那个例子,不知道function怎么执行,你直接找到这个function的接口方法apply在哪里调用的,function.apply(a),这里apply的传入的参数是a,这个时候,你用a当作参数,去执行方法体o.toString()就可以了

这里要提到一点,有个方法引用的语法糖吧,比如o -> o.toString(),这里其实用的是ObjecttoString()方法,所以你可以直接写成Object::toString

A a = new A();
Function function = Object::toString;
function.apply(a);

说了这么多,再回到题主问题上

“那builder的add方法是如何实现的呢?并没有看到对add方法进行lambda表达式的实现?”

我们知道builder是函数式接口Builder的实例,要知道它是如何实现的,我们就先找到Builder的接口方法add
然后我们再找add方法什么时候执行的

clipboard.png

找到了,builder.add(WeaponType.SWORD, Sword::new);,这里就已经执行了builderadd方法,往回看builder的定义,但是这时候builder并不是像我们之前的function一样在上一行有定义,而是builder本身就在一个lamdba表达式中,所以这就是方法中嵌套方法了,类似之前钓鱼的例子,如何钓鱼的纸上写的是一张地图,需要先要找到另一张纸才可以找到如何钓鱼的方法

所以还是按照之前我说的方法,既然builder这个时候是lamdba表达式中的一个参数,那还是看这个函数式接口是在哪里调用的即可

clipboard.png

经过查找,我们知道这个时候是调用的WeaponFactory.factory方法,而这个方法参数是个Consumer<Builder>Consumer是个自带的函数式接口,代表任何一个参数T,执行后,不返回

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

它的接口方法是accept,所以找到其执行的方法位置consumer.accept(map::put);
也就是说这个参数是map::put,这是一个方法引用,其实就是Map.put方法,那这个明明是Builder泛型的Consumer,它传入Map.put是个Builder么?

我们看看Map.put的定义

V put(K key, V value);

Builder是传入WeaponTypeSupplier<Weapon>,不返回
Map.put是传入泛型K和泛型V,返回V

感觉不一样。。。其实是一样的。。。因为不返回其实也是返回,它返回的是大写开头的Void,其实Builder应该是Builder<WeaponType, Supplier<Weapon>, Void>,此时由于Void,必须返回,且必须返回null,因此Map.put是一个Builder的实例

所以最终串起来,WeaponType.SWORD, Sword::new两个参数实际是执行了一个Map.put方法...

emmm,所以第一次看完你这个工厂,我咋感觉写了一个像这样的东西而已,不用BuilderWeaponFactory,因为底层用的map,所以map就完事了

public class App {
    private static Map<WeaponType, Supplier<Weapon>> map = new HashMap<>();

    static {
        map.put(WeaponType.SWORD, Sword::new);
        map.put(WeaponType.AXE, Axe::new);
        map.put(WeaponType.SPEAR, Spear::new);
        map.put(WeaponType.BOW, Bow::new);
    }

    /**
     * Program entry point.
     *
     * @param args command line args
     */
    public static void main(String[] args) {
        Weapon axe = map.get(WeaponType.AXE).get();
    }
}

当然这么写感觉不是很高逼格,或者说还不是很好,本来是想要表示一个一对一的对应关系的,所以花费一个额外的map对象来存储对应的关系,其实在Java中表示一一对应关系的可以才用枚举嘛,枚举就可以解决,我平常也比较喜欢用枚举来表示固定的一一对应关系,也好写注释,用枚举WeaponTypeMapper 来代替map

@Getter
@AllArgsConstructor
public enum WeaponTypeMapper {

    SWORD(WeaponType.SWORD, Sword::new),
    AXE(WeaponType.AXE, Axe::new),
    SPEAR(WeaponType.SPEAR, Spear::new),
    BOW(WeaponType.BOW, Bow::new),

    ;

    private WeaponType weaponType;
    private Supplier<Weapon> supplier;

    public static Weapon getWeapon(WeaponType weaponType){
        Weapon weapon = Arrays.stream(WeaponTypeMapper.values())
                .filter(weaponTypeMapper -> weaponTypeMapper.getWeaponType().equals(weaponType))
                .findFirst()
                .map(WeaponTypeMapper::getSupplier)
                .map(Supplier::get).get();
        return weapon;
    }
}

然后获取Weapon的时候,直接

public class App {
    public static void main(String[] args) {
        Weapon axe = WeaponTypeMapper.getWeapon(WeaponType.AXE);
    }
}

囧。。。以上。。。仅供参考

https://colobu.com/2014/10/28...

// 也可以这样
interface Builder<A, B> {
    void add(A type, Supplier<B> supplier);
}

consumer.accept((Builder<WeaponType, Weapon>)map::put);
interface P {
    void say(String msg);
}

// 通过lambda表达式创建了一个P实例,拥有say方法
P p = name -> {
  System.out.println(name);
};

p.say("lalala");
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏