2015-05-27 4 views
5

Мне удобно с функциональными языками и замыканиями, и была поражена следующей ошибкой: "Cannot refer to the non-final local variable invite defined in an enclosing scope".Что такое Java-способ обработки закрытий?

Вот мой код:

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 в экземпляре TimeTask это ошибка, потому что переменная не гарантируется существование. Поэтому мой вопрос: какой способ Java выразить то, что я хочу, а именно загрузить приглашение, а затем установить таймер, чтобы истечь приглашение через некоторое время.

+2

try 'final Пригласить пригласить = (Пригласить) критерии.uniqueResult();'? – Gosu

+3

Просто перейдите на Java 8 или сделайте это ^. –

+0

@Gosu, поэтому, если 'приглашение' является окончательным, я все равно могу изменить его свойства с помощью' setExpired() '? – gwg

ответ

2

Есть два способа исправить это:

  1. DECLARE 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); 
    
  2. Возьмите анонимный внутренний класс из уравнения:

    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 переменные в onymous внутренний класс просто состоит в том, что вы имеете дело с локальной переменной. Если вы попробуете то же самое с полем, вы не столкнетесь с какой-либо проблемой.Но область локальной переменной ограничена тем свойством, которым она принадлежит. К моменту, когда метод обратного вызова в TimerTask называется методом, который создал TimerTask, длинный и все локальные переменные исчезли. Однако, если вы объявите переменную как final, компилятор может безопасно использовать ее в анонимном классе.

+0

Хотя я исправил свою проблему из-за редизайна базы данных - благодаря @ JasonC - это лучше всего отвечает на исходный вопрос, который, по моему мнению, наиболее полезен для будущих поисковиков. – gwg

+0

Будьте очень осторожны с этим и спящим. Это отличный ответ в целом, но в конкретном примере с Hibernate также необходимо учитывать управление сеансом/транзакциями. Вам нужно будет принять текущий режим сеанса Hibernate в учетную запись и управлять сеансами соответственно (особенно, избегая передачи сеансов между потоками, вам нужно будет отключить его из режима session-object-per-thread, чтобы это было безопасно). Если «Приглашение» переносится через границы сеанса, вам нужно «слить()» обратно в кеш сеанса. –

+0

(Кстати, 'final' не увеличивает область действия переменной или сообщает компилятору, что вы хотите, чтобы он придерживался.' Final' просто [предотвращает изменение его значения] (https://docs.oracle .com/javase/specs/jls/se8/html/jls-4.html # jls-4.12.4), превращая его в константу, с которой компилятор [может смело справиться] (http://www.javaspecialists.eu /archive/Issue025.html) [@gwg Проверьте, что последняя ссылка для некоторых подсказок]). –

3

Насколько я знаю, ошибка не в том, что не гарантируется, что invite does not exists. Ошибка следует читать:

"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); 
    } 
} 

Это скопирует значение (ссылка) от invite к новому TimerTask объекта. Он не будет относиться к самой переменной. После того, как метод оставлен, после того, как переменная больше не существует (она перерабатывается из стека вызовов). Если бы кто-то ссылался на переменную, можно было бы создать указатель dangling.

Я думаю, что Java хочет переменной быть final из следующего кода:

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 нужно только думать, что значения для вызова метода «всегда существуют», что намного проще.

+0

Это хорошее объяснение того, что происходит, но я не уверен в решении. Например, если я делаю 'приглашение'' final', я до сих пор не обрабатываю транзакцию должным образом. Нужно ли мне повторно создавать транзакцию и все внутри 'run()'? В функциональном языке я бы просто передал обратный вызов и некоторые переменные и поступил с ним, но для этого решения требуется еще 3-4 дополнительных (например, с помощью try/catch) транзакций. – gwg

+1

Вы также можете использовать обратный вызов. Пожалуйста, не то, чтобы компилятор Java не ошибся в семантике 'invite'. Даже если 'приглашение' является нулевым, это не то, о чем компилятор заботится. –

2

У вас, похоже, есть еще несколько фундаментальных проблем с архитектурой базы данных и программной архитектуры.

Вместо того, чтобы устанавливать какое-то поле «с истекшим сроком» через определенное время, просто сохраните фактическое время истечения. Затем, когда пользователь выполняет действие, просто проверьте время истечения срока действия на текущее время, чтобы узнать, истекло ли приглашение. Таким образом, он всегда работает, и вам не нужно планировать таймеры или управлять долговременными транзакциями или что-то в этом роде. Он также неявно устойчив к перезагрузке/сбоям в работе программы (для текущего подхода, основанного на тайме, потребуются усилия, чтобы он не забывал истечь ожидающих приглашений, если программа завершается во время работы таймера).

Если вы хотите получать уведомления об истечении срока действия, добавьте к приглашению поле «пользователь было уведомлено» (например). Затем создайте одну повторяющуюся фоновую задачу (для этого может быть полезен таймер или ScheduledExecutorService), который периодически захватывает список всех истекших неопубликованных приглашений в одном запросе критерия. Выключите уведомления, установите оповещенные флаги, промойте, повторите. Вы можете отправлять уведомления в очереди пула потоков ExecutorService, если вы хотите, если уведомления требуют много времени (например, отправка сообщений электронной почты).

Закрытие (или их приближение) - не совсем правильный инструмент для этой работы.


Но, если вы должны делать в заданное время вещи флага, установить спящий режим для сеанса объект-в-нити режима (на самом деле, я думаю, что может быть даже режим по умолчанию), а затем использовать пул потоков ExecutorService (см. «Исполнители»), чтобы запланировать задачу, открывающую транзакцию, приглашение к запросам, ожидание (без таймера), затем делает это и закрывает транзакцию. Затем вся ваша транзакция находится в одном фоновом потоке, и ни одна из странных проблем управления транзакциями, в которой вы работаете, больше не существует.

Даже лучше не останавливаться на этом в одной длительной транзакции (например, что, если пользователь хочет удалить приглашение во время работы вашего таймера?). Откройте транзакцию, затем запросите приглашение, затем закройте его. Затем установите свой таймер (или используйте ScheduledExecutorService) и таймер откройте транзакцию, запросите приглашение, истечет приглашение и закройте его. Вероятно, вы не хотите, чтобы соединение db и/или транзакция были открыты для всего интервала MAX_TIME, нет причин для этого.


Что касается окончательной вещи, nonfinal переменного не может быть передан в анонимных внутренних классах, потому что вы не всегда можете гарантировать, что их значения не изменятся до кода анонимного класса запуска (компилятор не , и, как правило, не могут пройти проверку на то, как анонимный класс используется для обеспечения этой гарантии). Поэтому требуется final.

Просто объявить окончательный пригласить:

final Invite invite = ...; 

И вы можете использовать его в анонимном классе.

Подсказка под капотом объяснения может быть найдена here.

И да, вы можете изменить поля invite. Вы просто не можете назначить приглашение новому объекту. Но, как я уже сказал, ваш подход напуган, и поэтому вы сталкиваетесь с проблемами.

Я нахожусь на своем телефоне или я выкопаю соответствующую часть JLS для финального материала. Вы можете посмотреть его там для получения дополнительной информации.

Смежные вопросы