【问题标题】:Equality of instance of functional interface in java [duplicate]java中功能接口实例的相等性[重复]
【发布时间】:2015-05-11 04:27:59
【问题描述】:

我不确定如何确定函数接口的相等性/不变性。 我想当我在 java 8 中使用这种语法糖时可能无法确保相等,如果您有任何提示,请告诉我。

我为我的问题做了一个简短的代码 sn-p。

public interface Element {
    void doSomething(int a);
}

我已经尝试以功能方式添加此接口的实例

public class FunctionSet {

    public void doubleUp(int a) {
        System.out.println(a*2);
    }

    public void square(int a) {
        System.out.println(a*a);
    }

    public static void main(String[] args) {
        HashSet<Element> set = new HashSet<>();
        FunctionSet functionSet = new FunctionSet();

        set.add(functionSet::doubleUp);
        set.add(functionSet::square);

        System.out.println(set.add(functionSet::doubleUp));
    }

}

它打印为 true,这意味着没有任何相等性检查,而且一旦我添加它,我就无法从 Set 中删除任何实例。

如果我使用函数式接口作为参数,有什么方法可以比较这些实例吗?

将不胜感激任何帮助,在此先感谢!

【问题讨论】:

  • 可变性由状态决定。当一个对象没有状态时(即,当它没有实例字段时),那么它就不能是可变的。因此,无状态对象,例如大多数(全部?)lambdas 总是不可变的。不可变对象可以而且应该是单例,因为它们的状态永远不会改变,但是可以在不同的内存地址有两个原本相同的不可变对象......这将产生两个具有不同身份的等效对象。
  • 简短回答:不,您不能测试来自不同代码位置的两个 lambda 表达式是否相等。
  • @Louis Wasserman:代码位置无关紧要,正确的答案是,即使在相同的代码位置实例化,您也无法测试两个 lambda 表达式是否相等。虽然它在某些情况下可能看起来有效,但这是依赖于实现的行为。

标签: java java-8 immutability equality functional-interface


【解决方案1】:

您可以将方法引用存储到变量中:

public static void main(String[] args) {
    HashSet<Element> set = new HashSet<>();
    FunctionSet functionSet = new FunctionSet();

    Element fn = functionSet::doubleUp;
    set.add(fn);
    set.add(functionSet::square);

    System.out.println(set.add(fn));
}

这样返回false。

当您在不同的代码位置创建相同的 labmda 或方法引用时,这与您在两个位置创建新的匿名类大致相同:

public static void main(String[] args) {
    HashSet<Element> set = new HashSet<>();
    FunctionSet functionSet = new FunctionSet();

    set.add(new Element() {
        @Override
        public void doSomething(int a) {
            functionSet.doubleUp(a);
        }
    });
    set.add(new Element() {
        @Override
        public void doSomething(int a) {
            functionSet.square(a);
        }
    });

    System.out.println(set.add(new Element() {
        @Override
        public void doSomething(int a) {
            functionSet.doubleUp(a);
        }
    }));
}

所以每次它都是不同的对象,尽管它可能看起来一样。对于每个遇到的方法引用,都会在运行时创建单独的匿名类:

Element e1 = functionSet::doubleUp;
Element e2 = functionSet::doubleUp;

System.out.println(e1.getClass());
System.out.println(e2.getClass());

输出将是这样的:

class FunctionSet$$Lambda$1/918221580
class FunctionSet$$Lambda$2/1554547125

所以实际上它是两个不同类的两个不同对象。如果不比较它们的字节码,就很难断定它们做了同样的事情。另请注意,它们都捕获了functionSet 变量,因此还应确保它不会在两个方法引用之间发生更改。

我能想到的唯一解决方法是在代码中将所有方法引用声明为常量,然后再引用它们,而不是直接使用方法引用:

public static final Element FN_DOUBLE_UP = new FunctionSet()::doubleUp; 
public static final Element FN_SQUARE = new FunctionSet()::square; 

public static void main(String[] args) {
    HashSet<Element> set = new HashSet<>();

    set.add(FN_DOUBLE_UP);
    set.add(FN_SQUARE);

    System.out.println(set.add(FN_DOUBLE_UP));
}

【讨论】:

  • “每次都不同的对象”...不太好,但还好,我猜。但是为什么不能让equals 正常工作呢?
  • equals()的默认实现是继承自Object的方法,与==操作符相同。如果您有同一个不可变对象的两个单独实例,equals() 将是错误的,因为每个实例都有自己单独的标识。如果您正在重用 lambda,则应将其存储在 static 字段中,并按照 Tagir 的建议通过字段名称访问它。这将防止多次实例化。
  • @Thilo:添加了更多解释和可能的解决方法,虽然我不确定这会让你开心
  • 将两个静态常量绑定到任意的、否则未使用的 FunctionSet 实例看起来并不令人信服。如果这些实例无关紧要,则应将方法声明为static。否则,如果实例很重要,则无法以这种方式创建静态常量。无论哪种情况,代码都需要清理……
  • @srborlongan:由于Function.identity() 被实现为t-&gt;t,因此不能保证它返回一个唯一的实例。这样做是因为 Oracle 的 JRE 中的当前实现,但这不符合规范。更糟糕的是,在您建议的解决方法public static final Element doubleUp() { return new FunctionSet()::doubleUp; } 中,您可以确定它会在每次调用时返回一个新实例,所以问题仍然存在,这个方法应该解决什么问题......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 2013-11-25
  • 2016-01-07
  • 2018-01-16
  • 1970-01-01
  • 2012-11-27
  • 1970-01-01
相关资源
最近更新 更多