2012-06-19 24 views
9

Giả sử tôi có một hệ thống phân cấp lớp như sau:Java Generics, làm thế nào để thực thi hai đối số của một phương thức mở rộng một siêu lớp để có cùng một kiểu?

class Vehicle; 
class Car extends Vehicle; 
class Plane extends Vehicle; 

Tôi có một chức năng mà so sánh hai đối tượng

public <T extends Vehicle> generateDiff(T original, T copy) 

Tại thời gian biên dịch, phương pháp nêu trên đảm bảo hai đối tượng là Vehicle, nhưng nó không thể đảm bảo các loại của hai đối tượng giống nhau.

generateDiff(new Car(), new Car()); //OK 
generateDiff(new Plane(), new Plane()); //OK 
generateDiff(new Car(), new Plane()); //WRONG 

Tôi có thể đạt được điều này tại thời gian biên dịch bằng Generics không?

P.s: hiện tại, tôi đã triển khai nó sẽ ném ngoại lệ nếu Class của hai đối tượng không giống nhau. Nhưng tôi không hài lòng với điều này.

Xin cảm ơn trước.

Trả lời

8

Có, bạn có thể (loại)!

Loại T đang được inferred từ các đối số, nhưng bạn có thể chỉ định các loại:

MyClass.<Car>generateDiff(new Car(), new Plane()); // generates a compile error 

Nếu không có sự gõ phương pháp, loại T được suy ra là lớp hẹp nhất thỏa mãn các giới hạn như sử dụng , vì vậy các thông số CarPlane, loại hẹp nhất mà sẽ làm việc là Vehicle, vì vậy hai dòng sau là tương đương:

generateDiff(new Car(), new Plane()); // type is inferred as Vehicle 
MyClass.<Vehicle>generateDiff(new Car(), new Plane()); 

Đoạn mã trên giả định generateDiff() là một phương pháp tĩnh. Nếu đó là một phương thức thể hiện, bạn có thể gõ lớp của bạn và có kiểu đó được sử dụng trong phương thức của bạn.

+0

+1 cho suy luận kiểu! – UmNyobe

3

Không thể theo ý kiến ​​của tôi, bất kỳ phương pháp nào có thể chấp nhận Xe làm đối số sẽ có thể chấp nhận CAR và PLANE. Kể từ khi trình biên dịch không biết loại đối tượng sẽ đến (có thể là CAR, BUS, PLANE), nó không thể bảo đảm rằng hai params có cùng loại chính xác. Nếu một số người mở rộng CAR và tạo ra một FORD? cả hai đối tượng đều là CAR.

Cách duy nhất để đảm bảo điều này là thời gian chạy sử dụng logic tùy chỉnh.

1

Không, điều này không thể đạt được có hoặc không có generics. Về cơ bản, bạn đang yêu cầu nếu bạn có thể nói với trình biên dịch vi phạm các quy tắc của đa hình.

Ngay cả khi bạn đã xác định rõ ràng một phương pháp không có generics, như hình bên dưới, nó vẫn sẽ chấp nhận bất kỳ cặp lớp nào mở rộng Vehicle.

void generateDiff(Vehicle maybePlane, Vehicle maybeCar) { ... 

Có một trường hợp ngoại lệ, nhưng tôi không khuyên bạn nên sử dụng. Nếu bạn đang gọi phương thức đối với các đối tượng lớp final (hoặc bất kỳ lớp nào không được mở rộng) mở rộng Vehicle, bạn có thể ghi đè phương thức để khớp với chữ ký của lớp đó cho từng thông số. Nhưng bạn cần phải xác định từng cái một cách rõ ràng.

class Vehicle; 
final class Car extends Vehicle; 
final class Plane extends Vehicle; 

void generateDiff(Car car1, Car car2) { ... 
void generateDiff(Plane plane1, Plane plane2) { ... 

generateDiff(new Car(), new Car()); // OK 
generateDiff(new Plane(), new Plane()); // OK 
generateDiff(new Car(), new Plane()); // No matching method 
1

Tôi có thể đạt được điều này tại thời gian biên dịch bằng Generics không?

Không phải vì các loại được đặt vào thời gian chạy, nhưng bạn có thể chỉ cần sử dụng nội tâm và ném ngoại lệ nếu loại không hợp lệ được gửi đến hàm, mặc dù trường hợp đó có thể gây ra lỗi trong thiết kế của bạn , nhưng đó chỉ là ý kiến ​​của tôi.

instanceof nhà điều hành sẽ kiểm tra loại cơ bản và vì vậy bạn có thể tăng ngoại lệ hoặc làm điều gì đó phù hợp hơn.

public <T extends Vehicle> generateDiff(T original, T copy) 

kể từ khi bạn có thể so sánh các loại khác nhau, có lẽ bạn nên không có một chức năng duy nhất, vì nó sẽ đòi hỏi một số tiền hợp lý của if else nó có thể là khôn ngoan để thực hiện các chức năng tương ứng trong mỗi lớp để họ có thể đúng so sánh với các đối tượng thuộc loại thích hợp, mặc dù Im lấy một số giả định hợp lý, điều này có thể sai.

6

Sau khi bạn hiểu sâu hơn về nó, nó sẽ có một chút trừu tượng. Bạn cũng sẽ phải cung cấp loại lớp cho hàm này (xem bên dưới). Nếu bạn muốn thực thi hành vi như vậy, tôi khuyên bạn nên viết các phương thức riêng biệt chấp nhận các loại bạn muốn so sánh. Dù sao:

public <C extends Vehicle> void generateDiff(Class<C> type, C original, C copy); 

Và bạn có thể sử dụng nó như vậy:

generateDiff(Plane.class, new Plane(), new Plane()); // OK 
generateDiff(Car.class, new Car(), new Car()); // OK 
generateDiff(Plane.class, new Plane(), new Car()); // ERROR 
generateDiff(Vehicle.class, new Plane(), new Car()); // OK 

Không chắc lý do tại sao mọi người lành mạnh sẽ muốn làm điều này mặc dù! :)

+0

+1 cho 'Không chắc chắn tại sao bất kỳ người lành mạnh nào cũng muốn làm điều này! :) ' –

0

Không có điều đó là không thể!

Đây là những gì tôi sẽ làm:

class Vehicle; 
class Car extends Vehicle; 
class Plane extends Vehicle; 

class DiffGenerator { 
    public Diff generateDiff(Car original, Car copy) { 
    return generateDiff(original, copy) 
    } 

    public Diff generateDiff(Plane original, Plane copy) { 
    return generateDiff(original, copy) 
    } 

    private Diff generateDiff(Vehicle original, Vehicle copy) { 
    return generatedDiff; 
    } 

} 

Nhận thấy phương pháp tư nhân? private Diff generateDiff(Vehicle original, Vehicle copy)

3

Nói đúng ra, câu trả lời cho câu hỏi của bạn là 'không, không thể'.

Tuy nhiên, có workaround. Tạo phương thức <T extends Vehicle> VehicleDiffer<T> compare(T vehicleA) trong đó VehicleDiffer<T> có phương thức ReturnType with(T vehicleB). Bây giờ bạn có thể thực hiện các cuộc gọi sau đây:

compare(new Car()).with(new Car()); // okay 
compare(new Plane()).with(new Plane()); // okay 

Sau đây sẽ thất bại:

compare(new Car()).with(new Plane()); // with(Car) can't be called with argument type Plane 
+0

Ý tưởng thiên tài tôi nói. – Saintali

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