文章目錄
- 一、反射機制
-
- 1.1 基本概念
- 1.2 擷取位元組碼檔案
- 1.3 路徑問題
- 1.4 資源綁定器
- 1.5 Field相關操作
-
- 1.5.1 擷取Filed
- 1.5.2 反編譯Field
- 1.5.3 通過反射機制通路對象屬性(重點)
- 1.6 可變長參數
- 1.6 Method相關操作
-
- 1.6.1 反射Method
- 1.6.2 反編譯Method
- 1.6.3 通過反射機制調用一個對象的方法(重點)
- 1.7 Constructor相關操作
-
- 1.7.1 反編譯Constructor
- 1.7.2 反射機制調用構造方法
- 1.8 擷取父類和父接口
- 1.9 類加載器
- 二、注解
-
- 2.1 基礎
- 2.2 注解中的屬性
- 2.3 反射注解
- 2.4 注解在開發中的作用
一、反射機制
1.1 基本概念
1 通過反射機制可以操作位元組碼檔案(.class檔案),雖然代碼變複雜了,但是很靈活。
2 包:java.lang.reflect.*
- 重要類
1 java.lang.Class:代表整個位元組碼。
2 java.lang.reflect.Method:代表位元組碼中的方法位元組碼。
3 java.lang.reflect.Constructor:代表位元組碼中的構造方法位元組碼。
4 java.lang.reflect.Field:代表位元組碼中的屬性位元組碼(靜态變量+執行個體變量)。
1.2 擷取位元組碼檔案
- 三種方法
1 Class c = Class.forName(“完整類名帶包名”);
該方法是一個靜态方法,參數是一個字元串,這個字元串要是一個完整類名。
2 Class c = 對象.getClass();
java中每一個對象都有一個getClass()方法。
3 Class c = 任何類型.class;
java語言中任何一種類型,包括基本資料類型,它都有.class屬性。
- 擷取位元組碼的用處
1 擷取到位元組碼後,可以通過Class的newInstance()方法來執行個體化對象。這樣就可以做到在不改變代碼的情況下,做到不同對象的執行個體化。
2 一些進階架構比如ssh,ssm的底層實作原理就是反射機制。
3 注意:newInstance()方法内部實際上調用了無參數構造方法,必須保證無參構造存在才可以。
- 隻執行靜态代碼塊
1 如果你隻是希望一個類的靜态代碼塊執行,其它代碼一律不執行,你可以使用:Class.forName(“完整類名”);
2 這個方法的執行會導緻類加載,類加載時,靜态代碼塊執行。
3 後面JDBC技術的時候我們會使用到這個。
- 示例
1 以下代碼是靈活的,代碼不需要改動,可以修改配置檔案,配置檔案修改之後,可以建立出不同的執行個體對象。
// 建立classinfo.properties配置檔案。之後想要執行個體化其他對象,隻需修改此檔案即可。
className=java.lang.String
import java.io.FileReader;
import java.util.Properties;
public class ReflectTest02 {
public static void main(String[] args) throws Exception {
// 通過IO流讀取classinfo.properties檔案
FileReader reader = new FileReader("chapter05/src/classinfo.properties");
// 建立屬性類對象Map
Properties pro = new Properties();
// 加載
pro.load(reader);
// 關閉流
reader.close();
// 通過key擷取value
String className = pro.getProperty("className");
// 通過反射機制執行個體化(建立)對象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
1.3 路徑問題
1 對于路徑會存在這樣一個問題,把程式換到另一個地方進行運作時,有時候路徑就無效了。
2 有一種通用的方式,但是這種方式需要檔案再類路徑下,類路徑就是src檔案。
- 示例
1 Thread.currentThread() 目前線程對象
2 getContextClassLoader() 是線程對象的方法,可以擷取到目前線程的類加載器對象。
3 getResource() 【擷取資源】這是類加載器對象的方法,目前線程的類加載器預設從類的根路徑下加載資源。
4 Thread.currentThread().getContextClassLoader().getResource(“classinfo.properties”) 直接以流(InputStream)的形式傳回。
//建立一個classinfo.properties檔案在src下。
public class AboutPath {
public static void main(String[] args) {
// 擷取絕對路徑,該檔案需要在src檔案下。
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
System.out.println(path);
}
}
1.4 資源綁定器
1 java.util包下提供了一個資源綁定器,便于擷取屬性配置檔案中的内容。
2 資源綁定器,隻能綁定xxx.properties檔案。并且這個檔案必須在類路徑下。檔案擴充名也必須是properties,并且在寫路徑的時候,路徑後面的擴充名不能寫。
- 示例
import java.util.ResourceBundle;
public class ResourceBoundleTest {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className");
System.out.println(className);
}
}
1.5 Field相關操作
隻要看懂重點即可。
1.5.1 擷取Filed
Class studentClass = Class.forName(“com.malidong.reflect.Student”);
1擷取簡單類名
String simpleName = studentClass.getSimpleName();
2 擷取完整類名
String className = studentClass.getName();
3 擷取類中所有的public修飾的Field
Field[] fields = studentClass.getFields();
4 擷取所有的Field
Field[] fs = studentClass.getDeclaredFields();
5 擷取屬性的修飾符清單
int i = field.getModifiers(); // 傳回的修飾符是一個數字,每個數字是修飾符的代号!!!
6 可以将這個“代号”數字轉換成“字元串”嗎?
String modifierString = Modifier.toString(i);
7 擷取屬性的類型
Class fieldType = field.getType();
String fName = fieldType.getName();
String fName = fieldType.getSimpleName();
field.getName()
1.5.2 反編譯Field
通過反射機制,反編譯一個類的屬性Field。就是把類的屬性全部列印出來。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest05 {
public static void main(String[] args) throws Exception{
// 建立這個是為了拼接字元串。
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("java.lang.Thread");
s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
Field[] fields = studentClass.getDeclaredFields();
for(Field field : fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
1.5.3 通過反射機制通路對象屬性(重點)
給屬性指派set,擷取屬性的值get。
将架構采用,很少使用。
import java.lang.reflect.Field;
public class ReflectTest06 {
public static void main(String[] args) throws Exception{
// 我們不使用反射機制,怎麼去通路一個對象的屬性呢?
Student s = new Student();
// 給屬性指派
s.no = 1111;
// 讀屬性值
System.out.println(s.no);
// 使用反射機制,怎麼去通路一個對象的屬性。(set get)
Class studentClass = Class.forName("com.malidong.reflect.Student");
Object obj = studentClass.newInstance(); // obj就是Student對象。(底層調用無參數構造方法)
Field noFiled = studentClass.getDeclaredField("no"); // 擷取no屬性(根據屬性的名稱來擷取Field)
noFiled.set(obj, 22222); // 給obj對象的no屬性指派2222
// 讀取屬性的值
System.out.println(noFiled.get(obj));
// 可以通路私有的屬性嗎?
Field nameField = studentClass.getDeclaredField("name");
// 打破封裝(反射機制的缺點:打破封裝,可能會給不法分子留下機會!!!)
// 這樣設定完之後,在外部也是可以通路private的。
nameField.setAccessible(true);
// 給name屬性指派
nameField.set(obj, "jackson");
// 擷取name屬性的值
System.out.println(nameField.get(obj));
}
}
1.6 可變長參數
1 可變長度參數要求的參數個數是:0~N個。
2 可變長度參數在參數清單中必須在最後一個位置上,而且可變長度參數隻能有1個。
3 可變長度參數可以當做一個數組來看待。
- 示例
m();
m(10);
m(10, 20);
public static void m(int... args){
System.out.println("m方法執行了!");
}
1.6 Method相關操作
隻要看懂重點即可。
1.6.1 反射Method
1 擷取類了
Class userServiceClass = Class.forName(“com.malidong.reflect.UserService”);
2 擷取所有的Method(包括私有的!)
Method[] methods = userServiceClass.getDeclaredMethods();
3 擷取修飾符清單
Modifier.toString(method.getModifiers())
4 擷取方法的傳回值類型
method.getReturnType().getSimpleName()
5 擷取方法名
method.getName()
6 方法的修飾符清單(一個方法的參數可能會有多個。)
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
System.out.println(parameterType.getSimpleName());
}
1.6.2 反編譯Method
隻能傳回方法名,不能傳回方法裡的内容。反編譯就是利用編譯把方法打出來。
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest08 {
public static void main(String[] args) throws Exception{
StringBuilder s = new StringBuilder();
Class userServiceClass = Class.forName("java.lang.String");
s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");
Method[] methods = userServiceClass.getDeclaredMethods();
for(Method method : methods){
s.append("\t");
s.append(Modifier.toString(method.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");
// 參數清單
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
// 删除指定下标位置上的字元
s.deleteCharAt(s.length() - 1);
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
1.6.3 通過反射機制調用一個對象的方法(重點)
反射機制,讓代碼很具有通用性,可變化的内容都是寫到配置檔案當中,将來修改配置檔案之後,建立的對象不一樣了,調用的方法也不同了,但是java代碼不需要做任何改動。這就是反射機制的魅力。
import java.lang.reflect.Method;
public class ReflectTest09 {
public static void main(String[] args) throws Exception{
// 不使用反射機制,怎麼調用方法
// 建立對象
UserService userService = new UserService();
// 調用方法
boolean loginSuccess = userService.login("admin","123");
System.out.println(loginSuccess ? "登入成功" : "登入失敗");
// 使用反射機制來調用一個對象的方法該怎麼做?
Class userServiceClass = Class.forName("com.malidong.reflect.UserService");
// 建立對象
Object obj = userServiceClass.newInstance();
// 擷取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
/*
四要素:
loginMethod方法
obj對象
"admin","123" 實參
retValue 傳回值
*/
Object retValue = loginMethod.invoke(obj, "admin","123123");
System.out.println(retValue);
}
}
1.7 Constructor相關操作
隻要看懂重點即可。
1.7.1 反編譯Constructor
反編譯一個類的Constructor構造方法。操作class檔案,把所有的構造方法列印出來。
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ReflectTest10 {
public static void main(String[] args) throws Exception{
StringBuilder s = new StringBuilder();
Class vipClass = Class.forName("java.lang.String");
s.append(Modifier.toString(vipClass.getModifiers()));
s.append(" class ");
s.append(vipClass.getSimpleName());
s.append("{\n");
// 拼接構造方法
Constructor[] constructors = vipClass.getDeclaredConstructors();
for(Constructor constructor : constructors){
//public Vip(int no, String name, String birth, boolean sex) {
s.append("\t");
s.append(Modifier.toString(constructor.getModifiers()));
s.append(" ");
s.append(vipClass.getSimpleName());
s.append("(");
// 拼接參數
Class[] parameterTypes = constructor.getParameterTypes();
for(Class parameterType : parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
// 删除最後下标位置上的字元
if(parameterTypes.length > 0){
s.deleteCharAt(s.length() - 1);
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
1.7.2 反射機制調用構造方法
通過反射機制調用構造方法執行個體化java對象。
import java.lang.reflect.Constructor;
public class ReflectTest11 {
public static void main(String[] args) throws Exception{
// 不使用反射機制怎麼建立對象
Vip v1 = new Vip();
Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);
// 使用反射機制怎麼建立對象呢?
Class c = Class.forName("com.malidong.reflect.Vip");
// 調用無參數構造方法
Object obj = c.newInstance();
System.out.println(obj);
// 調用有參數的構造方法怎麼辦?
// 第一步:先擷取到這個有參數的構造方法
Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
// 第二步:調用構造方法new對象
Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
System.out.println(newObj);
// 擷取無參數構造方法
Constructor con2 = c.getDeclaredConstructor();
Object newObj2 = con2.newInstance();
System.out.println(newObj2);
}
}
1.8 擷取父類和父接口
public class ReflectTest12 {
public static void main(String[] args) throws Exception{
// String舉例
Class stringClass = Class.forName("java.lang.String");
// 擷取String的父類
Class superClass = stringClass.getSuperclass();
System.out.println(superClass.getName());
// 擷取String類實作的所有接口(一個類可以實作多個接口。)
Class[] interfaces = stringClass.getInterfaces();
for(Class in : interfaces){
System.out.println(in.getName());
}
}
}
1.9 類加載器
專門負責加載類的指令/工具。
JDK自帶有3個類加載器
1啟動類加載器:rt.jar
2 擴充類加載器:ext/*.jar
3 應用類加載器:classpath
假設有這樣一段代碼:String s = “abc”;
1 首先通過“啟動類加載器”加載。注意:啟動類加載器專門加載:C:\ProgramFiles\Java\jdk1.8.0_101\jre\lib\rt.jar。rt.jar中都是JDK最核心的類庫。
2 如果通過“啟動類加載器”加載不到的時候,會通過"擴充類加載器"加載。注意:擴充類加載器專門加載:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar。
3 如果“擴充類加載器”沒有加載到,那麼會通過“應用類加載器”加載。注意:應用類加載器專門加載:classpath中的類。
- 雙親委派機制
1 java中為了保證類加載的安全,使用了雙親委派機制。
2 優先從啟動類加載器中加載,這個稱為“父”,“父”無法加載到,再從擴充類加載器中加載,這個稱為“母”。雙親委派。如果都加載不到,才會考慮從應用類加載器中加載。直到加載到為止。
二、注解
2.1 基礎
[修飾符清單] @interface 注解類型名{
}
1 注解又叫做注釋,是一種引用資料類型,編譯後也是生成.class檔案。
2 注解使用時的文法格式是:@注解類型名
3 注解可以出現在類上,屬性上,方法上,變量上,等等。注解還可以出現在注解類型上。
- jdk内置注解
java.lang包下一共有三個注解類型:Deprecated,Override,SuppressWarnings。重點掌握前兩個。
Override:
1 隻能注解方法,是給編譯器參考的,和運作階段沒有關系。
2 凡是java中的方法帶有這個注解的,編譯器都會進行編譯檢查,如果這個方法不是重寫父類的方法,編譯器報錯,就是@override下有紅曲線。
Deprecated:
1 用 @Deprecated 注釋的程式元素,不鼓勵程式員使用這樣的元素,通常是因為它很危險或存在更好的選擇。
2 表示注解的東西已過時。調用的話,會直接被橫線劃掉。
- 元注解
1 元注解是用來标注“注解類型”的“注解”。常用的有Target,Retention。
Target:
1 用來标注“被标注的注解”可以出現在哪些位置上。
2 @Target(ElementType.METHOD):表示“被标注的注解”隻能出現在方法上。
3 @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
Retention:
1 用來标注“被标注的注解”最終儲存在哪裡。
2 Retention(RetentionPolicy.SOURCE):表示該注解隻被保留在java源檔案中。
3 @Retention(RetentionPolicy.CLASS):表示該注解被儲存在class檔案中。
4 @Retention(RetentionPolicy.RUNTIME):表示該注解被儲存在class檔案中,并且可以被反射機制所讀取。
2.2 注解中的屬性
1 如果一個注解當中有屬性,那麼必須給屬性指派。(除非該屬性使用default指定了預設值。)
2 如果屬性名是value,并且隻有一個屬性,名字可以省略。@MyAnnotation(“zhangsan”)
3 屬性的類型可以是: byte short int long float double boolean char String Class 枚舉類型以及以上每一種的數組形式。
public @interface MyAnnotation {
String name();
/*
年齡屬性
*/
int age() default 25; //屬性指定預設值
}
public class MyAnnotationTest {
//@MyAnnotation(屬性名=屬性值)
@MyAnnotation(name = "zhangsan")
public void doSome(){
}
}
2.3 反射注解
讀取注解的class檔案,如果能被反射,則需要是@Retention(RetentionPolicy.RUNTIME):
public class ReflectAnnotationTest {
public static void main(String[] args) throws Exception{
// 擷取這個類
Class c = Class.forName("com.bjpowernode.java.annotation5.MyAnnotationTest");
// 判斷類上面是否有@MyAnnotation
//System.out.println(c.isAnnotationPresent(MyAnnotation.class)); // true
if(c.isAnnotationPresent(MyAnnotation.class)){
// 擷取該注解對象
MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
//System.out.println("類上面的注解對象" + myAnnotation); // @com.bjpowernode.java.annotation5.MyAnnotation()
// 擷取注解對象的屬性怎麼辦?和調接口沒差別。
String value = myAnnotation.value();
System.out.println(value);
}
// 判斷String類上面是否存在這個注解
Class stringClass = Class.forName("java.lang.String");
System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class)); // false
}
}
- 通過反射擷取方法上的注解資訊
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
/*
username屬性
*/
String username();
/*
password屬性
*/
String password();
}
import java.lang.reflect.Method;
public class MyAnnotationTest {
@MyAnnotation(username = "admin", password = "456456")
public void doSome(){
}
public static void main(String[] args) throws Exception{
// 擷取MyAnnotationTest的doSome()方法上面的注解資訊。
Class c = Class.forName("com.bjpowernode.java.annotation6.MyAnnotationTest");
// 擷取doSome()方法
Method doSomeMethod = c.getDeclaredMethod("doSome");
// 判斷該方法上是否存在這個注解
if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.username());
System.out.println(myAnnotation.password());
}
}
}
2.4 注解在開發中的作用
1 假設有這樣一個注解,叫做:@Id
2 這個注解隻能出現在類上面,當這個類上有這個注解的時候,要求這個類中必須有一個int類型的id屬性。如果沒有這個屬性就報異常。如果有這個屬性則正常執行!
/*
自定義異常
*/
public class HasNotIdPropertyException extends RuntimeException {
public HasNotIdPropertyException(){
}
public HasNotIdPropertyException(String s){
super(s);
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表示這個注解隻能出現在類上面
@Target(ElementType.TYPE)
// 該注解可以被反射機制讀取到
@Retention(RetentionPolicy.RUNTIME)
public @interface MustHasIdPropertyAnnotation {
}
// 這個注解@Id用來标注類,被标注的類中必須有一個int類型的id屬性,沒有就報異常。
@MustHasIdPropertyAnnotation
public class User {
int id;
String name;
String password;
}
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception{
// 擷取類
Class userClass = Class.forName("com.bjpowernode.java.annotation7.User");
// 判斷類上是否存在Id注解
if(userClass.isAnnotationPresent(MustHasIdPropertyAnnotation.class)){
// 當一個類上面有@MustHasIdPropertyAnnotation注解的時候,要求類中必須存在int類型的id屬性
// 如果沒有int類型的id屬性則報異常。
// 擷取類的屬性
Field[] fields = userClass.getDeclaredFields();
boolean isOk = false; // 給一個預設的标記
for(Field field : fields){
if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
// 表示這個類是合法的類。有@Id注解,則這個類中必須有int類型的id
isOk = true; // 表示合法
break;
}
}
// 判斷是否合法
if(!isOk){
throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的類中必須要有一個int類型的id屬性!");
}
}
}
}