Trong Java, tôi có nên sử dụng dịch vụ cuối cùng của người dùng cho các tham số và địa phương ngay cả khi tôi không phải làm thế không?


105

Java cho phép đánh dấu các biến (trường / cục bộ / tham số) là final, để ngăn việc gán lại vào chúng. Tôi thấy nó rất hữu ích với các trường, vì nó giúp tôi nhanh chóng xem liệu một số thuộc tính - hoặc toàn bộ lớp - có nghĩa là bất biến.

Mặt khác, tôi thấy nó ít hữu ích hơn với các tham số và tham số, và thông thường tôi tránh đánh dấu chúng finalngay cả khi chúng sẽ không bao giờ được gán lại (với ngoại lệ rõ ràng khi chúng cần được sử dụng trong lớp bên trong) . Tuy nhiên, gần đây, tôi đã phát hiện ra mã được sử dụng cuối cùng bất cứ khi nào có thể, mà tôi đoán về mặt kỹ thuật cung cấp thêm thông tin.

Không còn tự tin về phong cách lập trình của mình, tôi tự hỏi những lợi thế và bất lợi khác của việc áp dụng ở finalbất cứ đâu, phong cách công nghiệp phổ biến nhất là gì và tại sao.


Các câu hỏi kiểu mã hóa @Amir dường như thuộc về nơi này tốt hơn so với trên SO và tôi không thể tìm thấy bất kỳ chính sách nào trong Câu hỏi thường gặp hoặc trên meta của trang web này về vấn đề này. Bạn có thể vui lòng chỉ dẫn cho tôi?
Sồi

1
@Oak Nó nói: "Vấn đề lập trình cụ thể, thuật toán phần mềm, mã hóa , hãy hỏi về Stack Overflow"
Amir Rezaei

7
@Amir Tôi không đồng ý, tôi không thấy đây là vấn đề mã hóa. Trong mọi trường hợp, cuộc tranh luận này không thuộc về nơi này, vì vậy tôi đã mở một chủ đề meta về vấn đề này .
Sồi

3
@amir đây hoàn toàn là chủ đề cho trang web này, vì đây là một câu hỏi chủ quan về lập trình - vui lòng xem / faq
Jeff Atwood

1
Re, biến cục bộ: Trình biên dịch Java cũ hơn đã chú ý đến việc bạn đã khai báo cục bộ finalhay chưa và được tối ưu hóa cho phù hợp. Trình biên dịch hiện đại đủ thông minh để tìm ra nó cho chính họ. Ít nhất là về các biến cục bộ, finalhoàn toàn vì lợi ích của độc giả con người. Nếu thói quen của bạn không quá phức tạp, thì hầu hết độc giả của con người cũng có thể tự mình tìm ra.
Solomon chậm

Câu trả lời:


67

Tôi sử dụng finalcùng một cách như bạn. Đối với tôi, nó có vẻ thừa đối với các biến cục bộ và các tham số phương thức và nó không truyền đạt thông tin bổ sung hữu ích.

Một điều quan trọng là cố gắng giữ cho các phương pháp của tôi ngắn gọn và sạch sẽ , mỗi phương pháp thực hiện một nhiệm vụ duy nhất. Do đó, các biến và tham số cục bộ của tôi có phạm vi rất hạn chế và chỉ được sử dụng cho một mục đích duy nhất. Điều này giảm thiểu cơ hội gán lại chúng vô tình.

Hơn nữa, như bạn chắc chắn biết, finalkhông đảm bảo rằng bạn không thể thay đổi giá trị / trạng thái của biến (không chính xác). Chỉ có điều bạn không thể chỉ định lại tham chiếu đến đối tượng đó sau khi khởi tạo. Nói cách khác, nó hoạt động trơn tru chỉ với các biến của các kiểu nguyên thủy hoặc bất biến. Xem xét

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

Điều này có nghĩa là trong nhiều trường hợp, nó vẫn không giúp dễ hiểu hơn những gì xảy ra bên trong mã và việc hiểu sai điều này thực tế có thể gây ra các lỗi tinh vi khó phát hiện.


1
Về chỉnh sửa - Tôi biết về ngữ nghĩa của final, cảm ơn bạn :) nhưng điểm hay về các phương pháp ngắn & sạch - Tôi đoán rằng nếu phương thức đó đủ ngắn thì rõ ràng một biến không được gán lại, có thậm chí ít động cơ để xem xét các finaltừ khóa.
Sồi

Sẽ không tuyệt sao nếu chúng ta có thể có các tham số cuối cùng & các biến cục bộ, và vẫn là một cú pháp ngắn và rõ ràng? lập trình
viên.stackexchange.com/questions/199783 / từ

7
"cuối cùng không đảm bảo rằng bạn không thể thay đổi giá trị / trạng thái của biến (không chính xác). Chỉ có điều bạn không thể chỉ định lại tham chiếu đến đối tượng đó sau khi khởi tạo." Nonprimitive = tham chiếu (các loại duy nhất trong Java là các kiểu nguyên thủy và các kiểu tham chiếu). Giá trị của một biến kiểu tham chiếu tham chiếu. Do đó, bạn không thể chỉ định lại tham chiếu = bạn không thể thay đổi giá trị.
user102008

2
+1 cho cạm bẫy tham chiếu / trạng thái. Lưu ý rằng mặc dù việc đánh dấu các biến cuối cùng có thể là cần thiết để tạo các bao đóng trong các khía cạnh chức năng mới của Java8
Newtopian

So sánh của bạn bị sai lệch như @ user102008 đã chỉ ra. Việc gán biến không giống như cập nhật giá trị của nó
ericn

64

Phong cách và suy nghĩ lập trình Java của bạn vẫn ổn - không cần phải nghi ngờ chính mình ở đó.

Mặt khác, tôi thấy nó ít hữu ích hơn với các tham số và tham số, và thông thường tôi tránh đánh dấu chúng là cuối cùng ngay cả khi chúng sẽ không bao giờ được gán lại (với ngoại lệ rõ ràng khi chúng cần được sử dụng trong lớp bên trong ).

Đây chính xác là lý do tại sao bạn nên sử dụng finaltừ khóa. Bạn nói rằng BẠN sẽ không bao giờ được chỉ định lại, nhưng không ai biết điều đó. Sử dụng finalngay lập tức định hướng mã của bạn thêm một chút nữa.


8
Nếu phương pháp của bạn rõ ràng và chỉ làm một điều người đọc cũng sẽ biết. Từ cuối cùng chỉ làm cho mã không thể đọc được. Và nếu không thể đọc được thì nó mơ hồ hơn nhiều
eddieferetro 3/03/2016

4
@eddieferetro Không đồng ý. Các từ khóa finalnêu ý định, làm cho mã dễ đọc hơn. Ngoài ra, một khi bạn phải xử lý mã trong thế giới thực, bạn sẽ nhận thấy nó hiếm khi nguyên sơ và rõ ràng, và tự do thêm finals ở mọi nơi bạn có thể giúp bạn phát hiện ra lỗi và hiểu mã kế thừa tốt hơn.
Andres F.

4
Là "ý định" mà biến sẽ không bao giờ thay đổi và mã của bạn có dựa vào thực tế đó không? Hoặc là ý định "thực tế là biến này không bao giờ thay đổi nên tôi đánh dấu nó là cuối cùng". Càng về sau là tiếng ồn vô dụng và có thể có hại. Và vì bạn dường như ủng hộ việc đánh dấu tất cả người dân địa phương, bạn sẽ làm sau. Lớn -1.
user949300

2
finalý định, thường là một điều tốt trong một đoạn mã, nhưng nó có giá của việc thêm lộn xộn thị giác. Giống như hầu hết mọi thứ trong lập trình, có một sự đánh đổi ở đây: thể hiện thực tế là biến của bạn sẽ chỉ được sử dụng một lần có giá trị bằng lời nói thêm?
christopheml

30

Một lợi thế của việc sử dụng final/ constbất cứ nơi nào có thể là nó làm giảm tải tinh thần cho người đọc mã của bạn.

Anh ta có thể yên tâm, rằng giá trị / tham chiếu không bao giờ bị thay đổi sau này. Vì vậy, anh ta không cần phải chú ý đến sửa đổi để hiểu tính toán.

Tôi đã thay đổi suy nghĩ về điều này sau khi học các ngôn ngữ lập trình chức năng thuần túy. Boy, thật là nhẹ nhõm, nếu bạn có thể tin tưởng vào một "biến số" để luôn giữ giá trị ban đầu của nó.


16
Chà, trong Java finalkhông đảm bảo rằng bạn không thể thay đổi giá trị / trạng thái của biến (không chính xác). Chỉ có điều bạn không thể chỉ định lại tham chiếu đến đối tượng đó sau khi khởi tạo.
Péter Török

9
Tôi biết, đó là lý do tại sao tôi phân biệt giữa giá trị và tham chiếu. Khái niệm này là hữu ích nhất trong bối cảnh cấu trúc dữ liệu bất biến và / hoặc chức năng thuần túy.
LennyProgrammer

7
@ PéterTörök Nó ngay lập tức hữu ích với các kiểu nguyên thủy và phần nào hữu ích với các tham chiếu đến các đối tượng có thể thay đổi (ít nhất là bạn biết bạn luôn luôn xử lý cùng một đối tượng!). Nó vô cùng hữu ích khi xử lý mã được thiết kế để trở nên bất biến từ đầu.
Andres F.

18

Tôi xem xét finaltrong các tham số phương thức và các biến cục bộ là nhiễu mã. Các khai báo phương thức Java có thể khá dài (đặc biệt là với các tổng quát) - không cần phải thực hiện chúng nữa.

Nếu các bài kiểm tra đơn vị được viết đúng, việc gán cho các tham số "có hại" sẽ được chọn, do đó nó không bao giờ thực sự là một vấn đề. Sự rõ ràng trực quan quan trọng hơn việc tránh một lỗi có thể không xảy ra do các bài kiểm tra đơn vị của bạn không đủ độ bao phủ.

Các công cụ như FindBugs và CheckStyle có thể được cấu hình để phá vỡ bản dựng nếu việc gán được thực hiện cho các tham số hoặc biến cục bộ, nếu bạn quan tâm sâu sắc đến những điều đó.

Tất nhiên, nếu bạn cần làm cho chúng cuối cùng, ví dụ vì bạn đang sử dụng giá trị trong một lớp ẩn danh, thì không vấn đề gì - đó là giải pháp sạch đơn giản nhất.

Ngoài hiệu quả rõ ràng của việc thêm các từ khóa bổ sung vào các tham số của bạn và do đó IMHO ngụy trang chúng, việc thêm các tham số cuối cùng vào phương thức thường có thể làm cho mã trong thân phương thức trở nên khó đọc hơn, làm cho mã kém hơn - trở thành "tốt", mã phải dễ đọc và đơn giản nhất có thể. Đối với một ví dụ giả định, giả sử tôi có một phương pháp cần xử lý tình huống không nhạy cảm.

Không có final:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

Đơn giản. Dọn dẹp. Mọi người đều biết chuyện gì đang xảy ra.

Bây giờ với 'trận chung kết', tùy chọn 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

Trong khi thực hiện các tham số finalngăn bộ mã hóa thêm mã xuống khỏi suy nghĩ rằng anh ta đang làm việc với giá trị ban đầu, có một rủi ro tương đương mà mã tiếp tục có thể sử dụng inputthay vì lowercaseInput, điều đó không nên và không thể bảo vệ, bởi vì bạn có thể ' t mang nó ra khỏi phạm vi (hoặc thậm chí gán nullcho inputnếu đó thậm chí sẽ giúp anyway).

Với 'trận chung kết', tùy chọn 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

Bây giờ chúng tôi vừa tạo ra nhiều tiếng ồn mã hơn và giới thiệu một cú đánh hiệu suất là phải gọi toLowerCase()n lần.

Với 'trận chung kết', tùy chọn 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

Nói về tiếng ồn mã. Đây là một con tàu đắm. Chúng tôi đã có một phương thức mới, một khối ngoại lệ bắt buộc, bởi vì mã khác có thể gọi nó không chính xác. Nhiều bài kiểm tra đơn vị để bao gồm ngoại lệ. Tất cả để tránh một dòng đơn giản, và IMHO thích hợp và vô hại.

Ngoài ra còn có một vấn đề là các phương thức không nên dài đến mức bạn không thể dễ dàng nhìn thấy nó và biết trong nháy mắt rằng việc gán tham số đã diễn ra.

Tôi nghĩ rằng đó là một cách thực hành / phong cách tốt mà nếu bạn gán cho một tham số, bạn thực hiện nó sớm trong phương thức, tốt nhất là dòng đầu tiên hoặc ngay sau khi kiểm tra đầu vào cơ bản, thay thế nó cho toàn bộ phương thức, có hiệu quả nhất quán trong phương pháp. Người đọc biết để mong đợi bất kỳ sự chuyển nhượng nào là rõ ràng (gần tuyên bố chữ ký) và ở một nơi nhất quán, điều này làm giảm đáng kể vấn đề mà việc thêm vào cuối cùng đang cố gắng tránh. Trên thực tế tôi hiếm khi gán cho các tham số, nhưng nếu tôi làm thì tôi luôn làm nó ở đầu một phương thức.


Cũng lưu ý rằng finalkhông thực sự bảo vệ bạn như lúc đầu có vẻ như:

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final không hoàn toàn bảo vệ bạn trừ khi loại tham số là nguyên thủy hoặc bất biến.


Trong trường hợp cuối cùng, finalsẽ cung cấp đảm bảo một phần rằng bạn đang xử lý cùng một Datetrường hợp. Một số đảm bảo tốt hơn không có gì (nếu có bất cứ điều gì, bạn đang tranh luận về các lớp bất biến từ đầu!). Trong mọi trường hợp, trong thực tế phần lớn những gì bạn nói sẽ ảnh hưởng đến các ngôn ngữ mặc định không thay đổi hiện có nhưng điều đó không có nghĩa là nó không phải là vấn đề.
Andres F.

+1 để chỉ ra rủi ro "mã đó tiếp tục xuống có thể sử dụng đầu vào". Tuy nhiên, tôi thích các tham số của mình hơn final, với khả năng biến chúng thành không phải là cuối cùng cho các trường hợp như trên. Nó chỉ không đáng để spam chữ ký với một từ khóa.
maaartinus

7

Tôi để nhật thực đặt finaltrước mỗi biến cục bộ, vì tôi xem xét nó để làm cho chương trình dễ đọc hơn. Tôi không làm cho nó với các tham số, vì tôi muốn giữ danh sách tham số càng ngắn càng tốt, lý tưởng là nó phải vừa trong một dòng.


2
Ngoài ra, đối với các tham số, bạn có thể yêu cầu trình biên dịch đưa ra cảnh báo hoặc lỗi nếu tham số được gán.
oberlies

3
Hoàn toàn ngược lại, tôi thấy khó đọc hơn vì nó chỉ làm lộn xộn mã.
Steve Kuo

3
@Steve Kuo: Nó chỉ cho phép tôi nhanh chóng tìm thấy tất cả các biến. không có lợi ích lớn, nhưng cùng với việc ngăn chặn các bài tập tình cờ, nó đáng giá 6 ký tự. Tôi sẽ hạnh phúc hơn nhiều nếu có một cái gì đó giống như varđánh dấu các biến không phải là cuối cùng. YMMV.
maaartinus

1
@maaartinus Đồng ý về var! Thật không may, nó không phải là mặc định trong Java và bây giờ đã quá muộn để thay đổi ngôn ngữ. Do đó, tôi sẵn sàng chấp nhận sự bất tiện nhỏ của việc viết final:)
Andres F.

1
@maaartinus Đồng ý. Tôi thấy rằng khoảng 80-90% các biến (cục bộ) của tôi không cần phải sửa đổi sau khi khởi tạo. Do đó, 80-90% trong số họ có finaltừ khóa được chuẩn bị trước, trong khi chỉ có 10-20% sẽ cần var...
JimmyB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.