zoukankan      html  css  js  c++  java
  • Java 泛型 协变性、逆变性

    Java 泛型 协变性、逆变性

    @author ixenos

    摘要:协变性、协变通配符、协变数组、协变返回值 

     

     

     

     协变性、逆变性和无关性


    在面向对象的计算机程序语言中,经常涉及到类型之间的转换,例如从具体类小猫到动物之间的类型转换(上行转换),或者从形状向三角形之间的转换(下行转换)。

    协变性(covariance)、逆变性(contravariance)和无关性(invariant)。他们都是用来描述类型转换的性质的术语,形式化的描述如下:

    如果AB是类型,f表示类型转换,≤表示子类型关系,(例如AB,表示AB的子类)那么:

    如果AB f(A) f(B),小推大那么f协变的;如果AB f(B) f(A),大推小那么f逆变的;如果上面两种都不成立,那么f无关的。

     

     

    Java语言有一些语法可以用协变性和逆变性来理解


    • Java泛型
    class DataHolder<T>{
    
    }

        假如类型 A ≤ B, 但是直接使用DataHolder<A> 和DataHolder<B>是不可协变的,之前我们已经叙述过。

        但是利用Java提供的通配符语法,却可以提供一个协变的类型转换
           DataHolder<A> ≤DataHolder<? Extends B>  // ?是通配符,但这样协变是类型不安全的

        例如:

    static boolean find(Iterable<? extends Object> where, Object what){ //通配符?赋给泛型变量,可以有一个协变的类型转换
            return false; 
    } 

        协变的类型转换:可使用Iterable<String> 来调用find函数。

     

     

    协变数组


      协变数组是有漏洞的,具体分析请看:数组协变带来的静态类型漏洞 rednaxelafx.iteye.com/blog/379703

      注意:
    例如 A = String, B = Object 我们这里有AB
    f(A) = String[], f(B) = Object[].
    因为Object[] objects = new String[n].(赋值) 所以我们可以认为数组具有协变性

     

     

    协变返回值(在子类重写方法的时候)


    在面向对象语言中,一个协变返回值方法是一个在子类覆盖(Override)该方法的时候,方法的返回值可以被一个“更窄”的类型所替代。(Java JDK5.0
    例如:

    public static class Super { 
          Object getSomething() { 
          return null; 
       } 
    } 
    
    
    public static class SubClass extends Super { 
    @Override 
         String getSomething() { 
               return null; 
         } 
    } 

    这段话主要的思想就是协同返回值使用在子类需要一个不同于父类覆盖方法的返回值,但是返回值却有继承关系的情况下。
    一个可能更好的例子如下所示:

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

    Iterator函数获得到当前集合的迭代器,在子类中,迭代器有着更确切的表示,所以使用了Iterator的子类ListIterator作为新的返回值。

     

     

    逆变参数(形参)


      继承的一个法则:子类不能比父类有更严格的限制
    先举个例子,可以一下子明白逆变参数的例子,其实逆变参数使用环境依然是在继承体系下的函数覆盖

    class Super { 
              void doSomething(String parameter) { 
              } 
    } 
     
    class Sub extends Super { 
            void doSomething(Object parameter) { //逆变了
             } 
    } 

    为了更好的理解这个问题,首先引入Liskov Substitution Principle的概念。这个原则的内容就是说,子类的对象可以在任何需要父类对象的地方使用
    这个原则和参数以及返回值有什么关系呢?
    假如我们定义类如下:

    class Super { 
           ReturnType doSomething(ParameterType a) { 
                return null; 
           } 
    } 
     
    class Sub extends Super { 
           ReturnType doSomething(ParameterType a) { 
                    return null; 
           } 
    } 

    假如我有Super sup;
    首先考虑参数,现在要调用sup.doSomething(o);
    如果两个函数需要的参数类型一致,没有什么值得讨论的。假如在一些情况下,sub需要的参数类型变成了和ParameterType有继承关系的另一个类NewParameterType。如下:

    class Sub extends Super { 
              ReturnType doSomething(NewParameterType a) { 
                    return null; 
              } 
    } 

    如果NewParameterTypeParameterType的子类,那么sup.doSomething(o)调用中,如果o是指向NewParameterType的对象,则不会被通过。只有NewParameterTypeParameterType的父类的时候,才不会发生任何问题(形参要变只能逆变)。所以在提出逆变参数的概念。
    同样地,利用这个原则也可以用来理解。
    假如在某使用环境中有如下语句:
    ReturnType rt = sup.doSomething(o);
    假如设计变更,子类需要返回一个与ReturnType有继承关系的类NewReturnType,如果返回的是ReturnType的父类,那么则上面语句则不能通过编译,只有NewReturnTypeReturnType的子类的时候,才不会违反Liskov子类原则。

     

     

  • 相关阅读:
    Ambiguous mapping. Cannot map 'labelInfoController' method
    在写ssh项目时浏览器页面出现http status 404 – not found
    JS页面出现Uncaught SyntaxError: Unexpected token < 错误
    Data truncation: Truncated incorrect DOUBLE value:
    个人最终总结
    结对编程--黄金点游戏
    Windows操作系统----锁住命令行窗口
    Windows操作系统下给文件夹右键命令菜单添加启动命令行的选项
    命令行下运行 java someClass.class出现 “错误:找不到或无法加载主类someClass ” 的解决方案
    Qt Quick程序的发布
  • 原文地址:https://www.cnblogs.com/ixenos/p/5647229.html
Copyright © 2011-2022 走看看