在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)。