• 关于泛型的一些细节


      之前在写公司项目的底层框架的时候用到一些泛型,实践中涉及到一些没关注到的细节,为此专门去Oracle官网把泛型的文档学习了一遍。

      Java中的泛型跟C++里面的Template(模板)是同一个类型的东西,都是为了在其他地方调用的时候可以传入各种参数类型。

      在实践中,与使用泛型有相似效果的是函数重载,即根据传入参数类型的不同,选择调用不同的函数。泛型和函数重载各有利弊,需要根据使用情景来选择。如果一段代码对于不同类型的参数,可以不做类型区分地使用,比如List的add方法,这时就用泛型。而如果一段代码对于传入的参数,应该根据不同的数据类型,执行不同的语句,这时就应该使用重载。为什么?因为这时如果使用泛型,就会出现大量的instanceof判断,判断之后还有各种影响代码质量的泛型与实际类型之间的类型强转,而如果返回值也是泛型,那就更麻烦了。典型的就是之前对SharedPreferences进行封装,对于不同类型的参数执行统一的get/put方法,但是如果传入String类型,底层就要执行getString/putString方法,如果传入int,就要执行getInt/putInt方法,这样就必须使用如下的函数重载形式:

        public static String get(String key, String defaultValue) {
            SharedPreferences sp = obtainPref();
            return sp.getString(key, defaultValue);
        }
    
        public static int get(String key, int defaultValue) {
            SharedPreferences sp = obtainPref();
            return sp.getInt(key, defaultValue);
        }
    
        public static boolean get(String key, boolean defaultValue) {
            SharedPreferences sp = obtainPref();
            return sp.getBoolean(key, defaultValue);
        }    

     另外像JDK源码里面,StringBuilder的append方法,也是根据参数类型写了一大堆看似啰嗦的重载函数,为什么?因为方法体不一样啊。

      回归正题,如果针对不同的参数类型,可以用同一段代码,还是推荐用泛型的,毕竟可以把几段代码合并成一段代码。

    public class MyClass<T> {
    
        public static void main(String[] args) {
            MyClass<Integer> myClass = new MyClass<>();
            myClass.printT(100);
            MyClass<Boolean> myClass2 = new MyClass<>();
            myClass2.printT(true);
        }
    
        public void printT(T t) {
            System.out.print(t);
        }
    }

       请注意,这里的泛型"T",代表的只能是Object类型,不能是int,boolean,char这些基本数据类型,比如像下面这样写就是错的:

    MyClass<int> myClass = new MyClass<>();  //报错
    myClass.printT(1);

      也就是说,其实T是继承自Object的。

      那么,为什么定义的时候泛型参数必须是Object,而实际传值的时候可以是100,true这些呢?因为JDK在编译时做了一个自动装箱的处理,把int类型包装成了Integer类型,boolean类型则包装成Boolean类型。可以参考我的另一篇blog: Java暗箱操作之自动装箱与拆箱

      代码里面每一个用到泛型参数 T, K, E,...都必须遵循先声明再使用的原则,即如果你提到了这些泛型名称,就必须在之前的某个地方被声明过,否则会报错。

      泛型的声明位置只能是两个地方,一是类名处,二是方法处,别的地方都不能声明。第一种方式,就是上面的 public class MyClass<T> {..}这种,在类名之后加,这样在类里面所有地方都能用"T"这个泛型参数。第二种方式在方法处声明可能不太常见,之前我也不太熟悉,但项目里确实用到了,只好研究一下,声明格式类似于这样:

    public <T> T printT(T t) {
        System.out.print(t);
    }

      这里的T就只能作用于方法体了,而且会覆盖类上声明的泛型,例如以下代码会正常运行:

    public class MyClass<T> {
    
        public static void main(String[] args) {
            MyClass<String> myClass = new MyClass<>();
            myClass.printT(100);
        }
    
        public <T> T printT(T t) {
            System.out.print(t);
            return t;
        }
    
    }

       调用时,类上的泛型是String,方法上传入的是Integer,那就以方法上的为准咯~

      特别注意,方法上的泛型参数必须声明在返回值之前,public/private之后,是有固定位置的。

       

      可以对调用时传入的泛型加限制条件,限制T必须是某个类(接口)的子类

    public class MyClass<T extends Number> {...}

      这里,T就只能是Number或者Number的子类Integer,Float,Long这些,传入String就是错误的。

      T也可以继承自多个类,注意这里的类是泛指,包括接口在内,即写成

    public class MyClass<T extends A & B & C> {...}

      其中A可以是类或接口,B、C只能是接口,即多继承的话至多只能有一个是类,且必须把类写在第一个。

      传入的泛型参数还可以是wildcard(通配符)

    MyClass<?> myClass = new MyClass<>();

       "?"是在调用时传入的东西,取代String, Integer这些实际的类型。

      有两种情况会传入"?":1、调用过程中仅涉及到Object的方法,像equals()等等;2、调用过程中不依赖于泛型。最典型的是Class<?>,因为调用的Class方法基本用不到泛型。

      

      更多内容参考Oracle官方文档:Generics

      

  • 相关阅读:
    雅虎天气接口
    解决activeandroid no such table
    解决Genymotion Error: “Unable to load VirtualBox Engine” on Yosemite. VirtualBox installed
    存金宝 价格提示
    添加 SSH 公钥
    ImportError: No module named flask.ext.wtf 解决方法
    Cannot fetch index base URL https://pypi.python.org/pypi/ 解决方法
    mac下只遍历目录不遍历文件
    dubbo源代码编译打包错误解决
    maven 基本配置
  • 原文地址:https://www.cnblogs.com/zhinengfeiyu/p/5790652.html
Copyright © 2020-2023  润新知