Java基础(九)——泛型

     介绍泛型之前,先来张图,从宏观上认识一下我们今天要介绍的主角:泛型。这张导图是笔者自己对泛型的一个整体印象,下面我们从why、what、how三个角度来分析一下,我们说的泛型。

                                     

                                                                                                                                                                         图一    泛型概括


     一、Why


    Java集合有个缺点,把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出对象时,该对象的编译类型就变成了Object类型。集合最元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常;由于把对象"丢进"集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出来集合元素后通常还需要进行强制类型转换。这种强制转换既增加了编程的复杂度,也可能引发ClassCastException异常。

     从Java5以后,Java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型(还有定义类的时候的成员变量,在类型不确定时也可以使用泛型表示),如我们平时定义list时:List<String>,这表明List只能保存字符串类型的对象。Java的参数化类型被称为泛型(Generic)。引入泛型,便可以是List对象记住它所有集合元素都是什么类型,很大程度上提高了程序的健壮性。


     二、What


    所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。在Java7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器后面也必须带泛型:


     三、How

     1.定义泛型接口、泛型类和泛型方法

    

    (1)定义泛型接口

//定义接口时指定了一个类型形参,该形参名为E
public interface List<E> extends Collection<E> {
    //在该接口里,E可作为类型使用
    //下面的方法里使用E作为参数类型
    void add(E X);
    Iterator<E> iterator();
 
}

//定义该接口时指定了两个类型形参,K、V
public interface Map<K,V>
{
    //在该接口里K、V完全可以作为类型使用
    Set<K> keySet();
    V put(K key, V value)
}


    (2)定义泛型类

package cn.gome.d_generic;

import java.util.List;

import org.omg.CORBA.portable.ApplicationException;

public class Apple<T>{
	//使用T类型形参定义实例变量
	private T info;
	//使用T类型参数定义构造器
	public Apple(T info){
		this.info = info;
	}
	public T getInfo() {
		return info;
	}
	public void setInfo(T info) {
		this.info = info;
	}
	
	public static void main(String[] args) {
		//传给T的形参类型是String,所以构造器参数只能是String
		Apple<String> apple = new Apple<>("苹果");
		System.out.println(apple.getInfo());
		//传给T的形参类型是Double,所以构造器参数只能是Double
		Apple<Double> apple2 = new Apple<>(5.26);
		System.out.println(apple2.getInfo());
	}	
}


     注意:从泛型类派生子类

//继承Apple类时,Apple类不能跟类型形参,编译错误
public class AppleChild1 extends Apple<T>{}

     如果想从Apple类派生一个子类,则可以改为如下代码

package cn.gome.d_generic;

public class AppleChild extends Apple<String>{

	public AppleChild(String info) {
		super(info);
	}
	
	public static void main(String[] args) {		
		AppleChild appleChild = new AppleChild("嘟嘟");
		System.out.println(appleChild.getInfo());
	}
}


     (3)定义泛型方法

 

     Java5提供了对泛型方法的支持,在定义类、接口时没有使用类型形参,但定义方法时想自己定义类型形参,也是可以的。

     假如我们需要实现这样一个方法:该方法负责将一个Object数组的所有元素添加到一个Collection集合中,考虑用如下代码类实现该方法:

package cn.gome.d_generic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class GenericTest1 {
	public static void fromArrayToCollection(Object[] a,Collection<Object> c){
		for (Object aObject : a) {
			c.add(aObject);
		}
	}
	
	public static void main(String[] args) {
		String[] strArr = {"a","b"};
		List<String> strList = new ArrayList<>();
		//Collection<String>对象不能当成Collection<Object>使用,下面代码出现编译错误
		GenericTest1.fromArrayToCollection(strArr, strList);
	}
}


    上面的代码中我们可以看到,Collection<String>作为方法的形参功能是十分有限的,因为Collection<String>不是Collection<Object>的子类型,为了解决这个问题,可以使用Java5提供的泛型方法,所谓泛型方法,就是在声明方式时定义一个或多个类型形参。

     泛型方法的语法格式如下:

修饰符 <T,S> 返回值类型 方法名(行参列表){
	//方法体...
}


     泛型方法的声明比普通方法的签名多了类型类型形参声明,类型形参用尖括号括起来,多个类型形参之间以逗号(,)隔开,所有类型形参声明放在方法修饰符和方法返回值类型之间。下面是使用泛型方法改写的类,使用了泛型方法,可以保持集合传入的类型形参之间原有的继承关系,增加了程序了灵活性。


package cn.gome.d_generic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class GenericTest2 {
	public static <T> void fromArrayToCollection(T[] a,Collection<T> c){
		for (T t : a) {
			c.add(t);
		}
	}
	
	public static void main(String[] args) {
		Object[] object = new Object[100];
		Collection<Object> collection = new ArrayList<>();
		//T代表Object类型
		fromArrayToCollection(object,collection);
		
		String[] strings = new String[100];
	    Collection<String> cStrings = new ArrayList<>();
	    //T代表String类型
	    fromArrayToCollection(strings, cStrings);
	    
	    Integer[] integers = new Integer[100];
	    Collection<Number> cNumbers = new ArrayList<>();
	    //T代表Number类型
	    fromArrayToCollection(integers, cNumbers);
	}
}


     2.类型形参

     Java泛型可以在定义类型形参时设定上限,用于表示传给改类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。

package cn.gome.d_generic;

public class Banana<T extends Number> {
	T col;
	public static void main(String[] args) {
		Banana<Integer> bi = new Banana<>();
		Banana<Double> ad = new Banana<>();
		
		//String不是Number的子类型,所以引起编译错误
		//Banana<String> bs = new Banana<>();
	}
}

     3.类型通配符

     为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个(?),将一个问号作为类型实参传给List集合,写作List<?>,也就是元素类型是未知类型的List。这个(?)被称为通配符,它的元素可以匹配任何类型。下面通过一个简单的例子来解释一下:


    (1)抽象的Shape类

package cn.gome.d_generic;

//形状抽象类
public abstract class Shape {
	public abstract void draw(Canvas c);
}


     (2)具体的Circle和Rectangle类


       Circle类:

package cn.gome.d_generic;

public class Circle extends Shape {

	@Override
	public void draw(Canvas c) {		
		System.out.println("在画布" + c + "上换一个圆");
	}
}


       Rectangel类:
package cn.gome.d_generic;

public class Rectangle extends Shape {

	@Override
	public void draw(Canvas c) {
		System.out.println("在画布" + c + "上画一个三角形");
	}
}


    (3)画布类

package cn.gome.d_generic;

import java.util.ArrayList;
import java.util.List;

//画布类
public class Canvas {
	public void drawAll(List<Shape> shapes){
		for (Shape shape : shapes) {
			shape.draw(this);
		}
	}
	
	public static void main(String[] args) {
		List<Circle> circles = new ArrayList<>();
		Canvas canvas = new Canvas();
		//不能把List<Circle>当成List<Shape>使用,所以下面代码引起编译错误
		//canvas.drawAll(shapes);
	}
}


     关键在于List<Circle>并不是List<Shape>的子类型,所以不能把List<Circle>对象当成List<Shape>使用,为了表示List<Circle>父类,可以考虑使用List<?>,把Canbas改为如下形式:

package cn.gome.d_generic;

import java.util.ArrayList;
import java.util.List;

//画布类
public class Canvas {
	public void drawAll(List<?> shapes){
		for (Object o: shapes) {
			Shape shape  = (Shape)o;
			shape.draw(this);
		}
	}
	
	public static void main(String[] args) {
		List<Circle> circles = new ArrayList<>();
		Canvas canvas = new Canvas();	
		canvas.drawAll(circles);
	}
}


     上面程序使用了通配符来表示所有的类型,但是的drawAll()方法可以接受List<Circle>对象作为参数,问题是上面的方法实现显得极为臃肿而繁琐,使用了泛型还需要进行强制类转换。实际上需要一种泛型表示方法,它可以表示所有Shape泛型List的父类,Java泛型提供了被限制的泛型通配符:


     (1)设定通配符上限:List<? extends T>  表示List接收的形式参数,只能是类型T的或者类型T的子类。

     (2)设定通配符下限:List<? super T> 表示List接收的形式参数,只能是T本身,或者是T的父类。


     使用上限通配符,改写上面的Canvas类如下:

package cn.gome.d_generic;

import java.util.ArrayList;
import java.util.List;

//画布类
public class Canvas {
	public void drawAll(List<? extends Shape> shapes){
		for (Shape o: shapes) {			
			o.draw(this);
		}
	}
	
	public static void main(String[] args) {
		//画圆
		List<Circle> circles = new ArrayList<>();
		circles.add(new Circle());
		circles.add(new Circle());		
		//画三角形
		List<Rectangle> rectangles = new ArrayList<>();
		rectangles.add(new Rectangle());
		rectangles.add(new Rectangle());
		rectangles.add(new Rectangle());
		
		Canvas canvas = new Canvas();	
		canvas.drawAll(circles);
		canvas.drawAll(rectangles);
	}
}


     执行结果如下:

     



 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值