资讯专栏INFORMATION COLUMN

Java泛型

lauren_liuling / 3041人阅读

摘要:通过方法返回值来确定具体泛型类型。泛型类定义的泛型声明在整个类定义中都有效。相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。

首先,明确一点的是:java的泛型是擦除的。
即系,泛型只存在于编译时,编译后都是Object类型(也不全是,见后面解析,运行时也能获得一定的泛型信息)。

泛型对于Java编程的作用:

最主要:增加编译时的类型安全检查。
其次:你说它能做到抽取不同类型的共同代码的话,可能就只适用于容器类。容器类中适合存放不同的类型,而且不管存放的类型是什么都适用。但是要求存放的类型是要一致。

泛型的所做的实质工作:
编译时,在泛型类的入口(如:方法参数),会进行编译时的类型安全检查(子类,父类关系检查,是否是安全的向上转型)。
而且编译器会在编译时增加语法糖,对泛型类的出口(如: 返回值),编译器会对字节码自动增加类型转换的代码。

编译前的泛型类:

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("hello", "你好");
    map.put("how are you?", "吃了没?");
    System.out.println(map.get("hello"));
    System.out.println(map.get("how are you?"));
}

把上面的代码编译成class文件后,再用字节码反编译工具进行反编译后:

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("hello", "你好");
    map.put("how are you?", "吃了没?");
    System.out.println((String) map.get("hello"));
    System.out.println((String) map.get("how are you?"));
}

在运行时,
(重要!)总结:擦除法所谓的擦除,仅仅是对字节码中方法中的方法体的Code属性中的字节码进行擦除(即方法体中的代码没有泛型),但是实际上的元数据(类定义,方法定义,字段定义)的Signature中还是保存着 具体参数化类型 信息。
上面描述的具体参数化类型信息,可以供用户查询得到。
(如:类定义MyList implements List 中, 在MyList字节码的类定义中会保存 具体参数化类型信息----(String) )。

在编译时,
我们知道编译器时掌握泛型信息的。
那么问题剩下: 编译器如何依据泛型信息在编译时进行类型安全检查。

第一点:
在(泛型类定义,泛型接口方法和函数)定义中,编译器如何确定具体泛型类型。

对于泛型类定义:
(通常一个变量引用的声明类型来决定,或者实例化对象时指定)
如果泛型类定义是(如:public interface Map{} ),
现在有一个变量声明,Map map; 那么这时编译器就认定 map变量的K的具体类型为String, V的具体类型为Integer。
又如,有一个实例化对象 new HashMap();那么这时编译器就认定 该对象的K的具体类型为String, V的具体类型为Integer。

对于 泛型接口方法和函数(包括实例化对象时的构造函数) :
编译器确定具体泛型类型是通过方法签名中的 方法参数 或者 方法返回值。
(1)通过 方法参数 来确定 具体泛型类型。
void java.util.Collections.sort(List list, Comparator c)
(2)通过 方法返回值 来确定 具体泛型类型。
List java.util.Collections.emptyList()
(3)构造函数
java.util.ArrayList.ArrayList(Collection c)

第二点:
在(泛型类定义,泛型接口方法和函数)中的泛型声明在编译器中的作用域(泛型声明的影响范围)和对应关系。

泛型类定义的泛型声明在整个类定义中都有效。
如果在泛型类定义中有泛型声明,(如:public interface Map{} ),则整个类定义中有如果出现 A 或者 B 的地方都会跟随类定义的泛型声明

泛型接口方法和函数的泛型声明在方法体和方法签名有效。如果在泛型接口方法和函数上声明了自己的泛型声明则独立开辟一个新的作用域,与类定义的泛型声明区分开。

public class CopyOfGenericCollection {
    
    // 在泛型接口方法和函数中新开辟一个泛型声明作用域
    // 这里的泛型声明A和B,与类定义中的A和B 是没有关系。
    public  Map unmodifiableMap1(Map m) {
        return null;
    }
    
    // 沿用泛型类定义的泛型声明
    public Map unmodifiableMap2(Map m) {
        return null;
    }
}

// 测试

public class Test {
    public static void main(String[] args) {
        CopyOfGenericCollection extend = new CopyOfGenericCollection();
        
        // 可以看到map1变量的类型为Map
        Map map1 = extend.unmodifiableMap1(null);
        
        // 可以看到map2变量的类型为Map
        Map map2 = extend.unmodifiableMap2(null);
    }
}

最后
编译的类型安全检查,最基本的就是类型转换,我们知道类型向上转型是安全。
所以要使包含泛型的代码中通过编译器的编译,我们要求赋值语句的都是,子类赋值父类,这就引入了 泛型系统的类型继承关系。

相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。
即List是Collection 的子类型,List可以替换Collection。这种情况也适用于带有上下界的类型声明。

当泛型类的类型声明中使用了通配符的时候, 其子类型可以在两个维度上分别展开。
如对Collection来说,其子类型可以在Collection这个维度上展开,即List和Set等;
也可以在Number这个层次上展开,即Collection和 Collection等。
如此循环下去,ArrayList和 HashSet等也都算是Collection的子类型。
如果泛型类中包含多个类型参数(如:Map),则对于每个类型参数分别应用上面的规则。

Collection col_number = null;
List list_number = null;
Set set_number = null;
Collection col_double = null;
Collection col_integer = null;
HashSet hashSet_double = null;
ArrayList arrayList_integer = null;

col_number = list_number;
col_number = set_number;
col_number = col_double;
col_number = col_integer;
col_number = hashSet_double;
col_number = arrayList_integer;

Collection col_number_super = null;
Collection col_list_super = null;
Collection col_col_super = null;
Collection col_arrayList_super = null;

// col_number_super = col_number;// compile error
// col_number = col_number_super;// compile error
col_number_super = (Collection) col_number;
col_number = (Collection) col_number_super;
col_list_super = col_col_super;
// col_col_super = col_list_super; // compile error
// col_list_super = col_arrayList_super; // compile error

Map map_generic = null;
Map map_number_list = null;
Map map_integer_arrayList = null;
Map map_integer_col = null;
Map map_generic_super = null;
map_generic = map_number_list;
map_generic = map_integer_arrayList;
// map_generic = map_integer_col; // compile error
// map_generic = map_generic_super; // compile error
// map_generic_super = map_generic; // compile error

Map map_generic_list_super = null;
Map map_generic_col_super = null;
map_generic_list_super = map_generic_col_super;
// map_generic_col_super = map_generic_list_super; // compile error

下面是摘录自知乎的见解:
https://www.zhihu.com/questio...

PECS原则
最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:
1.频繁往外读取内容的,适合用上界Extends。
2.经常往里插入的,适合用下界Super。

• 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
• 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
• 如果既要存又要取,那么就不要使用任何通配符。

什么是上界?
下面代码就是“上界通配符(Upper Bounds Wildcards)”:
Plate<? extends Fruit>
翻译成人话就是:一个能放水果以及一切是水果派生类的盘子。再直白点就是:啥水果都能放的盘子。
Plate<? extends Fruit>是Plate以及Plate的基类。
在这个体系中,上界通配符 “Plate<? extends Fruit>” 覆盖下图中蓝色的区域。

什么是下界?
相对应的,“下界通配符(Lower Bounds Wildcards)”:
Plate<? super Fruit>
表达的就是相反的概念:一个能放水果以及一切是水果基类的盘子。Plate<? super Fruit>是Plate的基类,但不是Plate的基类。对应刚才那个例子,Plate<? super Fruit>覆盖下图中红色的区域。在这个体系中,上界通配符

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70773.html

相关文章

  • java编程思想》—— 泛型

    摘要:引用泛型除了方法因不能使用外部实例参数外,其他继承实现成员变量,成员方法,方法返回值等都可使用。因此,生成的字节码仅包含普通的类,接口和方法。 为什么要使用泛型程序设计? 一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义类的对应类型;如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----摘自原书Ordinary classes and meth...

    CODING 评论0 收藏0
  • Java知识点总结(Java泛型

    摘要:知识点总结泛型知识点总结泛型泛型泛型就是参数化类型适用于多种数据类型执行相同的代码泛型中的类型在使用时指定泛型归根到底就是模版优点使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。 Java知识点总结(Java泛型) @(Java知识点总结)[Java, Java泛型] [toc] 泛型 泛型就是参数化类型 适用于多种数据类型执行相同的代码 泛型中的类型在使用时指定 泛...

    linkin 评论0 收藏0
  • 聊聊Java泛型及实现

    摘要:静态变量是被泛型类的所有实例所共享的。所以引用能完成泛型类型的检查。对于这个类型系统,有如下的一些规则相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。事实上,泛型类扩展都不合法。 前言 和C++以模板来实现静多态不同,Java基于运行时支持选择了泛型,两者的实现原理大相庭径。C++可以支持基本类型作为模板参数,Java却只能接受类作为泛型参数;Java可以在泛型类的方法中取得...

    lewif 评论0 收藏0
  • 浅析Java泛型

    摘要:泛型类在类的申明时指定参数,即构成了泛型类。换句话说,泛型类可以看成普通类的工厂。的作用就是指明泛型的具体类型,而类型的变量,可以用来创建泛型类的对象。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,...

    godiscoder 评论0 收藏0
  • 初探Java类型擦除

    摘要:可以看到,如果我们给泛型类制定了上限,泛型擦除之后就会被替换成类型的上限。相应的,泛型类中定义的方法的类型也是如此。参考语言类型擦除下界通配符和的区别 本篇博客主要介绍了Java类型擦除的定义,详细的介绍了类型擦除在Java中所出现的场景。 1. 什么是类型擦除 为了让你们快速的对类型擦除有一个印象,首先举一个很简单也很经典的例子。 // 指定泛型为String List list1 ...

    DevTalking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<