【java 2】java泛型

来源:互联网 发布:石家庄seo外包 编辑:程序博客网 时间:2024/05/02 15:32
一、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{//实例化泛型类,泛型指定为StringPoint<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——迁移兼容性
代价:
不能用于显示的引用运行时类型的操作之中,如转型、instanceofnew
但编译器仍然保持类型的内部一致性

类型擦除的主要过程如下:
     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;

说明:
如果AB的子类型则Bb=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

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

对于Integer的特别说明
Integer声明为final,因此不能定义它们的子类
//Jdk1.5对于Integer类的声明public final class Integerextends Numberimplements Comparable<Interger>
对于Integer的运算操作,都是先自动拆包成int然后进行的,计算完成后再自动打包成Integer
public static void main(String args[]){Integer i=new Integer(1);//Jdk1.5以下版本Integer j=1;//Jdk1.5int 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 blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.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



原创粉丝点击