【Java SE基础 八】Java泛型机制(下)

简介: 【Java SE基础 八】Java泛型机制(下)

泛型规范

包括泛型类型的一些限定以及泛型的一些类型限定、使用规范和继承规范

泛型类型限定

有时,类或方法需要对类型变量加以约束,否则传入类型可能不一定满足泛型使用条件:

  • 对类的限定:public class TypeLimitForClass<T extends List & Serializable>{}
  • 对方法的限定:public static<T extends Comparable<T>>T getMin(T a, T b) {}

其中对方法的限定表示,泛型参数类型必须实现了Comparable接口,否则就没有办法进行比较了

public class ArrayAlg {
    public static <T extends Comparable> T min(T[] array){
        if (array == null || array.length == 0){
            return null;
        }
        T smallest = array[0];
        for (int i=0;i<array.length;i++){
            if (smallest.compareTo(array[i])>0){
                smallest = array[i];
            }
        }
        return smallest;
    }
}

上述代码中的限制了用于实例化类型参数T的类型,必须是实现Comparable接口(只含有compareTo方法的标准接口)的类。如果没有对T进行限制,那么无法确保实例化T的类型具有compareTo方法。

对于类的限定中:一个类型变量可以有多个限定,例如:

<T extends Comparable & Serializable , U extends Comparable>//限定类型使用 “&”分隔,而“,”用于分隔类型参数。

在Java中,一个类可以实现多个接口,但只能有一个父类,所以在类型参数的限定中,可以有多个接口,但只能有一个类。

<T extends 接口1 & 接口2 & ... & 接口n & 类型1>

泛型使用规范

泛型有如下的使用规范,使用的时候需要注意:

  1. 不能实例化泛型类
  2. 静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的,动态变量和方法可以直接引用泛型类型变量
  3. 基本类型无法作为泛型类型,例如int必须转为integer
  4. 无法使用instanceof关键字或==判断泛型类的类型
  5. 泛型类的原生类型(反射获取的类对象)与所传递的泛型无关,无论传递什么类型,原生类是一样的
  6. 泛型数组可以声明但无法实例化,只能具体的new一个数组
  7. 泛型类不能继承Exception或者Throwable
  8. 不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出

以上就是泛型的使用规范。其实 泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的.

泛型的继承规则

泛型有如下三个继承规则:

  1. 对于泛型参数是继承关系的泛型类之间是没有继承关系的
  2. 泛型类可以继承其它泛型类,例如: public class ArrayList<E> extends AbstractList<E>
  3. 泛型类的继承关系在使用中同样会受到泛型类型的影响

通过代码实现如下:

//继承泛型类
private static class SubGenericInherit<T> extends GenericInherit<T> {
}
//父类泛型类
public class GenericInherit<T> {
    private T data1;
    private T data2;
    public T getData1() {
        return data1;
    }
    public void setData1(T data1) {
        this.data1 = data1;
    }
    public T getData2() {
        return data2;
    }
    public void setData2(T data2) {
        this.data2 = data2;
    }
    public static <V> void setData2(GenericInherit<Father> data2) {
    }
    public static void main(String[] args) {
        //Son 继承自 Father,泛型参数是继承关系
        Father father = new Father();
        Son son = new Son();
        //泛型父类传递不同类型参数,一个是父类,一个是子类
        GenericInherit<Father> fatherGenericInherit = new GenericInherit<>();
        GenericInherit<Son> sonGenericInherit = new GenericInherit<>();
        //泛型子类传递不同类型参数,一个是父类,一个是子类
        SubGenericInherit<Father> fatherSubGenericInherit = new SubGenericInherit<>();
        SubGenericInherit<Son> sonSubGenericInherit = new SubGenericInherit<>();
        /**
         * 对于传递的泛型类型是继承关系的泛型类之间是没有继承关系的
         * GenericInherit<Father> 与GenericInherit<Son> 没有继承关系
         * Incompatible types.
         */
        father = new Son();
        //fatherGenericInherit=new GenericInherit<Son>();  //编译报错
        /**
         * 泛型类可以继承其它泛型类,例如: public class ArrayList<E> extends AbstractList<E>
         */
        fatherGenericInherit=new SubGenericInherit<Father>();
        /**
         *泛型类的继承关系在使用中同样会受到泛型类型的影响
         */
        setData2(fatherGenericInherit);
       // setData2(sonGenericInherit);  //泛型父类的setData2方法要求传递一个泛型父类对象
        setData2(fatherSubGenericInherit);
       // setData2(sonSubGenericInherit);
    }

通配符泛型

泛型中可以使用通配符来解决集合的不变性,先来解释下集合的不变性和数组的协变性: 假设有一个函数 fun(Animal animal),如果我们传入一个Dog d 对象进去,编译器是不会报错的,这是多态的概念; 假设有一个函数 fun(Animal[] animals),如果我们传如一个Dog[] dogs数组进去,编译器也不会报错,这就是数组的协变性;假设有一个函数 fun(List<Animal> animal),如果我们传如一个List <Dog> dog 集合进去,编译器就会报错了,这就是集合泛型的不变性;那么该怎么办呢?我们可以将泛型改成这样

  • fun (List <? extends Animal> animal),这样之后,当我们再传入一个List dog 集合进去,编译器就就不会报错了。也就是说可以传入包含Animal的子类的List了。

这里就用到了通配符的概念,通配符有三种指定方式:

  1. <? extends Parent> 指定了泛型类型的上届
  2. <? super Child> 指定了泛型类型的下届
  3. <?> 指定了没有限制的泛型类型

其实通配符解决的是同一泛型类中的泛型参数的继承关系,接下来以此下图的继承关系为例来介绍下:

代码逻辑如下:

public class GenericByWildcard {
    private static void print(GenericClass<Fruit> fruitGenericClass) {
        System.out.println(fruitGenericClass.getData().getColor());
    }
    /**
     * 问题的提出,因为泛型参数的继承关系在使用中并不表示泛型类的继承关系,而且还得遵守泛型规则,所以Orange传进去不认。
     */
    private static void use() {
        GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
        print(fruitGenericClass);
        GenericClass<Orange> orangeGenericClass = new GenericClass<>();
        //类型不匹配,可以使用<? extends Parent> 来解决
        //print(orangeGenericClass);
    }
    /**
     * <? extends Parent> 指定了泛型类型的上届,子类可以正常使用
     */
    private static void printExtends(GenericClass<? extends Fruit> genericClass) {
        System.out.println(genericClass.getData().getColor());
    }
    public static void useExtend() {
        GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
        printExtends(fruitGenericClass);
        GenericClass<Orange> orangeGenericClass = new GenericClass<>();
        printExtends(orangeGenericClass);
        GenericClass<Food> foodGenericClass = new GenericClass<>();
        //Food是Fruit的父类,超过了泛型上届范围,类型不匹配
        //printExtends(foodGenericClass);
        //表示GenericClass的类型参数的上届是Fruit
        GenericClass<? extends Fruit> extendFruitGenericClass = new GenericClass<>();
        Apple apple = new Apple();
        Fruit fruit = new Fruit();
        /*
         * 道理很简单,? extends X  表示类型的上界,类型参数是X的子类,那么可以肯定的说,
         * get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。
         * 但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。
         * 总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。
         */
//        extendFruitGenericClass.setData(apple);
//        extendFruitGenericClass.setData(fruit);
        fruit = extendFruitGenericClass.getData();
    }
    /**
     * <? super Child> 指定了泛型类型的下届
     */
    public static void printSuper(GenericClass<? super Apple> genericClass) {
        System.out.println(genericClass.getData());
    }
    public static void useSuper() {
        GenericClass<Food> foodGenericClass = new GenericClass<>();
        printSuper(foodGenericClass);
        GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
        printSuper(fruitGenericClass);
        GenericClass<Apple> appleGenericClass = new GenericClass<>();
        printSuper(appleGenericClass);
        GenericClass<HongFuShiApple> hongFuShiAppleGenericClass = new GenericClass<>();
        // HongFuShiApple 是Apple的子类,达不到泛型下届,类型不匹配
        //printSuper(hongFuShiAppleGenericClass);
        GenericClass<Orange> orangeGenericClass = new GenericClass<>();
        // Orange和Apple是兄弟关系,没有继承关系,类型不匹配
       // printSuper(orangeGenericClass);
        //表示GenericClass的类型参数的下界是Apple
        GenericClass<? super Apple> supperAppleGenericClass = new GenericClass<>();
        supperAppleGenericClass.setData(new Apple());
        supperAppleGenericClass.setData(new HongFuShiApple());
        /*
         * ? super  X  表示类型的下界,类型参数是X的超类(包括X本身),
         * 那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,
         * 但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。
         * 编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
         * 总结:主要用于安全地写入数据,可以写入X及其子类型。
         */
        //supperAppleGenericClass.setData(new Fruit());
        //get方法只会返回一个Object类型的值。
        Object data = supperAppleGenericClass.getData();
    }
    /**
     * <?> 指定了没有限定的通配符
     */
    public static void printNonLimit(GenericClass<?> genericClass) {
        System.out.println(genericClass.getData());
    }
    public static void useNonLimit() {
        GenericClass<Food> foodGenericClass = new GenericClass<>();
        printNonLimit(foodGenericClass);
        GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
        printNonLimit(fruitGenericClass);
        GenericClass<Apple> appleGenericClass = new GenericClass<>();
        printNonLimit(appleGenericClass);
        GenericClass<?> genericClass = new GenericClass<>();
        //setData 方法不能被调用, 甚至不能用 Object 调用;
        //genericClass.setData(foodGenericClass);
        // genericClass.setData(new Object());
        //返回值只能赋给 Object
        Object object = genericClass.getData();
    }
}

下列哪些继承关系是正确的?

class A {}
class B extends A {}
class C extends A {}
class D extends B {}
Which four statements are true ?
A,The type List<A>is assignable to List.  //true
B,The type List<B>is assignable to List<A>.
C,The type List<Object>is assignable to List<?>. //true
D,The type List<D>is assignable to List<?extends B>.//true
E,The type List<?extends A>is assignable to List<A>.
F,The type List<Object>is assignable to any List reference.
G,The type List<?extends B>is assignable to List<?extends A>.//true

判断的时候有如下准则:

  1. 只看尖括号里边的,明确点和范围两个概念 ,List既是点也是范围,当表示范围时,表示最大范围,List<?>和List 是相等的,都代表最大范围和最大点
  2. 如果尖括号里的是一个类,那么尖括号里的就是一个点,比如List,List,List
  3. 如果尖括号里面带有问号,那么代表一个范围,<? extends A>代表小于等于A的范围,<? super A>代表大于等于A的范围,<?>代表全部范围
  4. 尖括号里的所有点之间互相继承都是错,除非是俩相同的点,因为泛型参数继承关系不代表泛型类有基础关系
  5. 尖括号小范围赋值给大范围,对,大范围赋值给小范围,错。如果某点包含在某个范围里,那么可以赋值,否则,不能赋值

以上就是在有通配符的情况下的泛型继承关系。

泛型原理

虚拟机是如何实现泛型的,其实泛型在Java里更像是一种语法糖。Java泛型是Java1.5之后才引入的,为了向下兼容。Java采用了C++完全不同的实现思想。Java中的泛型更多的看起来像是编译期用的,Java中泛型在运行期是不可见的,会被擦除为它的上级类型。如果是没有限定的泛型参数类型,就会被替换为Object.

GenericClass<String> stringGenericClass=new GenericClass<>();
GenericClass<Integer> integerGenericClass=new GenericClass<>();

CJava进行了类型擦除之后统一改为GenericClass<Object>

相关文章
|
2月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
223 10
|
2月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
345 1
|
4月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
238 4
|
4月前
|
安全 Java API
在Java中识别泛型信息
以上步骤和示例代码展示了怎样在Java中获取泛型类、泛型方法和泛型字段的类型参数信息。这些方法利用Java的反射API来绕过类型擦除的限制并访问运行时的类型信息。这对于在运行时进行类型安全的操作是很有帮助的,比如在创建类型安全的集合或者其他复杂数据结构时处理泛型。注意,过度使用反射可能会导致代码难以理解和维护,因此应该在确有必要时才使用反射来获取泛型信息。
207 11
|
5月前
|
设计模式 算法 Java
Java SE 与 Java EE 组件封装使用方法及实践指南
本指南详细介绍了Java SE与Java EE的核心技术使用方法及组件封装策略。涵盖集合框架、文件操作、Servlet、JPA、EJB和RESTful API的使用示例,提供通用工具类与基础组件封装建议,如集合工具类、文件工具类、基础Servlet、实体基类和服务基类等。同时,通过分层架构集成示例展示Servlet、EJB和JPA的协同工作,并总结组件封装的最佳实践,包括单一职责原则、接口抽象、依赖注入、事务管理和异常处理等。适合希望提升代码可维护性和扩展性的开发者参考。
184 0
|
6月前
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
170 0
|
6月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
231 0
|
6月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
368 32
AQS:Java 中悲观锁的底层实现机制
|
6月前
|
Java 区块链 网络架构
酷阿鲸森林农场:Java 区块链系统中的 P2P 区块同步与节点自动加入机制
本文介绍了基于 Java 的去中心化区块链电商系统设计与实现,重点探讨了 P2P 网络在酷阿鲸森林农场项目中的应用。通过节点自动发现、区块广播同步及链校验功能,系统实现了无需中心服务器的点对点网络架构。文章详细解析了核心代码逻辑,包括 P2P 服务端监听、客户端广播新区块及节点列表自动获取等环节,并提出了消息签名验证、WebSocket 替代 Socket 等优化方向。该系统不仅适用于农业电商,还可扩展至教育、物流等领域,构建可信数据链条。
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
130 1