Java lambda 自动推断出错

Java lambda 自动推断出错

场景

ListUtil 工具类

/**
 * List 的工具类
 *
 * @author rxliuli
 */
public class ListUtil {
    /**
     * 从第一个集合中过滤掉在第二个集合中的元素
     * 注:比较相等性是直接使用 {@code hashCode/equals} 方法,所以集合中的元素类型 {@link T} 必须要重写这两个方法
     *
     * @param left  第一个集合
     * @param right 第二个集合
     * @param <T>   集合中元素的类型
     * @return 第一个集合过滤掉第二个集合所得到的剩下的元素集合
     */
    public static <T> List<T> filter(List<T> left, List<T> right) {
        return left.stream()
                .filter(l -> !right.contains(l))
                .collect(Collectors.toList());
    }

    /**
     * 从第一个集合中过滤掉在第二个集合中的元素
     * 需要相同类型,但不需要重写 {@code hashCode/equals} 方法,直接比较函数 {@param f} 即可
     * 注意:该方法的时间复杂度为 On^2,如果类型相同没有特别的需求请使用 {@link #filter(List, List)} 方法
     *
     * @param left       第一个集合
     * @param right      第二个集合
     * @param comparator 比较器,指定如何比较两个元素是否相同
     * @param <T>        集合中元素的类型
     * @return 第一个集合过滤掉第二个集合所得到的剩下的元素集合
     */
    public static <T> List<T> filter(List<T> left, List<T> right, Comparator<T> comparator) {
        return filter(left, right, (BiFunction<T, T, Boolean>) (l, r) -> comparator.compare(l, r) == 0);
    }
    
    /**
     * 从第一个集合中过滤掉在第二个集合中的元素
     * 不需要相同类型,也不需要重写 {@code hashCode/equals} 方法,直接比较函数 {@param f} 即可
     * 注意:该方法的时间复杂度为 On^2,如果类型相同没有特别的需求请使用 {@link #filter(List, List)} 方法
     *
     * @param left  第一个集合
     * @param right 第二个集合
     * @param f     比较函数
     * @param <T>   第一个集合的泛型
     * @param <U>   第二个集合的泛型
     * @return 第一个集合独有元素组成的列表
     */
    public static <T, U> List<T> filter(List<T> left, List<U> right, BiFunction<T, U, Boolean> f) {
        return left.stream()
                .filter(l -> right.stream().noneMatch(r -> f.apply(l, r)))
                .collect(Collectors.toList());
    }
}

测试用例

@Test
public void filter3() {
    class User {
        private String name;
        private Integer age;

        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public User setName(String name) {
            this.name = name;
            return this;
        }

        public Integer getAge() {
            return age;
        }

        public User setAge(Integer age) {
            this.age = age;
            return this;
        }
    }
    final List<User> users1 = Lists.newArrayList(new User("rx", 17), new User("haor", 15));
    final List<User> users2 = Lists.newArrayList(new User("Ling", 15), new User("rx", 17));

    //这里无法调用 getName 方法
    ListUtil.filter(users1, users2, (u1, u2) -> Objects.equals(u1.getName(), u2.getName()));
}

然而吾辈无法在 lambda 中调用 getName() 方法,除非我加上类型

@Test
public void filter3() {
    class User {
        private String name;
        private Integer age;

        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public User setName(String name) {
            this.name = name;
            return this;
        }

        public Integer getAge() {
            return age;
        }

        public User setAge(Integer age) {
            this.age = age;
            return this;
        }
    }
    final List<User> users1 = Lists.newArrayList(new User("rx", 17), new User("haor", 15));
    final List<User> users2 = Lists.newArrayList(new User("Ling", 15), new User("rx", 17));

    //声明 User 类型后就调用 getName 方法了,发生了什么?
    ListUtil.filter(users1, users2, (User u1, User u2) -> Objects.equals(u1.getName(), u2.getName()));
}

明明使用了泛型,而且有类型自动推断,为什么还是要声明类型呢?

错误截图

clipboard.png

而 IDEA 似乎认为可以直接调用,甚至都有提示

clipboard.png

知道了问题发生的原因了,无法确定调用哪个???

clipboard.png

那么,为什么 ComparatorBiFunction 两个不相干的类会有冲突呢?以及这种冲突应当如何解决呢?

阅读 2.5k
4 个回答

嗯嗯,根据题主最新的细节,那就是啦,你有两个相同方法,两个filter方法,其实对应着这样两种调用场景

  1. 类型1, 类型2, 类型1+类型2的FunctionalInterface
  2. 类型1, 类型1, 类型1+类型1的FunctionalInterface

因为Comparator其实本质也是一个BiFunction,它的函数式方法是

int compare(T o1, T o2);

这样的模式不就是BiFunction么,只是说这两个是不同的FunctionalInterface,就像BinaryOperatorBiFunction类似

因此当你用相同的类型User去调用filter方法时,两个方法其实都匹配,所以第三个参数推断不出来,只能默认是Object类型了

假设你再创建一个新类型UserNew,跟User类型差不多,此时两个不同类型参数去调用filter方法,那肯定是才用第一个了,这样就不会报错了

final List<User> users1 = Lists.newArrayList(new User("rx", 17), new User("haor", 15));
final List<User> users2 = Lists.newArrayList(new User("Ling", 15), new User("rx", 17));
final List<UserNew> users3 = Lists.newArrayList(new UserNew("Ling", 15), new UserNew("rx", 17));

//这里无法调用 getName 方法
ListUtil.filter(users1, users2, (u1, u2) -> Objects.equals(u1.getName(), u2.getName()));
//这里可以调用 getName 方法
ListUtil.filter(users1, users3, (u1, u2) -> Objects.equals(u1.getName(), u2.getName()));

当然题主肯定会说,我也有用相同类型去调用方法1的场景啊,那就需要指明第三个参数类型
比如题主的例子,最终应该这么写

// 指明第三个参数是BiFunction,这样就不会报错了
BiFunction<User, User, Boolean> biFunction = (u1, u2) -> Objects.equals(u1.getName(), u2.getName());
ListUtil.filter(users1, users2, biFunction);

以上仅供参考(~ ̄(OO) ̄)ブ

试试写进方法里

1,这个Lists.newArrayList 方法是你自己封装的吧,是不没有带泛型?即便你使用list<user> 接收,原类型还是obj

2,使用工具包中的lists,带泛型参数的,和原生的arraylist 都是没问题的

3,运行结果
clipboard.png

我试了,不带类型可以访问getName()方法

部分代码
public static void main(String[] args) {
        List<User> users1 = Lists.newArrayList(new Example_1().new User("rx", 17), new Example_1().new User("haor", 15));
        List<User> users2 = Lists.newArrayList(new Example_1().new User("Ling", 15), new Example_1().new User("rx", 17));
        Example_1.filter(users1, users2, (u1, u2) -> Objects.equals(u1.getName(), u2.getName())).forEach(System.out::println);
    }

    public static <T, U> List<T> filter(List<T> left, List<U> right, BiFunction<T, U, Boolean> f) {
        return left.stream()
                .filter(l -> right.stream().noneMatch(r -> f.apply(l, r)))
                .collect(Collectors.toList());
    }

    class User {
        private String name;
        private Integer age;
        ...

clipboard.png

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