深入理解 - java泛型

泛型:字面意思可理解为广泛得、泛泛地类型,不具体指定为某一种类型;在使用上,则可以理解为类型参数,将类型当做参数传递给一个类、接口或者方法

泛型在哪里使用?

泛型可使用在类、接口以及方法

泛型在类中使用

示例如下:

public class GenericClass<T>{
    private T data;

    public GenericClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}
复制代码

调用如下:

public class GenericClassTest {
    public static void main(String[] args) {
        GenericClass<String> genericClassStr = new GenericClass<String>("generic class test");
        System.out.println(genericClassStr.getData());

        GenericClass<Integer> videoClassInt = new GenericClass<Integer>(123);
        System.out.println(videoClassInt.getData());
    }
}
复制代码

在创建对象的时候,将数据的类型传递给泛型类,标识符“T”则替换成了传递的类型,其中new Video<T>中的"T"可以不写,声明变量中已经写了类型,java提供了良好的类型检查,这里为了方便理解。

控制台输出:

generic class test
123
复制代码
泛型在接口中使用

与泛型在类中使用相似,不做赘述,示例如下:

public interface GenericInterface<T> {
    T getData();
}
复制代码
泛型在方法中使用

示例如下:

public class GenericMethod {
    public <T> String returnString(T data) {
        return "generic method test";
    }

    public <T> T returnGeneric(T data) {
        return data;
    }
}
复制代码

调用如下:

public class GenericMethodTest {
    public static void main(String[] args) {
        GenericMethod genericMethod = new GenericMethod();
        Object objGeneric = genericMethod.returnGeneric(123);
        System.out.println(objGeneric);
        Object objStr = genericMethod.returnString("generic method test");
        System.out.println(objStr);
    }
}
复制代码

在调用方法的时候,将传参的实际数据类型传入泛型方法,作为泛型的真实类型。很多同学对方法签名前面出现"<T> T"表示很疑惑,实际上不难理解,其中"<T>"是泛型声明,就像是上文VideoClass<T>中的"<T>"一样,而后面的"T"则是方法返回值的类型,在decodeData方法中将返回值类型由T改成了String,编译运行依然是正常的。

控制台输出:

123
generic method test
复制代码

用法补充

泛型类与泛型方法结合

泛型方法是可以出现在泛型类中的,他们之间的泛型声明相互独立,互不影响,例如,将上文泛型方法放入泛型类中:

public class GenericClassMethod<T> {
    private T data;

    public GenericClassMethod(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public <E> E returnGeneric(E data) {
        return data;
    }
}
复制代码

泛型方法中泛型声明"T"变成了"E",为啥呢?其实这只是一个变量,不改变也行,只是为了区别于泛型类中的泛型,换一个泛型变量有利于代码阅读。阅读过源码的同学,经常会看到还有"K", "V" 之类的,这些也都只是一个泛型变量,习惯用法如下:

T -- 任何类型
E -- Element 或者 Exception类型
K -- Key 代表键
V -- Value 代码值
S -- Subtype 子类型
复制代码
通配符"?""

除了上述常用泛型变量,还有一个比较特殊的替换符"?",它存在的意义是什么呢?

看代码如下:Base base = sub编译成功,而 List<Base> baseList = subList编译失败。

class Base{}
class Sub extends Base{}

public class WildCardTest {
    public static void main(String[] args) {
        Sub sub = new Sub();
        Base base = sub; // compile successfully
        
        List<Sub> subList = new ArrayList<Sub>();
        List<Base> baseList = subList; // compile failed
    }
}
复制代码

显然,泛型之间是没有继承关系得,自然也无法使用多态了。那么如果真的需要泛型之间的继承关系怎么办呢,通配符"?"应运而生,它表示指定类型范围内的类型范围。

通配符的形式:

  1. : 无限定范围的通配符;
  2. : 有上限的通配符;
  3. : 有下线的通配符;

当涉及到通配符“?”, 那么就与具体的类型操作无关。示范如下:

List<?> wildList = new Arraylist<String>();
wildList.size(); // compile successfully
wildList.add(666); // compile failed;
复制代码

wildList.size()编译通过,而wildList.add(666)编译失败,说明java对通配符只保留了与具体类型无关的操作。

传递多个泛型变量

泛型设计者也没有死板地只允许一种类型的传入,声明多个泛型以英文逗号隔开即可:

public class GenericMulti<T, E>{} // 泛型类
public <K, V> V convertData(K dataVai, V styleVai){} // 泛型方法
复制代码

应用场景及优点

  1. 类中的数据类型可以通过外界传入的方式灵活使用,增加了类的扩展性,以及代码的复用;
  2. 泛型多用于容器类,增加了编译器类型检查,不需要每次赋值都需要强制类型转化;
  3. 代码清晰,利于阅读,一眼就看得出来容器里面存了什么类型的数据;

扩展之类型擦除

类型擦除:泛型只存在于编译期间,在运行期,所有的泛型都会变成Object类型,即具体的类型被擦除了。 示例如下:

public class GenericErase<T> {
    private T data;

    public GenericErase(T data) {
        this.data = data;
    }
}

public class GenericEraseTest {
    public static void main(String[] args) {
        GenericErase<String> genericErase = new GenericErase<>("movie");
        Class clazz = genericErase.getClass();
        System.out.println("genericErase class is " + clazz.getName());

        Field[] fields = clazz.getDeclaredFields();
        for (Field field: fields) {
            System.out.println("field " + field.getName() + "`s type is " + field.getType().getName() );
        }
    }
}
复制代码

控制台输出如下:

genericErase class is com.peng.generic.blog.GenericErase
field data`s type is java.lang.Object
复制代码

由上可知,GenericErase在运行期为GenericErase,而data类型也又泛型T变成了Object

但这只是对类而言的,存入其中的对象依然是原有的具体类型。例如,new ArrayList("demo")在编译期,ArrayList是具有特性类型String的,但是在运行期间,String类型会被擦除,变成Object类型,但已经存入的“demo”对象依然是String类型,用多态的思想理解就是Object obj = "demo", obj依然是java.lang.String类型。
如果在代码运行期间,调用new ArrayList()这段代码,往里面存入Integer数据也是可以的,因为此时String类型被擦除,程序处于运行期间,没有编译器类型检查。那么如何实现呢?

反射:提供了在运行期操作类以及对象的功能,示例如下:

public class GenericClassTest {
    public static void main(String[] args) {
        List<String> listObj = new ArrayList<String>();
        listObj.add("movie");
        // listObj.add(666); compile error

        try {
            Method method = listObj.getClass().getDeclaredMethod("add", Object.class);
            method.invoke(listObj, 666); // add successfully
            System.out.println(listObj.toString());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
复制代码

int类型的字段666也被加入到List<String>中了,控制台输出如下:

[movie, 666]
复制代码

总结

泛型大量出现在框架源码中,显得有些高深莫测,加之平常使用得不多,不甚了解。抽点时间深入学习了一下,也就豁然开朗了。

转载于:https://juejin.im/post/5c6acd0551882562811d4bdd

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值