在JDK1.5之后引入新的特性之一:泛型。引入泛型后,如果明确设置了类型,则类型就是所设置的类型;如果没有设置类型,则默认类型为Object类。
1. 为什么要引入泛型?引入泛型的好处是什么?下面我拿List接口和ArrayList类举例从而对泛型作出解释:
在Eclipse上查看ArrayList类实现List接口的源码可以发现ArrayList类的声明中看到了<E>以及List接口中也声明了<E>:
//List接口源码(截取部分): public interface List<E> extends Collection<E> { int size(); boolean add(E e); E get(int index); boolean isEmpty(); boolean contains(Object o); Object[] toArray(); ... }
//ArrayList类的源码(截取部分): public class ArrayList<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,java.io.Serializable{ public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } public E get(int index) { rangeCheck(index); return elementData(index); } ... }
在上述的源码中可以发现List接口是泛型接口,那为什么用泛型接口,而不直接在List接口中用Object类接收任意引用类型呢?下面举例说明:
//当ArrayList类实现接口不使用泛型时: import java.util.ArrayList; public class Genericity { public static void main(String[] args) { //不使用泛型时: java.util.List list=new ArrayList(); //使用add()方法存String类的元素时,其默认接收的元素类型为Object类 list.add("list1"); list.add("list2"); list.add("list3"); //从list中取元素时,由于默认为Object类,所以必须向下转型才可以用String类的实例化对象接收 String string=(String)list.get(1); System.out.println(string); } }
//当ArrayList类实现List接口使用泛型时: import java.util.ArrayList; public class Genericity { public static void main(String[] args) { //使用泛型时: java.util.List<String> list=new ArrayList<>(); //使用add()方法存String类的元素 list.add("list1"); list.add("list2"); list.add("list3"); //从list中取元素时,由于使用了泛型,所以不需要向下转型 String string=(String)list.get(1); System.out.println(string); } }
通过以上两个代码的演示得出总结:使用泛型,避免了向下转型的操作。
//当ArrayList类实现List接口不使用泛型时: import java.util.ArrayList; public class Genericity { public static void main(String[] args) { //不使用泛型时:由于接收类型为Object类,所以可以存任意引用类型 java.util.List list=new ArrayList(); //使用add()方法存String类的元素, list.add("list1"); list.add("list2"); //使用add()方法存Integer类的元素 list.add(100); //此时索引值为2的元素存的是Integer类型,但用String类的实例化对象接收时在编译期并没有报错 String string=(String)list.get(2); //但运行期出现错误:ClassCastExpection System.out.println(string); } }
//当ArrayList类实现List接口使用泛型时: import java.util.ArrayList; public class Genericity { public static void main(String[] args) { //使用泛型时: java.util.List<Integer> list=new ArrayList<>(); //使用add()方法存Integer类的元素 list.add(100); list.add(101); list.add(102); //此时还是用String类的实例化对象接收Integer类元素时在编译期就会报错 String string=(String)list.get(2); System.out.println(string); } }
通过以上两个代码的演示得出总结:提高了安全性,将运行期的错误转换到编译期。
综上,使用泛型的好处有两点:
(1)避免向下转型。
(2)提高安全性:将运行期的错误转换到编译期。
2. 泛型类
泛型类指的是在类定义时并没有设置类中某些属性、方法的参数、返回类型,而是在类使用时(即在实例化时)再对类型进行定义,若要想进行泛型类的定义,就必须在类定义时做一个类型标记的声明。
2.1 泛型类的定义
class MyClass<T>{ //泛型类 private T data; //data的getter方法,返回类型为T public T getData() { return data; } //data的set方法,参数类型为T public void setData(T data) { this.data = data; } } public class Genericity { public static void main(String[] args) { } }
其中,尖括号<>中的T是类型参数,用于表示任意类型,对于T这个标识符,可以自行定义,但Java出于规范,建议用单个大写字母来代表类型参数。如果一个类被class ClassName <T>的形式定义,那么该类ClassName被称为泛型类
2.2 泛型类的使用
public class Genericity { public static void main(String[] args) { MyClass<String> myClass1=new MyClass<String>(); myClass1.setData("字符串类型"); System.out.println(myClass1.getData()); MyClass<Integer> myClass2=new MyClass<Integer>(); myClass2.setData(111); System.out.println(myClass2.getData()); } }
注:泛型只能接受类,所有的基本数据类型必须使用包装类。
2.3 泛型类可以接收多个类型参数
class MyClass<S,T>{ //泛型类 private T data; private S str; //data的getter方法,返回类型为T public T getData() { return data; } //data的set方法,参数类型为T public void setData(T data) { this.data = data; } //str的getter方法,返回类型为S public S getStr() { return str; } //str的setter方法,参数类型为S public void setStr(S str) { this.str = str; } } public class Genericity { public static void main(String[] args) { MyClass<String,Integer> myClass=new MyClass<String,Integer>(); myClass.setData(111); System.out.println(myClass.getData()); myClass.setStr("字符串类型"); System.out.println(myClass.getStr()); } }
3. 泛型方法
3.1 泛型方法的定义
class MyClass{ public <T>void myFun(T t){//泛型方法 System.out.println(t); } }
泛型方法和泛型类不同的地方就是:泛型方法是泛型在方法的返回类型的前面,而泛型类是泛型在类名称的后面。
3.2 泛型类与泛型方法共存
class MyClass<T>{ //泛型类 private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } public <T> T myFun(T t){ //泛型方法 return t; } } public class Genericity { public static void main(String[] args) { MyClass<Integer> myClass=new MyClass<>(); myClass.setData(111); System.out.println(myClass.getData()); String string=myClass.myFun("泛型方法"); System.out.println(string); } }
此时可以看出,泛型类的泛型T设置为Integer,而泛型方法的泛型T设置为String,故泛型类中的类型参数与泛型方法中的类型参数没有任何关系,泛型方法以自己定义的类型参数为主。为了避免混淆,若泛型类中有泛型方法,建议两者的类型参数不要同名。
4. 泛型接口
将泛型定义在接口中的接口称之为泛型接口。
4.1 泛型接口的定义
interface IMessage<T>{ //泛型接口 //抽象方法 T print(); void fun(T t); }
4.2 子类实现泛型接口
(1)在子类定义时继续使用泛型
interface IMessage<T>{ //泛型接口 //抽象方法 T print(); void fun(T t); } class MessageImpl<T> implements IMessage<T>{ //在子类定义时继续使用泛型 @Override public T print() { // TODO Auto-generated method stub return null; } @Override public void fun(T t) { // TODO Auto-generated method stub } }
(2)在子类实现泛型接口时明确设置类型
class MessageImpl implements IMessage<String>{ @Override public String print() { // TODO Auto-generated method stub return null; } @Override public void fun(String t) { // TODO Auto-generated method stub } //在子类实现泛型接口时明确设置类型 }
5. 通配符
在JDK1.5中的新特性泛型出现后,避免了向下转型,从而避免了ClassCastException异常,但出现新的问题:参数统一问题。下面举例说明关于参数统一的问题:
class MyClass<T>{ //泛型类 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //泛型设置为String MyClass<String> message1=new MyClass<>(); message1.setMessage("hahahaha"); fun(message1); //泛型设置为Integer MyClass<Integer> message2=new MyClass<>(); message2.setMessage(111); fun(message2); //编译报错,静态方法fun()不能接收参数message2,因为message2泛型设置的不是String而是Integer } //该静态方法用于调用Message泛型类的getMessage()方法 public static void fun(MyClass<String> message) { System.out.println(message.getMessage()); } }
以上代码的静态方法fun()就没有做到参数统一,因为它只能接收指定的泛型,不能接收所有的泛型类型,这种情况下就有了通配符。
5.1 通配符?
class MyClass<T>{ //泛型类 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //泛型设置为String MyClass<String> message1=new MyClass<>(); message1.setMessage("hahahaha"); fun(message1); //泛型设置为Integer MyClass<Integer> message2=new MyClass<>(); message2.setMessage(111); fun(message2); //编译不再报错 } //该静态方法用于调用Message泛型类的getMessage()方法 public static void fun(MyClass<?> message) { //使用通配符后,可以接收所有的泛型类型 System.out.println(message.getMessage()); } }
此时静态方法fun()使用了通配符"?",表示它可以接收任意类型,但是由于不确定类型,所以无法对泛型类中的属性进行修改,只可以读取。举例说明:
class MyClass<T>{ //泛型类 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //泛型设置为String MyClass<String> message1=new MyClass<>(); message1.setMessage("hahahaha"); fun(message1); //泛型设置为Integer MyClass<Integer> message2=new MyClass<>(); message2.setMessage(111); fun(message2); //编译不再报错 } //该静态方法用于调用Message泛型类的getMessage()方法 public static void fun(MyClass<?> message) { //使用通配符后,可以接收所有的泛型类型 message.setMessage("setter方法"); //此时编译报错,不确定传入的是什么类型的泛型,没法修改属性 //当你一开始定义fun()时并不知道要传入什么类型,故使用了通配符? //在fun()中你调用setMessage()方法时传入的为String类,但当你传入的参数泛型是Integer时,这就造成了不一致,所以不允许修改 System.out.println(message.getMessage()); } }
5.2 在?通配符的基础上产生子通配符:? extends 类
? extends 类:表示设置泛型上限,例如:? extends Number表示只能设置Number类及其子类
class MyClass<T extends Number>{ //泛型类,设置泛型上限 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //泛型设置范围为Number类及其子类,此时设置为Integer类 MyClass<Integer> message1=new MyClass<>(); message1.setMessage(111); fun(message1); } //该静态方法用于调用Message泛型类的getMessage()方法 public static void fun(MyClass<? extends Number> message) { //设置泛型上限 System.out.println(message.setMessage(1.11)); //该情况和通配符?的情况一致 //此时编译报错,不确定传入的是什么类型的泛型,没法修改属性 //当你一开始定义fun()时并不知道要传入什么类型,故使用了通配符? entends Number //在fun()中你调用setMessage()方法时若传入的为Float类,但当你传入的参数泛型是Integer时,这就造成了不一致 //所以直接不允许修改,不管你传入的参数类型是否和setMessage()方法中的参数一致,都不允许修改 System.out.println(message.getMessage()); } }
5.3 在?通配符的基础上产生子通配符:? super 类
? super 类:表示设置泛型下限,例如:? super String表示能够设置泛型的范围为String类及其父类Object类
class MyClass<T>{ //泛型类 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //此时泛型设置为String类 MyClass<String> message=new MyClass<>(); message.setMessage("设置泛型上限"); fun(message); } //该静态方法用于调用Message泛型类的getMessage()方法 public static void fun(MyClass<? super String> message) { //设置泛型下限 message.setMessage("setter"); //此时编译报错,不确定传入的是什么类型的泛型,没法修改属性 //当你一开始定义fun()时并不知道要传入什么类型,故使用了通配符? super String //通配符为? super 类允许修改,不管你传入的参数类型是否和setMessage()方法中的参数一致,因为有泛型下限,可以向下转型 System.out.println(message.getMessage()); } }
总结:
1.通配符?和设置泛型上限? extends 类都不能修改泛型类中的属性内容,而设置泛型下限? super 类可以修改泛型类中的属性内容。
2.设置泛型上限? extends 类能用在声明上,而设置泛型下限? super 类只能用在方法参数
6. 类型擦除
泛型信息只存在于代码编译期,在进入JVM之前,与泛型相关的信息都会被擦除掉,这称为类型擦除。
6.1 泛型类和普通类在Java虚拟机(JVM)内没有什么区别
class MyClass<T>{ //泛型类 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //此时泛型设置为String类 MyClass<String> message1=new MyClass<>(); //此时泛型设置为Integer类 MyClass<Integer> message2=new MyClass<>(); System.out.println(message1.getClass()); System.out.println(message2.getClass()); } } 运行结果: class MyClass class MyClass
解析:因为MyClass<String>和MyClass<Integer>在JVM中的Class相同。
6.2 类型擦除
import java.lang.reflect.Field; class MyClass<T>{ //泛型类,未指定泛型上限 private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } class Message<T extends Number>{ //泛型类,指定泛型上限 private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Genericity { public static void main(String[] args) { //在泛型类中未指定泛型上限 MyClass<Float> myclass=new MyClass<>(); Class cls1=myclass.getClass(); Field[] fields1=cls1.getDeclaredFields(); for(Field field:fields1) { System.out.println(field.getType()); } //在泛型类中指定泛型上限 Message<Float> message=new Message<>(); Class cls2=message.getClass(); Field[] fields2=cls2.getDeclaredFields(); for(Field field:fields2) { System.out.println(field.getType()); } } }
运行结果:
class java.lang.Object
class java.lang.Number
总结:
在泛型类型被擦除的时候,若泛型类中的类型参数没有指定上限,则<T>会被转译成基类Object类;若泛型类中的类型参数指定了上限(如:<T extends Number>),则类型参数就被替换成类型上限(如:Number)。