代码之家  ›  专栏  ›  技术社区  ›  LazySlav

在泛型ArrayList中查找对象[重复]

  •  0
  • LazySlav  · 技术社区  · 3 年前

    今天,我读了一些关于Java中的协变、逆变(和不变)的文章。我阅读了维基百科的英文和德文文章,以及IBM的其他一些博客文章和文章。

    但我仍然有点困惑,这些到底是关于什么的?有人说这是关于类型和子类型之间的关系,有人说它是关于类型转换,还有人说它用于决定方法是被重写还是重载。

    所以我想找一个简单的英语解释,向初学者展示什么是协变和逆变(以及不变性)。

    0 回复  |  直到 7 年前
        1
  •  356
  •   arieljannai    8 年前

    有人说这是关于类型和子类型之间的关系,有人说它是关于类型转换,还有人说它用于决定方法是被覆盖还是重载。

    以上所有内容。

    从本质上讲,这些术语描述了子类型关系如何受到类型转换的影响。也就是说,如果 A B 是类型, f 是类型转换,而子类型关系(即。 A ≤ B 意味着 A. 是的子类型 B ),我们有

    • f 是协变的,如果 A B 意味着 f(A) ≤ f(B)
    • f 如果 A B 意味着 f(B) ≤ f(A)
    • f 是不变的,如果以上两者都不成立

    让我们考虑一个例子。允许 f(A) = List<A> 哪里 List 由声明

    class List<T> { ... } 
    

    f 协变、反变还是不变?Covariant意味着 List<String> 是的子类型 List<Object> ,与 列表<对象> 是的子类型 列表<字符串> 不变的是两者都不是另一个的子类型,即。 列表<字符串> 列表<对象> 是不可转换的类型。在Java中,后者是正确的,我们(有些非正式地)说 泛型 是不变的。

    另一个例子。允许 f(A) = A[] f 协变、反变还是不变?也就是说,String[]是Object[]的一个子类型,Object[]是String[]的一子类型,还是两者都不是另一个子类型?(答案:在Java中,数组是协变的)

    这仍然相当抽象。为了更具体,让我们看看Java中的哪些操作是根据子类型关系定义的。最简单的例子是赋值。声明

    x = y;
    

    将仅在以下情况下编译 typeof(y) ≤ typeof(x) 。也就是说,我们刚刚了解到

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

    不会用Java编译,但是

    Object[] objects = new String[1];
    

    子类型关系很重要的另一个示例是方法调用表达式:

    result = method(a);
    

    非正式地说,这句话是通过赋值来评估的 a 到方法的第一个参数,然后执行方法的主体,然后将方法返回值分配给 result 。与上一个示例中的普通赋值一样,“右手边”必须是“左手边”的子类型,即此语句只能在以下情况下有效 typeof(a) ≤ typeof(parameter(method)) returntype(method) ≤ typeof(result) 。也就是说,如果方法由声明:

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

    以下任何表达式都不会编译:

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

    但是

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

    另一个重要的子类型是压倒一切的例子。考虑:

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

    哪里

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

    非正式地,运行时会将其重写为:

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

    对于要编译的标记行,重写方法的方法参数必须是被重写方法方法参数的超类型,返回类型必须是被覆盖方法的子类型。从形式上讲, f(A) = parametertype(method asdeclaredin(A)) 必须至少是相反的,并且如果 f(A) = returntype(method asdeclaredin(A)) 必须至少是协变的。

    注意上面的“至少”。这些是任何合理的静态类型安全的面向对象编程语言都会强制执行的最低要求,但编程语言可能会选择更严格的要求。在Java 1.4的情况下,重写方法时,参数类型和方法返回类型必须相同(类型擦除除外),即。 parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) 当覆盖时。由于Java 1.5,重写时允许协变返回类型,即以下将在Java 1.5中编译,但在Java 1.4中不编译:

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

    我希望我涵盖了一切——或者更确切地说,触及了表面。尽管如此,我还是希望这将有助于理解类型方差这一抽象但重要的概念。

        2
  •  28
  •   Roman Mahotskyi    4 年前

    方差是关于具有不同泛型参数的类之间的关系。他们的关系是我们能够铸造他们的原因。

    Co和Contra方差是非常合乎逻辑的东西。语言类型系统迫使我们支持现实生活中的逻辑。举个例子很容易理解。

    协方差

    例如,你想买一朵花,而你所在的城市有两家花店:玫瑰店和雏菊店。

    如果你问某人“花店在哪里?”有人告诉玫瑰店在哪里,可以吗?是的,因为玫瑰是一朵花,如果你想买一朵花,你可以买一朵玫瑰。如果有人回复你菊花店的地址,也是如此。 这是一个例子 协方差 :您可以铸造 A<C> A<B> 哪里 C 是的子类 B 如果 A 生成泛型值(作为函数的结果返回)。协方差是关于生产者的。

    类型:

    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();
        }
    }
    

    问题是“花店在哪里?”,答案是“玫瑰店在那里”:

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

    抵销

    例如,你想把花送给你的女朋友。如果你的女朋友喜欢任何一朵花,你能认为她是一个喜欢玫瑰的人,还是一个喜欢雏菊的人?是的,因为如果她喜欢任何一朵花,她都会喜欢玫瑰和雏菊。 这是 反方差 :你可以选 A<B> A<C> 哪里 C 是的子类 B 如果 A. 消耗一般价值。矛盾是关于消费者的。

    类型:

    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!");
        }
    
    }
    

    你把喜欢任何花的女朋友视为喜欢玫瑰的人,并送给她一朵玫瑰:

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

    您可以在 Source

        3
  •  13
  •   Joop Eggen    14 年前

    采用java类型的系统,然后类:

    任何T类型的对象都可以用T的子类型的对象代替。

    类型方差-类方法具有以下结果

    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;
    

    可以看出:

    • T必须是S亚型( 协变,因为B是A的亚型 )。
    • V必须是U的超型( 反变体 ,作为反向继承方向)。

    现在,共型和反型都与B是A的亚型有关。以下更强的打字法可以用更具体的知识来介绍。在子类型中。

    协方差(在Java中可用)是有用的,可以说在子类型中返回更具体的结果;尤其是当A=T和B=S时。 相反,你已经准备好处理一个更一般的论点了。