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中多个对象的呢?

阅读 796
评论 2019-03-06 提问
    2 个回答
    imango
    • 1.6k

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

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

    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");
      评论 赞赏
        撰写回答

        登录后参与交流、获取后续更新提醒