java泛型

lujiango lujiango     2022-11-30     754

关键词:

1. 引言

JDK1.5增加泛型支持很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象放入Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。
当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastException异常。
增加了泛型支持后的集合,完全可以记住集合中元素的类型,B并可以在编译时检查集合中元素的类型,如果试图想集合中添加不满足类型要求的对象,编译器就会提示错误。
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。

从JDK1.5以后,Java引入了“参数化类型(parameterized type)”的概念,允许程序在创建集合时指定集合元素的类型,Java的参数化类型被称为泛型(Generic)。
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。

2. 泛型类和接口

2.1 定义泛型接口和类

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。
当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。
例如,Apple<T>类定义构造器,其构造器名依然是Apple,而不是Apple<T>,调用该构造器时却可以使用Apple<T>的形式,当然应该为T形参传入实际的类型参数。

public interface Generic<E> 

    void add(E e);
    Iterator<E> iterator();
    E next();


interface MyMap<K, V> 
    Set<K> keySet();
    V put(K k, V v);

2.2 从泛型类派生子类 

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含类型形参。

interface ListString<T> extends List<T> 
    @Override
    boolean add(T x);


interface ListInteger extends List<Integer> 


2.3 并不存在泛型类

不管泛型的实际类型参数是什么,他们在运行时总有相同的类(class),在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或静态变量的声明和初始化中不允许使用类型形参。  

3. 类型通配符

List<String>类并不是List<Object>类的子类。
Java泛型设计原则是,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException异常。

3.1 使用类型通配符

为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?)将一个问号作为类型实参传给List集合,写作:List<?>,意思是元素类型未知的List。
这个问号被称为通配符,它的元素类型可以匹配任何类型。
但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中,比如

List<?> list = new ArrayList<Object>();
list.add(new Object());

会报编译错误:The method add(capture#1-of ?) in the type List<capture#1-of ?> is not applicable for the arguments (Object)
因为程序无法确定list集合中元素的类型,所以不能向其中添加对象。
根据其咱们List<E>接口定义的diamante可以发现:add方法有类型参数E作为集合的元素类型,所以传给add的参数必须是E类的对象或其子类的对象。
但因为在该例中不知道E是什么类型,所以程序无法将任何对象放入该集合。唯一的例外是null,它是所有引用类型的实例。
另一方面,程序可以调用get方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个Object。
因此,把get的返回值赋值给一个Object类型的变量,或者放在任何希望是Object类型的地方都可以。

3.2 设定类型通配符的上限

当直接使用List<?>这种形式时,即表明这个List集合可以使任何泛型List的父类。但还有一种特殊的情形,程序不希望这个List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类。
List<? extends A>标示所有A泛型List的父类--只要List尖括号里的类型是A的子类型即可,把A称为这个通配符的上限。
类似,由于程序无法确定这个受限制通配符的具体类型,所以不能把A对象或其子类的对象加入这个泛型集合中。

3.3 设定类型通配符下限

Java允许设定通配符的下限:<? super Type>, 这个通配符标示它必须是Type本身,或时Type的父类。
实现将src集合里面的元素复制到dest集合里面,然后返回最后一个被复制的元素。

public class GenericSuper 
    public static void main(String[] args) 
        List<Number> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        // copy实际返回的T类型为Number类型不对
        // Integer i = copy(list1, list2);
        Integer in = copy1(list1, list2);
    

    public static <T> T copy(Collection<T> dest, Collection<? extends T> src) 
        T last = null;
        for (T ele : src) 
            last = ele;
            dest.add(ele);
        
        return last;
    

    public static <T> T copy1(Collection<? super T> dest, Collection<T> src) 
        T last = null;
        for (T ele : src) 
            last = ele;
            dest.add(ele);
        
        return last;
    

  

3.4 设定类型形参的上限

Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是上限类型,要么是该上限类型的子类。

class A 


class Human<T extends A>  // 设置上限
    private Human<A> h = new Human<>();

定义了一个Human泛型类,该Human类型的类型形参的上限是A类,这表明使用Human类时为T形参传入的实际类型参数只能是A或A类的子类。
在一种极端情况下,需要为类型形参设定多个上限(至多有一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类,并且上限多个上限接口。

class A 



interface B 



class AB extends A implements B 



class Human<T extends A & B>  // 设置上限
    private Human<AB> h = new Human<>();

与类同时继承父类,实现接口类似的是,为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。
也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。

4. 泛型方法

在定义类、接口时可以使用类型形参,在该类的方法定义和成员变量定义、接口的方法定义中,这些类型形参可被当成普通类型来用。在另外一些情况下,定义类、接口时没有使用类型形参,但定义方法时想自己定义而理性形参,这也是可以的,Java5提供了堆泛型方法的支持。
泛型方法的定义格式如下:
修饰符 <T, S> 返回值类型 方法名(形参列表)


泛型方法方法的方法定义比普通方法的定义多了类型形参声明,类型形参声明以尖括号括起来,多个类型形参之间以逗号分隔,所有的类型形参声明放在方法修饰符和方法返回值类型之间。

class Haha 
    public <T> void smile(T t) 

    

与接口、类声明中定义的类型形参不同的是,方法声明中定义的形参只能在该方法里使用,比如Human里面的S,而接口、类声明中定义的类型形参则可以在整个接口、类中使用,比如Human里面的T。方法中的泛型参数无须显式传入实际类型参数。

class Human<T extends A & B>  // 设置上限
    private Human<AB> h = new Human<>();

    public <S> boolean eat(S s) 
        return true;
    

    public <S> void run(T t, S s) 

    

4.1 泛型方法和类型通配符的区别  

大多数是都可以使用泛型方法来代替类型通配符。
采用类型通配符:

interface Collction1<E> 
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);

采用泛型方法:

interface Collction2<E> 
    <T> boolean containsAll(Collection<T> c);
    <T extends E> boolean addAll(Collection<T> c);

上面方法使用<T extends E>泛型形式,这时定义类型形参时设定上限(其中E是接口定义的类型形参,在接口里E可当成普通类型使用)
上面两个方法中类型形参T只使用了一次,类型形参T产生的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的。
泛型方法允许类型形参被用来标示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。

如果有需要,也可以同时使用泛型方法和通配符。

interface Collections1 
    public <T> void copy(List<T> dest, List<? extends T> src);
    public <T, S extends T> void copy1(List<T> dest, List<S> src);

5. Java的“菱形”语法与泛型构造器

Java也允许在构造器中声明类型形参,这就是所谓的泛型构造器。
一旦定义了泛型构造器,可以让Java根据数据参数的类型来推断类型形参的类型,也可以显式为构造器中的类型形参指定实际的类型。

public class GenericConstructor 
    public static void main(String[] args) 
        new Foo("sdf");
        new Foo(12);
        new  <String>Foo("ddd");
    


class Foo 
    public <T> Foo(T t) 
        System.out.println(t);
    

菱形语法允许调用构造器时在构造器后使用一对<>代表泛型信息。但如果程序显式指定了泛型构造器中声明的而理性形参的实际类型,则不可以使用菱形语法。

public class GenericConstructor 
    public static void main(String[] args) 
        new Foo<>("sdf");
        new <Integer>Foo<String>(12);
        // 如果显式指定泛型构造器中声明的T形参是String,此时不能使用菱形语法。
        // new  <String>Foo<>("ddd");
    


class Foo<E> 
    public <T> Foo(T t) 
        System.out.println(t);
    

6. 泛型与数组

Java泛型有一个很重要的设计原则:如果一段代码在编译时没有提出“未经检查的转换”警告,则程序在运行时不会引发ClassCastException异常。基于这个原因,所以数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。但可以声明元素类型包含类型变量或类型形参的数组。
也就是说,只能声明List<String>[]形式的数组,但不能创建ArrayList<String>[10]这样的数据对象,但可以new ArrayList<?>[10]

7. 擦除和转换  

为了与老的Java老的代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型参数,如果没有为这个泛型指定实际的类型参数,则该类型参数被称为raw type(原始类型),默认是声明该类型参数时指定的第一个上限类型。
当吧一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在<>之间的类型信息都将被扔掉。比如一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了类型参数的上限(即Object)。

public class GenericSweep 
    public static void main(String[] args) 
        Orange<Integer> or = new Orange<>(6);
        // 当把or赋给一个不带泛型信息的or1变量时,编译器就会丢失or对象的泛型信息
        // 因为Orange的类型形参的上限是Number类,所以编译器依然知道or1的getSize返回Number类型,但具体是Number的哪个子类就不清楚了。
        Orange or1 = or;
        Number n1 = or1.getSize();
        // or1只知道size的类型是Number
        //Integer i = or1.getSize();
    



class Orange<T extends Number> 
    T size;

    public Orange(T size) 
        this.size = size;
    

    public T getSize() 
        return size;
    

从逻辑上,List<String>是List的子类,如果直接把一个List对象赋给一个List<String>对象应该引起编译错误,但实际上不会。
对泛型而言,可以直接把一个List对象赋给一个List<String>对象,编译器仅仅提示“未经检查的转换”。

public class GenericSweep1 

    public static void main(String[] args) 
        List<Integer> li = new ArrayList<>();
        li.add(8);
        li.add(9);
        List list = li;
        List<String> ls = list;
        // 运行时异常
        System.out.println(ls.get(0));

    

定义一个List<Integer>对象,这个List对象保留了集合元素的类型信息。当把这个List对象赋给一个List类型的list后,编译器就会丢失前者的泛型信息,即丢失list集合里元素的类型信息,这是典型的擦除。

  

java_泛型笔记(代码片段)

文章目录Java_泛型简介Java_泛型的概念Java_为什么存在泛型Java_自定义泛型结构Java_泛型的声明Java_泛型的实例化Java_泛型类Java_泛型方法Java_泛型在继承上的体现Java_通配符的使用Java_泛型简介集合容器类在设计阶段/声明阶段不能确... 查看详情

java泛型泛型简介(泛型类|泛型方法|静态方法的泛型|泛型类与泛型方法完整示例)(代码片段)

文章目录一、泛型简介二、泛型类三、泛型方法四、静态方法的泛型五、泛型类与泛型方法完整示例一、泛型简介泛型可以简单理解为参数化类型,主要作用在类,方法,接口上;java泛型与C++模板:Java中的泛型,是仿照C++中... 查看详情

java泛型与非泛型java(代码片段)

查看详情

java高级--java泛型

类型的参数化泛型类可以同时设置多个参数泛型类可以继承泛型类泛型类可以实现泛型接口示例--泛型类packagecom.date;publicclassGenericDemo{publicstaticvoidmain(String[]args){ GenClass<String>genClass=newGenClass<String>("tanlei"); System.out 查看详情

java泛型

1、什么是泛型Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许开发人员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。2、... 查看详情

java之泛型

1、为什么要有泛型Generic?2、总结Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不... 查看详情

java泛型是什么?一文带你吃透泛型(代码片段)

文章目录1.Java泛型2.泛型类3.泛型接口4.泛型方法5.泛型集合Java编程基础教程系列1.Java泛型Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。其主要的形式有泛型类,泛型接口和泛型方法。泛型概... 查看详情

java--泛型

泛型简而言之就是类型的参数化泛型分为泛型接口、泛型类和泛型方法,不存在泛型数组。举例//泛型类;publicclassData<T>{privateTdata;publicData(Tdata){this.data=data;}publicTgetData(){returndata;}}publicclassTest02{publicstaticvoidmain(String[]arg 查看详情

java泛型梳理

java泛型梳理概述泛型,即参数化类型,是在JDK1.5之后才开始引入的。所谓参数化类型是指所操作的数据类型在定义时被定义为一个参数,然后在使用时传入具体的类型。这种参数类型可以用在类,接口,方法的创建中,分别被... 查看详情

java泛型

Java泛型Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。假定我们有这... 查看详情

java----泛型

 自定义泛型:浮动  泛型只会在 查看详情

java-28java泛型

Java泛型如果我们只写一个排序方法,就能够对整型数组、字符串数组甚至支持排序的任何类型的数组进行排序,这该多好啊。Java泛型方法和泛型类支持程序员使用一个方法指定一组相关方法,或者使用一个类指定一组相关的类... 查看详情

java泛型

Java泛型如果我们只写一个排序方法,就能够对整形数组、字符串数组甚至支持排序的任何类型的数组进行排序,这该多好啊。Java泛型方法和泛型类支持程序员使用一个方法指定一组相关方法,或者使用一个类指定一组相关的类... 查看详情

java泛型

1/**2*泛型定义3*泛型是JavaSE1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。Java语言引入泛型的好... 查看详情

java学习----泛型

目录  1.泛型的定义  2.泛型的使用  3.泛型通配符 一、泛型的定义  泛型是java5的新特性,使用泛型会让程序编写简单安全,在编译的时候就会检查类型是否安全  定义:所谓泛型就是允许在定义类、接口和方... 查看详情

java泛型

Java中的泛型,通常使用<E>来表示。泛型是指参数化类型。例如:  List<String>li=newArrayList<String>() 泛型是JavaSE1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类... 查看详情

java泛型java使用泛型的意义

Java泛型Java使用泛型的意义@authorixenos  直接意义在编译时保证类型安全 根本意义a)类型安全问题源自可复用性代码的设计,泛型保证了类型安全的复用模板b)使用复用性模板时不用手动强制类型转换 三种泛型实现... 查看详情

java泛型

Java中的泛型(GenericType)基本精神:数据类型参数化集合框架中没使用泛型和使用泛型的比较没使用泛型ListmyIntList=newLinkedList();//1myIntList.add(newInteger(100));//2Integerx=(Integer)myIntList.iterator().next();//3  使用泛型List<Inte 查看详情