Phương thức có cùng cách xóa với phương thức khác trong loại


381

Tại sao không hợp pháp khi có hai phương thức sau trong cùng một lớp?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

Tôi hiểu compilation error

Phương thức add (Set) có cùng phương thức erasure (Set) như một phương thức khác trong kiểu Test.

Trong khi tôi có thể làm việc xung quanh nó, tôi đã tự hỏi tại sao javac không thích điều này.

Tôi có thể thấy rằng trong nhiều trường hợp, logic của hai phương thức đó sẽ rất giống nhau và có thể được thay thế bằng một

public void add(Set<?> set){}

phương pháp, nhưng điều này không phải lúc nào cũng đúng.

Điều này cực kỳ khó chịu nếu bạn muốn có hai constructorsđối số lấy các đối số đó vì sau đó bạn không thể thay đổi tên của một trong số đó constructors.


1
Điều gì nếu bạn hết cấu trúc dữ liệu và bạn vẫn cần nhiều phiên bản hơn?
Omry Yadan

1
Bạn có thể tạo các lớp tùy chỉnh kế thừa từ các phiên bản cơ sở.
willlma

1
OP, bạn đã đưa ra một số giải pháp cho vấn đề xây dựng? Tôi cần chấp nhận hai loại Listvà tôi không biết cách xử lý.
Tomáš Zato - Phục hồi Monica

9
Khi làm việc với Java, tôi thực sự nhớ C # ...
Svish

1
@ TomášZato, tôi đã giải quyết điều đó bằng cách thêm các thông số giả vào hàm tạo: Boolean noopSignatureOverload.
Nthalk

Câu trả lời:


357

Quy tắc này nhằm tránh xung đột trong mã kế thừa vẫn sử dụng các loại thô.

Đây là một minh họa về lý do tại sao điều này không được phép, được rút ra từ JLS. Giả sử, trước khi thuốc generic được giới thiệu cho Java, tôi đã viết một số mã như thế này:

class CollectionConverter {
  List toList(Collection c) {...}
}

Bạn mở rộng lớp học của tôi, như thế này:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

Sau khi giới thiệu thuốc generic, tôi quyết định cập nhật thư viện của mình.

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

Bạn chưa sẵn sàng để thực hiện bất kỳ cập nhật nào, vì vậy bạn rời khỏi Overriderlớp học của mình. Để ghi đè chính xác toList()phương thức, các nhà thiết kế ngôn ngữ đã quyết định rằng một loại thô là "tương đương ghi đè" với bất kỳ loại tổng quát nào. Điều này có nghĩa là mặc dù chữ ký phương thức của bạn không còn chính thức bằng chữ ký của siêu lớp của tôi, phương thức của bạn vẫn ghi đè.

Bây giờ, thời gian trôi qua và bạn quyết định bạn đã sẵn sàng để cập nhật lớp học của mình. Nhưng bạn làm hỏng một chút và thay vì chỉnh sửa toList()phương thức thô, hiện có , bạn thêm một phương thức mới như thế này:

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

Do sự tương đương ghi đè của các loại thô, cả hai phương thức đều ở dạng hợp lệ để ghi đè toList(Collection<T>)phương thức. Nhưng tất nhiên, trình biên dịch cần giải quyết một phương thức duy nhất. Để loại bỏ sự mơ hồ này, các lớp không được phép có nhiều phương thức tương đương với ghi đè, đó là nhiều phương thức có cùng loại tham số sau khi xóa.

Điều quan trọng là đây là một quy tắc ngôn ngữ được thiết kế để duy trì khả năng tương thích với mã cũ bằng cách sử dụng các loại thô. Nó không phải là một giới hạn theo yêu cầu của việc xóa các tham số loại; bởi vì độ phân giải phương thức xảy ra tại thời gian biên dịch, việc thêm các kiểu chung vào mã định danh phương thức sẽ là đủ.


3
Câu trả lời tuyệt vời và ví dụ! Tuy nhiên, tôi không chắc chắn nếu tôi hoàn toàn hiểu câu cuối cùng của bạn ("Bởi vì độ phân giải phương thức xảy ra vào thời gian biên dịch, trước khi xóa, không cần phải có sự thống nhất kiểu để thực hiện công việc này."). Bạn có thể xây dựng một chút?
Jonas E Rich

2
Có ý nghĩa. Tôi chỉ dành một chút thời gian để suy nghĩ về việc hợp nhất kiểu trong các phương thức mẫu, nhưng vâng: trình biên dịch đảm bảo đúng phương thức được chọn trước khi xóa kiểu. Xinh đẹp. Nếu nó không bị ô nhiễm bởi các vấn đề tương thích mã kế thừa.
Jonas Erich

1
@daveloyall Không, tôi không biết về một lựa chọn như vậy cho javac.
erickson

13
Không phải lần đầu tiên tôi gặp lỗi Java mà không có lỗi gì cả và có thể được biên dịch nếu chỉ các tác giả của Java sử dụng các cảnh báo như mọi người khác. Chỉ có họ nghĩ rằng họ biết mọi thứ tốt hơn.
Tomáš Zato - Tái lập Monica

1
@ TomášZato Không, tôi không biết bất kỳ cách giải quyết nào cho việc đó. Nếu bạn muốn một cái gì đó bẩn, bạn có thể vượt qua List<?>và một số enummà bạn xác định để chỉ ra loại. Logic đặc trưng kiểu có thể thực sự nằm trong một phương thức trên enum. Ngoài ra, bạn có thể muốn tạo hai lớp khác nhau, thay vì một lớp với hai hàm tạo khác nhau. Logic thông thường sẽ là trong một siêu lớp hoặc một đối tượng trợ giúp mà cả hai loại ủy nhiệm.
erickson

118

Java generic sử dụng kiểu xóa. Bit trong dấu ngoặc nhọn ( <Integer><String>) bị xóa, do đó, bạn kết thúc bằng hai phương thức có chữ ký giống hệt nhau ( add(Set)bạn thấy trong lỗi). Điều đó không được phép vì thời gian chạy sẽ không biết nên sử dụng cho từng trường hợp.

Nếu Java từng có các tổng quát thống nhất, thì bạn có thể làm điều này, nhưng điều đó có lẽ không thể xảy ra bây giờ.


24
Tôi xin lỗi nhưng câu trả lời của bạn (và các câu trả lời khác) không giải thích tại sao có lỗi ở đây. Quá trình phân giải quá tải được thực hiện tại thời gian biên dịch và trình biên dịch chắc chắn có thông tin loại cần thiết để quyết định phương thức nào sẽ liên kết theo địa chỉ hoặc bằng bất cứ phương thức nào được tham chiếu trong mã byte mà tôi tin là không phải chữ ký. Tôi thậm chí nghĩ rằng một số trình biên dịch sẽ cho phép điều này để biên dịch.
Stilgar

5
@Stilgar điều gì để ngăn chặn phương thức được gọi hoặc kiểm tra thông qua sự phản chiếu? Danh sách các phương thức được Class.getMethods () trả về sẽ có hai phương thức giống nhau, điều này sẽ không có ý nghĩa.
Adrian Mouat

5
Thông tin phản ánh có thể / nên chứa siêu dữ liệu cần thiết để làm việc với thuốc generic. Nếu không làm thế nào trình biên dịch Java biết về các phương thức chung khi bạn nhập thư viện đã biên dịch?
Stilgar

4
Sau đó, phương thức getMethod cần sửa chữa. Ví dụ, giới thiệu một tình trạng quá tải chỉ định quá tải chung và làm cho phương thức ban đầu chỉ trả về phiên bản không chung chung, không trả về bất kỳ phương thức nào được anot như chung chung. Tất nhiên điều này nên được thực hiện trong phiên bản 1.5. Nếu họ làm điều đó bây giờ họ sẽ phá vỡ tính tương thích ngược của phương thức. Tôi đứng trước tuyên bố của mình rằng loại tẩy không ra lệnh cho hành vi này. Đó là việc thực hiện không có đủ công việc có thể do nguồn lực hạn chế.
Stilgar

1
Đây không phải là một câu trả lời chính xác, nhưng nó nhanh chóng tổng hợp vấn đề trong một tiểu thuyết hữu ích: chữ ký phương thức quá giống nhau, trình biên dịch có thể không cho biết sự khác biệt và sau đó bạn sẽ gặp "vấn đề biên dịch chưa được giải quyết."
worc

46

Điều này là do Java Generics được triển khai với Type Erasure .

Các phương thức của bạn sẽ được dịch, tại thời điểm biên dịch, thành một cái gì đó như:

Phương pháp giải quyết xảy ra tại thời gian biên dịch và không xem xét các tham số loại. ( xem câu trả lời của erickson )

void add(Set ii);
void add(Set ss);

Cả hai phương thức có cùng chữ ký mà không có tham số loại, do đó lỗi.


21

Vấn đề là điều đó Set<Integer>Set<String>thực sự được coi là một Settừ JVM. Chọn một loại cho Tập hợp (Chuỗi hoặc Số nguyên trong trường hợp của bạn) chỉ là đường cú pháp được sử dụng bởi trình biên dịch. JVM không thể phân biệt giữa Set<String>Set<Integer>.


7
Đúng là thời gian chạy JVM không có thông tin để phân biệt từng loại Set, nhưng vì độ phân giải phương thức xảy ra vào thời gian biên dịch, khi có sẵn thông tin cần thiết, điều này không liên quan. Vấn đề là việc cho phép các tình trạng quá tải này sẽ mâu thuẫn với trợ cấp cho các loại thô, do đó chúng bị coi là bất hợp pháp trong cú pháp Java.
erickson

@erickson Ngay cả khi trình biên dịch biết nên gọi phương thức nào, nó không thể như trong mã byte, cả hai đều trông giống hệt nhau. Bạn cần thay đổi cách chỉ định một cuộc gọi phương thức, vì (Ljava/util/Collection;)Ljava/util/List;nó không hoạt động. Bạn có thể sử dụng (Ljava/util/Collection<String>;)Ljava/util/List<String>;, nhưng đó là một thay đổi không tương thích và bạn sẽ gặp phải những vấn đề không thể giải quyết được ở những nơi mà tất cả những gì bạn có là một loại bị xóa. Bạn có thể phải bỏ tẩy xóa hoàn toàn, nhưng điều đó khá phức tạp.
maaartinus

@maaartinus Có, tôi đồng ý rằng bạn phải thay đổi công cụ xác định phương thức. Tôi đang cố gắng giải quyết một số vấn đề không thể giải quyết được khiến họ từ bỏ nỗ lực đó.
erickson

7

Xác định một Phương thức duy nhất không có kiểu như void add(Set ii){}

Bạn có thể đề cập đến loại trong khi gọi phương thức dựa trên sự lựa chọn của bạn. Nó sẽ làm việc cho bất kỳ loại thiết lập.


3

Có thể trình biên dịch dịch Set (Integer) thành Set (Object) trong mã byte java. Nếu đây là trường hợp, Set (Integer) sẽ chỉ được sử dụng ở giai đoạn biên dịch để kiểm tra cú pháp.


5
Về mặt kỹ thuật, đây chỉ là loại thô Set. Generics không tồn tại trong mã byte, chúng là đường cú pháp để truyền và cung cấp an toàn kiểu thời gian biên dịch.
Andrzej Doyle

1

Tôi đã va vào điều này khi cố gắng viết một cái gì đó như: Continuable<T> callAsync(Callable<T> code) {....}Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} chúng trở thành trình biên dịch cho 2 định nghĩa về Continuable<> callAsync(Callable<> veryAsyncCode) {...}

Kiểu xóa theo nghĩa đen có nghĩa là xóa thông tin đối số kiểu từ tổng quát. Điều này thật phiền phức, nhưng đây là một hạn chế sẽ xảy ra với Java trong một thời gian. Đối với trường hợp hàm tạo không thể thực hiện được nhiều, 2 lớp con mới chuyên với các tham số khác nhau trong hàm tạo chẳng hạn. Hoặc sử dụng các phương thức khởi tạo thay vì ... (các hàm tạo ảo?) Với các tên khác nhau ...

đối với các phương thức hoạt động tương tự, việc đổi tên sẽ giúp ích, như

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

Hoặc với một số tên mô tả nhiều hơn, tự viết tài liệu cho các trường hợp oyu, như addNamesaddIndexeshoặc như vậy.

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.