Covariance, Invariance và Contravariance được giải thích bằng tiếng Anh đơn giản?


113

Hôm nay, tôi đọc một số bài viết về Covariance, Contravariance (và Bất biến) trong Java. Tôi đã đọc bài viết trên Wikipedia tiếng Anh và tiếng Đức, và một số bài viết và bài viết trên blog khác của IBM.

Nhưng tôi vẫn còn một chút bối rối về những gì chính xác là về? Một số người nói đó là về mối quan hệ giữa các kiểu và kiểu con, một số nói rằng đó là về chuyển đổi kiểu và một số nói rằng nó được sử dụng để quyết định xem một phương thức có bị ghi đè hay quá tải hay không.

Vì vậy, tôi đang tìm kiếm một lời giải thích dễ hiểu bằng tiếng Anh đơn giản, cho người mới bắt đầu biết Covariance và Contravariance (và Bất biến) là gì. Điểm cộng cho một ví dụ dễ hiểu.


Vui lòng tham khảo bài đăng này, nó có thể hữu ích cho bạn: stackoverflow.com/q/2501023/218717
Francisco Alvarado

3
Có lẽ tốt hơn là một câu hỏi loại trao đổi ngăn xếp của lập trình viên. Nếu bạn đăng bài ở đó, hãy cân nhắc chỉ nêu những gì bạn hiểu và những gì cụ thể khiến bạn bối rối, bởi vì ngay bây giờ bạn đang yêu cầu ai đó viết lại toàn bộ hướng dẫn cho bạn.
Hovercraft Full Of Eels

Câu trả lời:


288

Một số người nói rằng nó là về mối quan hệ giữa các kiểu và kiểu con, những người khác nói rằng nó là về chuyển đổi kiểu và những người khác nói rằng nó được sử dụng để quyết định xem một phương thức có bị ghi đè hay quá tải hay không.

Tất cả những điều trên.

Về cơ bản, các thuật ngữ này mô tả mối quan hệ kiểu con bị ảnh hưởng như thế nào bởi các phép biến đổi kiểu. Nghĩa là, nếu ABlà các kiểu, flà một phép biến đổi kiểu và ≤ quan hệ kiểu con (nghĩa A ≤ Blà đó Alà một kiểu con của B), chúng ta có

  • flà hiệp phương sai nếu A ≤ Bngụ ý rằngf(A) ≤ f(B)
  • flà trái ngược nếu A ≤ Bngụ ý rằngf(B) ≤ f(A)
  • f là bất biến nếu cả hai điều trên đều không

Hãy xem xét một ví dụ. Hãy để f(A) = List<A>nơi Listđược khai báo bởi

class List<T> { ... } 

Đồng fbiến, đối nghịch hay bất biến? Đồng biến có nghĩa là a List<String>là một kiểu con của List<Object>, đối nghịch rằng a List<Object>là một kiểu con của List<String>và bất biến mà không phải là một kiểu con của kiểu kia, tức là List<String>List<Object>là những kiểu không thể thay đổi được. Trong Java, điều sau là đúng, chúng tôi nói (hơi không chính thức) rằng generic là bất biến.

Một vi dụ khac. Hãy để f(A) = A[]. Đồng fbiến, đối nghịch hay bất biến? Nghĩa là, Chuỗi [] là kiểu con của Đối tượng [], Đối tượng [] là kiểu con của Chuỗi [], hay không phải là kiểu con của đối tượng kia? (Trả lời: Trong Java, các mảng là hiệp phương sai)

Điều này vẫn còn khá trừu tượng. Để làm cho nó cụ thể hơn, hãy xem các phép toán nào trong Java được định nghĩa theo quan hệ kiểu con. Ví dụ đơn giản nhất là phân công. Tuyên bố

x = y;

sẽ biên dịch chỉ nếu typeof(y) ≤ typeof(x). Đó là, chúng ta vừa biết rằng các câu lệnh

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

sẽ không biên dịch bằng Java, nhưng

Object[] objects = new String[1];

sẽ.

Một ví dụ khác trong đó quan hệ kiểu con quan trọng là một biểu thức gọi phương thức:

result = method(a);

Nói một cách không chính thức, câu lệnh này được đánh giá bằng cách gán giá trị của acho tham số đầu tiên của phương thức, sau đó thực thi phần thân của phương thức, rồi gán giá trị trả về cho phương thức result. Giống như phép gán đơn giản trong ví dụ trước, "phía bên phải" phải là một kiểu con của "phía bên trái", tức là câu lệnh này chỉ có thể hợp lệ nếu typeof(a) ≤ typeof(parameter(method))returntype(method) ≤ typeof(result). Đó là, phương thức if được khai báo bởi:

Number[] method(ArrayList<Number> list) { ... }

không có biểu thức nào sau đây sẽ biên dịch:

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());

nhưng

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());

sẽ.

Một ví dụ khác trong đó vấn đề định kiểu con được ghi đè. Xem xét:

Super sup = new Sub();
Number n = sup.method(1);

Ở đâu

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Number method(Number n);
}

Không chính thức, thời gian chạy sẽ viết lại điều này thành:

class Super {
    Number method(Number n) {
        if (this instanceof Sub) {
            return ((Sub) this).method(n);  // *
        } else {
            ... 
        }
    }
}

Đối với dòng được đánh dấu để biên dịch, tham số phương thức của phương thức ghi đè phải là siêu kiểu của tham số phương thức của phương thức bị ghi đè và kiểu trả về là kiểu con của phương thức bị ghi đè. Về mặt hình thức, f(A) = parametertype(method asdeclaredin(A))ít nhất phải là tương phản, và nếu f(A) = returntype(method asdeclaredin(A))ít nhất phải là hiệp phương sai.

Lưu ý "ít nhất" ở trên. Đó là những yêu cầu tối thiểu mà bất kỳ ngôn ngữ lập trình hướng đối tượng an toàn kiểu tĩnh hợp lý nào sẽ thực thi, nhưng một ngôn ngữ lập trình có thể chọn khắt khe hơn. Trong trường hợp của Java 1.4, kiểu tham số và kiểu trả về phương thức phải giống hệt nhau (ngoại trừ kiểu xóa) khi ghi đè phương thức, tức là parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))khi ghi đè. Kể từ Java 1.5, các kiểu trả về hiệp biến được phép khi ghi đè, tức là những kiểu sau sẽ được biên dịch trong Java 1.5, nhưng không phải trong Java 1.4:

class Collection {
    Iterator iterator() { ... }
}

class List extends Collection {
    @Override 
    ListIterator iterator() { ... }
}

Tôi hy vọng tôi đã che đậy mọi thứ - hay đúng hơn là làm trầy xước bề mặt. Tôi vẫn hy vọng nó sẽ giúp hiểu được khái niệm trừu tượng, nhưng quan trọng về phương sai kiểu.


1
Ngoài ra, vì các loại đối số tương phản trong Java 1.5 được phép khi ghi đè. Tôi nghĩ bạn đã bỏ lỡ điều đó.
Brian Gordon

13
Có phải họ không? Tôi vừa thử nó trong eclipse và trình biên dịch nghĩ rằng tôi có ý định quá tải hơn là ghi đè và từ chối mã khi tôi đặt chú thích @Override trên phương thức lớp con. Bạn có bằng chứng nào cho tuyên bố của mình rằng Java hỗ trợ các kiểu đối số trái ngược nhau không?
khen ngợi vào

1
À, bạn nói đúng. Tôi đã tin ai đó mà không tự mình kiểm tra.
Brian Gordon

1
Tôi đã đọc rất nhiều tài liệu và xem một vài bài nói về chủ đề này nhưng đây là lời giải thích tốt nhất cho đến nay. Thnx nhiều.
minzchickenflavor

1
+1 vì hoàn toàn dễ hiểu và đơn giản với A ≤ B. Ký hiệu đó khiến mọi thứ trở nên đơn giản và ý nghĩa hơn rất nhiều. Đọc tốt ...
Romeo Sierra

12

Lấy hệ thống kiểu java, rồi đến các lớp:

Bất kỳ đối tượng nào thuộc kiểu T đều có thể được thay thế bằng đối tượng thuộc kiểu con của T.

BIẾN DẠNG LOẠI - PHƯƠNG PHÁP LỚP CÓ HẬU QUẢ SAU

class A {
    public S f(U u) { ... }
}

class B extends A {
    @Override
    public T f(V v) { ... }
}

B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

Có thể thấy rằng:

  • T phải là kiểu con S ( hiệp phương sai, vì B là kiểu con của A ).
  • V phải supertype của U ( contravariant , như hướng thừa kế Contra).

Bây giờ đồng và trái ngược với B là kiểu con của A. Các cách đánh máy mạnh hơn sau đây có thể được giới thiệu với kiến ​​thức cụ thể hơn. Trong kiểu phụ.

Hiệp phương sai (có sẵn trong Java) rất hữu ích, nói rằng một trong những trả về một kết quả cụ thể hơn trong kiểu con; đặc biệt được thấy khi A = T và B = S. Sự tương phản cho biết bạn đã sẵn sàng để xử lý một lập luận chung chung hơn.


8

Phương sai là về mối quan hệ giữa các lớp với các tham số chung khác nhau. Mối quan hệ của họ là lý do tại sao chúng ta có thể chọn họ.

Phương sai Co và Contra là những thứ khá logic. Hệ thống loại ngôn ngữ buộc chúng ta phải hỗ trợ logic cuộc sống thực. Rất dễ hiểu bằng ví dụ.

Hiệp phương sai

Ví dụ, bạn muốn mua một bông hoa và bạn có hai cửa hàng hoa trong thành phố của bạn: cửa hàng hoa hồng và cửa hàng hoa cúc.

Nếu bạn hỏi ai đó "shop hoa ở đâu?" và ai đó cho bạn biết cửa hàng hoa hồng ở đâu, liệu nó có ổn không? vâng, bởi vì hoa hồng là một bông hoa, nếu bạn muốn mua một bông hoa, bạn có thể mua một bông hồng. Điều tương tự cũng áp dụng nếu ai đó trả lời bạn với địa chỉ của cửa hàng cúc. Đây là ví dụ về hiệp phương sai : bạn được phép ép kiểu A<C>tới A<B>, trong đó Clà một lớp con của B, nếu Atạo ra các giá trị chung (trả về là kết quả từ hàm). Hiệp phương sai là về nhà sản xuất.

Các loại:

class Flower {  }
class Rose extends Flower { }
class Daisy extends Flower { }

interface FlowerShop<T extends Flower> {
    T getFlower();
}

class RoseShop implements FlowerShop<Rose> {
    @Override
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop implements FlowerShop<Daisy> {
    @Override
    public Daisy getFlower() {
        return new Daisy();
    }
}

Câu hỏi là "cửa hàng hoa ở đâu?", Câu trả lời là "cửa hàng hoa hồng ở đó":

static FlowerShop<? extends Flower> tellMeShopAddress() {
    return new RoseShop();
}

Sự tương phản

Ví dụ bạn muốn tặng hoa cho bạn gái của mình. Nếu bạn gái yêu thích loài hoa nào, bạn có thể coi cô ấy như một người yêu hoa hồng, hay một người yêu hoa cúc? vâng, bởi vì nếu cô ấy yêu bất kỳ loài hoa nào, cô ấy sẽ yêu cả hoa hồng và hoa cúc. Đây là một ví dụ về contravariance : bạn được phép dàn diễn viên A<B>đến A<C>, nơi Clà lớp con của B, nếu Atiêu thụ giá trị chung. Sự tương phản là về người tiêu dùng.

Các loại:

interface PrettyGirl<TFavouriteFlower extends Flower> {
    void takeGift(TFavouriteFlower flower);
}

class AnyFlowerLover implements PrettyGirl<Flower> {
    @Override
    public void takeGift(Flower flower) {
        System.out.println("I like all flowers!");
    }

}

Bạn đang coi người bạn gái yêu bất kỳ loài hoa nào là người yêu hoa hồng và hãy tặng cô ấy một bông hồng:

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

Bạn có thể tìm thêm tại Nguồn .


@Peter, cảm ơn, đó là một điểm công bằng. Bất biến là khi không có mối quan hệ giữa các lớp với các tham số chung khác nhau, tức là bạn không thể chuyển A <B> thành A <C> bất kể mối quan hệ giữa B và C là gì.
VadzimV
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.