Java基础知识13Java反射原理以及基本使用和重写与重载的区别
1.Java反射機(jī)制
JAVA反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語言的反射機(jī)制。
要想解剖一個(gè)類,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是Class類中的方法.所以先要獲取到每一個(gè)字節(jié)碼文件對(duì)應(yīng)的Class類型的對(duì)象.
簡單的來說,反射機(jī)制指的是程序在運(yùn)行時(shí)能夠獲取自身的信息。在Java中,只要給定類的名字, 那么就可以通過反射機(jī)制來獲得類的所有信息。
2.類加載的過程
Class對(duì)象的由來是將class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)Class對(duì)象
3.Java反射的作用與解決的問題
Java中編譯類型有兩種:
靜態(tài)編譯:在編譯時(shí)確定類型,綁定對(duì)象即通過。
動(dòng)態(tài)編譯:運(yùn)行時(shí)確定類型,綁定對(duì)象。動(dòng)態(tài)編譯最大限度地發(fā)揮了Java的靈活性,體現(xiàn)了多態(tài)的應(yīng)用,可以減低類之間的耦合性。
Java反射是Java被視為動(dòng)態(tài)(或準(zhǔn)動(dòng)態(tài))語言的一個(gè)關(guān)鍵性質(zhì)。這個(gè)機(jī)制允許程序在運(yùn)行時(shí)透過Reflection APIs取得任何一個(gè)已知名稱的class的內(nèi)部信息,包括其modifiers(諸如public、static等)、superclass(例如Object)、實(shí)現(xiàn)之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于運(yùn)行時(shí)改變fields內(nèi)容或喚起methods。
Reflection可以在運(yùn)行時(shí)加載、探知、使用編譯期間完全未知的classes。即Java程序可以加載一個(gè)運(yùn)行時(shí)才得知名稱的class,獲取其完整構(gòu)造,并生成其對(duì)象實(shí)體、或?qū)ζ鋐ields設(shè)值、或喚起其methods。
一句話概括就是使用反射可以賦予jvm動(dòng)態(tài)編譯的能力,否則類的元數(shù)據(jù)信息只能用靜態(tài)編譯的方式實(shí)現(xiàn),例如熱加載,Tomcat的classloader等等都沒法支持。
3.1編譯期和運(yùn)行期區(qū)別
編譯期:檢查是否有語法錯(cuò)誤,如果沒有就將其翻譯成字節(jié)碼文件。即.class文件。
運(yùn)行期:java虛擬機(jī)分配內(nèi)存,解釋執(zhí)行字節(jié)碼文件。
3.2Java 重寫(Override)與重載(Overload)
3.2.1 重寫
重寫是子類對(duì)父類的允許訪問的方法的實(shí)現(xiàn)過程進(jìn)行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!
重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實(shí)現(xiàn)父類的方法。
重寫方法不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的異常。例如: 父類的一個(gè)方法申明了一個(gè)檢查異常 IOException,但是在重寫這個(gè)方法的時(shí)候不能拋出 Exception 異常,因?yàn)?Exception 是 IOException 的父類,拋出 IOException 異常或者 IOException 的子類異常。
重寫是父類與子類之間多態(tài)性的表現(xiàn),在運(yùn)行時(shí)起作用(動(dòng)態(tài)多態(tài)性,譬如實(shí)現(xiàn)動(dòng)態(tài)綁定)
方法的重寫是在運(yùn)行時(shí)進(jìn)行的。
在面向?qū)ο笤瓌t里,重寫意味著可以重寫任何現(xiàn)有方法。實(shí)例如下:
class Animal{
public void move(){
System.out.println("動(dòng)物可以移動(dòng)");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 對(duì)象
Animal b = new Dog(); // Dog 對(duì)象
a.move();// 執(zhí)行 Animal 類的方法
b.move();//執(zhí)行 Dog 類的方法
}
}
在上面的例子中可以看到,盡管 b 屬于 Animal 類型,但是它運(yùn)行的是 Dog 類的 move方法。
這是由于在編譯階段,只是檢查參數(shù)的引用類型。
然而在運(yùn)行時(shí),Java 虛擬機(jī)(JVM)指定對(duì)象的類型并且運(yùn)行該對(duì)象的方法。
方法的重寫規(guī)則:
參數(shù)列表與被重寫方法的參數(shù)列表必須完全相同。
返回類型與被重寫方法的返回類型可以不相同,但是必須是父類返回值的派生類(java5 及更早版本返回類型要一樣,java7 及更高版本可以不同)。
訪問權(quán)限不能比父類中被重寫的方法的訪問權(quán)限更低。例如:如果父類的一個(gè)方法被聲明為 public,那么在子類中重寫該方法就不能聲明為 protected。
父類的成員方法只能被它的子類重寫。
聲明為 final 的方法不能被重寫。
構(gòu)造方法不能被重寫。
3.2.2 重載(Overload)
重載(overloading) 是在一個(gè)類里面,方法名字相同,而參數(shù)不同。返回類型可以相同也可以不同。
每個(gè)重載的方法(或者構(gòu)造函數(shù))都必須有一個(gè)獨(dú)一無二的參數(shù)類型列表。
最常用的地方就是構(gòu)造器的重載。
方法重載是在編譯時(shí)執(zhí)行的。
重載規(guī)則:
被重載的方法必須改變參數(shù)列表(參數(shù)列表中的參數(shù)順序、個(gè)數(shù)、類型不一樣);
被重載的方法可以改變返回類型;
被重載的方法可以改變?cè)L問修飾符;
被重載的方法可以聲明新的或更廣的檢查異常;
方法能夠在同一個(gè)類中或者在一個(gè)子類中被重載。
無法以返回值類型作為重載函數(shù)的區(qū)分標(biāo)準(zhǔn)。
實(shí)例:
public class Overloading {
public int test(){
System.out.println("test1");
return 1;
}
public void test(int a){
System.out.println("test2");
}
//以下兩個(gè)參數(shù)類型順序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}
public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}
public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}
3.2.3 重寫與重載之間的區(qū)別
| 區(qū)別點(diǎn) | 重載方法 | 重寫方法 |
|---|---|---|
| 參數(shù)列表 | 必須修改 | 一定不能修改 |
| 返回類型 | 可以修改 | 返回類型與被重寫方法的返回類型可以不相同,但是必須是父類返回值的派生類 |
| 異常 | 可以修改 | 可以減少或刪除,一定不能拋出新的或者更廣的異常 |
| 訪問 | 可以修改 | 一定不能做更嚴(yán)格的限制(可以降低限制) |
方法重載:
1、同一個(gè)類中
2、方法名相同,參數(shù)列表不同(參數(shù)順序、個(gè)數(shù)、類型)
3、方法返回值、訪問修飾符任意
4、與方法的參數(shù)名無關(guān)
方法重寫:
1、有繼承關(guān)系的子類中
2、方法名相同,參數(shù)列表相同(參數(shù)順序、個(gè)數(shù)、類型),方法返回值相同
3、訪問修飾符,訪問范圍需要大于等于父類的訪問范圍
4、與方法的參數(shù)名無關(guān)
4.反射的使用
4.1 獲取Class對(duì)象的三種方式
Object ——> getClass();
任何數(shù)據(jù)類型(包括基本數(shù)據(jù)類型)都有一個(gè)“靜態(tài)”的class屬性
通過Class類的靜態(tài)方法:forName(String className)(常用)
測(cè)試案例:
/**
* @Author lucky
* @Date 2021/12/28 16:09
*/
public class ReflectTest {
public static void main(String[] args) {
//第一種方式獲取Class對(duì)象
Student stu1 = new Student();//這一new 產(chǎn)生一個(gè)Student對(duì)象,一個(gè)Class對(duì)象。
Class stuClass = stu1.getClass();//獲取Class對(duì)象
System.out.println(stuClass.getName());
//第二種方式獲取Class對(duì)象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判斷第一種方式獲取的Class對(duì)象和第二種方式獲取的是否是同一個(gè)
//第三種方式獲取Class對(duì)象
try {
Class stuClass3 = Class.forName("com.ttbank.flep.core.entity.Student");//注意此字符串必須是真實(shí)路徑,就是帶包名的類路徑,包名.類名
System.out.println(stuClass3 == stuClass2);//判斷三種方式是否獲取的是同一個(gè)Class對(duì)象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意:在運(yùn)行期間,一個(gè)類,只有一個(gè)Class對(duì)象產(chǎn)生。
三種方式常用第三種,第一種對(duì)象都有了還要反射干什么。第二種需要導(dǎo)入類的包,依賴太強(qiáng),不導(dǎo)包就拋編譯錯(cuò)誤。一般都第三種,一個(gè)字符串可以傳入也可寫在配置文件中等多種方法。
4.2通過反射獲取構(gòu)造方法并使用
Teacher類:
package com.ttbank.flep.core.entity;
/**
* @Author lucky
* @Date 2021/12/28 16:30
*/
public class Teacher {
//---------------構(gòu)造方法-------------------
//(默認(rèn)的構(gòu)造方法)
Teacher(String str){
System.out.println("(默認(rèn))的構(gòu)造方法 s = " + str);
}
//無參構(gòu)造方法
public Teacher(){
System.out.println("調(diào)用了公有、無參構(gòu)造方法執(zhí)行了。。。");
}
//有一個(gè)參數(shù)的構(gòu)造方法
public Teacher(char name){
System.out.println("姓名:" + name);
}
//有多個(gè)參數(shù)的構(gòu)造方法
public Teacher(String name ,int age){
System.out.println("姓名:"+name+"年齡:"+ age);//這的執(zhí)行效率有問題,以后解決。
}
//受保護(hù)的構(gòu)造方法
protected Teacher(boolean n){
System.out.println("受保護(hù)的構(gòu)造方法 n = " + n);
}
//私有構(gòu)造方法
private Teacher(int age) {
System.out.println("私有的構(gòu)造方法 年齡:" + age);
}
public void sayHello(){
System.out.println("hello");
}
}
典型應(yīng)用代碼:
public static void main(String[] args) {
try {
//01 獲取Class對(duì)象
Class<?> teacherClass = Class.forName("com.ttbank.flep.core.entity.Teacher");
//02 獲取構(gòu)造方法
Constructor<?> constructor = teacherClass.getConstructor(String.class,int.class);
//03 利用構(gòu)造方法創(chuàng)建聲明類Teacher的新實(shí)例
Teacher teacher = (Teacher) constructor.newInstance("lucky", 25);
teacher.sayHello();
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
控制臺(tái)輸出:
測(cè)試代碼:
package com.ttbank.flep.core.test;
import java.lang.reflect.Constructor;
/**
* @Author lucky
* @Date 2021/12/28 16:35
*/
public class ConstructorTest {
public static void main(String[] args) throws Exception {
//1.加載Class對(duì)象
Class clazz = Class.forName("com.ttbank.flep.core.entity.Teacher");
//2.獲取所有公有構(gòu)造方法
System.out.println("**********************所有公有構(gòu)造方法*********************************");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("************所有的構(gòu)造方法(包括:私有、受保護(hù)、默認(rèn)、公有)***************");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("*****************獲取公有、無參的構(gòu)造方法*******************************");
Constructor con = clazz.getConstructor(null);
//1>、因?yàn)槭菬o參的構(gòu)造方法所以類型是一個(gè)null,不寫也可以:這里需要的是一個(gè)參數(shù)的類型,切記是類型
//2>、返回的是描述這個(gè)無參構(gòu)造函數(shù)的類對(duì)象。
System.out.println("con = " + con);
//調(diào)用構(gòu)造方法
Object obj = con.newInstance();
// System.out.println("obj = " + obj);
// Student stu = (Student)obj;
System.out.println("******************獲取私有構(gòu)造方法,并調(diào)用*******************************");
con = clazz.getDeclaredConstructor(char.class);
System.out.println(con);
//調(diào)用構(gòu)造方法
con.setAccessible(true);//暴力訪問(忽略掉訪問修飾符)
obj = con.newInstance('男');
}
}
控制臺(tái)輸出:
**********************所有公有構(gòu)造方法********************************* public com.ttbank.flep.core.entity.Teacher(java.lang.String,int) public com.ttbank.flep.core.entity.Teacher(char) public com.ttbank.flep.core.entity.Teacher() ************所有的構(gòu)造方法(包括:私有、受保護(hù)、默認(rèn)、公有)*************** private com.ttbank.flep.core.entity.Teacher(int) protected com.ttbank.flep.core.entity.Teacher(boolean) public com.ttbank.flep.core.entity.Teacher(java.lang.String,int) public com.ttbank.flep.core.entity.Teacher(char) public com.ttbank.flep.core.entity.Teacher() com.ttbank.flep.core.entity.Teacher(java.lang.String) *****************獲取公有、無參的構(gòu)造方法******************************* con = public com.ttbank.flep.core.entity.Teacher() 調(diào)用了公有、無參構(gòu)造方法執(zhí)行了。。。 ******************獲取私有構(gòu)造方法,并調(diào)用******************************* public com.ttbank.flep.core.entity.Teacher(char) 姓名:男
調(diào)用方法:
<1>獲取構(gòu)造方法:
1).批量的方法:
public Constructor[] getConstructors() //所有"公有的"構(gòu)造方法 public Constructor[] getDeclaredConstructors()//獲取所有的構(gòu)造方法(包括私有、受保護(hù)、默認(rèn)、公有)
2).獲取單個(gè)的方法,并調(diào)用:
public Constructor getConstructor(Class... parameterTypes) //獲取單個(gè)的"公有的"構(gòu)造方法: public Constructor getDeclaredConstructor(Class... parameterTypes) //獲取"某個(gè)構(gòu)造方法"可以是私有的,或受保護(hù)、默認(rèn)、公有;
<2>newInstance是 Constructor類的方法(管理構(gòu)造函數(shù)的類)
api的解釋為:
newInstance(Object... initargs)
使用此 Constructor 對(duì)象表示的構(gòu)造方法來創(chuàng)建該構(gòu)造方法的聲明類的新實(shí)例,并用指定的初始化參數(shù)初始化該實(shí)例。
它的返回值是T類型,所以newInstance是創(chuàng)建了一個(gè)構(gòu)造方法的聲明類的新實(shí)例對(duì)象。并為之調(diào)用
5.Java中new和反射創(chuàng)建對(duì)象
5.1 Java中new創(chuàng)建對(duì)象
如果我們?cè)诖a中如果寫了一段
A a = new A();
在JVM中會(huì)幫你做的事情有以下:
JVM把類的.java文件編譯為一個(gè).class的字節(jié)碼文件
類加載器把.class文件加載進(jìn)jvm的內(nèi)存中,一個(gè)Class對(duì)象生成,并放入方法區(qū)中,這Class對(duì)象對(duì)于任何類都是唯一一個(gè)。
做完這些之后,才是new字段的工作:
判斷內(nèi)存中是否已經(jīng)有那個(gè)唯一的Class對(duì)象
如果沒有,則進(jìn)行上述操作。
如果有,則在內(nèi)存中申請(qǐng)空間開辟,即根據(jù)Class對(duì)象獲取有關(guān)的構(gòu)造器進(jìn)行實(shí)例化,在這里我們假設(shè)是一個(gè)無參數(shù)構(gòu)造,那么只需要newInstance()。
5.2 Java中使用反射創(chuàng)建對(duì)象
這次的代碼是我們最常見的反射代碼
Class c = Class.forName("A的全類名");
當(dāng)JVM編譯到這段代碼的時(shí)候,他的步驟是:
1、使用類加載,將對(duì)應(yīng).class加載入內(nèi)存的方法區(qū)中,并返回Class對(duì)象。
這時(shí)候,我們可以查看這個(gè)類對(duì)象里面的構(gòu)造器方法,并使用無參數(shù)構(gòu)造器進(jìn)行構(gòu)造實(shí)例,即如下代碼
Constructor constructor = c.getConstructor(); Object obj = constructor.newInstance();
用同樣的圖,我們可以畫出來。
到這里,我們幾乎可以知道無論是反射,還是New,其實(shí)都是通過類加載器對(duì).class文件加載進(jìn)內(nèi)存中,創(chuàng)建了Class對(duì)象。‘’
Java中反射屬于動(dòng)態(tài)編譯,而new屬于靜態(tài)編譯。
粗俗解釋:
1、靜態(tài)編譯相當(dāng)于把所有需要的東西都在初始化的時(shí)候加載了,如果程序一大,就很有可能會(huì)跑得慢。
2、動(dòng)態(tài)編譯,在編譯的時(shí)候,需要的模塊都沒有編譯進(jìn)去,啟動(dòng)程序的時(shí)候,模塊不會(huì)被加載而是在運(yùn)行的時(shí)候,需要哪個(gè)模塊就調(diào)用哪個(gè)模塊。
上面的過程告訴我們,我們?nèi)绻胣ew,那么我們要?jiǎng)?chuàng)建的類都是已經(jīng)“寫死”在.class文件里面了,我們無法控制JVM幫我們加載類的這一項(xiàng)工作。
但是如果我們用反射創(chuàng)建類對(duì)象,我們是相當(dāng)于親自“指導(dǎo)”JVM,我們“按需加載”.class文件,如果內(nèi)存里面沒有這個(gè)類的.class文件,那么我們用Class.forName()去叫類加載器幫忙加載就行了,而不是把程序停下來,再打一段代碼,再讓類加載器進(jìn)行加載,從而體現(xiàn)出了Java的“動(dòng)態(tài)性”。
因?yàn)镾pring在加載類的實(shí)例時(shí),我們知道其實(shí)是用工廠的方式,給出一個(gè)個(gè)實(shí)例,而在工廠里面,用了單例,但是真正實(shí)例化,則是反射的newInstance來創(chuàng)建對(duì)象,而不是new。
那么,為什么是反射的newInstance,而不是new呢?
那么首先我們必須明白,Java里面,反射的目的是什么?
高內(nèi)聚,低耦合。
進(jìn)一步,反射比new的好處是什么?
反射不用知道類名,可以直接實(shí)例化類,也就是不用硬編碼。
有人問了,不知道類名那怎么反射呢?
例子:
通過new,我們這么寫:
A a = New A();
通過反射,我們這么寫:
Class c = Class.forName(“A”); factory = (AInterface)c.newInstance();
其中AInterface是類A的接口,如果下面這樣寫,你可能會(huì)理解:
String className = “A”; Class c = Class.forName(className); factory = (AInterface)c.newInstance();
進(jìn)一步,如果下面寫,你可能會(huì)理解:
String className = readfromXMlConfig;//從xml 配置文件中獲得字符串 Class c = Class.forName(className); factory = (AInterface)c.newInstance();
上面代碼就消滅了A類名稱,優(yōu)點(diǎn):無論A類怎么變化,上述代碼不變,甚至可以更換A的兄弟類B , C , D….等,只要他們繼承Ainterface就可以。
參考文獻(xiàn):https://blog.csdn.net/qq_26558047/article/details/109745018
https://blog.csdn.net/weixin_34672875/article/details/115074666
https://blog.csdn.net/sinat_38259539/article/details/71799078----經(jīng)典
總結(jié)
以上是生活随笔為你收集整理的Java基础知识13Java反射原理以及基本使用和重写与重载的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sublime text 2中Emmet
- 下一篇: 有向图与关联矩阵