注釋 1.5新特性: 枚舉,注解,泛型。
1:靜态導入
* 如:import static java.lang.*
2:重載重寫
* overload:重載
* override:重寫,用可變參數實作的重載更加好的友善使用。
3:自動裝猜箱子
* Integer a1 = 1,Integer a2 = 1;這時候a1和a2相同。
* 當a1,a2的值不再-128-127之間就是不相同。因為在那個範圍的時候它就會自動
到常量值中查詢-128-127的值,不在該範圍的時候就建立出來。
這種a1,a2在-128-127之間公用一個值的時候叫做享員模式。
* 享員模式:多個相同對象的數值,隻存在一份。
* String類型就使用了享元模式.String對象是不變對象,一旦建立出來就不能改變,如果需要改變一個字元串的值,就隻 好建立一個新的String對象,在JVM内部,String對象都是共 的。如果一個系統中有兩個String對象所包含的字元串相 同的話,JVM實際上隻建立一個String對象提供給兩個引用,進而實作String對象的共享,String的inern()方法給出這 字元串在共享池中的唯一執行個體.
4:枚舉
* 當有的時候我們想固定一些值,隻能是這些值的時候用它控制:如星期幾
* 枚舉的值其實就是一個對象。枚舉的構造方法必須是私有的,不準别人随意建立枚舉類型對象。
* 枚舉的值初始化預設是調用枚舉的預設構造方法。無參數形式。要想調用有參數形似的構造方法
* 在值的後面如FRI(1) 這樣調用參數構造函數。
枚舉對每個值其實是枚舉的一個對象,當枚舉裡面有抽象方法的時候,枚舉就自認為是一個抽象類,是以那些對象是不可能被建立的,這時候每個值就要是一個子類,通過匿名類的方式
實作抽象方法,進而構造對象。
枚舉類隻有一個對象值的時候等于差不多是單列,因為構造方法私有。
5:消滅if else 如下:
消滅前代碼:
package com.moom;
public class WeekDay {
private WeekDay(){};
public static final WeekDay MON = new WeekDay();
public static final WeekDay TUS = new WeekDay();
public static final WeekDay WED = new WeekDay();
public WeekDay getNextDay() {
if(this == MON) {
return TUS;
}else if(this == TUS) {
return WED;
}else {
return MON;
}
}
public String toString() {
if(this==MON) {
return "MON";
}else if(this==TUS) {
return "TUS";
}else {
return "WED";
}
}
}
消滅後代碼
package com.moom;
public abstract class WeekDay2 {
private WeekDay2(){};
public static final WeekDay2 MON = new WeekDay2() {
@Override
public WeekDay2 getNextDay() {
return TUS;
}
@Override
public String toString() {
return "MON";
}
};
public static final WeekDay2 TUS = new WeekDay2() {
@Override
public WeekDay2 getNextDay() {
return WED;
}
@Override
public String toString() {
return "TUS";
}
};
public static final WeekDay2 WED = new WeekDay2() {
@Override
public WeekDay2 getNextDay() {
return MON;
}
@Override
public String toString() {
return "WED";
}
};
public abstract WeekDay2 getNextDay();
public abstract String toString();
}
6:class
* 所有類的一個總稱。
如:所有的人是是人,所有的類是class
* class是位元組碼
* 擷取類的class方式: eg:Person.class, person.getClass(),Class.forName("com.moom.Person")//有的自後從JVM中拿出來,沒有的時候類加載器加載。;
9個預定義class,8個基本(int ,long。。。。。),1個void.
* 實驗class的方法,包括數組的class.
* eg:
Class c1 = s1.getClass();
Class c2 = s2.getClass();
System.out.println(c1 == c2); // 表明隻會生成一份字節碼檔案
int a = 1;
System.out.println(int.class.getName()); //擷取Int class的名字
System.out.println(int.class.isPrimitive()); //檢視class是否是基本的class--int,double,boolean......
System.out.println(void.class.getName());
System.out.println(int[].class.isPrimitive()); // 表明數組不是基本類型CLASS
7:反射:反射就是把JAVA類中的各種成分映射成相應的JAVA類。
* 構造方法,根據class可以擷取所有類型的構造方法,方法可以根據參數擷取相應的構造方法,然後根據構造方法可以擷取對應的對象。
Constructor可以擷取對象的執行個體方法,根據相應的參數執行個體成對應的對象。
這樣做有一個不好,就是每次我要通過反射的方式擷取對象的時候,都要通過class擷取constructor在用constructor構造對象。
JAVA給我們提供了一個友善點的方式,就是Class直接由一個newInstance()的方法,傳回執行個體。
這個方法就是說調用預設的無參數的構造方法,裡面的實作機制其實也同擷取了無參數的constructor來構造對象。
* eg:
Constructor s = String.class.getConstructor(StringBuffer.class);
String s1 = (String)s.newInstance(new StringBuffer("a")); //根據構造方法,擷取對象
System.out.println(s1); //構造方法是怎麼樣的newInstance參數也要傳相應類型的參數
char[] c = {'c'};
Constructor s2 = String.class.getConstructor(char[].class);
String s3 = (String)s2.newInstance(c);
System.out.println(s3);
* Field:每個類的對應的字段。 通過它能擷取具體對象的字段的值,要注意的是:字段是屬于所有的類的,是以要擷取具體對象的值,就是Field.get(對象名字);
* eg:
private static Object getUser(String string, Map map) throws Exception{
Object o = Class.forName(string).newInstance(); //通過反射建立傳過來字元串的對象
Field[] fields = o.getClass().getDeclaredFields(); //通路對象類中所有的字段
for(Field field:fields) {
field.setAccessible(true); //把字段的可修改屬性設定成可以
field.set(o, map.get(field.getName())); //給對應對象的對應字段設定值。
}
return o;
}
* 可以在做練習: 題目:在一個類中有成員變量字元串值。通過反射把他們的值改變。
* Method:反射方法。
可以通過這個方式擷取類的對應的方法。
String str1 = "abcd";
Method methodCharAt = str1.getClass().getMethod("charAt", int.class);//擷取對應的charAt方法,并且參數是一個int
System.out.println(methodCharAt.invoke(str1, 1)); //調用方法,方法的第一個參數是對象的名字,第二個參數是方法的實際參數值
* //思考,當第一個參數是Null的時候,顯然這個調用不屬于任何對象,它就是類方法。
Method m = A.class.getMethod("getString", int.class);
System.out.println(m.invoke(null, 1));
* 當我們要通過反射調用時數組參數形式的方法的時候,當數組的參數是基本類型的時候。
比如方法 pulic static String getString(int[] x) 這時候我們在調用的時候可以這樣method.invoke(null,new int[]{1,2,3});
但是當是String 或者對象類型的時候就不可以這樣傳遞了,我們需要打包如method.invoke(null,Object(new String[]{"1","2","3"}));
或者method.invoke(null,new Object[]{new String[]{"1","2","3"}});
對不是靜态的方法也是同樣的效果
解釋:對于基本類型,它能自動拆解成數組形式,對于類類型數組,它把所有的當成一個包的整體等于是一個參數,進而會出現錯誤。
要先打下包,如上。
對數組的反射: 對于具有相同類型,相同維數長度的數組的class是相同的。
*eg: 數組的例子:
int[] a1 = new int[3];
int[] a3 = new int[4];
int[][] a2 = new int[2][3];
String[] s1 = new String[3];
String[][] s2 = new String[3][4];
String[] s3 = new String[]{"a","b","c"};
System.out.println(a1.getClass()==a3.getClass());
//System.out.println(a1.getClass()==s1.getClass());
Object object1 = a1;
Object object2 = a2;
//不行,因為它預設的是轉化成數組之後就是Int了,int不是Object
//Object[] o1 = a1;
//可以是因為轉成的是int[]數組,數組是對象
Object[] o2 = a2;
//這樣String類型是Object就可以當成數組處理
System.out.println(Arrays.asList(s3));
//int非組數類型,當成泛型對待,進而列印出的隻有一個元素
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a2));
問題:列印任意類型的對象,是數組的時候也要挨個列印print(object);
private static void print(Object obj) {
Class clazz = obj.getClass();
if(clazz.isArray()) {
int len = Array.getLength(obj);
for(int i=0; i<len; i++) {
System.out.print(Array.get(obj, i));
}
}else {
System.out.println(obj);
}
}
如何擷取數組當中元素的類型?System.out.print(Array.get(obj, i).getClass().getName()); 張老師錯了?
8:hashcode
* 首先隻是在實作了hash算法的集合當中才一般用用到它,比如hashset. 比如arraylist不用。就是說在一個類裡重寫hashcode方法在有利用到
實作hash算法的時候才用到它,不然可以不重寫它沒任何問題。
*hashset和arraylist差別。 當有一個對像要放入到arraylist當中的時候(其實是放對象的引用),放多少它的長度就是多少。
當時hashset的時候,它會根據你放入的這個對象計算hash值,當計算出來之後存在就不帕耍輝诰頭湃搿?
是以List的時候你重複放入一個對象集合的大小會增加,hash的時候它就不會增加。
例子:
public static void main(String[] args) throws Exception{
Collection c = new HashSet(); //是hash的時候c的size是3,list的時候size是4
Point p1 = new Point(3,3);
Point p2 = new Point(5,5);
Point p3 = new Point(3,3);
c.add(p1);
c.add(p2);
c.add(p3);
c.add(p1);
System.out.println(c.size());
}
*從上面的程式我們其實應該知道的是,p1和p3其實應該是相等的,是以當成一個就行了。是以這時候我們就應該hash的時候size是2.但是對象new來的時候都是根據記憶體的方式
計算它自己的hash值的,是以我們這時候就是不一樣的了,進而size是3. 是以在Point對象裡面我們重寫hashcode方法就行了,這樣就能保證對象計算出的hash值是一樣的,進而
也不會說重複放入我們覺得相同的對象了,但是隻是重寫hashcode方法沒用,因為它隻是計算hash值的東西,我們還不能确定兩個對象的資料是否相同。是以要同時重寫equal方法,
保證兩個對象的equals是真。
問題:如下:
Point p1 = new Point(3,3);
Point p2 = new Point(5,5);
Point p3 = new Point(3,3);
c.add(p1);
c.add(p2);
c.add(p3);
c.add(p1);
p1.setY(4); //這裡我們改變p1的y值
c.remove(p1); //我們這裡本來是想把p1移除的,抱歉因為你修改了y的值,我們移除的去算Hash值是和存進去的不一樣的,無法移除,發生記憶體洩露。
System.out.println(c.size());
注釋:所有參加hash值計算的字段的改變,使得我們無法再尋找到我們想移除的對象。
9:架構和工具類的異同。
* 架構是來調用你的東西,工具是你去調用的東西。
* 從配置檔案中讀取類名字,動态執行個體化對象。
* 配置檔案的路徑,
1:應該動态的擷取。配置檔案配置
2:放在classpath下擷取。
* 用classloader加載,
* 用類自己加載(其實還是間接的用了)
* 配置檔案一般通過classloader加載。一般的架構也都是如此。
* 架構中我們一般不用New,也不想固定說就是hashSet是以我們把它配置起來
對于檔案的讀取,我們現在是這樣放的相對路徑,現實中我們一般不可以這樣
因為我們是要給别人用,每個人的路徑都是不同的,有兩種方式解決,就是
每個人用的時候配置一下className.properties檔案的路徑,然後我們讀取
路徑下的檔案就可以了。
最常用的一種是把檔案放入classpath下,放完之後就是用classloader把它給
加載出來就行了。
//InputStream in = new FileInputStream("className.properties");
//這樣通過classlaoder我們就加載進去了,預設的路徑是項目的下面跟路徑。當放在某個包下的時候就加包名
//InputStream in = TestClassLoaderAndPath.class.getClassLoader().getResourceAsStream("className.properties");
//InputStream in = TestClassLoaderAndPath.class.getClassLoader().getResourceAsStream("source/className.properties");
//也可以這樣,直接通過類讀取,其實就是中間也是通過classloader隻是方式不一樣。更加友善
InputStream in = TestClassLoaderAndPath.class.getResourceAsStream("/source/className.properties");
Properties p = new Properties();
p.load(in);
in.close();
String className = p.getProperty("className");
Collection c = (Collection)Class.forName(className).newInstance();
10:javaBean(内省(introspector))
* introspector對JAVABEAN進行操作----特殊的JAVA類
* 他其實也是對JAVA類的操作,它的功能反射也能做到,但是它用來操作标準的JAVA類,更加友善。
*比如: 當我們通過反射來擷取某個類的的某個字段的set方法,然後根據set方法設定值,這時候
我們就要擷取這個字段值,然後再和set組合成方法名,在調用方法,比較麻煩。
然而我們用Intorspector的時候就友善多了。
比如我同樣式得到某個類某個屬性的set方法,其實就是對屬性寫的方法吧
我們隻要:PropertyDescriptor p = new PropertyDescriptor(屬性名字,對應的類對象);
p.getWriter()就得到了屬性的set方法等于就是,就直接在調用設定值了,
是以這種方式比反射更加友善快捷。
兩種方式的對字段的設定與讀取。
public static void main(String[] args) throws Exception{
Point p1 = new Point(3, 5);
setPropert(p1, "x", 4);
System.out.println(getProperty(p1, "x"));
}
public static void setProperty(Object object,String s,Object value) throws Exception{
PropertyDescriptor pd = new PropertyDescriptor(s, object.getClass());
//拿到S屬性的寫方法就是setS(),在調用傳回
Method methodWrite = pd.getWriteMethod();
//擷取到了方法,并且調用設定值
methodWrite.invoke(object,value);
}
public static Object getProperty(Object object,String s) throws Exception{
PropertyDescriptor pd = new PropertyDescriptor(s, object.getClass());
//拿到S屬性的讀方法就是getS(),在調用傳回
Method methodRead = pd.getReadMethod();
//擷取到了方法,并且調用設定值
return methodRead.invoke(object);
}
//一種更加複雜的實作
public static void setPropert(Object object,String s,Object value) throws Exception {
PropertyDescriptor[] pds = Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors();
for(PropertyDescriptor pd:pds) {
if(pd.getName().equals(s)) {
pd.getWriteMethod().invoke(object, value);
break;
}
}
}
11:既然各種對類的操作我們都用到,然後直接又寫,我們就打包咯,随時能用,是以有個開源的包beanutils就對這些功能進行了 包裝。
public static void main(String[] args) throws Exception{
Point p1 = new Point(3, 5);
//通過BeanUtils設定字段的值和讀取字段的值
BeanUtils.setProperty(p1, "x", 4);
System.out.println(BeanUtils.getProperty(p1, "x"));
//寫成字元串的形式也行,因為BeanUtils是用String的方式來設定字段的值
BeanUtils.setProperty(p1, "x", "47");
System.out.println(BeanUtils.getProperty(p1, "x"));
//輸出是:java.lang.String
System.out.println(BeanUtils.getProperty(p1, "x").getClass().getName());
//PropertyUtils.setProperty(p1, "x", "66");錯,因為PropertyUtils不會把字段當成字元串處理。
PropertyUtils.setProperty(p1, "x", 66);
System.out.println(PropertyUtils.getProperty(p1, "x"));
//輸出是:java.lang.Integer
System.out.println(PropertyUtils.getProperty(p1, "x").getClass().getName());
}
12:JAVA注解
* 注解能使我們發現自己程式中可能存在的錯誤,比如當你寫了override注解的時候,你覆寫父類的方法,如果覆寫不正确,編譯就不可能會通過。
這樣能更好的有助于我們發現錯誤,解決錯誤。
* 注解可以讓我們告訴别人我們的類庫中的某個方法已經廢棄了,可以使用别的方式代替。可以用deprecated注解。
* 注解也同樣可以讓我們消除我們調用已經廢棄的方法而産生的警告。 用@SuppressWarnings("deprecation")
* 注解的标記可以在各種地方,如包,類,方法,變量......
* 注解類的編寫
public @interface moom {
String str(); //一個方法,方法的傳回值是String,每個用到注解的地方相當于一個Moom注解執行個體
}
* 注解的生命周期
就是說注解是在什麼情況下起作用:三種:在編寫代碼的時候(source),在編譯的時候存在(class),在運作的時候存在(runtime)。
三種其實是一個枚舉,枚舉的名字是:RetentionPolicy
* 注解的擷取與例子
moom mo = Test.class.getAnnotation(moom.class);
mo.str();//通路注解的方法,傳回寫注解時候的數值
* 注解retention
前面說到注解有三種類型,這個也是一個注解,它就是用來指定注解的生命周期。
eg:
@retention(RetentionPolicy.RUNTIME)
* 注解target
用來指定注解可以用在什麼地方。
eg:
@Target(value={ElementType.METHOD}):指定名用方法上,還有type(類上),package,annotation......
* 可以給方法設定預設值,當我們沒有預設值的時候,我們可以發現在我們使用這個注解的時候我們必須給注解所擁有的方法指定
相應的值。如下方式:String str() default "abc";這樣我們就預設的設定了方法的預設傳回值。在調用的時候,如果我們沒有
給他設定相應的值,它就取這個預設的值。
* 注解裡的方法可以傳回好多類型,比如:string,int,數組,class,annotation,enum.....
eg:
public @interface moom {
String str() default "a";
int intt();
String[] stt();
Light getLight();
Retention getRoom();
Class getC();
}
用的時候相應的設定方法如下:
@moom(intt=1,stt={"a","b","c"},getLight=Light.GREEN,getRoo[email protected](value = RetentionPolicy.RUNTIME),getC=String.class)
列印設定的相關資訊:
moom mo = AnnotationTest.class.getAnnotation(moom.class);
System.out.println(mo.str());
System.out.println(mo.intt());
System.out.println(Arrays.asList(mo.stt()));
System.out.println(mo.getLight().getLigth().name());
System.out.println(mo.getRoom().value());
System.out.println(mo.getC().getName());
13:泛型
* 泛型幾基本英雄,消除繁瑣的類型轉換。
有時候我們給一個集合或者在反射的時候要指定一些東西,拿出來的時候是一個Object,這時候我們就要用類型轉換轉成相應的類型。
如果用類型,我們在放之前就指定是什麼類型的,就可以防止類型轉換了,因為它知道你要拿的類型是什麼類型的。
* 使用的泛型之後的比如所有集合,雖然它們的參數化類型不一樣,但是它還是和JDK4一樣,為的效率,每次我們拿它們的class拿到的是同一份
eg:
Collection<String> con = new ArrayList<String>();
Collection<Integer> con1 = new ArrayList<Integer>();
//true 說明不同類型參數的集合,其實拿到的是同一份class位元組碼
System.out.println(con.getClass()== con1.getClass());
* 使用了參數類型化的集合之後我們是不能放其它的東西 在裡面了。
如:Collection<String> con = new ArrayList<String>();我們是不在在編輯的時候con.add(1).但是我們知道的是JDK4不用泛型的時候是能
放進去任何類型的。是以應該支援,這時候我們就應該想到用反射,在運作的時候它才知道我們放進去的是int型 的,進而也就不會在編譯的時候
報錯,不讓我們放進去了。
eg:
Collection<String> con = new ArrayList<String>();
con.add("abc");
Method methodAdd = con.getClass().getMethod("add", Object.class);
methodAdd.invoke(con, 1);
System.out.println(con);
這樣我們就是成功的把INT型的數值放進了在聲明 的時候是放入String的集合中。
* 但是有的時候我們還是希望能夠處理各種各樣的參數類型化的集合。
eg:有個方法,我們隻要給它一個集合,不管是什麼樣的參數類型的。都能列印出。
這時候 就用到了泛型的通配符?
public static void print(Collection<?> con) {
System.out.println(con);
}
這樣的話,我們定了?的化,它就能接收不同類型的參數類型的集合了。
* Collection<? extends Object> 有的時候我們會看到這樣的。 其實我們也應該可以發現也有?比對,當是?的時候它是可以比對任何參數化類型的資料的。
當用了extends的時候,它的意思就是說隻能比對是繼承了Object的類型(包括OBject本身)。
eg:Collection<? extends Number> con = new ArrayList<Integer>();對的
Collection<? extends Number> con = new ArrayList<String>();錯的
*小例子
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Set<Entry<String,Integer>> set = map.entrySet();
for(Map.Entry<String, Integer> m:set) {
System.out.println(m.getKey() + "," + m.getValue());
}
* 泛型方法 public T <T> getS(T a ,T b ) { return T;}
* 泛型類
public class DAO<T> {
public void add(T t) {
}
public T find(int id) {
return null;
}
}
* 擷取方法上的參數裡的泛型參數的類型
Method method = GenericTest.class.getMethod("pt", Vector.class); //擷取Pt方法,參數是Vector
Type[] type = method.getGenericParameterTypes(); //擷取方法的泛型化參數
System.out.println(type[0]);
ParameterizedType t = (ParameterizedType)type[0]; //拿到第一個泛型化的參數
System.out.println(t.getActualTypeArguments()[0] == String.class); //拿到第一個參數的泛型的實際類型,看看它的CLASS是否是String.class
System.out.println(t.getRawType()==VeLASS); //拿到第一個參數的類型,看它是的CLASS是否是Vector.class.
14:類加載器。
* 預設有三個:BootStrap,ExtClassLoader,AppClassLoader
* 類加載器也是類,但是類加載器又是誰加載的呢。是由BootStrap,它不是一個類,它是JVM開的時候就在記憶體當中存在,是由C++編寫的。
* BootStrap----最上面的類加載器,主要用來加載jre/lib/rt.jar下的類,就是系統給我們提供的常用的類
ExtClassLoader----BootStrap的子加載器,主要是用來加載jre/lib/ext/*.jar.是以當我們想把加載我們自己的類的時候,我們就可以把這
些類放到該目錄下就能加載通路了,進而不會出現classNotFoundException.
AppClassLoader---ExtClassLoader的子加載器,主要用來加載classpath下的類。是以我們隻要設定classpath就能設通路到我們自己想要通路的類了。
* 類加載器的機制:我們應該知道當我們想要寫一個類,比如自己的類java.lang.System來覆寫系統的這個類,這是不可能的,因為JVM是這樣做的。當你
想要加載一個類的時候,先用這個類比如AppClassLoader加載,AppClassLoader然後叫它的爸爸ExtClassLoader加載,ExtClassLoader又叫BootStrap加載
它找到了JAVA.LANG.STRING這個類,然後加載傳回去就行了。 這樣做一是能避免别人這樣做覆寫是吧。 二是,如果我們有多個加載器,每個加載器都加載
同一個類,這樣的話每個都行成一份class,給記憶體造成壓力。是以交給爸爸的話,就加載一份,以後你們要加,直接向爸爸要就行了。
* eg:
//這樣的話會報空指針錯誤,因為它是用BootStrap來加載的,它是用c++編寫的,根本不存在類的class,是以被是空
//System.out.println(System.class.getClassLoader().getClass().getName());
//這樣列印我們就會發現列印出null
System.out.println(System.class.getClassLoader());
//我們發現時AppClassLoader加載的,因為ClassLoader也是一個類,是以也是.getClassLoader().getClass().getName()通路
System.out.println(DAO.class.getClassLoader().getClass().getName());
//我們發現列印的是ExtClassLoader,因為我剛才把AnnotationTest打包到了jre/lib/ext/下去了。
System.out.println(AnnotationTest.class.getClassLoader().getClass().getName());
ClassLoader cl = DAO.class.getClassLoader();
while(cl!=null) {
System.out.println(cl.getClass().getName());
cl = cl.getParent();
}
* 這樣的話,我們不就是也可以寫自己的類加載器,然後在程式運作的時候指定我們要加載的類,進而不用說每次都把我們想加載的類放到classpath下了。
不過應該我們也能在程式擷取AppClassLoader---ExtClassLoader 這些加載器,然後讓它們加載我們想加載的類。
剛才自己做了一下實驗發現,ExtClassLoader意思就是說它隻能加載jre/lib/ext下在類,我本來還想說通過程式運作的時候看看能不能讓它加載别的類。
看來是不行的了,還是得自己寫classloader,不能在現在的classloader下面做什麼不好的想法。
同時讓我回想起來了tomcat的類加載機制。
* 利用加密class的方式引出自定義的ClassLoader
首先寫一個簡單的加密:
public static void main(String[] args) throws Exception{
String s1 = args[0];
String s2 = args[1];
FileInputStream in = new FileInputStream(s1);
FileOutputStream out = new FileOutputStream(s2);
cpy(in,out);
}
private static void cpy(InputStream in,OutputStream out) throws Exception{
int b;
while((b=in.read())!=-1) {
out.write(b ^ 0xff);
}
in.close();
out.close();
}
加密方法是對In檔案進行加密,然後輸出是Out
從參數中接收源來目的類所在的地方,然後加密後就行了,生成一樣的檔案名的類。
這時候我們把加密後的class拷貝到沒有加密的class上覆寫它。然後我們在運作之
前程式用到class的地方,我們發現就會出錯哦。 因為那個class已經不是正常的了
是加密後的了,我們要解密類加載器才能進行加載。
這時候我們就自己定一個類加載器去解釋這個類就行了。如下:
public static void main(String[] args) throws Exception{
String s1 = args[0]; //這是運作的時候給我們傳過來要加密的class所在的路徑
String s2 = args[1]; //把加密後的class放到哪個目錄下
FileInputStream in = new FileInputStream(s1);
FileOutputStream out = new FileOutputStream(s2);
cpy(in,out); //加密方法
}
private static void cpy(InputStream in,OutputStream out) throws Exception{
int b;
while((b=in.read())!=-1) {
out.write(b ^ 0xff); //加密很假單,隻是異或一下,所解和時候同樣用這個方法就解開了
}
in.close();
out.close();
}
private String classDir; //加載的CLASS所在的目錄
public MyClassLoader() {}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = classDir + "/" + name + ".class"; //加載的類的名字
System.out.println(className);
try {
FileInputStream in = new FileInputStream(className);
ByteArrayOutputStream out = new ByteArrayOutputStream();
cpy2(in, out); //把加載的檔案流傳回byte[]
in.close();
out.close();
byte[] bytes = out.toByteArray();
return this.defineClass(name,bytes, 0, bytes.length) //真正的解析加載成class
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
private static void cpy2(InputStream in,OutputStream out) throws Exception{
int b;
while((b=in.read())!=-1) {
out.write(b); //這個是我用到加載對應檔案夾下的class,不是用來加密的。
}
in.close();
out.close();
}
* 這樣的話加載方式,我們以後就可以做自己想做的事,自己想加載的class,而不用說用系統給我們的。
* 類加載,當我們加載的時候一個類是用一個類加載器加載,那麼它所在的類裡的用到的類也是用它自己
類加載器加載上或者父類加載器加載。都不存在的話就出錯。就算它的子加載器能加載這個CLASS也會
報錯。
15:代理
StringBuilder(線程不安全) StringBuffer(線程安全)
原理:JDK的代理是利用Proxy來建立代理。Proxy.getProxyClass(所有代理類的classloader,所要代理類所要實作的接口);
擷取代理類的class之後,我們順利成章的本來應該是newInstance()出現一個對象形成代理吧,但是事實卻不是這樣的,我們
通過列印代理類的所有構造方法發現,它隻存在一個構造方法,構造方法的參數是InvocationHandler,是以我們要傳遞一個
InvocationHandler才能建立出代理類。
Proxy.getProxyClass(Test.class,Test.class.getInterfaces()).getConstructor(InvocationHandler.class).newInstance(new InvocationHandlerChildern());
傳遞一個實作InvocationHandler的接口的類的對象建立執行個體。
我們發現實作InvocationHandler就要實作它的方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
代理類在調用任何實際的方法的時候都會調用這個方法。是以我們要調用真實方法的時候,應該在實作InvocationHandler接口類裡面儲存目标類
的一個引用,然後再建立實作InvocationHandler的類對象的時候,傳遞目标類,然後再Invoke方法裡面用真實目标類調用真實方法。在傳回結果。
JDK動态代理原理:
用戶端(調用的時候傳遞目标類,而且傳遞實作InvocationHandler接口類的對象)----->代理類(接收目标類和InvactionHandler對象,儲存到自己的變量當中)
------>代理類根據客戶段調用的方法,代理類裡面的實作也是這樣的-------void test() {
invocationHandler.invoke()
}
---->就是說用戶端調用test()方法,代理類它也有一個test()方法,然後進行調用這個方法,在用invocationHandler調用Invoke方法,這時候程式就進入到我們
寫的invocationHandler的invoke方法裡面進行執行,我們就可以在這個方法裡面,做一些自己想做的事情,做完之後再用真實的目标對象調用我們确實想調用的
方法,然後結束。
* 代理,我們隻是想說通過它能多做一些操作,是以說我們生成代理應該給它傳遞我們要人家做什麼事情的對象,當然我們不肯給代理說你就做這個事情,不能寫死
了,那天程式需要改動又要修改源程式,那麼不利于擴充,是以我們可以用接口,用一個接口去引用我們想要用的對象,以後每次我想改變我們的代理所需要做的
事情,我們就用新的事情,繼承接口,傳遞對象,就可以實作想要做的事情。 多态的 展現。
* 順便學習了Spring的AOP實作。在實作的過程中,進一步加強我自己對SPRING的了解和對JDK動态代理的原理。