Tại sao Java có các phương thức `void`?


51

Có / tại sao Java cần phải có voidcác phương thức? Tham khảo :

Bất kỳ phương thức nào được khai báo void đều không trả về giá trị.

Theo như tôi có thể nghĩ, mọi việc sử dụng voidsẽ được phục vụ tốt hơn bằng cách trả về một cờ trạng thái, đối tượng được gọi, hoặc null.

Điều này sẽ làm cho mọi cuộc gọi trở thành một câu lệnh có thể gán được, và sẽ tạo điều kiện cho các kiểu xây dựng và chuỗi phương thức. Các phương thức chỉ được gọi cho các hiệu ứng của chúng thường sẽ trả về kiểu boolean hoặc kiểu chung Successhoặc đưa ra một ngoại lệ khi thất bại.


Vì vậy, chúng ta không cần một cú pháp đặc biệt để viết chương trình con. Chúng ta có thể sử dụng cùng một chức năng sử dụng.
candied_orange

Câu trả lời:


158

Bởi vì có một sự khác biệt giữa "Chức năng này có thể thành công hoặc thất bại và đủ tự nhận thức rằng nó có thể cho biết sự khác biệt" và "Không có phản hồi về tác dụng của chức năng này". Nếu không void, bạn sẽ kiểm tra vô tận các mã thành công và tin rằng bạn đang viết phần mềm mạnh mẽ, trong khi thực tế bạn không làm gì cả.


10
Nhưng dù thành công hay thất bại là điều cần thể hiện bằng cách ném hoặc không ném ngoại lệ, không trả lại cờ. Và trả về voidkhông nói bất cứ điều gì về việc phương thức hoặc chức năng có đủ tự nhận thức để ném cho phù hợp hay không.
Volker Siegel

12
@VolkerSiegel: Bối cảnh của câu trả lời là câu hỏi. OP đề nghị rằng trong mọi trường hợp sử dụng void, hàm sẽ thay vào đó trả về một cờ trạng thái biểu thị thành công của sự thất bại. Trong bối cảnh đó, khoảng trống trả về DOES thực sự nói rằng hàm theo nghĩa đen không có gì để trả về. Việc buộc một hàm như vậy luôn luôn trả về 0 để biểu thị thành công sẽ cung cấp cho người dùng hàm tin rằng họ đang kiểm tra lỗi khi trong thực tế, giá trị trả về luôn là 0 bất kể thành công hay thất bại.
slebetman

19
@VolkerSiegel Có một số lượng lớn các tình huống trong đó các trường hợp ngoại lệ không phải là cách chính xác để làm việc. Trong thực tế, tôi muốn nói rằng hiếm có trường hợp ngoại lệ nào đúng hơn sai - ngoại lệ có nghĩa là điều gì đó khủng khiếp đã xảy ra cần phải được tính đến. Một trường hợp thất bại dự kiến ​​không bao giờ nên sử dụng một ngoại lệ.
Gabe Sechan

35
@GabeSechan Không, nên ném ngoại lệ nếu phương thức không thể thực hiện được hợp đồng của nó. Nếu phương thức này yêu cầu một tham chiếu không hoàn chỉnh nhưng nó có một tham chiếu, đó là một thất bại dự kiến ​​và nó sẽ ném.
Andy

5
Có nhiều cú pháp thay thế để khắc phục điều đó. Lý do họ chọn giải pháp (vụng về) này là vì C sử dụng nó.
Marco van de Voort

95
  1. Bởi vì C có một voidkiểu và Java được thiết kế để tuân theo nhiều quy ước của họ ngôn ngữ C.
  2. Có nhiều hàm mà bạn không muốn trả về giá trị. Bạn sẽ làm gì với "một loại chung Success" nào? Trong thực tế, các giá trị trả về để biểu thị thành công thậm chí ít quan trọng hơn trong Java so với C, bởi vì Java có các ngoại lệ để chỉ ra thất bại và C thì không.

8
> "Bạn sẽ làm gì với" một loại Thành công chung "nào?" - khi viết mã chung, một loại giá trị duy nhất (được gọi là loại Đơn vị btw) rất hữu ích vì nó loại bỏ trường hợp đặc biệt của các hàm "chỉ trả về" nhưng không trả về bất kỳ thứ gì cụ thể. Nhưng khi Java lần đầu tiên được tạo, nó thậm chí còn có hệ thống kiểu ít biểu cảm hơn (không có tham số kiểu), do đó không có nhiều khác biệt thực tế. Và bây giờ đã quá muộn để thay đổi. Các ngôn ngữ hiện đại hơn thực sự tránh "loại" (thực ra nó thậm chí không phải là một loại, chỉ là một điểm đánh dấu đặc biệt cho các phương thức) và sử dụng loại Đơn vị thay thế.
Sange Borsch

9
@SargeBorsch Kiểu của Java Voidhoạt động như một loại Đơn vị (ví dụ như Generics), tuy nhiên, thật đáng buồn, nó khác với void(chú thích bên cạnh: một chú mèo con đáng yêu chết mỗi khi bạn phát minh ra một loại mới để sửa loại bạn đã nhầm trong phiên bản trước). Nếu bất cứ ai không quen thuộc với các loại Đơn vị, đây là một phần giới thiệu khá: en.wikipedia.org/wiki/Unit_type
Ogre Psalm33

1
@ OgrePsalm33 nó không thực sự hoạt động như một loại Đơn vị (ít nhất là trong các phương tiện thực tế - người ta vẫn phải viết "return null;" để trả về từ phương thức có kiểu trả về Void và trình biên dịch AFAIK phân bổ không gian trên ngăn xếp cho các tham chiếu Void , không lợi dụng thực tế là việc đọc các giá trị này là vô nghĩa), đó chỉ là một quy ước để sử dụng nó trong đó loại Đơn vị "phù hợp" sẽ phù hợp hơn.
Sange Borsch

3
@immibis vì Loại đơn vị theo định nghĩa phải có chính xác một giá trị và Void không có giá trị. Có, có thể sử dụng null(thực tế đây là lựa chọn duy nhất), nhưng null là một điều đặc biệt, mọi giá trị của kiểu tham chiếu trong Java có thể là null (đây là một sai lầm khác trong thiết kế ngôn ngữ). Và, như tôi đã nói trước đó, nếu bạn đang sử dụng Voidkiểu trả về thay vì voidđánh dấu, trình biên dịch Java không phải là bạn của bạn.
Sange Borsch

5
@SargeBorsch Void có chính xác một giá trị null. Thực tế nulllà một thành viên của hầu hết các loại là không liên quan đến việc Voidlà một loại đơn vị.
dùng253751

65

Các gốc lý do tại sao ngôn ngữ có một voidloại là bởi vì như trong C, những người tạo ra ngôn ngữ không muốn không cần thiết làm phức tạp các cú pháp của ngôn ngữ với quy trình s và chức năng là cách Pascal đã làm.

Đó là lý do ban đầu.

Lời đề nghị của bạn:

trả lại một cờ trạng thái

Đó là điều không nên. Chúng tôi không sử dụng cờ trạng thái. Nếu có gì sai, chúng tôi báo cáo thông qua các trường hợp ngoại lệ.

đối tượng được viện dẫn

Phong cách thông thạo của các yêu cầu là một sự phát triển gần đây. Nhiều lập trình viên rất vui vẻ sử dụng thành thạo ngày hôm nay và đảo mắt nếu họ phải sử dụng một giao diện không hỗ trợ nó thậm chí không được sinh ra khi Java được tạo.

trả về null

Điều đó sẽ buộc bạn phải khai báo một phương thức là trả về một cái gì đó, trong khi thực tế nó không trả về bất cứ thứ gì, vì vậy nó sẽ rất khó hiểu với một người đang nhìn vào một giao diện đang cố gắng tìm ra nó làm gì. Mọi người chắc chắn sẽ phát minh ra một số lớp vô dụng khác có nghĩa là "không có giá trị trả về" để tất cả các hàm không có gì để trả về có thể trả về một tham chiếu null cho một lớp như vậy. Đó sẽ là vụng về. May mắn thay, có một giải pháp cho điều này, nó được gọi là void. Và đó là câu trả lời cho câu hỏi của bạn.


3
Vui lòng cung cấp một nguồn cho "Lý do ban đầu ...". Theo hiểu biết của tôi, lý do thực sự là vì C được thiết kế để lắp ráp di động.
Thorbjørn Ravn Andersen

6
@ ThorbjørnRavnAndersen có thực sự là bạn yêu cầu tôi cung cấp một nguồn cho tuyên bố rằng cú pháp của java có nguồn gốc từ cú pháp C không?
Mike Nakis

3
@ ThorbjørnRavnAndersen oh, tôi hiểu rồi, tôi hiểu lầm rồi. Vì vậy, bạn muốn có một nguồn cho yêu cầu của tôi về C, không phải về Java. Được rồi, tôi xin lỗi, tôi không có nguồn cho điều đó. Nhưng tôi nghĩ nó khá rõ ràng. (Tôi đã từng lập trình trong Pascal năm 1987, khi tôi chuyển sang C. Chúa ơi, đó là 30 năm trước.)
Mike Nakis

6
Nó không rõ ràng. C được phát triển gần như cùng lúc với pascal bởi những người mà hầu như không biết nó tồn tại. Xin đừng nêu các giả định là sự thật.
Thorbjørn Ravn Andersen

12
Các thực lý do ban đầu là theo mặc định chức năng C trở int, do đó, một từ khóa được thêm vào sau đó để K & R để chỉ ra "không kiểu trả về.
user207421

20

Một số phương pháp, như System.out.printlnkhông trả lại bất cứ điều gì hữu ích, nhưng được gọi hoàn toàn cho tác dụng phụ này. voidlà một chỉ báo hữu ích cho trình biên dịch và cho người đọc mã mà không có giá trị hữu ích nào được trả về.

Trở về nullthay vì voidcó nghĩa là bạn sẽ chỉ nhận được một NullPointerExceptionkhoảnh khắc bạn sử dụng giá trị này cho bất cứ điều gì. Vì vậy, bạn giao dịch một lỗi thời gian biên dịch cho một lỗi thời gian chạy là cách tồi tệ hơn. Hơn nữa, bạn sẽ phải xác định loại trả về là Object, sẽ gây hiểu lầm và khó hiểu. (Và xích vẫn không hoạt động.)

Mã trạng thái thường được sử dụng để chỉ ra các điều kiện lỗi, nhưng Java có ngoại lệ cho mục đích này.

Trả về thislà không thể từ các phương thức tĩnh.

Trả lại một Successđối tượng chung sẽ không có mục đích hữu ích.


1
Hoàn toàn đồng ý với bạn, câu trả lời đơn giản và ngắn gọn nhất.
webo80

11

Một lý do là nó có thể gây hiểu lầm khi trả lại bất cứ thứ gì khác hơn là, nói , null. Thí dụ:

Nên Arrays.sort(a)trả lại cái gì?

Nếu bạn cho rằng anên trả lại để cuộc gọi có thể bị xiềng xích (dường như là câu trả lời của bạn đánh giá từ câu hỏi của bạn), thì không còn rõ liệu giá trị trả về là bản sao của đối tượng ban đầu hay chính đối tượng ban đầu . Cả hai đều có thể. Và vâng, bạn có thể đưa nó vào tài liệu, nhưng điều mơ hồ là bạn không nên tạo ra sự mơ hồ ngay từ đầu.

Mặt khác, nếu bạn cho rằng nullnên trả lại, điều đó đặt ra câu hỏi về giá trị trả về nào có thể cung cấp cho người gọi và tại sao lập trình viên nên buộc phải viết return nullkhi không có thông tin.

Và nếu bạn trả lại một cái gì đó hoàn toàn vô lý (như độ dài a) thì điều đó chỉ khiến việc sử dụng giá trị trả về đó thực sự khó hiểu - chỉ cần nghĩ rằng nó khó hiểu hơn bao nhiêu để nói int len = Arrays.sort(a)thay vì int len = A.length!


1
Nói một cách công bằng, lập trình viên không nhất thiết phải viết return null;- JVM cũng có thể yêu cầu rằng nếu điều khiển rơi khỏi phần cuối của hàm bằng cách khác với câu lệnh returnhoặc throwcâu lệnh rõ ràng , nullsẽ hoàn toàn trả về cho người gọi. IIRC C thực hiện điều đó, ngoại trừ giá trị trả về không được xác định trong trường hợp đó (nó sẽ là bất kỳ giá trị nào xảy ra ở vị trí được sử dụng cho giá trị trả về tại thời điểm đó).
một CVn

2
Câu trả lời đúng cho câu hỏi in đậm của bạn là "một mảng được sắp xếp".
Bryan Boettcher

2
@BryanBoettcher: Bạn đã không đọc phần còn lại của nó?
Mehrdad

1
@Michael Kjorling: đó là một thuộc tính cơ bản của ngôn ngữ Java để ngăn chặn các lỗi như vậy, tức là không có hành vi không xác định được nếu bạn quên một returncâu lệnh. Thay vào đó, bạn nhận được một lỗi biên dịch cho bạn biết về lỗi của bạn. Chèn một ẩn return null;sẽ tốt hơn so với hành vi không xác định được, nhưng không nhiều. Điều đó chỉ hữu ích khi kiểu trả về không có nghĩa, nhưng trình biên dịch rút ra kết luận từ đâu, rằng giá trị trả về không có nghĩa, khi nào thì không void?
Holger

2
@ MichaelKjorling: Mười Nếu returnthực sự không thêm bất kỳ giá trị nào, thì, như đã nói, không có chỉ báo nào cho trình biên dịch rằng trả về sẽ không thêm bất kỳ giá trị nào, nếu không có voidloại nào . Thực tế thì ngược lại, giả định đầu tiên là một phương thức khai báo kiểu trả về muốn returnmột cái gì đó hữu ích của kiểu đó. Và chúng tôi chưa nói về các kiểu nguyên thủy, không có nullgiá trị, do đó, bất kỳ giá trị mặc định nào được trình biên dịch đưa vào sẽ mâu thuẫn với phạm vi các giá trị trả về có thể hữu ích.
Holger

7

Trả lại một giá trị booleanluôn bằng với truenhững gì bạn đề xuất, không chỉ là vô nghĩa (giá trị trả về không mang thông tin), mà thực sự sẽ gây hiểu nhầm. Hầu hết thời gian, tốt nhất là với các loại trở lại sử dụng mà chỉ thực hiện càng nhiều thông tin thực sự là có sẵn - đó là lý do tại sao trong Java bạn có booleancho true/ falsechứ không phải trả lại một intvới 4 tỷ giá trị có thể. Tương tự, trả về một booleanvới hai giá trị có thể có trong đó chỉ có một giá trị có thể ("thành công chung"), sẽ gây hiểu nhầm.

Nó cũng sẽ thêm chi phí hiệu suất không cần thiết.

Nếu một phương thức chỉ được sử dụng cho tác dụng phụ của nó, thì không có gì để trả về và kiểu trả về voidphản ánh chính xác điều đó. Các lỗi hiếm gặp tiềm năng có thể được báo hiệu thông qua các ngoại lệ.

Một điều có thể được cải thiện, sẽ là tạo ra voidmột loại thực tế, chẳng hạn như Scala Unit. Điều này sẽ giải quyết một số vấn đề như xử lý thuốc generic.


Sự thật thú vị: List.addluôn luôn quay trở lại true, nhưng đó là để tương thích với hợp đồng rộng hơn Collection.add, vì vậy Set.addcó thể trả lại truehoặc false.
Holger

4

Java là một ngôn ngữ tương đối cũ. Và vào năm 1995 (Khi nó được tạo ra) và ngay sau đó các lập trình viên đã rất quan tâm đến lượng thời gian mà một quá trình kiểm soát bộ xử lý cũng như việc tiêu thụ bộ nhớ. Trả về khoảng trống sẽ loại bỏ một vài chu kỳ xung nhịp và một chút tiêu thụ bộ nhớ khỏi lệnh gọi hàm vì bạn sẽ không phải đặt giá trị trả về trên ngăn xếp và sau đó bạn sẽ không phải xóa nó.

Mã hiệu quả không mang lại cho bạn thứ gì đó bạn sẽ không bao giờ sử dụng, và do đó, bỏ trống vào thứ gì đó có giá trị trả lại vô nghĩa là cách thực hành tốt hơn nhiều so với trả về giá trị thành công.


14
Các lập trình viên, những người rất quan tâm đến tốc độ sẽ không sử dụng Java trong những ngày này, bởi vì các triển khai Java đầu tiên rất chậm. Chậm đến mức nhiều người hiện không làm việc với nó vẫn còn ấn tượng rằng Java là từ đồng nghĩa với chất béo và chậm.
Sange Borsch

8
@SargeBorsch làm tôi nhớ đến một trò đùa lập trình cũ từ 20 năm trước. "Cốc cốc." "Ai đó?" ... ... ... ... tạm dừng rất lâu ... ... ... "Java"
Dan Neely

4
@DanNeely và một thời gian sau, một chiếc xe do AI điều khiển đâm vào một bức tường với tốc độ tối đa, thậm chí không cố gắng sử dụng phanh. Nó được viết bằng Java. Vì vậy, Java không phanh ngay bây giờ! (đó là một cách chơi chữ trong tiếng Nga, không thể dịch nó sang tiếng Anh rất tốt)
SUND Borsch

2
@SargeBorsch Những gì bạn có là một cách chơi chữ tiếng Anh, ngay cả khi nó bị mất một số sắc thái trong dịch thuật. Và dịch chữ có lẽ khó hơn thơ.
Dan Neely

3
Các lập trình viên giỏi vẫn quan tâm đến hiệu năng và mức tiêu thụ bộ nhớ, điều này khiến mọi người không cần thiết phải trả lại rác liên quan ngay cả ngày nay.
Sklivvz

2

Tôi đã đọc các đối số gợi ý rằng tùy chọn thứ hai (trả về this) nên là cách tiếp cận thay vì void. Đây là một ý tưởng khá hay nhưng cách tiếp cận theo kiểu nhà xây dựng không phổ biến tại thời điểm Java được tạo. Nếu nó phổ biến ở thời điểm đó như bây giờ, thì đó có thể là cách tiếp cận được thực hiện. Trở về nulllà một ý tưởng thực sự tồi tệ IMO. Tôi ước gì nullcả trong ngôn ngữ.

Vấn đề bây giờ là nếu một phương thức trả về một thể hiện của kiểu riêng của nó, thì không rõ đó là một đối tượng mới hay cùng một đối tượng. Thường thì nó không (hoặc không nên) quan trọng nhưng lần khác nó lại quan trọng. Tôi cho rằng ngôn ngữ có thể được thay đổi để hoàn toàn trả thisvề các phương thức void. Vấn đề duy nhất tôi có thể nghĩ đến lúc này là nếu phương thức này sau đó được thay đổi để trả về một đối tượng mới cùng loại, sẽ không có cảnh báo biên dịch nào nhưng đó có thể không phải là vấn đề lớn. Hệ thống loại hiện tại không quan tâm đến các loại thay đổi này ngay bây giờ. Làm thế nào điều này tương tác với kế thừa / giao diện sẽ cần một số xem xét nhưng nó sẽ cho phép các API cũ mà không có kiểu trình tạo dễ dàng được gọi như thể chúng đã làm.


2
@Cody Điểm tốt, điều này sẽ không áp dụng cho các phương thức tĩnh.
JimmyJames

14
@ marisbest2 Đối số nào?
8bittree

3
Chà, nếu nullkhông phải là một phần của ngôn ngữ, mọi người sẽ sử dụng tất cả các loại giá trị trọng tâm có nghĩa là "vui lòng bỏ qua giá trị đối số / giá trị trả về này, nó không chứa bất kỳ giá trị hợp lý nào". Vấn đề với nullkhông phải là bản thân nó, chỉ có điều nó có xu hướng được sử dụng không chính xác. Tôi muốn rằng vấn đề này sẽ chỉ được phóng đại nếu tiêu chuẩn hóa nullđược thay thế bằng vô số giải pháp tùy chỉnh, trong đó mỗi triển khai sẽ hành xử khác nhau như âm thầm bỏ qua việc sử dụng hoặc ném ngoại lệ đặc biệt thay vì ngoại lệ con trỏ null độc lập, v.v.
cmaster

2
@cmaster một sự thay thế rõ ràng cho nullmột loại tùy chọn thích hợp (như, Maybetrong Haskell). Vấn đề với null trong Java là không có cách nào chắc chắn để quyết định ngay lập tức nếu một giá trị là nullable trong thực tế hay không. Điều này dẫn đến cả hai: lập trình phòng thủ không cần thiết (kiểm tra null trong trường hợp vô nghĩa, do đó làm tăng tỷ lệ phần trăm trong mã) và các NPE tình cờ trong sản xuất (nếu quên kiểm tra nơi cần thiết). Vâng, nó được giải quyết một phần bằng các chú thích nhưng chúng là tùy chọn, không phải lúc nào cũng được kiểm tra, v.v. Vì vậy, chúng sẽ không cứu bạn ở các ranh giới với mã của bên thứ ba.
Sange Borsch

2
@SargeBorsch Tôi đồng ý với điều đó :-) Sự phản đối của tôi là chống lại một ngôn ngữ mà không có cách tiêu chuẩn để diễn đạt "vui lòng bỏ qua giá trị này". nullhoạt động tốt cho điều này (fine = đơn giản), nhưng các giá trị tùy chọn được tiêu chuẩn hóa với ngữ nghĩa vững chắc chắc chắn là một tùy chọn tốt khác (fine = safe).
cmaster

2

Một khía cạnh khác:

Java là một ngôn ngữ tĩnh với các tính năng khá nghiêm ngặt. Điều này có nghĩa là nhiều điều sẽ khá cởi mở hoặc năng động trong các ngôn ngữ khác (c / f Ruby, Lisp, v.v.) được xác định nghiêm ngặt.

Đây là một quyết định thiết kế chung. "Tại sao" rất khó trả lời (tốt, bởi vì các nhà thiết kế ngôn ngữ nghĩ rằng nó sẽ tốt!). "What for" khá rõ ràng: nó cho phép trình biên dịch phát hiện rất nhiều lỗi, nói chung là một tính năng khá tốt cho bất kỳ ngôn ngữ nào. Thứ hai, nó làm cho nó tương đối dễ dàng để lý luận về ngôn ngữ. Ví dụ, tương đối dễ dàng để tạo ra tính chính xác chứng minh trong (tập hợp con) ngôn ngữ Java; để so sánh, điều đó hầu như không thể trong một ngôn ngữ động như Ruby et al.

Suy nghĩ này thấm vào ngôn ngữ, ví dụ, tuyên bố có hiệu lực của các ngoại lệ có thể xảy ra mà một phương thức có thể đưa ra, loại interfaceso với riêng biệt classđể tránh nhiều kế thừa mơ hồ, v.v. Đối với những gì nó là (một ngôn ngữ trong thế giới thực OOP bắt buộc tĩnh tập trung mạnh vào xử lý lỗi thời gian biên dịch) những điều đó thực sự khá thanh lịch và mạnh mẽ. Họ đến gần hơn với các ngôn ngữ lý thuyết (khoa học) được tạo ra nhằm mục đích khám phá một số vấn đề này hơn bất kỳ ngôn ngữ trong thế giới thực nào khác trước đây (tại thời điểm đó, làm phiền bạn).

Vì thế. Có một voidloại nghiêm ngặt là một thông điệp rõ ràng: phương pháp này không trả lại bất cứ điều gì, thời gian. Đó là những gì nó được. Việc thay thế nó bằng việc thực thi để luôn trả lại một cái gì đó sẽ dẫn đến hành vi năng động hơn nhiều (như trong Ruby khi mọi def đều có giá trị trả về rõ ràng hoặc ẩn), điều này sẽ không tốt cho khả năng chứng minh và lý luận; hoặc phình to bằng cách sử dụng một số cơ chế khác ở đây.

(Và NB, Ruby (ví dụ) xử lý việc này khác nhau và giải pháp của nó vẫn hoàn toàn có thể chấp nhận được như Java, bởi vì nó có một triết lý hoàn toàn khác. Ví dụ, nó ném hoàn toàn khả năng chứng minh và tính hợp lý ra khỏi cửa sổ trong khi tập trung lớn vào tính biểu cảm cực cao của ngôn ngữ.)

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.