我是靠谱客的博主 超帅盼望,最近开发中收集的这篇文章主要介绍黑马程序员——Java 泛型Java 泛型(Generircs),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

------- android培训、java培训、期待与您交流! ----------


Java 泛型(Generircs)


1.介绍


泛型是JDK 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

引入泛型的好处是安全简单。在JDK 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

1.1 定义简单的泛型

首先来看看List和Iterator中定义的泛型,

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}
public interface List<E> extends Collection<E> {
	Iterator<E> iterator();
	Object[] toArray();
	<T> T[] toArray(T[] a);
	boolean add(E e);
	E set(int index, E element);

}

首先定义一个不使用泛型的Box类,

//不使用泛型
public class Box {
        private Object object;
        public void add(Object object) {
            this.object = object;
        }
        public Object get() {
            return object;
        }
}
public class BoxDemo1 {
    public static void main(String[] args) {
        // ONLY place Integer objects into this box!
        Box integerBox = new Box();
        integerBox.add(new Integer(10));
        Integer someInteger = (Integer)integerBox.get();
        System.out.println(someInteger);
    }
}


如果我们在add方法中添加一个字符串类型的参数会出现怎样的结果?

public class BoxDemo1 {
    public static void main(String[] args) {
        // ONLY place Integer objects into this box!
        Box integerBox = new Box();
        //integerBox.add(new Integer(10));
		/*
		 *	Imagine this is one part of  a large application modified by one programmer.
		 *  Note how the type is now String.
		 */ 
        integerBox.add("10");//String
		Integer someInteger = (Integer)integerBox.get();

        System.out.println(someInteger);
    }
}

输出运行结果:

---------- java ----------
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at Demo.main(Demo.java:21)


可以看出此时的结果会出现类型转换常,虽然add方法的参数是object类型,把一个数字10作为一个字符串传进去是可以的,但是结果返回时把Object转换成了Integer类型。

使用泛型定义Box类,如下:


/**
 * Generic version of the Box class.
 * @param <T> the type of value being boxed
 */

public class Box<T> {

    // T stands for "Type"
    private T t;

    public void add(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

现在把T换成Integer类型,Box<Integer>
public class BoxDemo3 {
    public static void main(String[] args) {
        Box<Integer> integerBox =  new Box<Integer>();
        integerBox.add(new Integer(10));
        // 不用强制转换
        Integer someInteger = integerBox.get();
        System.out.println(someInteger);
    }
}

这里指定了参数类型是Integer,那么我们在创建这个Box对象的时候也指定了一个类型参数,这样就取代了类型之间的强制转换。这里有个很大的不同是,编译器现在能够在编译时检查程序的正确性。当我们说integerBox被声明为Box<Integer>类型,这就告诉我们无论何时何地使用integerBox变量,编译器保证其中的元素的正确的类型。实际上,这样增加了不少可读性和稳定性。


2.深入泛型


泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换(casting)操作,编译器保证了这些类型转换(casting)的绝对无误。如下面代码:

/******* 不使用泛型类型 *******/
List list1 = new ArrayList();
list1.add(100);					    //编译器不检查值
String str1 = (String)list1.get(0); //需手动强制转换,如转换类型与原数据类型不一致将抛出ClassCastException异常

/******* 使用泛型类型 *******/
List<String> list2 = new ArrayList<String>();
list2.add("value");         //[类型安全的写入数据] 编译器检查该值,该值必须是String类型才能通过编译
String str2 = list2.get(0); //[类型安全的读取数据] 不需要手动转换



2.1 类型擦除


Java中的泛型只存在于编译期,在将Java源文件编译完成Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除(type erasure)。
List<String>    list1 = new ArrayList<String>();
List<Integer>   list2 = new ArrayList<Integer>();

System.out.println(list1.getClass() == list2.getClass()); // 输出结果: true
System.out.println(list1.getClass().getName()); // 输出结果: java.util.ArrayList
System.out.println(list2.getClass().getName()); // 输出结果: java.util.ArrayList

在以上代码中list1,list2分别定义为 List<String> 和 List<Integer> 等类型,在编译之后都会变成 List,而由泛型附加的类型信息对 JVM 来说是不可见的,所以list1,list2的class对象相同。class对象的名字都是java.util.ArrayList,这都说明 List<String> 和 List<Integer> 的对象使用的都是同一份字节码,运行期间并不存在泛型。

public class GenericsApp {
    public void method(List<String> list){
        //
    }
    
    /*
     * 编译出错,这两个方法不属于重载,由于类型的擦除,使得这两个方法的参数列表的参数均为List类型,
     * 这就相当于同一个方法被声明了两次,编译自然无法通过了
     * 
    public void method(List<Integer> list){
        
    }
    */
}

我可以用javap命令反编译字节码来查看method方法的参数列表的参数类型。


从图中可以看出,经反编译后的源码中 method 方法的参数变成了 List 类型,说明泛型的类型被擦除了,字节码文件中不存在泛型,也就是说,运行期间泛型并不存在,它在编译完成之后就已经被擦除了。


2.2 泛型类型的继承规则


在使用泛型类时,需要了解一些有关继承和子类型的准则。泛型类型跟其是否是泛型类型的子类型没有任何关系。

考虑一个类和一个子类,有如下两个Employee类和Manager类,

class Employee
{  
	private String name;
	private double salary;
	private Date hireDay;//入职日期
	/**
	  @param n the employee's name
	  @param s the salary
	  @param year the hire year
	  @param month the hire month
	  @param day the hire day
	*/
	public Employee(String n, double s, int year, int month, int day)
	{  
	  name = n;
	  salary = s;
	  GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
	  hireDay = calendar.getTime();
	}

	public String getName()
	{
	  return name;
	}

	public double getSalary()
	{  
	  return salary;
	}

	public Date getHireDay()
	{  
	  return hireDay;
	}

	public void raiseSalary(double byPercent)
	{  
	  double raise = salary * byPercent / 100;
	  salary += raise;
	}
}

class Manager extends Employee
{  
	private double bonus;
	/**
	  @param n the employee's name
	  @param s the salary
	  @param year the hire year
	  @param month the hire month
	  @param day the hire day
	*/
	public Manager(String n, double s, int year, int month, int day)
	{  
	  super(n, s, year, month, day);
	  bonus = 0;
	}
	public double getSalary()
	{ 
	  double baseSalary = super.getSalary();
	  return baseSalary + bonus;
	}
	public void setBonus(double b)
	{  
	  bonus = b;
	}
	public double getBonus()
	{  
	  return bonus;
	}
}

Pair<Employee>是Pair<Manager>的一个子类吗?NO!!或许我们会感到奇怪,如下面代码将不能编译成功:

class Pair<T>
{
	private T first;
	private T second;
	public Pair(){
		first = null;
		second = null;
	}
	public Pair(T first, T second){
		this.first = first;
		this.second = second;
	}
	public void setFirst(T newValue){first = newValue;}
	public void setSecond(T newValue){second = newValue;}
	public T getFirst(){return first;}
	public T getSecond(){return second ;}
}

class ArrayAlg
{
	public static <T> Pair<T> makePair(Class<T> c1){
		try{
			// Pair<String> p = Pair.makePair(String.class);
			return new Pair<T>(c1.newInstance(),c1.newInstance());
		}catch(Exception ex){
			return null;
		}
	}
	/**
	 * 计算泛型数组的最大值和最小值
	 */
	 public static <T extends Comparable> Pair<T> minmax(T[] a){
		if(a ==null || a.length==0) return null;
		T min =a[0];
		T max =a[0];
		for(int i=1;i<a.length;i++){
			if(min.compareTo(a[i])>0) min =a[i];
			if(max.compareTo(a[i])<0) max =a[i];
		}
		return new Pair<T>(min,max);
	 }
	/**
	 * 变量类型的限定
	 * T 限制为实现了Comparable接口,(只含有一个方法compareTo)的类。T extends Comparable
	 */
	public static <T extends Comparable> T min(T[] a){
		if(a == null || a.length==0)
			return null;
		T smallest = a[0];
		for(int i =1;i<a.length;i++){
			if(smallest.compareTo(a[i])>0)
				smallest = a[i];
		}
		return smallest;
	}
}

// 不能编译通过
Manager[] honchos = null;
Pair<Employee> result = ArrayAlg.minmax(honchos);// Error

minmax方法返回Pair<Manager>,而不是Pair<Employee>,并且这样的赋值时不合法的。无论S和T有什么关系,通常,Pair<S>和Pair<T>没有声明联系。如下图:



假设与许将Pair<Manager>转换为Pair<Employee>,考虑一下代码:

Pair<Manager> managerBuddies = new Pair<Manager>(ceo,cfo);
Pair<Employee> employeeBuddies = managerBuddies;
显然, managerBuddies和employeeBuddies引用了相同对象,现在将CFO和一个普通员工组成一对,这对于Pair<Manager>来说应该是不可能的。

注意:必须要注意泛型和Java数组之间的重要区别,可以将Manager[]数组赋给一个类型Employee[]的变量:

Manager[] managerBuddies = {ceo,cfo};
Employee[] employeeBuddies = managerBuddies; //OK

然而,数组带有特别的保护,如果试图将一个低级别的雇员存储到emplouyeeBuddies[0]中,虚拟机将会抛出ArrayStoreException异常。记住永远可以将参数化类型转换为一个原始类型。例如,Pair<Manager>是原始类型Pair的一个子类型。

Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
Pair<Manager> buddies = new Pair<Manager>(ceo, cfo);  
Pair rawBuddies = buddies ; //转换到原始类型
rawBuddies.setFirst(...);

转换成原始类型之后会产生类型错误吗?会!!,rawBuddies.setFirst(new File("...")),当使用getFisrt获得外来对象并赋给Manager变量时,与通常一样,会抛出ClassCastException异常。这里失去的只是泛型程序设计提供的附加安全性。

最后,泛型类可以扩展或实现其他的泛型类,就这一点而言,与普通的类没有什么区别。例如,ArrayList<T>类实现List<T>接口,这意味着,一个ArrayList<Manager>可以被转换为一个List<Manager>。但是,如前面所见,一个ArrayList<Manager>不是一个ArrayList<Employee>或List<Employee>。如下图所示:



2.3 泛型中的通配符(?)


在使用泛型类的时候,既可以指定一个具体的类型,如List<String>就声明了具体的类型是String;也可以用通配符?来表示未知类型,如List<?>就声明了List中包含的元素类型是未知的。通配符所代表的其实是一组类型,但具体的类型是未知的。List<?>所声明的就是所有类型都是可以的。但是List<?>并不等同于List<Object>。List<Object>实际上确定了List中包含的是Object及其子类,在使用的时候都可以通过Object来进行引用。而List<?>则其中所包含的元素类型是不确定。其中可能包含的是String,也可能是Integer。如果它包含了String的话,往里面添加Integer类型的元素就是错误的。正因为类型未知,就不能通过new ArrayList<?>()的方法来创建一个新的ArrayList对象。因为编译器无法知道具体的类型是什么。但是对于List<?>中的元素确总是可以用Object来引用的,因为虽然类型未知,但肯定是Object及其子类。考虑下面的代码:

public void wildcard(List<?> list) {
	list.add(1);//编译错误 
}

试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。

通配符类型Pair<? extends Employee>,表示任何泛型Pair类型,它的参数是Employee的子类,如Pair<Manager>,但不是Pair<String>。下面有个打印雇员的方法,

public static void printBuddies(Pair<Employee> p) {
	Employee first = p.getFirst();
	Employee second = p.getSecond();
	System.out.println(first.getName() + " and " + second.getName()+ " are buddies.");
}

这里不能将Pair<Manager>传递给这个方法,这点很受限制,解决方法就是使用通配符类型:

public static void printBuddies(Pair<? extends Employee> p) {//}

类型Pair<Manager>是Pair<? extends Employee>的子类型。


使用通配符通过Pair<? extends Employee>的引用会破环Pair<Manager>吗?

Pair<Manager> managerBuddies = new Pair<Manager>(ceo,cfo);
Pair<? extends Employee> wildcardBuddies = managerBuddies; // 引用完全OK,
//wildcardBuddies.setFirst(cko); //error

这个不会引起破坏。但是setFirst()方法的调用有一个类型错误,"The method setFirst(capture#1-of ? extends Employee) in the type Pair<capture#1-of ? extends Employee> is not applicable for the arguments (Manager)",在方法中无法捕获到Manager可用的参数。

仔细分析类型Pair<? extends Employee>,其方法似乎是这样的:

[? extends Employee] getFirst()
void setFirst(? extends Employee)

这样将不可以调用setFirst方法。编译器只知道需要某个Employee的子类型,但是不知道具体是什么类型。它拒绝传递任何特定的类型。使用getFirst就不存在这个问题:将getFirst的返回值赋给一个Employee的引用完全可以的。这就是引入有限定的通配符的关键之处,现在可以有办法区别安全的访问器方法和不安全的更改器方法了。

带有通配符超类型限定的通配符的行为与前面的通配符限定的行为恰好相反,可以为方法提供参数,但是不能使用返回值。例如,Pair<? super Manager>有方法:

void setFirst(? super Manager) 
? super Manager getFirst()

编译器不知道setFirst方法的确切类型,但是可以用任意Manager对象调用它,而不能用Empolyee对象调用。如果调用getFirst,返回的对象类型就不会得到保证,只能把它赋给一个Object。

有一个方法把一个经理的数组,并且想把奖金最高和最低的经理放在同一个Pair对象中,这里最合适的就是Pair类型就是Pair<Employee>,也可以是Pair<Object>。


/**
	 *  通配符? 上限
	 *  经理数组,把奖金最高和最低的经理放在一个Pair对象中。在这里Pair是Pair<Employee>类型的。Pair<Object>也可以
	 * @param a
	 * @param result
	 */
	public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) {
		if (a == null || a.length == 0)
			return;
		Manager min = a[0];
		Manager max = a[0];
		for (int i = 1; i < a.length; i++) {
			if (min.getBonus() > a[i].getBonus())
				min = a[i];
			if (max.getBonus() < a[i].getBonus())
				max = a[i];
		}
		result.setFirst(min);
		result.setSecond(max);
	}
直观地讲,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

下面是超类型限定的另一种应用。Comparable接口本身就是一个泛型类型,声明如下:

public interface Comparable<T> {

	public int compareTo(T o);
}

在此,类型变量指示了 o参数的类型。例如,String类实现Comparable<String>,它的compareTo方法被声明为:

public int compareTo(String anotherString) 

显式的参数又一个正确的类型,在JDK5.0之前anotherString是一个Object,并且这个方法的实现需要强制类型转换。由于Comparable是一个泛型类型,也许可以把ArrayAlg类中的mimax方法重构的更好一些。

public static <T extends Comparable<T>> Pair<T> minmax(T[] a) 
   {
      if (a == null || a.length == 0) return null;
      T min = a[0];
      T max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<T>(min, max);
   }

这样写比只使用 <T extends Complarable>更彻底,对许多类来讲工作的更好。比如说求一个String数组的最小值,T就是String类型的,而String是Complarable<String>的子类型。但是处理一个GregorianCalendar对象数组时,就会出现问题。GregorianCalendar是Calendar的子类,并且Calendar实现了Complarable<Calendar>。

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
	///实现了 Comparable<Calendar>接口
}
public class GregorianCalendar extends Calendar {
	///
}

因此GregorianCalendar也是实现的是Comparable<Calendar>,而不是Comparable<GregorianCalendar>。这时就可以使用超类型限制通配符:

public static <T extends Comparable<? super T>> Pair<T> minmax(T[] a){
	if (a == null || a.length == 0)
		return null;
	T min = a[0];
	T max = a[0];
	for (int i = 1; i < a.length; i++) {
		if (min.compareTo(a[i]) > 0)
			min = a[i];
		if (max.compareTo(a[i]) < 0)
			max = a[i];
	}
	return new Pair<T>(min, max);
}

而compareTo方法就可以写成:

public int compareTo(? super T)

有可能被声明为使用类型T的对象,也有可能使用T的超类型(例如,当T是GregorianCalendar)。无论如何,传递一个T类型的对象给compareTo方法都是安全的。


2.4 限定泛型的上下界

对于List<?>中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。如List<? extends Number>说明List中可能包含的元素类型是Number及其子类。而List<? super Number>则说明List中包含的是Number及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。比如访问List<? extends Number>的时候,就可以使用Number类的intValue()等方法。

有限制的通配符(Bounded Wildcards)考虑一个简单的画图程序:

public abstract class Shape {
	public abstract void draw(Canvas c);
}
//圆形
public class Circle extends Shape {
	private int    x, y, radius;
	public void draw(Canvas c) { 
		// ...
	}
}
//矩形
public class Rectangle extends Shape {
	
	private int  x, y, width, height;	
	public void draw(Canvas c) {
		// ...
	}
}

这些类可以在一个画布(Canvas)上被画出来:

public class Canvas {

	public void draw(Shape s) {
		s.draw(this);
	}
}

所有的图形通常都有很多个形状。假定它们用一个list来表示,Canvas里有一个方法来画出所有的形状会比较方便:

public void drawAll(List<Shape> shapes) {
	  for (Shape s : shapes) {
		 s.draw(this);
	 }
}

现在,drawAll()方法所作的只是从这个list读取shape,因此它应该也能对List<Circle>调用。我们真正要的是这个方法能够接受一个任意种类的shape:

public void drawAll(List<? extends Shape> shapes) { //..}


把类型 List<Shape> 替换成了 List<? extends Shape>。现在drawAll()可以接受任何Shape的子类的List,所以我们可以对List<Circle>进行调用。

List<? extends Shape>是有限制通配符的一个例子。这里 "?" 代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类(它可以是Shape本身或者Shape的子类而不必是extends自Shape)。我们说Shape是这个通配符的上限(upper bound)。像平常一样,要得到使用通配符的灵活性有些代价。这个代价是,现在向shapes中写入是非法的对象。比如下面的代码:

public void addRectangle(List<? extends Shape> shapes) {
   shapes.add(0, new Rectangle()); // compile error!
}

上面的代码是不允许的。因为shapes.add的第二个参数类型是? extends Shape ——一个Shape未知的子类。因此我们不知道这个类型是什么,我们不知道它是不是Rectangle的父类;它可能是也可能不是一个父类,所以这里传递一个Rectangle不安全。


注意:使用<?>或是<? extends SomeClass>的声明方式,意味著您只能通过该名称來取得所参考实例的信息,或者是移除某些信息,但不能增加它的信息,因为只知道当中放置的是SomeClass的子类,但不确定是什么类的实例,编译器不让您加入信息,理由是,如果可以加入信息的話,那么您就得記得取回的实例是什么类型,然后转换为原來的类型方可进行操作,这就失去了使用泛型的意义。

 


 

2.5 无限定通配符和捕获通配符

 

在泛型中,有一种无限制的通配符类型,比如Pair<?>,这里的“?”就代表了对这个Pair中元素没有任何限制或者根本不关心。在提供了方便性的同时它也有个很大的限制,那就是使用了无限制通配符类型的泛型Pair<?>,是不能添加任何元素进去的,除了null外。在类型Pair<?>有方法如:

? getFirst()
void setFirst(?)

getFirst的返回值只能赋给一个Object。setFirst方法不能被调用,甚至不能用Object调用。Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始的Pair类的setObject方法。

有时候这样的弱类型泛型对于有些简单的操作非常有用,例如,下面这个方法将用来测试一个Pair是否包含了指定的对象,它不需要实际的类型。

public static boolean hasNulls(Pair<?> p){
	return p.getFirst() == null || p.getSecond() ==null;
}

编写一个交换一个pair元素的方法:

public static void swap(Pair<?> p) {}

通配符不是类型变量,因此,不能在编写代码中使用“?”作为一个类型。也就是说,下面代码是非法的:

? t = p.getFirst(); // Error
p.setFirst(p.getSecond());
p.setSecond(t);

 

这个问题因为在交换的时候必须临时保存第一个元素。这个问题可以用辅助方法swapHelper解决,

public static <T> void swapHelper(Pair<T> p) {
	T t = p.getFirst();
	p.setFirst(p.getSecond());
	p.setSecond(t);
}

 

注意,swapHelper方法是一个泛型方法,而swap方法不是,它具有固定的Pair<?>类型的参数。现在可以由swap调用swapHelper方法:

public static void swap(Pair<?> p) {
	swapHelper(p);
}


 

这种情况下,swapHelper方法的参数T捕获通配符。它不知道是那种类型的通配符,但是,这是一个明确的类型,并且<T>swapHelper的定义只有在T指出类型时才有明确的含义。当然并不是一定要使用通配符。可以直接实现没有通配符的泛型方法<T> void swap(Pair<T> p)。

public static void maxminBonus(Manager[] a, Pair<? super Manager> result) {
	minmaxBonus(a, result);
	PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
}

在这里,通配符捕获机制是不可避免的。通配符捕获只有在许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。例如,ArrayList<Pair<T>>中的T永远不能捕获ArrayList<Pair<?>>中的通配符。数组列表可以保存两个Pair<?>,分别针对?的不同类型。

 

2.6 泛型接口

 

在JDK 5.0之后,不仅可以声明泛型类,还可以声明泛型接口,声明泛型接口和声明泛型类相似,直接在接口名称后面加上<T>即可。定义一个泛型接口:

interface Info<T>
{
	public T getValue();
}

泛型接口定义完成之后,就要实现这个泛型接口,定义泛型接口的子类有两种方式:

a).一种是直接在子类后面声明泛型。

b).一种是直接在子类实现的接口中明确指定泛型类型。

方式1,

class InfoImpl<T> implements Info<T>
{
	private T value;
	public InfoImpl(T v){
		this.setValue(v);
	}
	public void setValue(T v){
	this.value = v;
	}
	public T getValue(){
	return this.value;
	}
}
此程序泛型接口的子类声明了与接口中相同的泛型标志,使用以上子类的方法与前的类似,使用泛型接口的子类如下:

public class Demo
{
	public static void main(String[] args){
	Info<String> i = null;
	i = new InfoImpl<String>("zhangsan");
	System.out.println("value is "+i.getValue());
	}
}


使用方式2,

class InfoImpl implements Info<String>//指定类型为String
{
	private String value;
	public InfoImpl(String v){
		this.setValue(v);
	}
	public void setValue(String v){
	this.value = v;
	}
	public String getValue(){
	return this.value;
	}
}


此方式在子类实现接口时,直接在实现的接口处指定了具体的泛型类型String,这样在覆写Info接口中的getValue()方法时直接指明类型为String即可。使用此泛型接口的子类:

public class Demo
{
	public static void main(String[] args){
	Info<String> i = null;
	i = new InfoImpl("zhangsan");
	System.out.println("value is "+i.getValue());
	}
}

此时,在程序实例化子类对象时,不用再指定泛型,因为在声明子类时已经明确地指定了具体类型。


2.7 泛型方法及泛型类实例


在前面所有的所有的泛型操作都是将整个类进行泛型化,但同样也可以在类中定义泛型化的方法。泛型方法的定义与其所在的类是否是泛型类是没有任何关系的,所在的类可以是泛型类,也可以不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入的数据类型。泛型方法定义方式如下:


调用泛型方法,

说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。

定义一个泛型方法:

class GenericsMethodDemo
{
	public <T> T fun(T t){ //可以接收任意类型的数据
		return t;
	}
}
public class Demo
{
	public static void main(String[] args){
		GenericsMethodDemo gmd = new GenericsMethodDemo();
		String str = gmd.fun("zhangsan"); //接收字符串
		int i = gmd.fun(20);              //接收整型
		System.out.println(str);
		System.out.println(i);
	}
}

输出运行结果:

---------- java ----------
zhangsan
20

以上程序中的fun()方法是将接收的参数直接返回,而且在方法接收参数中使用了泛型操作,所以此方法可以接收任意类型的数据,而且此方法的返回值类型将由泛型决定。

如果可以通过泛型方法返回一个泛型类的实例化对象,则必须在方法的返回类型声明处明确地指出泛型标志。

通过泛型方法返回返回泛型类实例:

class Info<T extends Number>
{	//此处泛型 T 只能是数字类型
	private T var;

	public T getVar(){
		return var;
	}
	public void setVar(T var){
		this.var=var;
	}
	public String toString(){
		return this.var.toString();
	}
}
public class Demo
{	
	/**
	 * 泛型方法
	 * @param <T extends Number> 声明一个泛型 T,T只能是Number类型或子类型
	 * @return Info<T>该方法的返回值的类型
	 */
	public static <T extends Number> Info<T> fun(T params){
		// 根据传递的数据类型实例化Info对象
		Info<T> temp = new Info<T>();
		
		temp.setVar(params);
		//返回实例化对象
		return temp; 
	}

	public static void main(String[] args){
		//方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
		Info<Integer> i = fun(20);
		Info<Double> di = fun(22.22);
		
		System.out.println(i);
		System.out.println(di);
	}
}


为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。


2.8 泛型创建数组

泛型数组 只能作为参数类型或者方法参数。在Java中,Object[]数组可以是任何数组的父类,或者说,任何一个数组都可以向上转型成父类的数组,这个时候如果我们往里面放不同于原始数据类型 但是满足后来使用的父类类型的话,编译不会有问题,但是在运行时会检查加入数组的对象的类型,于是会抛ArrayStoreException:

String[] strArray = new String[20];
Object[] objArray = strArray;
objArray[0] = new Integer(1); // throws ArrayStoreException at runtime

(a)、参数类型
1.  泛型list的数组,形如:ArrayList<T>[]

ArrayList<T>[] list = new ArrayList<T>[n];
for(int i = 0; i < n; i++){
	list[i] = new ArrayList<T>;
}


(b)、泛型数组的集合,形如:ArrayList<T[n]>

import java.lang.reflect.Array;
//...
ArrayList<T[n]> lst = new ArrayList<T[n]>;
lst.add((T[])Array.newInstance(type,size));

 

Type类型为Class<T>,需要调用者指定,size为要开辟的数组长度;另外,具体创建数组中的参数,也需要用type来指定。

T[] t = lst.get(0);
for (int i = 0; i < size; i++)
t[] = type.newInstance();


在Java中,不能通过直接通过T[] tarr=new T[10]的方式来创建数组,最简单的方式便是通过Array.newInstance(Class<t>type,int size)的方式来创建数组例如下面的程序

public class ArrayMarker<T> {
	private Class<T> type;
	public ArrayMarker(Class<T> type) {
		this.type = type;
	}
	//数组
	T[] createArray(int size){
		return (T[]) Array.newInstance(type, size);
	}
	//集合
	List<T> createList(){
		return new ArrayList<T>();
	}
	public static void main(String[] args) {
		 
		ArrayMarker<Type> am2 = new ArrayMarker<Type>(Type.class);
		System.out.println(Arrays.asList(am2.createArray(10)));
		System.out.println(Arrays.asList(am2.createList()));
	}
}
class Type{
	public String toString(){
		return "type";
	}
}


程序输出结果:

[null, null, null, null, null, null, null, null, null, null]
[[]]


 

根据数组类型或普通类型的class创建数组:

 /**
	 * 根据普通类型的class创建数组
	 * @param <T> 目标类型
	 * @param clazz
	 * @param length 数组长度
	 * @return
	 */
	public static <T> T[] newArrayByClass(Class<T> clazz, int length) {
	    return (T[]) Array.newInstance(clazz, length);
	}
	/**
	 * 根据数组类型的class创建对应类型的数组
	 * @param <T> 目标类型
	 * @param clazz
	 * @param length 数组长度
	 * @return
	 */
	public static <T> T[] newArrayByArrayClass(Class<T[]> clazz, int length) {
		return (T[]) Array.newInstance(clazz.getComponentType(), length);
	}

上面的这个例子比较简单,但是如果你有接触过泛型数组,你便对他的复杂度有一定的了解,由于创建泛型数组比较复杂,所以在实际的应用过程中一般会选择List的对泛型进行存储,如果实在需要使用泛型数组,则需要注意数组的在运行时的类型,think in java这本书中,对泛型数组的处理通过四个小程序对其进行了比较完整的描述:

程序一:这个程序主要说明了,在使用泛型数组中容易出现的问题,由于书中对于程序的说明比较详细,所以只对程序做引用

public class ArrayofGenric {
	
	public static void main(String[] args) {

		Genric<Integer>[] genArr;
		
		//genArr = (Genric<Integer>[]) new Object[]{};
		//genArr = new Genric<Integer>[2];
		
		genArr = (Genric<Integer>[]) new Genric[2];
		System.out.println(genArr);
	}
}

class Genric<T> {
}

程序二:这个程序主要是说明在程序的执行过程中,泛型数组的类型信息会被擦除,且在运行的过程中数组的类型有且仅有Object[],如果我们强制转换成T[]类型的话,虽然在编译的时候不会有异常产生,但是运行时会有ClassCastException抛出

 

public class ArrayofGenric2<T> {
	private T[] ts;
	public ArrayofGenric2(int size) {
		ts = (T[]) new Object[size];
	}
	public T[] rep() {
		return ts;
	}
	public T get(int index) {
		return ts[index];
	}
	public void set(int index, T t) {
		ts[index] = t;
	}
	public static void main(String[] args) {
		ArrayofGenric2<String> aog2 = new ArrayofGenric2<String>(10);
		Object[] objs = aog2.rep();
		System.out.println(objs);
		// will throw ClassCastException
		// String[] strs = aog2.rep();
		// System.out.println(objs);
	}
}



程序三:主要说明在对象中通过用Object[]来保存数据,则生成对象是,可以对其持有的对象在T和object之间进行转换,但是当设计到数组的转换时,还是会报ClassCastException

public class ArrayofGenric3<T> {
	Object[] ts;
	public ArrayofGenric3(int size) {
		ts = new Object[size];
	}
	public T[] rep() {
		return (T[]) ts;
	}
	public T get(int index) {
		return (T) ts[index];
	}
	public void set(int index, T t) {
		ts[index] = t;
	}
	public static void main(String[] args) {
		ArrayofGenric3<Integer> aog3 = new ArrayofGenric3<Integer>(10);
		Object[] objs = aog3.rep();
		for (int i = 0; i < 10; i++) {
			aog3.set(i, i);
			System.out.println(aog3.get(i));
		}
		//ClassCastException
		//Integer[] strs = aog3.rep();
		//System.out.println(strs);
	}
}


程序四:是对泛型数组相对而言比较完美的解决方案

public class ArrayofGenric4<T> {

	T[] ts;
	public ArrayofGenric4(Class<T> type, int size) {
		// TODO Auto-generated constructor stub
		ts = (T[]) Array.newInstance(type, size);
	}
	public T[] rep() {
		return (T[]) ts;
	}
	public T get(int index) {
		return (T) ts[index];
	}
	public void set(int index, T t) {
		ts[index] = t;
	}
	public static void main(String[] args) {
		ArrayofGenric4<Integer> aog4 = new ArrayofGenric4<Integer>(Integer.class, 10);
		Object[] objs = aog4.rep();

		for (int i = 0; i < 10; i++) {
			aog4.set(i, i);
			System.out.println(aog4.get(i));
		}
		try {
			Integer[] strs = aog4.rep();
			System.out.println("User Array.newInstancer to create generic of array was successful!!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}



2.9 反射与泛型


从JDK5.0起,Class类是泛型的。例如,String.class实际上是一个Class<String>类的对象(唯一的对象)。类型参数非常有用,它允许Class<T>方法的返回类型更具有针对性。下面Class<T>中的方法就是使用了类型参数:

 

public T newInstance()
public T cast(java.lang.Object)
public T[] getEnumConstants()
public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader)
public static java.lang.Class<?> forName(java.lang.String)
public <U> java.lang.Class<? extends U> asSubclass(java.lang.Class<U>)
public java.lang.Class<?>[] getClasses()
public java.lang.reflect.Constructor<?>[] getConstructors()
public java.lang.Class<?>[] getDeclaredClasses()
public java.lang.reflect.Constructor<?>[] getDeclaredConstructors()
public java.lang.reflect.Type getGenericSuperclass()
public java.lang.String getSimpleName()
public java.lang.reflect.TypeVariable<java.lang.Class<T>>[] getTypeParameters()

有时匹配方法中的Class<T>参数的类型变量是很有价值的。下面有个应用示例:

public static <T> Pair<T> makePair(Class<T> c) throws Exception{

	return new Pair<T>(c.newInstance(),c.newInstance());
}


如果调用makePair(Employee.class),Employee.class就是类型Class<Employee>的一个对象。makePair方法的类型参数T同Employee匹配,并且编译器可以推断出这个方法返回一个Pair<Employee>。

为了表达泛型类型声明,JDK5.0在java.lang.reflect包中提供了一个新的接口Type。这个接口包含下列子类型:

Class 类,描述具体类型。
TypeVariable 接口,描述类型变量(如T extends Comparable<? super T>)。
WildcardType 接口,描述通配符(如 ? super T)。
ParameterizedType 接口,描述泛型类或接口类型(如Complarable<? super T>)。
GenericArrayType 接口,描述泛型数组(如T[])。




java.lang.Class 类,提供了一个 getGenericSuperclass() 方法来获取直接超类的泛型类型。

 

public class Base<T> {

	private Class<T> entityClass;

	//泛型的 实际类型参数 的类全名
	public String getEntityName(){
		return entityClass.getName();
	}
	//泛型的 实际类型参数 的类名
	public String getEntitySimpleName(){
		return entityClass.getSimpleName();
	}
	//泛型的 实际类型参数 的Class
	public Class<T> getEntityClass(){
		return entityClass;
	}
	
	{
		//entityClass = (Class<T>) ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
		try {
			//获得实际运行的类的Class
			Class<?> clazz = getClass();
			//获得实际运行的类的直接超类的泛型类型
			Type type = clazz.getGenericSuperclass();
			//如果该泛型类型是参数化类型
			if (type instanceof ParameterizedType) {
				//获取泛型类型的实际类型参数集
				Type[] parameterizType = ((ParameterizedType)type).getActualTypeArguments();
				//取出第一个(下标为0)参数的值
				entityClass = (Class<T>) parameterizType[0];
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


(Class<T>) parameterizedType[0],怎么就知道第一个参数(parameterizedType[0])就是该泛型的实际类型呢?很简单,因为 Base<T> 的泛型的类型参数列表中只有一个参数,所以,第一个元素就是泛型 T 的实际参数类型。

public static void main(String[] args) {
		System.out.println("用getGenericSuperclass()方法来获取直接超类的泛型类型.");
		Base<String> base = new Base<String>();
		System.out.println(base.getEntityClass());
		//System.out.println(base.getEntityName());//NullPointerException
		//System.out.println(base.getEntitySimpleName());//NullPointerException

}


从打印的结果来看,Base 类并不能直接来使用,为什么会这样?原因很简单,由于 Base 类中的 clazz.getGenericSuperclass() 方法,返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。如果超类是参数化类型,则返回的 Type 对象必须准确反映源代码中所使用的实际类型参数。如果此 Class 表示 Object 类、接口、基本类型或 void,则返回 null。如果此对象表示一个数组类,则返回表示 Object 类的 Class 对象。所以Base 类不能够直接来使用,而是应该通过其子类来使用,Base 应该用来作为一个基类,我们要用的是它的具体的子类。

public class Children extends Base<Children>{

	
}
Children child = new Children();
System.out.println(child.getEntityClass());
System.out.println(child.getEntityName());
System.out.println(child.getEntitySimpleName());


程序输出结果:

class Children
Children
Children


泛型反射的关键是获取 ParameterizedType 接口,再调用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可获得实际绑定的类型。由于去参数化(擦拭法),也只有在 超类(调用 getGenericSuperclass 方法) 或者成员变量(调用 getGenericType 方法)或者方法(调用 getGenericParameterTypes 方法)像这些有方法返回 ParameterizedType 类型的时候才能反射成功。

下面将变量和方法的两种反射获得泛型的实际类型参数说下。因为泛型编译后会去参数化(擦拭法),因此无法直接用反射获取泛型的参数类型,可以把泛型用做一个方法的参数类型,方法可以保留参数的相关信息,这样就可以用反射先获取方法的信息,然后再进一步获取泛型参数的相关信息,这样就得到了泛型的实际参数类型。通过方法,反射获得泛型的实际类型参数:

public void applyCollection(Collection<Number> collection){
	//声明一个空方法,并将泛型用作方法的参数类型
}

public static void main(String[] args) {
	
	System.out.print("通过方法,反射获得泛型的实际类型参数:");
	try {
		Class<?> clazz = Test.class;
		Method method = clazz.getDeclaredMethod("applyCollection", Collection.class);//获得方法
		Type[] type = method.getGenericParameterTypes();//获得泛型类型参数集
		ParameterizedType ptype = (ParameterizedType) type[0];//将其转成参数化类型,因为在方法中泛型是参数,且Number是第一个类型参数
		type = ptype.getActualTypeArguments();//获得参数的实际类型
		System.out.println(type[0]);//取出第一个元素
	} catch (Exception e) {
		e.printStackTrace();
	}
}


 程序输出结果:

通过方法,反射获得泛型的实际类型参数:
class java.lang.Number


通过字段变量,反射获得泛型的实际类型参数:


 

private Map<String, Number> collection;

public static void main(String[] args) {
	
	System.out.println("通过字段变量,反射获得泛型的实际类型参数:");
	try {
		Class<?> clazz = Test.class;
		Field field = clazz.getDeclaredField("collection");//获得字段变量
		Type type = field.getGenericType();//获得泛型的类型
		ParameterizedType ptype = (ParameterizedType) type;//转换成参数化类型
		System.out.println(ptype.getActualTypeArguments()[0]);//取出第一个参数的实际类型
		System.out.println(ptype.getActualTypeArguments()[1]);//取出第二个参数的实际类型
	} catch (Exception e) {
		e.printStackTrace();
	}
}


程序输出结果:

通过字段变量,反射获得泛型的实际类型参数:
class java.lang.String
class java.lang.Number

 




------- android培训、java培训、期待与您交流! ----------


最后

以上就是超帅盼望为你收集整理的黑马程序员——Java 泛型Java 泛型(Generircs)的全部内容,希望文章能够帮你解决黑马程序员——Java 泛型Java 泛型(Generircs)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部