1.Java泛型的由来
2.泛型的作用
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等。其实在使用的时候还是进行带强制类型的转化,只不过这是比较安全的转换,因为在编译阶段已经确保了数据的一致性。
类型擦除的主要过程如下:
1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
2.移除所有的类型参数。
1.1、对于擦除的补偿:
擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要某些确切类型的信息都无法工作。
1.2、创建类型实例
用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、创建泛型数组
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.边界
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;
****?extends通配符
List<Apple>apples = new ArrayList<Apple>();
List<? extends Fruit>fruits =apples;
//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通配符
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);
}
}
三、泛型的局限性
任何基本类型都不能作为参数类型
Java自动包装机制——自动实现int到Integer的双向转换
包装器类(wrapper)
//Jdk1.5对于Integer类的声明
public final class Integer
extends Number
implements Comparable<Interger>
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