【java 2】java泛型

一、Java泛型的基本内容

1.Java泛型的由来

 泛型是Java SE 5.0中引入的新特征
 目标:编写更通用的代码,使其可应用于“某种不具体的类型”
 Java泛型 Vs. C ++模板
 举例:ArrayList<T>
 语法:类型变量由尖括号界定,放在类名之后

2.泛型的作用

为程序员节省某些 Java 类型转换上的操作

List<Apple> box= ...;

Apple apple =box.get(0);//--返回apple实例,不需要类型转换

如果没有泛型:

List box = ...;

Apple apple =(Apple)box.get(0);//--需要类型转换

泛型的好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作,从而保证类型正确性

3.泛型的实现方法

3.1泛型类

具有一个或多个泛型的类。实例:

package mypackage;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class Point<T>//泛型类
{
	private T m_var;
	public T GetValue()
	{
		return m_var;
	}
	public void SetValue(T var)
	{
		m_var = var;
	}
}
public class GenericDemo1 {
	public static void main(String args[]) throws IOException
	{
		//实例化泛型类,泛型指定为String
		Point<String> p=new Point<String>();
		//提示输入一个字符串
		System.out.println("Please input a string:");
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String var = br.readLine();
		
		p.SetValue(var);
		System.out.println("Your input is:"+p.GetValue());
	}

}

3.2泛型方法

在泛型类或非泛型类中都可以包含参数化方法,泛型方法使得该方法能够独立于类而产生变化。
实例:
package mypackage1;
class Demo{
	//泛型参数列表置于返回值之前
	public <T> T fun(T var)
	{
		return var;
	}
}
public class GenericDemo2 {
	public static void main(String args[]){
		//使用泛型类时,必须在创建时指定类型参数的值;使用泛型方法时,编译器会为
		//我们找出具体的类型--类型参数诊断
		Demo d = new Demo();
		String str = d.fun("strTest");
		int i = d.fun(100);
		System.out.println("String output:"+str);
		System.out.println("Integer output:"+i);
	}
}

对于
static 方法而言,无法访问泛型类的类型参数,就需要将该方法泛型化。




二、Java泛型的关键技术

1.擦除

通用理解:
擦除是将泛型类型以其父类代替,如String 变成了Object等。其实在使用的时候还是进行带强制类型的转化,只不过这是比较安全的转换,因为在编译阶段已经确保了数据的一致性。

在泛型代码内部无法获得任何有关泛型参数的类型信息
ArrayList<String> ArrayList<Integer> 运行时被认为是相同的类型,两者都被擦除成它们的“原生类型”,即 List ;而普通的类型变量 ( 例如 <String>) 在未指定边界的情况下被擦除为 Object
Java 泛型实现的折中机制,减少了泛型的泛化性

核心动机:
使得泛化的客户端可以用非泛化的类库来使用,在不破坏现有类库的情况下将泛化融入 Java—— 迁移兼容性
代价:
不能用于显示的引用运行时类型的操作之中,如转型、 instanceof new
但编译器仍然保持类型的内部一致性

类型擦除的主要过程如下:
     1.
将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
     2.
移除所有的类型参数。


1.1、对于擦除的补偿:

擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要某些确切类型的信息都无法工作。

通过引入 类型标签 来对擦除进行补偿
显示传递类型的 Class 对象

1.2、创建类型实例

在泛型类中无法用 new T() 创建参数类型实例
关于 T 的信息被擦除
编译器不能验证 T 具有默认构造函数
解决方案:传递一个工厂对象,如 Class 对象
用Class类的newInstance方法创建实例
class ClassAsFactory<T>{
	T x;
	public ClassAsFactory(Class<T> kind){
		try{
			x=kind.newInstance();
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
}


1.3、创建泛型数组

泛型类中无法创建一个确切类型的数组(包括类型参数)
数组将跟踪它们的实际类型,这个类型是在数组被创建时确定的,只存在于编译期。
运行时,仍旧是 Object 数组。
解决方案:创建一个被擦除类型的新数组并对其转型
转型时机最好选在需要返回数组元素时,内部仍然使用 Object[]
public class GenericDemo3<T> {
	private Object[] array;
	public GenericDemo3(int size){
		//创建一个Object数组
		array = new Object[size];
	}
	public void put(int index, T item){
		array[index] = item;
	}
	//该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。
	@SuppressWarnings("unchecked") 
	public T get(int index){
		//返回前强制类型转换
		return (T)array[index];
	}
	@SuppressWarnings("unchecked")
	public T[] rep(){
		return (T[])array;
	}
}

2.边界

产生原因:
由于擦除移除了类型信息,所以可以用无界泛型参数调用的方法只是那些可以用 Object 调用的方法。
通过“边界”可以将参数限制为某个类型的子集,从而可以用这些类型子集调用类型的方法。
目的:
强制规定泛型可应用的类型
可以按照自己定义的边界类型来调用方法
关键字: extends

Java泛型编程中使用extends关键字指定泛型参数类型的上边界(后面还会讲到使用super关键字指定泛型的下边界),即泛型只能适用于extends关键字后面类或接口的子类。
Java泛型编程的边界可以是多个,使用如<T extends A & B & C>语法来声明,其中只能有一个是类,并且只能是extends后面的第一个为类,其他的均只能为接口(和类/接口中的extends意义不同)。
使用了泛型边界之后,泛型对象就可以使用边界对象中公共的成员变量和方法。

package mypackage3;
import java.awt.Color;
//import java.awt.Dimension;
interface HasColor{
	Color getColor();
}

class Colored<T extends HasColor>{
	T item;
	Colored(T item){
		this.item=item;
	}
	Color color(){
		//调用HasColor接口实现类的getColor()方法
		return item.getColor();
	}
}
class Dimension{public int x,y,z;}
class ColoredDimension<T extends Dimension & HasColor>{
	T item;
	ColoredDimension(T item){
		this.item=item;
	}
	T getItem()
	{
		return item;
	}
	Color color(){
		//调用HasColor接口实现类的getColor()方法
		return item.getColor();
	}
	//获取Dimension类中定义的x,y,z成员变量
	int getX(){
		return item.x;
	}
	int getY(){
		return item.y;
	}
	int getZ(){
		return item.z;
	}
}
interface Weight{
	int weight();
}
class Solid<T extends Dimension & HasColor & Weight>{
	T item;
	Solid(T item){
		this.item=item;
	}
	T getItem(){
		return item;
	}
	//调用HasColor接口实现类的getColor()方法
	Color color(){
		return item.getColor();
	}
	int getX(){
		return item.x;
	}
	int getY(){
		return item.y;
	}
	int getZ(){
		return item.z;
	}
	int weight(){
		return item.weight();
	}
}
class Bounded extends Dimension implements HasColor, Weight{
	public Color getColor(){
		return null;
	}
	public int weight(){
		return 0;
	}
}
public class GenericDemo4 {
	public static void main(String[] agrs){
		Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
		solid.color();
		System.out.println(solid.getX());
		solid.getY();
		solid.getZ();
		solid.weight();
	}

}

3.通配符

设计如下的类层次结构:

这样的赋值是允许的:

Applea=…;

Fruit f=a;

这样的赋值是无效的:

List<Apple> apples=…;

List<Fruit> fruits=apples;

说明:
如果 A B 的子类型则 B b =a;
但是 List<A> List<B> 无关,不能直接赋值
需要使用通配符

****?extends通配符
通过通配符完成 List<A> 的实例到 List<B> 的赋值

List<Apple>apples = new ArrayList<Apple>();

List<? extends Fruit>fruits =apples;

<? extends Fruit> 可以读作“具有任何从 Fruit 继承的类型的列表”
通配符引用的是明确的类型,我们不知道 T 究竟是什么,所以为了保证安全不允许加入任何类型的数据

//fruits.add(new Fruit());

//fruits.add(new Apple());

//fruits.add(new Strawberry());

//fruits.add(new Object());  --无法向fruits中添加任何类型的对象

Fruit f=fruits.get(o);//唯一可以完成的操作是从fruits中取出一个对象,因为此时对象的类型是明确的


****?super---超类型通配符

List<Fruit>fruits = new ArrayList<Fruit>();

List<? super Apple>= fruits;//Fruits指向装有Apple的某种超类的List,不明确是什么超类,但知道Apple和任何Apple的子类都跟它的类型兼容

添加和读取操作:

fruits.add(new Apple());

fruits.add(new FujiApple());//可以添加Apple以及Apple的子类

//fruits.add(new Fruit());

//fruits.add(new Object());//但不能添加Apple的任何超类

Object o=fruits.get(0);//从fruits中取出的对象只能确保是Object类型

总结:如果你想从一个数据类型里获取数据,使用? extends通配符

           如果你想把对象写入一个数据结构里,使用 ? super 通配符
    PECS 法则( Joshua Bloch《Effective Java》 ):
    Producer Extends, Consumer Super

无界通配符<?>
public class CaptureConversion{
	//f1中类型确定,没有通配符或边界
	static <T> void f1(ArrayList<T> array){
		T t = array.get(0);
		System.out.println(t.getClass().getSimpleName());
	}
	//f2中ArrayList参数是无界通配符。因此看起来是未知的
	static void f2(ArrayList<?> array){
		f1(array);
	}
}

说明:f2()中调用f1(),而f1()需要一个已知参数,这里发生的是:参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中使用。

package mypackage4;

class Info<T>{
	private T var;  //定义泛型 变量
	public void setVar(T var){
		this.var = var;
	}
	public T getVar(){
		return this.var;
	}
	public String toString(){ //直接打印
		return this.var.toString();
	}
}

public class GenericDemo5 {
	public static void main(String args[]){
		Info<String> i= new Info<String>(); //使用String为泛型类型
		i.setVar("test");                   //设置内容
		fun(i);
	}
	public static void fun(Info<?> temp){   //可以接收任意的泛型对象
		System.out.println("内容"+temp);
	}
}

三、泛型的局限性

不能实例化类型变量
参数化类型的数组不合法
任何基本类型都不能作为参数类型
可以创建一个 ArrayList<Integer> 数组,但不能创建 ArrayList<int> 数组
原因:擦除后 ArrayList 类含有 Object 类型的域,但 Object 不能存储 int
解决方案:

  Java自动包装机制——自动实现intInteger的双向转换

包装器类(wrapper

声明为 final jdk 实现)
ArrayList <Integer> 效率远低于 int[]

对于Integer的特别说明
Integer 声明为 final ,因此不能定义它们的子类
//Jdk1.5对于Integer类的声明
public final class Integer
extends Number
implements Comparable<Interger>
对于 Integer 的运算操作,都是先自动拆包成 int 然后进行的,计算完成后再自动打包成 Integer
public static void main(String args[]){
	Integer i=new Integer(1);//Jdk1.5以下版本
	Integer j=1;//Jdk1.5
	
	int a=j.intValue();//手动拆箱
	int b=j;//自动拆箱
	j++;//先进行自动拆箱后进行++
	System.out.println("a="+a+", b="+b+", j="+j);
}

不能实现同一个泛型接口的两种变体
由于擦除原因,这两个变体会成为相同的接口
泛型规范原则:要想支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一个接口的不同参数化。

//CompileTimeError

Interface Payable<T>{}

Class Employee implements Payable<Employee>{}//都被擦除为Payable,相当于重复实现同一个接口,发生冲突tu

Class Hourly extends Employee implementsPayable<Hourly>{}

但是删除参数类型后这段代码是合法的。

思考题:
1.比较Java泛型和C++模板在内部实现机制上有什么不同。(提示:Java泛型使用擦除机制,C++模板呢?)

(1)对于Java泛型的参数类型,任何基本类型都不能作为参数类型,如可以创建一个ArrayList<Integer>数组,但不能创建ArrayList<int>数组
(2)Java泛型不能实现同一个泛型接口的两种变体由于擦除原因,这两个变体会成为相同的接口。在 C++ 模板中,编译器使用提供的类型参数来扩充模板,因此,为  List<A> 生成的 C++ 代码不同于为 List<B> 生成的代码,List<A> 和 List<B> 实际上是两个不同的类。而 Java 中的泛型则以不同的方式实现,编译器仅仅对这些类型参数进行擦除和替换。类型 ArrayList<Integer> 和 ArrayList<String> 的对象共享相同的类,并且只存在一个 ArrayList 类。因此在c++中存在为每个模板的实例化产生不同的类型,这一现象被称为“模板代码膨胀”,而java则不存在这个问题的困扰。java中虚拟机中没有泛型,只有基本类型和类类型,泛型会被擦除,一般会修改为Object,如果有限制,例如 T extends Comparable,则会被修改为Comparable。而在C++中不能对模板参数的类型加以限制,如果程序员用一个不适当的类型实例化一个模板,将会在模板代码中报告一个错误信息。

2.尝试使用通配符,完成向一个具有<? extends Fruit>(或者<? super Apple>)类型参数的数组中添加和取出各种类型实例,试试能否成功?



List<? extends Fruit> 表示 “具有任何从Fruit继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List<Apple> 赋值。
List<? super Apple> 表示“具有任何Apple超类型的列表”,列表的类型至少是一个 Apple 类型,因此可以安全的向其中添加Apple 及其子类型。由于List<? super Apple>中的类型可能是任何Apple的超类型。

代码如下:
package mypackage5;

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



class Fruit extends Object{}
class Apple extends Fruit{}
class Strawberry extends Fruit{}
class FujiApple extends Apple{}


public class GenericDemo6 {
	List<Apple> apples = new ArrayList<Apple>();
	public void upperBound(List<? extends Fruit> fruits)
	{
		fruits=apples;
		//无法向fruits中添加任何类型的对象
		fruits.add(new Fruit());
		fruits.add(new Apple());
		fruits.add(new Strawberry());
		fruits.add(new Object());
		fruits.add(null);//唯一能add是null
		//唯一可以完成的操作时从fruit作用取出一个对象,因为此时对象是明确的。
		Fruit f = fruits.get(3);
		Apple a = (Apple)fruits.get(2);
	}
	List<Fruit> fruits1 = new ArrayList<Fruit>();
	//Fruits指向装有Apple的某种超类的List,不明确是什么超类,但知道Apple和任何Apple的子类都跟它的类型兼容
	public void lowerBound(List<? super Apple> fruits2){
		fruits2=fruits1;
		//可以添加Apple以及Apple的子类
		fruits2.add(new Apple());
		fruits2.add(new FujiApple());
		//但不能添加Apple的任何超类
		fruits2.add(new Object());
		fruits2.add(new Fruit());
		//从fruits中取出的对象只能确保是Object类型
		Object o=fruits2.get(1);
		Fruit f=fruits2.get(1);
	}
}


存在疑问:

为什么如果fruits变量不是方法的参数时,根本无法使用add方法??该问题尚未解决。

3.Web开发中,我们经常会遇到将对象序列化并传递的问题。利用泛型设计一个工具类,完成将某个对象转换成其他对象类型(如XML)的功能。
 将一个java对象转换成xml文件,或者xml文件转换为一个java对象。采用jaxb api就可以做到。由于初步学习,参考了相关资料。
具体实现步骤如下:
1.创建一个GenericTest工程,new一个mypackage6的包,在包里new两个类,Person1.java和GenericDemo6.java。
2.Person1.java类主要是用于测试代码的java对象。
package mypackage6;

import java.util.Calendar;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="Test",namespace="mypackage6") //这句很重要,否则会报错
public class Person1 {
	 @XmlElement
	    Calendar birthDay; //birthday将作为person的子元素

	    @XmlAttribute
	    String name; //name将作为person的的一个属性

	    public Address getAddress() {
	        return address;
	    }

	    @XmlElement
	    Address address; //address将作为person的子元素

	    @XmlElement
	    Gender gender; //gender将作为person的子元素

	    @XmlElement
	    String job; //job将作为person的子元素

	    public Person1(){
	    }
	    
	    public Person1(Calendar birthDay, String name, Address address, Gender gender, String job) {
	        this.birthDay = birthDay;
	        this.name = name;
	        this.address = address;
	        this.gender = gender;
	        this.job = job;
	    }
	    
	}


	//Gender枚举
enum Gender{
	    MALE(true),
	    FEMALE (false);
	    private boolean value;
	    Gender(boolean _value){
	        value = _value;
	    }
	}

	//Address类
class Address {
	    @XmlAttribute
	    String country;
	    @XmlElement
	    String state;
	    @XmlElement
	    String city;
	    @XmlElement
	    String street;
	    String zipcode; //由于没有添加@XmlElement,所以该元素不会出现在输出的xml中

	    public Address() {
	    }

	    public Address(String country, String state, String city, String street, String zipcode) {
	        this.country = country;
	        this.state = state;
	        this.city = city;
	        this.street = street;
	        this.zipcode = zipcode;
	    }


public String getCountry() {
	        return country;
	    }
	}



2.GenericDemo6是主要的实现代码和main测试代码。
package mypackage6;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Calendar;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

class JaxbUtils<T>{//T为泛型

	private static final String DEFAULT_ENCODING = "gbk";
	/**
	 * 指定对象和编码方式将对象解析为xml字符串
	 * @param <T>
	 * 
	 * @param obj
	 * @param encoding
	 * @return
	 * @throws IOException 
	 * @throws JAXBException 
	 */
	public String objectToXmlString(T obj, String encoding) throws JAXBException, IOException {
		return objectToXmlString(obj, true, false, encoding);
	}

	/**
	 * 按照默认的编码方式将对象解析为xml字符串
	 * @param <T>
	 * 
	 * @param obj
	 * @return
	 * @throws IOException 
	 * @throws JAXBException 
	 */
	public String objectToXmlString(T obj) throws JAXBException, IOException {
		return objectToXmlString(obj, null);
	}

	/**
	 * 
	 * @param <T>
	 * @param obj
	 * @param isFormat
	 *            是否格式化
	 * @param cancelXMLHead
	 *            是否省略xml文件头
	 * @param encoding
	 *            编码方式, 默认为“gb312”
	 * @return
	 * @throws JAXBException 
	 * @throws IOException 
	 */
	public String objectToXmlString(T obj, boolean isFormat,boolean cancelXMLHead, String encoding) throws JAXBException, IOException {
			if (encoding == null) {
				encoding = DEFAULT_ENCODING;
			}

			JAXBContext context = JAXBContext.newInstance(obj.getClass());//获取对象类
			Marshaller m = context.createMarshaller();
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormat);
			m.setProperty(Marshaller.JAXB_FRAGMENT, cancelXMLHead);
			m.setProperty(Marshaller.JAXB_ENCODING, encoding);
			ByteArrayOutputStream out = new ByteArrayOutputStream();

			m.marshal(obj, out);
			String xmlString = new String(out.toByteArray());//xmlString为最后解析后的xml文件内容
			out.flush();
			out.close();
			System.out.println(xmlString);
			return xmlString;
	}
}
public class GenericDemo6{
	public static void main(String args[])
	{
		JaxbUtils<Person1> var=new JaxbUtils<Person1>();
		Address address = new Address("China", "Beijing", "Beijing","xituchenglu 10", "100876");
		Person1 p = new Person1(Calendar.getInstance(), "zhenghaimin", address,
		Gender.FEMALE, "student");
		try {
			var.objectToXmlString(p);
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

执行结果如下:
<?xml version="1.0" encoding="gbk" standalone="yes"?>
<ns2:Test name="zhenghaimin" xmlns:ns2="mypackage6">
    <birthDay>2012-11-15T18:17:53.905+08:00</birthDay>
    <address country="China">
        <state>Beijing</state>
        <city>Beijing</city>
        <street>xituchenglu 10</street>
    </address>
    <gender>FEMALE</gender>
    <job>student</job>
</ns2:Test>


PS:好吧,以前没用过Java发现eclipse改代码实在能力太强了,用它编程有点像用傻瓜相机照相的感觉。。。=。=!
网易博客:http://bingxinye1.blog.163.com/blog



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值