什么';Java处理闭包的方式是什么?

什么';Java处理闭包的方式是什么?,java,Java,我熟悉函数语言和闭包,并对以下错误感到惊讶: 这是我的密码: Session dbSession = HibernateUtil.getSessionFactory().openSession(); Transaction dbTransaction = dbSession.beginTransaction(); Criteria criteria = dbSession.createCriteria(Invite.class).add(Restrictions.eq("uuid", path)

我熟悉函数语言和闭包,并对以下错误感到惊讶:

这是我的密码:

Session dbSession = HibernateUtil.getSessionFactory().openSession();
Transaction dbTransaction = dbSession.beginTransaction();
Criteria criteria = dbSession.createCriteria(Invite.class).add(Restrictions.eq("uuid", path).ignoreCase());
Invite invite = (Invite) criteria.uniqueResult();
if (invite.isExpired()) {
    // Notify user the invite has expired.
} else {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            // ERROR: `invite` is not guaranteed to exist when this code runs
            invite.setExpired(true);
        }
    }, MAX_TIME);
}

据我所知,在
TimeTask
实例中引用
invite
是一个错误,因为该变量不能保证存在。所以我的问题是,Java是如何表达我想要的,即加载一个invite,然后设置一个计时器,使invite在一段时间后过期。

据我所知,错误并不在于不能保证
invite不存在。错误应为:

"cannot refer to a non-final variable inside an inner class defined in a different method"
我认为错误是因为当
invite
变量不能保证这样做时,它会导致各种各样的问题

如果Java运行时输入以下代码:

new TimerTask() {
    @Override
    public void run() {
        // ERROR: `invite` is not guaranteed to exist when this code runs
        invite.setExpired(true);
    }
}
Session dbSession = HibernateUtil.getSessionFactory().openSession();
Transaction dbTransaction = dbSession.beginTransaction();
Criteria criteria = dbSession.createCriteria(Invite.class).add(Restrictions.eq("uuid", path).ignoreCase());
Invite invite = (Invite) criteria.uniqueResult();
if (invite.isExpired()) {
    // Notify user the invite has expired.
} else {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            // ERROR: `invite` is not guaranteed to exist when this code runs
            invite.setExpired(true);
        }
    }, MAX_TIME);
    invite = null; //after creating the object, set the invite.
}
它将
invite
值(参考)复制到新的
TimerTask
对象。它不会引用变量本身。在方法被保留之后,在所有变量都不存在之后(它从调用堆栈中回收)。如果引用变量,可以创建一个悬空指针

我认为Java希望变量是
final
,因为以下代码:

new TimerTask() {
    @Override
    public void run() {
        // ERROR: `invite` is not guaranteed to exist when this code runs
        invite.setExpired(true);
    }
}
Session dbSession = HibernateUtil.getSessionFactory().openSession();
Transaction dbTransaction = dbSession.beginTransaction();
Criteria criteria = dbSession.createCriteria(Invite.class).add(Restrictions.eq("uuid", path).ignoreCase());
Invite invite = (Invite) criteria.uniqueResult();
if (invite.isExpired()) {
    // Notify user the invite has expired.
} else {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            // ERROR: `invite` is not guaranteed to exist when this code runs
            invite.setExpired(true);
        }
    }, MAX_TIME);
    invite = null; //after creating the object, set the invite.
}

您可能希望稍后在流程中将
invite
设置为
null
。这将对
TimerTask
对象产生影响。为了避免此类问题,通过强制变量为
final
,可以清楚地知道传递给
TimerTask
的值是什么,以后不能修改,Java程序员只需认为方法调用的值“始终存在”这要容易得多。

这里似乎有一些更基本的数据库设计和软件架构问题

与其在一定时间后设置一些“过期”字段,不如存储实际的过期时间。然后,当用户执行操作时,只需对照当前时间检查过期时间,以查看邀请是否过期。这样,它总是有效的,您不必安排计时器或管理长时间运行的事务或诸如此类的事情。它还隐式地在程序重启/崩溃时保持不变(如果程序在计时器运行时终止,当前基于计时器的方法将需要努力防止它忘记终止挂起的邀请)

如果您希望实时通知到期情况,请将“用户已收到通知”字段(例如)添加到邀请中。然后创建一个重复的后台任务(一个计时器可以完成此任务,或者一个
ScheduledExecutorService
),该任务在单个条件查询中定期获取所有过期未通知邀请的列表。发出通知,设置通知标志,冲洗,重复。如果通知非常耗时(例如发送电子邮件),您可以在线程池
ExecutorService
中排队通知(如果愿意)

闭包(或其近似值)不是这个工作的合适工具


但是,如果必须执行定时标志,请将hibernate设置为每线程会话对象模式(实际上,我认为这甚至可能是默认模式),然后使用线程池ExecutorService(请参阅Executors)来调度打开事务、查询邀请、等待(无计时器)的任务,然后执行该任务并关闭事务。然后,您的整个事务都在一个后台线程上,您遇到的奇怪的事务管理问题不再存在

更好的是,停止在一个长时间运行的事务中尝试所有这些(例如,如果用户希望在计时器运行时删除邀请,该怎么办?)。打开事务,然后查询邀请,然后关闭它。然后设置计时器(或使用ScheduledExecutorService
),让计时器打开事务,查询邀请,使邀请过期,然后关闭它。您可能不想在整个时间间隔内保持数据库连接和/或事务打开,没有理由这样做


至于最后一件事,非最终变量不能在匿名内部类中引用,因为您不能总是保证它们的值在匿名类代码运行之前不会更改(编译器不会,通常也不会,费尽心思分析匿名类是如何用来做保证的)。因此,它需要
final

只需宣布邀请最终:

final Invite invite = ...;
您可以在匿名类中使用它

可以找到引擎盖下的解释提示

是的,您仍然可以修改
invite
的字段。您无法将invite分配给新对象。但就像我说的,你的方法很古怪,所以你遇到了问题


我正在打电话,或者我会从JLS的相关部分找到最后的资料。您可以在那里查找更多信息。

有两种方法可以解决此问题:

  • invite
    声明为
    final
    ,以便匿名内部类可以访问它

    final Invite invite = (Invite) criteria.uniqueResult();
    ...
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            invite.setExpired(true);
        }
    }, MAX_TIME);
    
  • 将匿名内部类从等式中去掉:

    public class InviteTimeoutTask extends TimerTask {
    
        private final Invite invite;
    
        public InviteTimeoutTask(Invite invite) {
            this.invite = invite;
        }
    
        @Override
        public void run() {
            invite.setExpired(true);
        }
    }
    
    然后像这样使用它:

    final Invite invite = (Invite) criteria.uniqueResult();
    ...
    Timer timer = new Timer();
    timer.schedule(new InviteTimeoutTask(invite), MAX_TIME);
    


  • 您只能在匿名内部类中引用
    final
    变量的原因很简单,因为您处理的是局部变量。如果你在一个领域尝试同样的方法,你不会遇到任何问题。但局部变量的范围仅限于它所属的方法。当调用
    TimerTask
    中的回调方法时,创建
    TimerTask
    的方法已经很长时间了,所有局部变量都消失了。但是,如果将变量声明为
    final
    ,编译器可以在匿名类中安全地使用它

    尝试
    final Invite=(Invite)条件。uniqueResult()?只需升级到Java 8或那样做^。我认为这只是检测到Java程序员可能存在的一些问题/错误想法@