Java基础之泛型
发布日期:2021-05-06 23:31:42 浏览次数:19 分类:技术文章

本文共 4073 字,大约阅读时间需要 13 分钟。

前言

学习泛型之前,要先了解“语法糖”。语法糖是指在计算机语言中添加某种语法,对语言的功能无影响,但是更方便程序员的使用。Java最常见的语法糖包括:泛型、变长参数、条件编译、自动拆装箱、内部类等。虚拟机并不支持这些语法,在编译阶段就被还原为了基础语法结构,这个过程叫做解语法糖。

引入泛型的目的

通过泛型的引入,使得在编译阶段完成一些类型转换工作,避免在运行时因为强制类型转换出现ClassCastException(类型转换异常)

初识泛型

在JDK1.5之前都是这么写代码的,List里面存的都是Object类型数据。任何对象类型都可以放入、取出。

List list = new ArrayList();        list.add("1");        list.add("2");        System.out.println(list.get(0));

但是这么写有两个隐患:

1.当放入一个对象时,由于集合并不知道放入的对象的类型,所以取出的时候对象的编译类型就变成了Object
2.运行时需要人为的强制转换类型到具体目标,很容易出现类型转换异常
所以,泛型出现之后,写法就变了:

List
list = new ArrayList<>(); list.add("1"); list.add("2"); System.out.println(list.get(0));

这就是泛型,可以把类型参数当作使用参数化类型时指定的类型的一个占位符。如此,带来了很多好处。

泛型的好处

1.类型安全,类型错误在编译期间就被捕获到了,而非在运行期间当作ClassCaseException抛出。提高了程序可靠性,方便开发者排查错误

2.消除了代码中的强制类型转换,增强代码可读性
3.优化

泛型的使用

泛型类和泛型接口

泛型的实质即允许在定义接口、类时声明类型形参,类型形参在整个接口、类中可以当作类型使用。

泛型类

定义一个容器类,存放键值对key-value。键值对的类型不确定,使用泛型定义,分别为k和v

public class Container
{
private K key; private V value; public Container(K key, V value) {
this.key = key; this.value = value; } public K getKey() {
return key; } public void setKey(K key) {
this.key = key; } public V getValue() {
return value; } public void setValue(V value) {
this.value = value; }}

在使用这个容器类Container时,只需要指明K/V具体类型即可。这样,创建出逻辑不同的Container实例,存放不同的数据类型

Container
c1 = new Container<>("id","name");Container
c2 = new Container<>(1,"name");

泛型类派生子类

创建了带泛型声明的接口、父类后,可以为该接口创建实现类,或从父类派生子类。但是使用派生子类时,必须得指明具体类型:

public class A extends Container
{
}

如果不指明类型,会默认K、V形参为Object:

public class A extends Container{
}

泛型方法

在类、接口中没有使用泛型,定义方法时想定义类型形参,就会使用泛型方法

public class Main {
public static
void out(T t){
System.out.print(t); } public static void main(String []args){
out("husky"); out(123); }}

通过这个例子,表明泛型方法就是在定义方法时,声明一个或多个类型形参,泛型方法用法格式如下:

修饰符 
返回值类型 方法名(形参列表){
方法体}

需要注意的是,方法中定义的形参只能在方法中使用,类、接口中定义的类型形参可以在整个类、接口中使用

class Demo{
public
T fun(T t){
//可以接受任意类型的数据 return t; }}public class GenerycityDemo {
public static void main(String []args){
Demo demo = new Demo(); String str = demo.fun("husky"); Integer i = demo.fun(123); System.out.println(str); System.out.println(i); }}

调用fun方法,传入实际的对象,编译期就会判断出类型形参T所代表的类型

泛型构造器

public class Person {
public
Person(T t) {
System.out.print(t); }}
public class Main {
public static void main(String []args){
new Person(222);//隐式 new
Person("husky");//显示 }}

这里如果构造器是泛型构造器,类也是泛型类。此时构造器可以显示指定类型参数(构造器前面放<>),泛型类的类型实参也需要指定(构造器后放<>),出现两个“<>”就出问题了:

public class Person
{
public
Person(T t) {
System.out.print(t); }}
//Person
person = new
Person <> (222);这样就编译出错Person
person = new
Person(222);

类型通配符

?,匹配任意类型的类型实参。List<?>,代表List元素类型未知。

public void test(List
list){
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i)); } }

此时可以传入任意类型的List调用test方法:

List
list = new ArrayList
();list.add(new Object);//报错

但是不能把元素添加进去,程序无法确定集合中元素的类型

带限通配符

使用通配符的目的是为了限制泛型的类型参数的类型,使其满足条件。主要分为:上限通配符和下限通配符

上限通配符

如果想限制使用泛型类别时,只能用某特定类型或其子类型,才能实例化该类型时,可以在定义类型时,可以用extends关键字指定这个类型必须继承某各类,或实现某个接口,也可以是这个类或接口本身。

//表明集合中的类型都是shape类型或者子类型,Circle是其子类List
list = new ArrayList
();

下限通配符

如果想限制使用泛型类别时,只能用某特定类型或其子类型,才能实例化该类型时,可以在定义类型时,可以用super关键字指定这个类型必须是某各类的父类,或是某个接口的父接口,也可以是这个类或接口本身。

//Shape是其父类List
list = new ArrayList
();

类型擦除

Class c1 = new ArrayList
().getClass();Class c2 = new ArrayList
().getClass();System.out.print(c1==c2); //输出true

不管为泛型的类型参数传入那种类型实参,对java而言都会被当做同一类处理,在内存中也只占用一块内存空间。泛型只作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,将会把泛型的相关信息擦除,即成功编译过后的class文件是不包含有任何泛型信息的,泛型信息也不会进入到运行时阶段。

在静态方法、静态初始化块或者静态变量的声明和初始化中,不允许使用类型形参。并且系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类

上一篇:Java基础之反射
下一篇:设计模式之观察者模式学习笔记

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2025年03月20日 19时21分35秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章