【问题标题】:Can an interface somehow prevent lambda expression implementations?接口可以以某种方式阻止 lambda 表达式的实现吗?
【发布时间】:2015-08-18 08:27:43
【问题描述】:

背景

我将以下接口作为 API 的一部分公开:

public interface Pasture {
    /**
     * @param t         The time of the visit (as measured from optimization starting point).
     * @param tLast     The time of the preceding visit (as measured from optimization starting point).
     * @return          The expected reward that will be reaped by visiting under the given conditions.
     */
    double yield(long t, long tLast);    
}

客户端将“牧场”模型作为实现此接口的对象传递给我。每个对象代表一个牧场。

在 API 方面,我会在不同时间跟踪对这些对象的“访问”,然后在需要知道牧场同时生产多少时调用 pasture.yield(time, lastVisitTime)

问题出现了,这个接口可以在客户端实现为 lambda 表达式,而 apparently each lambda expression instantiation does not necessarily create a new object with a new identity,这是我用来跟踪在什么时间访问了哪些牧场的方法。

问题

有没有办法阻止接口被实现为 lambda 表达式,而是强制客户端将其实现为匿名类。当然,向它添加一个虚拟方法可以解决问题,但在我看来,这将是任意且不整洁的。还有其他方法吗?

【问题讨论】:

  • 你不能检测到重复的牧场通过并适当地处理它吗?注意:使用匿名内部类确实保证每次都会创建一个新的。
  • ^ 这个:lambda 和匿名类的区别主要是在这方面的语法。也许您需要更高抽象级别的另一种方法(也许“Pasture”作为一个抽象类,只能通过工厂左右实例化,但对于具体提示,缺少有关意图使用的详细信息......)
  • 什么会阻止您的客户将Pasture 的单个实例保存在静态/实例变量中并重用它? Java 编译器在对 lambda 进行脱糖时会执行完全相同的操作。
  • 那么您的客户必须注意遵守合同,无论是使用 lambda 表达式还是任何其他方式来实现接口。关键是 lambdas 对你来说不是一个特例。
  • 在Java中很简单:你申请new,你得到一个新的实例;您不应用它-> 不保证。每个 Java 开发人员都应该意识到这一点。 Lambda 没有什么特别之处,它们只是另一种表达方式。普通的 Java 开发人员是否期望 (Integer) 3 总是返回 Integer 的新实例?我希望不会,而且肯定是开发人员有责任知道这一点。

标签: java lambda


【解决方案1】:

问题不是 lambda 边缘情况。

您正在假设同一个对象代表同一个Pasture,因此使您的对象同时做两件事。那是代码异味,是您遇到困难的原因。

您应该强制您的Pasture 对象实现equals 之类的东西,这样您就可以检查这些项目是否相同。遗憾的是,没有interface 可以做到这一点,最近的是Comparable

public interface Pasture extends Comparable<Pasture> {

【讨论】:

  • 关于设计的好点,但我只想根据equalshashCode 指定不同牧场必须不相等的合同。这将进一步允许 OP 有一个有意义的存储实现(HashMap)。
  • @tennenrishin - 代码异味源于您的(错误的)假设,即如果两个对象相等,则它们是不同对象。这是一个不安全的假设——在 Java 中你只能假设如果两个对象 are 相等(在== 意义上)那么它们是同一个对象。通过强制使用 equals(正如 Marko 建议的那样)或 compareTo,您是在执行合同而不是假设它。
  • @MarkoTopolnik - 同意 - 遗憾的是,没有可强制执行的机制来确保对象正确实现 equalsComparable 不是完美的解决方案,但恕我直言,它比仅仅记录要求更好。
  • 好吧,不要让我开始使用损坏的Comparable 实现:) 没有任何东西 可以在任何编程语言中强制正确性。如果牧场无与伦比,Comparable 就无法解决 OP 的问题。
  • 没错,就是一针见血。在合同中谈论equals 使开发人员敏锐地意识到需要提供适当实施的实例。它还明确指出 Pasture 实际上有 两个 关注点,即使它在接口中只有一个自定义方法。您甚至可以将equals 添加到界面并在那里适当地记录它。
【解决方案2】:

您可以在方法中添加一些已检查的异常:

public interface Pasture {
   double yield(long t, long tLast) throws Exception;    
}

但拒绝 lambda 是一种奇怪的方法。

【讨论】:

    【解决方案3】:

    "[...] 显然,每个 lambda 表达式实例化并不一定会创建一个具有新标识的新对象,这是我所依赖的,以便跟踪在什么时间访问了哪些牧场。"

    强调我的。)

    当你在做某事时,它给你带来了麻烦,为什么不停止做呢?

    相反,每当客户向您传递“牧场”时,为其分配一个新的内部 ID 并关联牧场(以及您可能希望关联的任何元数据,例如上次访问时间)存储它)与ID。

    这样,如果客户端多次向您传递相同的牧场对象,没关系 - 就您的代码而言,这些牧场将具有不同的 ID,因此代表不同的牧场使用他们自己的上次访问时间和其他数据,即使它们的客户端部分可能由同一个对象实现。

    (至少,只要重用的牧场对象不保持任何可变的内部状态,这是正确的;但您的客户应该知道不要重用具有这种状态的对象,so does the Java 8 lambda implementation。)

    这是一个使用简单的连续整数 ID 的快速示例实现:

    public class PastureManager {
        private int maxID = 0;
        private Map<Integer, Pasture> pastures = new HashMap<Integer, Pasture> ();
        private Map<Integer, Long> lastVisit = new HashMap<Integer, Long> ();
        long time = 0;
    
        public int addPasture (Pasture pasture) {
            maxID++;
            int id = maxID;
            pastures.put(id, pasture);
            lastVisit.put(id, time);
            return id;  // in case the client needs to re-identify the pasture
        }
    
        public double getYieldFor (int id) {
            Pasture pasture = pastures.get(id);
            if (pasture == null) {
                throw new IllegalArgumentException("Invalid pasture ID " + id);
            }
            long lastTime = lastVisit.put(id, time);
            return pasture.yield(time, lastTime);
        }
    
        // ...remaining code omitted...
    }
    

    这里的关键特性是,在添加 Pasture 对象并为其分配 ID 之后,您永远不会传递它们,并且您尤其是不比较它们或将它们用作映射键。相反,任何需要引用特定牧场的代码都应该使用牧场的 ID;只有addPasture()getYieldFor() 方法(可能还有removePasture(),如果你有这样的方法)需要访问存储在pastures 映射中的实际Pasture 对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-02-06
      • 2011-02-22
      • 2015-07-05
      • 2011-04-21
      • 1970-01-01
      • 2023-03-18
      • 2018-02-08
      相关资源
      最近更新 更多