網(wǎng)站建設(shè)價(jià)格明細(xì)表和網(wǎng)站預(yù)算搜索最全的搜索引擎
反射
一、反射的概念
反射:加載類,反射出類的各個(gè)組成部分(類的成員:構(gòu)造方法,屬性,方法)
java反射機(jī)制:在運(yùn)行狀態(tài)中,對于任何一個(gè)類都能夠知道這個(gè)類的所有屬性和方法;對于任意一個(gè)對象,能夠調(diào)用它的任意屬性和方法;這種動(dòng)態(tài)獲取信息的方式就稱為反射。
二、加載類???怎么加載
當(dāng)程序要使用某個(gè)類時(shí),如果這個(gè)類沒有加載到內(nèi)存中,則系統(tǒng)會(huì)通過加載,連接,初始化三個(gè)步驟來實(shí)現(xiàn)對這個(gè)類的初始化。
-
加載:
將class(字節(jié)碼)文件讀取到內(nèi)存中,并為之創(chuàng)建一個(gè)Class對象
任何類被使用時(shí)都會(huì)被創(chuàng)建一個(gè)Class對象(注:一個(gè)類只有一個(gè)Class對象)
-
連接:
- 驗(yàn)證:是否有正確的內(nèi)部結(jié)構(gòu),并和其它協(xié)調(diào)一致
- 準(zhǔn)備:負(fù)責(zé)為類的靜態(tài)成員分配內(nèi)存,并設(shè)置默認(rèn)初始化
- 解析:將類的二進(jìn)制數(shù)據(jù)中的符號引用替換成直接引用
-
初始化:
該階段主要是為類的類變量初始化值的,初始化有兩種方式:
- 在聲明類變量時(shí),直接給變量賦值
- 在靜態(tài)初始化塊為類變量賦值
(一)類加載的時(shí)機(jī)
- 創(chuàng)建類的實(shí)例
- 訪問類的靜態(tài)成員或給靜態(tài)變量賦值
- 調(diào)用類的吧靜態(tài)方法
- 初始化某個(gè)類的子類
- java命令運(yùn)行某個(gè)類(如:java HelloWorld)
- 使用反射的方法(將某個(gè)類加載到類加載區(qū))強(qiáng)制創(chuàng)建某個(gè)類的Class對象
(二)類加載器
負(fù)責(zé)將class 文件加載到內(nèi)存中,并為之創(chuàng)建一個(gè)Class對象,如果了解類加載器的機(jī)制,可以的更好的理解程序的運(yùn)行
類加載器的組成:
-
根類加載器: bootstrap classLoader
也被稱為引導(dǎo)類加載類,負(fù)責(zé)Java核心類的加載
比如: System, String 等,在 JDK 中的JRE 中 lib 中的 rt.jar文件中
-
擴(kuò)展類加載器: extension classLoader
負(fù)責(zé)jre的擴(kuò)展目錄中的jar的加載
-
系統(tǒng)類加載器: System classLoader
負(fù)責(zé)在JVM啟動(dòng)時(shí)加載來自java命令的class文件
三、反射類的成員
(一)獲取Class對象的方式
// 先創(chuàng)建了一個(gè)Person類
public class Person {private String name;int age;public String address;public Person() {}private Person(String name){this.name = name;}Person(String name, int age) {this.name = name;this.age = age;}public Person(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}public void show(){System.out.println("show");}public void method(String s){System.out.println("method"+s);}public String getString(String s,String m){return s+"----"+m;}private void function(){System.out.println("function");}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", address='" + address + '\'' +'}';}
}
// 獲取Person類的Class對象
// 方法1:
Person person = new Person();
Class c = person.getClass();
// 方法2:
Class c2 = Person.class;
// 方法3:
Class c3 = Class.forName("com.gxa.demo1.Person");
// c=c2=c3:因?yàn)橐粋€(gè)類的Class對象只有一個(gè)
注:在開發(fā)中我們常使用第三種方式去做反射,因?yàn)榈谌N傳入的是個(gè)字符串而不是具體的類名,這樣的話就可以把這個(gè)值方法配置文件中去,方便修改。
(二)獲取類加載器
a: 得到類的Class對象
Class c = Class.forName("com.gxa.demo1.Person");
b: 獲取類的加載器
ClassLoader classLoader = c.getClassLoader();
-
使用類加載器加載其它的文件
// 得到Class對象 Class c = Class.forName("com.gxa.demo1.Person"); // 獲取類加載器對象 ClassLoader classLoader = c.getClassLoader(); // 類加載器加載其它文件(加入我的src目錄下面有一個(gè)jdbc.properties文件) InputStream in = classLoader.getResourceAsStream("jdbc.properties");
注:如果找不到文件則會(huì)返回null給輸入流
(三)反射類的構(gòu)造方法
-
public Constructor<T> getConstructor(類<?>... parameterTypes)
:返回一個(gè)Constructor
對象,返回的是public修飾的構(gòu)造方法;如果不寫參數(shù)則返回?zé)o參的構(gòu)造函數(shù)對象;如果構(gòu)造方法不是公共的將會(huì)報(bào)異常:NoSuchMethodExceptionClass c = Class.forName("com.gxa.demo1.Person"); // 返回公共的無參構(gòu)造,如果無參構(gòu)造不是public修飾的將會(huì)報(bào)錯(cuò) Constructor constructor = c.getConstructor(); System.out.println(constructor); // 有參數(shù)傳入:傳入的參數(shù)是數(shù)據(jù)類型類的Class對象;下面是獲取一個(gè)參數(shù)為String類型的構(gòu)造方法對象 Constructor constructor = c.getConstructor(String.class); System.out.println(constructor);
-
public Constructor<?>[] getConstructors()
:返回一個(gè)public修飾的構(gòu)造方法的Constructor對象的數(shù)組Constructor[] constructors = c.getConstructors(); // 返回的構(gòu)造方法都是public修飾的 System.out.println(constructors);
-
public Constructor<T> getDeclaredConstructor(類<?>... parameterTypes)
:返回一個(gè)Constructor
對象,不傳參數(shù)返回?zé)o參的構(gòu)造方法(沒有修飾符的限制),如果構(gòu)造方法不存在將報(bào)異常:NoSuchMethodException// 獲取無參的構(gòu)造方法 Constructor declaredConstructor = c.getDeclaredConstructor(); System.out.println(declaredConstructor);
-
public Constructor<?>[] getDeclaredConstructors()
:返回所有構(gòu)造方法的Constructor對象的數(shù)組// 獲取所有方法 Constructor[] declaredConstructors = c.getDeclaredConstructors(); System.out.println(declaredConstructors);
1、通過Constructor對象創(chuàng)建實(shí)例
// 獲取Class對象
Class c = Person.class;
// 獲取Person類的無參構(gòu)造
Constructor declaredConstructor = c.getDeclaredConstructor();
// 通過構(gòu)造方法對象創(chuàng)建一個(gè)Person類的實(shí)例對象
Object obj = declaredConstructor.newInstance();
System.out.println(obj);
注:如果獲取的構(gòu)造方法對象是一個(gè)非私有的,那么將會(huì)報(bào)IllegalAccessException
異常;
解決方案:
在Constructor類里面有一個(gè)方法叫作:public void setAccessible(boolean flag)
的方法,將此對象的accessible
標(biāo)志設(shè)置為指示的布爾值。 true
的值表示反射對象應(yīng)該在使用時(shí)跳過java的語法檢查。
// 獲取Class對象
Class c = Person.class;
// 獲取Person類的無參構(gòu)造
Constructor declaredConstructor = c.getDeclaredConstructor();
// 設(shè)置constructor對象的訪問權(quán)限
declaredConstructor.setAccessible(true);
// 通過構(gòu)造方法對象創(chuàng)建一個(gè)Person類的實(shí)例對象
Object obj = declaredConstructor.newInstance();
System.out.println(obj);// 通過有參構(gòu)造方法創(chuàng)建類的實(shí)例
Class c = Person.class;Constructor declaredConstructor = c.getDeclaredConstructor(String.class);declaredConstructor.setAccessible(true);Object obj = declaredConstructor.newInstance("張三");
System.out.println(obj);
(四)反射類的屬性
與獲取構(gòu)造方法對象的獲取方法相似:
-
public Field getField(String name)
:返回一個(gè)Field
對象,它反映此表示的類或接口的指定公共成員字段類對象。Class c = Person.class; Field addressField = c.getField("address"); System.out.println(addressField);
注:在使用getField方法是如果傳的name為不存在的屬性或非公共屬性將會(huì)報(bào)異常:NoSuchFieldException
-
public Field[] getFields()
:返回包含一個(gè)數(shù)組Field
對象反射由此表示的類或接口的所有可訪問的公共字段類對象。// 獲取該類公共屬性的field對象的數(shù)組 Field[] fields = c.getFields();
-
public Field getDeclaredField(String name)
:返回一個(gè)Field
對象,它反映此表示的類或接口的指定字段類對象。Field name = c.getDeclaredField("name");
-
public Field[] getDeclaredFields()
:返回的數(shù)組Field
對象反映此表示的類或接口聲明的所有字段類對象。獲取所有屬性的field對象集合 Field[] fields = c.getDeclaredFields();
1、通過反射來給Person的對象賦值
// 沒有對象就沒有屬性
// 要使用屬性,必須 要有對象
// 1、反射一個(gè)無參的構(gòu)造方法的Constructor對象
Constructor constructor = c.getDeclaredConstructor();
// 2、創(chuàng)建對應(yīng)的對象
Object obj = constructor.newInstance();
// 3、反射Person類的屬性對象
Field namefield = c.getDeclaredField("name");
Field agefield = c.getDeclaredField("age");
Field addressfield = c.getDeclaredField("address");
// 4、給對象實(shí)例賦值
namefield.setAccessible(true); // 設(shè)置私有屬性的訪問權(quán)限
namefield.set(obj,"張三");
agefield.set(obj,18);
addressfield.set(obj,"成都");
System.out.println(obj);
(五)反射類的方法
-
public 方法 getDeclaredMethod(String name,類<?>... parameterTypes)
:返回一個(gè)方法
對象,它反映此表示的類或接口的指定聲明的方法類對象。 -
public 方法[] getDeclaredMethods()
:返回包含一個(gè)數(shù)組方法
對象反射的類或接口的所有聲明的方法,通過此表示類
對象,包括公共,保護(hù),默認(rèn)(包)訪問和私有方法,但不包括繼承的方法。 -
public 方法 getMethod(String name,類<?>... parameterTypes)
:返回一個(gè)方法
對象,它反映此表示的類或接口的指定公共成員方法類
對象。 -
public 方法[] getMethods()
:返回包含一個(gè)數(shù)組方法
對象反射由此表示的類或接口的所有公共方法類
對象,包括那些由類或接口和那些從超類和超接口繼承的聲明。// 1、獲取Class對象 Person person = new Person(); Class aClass = person.getClass(); // 2、獲取Person的方法Method對象 Class對象.getDeclaredMethod("方法名",數(shù)據(jù)類型的Class對象); Method method = aClass.getDeclaredMethod("method", String.class); // invoke是Method類中的方法:表示調(diào)用某個(gè)具體對象的方法 Method對象.invoke(類的實(shí)例對象,參數(shù)...); // 通過Method對象調(diào)用person對象的method方法,為了防止權(quán)限不夠(方法為私有的)最好使用setAccessible(true)來跳過java的代碼檢查 method.setAccessible(true); method.invoke(person,"反射調(diào)用了method方法");
案例:
在ArrayList 對象中添加一個(gè)字符串;
我們知道指定了ArrayList中參數(shù)的類型再傳入其他類型的話java會(huì)報(bào)錯(cuò),那么這個(gè)時(shí)候我們就可以通過反射的方式去獲取ArrayList類中的add方法給ArrayList的對象賦值,因?yàn)锳rrayList類的add方法傳入的參數(shù)是一個(gè)泛型而不是具體的類型,所以我們可以添加任意數(shù)據(jù):
// 1、創(chuàng)建一個(gè)ArrayList<Integer>對象 ArrayList<Integer> list = new ArrayList<>(); // 2、通過反射的方式獲取ArrayList的Class對象 Class listClass = list.getClass(); // 3、通過Class對象反射add方法 Method listAdd = listClass.getDeclaredMethod("add",Object.class); // 4、通過ArrayList類的add方法給ArrayList<Integer>實(shí)例對象添加值// 4.1、為了防止訪問的權(quán)限不夠加上setAccessible方法跳過java代碼檢查 listAdd.setAccessible(true);// 4.2、添加值 listAdd.invoke(list,"我在Integer類型的集合里添加了一個(gè)字符串"); // 5、打印輸出list集合 System.out.println(list);
面試題
面試題
第一題:JDBC操作數(shù)據(jù)庫的步驟 ?
1)注冊數(shù)據(jù)庫驅(qū)動(dòng)。
2)建立數(shù)據(jù)庫連接。
3)獲取statemen對象。
4)執(zhí)行SQL語句。
5)返回結(jié)果(如果是查詢操作有結(jié)果集:處理結(jié)果集)。
6)(釋放資源)關(guān)閉數(shù)據(jù)庫連接
第二題:JDBC中的Statement 和PreparedStatement,CallableStatement的區(qū)別?
1)PreparedStatement是預(yù)編譯的SQL語句,效率高于Statement。
2)PreparedStatement支持“?”操作符,相對于Statement更加靈活。
3)PreparedStatement可以防止SQL注入,安全性高于Statement。
4)CallableStatement適用于執(zhí)行存儲(chǔ)過程。
第三題:execute,executeQuery,executeUpdate的區(qū)別是什么?
· Statement的execute(String query)方法用來執(zhí)行任意的SQL查詢,如果查詢的結(jié)果是一個(gè)ResultSet,這個(gè)方法就返回true。如果結(jié)果不是ResultSet,比如insert或者update查詢,它就會(huì)返回false。我們可以通過它的getResultSet方法來獲取ResultSet,或者通過getUpdateCount()方法來獲取更新的記錄條數(shù)。
· Statement的executeQuery(String query)接口用來執(zhí)行select查詢,并且返回ResultSet。即使查詢不到記錄返回的ResultSet也不會(huì)為null。我們通常使用executeQuery來執(zhí)行查詢語句,這樣的話如果傳進(jìn)來的是insert或者update語句的話,它會(huì)拋出錯(cuò)誤信息為 “executeQuery method can not be used for update”的java.util.SQLException。
· Statement的executeUpdate(String query)方法用來執(zhí)行insert或者update/delete(DML)語句,或者 什么也不返回DDL語句。返回值是int類型,如果是DML語句的話,它就是更新的條數(shù),如果是DDL的話,就返回0。
· 只有當(dāng)你不確定是什么語句的時(shí)候才應(yīng)該使用execute()方法,否則應(yīng)該使用executeQuery或者executeUpdate方法。
第四題:PreparedStatement的缺點(diǎn)是什么,怎么解決這個(gè)問題?
PreparedStatement的一個(gè)缺點(diǎn)是,我們不能直接用它來執(zhí)行in條件語句;需要執(zhí)行IN條件語句的話,下面有一些解決方案:
1)分別進(jìn)行單條查詢——這樣做性能很差,不推薦。
2)使用存儲(chǔ)過程——這取決于數(shù)據(jù)庫的實(shí)現(xiàn),不是所有數(shù)據(jù)庫都支持。
3)動(dòng)態(tài)生成PreparedStatement——這是個(gè)好辦法,但是不能享受PreparedStatement的緩存帶來的好處了。
4)在PreparedStatement查詢中使用NULL值——如果你知道輸入變量的最大個(gè)數(shù)的話,這是個(gè)不錯(cuò)的辦法,擴(kuò)展一下還可以支持無限參數(shù)。
第五題:JDBC的ResultSet是什么?
在查詢數(shù)據(jù)庫后會(huì)返回一個(gè)ResultSet,它就像是查詢結(jié)果集的一張數(shù)據(jù)表。
ResultSet對象維護(hù)了一個(gè)游標(biāo),指向當(dāng)前的數(shù)據(jù)行。開始的時(shí)候這個(gè)游標(biāo)指向的是第一行。如果調(diào)用了ResultSet的next()方法游標(biāo)會(huì)下移一行,如果沒有更多的數(shù)據(jù)了,next()方法會(huì)返回false??梢栽趂or循環(huán)中用它來遍歷數(shù)據(jù)集。
默認(rèn)的ResultSet是不能更新的,游標(biāo)也只能往下移。也就是說你只能從第一行到最后一行遍歷一遍。不過也可以創(chuàng)建可以回滾或者可更新的ResultSet。
當(dāng)生成ResultSet的Statement對象要關(guān)閉或者重新執(zhí)行或是獲取下一個(gè)ResultSet的時(shí)候,ResultSet對象也會(huì)自動(dòng)關(guān)閉。
可以通過ResultSet的getter方法,傳入列名或者從1開始的序號來獲取列數(shù)據(jù)。