泛型
泛型是用於解決安全問題的,是一個安全機制。JDK1.5以後出現的新特性
JDK1.5的集合類希望在定義集合時,明確表明你要向集合中裝入那種類型的數據,無法加入指定類型以外的數據。
泛型是提供給javac編譯器使用的可以限定集合中的輸入類型說明的集合時,會去掉“類型”信息,使程序運行效率不受影響,對參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。
由於編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,如用反射得到集合,再調用add方法即可。
使用泛型的好處
1、使用泛型集合,可將一個集合中的元素限定爲一個特定類型,集合中只能存儲同一個類型的對象;這樣就將運行時期出現的問題ClassCastException轉移到了編譯時期,方便與程序員解決問題,讓運行時期問題減少,提高安全性。
2、當從集合中獲取一個對象時,編譯器也可知道這個對象的類型,不需要對對象進行強制轉化,避免了強制轉換的麻煩,這樣更方便。
泛型格式:通過<>來定義要操作的引用數據類型
如:TreeSet<String> -----> 來定義要存入集合中的元素指定爲String類型
泛型定義中的術語
如:ArrayList<E>類和ArrayList<Integer>
1、ArrayList<E>整個稱爲泛型類型
2、ArrayList<E>中的E稱爲類型變量或類型參數
3、整個ArrayList<Integer>稱爲參數化類型
4、ArrayList<Integer>中的Integer稱爲類型參數的實例或實際類型參數
5、ArrayList<Integer>中的<>稱爲typeof
6、ArrayList稱爲原始類型
在使用java提供的對象時,何時寫泛型?
通常在集合框架中很常見,只要見到<>就要定義泛型,其實<>就是用來接收類型的,當使用集合時,將集合中要存儲的數據類型作爲參數傳遞到<>中即可。
關於參數化類型的幾點說明
1、參數化類型與原始類型的兼容性
第一、參數化類型可引用一個原始類型的對象,編譯只是報警告,能不能通過編譯,是編譯器說了算。
如:Collection<String> coll = new Date();
第二、原始類型可引用一個參數化類型的對象,編譯報告警告
如:Collection coll = new Vector<String>();
原來的方法接受一個集合參數,新類型也要能傳進去。
2、參數的類型不考慮類型參數的繼承關係:
Vector<String> v = new Vector<Objec>();//錯誤的
不寫Object沒錯,寫了就是明知故犯
Vector<Objec> v = new Vector<String>();//錯誤的
3、在創建數組實例時,數組的元素不能使用參數化的類型
如:Vector<Integer> v[] = newVector<Integer>[10];//錯誤的
<span style="font-family:Arial;">import java.lang.reflect.Constructor;
import java.util.*;
public class Generic {
public static void main(String[] args) throws Exception {
ArrayList<String> al = new ArrayList<String>();
al.add("25");
al.add("b");
System.out.println(al.get(1));
ArrayList<Integer> at = new ArrayList<Integer>();
at.add(23);
at.add(3);
System.out.println(at.get(1));
//編譯器生成的字節碼會去掉泛型的類型信息
System.out.println((al.getClass() == at.getClass()) +
"-->" + at.getClass().getName());
//at.add("ab")-->報錯,存儲的應爲Integer類型
//反射方式,由於編譯器生成的字節碼會去掉泛型的類型信息,
//所以用反射可跳過編譯器,存入任何類型
at.getClass().getMethod("add",Object.class).invoke(at,"abcd");
at.getClass().getMethod("add",Object.class).invoke(at,5);
System.out.println("反射方式:" + at.get(3));
System.out.println("反射方式:" + at.get(4));
//反射方式獲得new String(new StringBuffer("abc"));
Constructor<String> cons = String.class.getConstructor(StringBuffer.class);
String st = cons.newInstance(new StringBuffer("abc"));
System.out.println(st);
</span>
泛型中的通配符
當傳入的類型不確定時,可以使用通配符
1、使用?通配符可引用其他各種類型化的類型,通配符的變量主要用作引用,也可調用與參數化無關的方法,但不能調用與參數化有關的方法。
2、可對通配符變量賦任意值:
如:Collection<?> coll coll = newHashSet<Date>();
泛型的限定:對於一個範圍內的一類事物,可以通過泛型限定的方式定義,有兩種方式:1、? extends E:可接收E類型或E類型的子類型;稱之爲上限。
如:Vector<? extends Number> x = newvector<Integer>();
2、? super E:可接收E類型或E類型的父類型;稱之爲下限。
如:Vector<? super Integer>x = newvector<Number>();
/*
泛型的限定:
*/
import java.util.*;
class GenerticXian2
{
public static void main(String[] args)
{
TreeSet<Student> s = new TreeSet<Student>(new Comp());
s.add(new Student("stu0"));
s.add(new Student("stu3"));
s.add(new Student("stu1"));
print(s);
System.out.println("Hello World!");
TreeSet<Worker> w = new TreeSet<Worker>(new Comp());
w.add(new Worker("Worker0"));
w.add(new Worker("Worker3"));
w.add(new Worker("Worker1"));
print(w);
}
public static void print(TreeSet<? extends Person> ts) {
Iterator<? extends Person> it = ts.iterator();
while (it.hasNext()){
Person p = it.next();
System.out.println(p.getName());
}
}
}
class Person implements Comparable<Person> {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int compareTo(Person p){
return this.getName().compareTo(p.getName());
}
}
class Comp implements Comparator<Person> {
public int compare(Person p1,Person p2){
return p1.getName().compareTo(p2.getName());
}
}
class Student extends Person {
Student(String name){
super(name);
}
}
class Worker extends Person {
Worker(String name){
super(name);
}
}
1、何時定義泛型方法?
爲了讓不同方法可以操作不同的類型,而且類型不確定,那麼就可以定義泛型方法
2、特殊之處:靜態方法不可以訪問類上定義的泛型,如果靜態方法操作的引用數據類型不確定,可以將泛型定義在方法上。
泛型方法的特點:
1、位置:用於放置泛型的類型參數的<>應出現在方法的其他所有修飾符之後和在方法的返回類型之前,也就是緊鄰返回值之前,按照慣例,類型參數通常用單個大寫字母表示。
2、只有引用類型才能作爲泛型方法的實際參數
3、除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符。
4、普通方法、構造函數和靜態方法中都可以使用泛型。
5、可以用類型變量表示異常,稱之爲參數化的異常,可用於方法的throws列表中,但是不能用於catch子句中。
6、在泛型中可同時有多個類型參數,在定義它們的<>中用逗號分開。
T和?有什麼區別呢?
1、T限定了類型,傳入什麼類型即爲什麼類型,可以定義變量,接收賦值的內容。
2、?爲通配符,也可以接收任意類型但是不可以定義變量。
若類實例對象中多出要使用到同一泛型參數,即這些地方引用類型要保持同一個實際類型時,這時候就要採用泛型類型的方式進行定義,也就是類級別的泛型。
當類中要操作的引用數據類型不確定時,在早期定義Object來完成擴展,而現在定義泛型。
泛型類定義的泛型,在整個類中都有效,如果被方法調用,那麼泛型類的對象要明確需要操作的具體類型後,所有要操作的類就已經固定了。
類級別的泛型是根據引用該類名時指定的類型信息來參數化類型變量的。
總結
對泛型的定義:
第一、定義泛型:當又不確定的類型需要傳入到集合中,需要定義泛型
第二、定義泛型類:如果類型確定後,所操作的方法都是屬於此類型,則定義泛型類
第三、定義泛型方法:如果定義的方法確定了,裏面所操作的類型不確定,則定義泛型方法
<span style="font-family:Arial;">//測試
class GenerticTest {
public static void main(String[] args) {
//創建泛型類對象
GenClass<Worker> g = new GenClass<Worker> ();
g.setTT(new Worker());
Worker w = g.getTT();
g.showC(w);
System.out.println("----------------------");
//泛型方法測試
GenMethod<String> g1 = new GenMethod<String>();
GenMethod.showS("SSS");
g1.show("sesf");
g1.print("heheh");
g1.printY(new Integer(5));
System.out.println("------------------------");
//泛型接口測試
GenInter g2 = new GenInter();
g2.show("haha");
System.out.println("Hello World!");
GenImpl<Integer> g3 = new GenImpl<Integer>();
g3.show(new Integer(95));
}
}
//泛型類
class GenClass<TT> {
//定義私有屬性
private TT t;
//定義公共設置方法,設置屬性
public void setTT(TT t) {
this.t = t;
}
//定義公共訪問方法,訪問屬性
public TT getTT() {
return t;
}
//定義方法
public void showC(TT t) {
System.out.println("GenClass show:" + t);
}
}
//創建Worker類,作爲類型傳入泛型類中
class Worker {}
//泛型方法
class GenMethod<T> {
//靜態的泛型方法
public static <S> void showS(S s) {
System.out.println("static show:" + s);
}
//非靜態泛型方法
public void show(T t) {
System.out.println("未指定T show:" + t);
}
public void print(T t) {
System.out.println("指定T print:" + t);
}
//指定接受其他類型的泛型方法
public <Y> void printY(Y y) {
System.out.println("和類指定的不同,爲Y print:" + y);
}
}
//泛型接口
interface Inter<T> {
void show(T t);
}
//一般類實現泛型接口
class GenInter implements Inter<String> {
public void show(String s) {
System.out.println("接口 show:" + s);
}
}
//泛型類實現泛型接口
class GenImpl<T> implements Inter<T> {
public void show(T t) {
System.out.println("類接收類型不確定的實現接口 show:" + t);
}
</span>
類型推斷
編譯器判斷泛型方法的實際參數的過程,稱之爲類型推斷。類型推斷是相對於直覺推斷的,其實現方法是一種非常複雜的過程
類型推斷的具體規則:根據調用泛型方法時,實際傳遞的參數類型或返回值的類型來推斷。
1、當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那麼根據調用方法時,該處的實際應用類型來確定,這很容易憑着感覺推斷出來,即直接根據調用方法時,傳遞的參數類型或返回值來決定泛型參數的類型,如:
swap(newString[3],1,2) static <E> void swap(E[] a, inti, int j);
2、當某個類型變量在某個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時,這多處的實際應用類型都對應同一種類型來表示,這很容易憑感覺推斷出來:
add(3,5) static<T> T add(T a,T b);
3、若對應了不同類型,且沒有使用返回值,這是取多個參數中的最大交集類型,如下面的對應類型Number,編譯沒問題,但是運行會出錯:
fill(new Integer[3],3.5f) static<T> void fill(T[] a,T v);
4、若對應了不同類型,且使用了返回值,這時候優先考慮返回值類型,如下面語句實際對應的類型就是Integer了,編譯將報錯,將變量x類型改爲float,對此eclipse報錯提示,接着再將變量x類型改爲Number,則就沒了錯誤:
int x = add(3,3.5f) static<T> T add(T a,T b);
5、參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型爲Object,編譯沒問題,而第二種情況則會根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯將出現問題:
copy(newInteger[5],new String[5]); static<T> T copy(T[] a,T[] b);
<span style="font-family:Arial;">package cn.itcast.text2;
import java.lang.reflect.*;
import java.sql.Date;
import java.util.*;
import cn.itcast.text1.ReflectPoint;
public class GenerticTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Object obj = "abc";
String str = autoContor(obj);
GenerticDao<ReflectPoint> gd = new GenerticDao<ReflectPoint>();
gd.add(new ReflectPoint(3,5));
//通過獲得方法本身的方法
Method applyMethod = GenerticTest.class.getMethod("applyVector", Vector.class);
//通過方法的獲取泛型參數的方法得到原始參數類型的集合
Type[] types = applyMethod.getGenericParameterTypes();
//將參數類型轉換爲參數化類型
ParameterizedType pType = (ParameterizedType)types[0];
//得到原始類型
System.out.println(pType.getRawType());
//得到實際參數類型
System.out.println(pType.getActualTypeArguments()[0]);
}
</span>
IntroSpector:即內省,是對內部進行檢查,瞭解更多的底層細節。
內省的作用:主要針對JavaBean進行操作。
JavaBean(存在於java.bean包中)
JavaBean是一種特殊的Java類,主要用於傳遞數據信息,這種Java類中的方法主要用於訪問私有的字段,且方法都符合某種特殊的命名規則。它是一種特殊的Java類,其中的方法名稱等,都符合特殊的規則。只要一個類中含有get和set打頭的方法,就可以將其當做JavaBean使用。
javabean中字段和屬性:
字段就是我們定義的一些成員變量,如private String name;等
而屬性是具有某些功能,Bean屬性,是含有get或set方法的那些屬性的字段,即這個變量的get屬性,set屬性等。
作用:如果要在兩個模板之間傳遞多個信息,可將這些信息封裝到一個JavaBean中,這種JavaBean的實例對象通常稱之爲值對象(Value Object,簡稱VO),這些信息在類中用私有字段來儲存,如果讀取或設置這些字段的值,則需要通過一些相應的方法來訪問。
命名方式:JavaBean的屬性是根據其中的setter和getter方法來確定的,而不是依據其中的變量,如方法名爲setId,則中文意思是設置Id,getId也是如此;去掉前綴,剩餘部分就是屬性名稱,如果剩餘部分的第二個字母小寫,則把剩餘部分改爲小寫。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。
總之:一個類被當做JavaBean使用時,JavaBaan的屬性是根據方法名推斷出來的,它根本看不到Java類內部的成員變量。
JavaBean的好處:
一個符合JavaBean特點的類當做普通類一樣可以使用,但是把它當做JavaBean類用肯定有好處的:
1)在JavaEE開發中,經常要使用JavaBean。很多環境就要求按JavaBean的方式進行操作,別人都這麼用,那麼就必須要求這麼做。
2)JDK中提供了對JavaBean進行操作的API,這套API稱爲內省,若要自己通過getX的方式來訪問私有x,可用內省這套API,操作JavaBean要比使用普通的方式更方便。
package cn.itcast.text1;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IntroSpectorTest {
/**
* @param args
*/
/*
* public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3,5);
String propertyName = "x";
//"x"-->"X"-->"getX"-->MethodGetX-->
//內省的方式:
//屬性描述符:PropertyDescriptor
//get屬性信息
PropertyDescriptor pd =
new PropertyDescriptor(propertyName,pt1.getClass());
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(pt1);
System.out.println(retVal);
//set屬性信息
Object value = 7;
PropertyDescriptor pd2 =
new PropertyDescriptor(propertyName,pt1.getClass());
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
System.out.println(pt1.getX());
}
*/
//上面的get或set代碼分別通過選中要重構的代碼,通過右擊選重構獲得get和set方法:
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3,5);
String propertyName = "x";
//一般方式:"x"-->"X"-->"getX"-->MethodGetX-->
//內省方式:
//通過get和set方法獲取屬性值
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
Object value = 7;
setProperty(pt1, propertyName, value);
System.out.println(pt1.getX());
}
//設置屬性值的方法 //此處的類型爲Object,通用,下同
private static void setProperty(Object rf, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
//創建屬性描述符對象,將屬性名稱和加載文件等信息寫入其中
PropertyDescriptor pd =
new PropertyDescriptor(propertyName,rf.getClass());
//通過反射的方法類Method,獲取屬性所對應的set方法
Method methodSetX = pd.getWriteMethod();
methodSetX.invoke(rf, value);
}
//獲取屬性值的方法
private static Object getProperty(Object rf, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
//創建屬性描述符對象,獲取屬性所對應的名稱和加載文件等信息
PropertyDescriptor pd =
new PropertyDescriptor(propertyName,rf.getClass());
//通過反射的方法類Method,獲取屬性所對應的get方法
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(rf);
return retVal;
}
}
對JavaBean的複雜內省操作
1、在IntroSpector類中有getBeanInfo(Class cls)的方法。
2、獲取Class對象的Bean信息,返回的是BeanInfo類型。
3、BeanInfo類中有getPropertyDescriptors()的方法,可獲取所有的BeanInfo的屬性信息,返回一個PropertyDescriptor[]。
4、在通過遍歷的形式,找出與自己想要的那個屬性信息。
如:改寫get方法:
…
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object value = null;
for(PropertyDescriptor pd : pds){
if(pd.getName().equals(propertyName)){
Method methodGetX = pd.getReadMethod();
value = methodGetX.invoke(pt1);
break;
}
}
…
BeanUtils工具包
1、BeanUtils等工具包都是由阿帕奇提供的,爲了便於開發。BeanUtils可以將8種基本數據類型進行自動的轉換,因此對於非基本數據類型,就需要註冊轉換器Converter,這就需要ConverUtils包,
2、好處:
1)提供的set或get方法中,傳入的是字符串,返回的還是字符串,因爲在瀏覽器中,用戶輸入到文本框的都是以字符串的形式發送至服務器上的,所以操作的都是字符串。也就是說這個工具包的內部有自動將整數轉換爲字符串的操作。
2)支持屬性的級聯操作,即支持屬性鏈。如可以設置:人的腦袋上的眼鏡的眼珠的顏色。這種級聯屬性的屬性連如果自己用反射,那就很困難了,通過這個工具包就可以輕鬆調用。
3、可以和Map集合進行相互轉換:可將屬性信息通過鍵值對的形式作爲Map集合存儲(通過staticjava.util.Map describe(java.lang.Object bean)的方法),也可以將Map集合轉換爲JavaBean中的屬性信息(通過static voidpopulate(java.lang.Object bean, java.util.Map properties)的方法)。
4、示例:
1)設置和獲取屬性值:
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import org.junit.Test;
public class BeanUtilDemo {
/**
* BeanUtils使用
*/
@Test
public void test1() throws Exception{
//創建對象,設置屬性值
Person p = new Person();
BeanUtils.setProperty(p, "name", "zzz");
String name = BeanUtils.getProperty(p, "name");
System.out.println(name);
}
@Test
public void test2() throws Exception{
//創建對象,傳入屬性值
Person p = new Person();
String name = "wangwu";
String age = "23";
String hight = "173.5";
//設置屬性值
BeanUtils.setProperty(p, "name", name);
BeanUtils.setProperty(p, "age", age);
BeanUtils.setProperty(p, "hight", hight);
//獲取屬性值
System.out.println(BeanUtils.getProperty(p, "name"));
System.out.println(BeanUtils.getProperty(p, "age"));
System.out.println(BeanUtils.getProperty(p, "hight"));
}
2)未註冊的屬性值的獲取和設置
//獲取未註冊的屬性,即非八種基本數據類型的引用類型
//private Date birthday
@Test
public void test3() throws Exception{
Person p = new Person();
String name = "wangwu";
String age = "23";
String hight = "173.5";
String birthday = "1990-09-09";
ConvertUtils.register(new Converter() {
//註冊器Converter接口中方法的重寫
@Override
public Object convert(Class type, Object value) {
if(value == null)
return null;
if(!(value instanceof String))
throw new ConversionException("只支持String類型的轉換");
String str = (String) value;
if(value.equals(""))
return null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try{
return sdf.parse(str);
}catch(ParseException e){
throw new RuntimeException(e);//異常鏈不能掉,這裏必須寫上e
}
}},
Date.class);
//測試
BeanUtils.setProperty(p, "name", name);
BeanUtils.setProperty(p, "age", age);
BeanUtils.setProperty(p, "hight", hight);
BeanUtils.setProperty(p, "birthday", birthday);
System.out.println(BeanUtils.getProperty(p, "name"));
System.out.println(BeanUtils.getProperty(p, "age"));
System.out.println(BeanUtils.getProperty(p, "hight"));
System.out.println(BeanUtils.getProperty(p, "birthday"));
}
//使用已經寫好的註冊器DateLocaleConverter
@Test
public void test4() throws Exception{
Person p = new Person();
String name = "wangwu";
String age = "23";
String hight = "173.5";
String birthday = "1990-09-09";
//將日期註冊到BeanUtils上
ConvertUtils.register(new DateLocaleConverter(), Date.class);//提供的註冊器不健壯,因爲傳入空字符串,就會報錯
//所以,當沒有提供註冊器或需要加強註冊器的時候,可以自己寫
//測試
BeanUtils.setProperty(p, "name", name);
BeanUtils.setProperty(p, "age", age);
BeanUtils.setProperty(p, "hight", hight);
BeanUtils.setProperty(p, "birthday", birthday);
System.out.println(BeanUtils.getProperty(p, "name"));
System.out.println(BeanUtils.getProperty(p, "age"));
System.out.println(BeanUtils.getProperty(p, "hight"));
System.out.println(BeanUtils.getProperty(p, "birthday"));
Date date = p.getBirthday();
System.out.println(date.toLocaleString());
}
3)Map集合在BeanUtils中的應用:
//Map集合在BeanUtils中的應用
@Test
public void test5() throws Exception {
/*
* JDK 7.0新特性:
* Map map = {"name" : "zs", "age" : 22, "hight" : 176.5};
*/
//將數據存入集合
Map map = new TreeMap();
map.put("name", "zhangsan");
map.put("age", "20");
map.put("hight", "172.5");
map.put("birthday", "1999-10-02");
//註冊器
ConvertUtils.register(new DateLocaleConverter(), Date.class);
//獲取屬性
Person p = new Person();
BeanUtils.populate(p, map);
System.out.println(BeanUtils.getProperty(p, "name"));
System.out.println(BeanUtils.getProperty(p, "age"));
System.out.println(BeanUtils.getProperty(p, "hight"));
System.out.println(BeanUtils.getProperty(p, "birthday"));
}
//屬性鏈
@Test
public void test6() throws Exception {
Person p = new Person();
BeanUtils.setProperty(p, "birthday.time", "111212");
System.out.println(BeanUtils.getProperty(p, "birthday.time"));
}
補充
1)BeanUtils是以字符串的形式進行操作的
2)PropertyUtils是以傳入值本身的類型進行操作的。
//PropertyUtils可直接解析爲指定類型,而BeanUtils只能指定字符串的類型
@Test
public void test7() throws Exception {
Person p = new Person();
System.out.println("-----BeanUtiles-------");
BeanUtils.setProperty(p, "age", "22");//字符串形式
System.out.println(BeanUtils.getProperty(p, "age"));
System.out.println(BeanUtils.getProperty(p, "age").getClass().getName());
System.out.println("-----PropertyUtiles-------");
PropertyUtils.setProperty(p, "age", 22);//Integer形式
System.out.println(PropertyUtils.getProperty(p, "age"));
System.out.println(PropertyUtils.getProperty(p, "age").getClass().getName());
}
詳細請查看: http://edu.csdn.net