2015-05-27 13 views
5

Tôi cảm thấy thoải mái với các ngôn ngữ và đóng cửa chức năng và đã rất ngạc nhiên bởi lỗi sau: "Cannot refer to the non-final local variable invite defined in an enclosing scope".Cách xử lý đóng cửa Java là gì?

Đây là mã của tôi:

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

Theo tôi được biết, tham khảo invite trong TimeTask dụ được một lỗi vì biến mà không được bảo đảm để tồn tại. Vì vậy, câu hỏi của tôi là, cách Java thể hiện những gì tôi muốn, cụ thể là để tải một lời mời và sau đó thiết lập một bộ đếm thời gian để hết hạn lời mời sau một khoảng thời gian.

+2

thử 'lời mời cuối cùng mời = (Mời) criteria.uniqueResult();'? – Gosu

+3

Chỉ cần nâng cấp lên Java 8 hoặc làm điều đó ^. –

+0

@Gosu, vì vậy nếu 'lời mời' là cuối cùng, tôi vẫn có thể thay đổi các thuộc tính trên nó bằng cách sử dụng' setExpired() '? – gwg

Trả lời

2

Có hai cách để sửa lỗi này:

  1. Declare invite như final để nó trở nên dễ tiếp cận với các lớp bên trong vô danh.

    final Invite invite = (Invite) criteria.uniqueResult(); 
    ... 
    Timer timer = new Timer(); 
    timer.schedule(new TimerTask() { 
        @Override 
        public void run() { 
         invite.setExpired(true); 
        } 
    }, MAX_TIME); 
    
  2. Lấy lớp bên trong vô danh ra khỏi phương trình:

    public class InviteTimeoutTask extends TimerTask { 
    
        private final Invite invite; 
    
        public InviteTimeoutTask(Invite invite) { 
         this.invite = invite; 
        } 
    
        @Override 
        public void run() { 
         invite.setExpired(true); 
        } 
    } 
    

    Và sau đó sử dụng nó như thế này:

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

Lý do bạn chỉ có thể tham khảo các biến số final trong lớp bên trong không thích hợp chỉ đơn giản là bạn đang xử lý một biến cục bộ. Nếu bạn thử điều tương tự với một trường, bạn sẽ không gặp phải bất kỳ vấn đề nào.Nhưng phạm vi của biến cục bộ được giới hạn trong phương thức mà nó thuộc về. Vào thời điểm phương thức gọi lại trong TimerTask được gọi là phương pháp tạo ra các TimerTask là dài hơn và tất cả các biến địa phương đã biến mất. Tuy nhiên nếu bạn khai báo biến là final trình biên dịch có thể sử dụng nó một cách an toàn trong lớp ẩn danh.

+0

Trong khi tôi khắc phục sự cố của mình do thiết kế lại cơ sở dữ liệu - nhờ @ JasonC - câu trả lời hay nhất này là câu hỏi ban đầu mà tôi thấy hữu ích nhất cho người tìm kiếm trong tương lai. – gwg

+0

Hãy thực sự cẩn thận với điều này và Hibernate. Đây là một câu trả lời tuyệt vời nói chung, nhưng trong ví dụ cụ thể với Hibernate, quản lý phiên/giao dịch cũng cần phải được tính đến. Bạn sẽ phải sử dụng chế độ phạm vi phiên hiện tại của Hibernate và quản lý các phiên tương ứng (đặc biệt là các phiên truyền phần mềm giữa các luồng, bạn sẽ phải lấy nó ra khỏi chế độ phiên-đối tượng-mỗi-thread để an toàn làm điều đó). Nếu một 'Mời' được thực hiện qua các biên của phiên, bạn cần phải' merge() 'nó trở lại vào bộ đệm phiên. –

+0

(Bằng cách này, 'final' không tăng phạm vi của một biến hoặc nói với trình biên dịch mà bạn muốn nó dính xung quanh.' Final' chỉ đơn giản là [ngăn cản giá trị của nó khỏi bị thay đổi] (https://docs.oracle .com/javase/specs/jls/se8/html/jls-4.html # jls-4.12.4), biến nó thành một hằng số mà trình biên dịch [có thể xử lý một cách an toàn] (http://www.javaspecialists.eu /archive/Issue025.html) [@gwg Kiểm tra liên kết cuối cùng để biết một số gợi ý]). –

3

Theo như tôi biết, lỗi không phải là nó không được đảm bảo rằng invite does not exists. Lỗi nên đọc:

"cannot refer to a non-final variable inside an inner class defined in a different method" 

Tôi nghĩ rằng lỗi này là do nó sẽ gây ra mọi sự cố khi biến số invite không được đảm bảo.

Nếu thời gian chạy Java vào đoạn mã sau:

new TimerTask() { 
    @Override 
    public void run() { 
     // ERROR: `invite` is not guaranteed to exist when this code runs 
     invite.setExpired(true); 
    } 
} 

Nó sẽ sao chép các giá trị (tham khảo) của invite đến đối tượng mới TimerTask. Nó sẽ không đề cập đến biến đó. Sau khi phương thức được để lại, sau khi tất cả biến không còn tồn tại nữa (nó được tái chế từ ngăn xếp cuộc gọi). Nếu có tham chiếu đến biến, người ta có thể tạo con trỏ treo lơ lửng.

Tôi nghĩ rằng Java muốn biến được final vì đoạn mã sau:

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. 
} 

Người ta có thể muốn thiết lập invite sau trong tiến trình để null. Điều này sẽ có tác động lên đối tượng TimerTask. Để tránh điều đó loại vấn đề, bởi thực thi biến là final, rõ ràng những gì giá trị được truyền cho TimerTask nó không thể được sửa đổi sau đó và một lập trình viên Java chỉ có suy nghĩ rằng các giá trị cho một cuộc gọi phương pháp "luôn luôn tồn tại" đó là dễ dàng hơn nhiều.

+0

Đây là một lời giải thích tốt cho những gì đang xảy ra, nhưng tôi không chắc chắn về giải pháp. Ví dụ: nếu tôi thực hiện 'lời mời'' final', tôi vẫn không xử lý giao dịch đúng cách. Tôi có cần phải tái khởi tạo một giao dịch và mọi thứ bên trong 'run()' không? Trong một ngôn ngữ chức năng, tôi sẽ chỉ chuyển qua một cuộc gọi lại và một số biến và được thực hiện với nó, nhưng giải pháp này yêu cầu thêm 3-4 thụt đầu dòng (đặc biệt là với 'try/catch' xung quanh các giao dịch). – gwg

+1

Bạn cũng có thể sử dụng gọi lại. Xin vui lòng không phải là trình biên dịch Java không lỗi trên ngữ nghĩa của 'lời mời'. Ngay cả khi 'lời mời' là null thì đó không phải là thứ mà trình biên dịch quan tâm. –

2

Bạn dường như có một số vấn đề thiết kế cơ sở dữ liệu và kiến ​​trúc phần mềm cơ bản hơn ở đây.

Thay vì thiết lập một số lĩnh vực "hết hạn" sau một khoảng thời gian nhất định, chỉ cần lưu trữ thời gian hết hạn thực tế. Sau đó, khi người dùng thực hiện một hành động, chỉ cần kiểm tra thời gian hết hạn so với thời gian hiện tại để xem liệu lời mời có hết hạn hay không. Bằng cách đó, nó luôn hoạt động và bạn không phải lên lịch hẹn giờ hoặc quản lý các giao dịch dài hạn hoặc bất kỳ thứ gì như thế. Nó cũng ngầm liên tục trong quá trình khởi động lại chương trình/sự cố (phương pháp dựa trên bộ đếm thời gian hiện tại sẽ yêu cầu nỗ lực ngăn không cho nó hết hạn để chờ lời mời nếu chương trình bị chấm dứt trong khi bộ hẹn giờ đang chạy).

Nếu bạn muốn có thông báo hết hạn đang diễn ra, hãy thêm trường "người dùng đã được thông báo" (ví dụ) vào lời mời. Sau đó, tạo một tác vụ lặp lại đơn (bộ đếm thời gian có thể tốt cho việc này hoặc ScheduledExecutorService) định kỳ lấy danh sách tất cả lời mời không được thông báo đã hết hạn trong một truy vấn tiêu chí duy nhất. Tắt thông báo, đặt cờ được thông báo, rửa sạch, lặp lại. Bạn có thể xếp hàng các thông báo trong một nhóm luồng ExecutorService nếu bạn muốn, nếu thông báo mất nhiều thời gian (ví dụ: gửi email).

Đóng cửa (hoặc xấp xỉ) không phải là công cụ phù hợp cho công việc này.


Nhưng, nếu bạn phải làm điều cờ theo thời gian, thiết lập chế độ ngủ đông sang chế độ phiên đối tượng mỗi chủ đề (trên thực tế, tôi nghĩ rằng thậm chí có thể là chế độ mặc định), sau đó sử dụng một ExecutorService bơi thread (xem Executors) để lên lịch một nhiệm vụ mở giao dịch, truy vấn mời, chờ (không hẹn giờ), sau đó thực hiện và đóng giao dịch. Sau đó, toàn bộ giao dịch của bạn là trên một chuỗi nền và không có vấn đề quản lý giao dịch lạ nào bạn đang gặp phải nữa.

Thậm chí tốt hơn, hãy ngừng thử tất cả điều này trong một giao dịch chạy dài duy nhất (ví dụ: nếu người dùng muốn xóa lời mời trong khi bộ hẹn giờ của bạn đang chạy?). Mở một giao dịch rồi truy vấn lời mời rồi đóng nó lại. Sau đó, đặt bộ hẹn giờ của bạn (hoặc sử dụng ScheduledExecutorService) và để bộ hẹn giờ mở một giao dịch, truy vấn lời mời, hết hạn lời mời, sau đó đóng nó. Có thể bạn không muốn giữ kết nối và/hoặc giao dịch db mở trong toàn bộ khoảng thời gian MAX_TIME, không có lý do gì để làm điều đó.


Đối với điều cuối cùng, biến nonfinal không thể được đề cập đến trong các lớp bên trong vô danh vì bạn có thể không phải lúc nào đảm bảo rằng giá trị của chúng sẽ không thay đổi trước khi mã lớp vô danh đang chạy (trình biên dịch không và thường không thể, trải qua những rắc rối khi phân tích cách lớp ẩn danh được sử dụng để thực hiện bảo đảm đó). Vì vậy, nó yêu cầu final.

Chỉ cần tuyên bố mời chính thức:

final Invite invite = ...; 

Và bạn có thể sử dụng nó trong lớp ẩn danh của bạn.

Có thể tìm thấy gợi ý giải thích dưới mui xe here.

Và có bạn có thể sửa đổi các trường của invite. Bạn không thể chỉ định lời mời cho một đối tượng mới. Nhưng như tôi đã nói, cách tiếp cận của bạn rất sôi nổi và vì vậy bạn đang gặp phải vấn đề.

Tôi đang sử dụng điện thoại của mình hoặc tôi sẽ tìm hiểu phần có liên quan của JLS cho nội dung cuối cùng. Bạn có thể tìm nó ở đó để biết thêm thông tin.

Các vấn đề liên quan