早期没有泛型概念时,创建集合存入的元素类型都当成Object,使用的时候必须清楚集合中每个元素的类型,否则当我们对元素进行操作时容易出错,例如转换元素的类型可能出现ClassCastException异常。
示例代码:
如下代码,for循环循环到第二个元素的时候,由于元素原始类型为int,而代码强制转换为String,出现了ClassCastException。
public class Main { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); arrayList.add("ggy"); //存入String类型 arrayList.add(123); //存入int类型 arrayList.add(true); //存入bool类型 //使用集合时,我们并清楚集合中每个元素类型,假设都当成String进行处理 for (int i = 0; i < arrayList.size(); i++) { String s = (String)arrayList.get(i);//此次进行强转 System.out.println(s); } }}
针对以上问题,Jdk5开始引入泛型,我们来看下使用泛型,如何处理上述的问题。
如下图,当我们对集合设置了泛型后,集合添加了不同类型的元素时,代码处直接标红,编译不同。
Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。
泛型标识可以作为成员变量的类型,和成员方法的返回值类型及参数类型
class 类名称 <泛型标识,泛型标识,…> { private 泛型标识 变量名; .....}
常用的泛型标识:T、E、K、V
示例代码
public class Generice <T>{ //T为泛型类的泛型标识 private T key; //成员变量的参数类型被指定为T泛型标识 public Generice(T key) { //泛型标识作为构造器的参数类型 this.key = key; } public T getKey() { //泛型标识作为成员方法的返回值类型 return key; } public void setKey(T key) { //泛型标识作为成员方法的参数类型 this.key = key; }}
Java1.7以前
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
Java1.7以后
后面的<>中的类型可以省略
类名<具体的数据类型> 对象名 = new 类名<>();
示例代码:
public class Animal <T>{ private T name; public T getName() { return name; } public void setName(T name) { this.name = name; }}class Main{ public static void main(String[] args) { Animal<String> animal = new Animal<>(); //泛型类使用 animal.setName("Ggy"); System.out.println(animal.getName()); }}
1)泛型类,如果没有指定具体的数据类型,此时操作类型是Object
如下第一张图,没有指定类型时,idea显示形参化类的原始使用,第二张图指定了String没有显示这个信息。
2)泛型的类型参数只能是类类型,不能是基本数据类型
3)泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
可以结合后面的泛型擦除理解,泛型擦除之后 Animal< Integer> 和Animal< String>都是Animal类。
1)子类也是泛型类,子类和父类的泛型类型要一致
语法:class ChildGeneric<T> extends Generic<T>
2)子类不是泛型类,父类要明确泛型的数据类型
语法:class ChildGeneric extends Generic<String>
泛型标识可以作为成员方法的返回值类型及参数类型
interface 接口名称 <泛型标识,泛型标识,…> { 泛型标识 方法名(); .....}
常用的泛型标识:T、E、K、V
示例代码:
public interface Animal<T> { void getName(T t); T getName();}
接口名<具体数据类型> 变量名 = new 实现类名<>();
示例代码:
public interface Animal<T> { T getName(); void setName(T name);}class Cat<T> implements Animal<T>{ private T name; @Override public T getName() { return name; } @Override public void setName(T name) { this.name = name; }}class Main{ public static void main(String[] args) { //使用泛型接口 Animal<String> cat1 = new Cat<>(); Cat<String> cat2 = new Cat<>(); }}
1)实现类也是泛型类,实现类和接口的泛型类型要一致
2)实现类也是泛型类,实现类和接口的泛型类型要一致
泛型类,是在实例化类的时候指明泛型的具体类型,泛型方法,是在调用方法的时候指明泛型的具体类型。
语法:
修饰符 <T,E, ...> 返回值类型 方法名(形参列表) { 方法体...}
【注】泛型接口\类的泛型标识,如果与接口\类内部的泛型方法的泛型标识同名,这两个泛型标识是不能等同的。
按如下方式可传递多个参数
public <E> void print(E... e){ for (E e1 : e) { System.out.println(e); } }
示例代码
public interface Generic { public <E> List<E> getList(E...e);}class SubGeneric implements Generic{ @Override public <E> List<E> getList(E... e) { ArrayList<E> list = new ArrayList<>(); Arrays.stream(e).forEach(a->{ list.add(a); }); return list; }}class Main{ public static void main(String[] args) { SubGeneric subGeneric = new SubGeneric(); //传递多个参数 List<? extends Serializable> list = subGeneric.getList("ggy", 123, "czp", true); System.out.println(list); }}
类型通配符一般是使用"?"代替具体的类型实参。类型通配符是类型实参,而不是类型形参。
语法
类/接口<? extends 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
示例代码:
语法
类/接口<? super实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类类型。
示例代码:
只能用在方法的返回值或者方法的参数列表中,不能用在类上。
用在类上报错:
用在方法上:
下述代码我通常会误解认为应该编译通过,但是编辑器显示编译出现问题,这是因为当泛型指定了实参后,传入的参数的类型应该和指定的实参类型一致,如下printCollection方法的参数的泛型指定为Object,那么我们调用这个方法时,传入的参数的泛型也应该是Object.
那么上面的案例能否优化呢?可以通过“?”来实现,“?”代表任意的实参,所以编译可以通过。
或者我们通过泛型类实现也可以
或者通过泛型方法实现
泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除。