在java中为什么_属性绑定到类型_方法绑定到对象_13. Java基础之类型信息(RTTI和反射)...
一. Java反射機制介紹
Java 反射機制。通俗來講呢,就是在運行狀態中,我們可以根據“類的部分已經的信息”來還原“類的全部的信息”。這里“類的部分已經的信息”,可以是“類名”或“類的對象”等信息。“類的全部信息”就是指“類的屬性,方法,繼承關系和Annotation注解”等內容。
舉個簡單的例子:假設對于類ReflectionTest.java,我們知道的唯一信息是它的類名是“com.skywang.ReflectionTest”。這時,我們想要知道ReflectionTest.java的其它信息(比如它的構造函數,它的成員變量等等),要怎么辦呢?
這就需要用到“反射”。通過反射,我們可以解析出ReflectionTest.java的完整信息,包括它的構造函數,成員變量,繼承關系等等。
在了解了“java 反射機制”的概念之后,接下來思考一個問題:如何根據類的類名,來獲取類的完整信息呢?
這個過程主要分為兩步:
第1步:根據“類名”來獲取對應類的Class對象。
第2步:通過Class對象的函數接口,來讀取“類的構造函數,成員變量”等信息。
下面,我們根據示例來加深對這個概念的理解。示例如下(Demo1.java):
1 packagecom.test.b;2
3 public classPerson {4 publicPerson() {5 System.out.println("no param constru");6 }7
8 publicPerson(String name) {9 System.out.println("have param constru");10 }11
12 }
View Code
1 public classDemo1 {2 public static void main(String args[]) throwsClassNotFoundException, InstantiationException, IllegalAccessException {3 Class> class1=Class.forName("com.test.b.Person");4 Object object=class1.newInstance();5 System.out.println("cls="+class1);6 System.out.println(object);7 }8
9 }
View Code
1 no param constru2 cls=classcom.test.b.Person3 com.test.b.Person@33909752
View Code
說明:
(01) Person類的完整包名是"com.test.b.Person"。而 Class.forName("com.test.b.Person"); 這一句的作用是,就是根據Person的包名來獲取Person的Class對象。
(02) 接著,我們調用Class對象的newInstance()方法,創建Person對象。
現在,我們知道了“java反射機制”的概念以及它的原理。有了這個總體思想之后,接下來,我們可以開始對反射進行深入研究了。
二. Class詳細說明
RTTI(Run-Time Type Infomation),運行時類型信息。可以在運行時識別一個對象的類型。類型信息在運行時通過Class對象表示,Class對象包含與類有關的信息,可以使用Class對象來創建類的實例。RTTI和Class對象有莫大的關系。
每個類對應一個Class對象,這個Class對象放在.class文件中,當我們的程序中首次主動使用某類型時,會把該類型所對應的Class對象加載進內存。
我們如何獲取到Class對象呢?有三種方法
1. Class.forName("全限定名");(其中,全限定名為包名+類名)。
2. 類字面常量,如String.class,對應String類的Class對象。
3.通過getClass()方法獲取Class對象,如String str = "hello";str.getClass();。
通過一個類對應的Class對象后,我們可以做什么?我們可以獲取該類的父類、接口、創建該類的對象、該類的構造器、字段、方法等等。
下面我們通過例子來熟悉Class對象的各種用法。
1. 三種獲取對象Class對象的方法
note1:在面向對象的世界里,萬事萬物都是對象。類是對象,類是java.lang.Class類的實例對象。
note2:查看Class類的源碼,發現構造函數是private的,只有虛擬機才可以創建class對象。
note3:任何一個類都是Class的實例對象,這個實例對象有3中標識方式:(1)(2)(3)所示。
(1)類名.class-------不會引起類初始化
這實際告訴我們任何一個類都有一個隱含的靜態成員變量class
1 public classPerson {2 public int a=1;3 public static int b=2;4 public final static int c=3;5 static{6 System.out.println("hello");7 }8
9 }10
11
12 public classTest2 {13 public static voidmain(String args[]) {14 Class> class1=Person.class; //未引起初始化
15 }16
17 }18
19
20 未輸出任何東西,說明.class沒有起到初始化作用
View Code
(2)對象.getClass()------會引起類初始化
1 public classTest2 {2 public static voidmain(String args[]) {3 Person person=newPerson();4 Class>class1=person.getClass();5 }6
7 }8
9
10
11 輸出:12 hello
View Code
(3)Class.forName(“全限定名”)--------會引起類初始化
1 public classTest2 {2 public static void main(String args[]) throwsClassNotFoundException {3 Class>class1=Class.forName("com.test.a.Person");4 }5
6 }7
8
9 輸出:10 hello
View Code
(4)一個類只可能是Class類的一個實例對象
因此上述三種方法都會得到同樣的一個Class類的實例對象。
1 packagecom.test.a;2
3 public classTest {4 public static void main(String args[]) throwsClassNotFoundException {5
6 Class class1 = Test.class;7 Test test = newTest();8 Class class2 =test.getClass();9 Class class3 = Class.forName("com.test.a.Test");10 System.out.println(class1 ==class2);11 System.out.println(class1 ==class3);12 }13 }14 true
15 true
View Code
(5)可以通過類的類類型創建該類的對象實例
Foo foo=(Foo)c1.newInstance();
2. Class類本身定義的方法使用
1 packagecom.test.a;2
3 public classPerson {4 publicString name;5 public intage;6
7 publicString getName() {8 returnname;9 }10
11 private voidsetName(String name) {12 this.name =name;13 }14
15 public intgetAge() {16 returnage;17 }18
19 private void setAge(intage) {20 this.age =age;21 }22 }23 packagecom.test.a;24
25 public class Woman extendsPerson{26 publicDouble salary;27 privateString sex;28 publicWoman(String sex) {29 this.sex=sex;30 }31 privateWoman() {32
33 }34 public voidprint()35 {36 System.out.println("hello");37 }38
39 private voidprint2() {40 System.out.println("hello2");41 }42
43 }
View Code
(1)獲取包名
1 packagecom.test.a;2
3 public classTest {4 public static voidmain(String args[]) {5 Class class1 = Test.class;6 System.out.println(class1.getName());7 System.out.println(class1.getSimpleName());8 Class c1 = double.class;9 Class c2 = String.class;10 Class c3 = Void.class;11
12 System.out.println(c1.getName());13 System.out.println(c1.getSimpleName());14 System.out.println(c2.getName());15 System.out.println(c2.getSimpleName());//不包含包名
16 System.out.println(c3.getName());17 System.out.println(c3.getSimpleName());18 }19 }20
21
22 com.test.a.Test23 Test24 double
25 double
26 java.lang.String27 String28 java.lang.Void29 Void
View Code
(2)獲取方法
1 packagecom.test.a;2
3 importjava.lang.reflect.Method;4
5 public classTest {6 public static voidmain(String args[]) {7 Woman woman=newWoman();8 Class class1=woman.getClass();//傳遞的是哪個子類的對象,class1就是該子類的類類型9 //一個成員方法就是一個Method對象
10 Method[] publicMethods=class1.getMethods();//得到所有的public方法,包含從父類繼承而來的Public方法
11 for(Method i:publicMethods) {12 System.out.println(i);13 }14
15 System.out.println("***************");16 Method[] declaredMethod=class1.getDeclaredMethods();//只打印該類自己定義的方法(沒有權限限制)
17 for(Method i:declaredMethod) {18 System.out.println(i);19 }20 }21 }22
23 /**
24 public void com.test.a.Woman.print()25 public java.lang.String com.test.a.Person.getName()26 public int com.test.a.Person.getAge()27 public final void java.lang.Object.wait() throws java.lang.InterruptedException28 public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException29 public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException30 public boolean java.lang.Object.equals(java.lang.Object)31 public java.lang.String java.lang.Object.toString()32 public native int java.lang.Object.hashCode()33 public final native java.lang.Class java.lang.Object.getClass()34 public final native void java.lang.Object.notify()35 public final native void java.lang.Object.notifyAll()36 ***************37 public void com.test.a.Woman.print()38 private void com.test.a.Woman.print2()39
40 */
View Code
(3)獲取成員變量
1 packagecom.test.a;2
3 importjava.lang.reflect.Field;4 importjava.lang.reflect.Method;5
6 public classTest {7 public static voidmain(String args[]) {8 Woman woman=newWoman();9 Class class1=woman.getClass();//傳遞的是哪個子類的對象,class1就是該子類的類類型
10 Field fields[]=class1.getFields();11 for(Field i:fields) {12 System.out.println(i);13 }14
15 System.out.println("**************");16 Field decFileds[]=class1.getDeclaredFields();17 for(Field i:decFileds) {18 System.out.println(i);19 }20 }21 }22
23 /**
24 public java.lang.Double com.test.a.Woman.salary25 public java.lang.String com.test.a.Person.name26 public int com.test.a.Person.age27 **************28 public java.lang.Double com.test.a.Woman.salary29 private java.lang.String com.test.a.Woman.sex30
31
32 */
View Code
(4)獲取對象的構造函數信息
1 packagecom.test.a;2
3 importjava.lang.reflect.Constructor;4 importjava.lang.reflect.Field;5 importjava.lang.reflect.Method;6
7 public classTest {8 public static voidmain(String args[]) {9 Woman woman=new Woman("femal");10 Class class1=woman.getClass();//傳遞的是哪個子類的對象,class1就是該子類的類類型
11
12 Constructor constructors[]=class1.getConstructors();13 for(Constructori:constructors) {14 System.out.println(i);15 }16 System.out.println("********");17
18 Constructor constructors2[]=class1.getDeclaredConstructors();19 for(Constructori:constructors2) {20 System.out.println(i);21 }22 }23 }24
25 /**
26 public com.test.a.Woman(java.lang.String)27 ********28 public com.test.a.Woman(java.lang.String)29 private com.test.a.Woman()30
31 */
View Code
3. 靜態編譯 vs 動態編譯
Java中編譯類型有兩種:
靜態編譯:在編譯時確定類型,綁定對象即通過。
動態編譯:運行時確定類型,綁定對象。動態編譯最大限度地發揮了Java的靈活性,體現了多態的應用,可以減低類之間的耦合性。
在靜態語言中,使用一個變量時,必須知道它的類型。在Java中,變量的類型信息在編譯時都保存到了class文件中,這樣在運行時才能保證準確無誤;換句話說,程序在運行時的行為都是固定的。如果想在運行時改變,就需要反射這東西了。
三. 深入理解反射
1. 為什么會有反射
假如你在程序運行過程中,從磁盤上或者從網絡上讀取接收了一串代表一個類的字節,既然這個類在你的程序被編譯很久之后才出現,那么你怎樣使用這樣的類呢?
解決:Class類和java.lang.reflect類庫一起對反射的概念進行了支持。
如果在編譯時編譯器不知道某個特定類的信息,本質是編譯時無法獲得并打開特定類的.class文件,而是在程序運行起來時jvm才擁有該特定類的.class文件。那么,如何使用這樣的文件呢?于是“反射”這個概念應運而生---提供在運行時操作.class文件的統一API
1 classTestClass {}2 ? ? TestClass testClass = newTestClass();3 ? ? Class c = Class.forName("TestClass"); ?TestClass testClass = c.newInstance(); //運行期間檢查
4 ? ? Class c = TestClass.class; ?TestClass testClass = c.newInstance(); ? //編譯期間檢查
View Code
與傳統RTTI必須在編譯器就知道所有類型不同,反射不必在編譯期就知道所有的類型,它可以在運行過程中使用動態加載的類,而這個類不必在編譯期就已經知道。反射主要由java.lang.reflect類庫的Field、Method、Constructor類支持。這些類的對象都是JVM在運行時進行創建,用來表示未知的類。
關于兩者的區別更深刻表達如下:對于RTTI而言,編譯器在編譯時打開和檢查.class文件;對于反射而言,.class文件在編譯時是不可獲取的,所以在運行時打開和檢查.class文件。
其實在的第一個例子中我們已經用到了Constructor、Method類,現在我們來更加具體的了解Constructor、Method、Field類。
1 packagecom.test.a;2
3 importjava.lang.reflect.Constructor;4 importjava.lang.reflect.Field;5 importjava.lang.reflect.InvocationTargetException;6 importjava.lang.reflect.Method;7
8 importjavax.activation.FileDataSource;9
10 public classTest2 {11 public static void main(String args[]) throwsNoSuchMethodException, SecurityException, InstantiationException,12 IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {13 Class> class1 = Person.class;14 Constructor> constructor = class1.getConstructor(int.class, String.class);//區別getConstructors,這里不是Integer.class
15 Person person1 = (Person) constructor.newInstance(23, "Tom");16 System.out.println(person1);17
18 //Method method=class1.getMethod("f");//運行異常,因為該方法不適用于private方法
19 Method method2 = class1.getMethod("g");20 method2.invoke(person1);21 Method method3 = class1.getDeclaredMethod("f");22 method3.setAccessible(true);23 method3.invoke(person1);//必須要setAccessible成true,才可以訪問private方法
24
25 Field field = class1.getDeclaredField("age");26 System.out.println(person1);27 field.setAccessible(true);28 field.set(person1, 34);29 System.out.println(person1);30 }31
32 }
View Code
1 The name is: Tomthe age is 23
2 I am publicfunction3 I am privatefunction4 The name is: Tomthe age is 23
5 The name is: Tomthe age is 34
View Code
note:
反射使用總結:通過運行時期加載class文件。首先由class文件生成對應的構造器,再利用構造器生成具體的實例對象。
然后通過class對象生成Method對象,利用Mehod對象的invoke來調用方法;最后用class對象生成Field對象,利用filed對象
來完成變量的賦值。-------總之就是用class對象來生成一個類的所有對象(反射),完成類的實例化,從而訪問該類的對象的成員和方法
說明:反射可以讓我們創建一個類的實例、在類外部訪問類的私有方法、私有字段。
2.方法的反射
(1)如何獲取某個方法
方法的名稱和方法的參數列表才能唯一決定某個方法
(2)方法反射的操作
method.invoke(對象,參數列表)
note:要獲取一個方法,就是要獲取類的信息,要獲取一個類的信息,就是獲取類類型。
1 packagecom.test.a;2
3 importjava.lang.reflect.InvocationTargetException;4 importjava.lang.reflect.Method;5
6 public classTest {7 public static void main(String args[]) throwsNoSuchMethodException, SecurityException, IllegalAccessException,8 IllegalArgumentException, InvocationTargetException {9 //1.獲取類的信息
10 R a1 = newR();11 Class class1 =a1.getClass();12 //2.獲取方法:名稱和參數列表來決定
13 Method method = class1.getMethod("print", new Class[] { int.class, int.class});14 //也可以寫成Method method2=class1.getMethod("print",15 //int.class,int.class);因為...代表可變參數,可以攜程數組形式,也可以全部寫出來16
17 //3.方法的反射操作18 //note1:方法的反射操作是用method對象來進行方法調用,和a1.print調用的效果相同。(正常情況下是對象操作方法,反射反過來,通過print對象操作a1)19 //note2:如果方法沒有返回值,返回null,有返回值返回具體的返回值
20 Object object = method.invoke(a1, new Object[] { 10, 20});21
22 }23 }24
25 packagecom.test.a;26
27 public classR {28 public void print(int a, intb) {29 System.out.println(a +b);30 }31
32 public voidprint(String a, String b) {33 System.out.println(a.toUpperCase() + "," +b.toLowerCase());34 }35 }
View Code
30
3.Java通過反射了解集合泛型的本質
1 packagecom.test.a;2
3 importjava.lang.reflect.InvocationTargetException;4 importjava.lang.reflect.Method;5 importjava.util.ArrayList;6
7 public classTest {8 public static void main(String args[]) throwsNoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {9 ArrayList list1=newArrayList();10 ArrayList list2=new ArrayList<>();11 list2.add("a");//only string,can't list.add(20);
12 Class class1=list1.getClass();13 Class class2=list2.getClass();14 System.out.println(class1==class2);15 /**
16 * class1==class2的結果為true說明編譯后的集合的泛型是去泛型化的。17 * Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯就無效了。18 * 驗證:我們可以通過方法的反射來操作,繞過編譯19 */
20 Method method=class2.getMethod("add", Object.class);21 method.invoke(list2, 20);//繞過編譯操作就繞過了泛型
22 System.out.println(list2.size());23 System.out.println(list2);24
25 }26 }27
28 true
29 2
30 [a, 20]
View Code
4.反射和傳統RTTI區別
傳統的RTTI與反射最主要的區別,在于RTTI在編譯期需要.class文件,而反射不需要。傳統的RTTI使用轉型或Instance形式實現,但都需要指定要轉型的類型,比如:
public void rtti(Object obj){
A a= A(obj);
// A a= Class.forName("com.rtti.A")
// obj instanceof A
}
注意其中的obj雖然是被轉型了,但在編譯期,就需要知道要轉成的類型A,也就是需要A的.class文件。
相對的,反射完全在運行時在通過Class類來確定類型,不需要提前加載A的.class文件。
相同:
它倆的目的一樣:
在運行時,識別對象和類的信息。
相同點:
目的相同;
功能都是通過Class類來實現的
不同點:
反射與RTTI的本質區別只是檢查一個類的.class文件的時機不同:
反射:.class 文件是在編譯時不可獲得的,所以在運行時打開和檢查未知類的.class文件從而變已知。
RTTI:? .class 文件是在編譯時打開和檢查。
5.反射的缺點
反射機制給予Java開發很大的靈活性,但反射機制本身也有缺點,代表性的缺陷就是反射的性能,一般來說,通過反射調用方法的效率比直接調用的效率要至少慢一倍以上。
6.為什么要引入反射以及在實際開發中反射的作用
參考:https://www.cnblogs.com/Eason-S/p/5851078.html
Spring中的IoC的實現原理就是工廠模式加反射機制。
(1).我們首先看一下不用反射機制時的工廠模式:
1 /**
2 * 工廠模式3 */
4 interfacefruit{5 public abstract voideat();6 }7
8 class Apple implementsfruit{9 public voideat(){10 System.out.println("Apple");11 }12 }13
14 class Orange implementsfruit{15 public voideat(){16 System.out.println("Orange");17 }18 }19 //構造工廠類20 //也就是說以后如果我們在添加其他的實例的時候只需要修改工廠類就行了
21 classFactory{22 public staticfruit getInstance(String fruitName){23 fruit f=null;24 if("Apple".equals(fruitName)){25 f=newApple();26 }27 if("Orange".equals(fruitName)){28 f=newOrange();29 }30 returnf;31 }32 }33
34 classhello{35 public static voidmain(String[] a){36 fruit f=Factory.getInstance("Orange");37 f.eat();38 }39 }
View Code
當我們在添加一個子類的時候,就需要修改工廠類了。如果我們添加太多的子類的時候,改的就會很多。
(2). 利用反射機制的工廠模式:
1 packageReflect;2
3 interfacefruit{4 public abstract voideat();5 }6
7 class Apple implementsfruit{8 public voideat(){9 System.out.println("Apple");10 }11 }12
13 class Orange implementsfruit{14 public voideat(){15 System.out.println("Orange");16 }17 }18
19 classFactory{20 public staticfruit getInstance(String ClassName){21 fruit f=null;22 try{23 f=(fruit)Class.forName(ClassName).newInstance();24 }catch(Exception e) {25 e.printStackTrace();26 }27 returnf;28 }29 }30
31 classhello{32 public static voidmain(String[] a){33 fruit f=Factory.getInstance("Reflect.Apple");34 if(f!=null){35 f.eat();36 }37 }38 }
View Code
現在就算我們添加任意多個子類的時候,工廠類就不需要修改。
使用反射機制的工廠模式可以通過反射取得接口的實例,但是需要傳入完整的包和類名。而且用戶也無法知道一個接口有多少個可以使用的子類,所以我們通過屬性文件的形式配置所需要的子類。
(3).使用反射機制并結合屬性文件的工廠模式(即IoC)
首先創建一個fruit.properties的資源文件:
1 apple=Reflect.Apple
2 orange=Reflect.Orange
然后編寫主類代碼:
1 packageReflect;2
3 import java.io.*;4 import java.util.*;5
6 interfacefruit{7 public abstract voideat();8 }9
10 class Apple implementsfruit{11 public voideat(){12 System.out.println("Apple");13 }14 }15
16 class Orange implementsfruit{17 public voideat(){18 System.out.println("Orange");19 }20 }21 //操作屬性文件類
22 classinit{23 public static Properties getPro() throwsFileNotFoundException, IOException{24 Properties pro=newProperties();25 File f=new File("fruit.properties");26 if(f.exists()){27 pro.load(newFileInputStream(f));28 }else{29 pro.setProperty("apple", "Reflect.Apple");30 pro.setProperty("orange", "Reflect.Orange");31 pro.store(new FileOutputStream(f), "FRUIT CLASS");32 }33 returnpro;34 }35 }36
37 classFactory{38 public staticfruit getInstance(String ClassName){39 fruit f=null;40 try{41 f=(fruit)Class.forName(ClassName).newInstance();42 }catch(Exception e) {43 e.printStackTrace();44 }45 returnf;46 }47 }48
49 classhello{50 public static void main(String[] a) throwsFileNotFoundException, IOException{51 Properties pro=init.getPro();52 fruit f=Factory.getInstance(pro.getProperty("apple"));53 if(f!=null){54 f.eat();55 }56 }57 }58 //【運行結果】:Apple
View Code
四.反射和多態的區別
既然 RTTI是運行時類型檢查,為什么還要嚴格和多態區別呢,用《thinking in java》的原話說就是“Java希望我們始終使用多態機制,只在必須的時候使用RTTI”。那么,RTTI與多態到底有什么區別呢?
是因為:多態的實現是依靠rtti(運行時類型檢查或者叫后期綁定)??
總結
以上是生活随笔為你收集整理的在java中为什么_属性绑定到类型_方法绑定到对象_13. Java基础之类型信息(RTTI和反射)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring boot 加载静态文件
- 下一篇: 微服务集成cas_Spring Boot