java泛型特性,你了解多少?

简介: 对java泛型特性的了解,很多时候是从集合对象接触到的,今天小编带大家一起去深入的了解泛型的缘由和使用方式!

01、泛型的由来

小编想告诉大家的是:泛型的产生本质是来源于软件设计!

在软件设计的过程中经常会用到容器类,容器类代码都一样只是数据类型不同,如果能够让一种类型容纳所有类型,就可以实现代码重用,但是没有一种类型可以容纳所有类型,为了解决容器的问题,由此就产生了泛型设计。

由此可见,泛型是一个不确定的参数类型,即“参数化类型”!

泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

那么怎么使用泛型,进行软件设计呢?

02、使用方式

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

2.1、泛型类

泛型类型用于类的定义中,典型的就是各种容器类,如:List、Set、Map。

最普通的自定义泛型类

  1. /**
  2. * 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
  3. * 在实例化泛型类时,必须指定T的具体类型
  4. */
  5. publicclassGeneric<T>{
  6.    /**key这个成员变量的类型为T,T的类型由外部指定*/
  7.    private T key;

  8.    /**泛型构造方法形参key的类型也为T,T的类型由外部指定*/
  9.    publicGeneric(T key){
  10.        this.key = key;
  11.    }

  12.    /**泛型方法getKey的返回值类型为T,T的类型由外部指定*/
  13.    public T getKey(){
  14.        return key;
  15.    }
  16. }

传参方式

  1. //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
  2. //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
  3. Generic<Integer> genericInteger =newGeneric<Integer>(12345678);

  4. //传入的实参类型需与泛型的类型参数类型相同,即为String.
  5. Generic<String> genericString =newGeneric<String>("hello");
  6. System.out.println("泛型测试","key is "+ genericInteger.getKey());
  7. System.out.println("泛型测试","key is "+ genericString.getKey());

输出结果

  1. 泛型测试: key is12345678
  2. 泛型测试: key is hello

定义的泛型类,就一定要传入泛型类型实参么?

并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

举个例子

  1. Genericgeneric=newGeneric("111111");
  2. Generic generic1 =newGeneric(4444);
  3. Generic generic2 =newGeneric(55.55);
  4. Generic generic3 =newGeneric(false);

  5. System.out.println("泛型测试","key is "+generic.getKey());
  6. System.out.println("泛型测试","key is "+ generic1.getKey());
  7. System.out.println("泛型测试","key is "+ generic2.getKey());
  8. System.out.println("泛型测试","key is "+ generic3.getKey());

输出结果

  1. 泛型测试: key is111111
  2. 泛型测试: key is4444
  3. 泛型测试: key is55.55
  4. 泛型测试: key isfalse

总结:

1、泛型的类型参数只能是类类型,不能是简单类型。2、不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。如下:

  1. if(ex_num instanceofGeneric<Number>){  
  2. }

2.2、泛型接口

泛型接口与泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中。

  1. /**
  2. * 定义一个泛型接口
  3. */
  4. publicinterfaceGenerator<T>{
  5.    public T next();
  6. }

当实现泛型接口的类,未传入泛型实参时

  1. /**
  2. * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
  3. *
  4. * 如果不声明泛型,编译器会报错:"Unknown class"
  5. */
  6. publicclassFruitGenerator<T>implementsGenerator<T>{
  7.    @Override
  8.    public T next(){
  9.        returnnull;
  10.    }
  11. }

当实现泛型接口的类,传入泛型实参时

  1. /**
  2. * 传入泛型实参时:
  3. * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
  4. * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
  5. * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
  6. * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
  7. */
  8. publicclassFruitGeneratorimplementsGenerator<String>{

  9.    @Override
  10.    publicStringnext(){
  11.        return'hello world';
  12.    }
  13. }

2.3、泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型 。

  1. /**
  2. * 泛型方法的基本介绍
  3. * @param tClass 传入的泛型实参
  4. * @return T 返回值为T类型
  5. */
  6. public<T> T genericMethod(Class<T> tClass)throwsInstantiationException,
  7.  IllegalAccessException{
  8.        T instance = tClass.newInstance();
  9.        return instance;
  10. }

说明:

1、public 与 返回值中间 <T>非常重要,可以理解为声明此方法为泛型方法。2、只有声明了 <T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法,如get、set。3、 <T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。4、与泛型类的定义一样,此处T可以随便写为任意标识,常见的如 TEKV等形式的参数常用于表示泛型。

方法调用方式

  1. //通过泛型方法,实例化一个Test对象
  2. Object obj = genericMethod(Class.forName("com.test.Test"));

03、其他使用介绍

3.1、泛型通配符

还是举例子,我们知道 IngeterNumber的一个子类,那么问题来了,在使用 Generic<Number>作为形参的方法中,能否使用 Generic<Ingeter>的实例传入呢?

为了弄清楚这个问题,我们使用 Generic<T>这个泛型类做例子:

  1. publicclassGeneric<T>{

  2.    private T key;

  3.    public T getKey(){
  4.        return key;
  5.    }

  6.    publicvoid setKey(T key){
  7.        this.key = key;
  8.    }

  9.    publicGeneric(){
  10.        super();
  11.        // TODO Auto-generated constructor stub
  12.    }

  13.    publicGeneric(T key){
  14.        super();
  15.        this.key = key;
  16.    }
  17. }

测试

  1. publicclassGenericTest{

  2.    publicstaticvoid main(String[] args){
  3.        //编译器会为我们报错:Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
  4.        Generic<Integer> generic1 =newGeneric<Integer>(1);
  5.        newGenericTest().showKeyValue(generic1);
  6.    }

  7.    publicvoid showKeyValue(Generic<Number> obj){
  8.        System.out.println("泛型测试:key value is "+ obj.getKey());
  9.    }
  10. }

通过提示信息我们可以看到 Generic<Integer>不能被看作为 Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

如何解决上面的问题?总不能为了定义一个新的方法来处理 Generic<Integer>类型的类,这显然与java中的多态理念相违背。因此我们需要一个在逻辑上可以表示同时是 Generic<Integer>Generic<Number>父类的引用类型。由此类型通配符应运而生。

将上面的方法改一下:

  1. publicvoid showKeyValue(Generic<?> obj){
  2.       System.out.println("泛型测试:key value is "+ obj.getKey());
  3. }

类型通配符一般是使用 ?代替具体的类型实参。

3.2、泛型上下边界

在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

  • 上界通配符

为泛型添加上边界,即传入的类型实参必须是指定类型的子类型!例如:

  1. publicvoid showKeyValue(Generic<?extendsNumber> obj){
  2.        System.out.println("泛型测试:key value is "+ obj.getKey());
  3. }

测试

  1. publicstaticvoid main(String[] args){
  2.        Generic<Integer> generic1 =newGeneric<Integer>(1);
  3.        //编译器会提示错误,因为String类型并不是Number类型的子类
  4.        Generic<String> generic2 =newGeneric<String>("1");
  5.        newGenericTest().showKeyValue(generic1);
  6.        newGenericTest().showKeyValue(generic2);
  7. }
  • 下界通配符

下界通配符的意思是容器中只能存放T及其T的基类类型的数据,说白了,就是跟上界相反的过程

  1. publicvoid showKeyValue(Generic<?superInteger> obj){
  2.        System.out.println("泛型测试:key value is "+ obj.getKey());
  3. }

测试

  1. publicstaticvoid main(String[] args){
  2.        Generic<Integer> generic1 =newGeneric<Integer>(1);
  3.        Generic<Number> generic2 =newGeneric<Number>(100);
  4.        newGenericTest().showKeyValue(generic1);
  5.        newGenericTest().showKeyValue(generic2);
  6. }

输出结果

  1. 泛型测试:key value is1
  2. 泛型测试:key value is100

最后简单介绍下Effective Java这本书里面介绍的PECS原则。

  • 上界不能往里存,只能往外取,适合频繁往外面读取内容的场景。
  • 下界不影响往里存,但往外取只能放在Object对象里,适合经常往里面插入数据的场景。

是不是听的很懵,没关系,看上面的例子就可以了,哈哈哈!

04、总结

本文中的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性。另外,一提到泛型,相信大家用到最多的就是在集合中,其实,在实际的编程过程中,自己可以使用泛型去简化开发,且能很好的保证代码质量。


相关文章
|
16天前
|
JavaScript Java 编译器
Java包装类和泛型的知识点详解
Java包装类和泛型的知识点的深度理解
|
22天前
|
安全 Java 数据安全/隐私保护
|
22天前
|
搜索推荐 Java
Java的面向对象特性主要包括封装、继承和多态
【4月更文挑战第5天】Java的面向对象特性主要包括封装、继承和多态
15 3
|
1月前
|
Java
java中的泛型类型擦除
java中的泛型类型擦除
13 2
|
1月前
|
人工智能 Java 编译器
Java 19的未来:新特性、性能优化和更多
Java 19的未来:新特性、性能优化和更多
|
2天前
|
安全 Java 大数据
探索Java的奇妙世界:语言特性与实际应用
探索Java的奇妙世界:语言特性与实际应用
|
3天前
|
Java
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
8 0
|
3天前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
15 0
|
9天前
|
机器学习/深度学习 Java API
Java8中的新特性
Java8中的新特性
|
11天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【4月更文挑战第16天】本文将介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁、易读。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而使代码更加简洁、高效。本文将通过实例代码详细讲解这两个新特性的使用方法和优势。