Java 泛型详解

在日常的开发中,我们会看到别人的框架很多地方会使用到泛型,泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。本篇博客我们就来详细解析一下泛型的知识。

泛型类定义及使用

使用泛型有什么好处呢?首先我们先看一个例子,假设我们有两个类,代码如下:

#StringClass 

public class StringClass {

    private String x ;

    private String y ;

    public String getY() {

        return y;

    }

    public void setY(String y) {

        this.y = y;

    }

    public String getX() {

        return x;

    }

    public void setX(String x) {

        this.x = x;

    }

}

#IntClass 

public class IntClass {

    private int x ;

    private int y ;

    public int getY() {

        return y;

    }

    public void setY(int y) {

        this.y = y;

    }

    public int getX() {

        return x;

    }

    public void setX(int x) {

        this.x = x;

    }

}

观察上面两个类StringClass 和IntClass,他们除了变量类型不一样,一个是String一个是int以外,其它并没有什么区别!那我们能不能合并成一个呢?通过泛型就可以解决,首先看一下泛型的类是怎么定义的:

public class ObjClass<T> {

    private T x ;

    private T y ;

    public T getX() {

        return x;

    }

    public void setX(T x) {

        this.x = x;

    }

    public T getY() {

        return y;

    }

    public void setY(T y) {

        this.y = y;

    }

}

那么这时候上面的两个类就可以通过泛型的设置,相应生成

ObjClass<String> stringClass = new ObjClass<String>();

        stringClass.setX("haha");

        ObjClass<Integer> intClass = new ObjClass<Integer>();

        intClass.setX(100);

        Log.d("yyy", "stringClass:" + stringClass.getX() + ",intClass:" + intClass.getX());

从结果中可以看到,我们通过泛型实现了开篇中StringClass类和IntClass类的效果。

接下来介绍泛型如何定义及使用:

1.首先需要定义泛型:ObjClass

ObjClass ,即在类名后面加一个尖括号,括号里是一个大写字母。这里写的是T,其实这个字母可以是任何大写字母,无论使用哪个字母,意义都是相同的。如果你想学习Java可以来这个群,首先是二二零,中间是一四二,最后是九零六,里面有大量的学习资料可以下载

2.在类中使用泛型

这个T表示派生自Object类的任何类,比如String,Integer,Double等等。这里要注意的是,T一定是派生于Object类的。

private T x ;

    private T y ;

    public T getX() {

        return x;

    }

    public void setX(T x) {

        this.x = x;

    }

    public T getY() {

        return y;

    }

    public void setY(T y) {

        this.y = y;

    }

3.使用泛型类

泛型类的使用代码如下:

ObjClass<String> stringClass = new ObjClass<String>();

        stringClass.setX("haha");

        ObjClass<Integer> intClass = new ObjClass<Integer>();

        intClass.setX(100);

首先,需要构造一个实例:

ObjClass<String> stringClass = new ObjClass<String>();

泛型类的构造则需要在类名后添加上,即一对尖括号,中间写上要传入的类型。

因为我们构造时,是这样的:ObjClass,所以在使用的时候也要在ObjClass后加上类型来定义T代表的意义。

尖括号中,你传进去的是什么,T就代表什么类型。这就是泛型的最大作用,我们只需要考虑逻辑实现,就能拿给各种类来用。

多泛型变量定义

1.多泛型变量定义

我们不止可以在类中设置一个泛型变量T,还可以声明多个泛型变量,写法如下:

public class ObjClass<T,U>

也就是在原来的T后面用逗号隔开,写上其它的任意大写字母即可,如果还有多个,依然使用逗号分隔开即可,则我们前面定义的泛型类就会变成下面这样:

public class ObjClass<T,U> {

    private T x ;

    private U y ;

    public T getX() {

        return x;

    }

    public void setX(T x) {

        this.x = x;

    }

    public U getY() {

        return y;

    }

    public void setY(U y) {

        this.y = y;

    }

}

ObjClass<String,Integer> stringClass = new ObjClass<String,Integer>();

        stringClass.setX("haha");

        stringClass.setY(100);

从上面的代码中,可以明显看出,就是在新添加的泛型变量U用法与T是一样的。

2.泛型的字母规范

虽然在类中声明泛型任意字母都可以,但为了可读性,最好遵循以下的规范:

E — Element,常用在java Collection里,如:  List<E>,Iterator<E>,Set<E>

 K,V — Key,Value,代表Map的键值对

 N — Number,数字

 T — Type,类型,如String,Integer等等

泛型接口定义及使用

在接口上定义泛型与在类中定义泛型是一样的,代码如下:

interface MsgClass<T> {

    public T getMsg() ;

    public void setMsg(T x);

}

我们可以利用泛型类来构造填充泛型接口

public class Message<T,U> implements MsgClass<T>{

    private T msg;

    @Override

    public T getMsg() {

        return msg;

    }

    @Override

    public void setMsg(T msg) {

        this.msg = msg;

    }

}

在这个类中,我们构造了一个泛型类Message,然后把泛型变量T传给了MsgClass,这说明接口和泛型类使用的都是同一个泛型变量。

我们还可以构造一个多个泛型变量的类,并继承自MsgClass接口:

public class Message<T,U> implements MsgClass<T>{

    private U name;

    private T msg;

    @Override

    public T getMsg() {

        return msg;

    }

    @Override

    public void setMsg(T msg) {

        this.msg = msg;

    }

    public U getName() {

        return name;

    }

    public void setName(U name) {

        this.name = name;

    }

}

泛型函数定义及使用

我们不但可以在类声明中使用泛型,还可以在函数声明中也使用泛型,使用如下:

public class ObjClass {

    //静态函数

    public static <T> void StaticMethod(T a) {

    }

    //普通函数

    public <T> void OrgnicMethod(T a) {

    }

}

上面分别是静态泛型函数和常规泛型函数的定义方法,与以往方法的唯一不同点就是在返回值前加上来表示泛型变量。

无论哪种泛型方法都有两种使用方法:

//静态方法

ObjClass.StaticMethod("adfdsa");//使用方法一

ObjClass.<String>StaticMethod("adfdsa");//使用方法二

//常规方法

ObjClass objClass = new ObjClass();

objClass.OrgnicMethod(new Integer(111));//使用方法一

objClass.<Integer>OrgnicMethod(new Integer(111));//使用方法二

方法一,隐式传递了T的类型,这种隐式的传递方式,代码不利于阅读和维护。因为从外观根本看不出来你调用的是一个泛型函数。

方法二,例如上面例子中,将T赋值为Integer类型,这样OrgnicMethod(T a)传递过来的参数如果不是Integer那么编译器就会报错。

当然泛型函数的返回值也可以使用泛型表示:

public static <T> List<T> parseArray(String response,Class<T> object){  

    List<T> modelList = JSON.parseArray(response, object);  

    return modelList;  

}

函数返回值是List类型。和void的泛型函数不同,有返回值的泛型函数要在函数定义的中在返回值前加上标识泛型;还要说明的是,上面中,使用Class传递泛型类Class对象

泛型数组

泛型同样可以用来定义在数组上

//定义  

        public static <T> T[] fun1(T...msg){  // 接收可变参数    

            return msg ;            // 返回泛型数组    

        }

        //使用  

        public static void main(String args[]){

            Integer i[] = fun1(8,9,8,44) ;

            Integer[] result = fun1(i) ;

        }

定义了一个静态函数,然后定义返回值为T[],参数为接收的T类型的可变长参数。

泛型的通配符

在开发中对象的引用传递(向上向下传递)是最常见的,但是,在泛型的操作中,在进行引用传递的时候泛型类型必须匹配才可以传递,否则不能传递。

例如,如下没有进行泛型类型匹配,一个是String,一个是Object类型。

class Info<T>{

    private T var ;        // 定义泛型变量

    public void setVar(T var){

        this.var = var ;

    }

    public T getVar(){

        return this.var ;

    }

    public String toString(){   

        return this.var.toString() ;

    }

};

public class demo1 {

        public static void main(String args[]) {

            // 使用String为泛型类型

            Info<String> i = new Info<String>();        

            i.setVar("ABCD");

            //把String泛型类型的i对象传递给Object泛型类型的temp。

            fun(i);                   

        }

        // 接收Object泛型类型的Info对象

        public static void fun(Info<Object> temp) {        

            System.out.println("内容:" + temp);

        }

    }

编译发生错误。

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 

    The method fun(Info<Object>) in the type demo1 is not applicable for the arguments (Info<String>)

    at Thread1.demo1.main(demo1.java:18)

泛型对象进行引用传递的时候,类型必须一致,如果非要传递,则可以将fun方法中Info参数的泛型取消掉(变成 void fun(Info temp))。、

以上确实改进了功能,但是似乎不是很妥当,毕竟之前指定过泛型。

以上程序在fun()方法中使用 "Info<?>" 的代码形式,表示可以使用任意的泛型类型对象,这样的话fun()方法定义就合理了,但是使用以上方法也有需要注意的地方,

即:如果使用“?“接收泛型对象的时候,则不能设置被泛型指定的内容。

class Info<T>{

    private T var ;        

    public void setVar(T var){

        this.var = var ;

    }

    public T getVar(){

        return this.var ;

    }

    public String toString(){    

        return this.var.toString() ;

    }

};

public class GenericsDemo{

    public static void main(String args[]){

        Info<String> i = new Info<String>() ;       

        i.setVar("ABCD") ;                            

        fun(i) ;

    }

    public static void fun(Info<?> temp){        

        System.out.println("内容:" + temp) ;

    }

};

如果使用”?“意味着可以接收任意的内容,但是此内容无法直接使得用”?“修饰的泛型的对象进行修改。如下就会出问题:

class Info<T>{

    private T var ;        

    public void setVar(T var){

        this.var = var ;

    }

    public T getVar(){

        return this.var ;

    }

    public String toString(){   

        return this.var.toString() ;

    }

};

public class demo1{

    public static void main(String args[]){

        Info<?> i = new Info<String>() ;       

        i.setVar("ABCD") ;                            

    }

};

运行结果:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 

    The method setVar(capture#1-of ?) in the type Info<capture#1-of ?> is not applicable for the arguments (String)

    at Thread1.demo1.main(demo1.java:17)

在使用”?“只能接收,不能修改。

泛型的上限

class Info<T>{

    private T var ;        

    public void setVar(T var){

        this.var = var ;

    }

    public T getVar(){

        return this.var ;

    }

    public String toString(){    

        return this.var.toString() ;

    }

};

public class GenericsDemo{

    public static void main(String args[]){

        Info<Integer> i1 = new Info<Integer>() ;        

        Info<Float> i2 = new Info<Float>() ;            

        i1.setVar(30) ;                                    

        i2.setVar(30.1f) ;                                

        fun(i1) ;

        fun(i2) ;

    }

    public static void fun(Info<? extends Number> temp){    // 只能接收Number及其Number的子类

        System.out.print(temp + "、") ;

    }

};

运行成功。但是,如果传入的泛型类型为String的话就不行,因为String不是Number子类。

在类中使用泛型上限。

class Info<T extends Number>{    // 此处泛型只能是数字类型

    private T var ;        

    public void setVar(T var){

        this.var = var ;

    }

    public T getVar(){

        return this.var ;

    }

    public String toString(){   

        return this.var.toString() ;

    }

};

public class demo1{

    public static void main(String args[]){

        Info<Integer> i1 = new Info<Integer>() ;        // 声明Integer的泛型对象

    }

};

如果在使用Info的时候设置成String类型,则编译的时候将会出现错误(String不是Number子类)

注意:利用 <? extends Number> 定义的变量,只可取其中的值,不可修改

原因如下:

因为Info的类型为 Info

泛型的下限

<? super XXX> 表示填充为任意XXX的父类

class Info<T>{

    private T var ;        

    public void setVar(T var){

        this.var = var ;

    }

    public T getVar(){

        return this.var ;

    }

    public String toString(){    

        return this.var.toString() ;

    }

};

public class GenericsDemo21{

    public static void main(String args[]){

        Info<String> i1 = new Info<String>() ;        // 

        Info<Object> i2 = new Info<Object>() ;        // 

        i1.setVar("hello") ;

        i2.setVar(new Object()) ;

        fun(i1) ;

        fun(i2) ;

    }

    public static void fun(Info<? super String> temp){    // 只能接收String或Object类型的泛型,String类的父类只有Object类

        System.out.print(temp + "、") ;

    }

};

Object类和String类都是String的父类,所有运行成功,但是如果此时用Integer则会出错,因为integer并不是String父类。

注意:使用super通配符:能存不能取

如何理解呢?假设有3个类,继承关系如下:

class CEO extends Manager {  

}  

class Manager extends Employee {  

}  

class Employee {  

}

然后书写如下代码:

List<? super Manager> list;  

list = new ArrayList<Employee>();  

//存  

list.add(new Employee()); //编译错误  

list.add(new Manager());  

list.add(new CEO());

为什么而list.add(new Employee());是错误的?

因为list里item的类型是

List<Employee> list = new ArrayList<Employee>();  

list.add(new Manager());  

list.add(new CEO());

在这里,正因为Manager和CEO都是Employee的子类,在传进去list.add()后,会被强制转换为Employee!

现在回过头来看这个:

List<? super Manager> list;  

list = new ArrayList<Employee>();  

//存  

list.add(new Employee()); //编译错误  

list.add(new Manager());  

list.add(new CEO());

编译器无法确定 <? super Manager> 的具体类型,但唯一可以确定的是Manager()、CEO()肯定是 <? super Manager> 的子类,所以肯定是可以add进去的。但Employee不一定是 <? super Manager> 的子类,所以不能确定,不能确定的,肯定是不允许的,所以会报编译错误。

最后强调一下, List<? super Manager> list取出的只能是Object 类型,这里虽然看起来是能取的,但取出来一个Object类型,是毫无意义的。所以才有了“super通配符:能存不能取”的结论。

总结1)使用?可以接收任意泛型对象。

2)泛型的上限:?extends 类型(能取不能存)。

3)泛型的下限:?super 类型? super 通配符(能存不能取)。

时间: 2024-12-22 06:35:07

Java 泛型详解的相关文章

Java泛型详解_java

1. Why --引入泛型机制的原因     假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小可以改变的Date对象数组,这时我们当然希望能够重用之前写过的那个针对String对象的ArrayList实现.     在Java 5之前,ArrayList的实现大致如下: public class ArrayList { public Object get(int i) { ... }

Java泛型详解(上)

一. 什么是泛型 泛型是一种程序设计手段(机制),使用泛型可以让你的代码被很多不同类型的对象所重用,提高代码的重用性,还可以提高代码的可读性和安全性 比如,我们经常使用的ArrayList类,就是一个泛型类,也正因如此,它可以接受很多不同类型的对象 /* 可以根据需要存储不同类型的对象 */ ArrayList<Integer> arraylist = new ArrayList<Integer>(); ArrayList<String> arraylist = new

Java泛型详解

一 概念 1.1 为什么需要泛型?           当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,该对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型.因此,取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现"java.lang.ClassCastException"异常.使用泛型就可以解决此类问题. 1.2 什么是泛型?         泛型(Generic type 或者 generics)是对 Java 语

Java泛型详解(下)

九. 泛型类型的继承规则 假设现在有一个类Employee和它的子类Manager 现在问题来了: Pair<Manager>是Pair<Employee>的子类吗? 答案是:不是 例如,下面的代码将不会编译成功: Manager[] topHonchos = ...; Pair<Employee> result = ArrayAlg.minmax(topHonchos); //Error //minmax方法返回Pair<Manager>, 而不是Pair

Java设计模式详解之门面模式(外观模式)_java

门面模式(Facade Pattern)也叫外观模式,它隐藏系统的复杂性,并向客户端提供一个可以访问系统的接口.这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性,为子系统中的一组接口提供了一个统一的高层访问接口,这个接口使得子系统更容易被访问或使用.这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用. 简而言之,就是把一堆复杂的流程封装成一个接口供给用户更简单的使用,这个设计模式里有三个角色: 1)门面角色( facade ):

Java虚拟机详解----JVM常见问题总结

[正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考本人之前的系列文章,尤其是那篇:Java虚拟机详解04----GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾回收.类加载机制.   先把本文的目录画一个思维导图:(图的源文件在本文末尾)   一.Java引用的四种状态: 强引用: 用的最广.我们平时写代码时,new一个Object存放在堆内存,然后用一个引用指向它,这就是强引用. 如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java

java关键字(详解)

基本类型 1 boolean 布尔型 2 byte 字节型 3 char 字符型 4 double 双精度 5 float 浮点 6 int 整型 7 long 长整型 8 short 短整型 9 null 空 10 true 真 11 false 假 程序控制语句 1 break 跳出中断 2 continue 继续 3 return 返回 4 do 运行 5 while 循环 6 if 如果 7 else 否则 8 for 循环 9 instanceof 实例 10 switch 观察 11

Java NIO 详解(一)

NIO即新的输入输出,这个库是在JDK1.4中才引入的.它在标准java代码中提供了高速的面向块的IO操作. 一.基本概念描述 1.1 I/O简介 I/O即输入输出,是计算机与外界世界的一个借口.IO操作的实际主题是操作系统.在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通过stream对象一次移动一个字节.流IO负责把对象转换为字节,然后再转换为对象. 关于Java IO相关知识请参考我的另一篇文章:Java IO 详解 1.2 什么是NIO NIO即New

HBase Java API详解

[本文转自HBase Java API详解] HBase是Hadoop的数据库,能够对大数据提供随机.实时读写访问.他是开源的,分布式的,多版本的,面向列的,存储模型. 在讲解的时候我首先给大家讲解一下HBase的整体结构,如下图: HBase Master是服务器负责管理所有的HRegion服务器,HBase Master并不存储HBase服务器的任何数据,HBase逻辑上的表可能会划分为多个HRegion,然后存储在HRegion Server群中,HBase Master Server中存