Java之泛型

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

猜你喜欢

转载自blog.csdn.net/tongxuexie/article/details/80084970