Java程序中的各個Java類屬於同一類事物,描述這類事物的Java類名就是Class。Class類中提供了大量操作字節碼文件的方法。Class類代表Java類,它的各個實例對象又分別對應對應各個類在內存中的字節碼,例如,Person類的字節碼,ArrayList類的字節碼,等等。一個類被類加載器加載到內存中,佔用一片存儲空間,這個空間裏面的內容就是類的字節碼,不同的類的字節碼是不同的,所以它們在內存中的內容是不同的,這一個個的空間可分別用一個個的對象來表示,這些對象顯然具有相同的類型,這個類型就是Class類型
我的理解:其實Class類就是用來操作.class文件的,將.class文件封裝成對象,使用Class類中的方法進行操作
1.類名.class,例如,System.class 通常用於獲取已知類或者類型的.class文件2.對象.getClass(),例如,new Date().getClass() 通常用於獲取自定義對象的.class文件
|--java.lang.Object|--class<?> getClass() 返回object的運行時類
3.Class.forName("類名"),例如,Class.forName("java.util.Date") 通常用於獲取API文檔中的.class文件|--java.lang.Class|--static Class<?> forName(String className) 返回與帶有給定字符串名的類或接口相關聯的 Class 對象。
分別是:
他們的內部都封裝了一個TYPE字段,這九個類的包裝類對象調用TYPE字段後都會返回一個Class類
表示基本類型 byte 的 Class 實例。
代碼示例:
package com.learn;
public class ReflectionDemo
{
/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException
{
// TODO Auto-generated method stub
//獲取字節碼文件的第一種方法
Class cls1 = Thread.class;
//獲取字節碼文件的第二種方法
Class cls2 = new Thread().getClass();
//獲取字節碼文件的第三種方法
Class cls3 = Class.forName("java.lang.Thread");
//判斷他們是否是同一個字節碼文件
System.out.println(cls1 == cls2);
System.out.println(cls2 ==cls1);
//判斷該字節碼文件是否是基本數據類型的字節碼文件
System.out.println(long.class.isPrimitive());
//判斷是否是同一個字節碼文件
System.out.println(int.class == Integer.TYPE);
//返回值爲false
System.out.println(int.class == Integer.class);
//void是9個預定義實例對象中的其中一個
System.out.println(void.class.isPrimitive());
//判斷字節碼文件是否爲數組類型
System.out.println(int[].class.isArray());
}
}
反射就是把Java類中的各種成分映射成相應的java類。
概述:
一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Constructor、Package等等。繼承體系java.lang.reflect
|--java.lang.reflect.AccessibleObject
|--java.lang.reflect.Field|--java.lang.reflect.Method|--java.lang.reflect.Constructor
一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象,對這些實例對象的操作是學習重點
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一個構造方法:
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//獲得方法時要用到類型
1.java.lang.reflect.Constructor中的 newInstance()方法通常方式:String str = new String(new StringBuffer("abc"));反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));//調用獲得的方法時要用到上面相同類型的實例對象2.java.lang.Class中的newInstance()方法Class.newInstance()方法:例子:String obj = (String)Class.forName("java.lang.String").newInstance();該方法內部先得到默認的構造方法,然後用該構造方法創建實例對象。該方法內部的具體代碼是怎樣寫的呢?用到了緩存機制來保存默認構造方法的實例對象。
package com.learn;
import java.lang.reflect.Constructor;
public class ReflectDemo2
{
/**
* @param args
* @throws Exception
* @throws SecurityException
*/
public static void main(String[] args) throws SecurityException, Exception
{
// TODO Auto-generated method stub
//遍歷Thread類中的所有構造函數
//獲取類中的所有構造方法,存入集合中
Constructor[] constructors = Class.forName("java.lang.Thread").getConstructors();
for(Constructor constructor : constructors)
{
System.out.println(constructor);
}
//獲取一個指定參數類型的構造函數
//方法一:costructor.newInstance(Type ... args);
Constructor constructor1 = Class.forName("java.lang.String").getConstructor();
String str = (String)constructor1.newInstance();
System.out.println(str.length());
//方法二:Class.newInstance();
String str1 = (String)Class.forName("java.lang.String").newInstance();
System.out.println(str.length());
}
}
總結:
異常會發生在兩個狀態下1.編譯時,JVM編譯源文件時,檢查等號左邊的變量定義2.運行時,執行文件時,檢查等號右邊的部分
package com.learn;
import java.lang.reflect.Field;
public class ReflectDemo3
{
/**
* @param args
* @throws Exception
*
*/
public static void main(String[] args) throws Exception
{
Demo3 d = new Demo3(6,5);
//獲取私有的成員變量
Field fieldX = Demo3.class.getDeclaredField("x");
//暴力反射,強制獲取私有的成員變量
fieldX.setAccessible(true);
System.out.println(fieldX.get(d));
Field fieldY = Demo3.class.getField("y");
System.out.println(fieldX.get(d));
}
}
public class Demo3
{
private int x = 9;
public int y = 9;
public Demo3(int x, int y)
{
super();
this.x = x;
this.y = y;
}
}
總結:
package com.learn;
import java.lang.reflect.Field;
public class FieldTest
{
/**
* @param args
* @param Demo
*/
public static void main(String[] args) throws Exception
{
// 創建Demo1類的對象
Demo1 d = new Demo1("abc","aabb","cctv");
//將對象傳入替換字符的方法中
replaceChar(d);
System.out.println(d);
}
private static void replaceChar(Object obj) throws Exception
{
//獲取對象中所有的Field
Field[] fields = obj.getClass().getFields();
//遍歷集合中的field
for(Field field : fields)
{
//判斷該field是否是String類型
if(field.getType() == String.class)
{
//獲取field在指定對象中所對應的字段
String oldstr = (String) field.get(obj);
//替換字段中的字符
String newstr = oldstr.replace('b','a');
//將新的字段添加進對象對應的字段中
field.set(obj, newstr);
}
}
}
}
public class Demo1
{
public String str1 ;
public String str2 ;
public String str3 ;
public Demo1(String str1, String str2, String str3)
{
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
public String toString()
{
return str1+"::"+str2+"::"+str3;
}
}
|--Field getField(String name):獲取字節碼文件中包含指定未私有變量的Field對象(只能獲取未被隱藏的field)|--Field[] getFields():獲取字節碼文件中所有的Field對象|--Field getDeclaredField:獲取字節碼文件中變量的Field對象(無論是否隱藏都可以獲取,當獲取的field是隱藏成員變量時,需進行暴力反射)|--Field[] getDeclaredFields:獲取字節碼文件中的所有變量
|--Object get(Object obj):返回指定對象上字段的值|--Class getType():返回Field對象中field的數據類型|--void set(Object obj, Object value) :將指定對象變量上此 Field 對象表示的字段設置爲指定的新值。
|--void AccessibleObject(boolean flag):對隱藏的field進行暴力反射
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
調用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
注意:
package com.learn;
import java.lang.reflect.Method;
public class ReflctMethod
{
public static void main(String[] args) throws Exception
{
Demo1 d = new Demo1("qwer","tyui","asdf");
Method toString = Demo1.class.getMethod("toString");//空參數的方法,只要傳入方法名,沒參數就不用傳入參數類型
System.out.println(toString.invoke(d));//第一個傳入的Object是調用方法的對象,第二個傳入的是具體的參數,沒有就不傳
}
}
public class Demo1
{
public String str1 ;
public String str2 ;
public String str3 ;
public Demo1(String str1, String str2, String str3)
{
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
public String toString()
{
return str1+"::"+str2+"::"+str3;
}
}
總結:
當獲取的方法是一個空參數的方法時,getMethod(String name,Class DataType)方法的第二個參數不用傳入,在調用獲取的方法時,invoke(Object obj,Object... args)第二個參數不用傳值,也不能寫爲null。
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的語法,需要將一個數組作爲參數傳遞給invoke方法時,數組中的每個元素分別對應被調用方法中的一個參數,所以,調用charAt方法的代碼也可以用Jdk1.4改寫爲 charAt.invoke(“str”, new Object[]{1})形式。
package com.learn;
import java.lang.reflect.Method;
public class ReflctMethod
{
public static void main(String[] args) throws Exception
{
Demo1 d = new Demo1("qwer","tyui","asdf");
//如果有參數,那麼此時的參數類型的字節碼文件是Object[].class
Method toString = Demo1.class.getMethod("toString");
//空參數的方法,所以數組中的參數列表中沒有元素
System.out.println(toString.invoke(d,new Object[]{}));
}
}
public class Demo1
{
public String str1 ;
public String str2 ;
public String str3 ;
public Demo1(String str1, String str2, String str3)
{
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
public String toString()
{
return str1+"::"+str2+"::"+str3;
}
}
用反射方式執行某個類中的main方法
main方法的格式是固定的,public static void main(String[] args) 注意:JDK1.5後出現的可變參數:String[]... args從主函數格式中我們知道函數名:main參數類型:String[]參數個數:0長度:0
package com.learn;
import java.lang.reflect.Method;
public class ReflectMain
{
public static void main(String[] args) throws Exception
{
String str = args[0];
Method mainMethod = Class.forName(str).getMethod("main", String[].class);
mainMethod.invoke(null, (Object)new String[]{});
}
}
運行以上程序發現,將會報角標越界異常以上程序其實就是:Method mainMethod = Class.forName(類全名).getMethod("main",String[].class);
啓動Java程序的main方法的參數是一個字符串數組,即public static void main(String[] args),通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?按jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javac會到底按照哪種語法進行處理呢?jdk1.5肯定要兼容jdk1.4的語法,會按jdk1.4的語法進行處理,即把數組打散成爲若干個單獨的參數。所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”}),javac只把它當作jdk1.4的語法進行理解,而不把它當作jdk1.5的語法解釋,因此會出現參數類型不對的問題。
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,編譯器會作特殊處理,編譯時不把參數當作數組看待,也就不會將數組打散成若干個參數了
基本數據類型數組和非基本數據類型的區別1.具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象。2.代表數組的Class實例對象的getSuperClass()方法返回的父類爲Object類對應的Class。
基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。
示例:int[] arr = new int[]{1,2,3};String[] arr1 = new String[]{"a","b","c"};Object obj = Arrays.asList(arr);Object obj1 = Arrays.asList(arr1);System.out.println(arr);System.out.println(arr1);輸出結果:[[I@b6e39f] 因爲基本類型的一維數組是Object類型,所以asList()方法將arr數組當做一個對象處理[a, b, c] 而String類型的數組即可作爲Object類型也可當做Object[]類型使用,String內部的元素都是對象,輸出的是對象的集合
Array工具類用於完成對數組的反射操作。
package com.learn;
import java.lang.reflect.Array;
public class ReflectArray
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
String[] str = new String[]{"a","b","c"};
PrintObject(str);
}
private static void PrintObject(Object obj)
{
// 有兩種情況,一種是obj是數組類型,一種就是普通對象
Class cls = obj.getClass();
if(cls.isArray())
{
for(int x = 0;x<Array.getLength(obj);x++)
{
System.out.println(Array.get(obj, x));
}
}else
{
System.out.println(obj);
}
}
}
用到的的方法
package com.learn;
public class ReflectPoint
{
private int x;
private int y;
public ReflectPoint(int x, int y)
{
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
測試代碼:package com.learn;
import java.util.ArrayList;
import java.util.Collection;
public class ReflectTest
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
Collection<ReflectPoint> list = new ArrayList<ReflectPoint>();
ReflectPoint rp = new ReflectPoint(1,2);
ReflectPoint rp1 = new ReflectPoint(2,3);
ReflectPoint rp2 = new ReflectPoint(3,4);
ReflectPoint rp3 = new ReflectPoint(3,4);
list.add(rp);
list.add(rp1);
list.add(rp2);
list.add(rp3);
System.out.println(list.size());
}
}
框架的概念及用反射技術開發框架的原理
框架與框架要解決的核心問題
我做房子賣給用戶住,由用戶自己安裝門窗和空調,我做的房子就是框架,用戶需要使用我的框架,把門窗插入進我提供的框架中。框架與工具類有區別,工具類被用戶的類調用,而框架則是調用用戶提供的類。
框架要解決的核心問題
模擬框架示例:我在寫框架(房子)時,你這個用戶可能還在上小學,還不會寫程序呢?我寫的框架程序怎樣能調用到你以後寫的類(門窗)呢?因爲在寫才程序時無法知道要被調用的類名,所以,在程序中無法直接new某個類的實例對象了,而要用反射方式來做。
classname = java.util.ArrayList
package com.learn;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
public class ReflectTest2
{
/**
* @param args
* 創建一個加載任意集合的框架
*/
public static void main(String[] args) throws Exception
{
//獲取文件
InputStream is = new FileInputStream("config.properties");
//創建空列表對象
Properties p = new Properties();
//加載配置文件
p.load(is);
//關閉流資源
is.close();
//獲取鍵對應的值
String classname = p.getProperty("classname");
//使用反射創建配置文件中要建立的對象
Collection collection = (Collection)Class.forName(classname).newInstance();
collection.add(new ReflectPoint(1,2));
collection.add(new ReflectPoint(1,2));
collection.add(new ReflectPoint(1,2));
collection.add(new ReflectPoint(1,2));
System.out.println(collection.size());
}
}
InputStream is=ReflectTest.class.getClassLoader(). getResourceAsStream("com/itheima/day01/config.properties");
2,利用Class方式進行加載,使用相對路徑的方式
InputStream is = ReflectTest.class.getResourceAsStream("config.properties");
3,利用Class方式進行加載,使用絕對路徑的方式
InputStream is = ReflectTest.class.getResourceAsStream("com/itheima/day01/config.properties");