资讯专栏INFORMATION COLUMN

认识Java泛型

nanchen2251 / 3369人阅读

摘要:在这种情况下,编译器认为调用泛型方法后,其返回值被赋给一个类型的变量显示地指明类型参考资料编程的逻辑

一、泛型的概念

泛型实现了参数化类型的概念,使代码可以用于多种类型

二、泛型的目的

希望类和方法能够具备最广泛的表达能力

用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性

三、泛型的使用

普通泛型类

public class NormalGenericsClass {
    private T key;
    public Generics(T key){
        this.key = key;
    }
    public T getKey() {
        return key;
    }
    public void setKey(T key) {
        this.key = key;
    }
}


// 泛型类实例
NormalGenericsClass a = new NormalGenericsClass(0);
// Java7 开始支持省略后面的参数类型
NormalGenericsClass a = new NormalGenericsClass<>(0);

普通泛型接口

// Java中的 Comparator接口
public interface Comparator {

    int compare(T o1, T o2);

    boolean equals(Object obj);
}

// 接口实现例子 java.text.Collator
public abstract class Collator implements java.util.Comparator, Cloneable
{
    //...主体代码
}

普通泛型方法

private  int normalGenericsMethod(Generics i){ 
//  表示声明 T为泛型,写在返回值类型之前
// 也可以声明多个泛型,如:
    return 0;
}

// 这是错误的,会提示 Cannot resolve symbol "T"
/*private int normalGenericsMethod(Generics i){ 
        return 0;
}*/

有上界的泛型:
上界:通过关键字extends来表示给定的上界,那么参数就必须是给定的上界或者其子类型。上界可以是某个具体的类或接口,也可以是其他参数

上界为具体的类

public class GenericsUpperBound extends NormalGenericsClass {
    public GenericsUpperBound(T key){
        super(key);
    }
}

上界为具体的接口

public  T compareWith(T[] arr){
    T start = arr[0];
    for(int i = 1; i < arr.length; i++){
        System.out.print(start.equals(arr[i]));
    }
    return start;
}

上界为其他类型参数

public class OtherUpperBound{
    public  void otherArgs(NormalGenericsClass a){
        // E是OtherUpperBound的类型参数,T是otherArgs方法的类型参数
        // T的上界限定为E
    }
}

//例子:
OtherUpperBound a = new OtherUpperBound<>;
OtherUpperBound b = new OtherUpperBound<>;
a.otherArgs(b);

四、通配符

有限定通配符

//重写6中的方法
public void otherArgs(NormalGenericsClass a){
   
}
取自《Java编程的逻辑》8.2  
的区别
:用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面
:用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个类型是未知的,只知道是E或E的某个子类型

无限定通配符

public int demo(OtherUpperBound a){
    
}
//  这两个等效
public  int demo(OtherUpperBound a){
    
}

通配符重要限制: 只能读,不能写

取自《Java编程的逻辑》8.2  
总结:
1) 通配符形式都可以用类型参数的形式来替代,通配符能做的,用类型参数都能做
2) 通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好,所以,能用通配符的就用通配符
3) 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数
4) 通配符形式和类型参数往往配合使用,定义必要的类型参数,使用通配符表达依赖,并接受更广泛的数据类型

超类型通配符(无法用类型参数替代)

public int demo(OtherUpperBound a){
    
}
取自《Java编程的逻辑》8.2   
总结:
1)通配符的目的是为了使方法接口更为灵活,可以接受更为广泛的类型
2)用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象,它不能被类型参数形式替代
3)用于灵活读取,使得方法可以读取E或E的任意子类型的容器对象,它们可以用类型参数的形式替代,但通配符形式更为简洁

五、泛型的局限性

父类实现了一个泛型接口,子类希望自定义泛型接口中的方法,只能重写父类的实现

class Base implements Comparable

class Child extends Base

// 希望重写Comparable的比较方法
/*
    class Child extends Base implements Comparable
    // 错误,因为类型擦除的原因,实际实现的都是Comparable接口,接口不允许被实现两次
*/

// 正确重写Comparable的比较方法的方式
class Child extends Base {
    @Override
    public int compareTo(Base o){
        if(!(o instanceof Child){
            throw new IllegalArgumentException();
        }
        Child c = (Child)o;
        实现代码
        return 0;
    }
    其他代码
}

类型参数不能作为静态变量和静态方法的类型

Class Normal{
    public static demo1(T param){
        // 错误的方法
    }
    public static  void demo2(E param){
        // 正确的方法
    }
}

不能通过类型参数创建对象

T a = new T(); // error Type parameter "T" cannot be instantiated directly

六、泛型的使用细节

Java中的泛型是通过类型擦除实现的,类型参数在编译时会被替换为Object

对于不同传入的类型实参,生成的相应对象实例一样

Generics demo = new Generics<>(123);
Generics demo2 = new Generics<>("test");
System.out.println(demo.getClass() == demo2.getClass()); // true

静态方法和泛型
静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

/**
  如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
  即使静态方法要使用泛型类中已经声明过的泛型也不可以。
  如:public static void show(T t){..},此时编译器会提示错误信息
     "StaticGenerator cannot be refrenced from static context"
*/
public static  void show(T t){
}

泛型的上下边界添加,必须与泛型的声明一起

//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,  
//即在泛型声明的时候添加
//public  T showKeyName(Generic container)
编译器会报错:"Unexpected bound"
public  T showKeyName(Generic container){
    System.out.println("container key :" + container.getKey());
    T test = container.getKey();
    return test;
}

基本类型不能用于实例化类型参数
泛型要求能包容的是对象类型,基本类型在java中不属于对象

运行时类型查询只适用于原始类型

NormalGenericsClass a = new NormalGenericsClass<>(0);

a instanceof NormalGenericsClass;
// Error Illegal generic type for instanceof

a instanceof NormalGenericsClass;
// Error Cannot resolve symbol "T"

a instanceof NormalGenericsClass; // Pass
a instanceof NormalGenericsClass; // Pass

a.getClass(); // class com.example.demo.generics.NormalGenericsClass

类型推断只对赋值操作有效
Eg:

public class New{
    public static  Map map(){
        return new HashMap();
    }
}

/**
       -- 方法中传参
 这时编译器不会执行类型判断。在这种情况下,编译器认为:调用泛型方法后,  
 其返回值被赋给一个Object类型的变量
*/
public class Test{
    public static void main(String args[]){
        fun(New.map());
    }
}

public class Test{
    public static void main(String args[]){
        fun(New.

参考资料:
[1]《Java编程的逻辑》
[2]《Thinking in Java》
[3] https://blog.csdn.net/s10461/...
[4] https://www.cnblogs.com/lwbqq...

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

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

相关文章

  • 好文章必读 - 收藏集 - 掘金

    摘要:方法即为收集器,它接收高阶函数和的后端掘金年的第一天,我坐在独墅湖边,写下这篇文章。正因如此,所以最全系列教程后端掘金是从版本开始引入的一个新的,可以替代标准的。 设计模式之单例模式 - 掘金前言 作为一个好学习的程序开发者,应该会去学习优秀的开源框架,当然学习的过程中不免会去阅读源码,这也是一个优秀程序员的必备素养,在学习的过程中很多人会遇到的障碍,那就是设计模式。很多优秀的框架会运...

    FrozenMap 评论0 收藏0
  • Java开发

    摘要:大多数待遇丰厚的开发职位都要求开发者精通多线程技术并且有丰富的程序开发调试优化经验,所以线程相关的问题在面试中经常会被提到。将对象编码为字节流称之为序列化,反之将字节流重建成对象称之为反序列化。 JVM 内存溢出实例 - 实战 JVM(二) 介绍 JVM 内存溢出产生情况分析 Java - 注解详解 详细介绍 Java 注解的使用,有利于学习编译时注解 Java 程序员快速上手 Kot...

    LuDongWei 评论0 收藏0
  • Hollis原创|不了解这12个语法糖,别说你会Java

    摘要:但其实,虚拟机并不支持这些语法糖。方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。GitHub 2.5k Star 的Java工程师成神之路 ,不来了解一下吗); GitHub 2.5k Star 的Java工程师成神之路 ,真的不来了解一下吗); GitHub 2.5k Star 的Java工程师成神之路 ,真的确定不来了解一下吗); 本文从 ...

    番茄西红柿 评论0 收藏0

发表评论

0条评论

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