黑馬程序員_java基礎加強(二) JDK1.5新特性

      

                ------ android培訓java培訓java學習型技術博客、期待與您交流! ---------- 

           JDK1.5新特性

1、泛型

    JDK1.5以後出現的機制,用於解決安全問題,是一個類型安全機制。l  

     泛型出現的原因:

由於集合可以存儲不同類型的數據,所以取元素時有可能會導致類型轉換錯誤,所以在開始

定義集合的時候就限定集合只能存某種引用數據類型的對象,減少操作集合時出錯的機率。

    泛型的書寫格式:通過<>來定義要操作的引用數據類型:ArrayList<Person> al = new ArrayList<Person>();

    泛型中可以同時有多個類型參數,在定義它們的尖括號中用逗號分開。例如:

    public static <K,V> getVlaue(K key) {return map.get(key);}

    

    泛型出現的好處:

         提高了程序的安全性

         將運行期遇到的問題轉移到了編譯期(ClassCastException

         省去了類型強轉的麻煩

     泛型類的出現優化了程序設計

對於參數化的泛型,編譯生成的字節碼會去掉泛型的類型信息,getClass()方法的返回值和原始類型完全一樣,

    所以用反射的方式就可以向集合中添加其它類型的數據。

    getclas().getMethod(String name,Class Parameters).(集合名字,要添加的元素)

泛型沒出現之前:

    

 class Tool{
private Object obj;
public void setObj(Object obj){
this.obj = obj;
}
public Object getObj(){
return obj;
}
public static void mian(String[]  args) {
Tool t = new Tool();
t.setObj(new Worker());	 //編譯不會報錯,運行會報類型轉換異常
Student s = (Student)t.getObj();//需要強轉類型
}
    }

泛型出現之後:

    

 class Tool<Q> {
private Q obj;
public void setObj(Q obj) {
this.obj = obj;
}
public Q getObj() {
return obj;
}
public static void mian(String[]  args) {
Tool<Worker> t = new Tool<Worker>();
t.setObject(new Worker());//如果傳入t.setObject(new Student());編譯時會出現失敗。
  //將運行時出現的問題轉移到了編譯時期。
Worker w = t.getObj();	  //不需要再進行強轉。
}
    }

    ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:

ArrayList<E>:泛型類型。沒有指定具體的泛型,E稱爲類型變量或類型參數。

ArrayList<Integer>:參數化的類型。指定了具體的泛型,Integer稱爲類型參數的實例或實際類型參數。

ArrayList<Integer><>typeof

ArrayList稱爲原始類型。

參數化類型可以引用一個原始類型的對象 Collection<String> c = new Vector();

原始類型可以引用一個參數化類型的對象 Collection c = new Vector<String>();

編譯器不允許創建類型變量的數組,即在創建數組實現時,數組的元素不能使用參數化的類型 

例如:Vector<integer>[] vectorList = new Vector<Integer>[10];

參數化類型不考慮類型參數的繼承關係 Vector<String> = new Vector(Object);//錯誤,反之亦然

Vector v = new Vector<String>(); Vector<Object> v1 = v;//不要把兩行代碼結合起來

可以用類型變量表示異常,稱爲參數化的異常,可以用於方法的throws列表中,但不能用於catch語句中。

private static <T extends Exception> sayHello() throws T {

    try {} catch (Exception e) {throw (T)e}}

}

    泛型的應用:

自定義泛型:類或者接口的旁邊有<>,<>傳遞實際參數,這個參數是具體引用數據類型(不能是基本數據類型)

    使用集合時,將集合中要存儲的數據類型作爲參數傳遞到<>中即可

自定義泛型類:當類中要操作的引用數據類型不確定的時候,就使用泛型類。具體操作什麼類型的對象,由使用

      該類的使用者來明確,將具體的類型做爲實際參數傳遞給<>。泛型類定義的泛型,在整個類中有

      效。如果被方法使用,那麼泛型類的對象明確要操作的具體類型後,所有要操作的類型就已固定。

      swap(new int[3],3,5);//該語句會報錯,編譯器不會對int[]中的int數進行自動裝箱和拆箱

      因爲new int[]本身就是一個對象了。swap(String[],3,5);該語句不報錯

泛型定義在方法上:爲了讓不同方法可以操作不同類型,而且類型還不確定。泛型方法的類型參數只能是引用數

  據類型,不能是基本數據類型

            成員方法泛型:


class Tool<T>{
    public void show(T t){}
    public <Q> void print(Q q){}
}//show()方法的泛型和類泛型一致,類中指定了類型,調用show()方法就不能再添加別的類型
//print()方法是在方法上指定泛型,可以操作任意類型而不受類中泛型限制
            靜態方法泛型:
class Tool<T>{
    public void show(T t){
System.out.println("show:"+t);
    }
    public static <W> void method(W t){	//如果靜態方法操作的應用數據類型不確定,可以將泛型
System.out.println("method:"+t);//定義在方法上,但不能訪問類上定義的泛型。
    }
}


    定義泛型類型:

如果類的實例對象中多處都要用到同一個泛型參數,即這些地方引用的泛型類型要保持同一個實際類型時,這時

    要採用泛型類型的方式定義,也就是類級別的泛型,語法格式如下:

    public class GenericDao<T> {//在類上定義泛型,方法中用到的類型和類的泛型類型是一致的。

private T find1;

public void add(T object) {}

public T getByid(int id) {}

    }

    dao-->data access object:數據訪問對象(crud: create remove update delete

類級別的泛型是根據引用該類名時,指定的類型信息,來參數化類型變量的。以下兩種方式都可以:

    GenericDao<String> dao = null; new GenericDao<String> ();

當一個變量被聲明爲泛型時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用。因爲

    靜態成員是被所有參數化的類所共享的,所以靜態成員應該有類級別的泛型參數。

    類型參數類型推斷:

編譯器判斷泛型方法實際類型參數的過程稱爲類型推斷。類型推斷是相對於知覺推斷的,其實現方法是很複雜的過程

根據調用泛型方法時實際傳遞的參數類型或返回值類型來推斷,具體規則如下:

    當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那麼根據調用方法時該處的實際應

用類型來確定,這很容易憑着感覺推斷出來,即直接根據調用方法時傳遞的參數類型或返回值類型來決定泛

型參數的類型。如:swap(new String[3],3,4) --> static<E> void swap(E[] a,int j)

    當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類

型都對應同一種類型來確定,這很容易憑着感覺推斷出來,例如:add(3,3)-->static<T> T add(T a,T b)

    當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類

型對應到了不同類型,且沒有返回值類型,這時候取多個類型的最大交集類型。下面語句實際對應的類型是

Number,編譯沒有問題,運行時出問題:fill(new Integer[3],3,3.5f)-->static<T> void fill(T[],T v)

    當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類

型對應到了不同類型,且有返回值,這時優先考慮返回值類型。下面語句實際對應的類型是Integer,編譯報

錯,將變量x的類型改爲float,對比Eclipse報告的錯誤提示,接着再將變量x類型改爲Number,則不會出錯:

int x = (3,3.5f)-->static<T> T add(T a, T b);

    參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際類型參數爲Object,編譯沒有問題,而第二種情況則

根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯將出現問題:

copy(new Integer[5],new String[5])-->static<T> void copy(T[] a,T[] b);

    泛型接口:

interface Inter<T> {
    public void show(T t);
}
class Demo1<T> implements Inter<T>{	//類在實現接口的時候沒有指定引用數據類型
    public void show(T t){
System.out.println("show :"+t);
    }
}
class Demo2 implements Inter<String>{	//類在實現接口的時候指定了引用數據類型
    public void show(String s){
System.out.println(s);
    }
}
    泛型通配符:<?>,可以理解爲佔位符。
ArrayList<String> al = new ArrayList<String>;
    al.add("abc1");
    al.add("abc2");
HashSet<Integer> hs = new HashSet<Integer>();
    hs.add(1);
    hs.add(2);
boolean b = al.containsAll(hs);//false


通過上面的示例分析:

        在要比較的類型不確定時候用 ? ,表示任意類型。而T是聲明具體類型變量,只能比較該類類型,或該類類型的子類

?通配符定義的變量主要用作引用,可以調用與參數化無關的方法,不能與參數化有關的方法。

    泛型的限定:

? extends E(上限)接收E或者E的子類型,固定父類型,只要是該類型的子類就可以傳入,查看API(ArrayList)

? super E(下限):接收E或者E的父類型,固定了子類型,只要是該類型的父類或者接口就可以傳入,查看API(TreeSet)

並且可以用&符號來指定多個邊界。如:<V extends Serializable & cloneable> void method() {}

2    類加載器:是加載類的工具。

    作用:jvm把一個類的二進制表現形式(.class也就是字節碼)文件加載到內存中去,通常這個字節碼的原始信息放在硬盤上classpath指定的目錄

  下。把字節碼文件中的內容加載到硬盤裏面,再對它進行一些處理,處理完的結果就是字節碼文件。處理這些工作的工具就是類加載器。

     類加載器也是java類,因爲其它是java類的類加載器本身也要被類加載器加載,所以必須由第一個不是java類的類加載器來加載,這個加載

器就是BootStrap,不需要被加載。它是嵌套在jvm內核裏面的,虛擬機內核一啓動BootStrap就已經存在,它是由C++語言編寫的一段二進制代碼。

    java虛擬機中的所有類加載器採用具有父子關係的樹形結構進行組織,在實例化每個類加載器對象時,需要爲其指定一個

父級類加載器對象或默認採用系統類加載器爲其父類加載。java虛擬機中可以安裝多個類加載器,系統默認三個主要

類加載器,每個類加載器負責加載特定位置的類。類加載器之間的父子關係和管轄範圍如下所示:

BootStrap -->JRE/lib/rt.jar

    |--ExtClassLoader -->JRE/lib/ext/*.jar

|--AppClassLoader -->Classpath指定的所有jar或目錄

    |--自定義類加載器(extends ClassLoader) -->加載特定的目錄

    類加載的委託機制:

java虛擬機要加載一個類時:

    首先當前線程的類加載器去加載線程中的第一個類。

    如果類A中引用了類Bjava虛擬機將使用加載類A的類加載器來加載類B

    還可以直接調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。

每個類加載器加載類時,先委託給其上級類加載器:

    當所有父類加載器沒有加載到類,回到發起者類加載器,還加載不了,拋ClassNotFoundExcepiton,不是再去

    再去找發起者類加載器的子類,因爲沒有getChild()。即使有,那麼有多個子類也不知道找哪一個。 

    自定義類加載器:其實就是模板設計模式(TemplatePattern

    自定義的類加載器繼承ClassLoader

    loadClass()方法不用重寫

    重寫findClass()方法

    通過defineClass()將得到的二進制數據文件轉換成字節碼文件

編程步驟;

    ①編寫一個對文件內容進行簡單加密的程序。

    ②編寫一個自己的類加載器,可以實現對加密過的類進行裝載和解密。

    ③編寫一個程序調用類加載器加載類,在源程序中不能用該類名定義引用變量,因爲編譯器無法識別這個類,

       程序中除了可以使用ClassLoad.load()方法外,還可以使用設置線程的上下文類加載器或者系統類加載器,

       然後在使用Class.forName()

實驗步驟:

    1、對不帶包名的class文件進行加密,加密結果存放到另外一個目錄。

    2、運行加載類的程序,結果能夠被正常加載,但打印出來的類加載器名稱爲:

       AppClassLoaderjava MyClassLoader MyTest F:\itcast

    3、用加密後的類文件替換Classpath環境下的類文件,再執行上一步操作就會出現問題,錯誤說明是:

       AppClassLoader類加載器加載失敗。

    4、刪除Classpath環境下的類文件,再執行上一步操作就沒有任何問題。

    

 class MyClassLoader extends ClassLoader {	//自定義類加載器
        private String classDir;
        public MyClassLoader() {};
public MyClassLoader(String classDir) {	//傳遞一個文件目錄
    this.classDir = classDir;
}	 //重寫父類加載器中的findClass()方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
    String classFileName = classDir + "\\" + name + ".class";
    try {
        File InputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);	 //將文件中的內容進行加密
fis.close();
byte[] bytes = bos.toByteArray();
return difineClass(bytes,0,bytes.length);
    } catch (Exception e) {
e.printStackTrace();
    }
    return super.findClass(name);
}
public static void cypher(InputStream ips,OutputStream ops) throws Exception {
    int b = -1;	 //加密文件方法cypher()
    while((b = ips.read()) != -1) {
ops.write(((byte)b) ^ 0xff);
    }
}
public static void main(String[] args) throws Exception {
    String srcPath = args[0];
    String destDir = args[1];
    File InputStream fis = new FileInputStream(srcPath);
    String destFileName = srcPath.substring(srcPath.lastIndexOf('\\') + 1);
    String destPath = destDir + "\\" + destFileName;
    File OutputStream fos = new FileOutputStream(destPath);
    cypher(fis,fos);
    fis.close;
    fos.close;
}
    }
    class ClassLoaderAttachment extends Date {
public String toString() {
    return "Hello,Itcast!";
}
    }
    class ClassLoaderTest {
public static void main(String[] args) {
    	 //父類加載器只能加載帶包名的文件
    Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
    Date d = (Date)clazz.newInstance();//不能用類名定義引用變量,因爲編譯器無法識別這個類
    System.out.println(d1);
}
    }

3、代理的概念與作用

    要爲已存在的具有多個相同接口目標類的各個方法增加一些系統功能,就要編寫一個與目標類具有相同接口的代理類,

    代理類的每個方法必須要調用與目標類對應相同的方法,並在調用方法的前面或者後面加上系統功能的代碼。

    如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序。在配置文件中配置是使用目標類,還是代理

        類,這樣以後很容易切換。譬如:想要日誌功能時,就配置代理類,否則配置目標類。這樣增加系統功能很容易,

以後運行一段時間後,再去掉系統功能也很容易。

    代理架構:

Client(客戶端調用程序)-->Target(目標類)-->Interface(接口)

      程序中如過沒有使用代理,就會直接引用目標類

      -->Poxy(代理類)-->Target(目標類)-->Interface(接口)

      如果使用代理,直接引用接口,這樣就可以看切換

    AOP

系統存在交叉業務,一個交叉業務就是要切入到系統中的一個方面。

安全 事物 日誌      ---->交叉業務(穿插到很多個對象中)

    StudentService ------|-------|--------|----

    CourseService  ------|-------|--------|----      ---->模塊(對象,也就是系統)

    MiscService    ------|-------|--------|----

具體的程序代碼描述交叉業務

    Method1 { Method2 { Method3 {

    ---------------------------------------- 切面    ---->交叉業務切入到系統中就像一個切面

    .... .... ....

    ---------------------------------------- 切面

    } } }

交叉業務的編程即面向方面的編程(Aspect oriented program 簡稱AOP)AOP的目標就是要使交叉業務模塊化。可

    以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的

    ---------------------------------------- 切面

    func1 { func2 { func3 {

    .... .... ....

    } } }

    ---------------------------------------- 切面

使用代理技術就可以解決這個問題,代理是實現AOP功能的核心和關鍵技術

    動態技術代理:

要爲系統中各個接口的類增加代理功能,會需要很多的代理類,全部採用靜態代理方式非常麻煩。

jvm可以在運行期生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。

jvm生成的動態類必須實現一個或多個接口,所以jvm生成的動態代理類必須要和目標類具有有相同的接口

如果目標類沒有實現接口,CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理。所

    CBLIB庫是第三方類庫。

代理類的各個方法中通常除了要調用目標的相應方法和返回目標返回的結果外,還可以在代理類方法中的如下

    四個位置加上系統功能代碼:

    在調用目標方法之前

    在調用目標方法之後

    在調用目標方法前後

    在處理目標方法異常的catch塊中

    分析jvm生成的動態類:

        創建實現了Collection接口的動態類和查看其名稱。

編碼列出動態類中所有的鄂構造方法和參數簽名

編碼列出動態類中所有的方法和參數簽名

分析Proxy.getProxyClass()的各個參數:

    ClassLoader:內存生成的字節碼沒有ClassLoader,沒有通過類加載器加載,但必須要指定一個類加載器。這時可以使用

和要實現的接口相同的類加載器。

    Interfaces:要實現的接口

創建動態類的實例對象

    用反射方法獲得構造方法

    編寫一個最簡單的invokcationHandler

    調用構造方法創建動態類的實力對象,便將編寫的invokcationHandler類實例對象傳遞進去

    打印創建的對象和調用對象的沒有返回值的方法和getClass(),演示調用其它有返回值的方法報告異常

總結:讓jvm創建動態類及其實例對象,需要提供三個方面的信息:

    生成的類中有哪些方法,通過讓其實例那些接口的方式進行告知;

    產生的類字節碼文件有一個關聯的類加載器對象;

    生成類中方法的代碼是怎樣的,也要由我們提供。把代碼寫在一個約定好了接口對象的方法中,把對象

傳遞給它,它調用我的方法,即相當於插入了我的代碼。提供執行代碼的對象是InvokcationHandler

對象。它是在創建動態類實例對象的構造方法時傳遞進去的。在上面InvokcationHandler對象的

invoke()中加一些代碼,就可以看到這些代碼被調用運行了。

Proxy.newProxyInstance()方法直接創建出代理對象,需要使用匿名內部類。

    動態生成的類的內部代碼:

動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口的所有方法和一個

    接收InvokcationHandler參數的構造方法。

構造方法接受一個InvokcationHandler對象,該方法內部的代碼如下所示:

    InvocationHandler handler;

    public Proxy(InvocationHandler handler) {this.handler = handler}

Collection接口中所有實現的方法,只要被調用就會調用InvocationHandler接口中定義的invoke()。而

    invoke(Object proxy,Method method,Object[] args)方法中的三個參數分別是調用invoke()的方法

    的對象、方法、參數。例如:proxy.add("zxx");

 

    動態語言:

代碼不是在編程時編好的,而是程序運行時,把一串字符串當作代碼運行。

class Proxy {	 //創建實現了Collection接口的動態類並查看方法列表
    public static void main(String[] args) {
Class clazzProxy1 = Proxy.getProxyClass(Collectoin.class.getClassLoader(),Collectoin.class);
System.out.println(clazzProxy1.getName());
System.out.println("----Constructors and Methods list");
Constructors[] Constructors = calzzProxy1.getConstructors();	//獲取動態類的構造器
for(Constructor constructor : constructors) {
    String name = constructor.getName();	 //獲取構造器的名稱
    StringBuilder sBuilder = new StringBuilder(name);
    sBuilder.append('(');
    Class[] clazzParams = constructor.getParameterTypes();	//獲取構造器的參數列表字節碼
    for(Class clazzParam : calzzParams) {
sBuilder.append(clazzParam.getName()).append(',');	//把字節碼文件添加到容器中
    }
    if(calzzParam.length() !== null && clazzParam.length() != 0)//如果不爲空長度不爲0
sBuilder.deleteCharAt(sBuilder.length()- 1);	 //刪除最後的','
    sBuilder.append(')');	 //改爲添加')
    System.out.println(sBuilder.toString());
} 
Method[] Methods = calzzProxy1.getMethods();	 //獲取動態類的方法
for(Method method : Methods) {
    String name = method.getName();	 //獲取方法的名稱
    StringBuilder sBuilder = new StringBuilder(name);
    sBuilder.append('(');
    Class[] clazzParams = method.getParameterTypes();	 //獲取方法的參數列表字節碼
    for(Class clazzParam : calzzParams) {
sBuilder.append(clazzParam.getName()).append(',');	//把字節碼文件添加到容器中
    }
    if(calzzParam.length() !== null && clazzParam.length() != 0)//如果不爲空長度不爲0
sBuilder.deleteCharAt(sBuilder.length()- 1);	 //刪除最後的','
    sBuilder.append(')');	 //改爲添加')
    System.out.println(sBuilder.toString());
} 
System.out.println("----begin create instance");	 //建立代理類實力對象,取構造器
Constructor constructor = clazzProxy1.getConstructor(invokcationHandler.class);
/*
class MyInvokcationHandler1 implements InvokcationHandler {	//構造器參數是一個接口,子類實現
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
return null;
    }
    	 //該對象是Collection實現類對象
    Collection proxy1 = (Collection)constructor.newInstance(new MyIncokcationHandler1());
    System.out.println(proxy1);	//返回null,對象沒有創建成功或者toString()返回的是null
    proxy1.clear();
    proxy1.size();//出現空指針異常,size()調用了invoke(),內部的返回值是null
}*/
//匿名內部類實現上述代碼
Collection proxy2 = constructor.newInstance(new InvokcationHandler() {
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
return null;
    }
});
Collection proxy3 = (Collection)Proxy.newProxyInstance(
    Collection.class.getClassLoader,
    new Class[] {Collection.class},
    new InvokcationHandler() {
        public Object invoke(Object proxy,Method method) throws Throwable {
Arraylist target = new ArrayList();
    return method.incoke(target,args);
}
    });
proxy.add("zxx");
proxy.add("lhm");
proxy.add("bxd");
System.out.println(proxy3.size());//長度爲0,add()調用了invoke(),每次都會創建新target
    }
}






               ------ android培訓java培訓java學習型技術博客、期待與您交流! ---------- 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章