27.01_反射(類的加載概述和加載時機)
- 當程式要使用某個類時,如果該類還未被加載到記憶體中,則系統會通過加載、連接配接、初始化三步來實作對這個類進行初始化。
- 加載
- 就是指将class檔案讀入記憶體,并為之建立一個Class對象。任何類被使用時系統都會建立一個Class對象。
- 連接配接
- 驗證 是否有正确的内部結構,并和其他類協調一緻
- 準備 負責為類的靜态成員配置設定記憶體,并設定預設初始化值
- 解析 将類的二進制資料中的符号引用替換為直接引用
- 初始化
- 建立類的執行個體
- 通路類的靜态變量,或者為靜态變量指派
- 調用類的靜态方法
- 使用反射方式來強制建立某個類或接口對應的java.lang.Class對象
- 初始化某個類的子類
- 直接使用java.exe指令來運作某個主類
27.02_反射類加載器的概述和分類)
- 負責将.class檔案加載到記憶體中,并為之生成對應的Class對象。雖然我們不需要關心類加載機制,但是了解這個機制我們就能更好的了解程式的運作。
- BootStrap ClassLoader 根類加載器
- Extension ClassLoader 擴充類加載器
- System ClassLoader 系統類加載器
- BootStrap ClassLoader 根類加載器
- 也被稱為引導類加載器,負責java核心類的加載
- 比如System,String等,在JDK中JRE的lib目錄下rt.jar檔案中
- Extension ClassLoader 擴充類加載器
- 負責JRE的擴充目錄中jar包的加載
- 在JDK中JRE的lib目錄下ext目錄
- System ClassLoader 系統類加載器
- 負責在JVM啟動時加載來自java指令的class檔案,以及classpath環境變量所指定的jar包和類路徑
27.03_反射(反射概述)
- JAVA反射機制是在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;
- 對于任意一個對象,都能夠調用它的任意一個方法和屬性;
- 這種動态擷取的資訊以及動态調用對象的方法的功能稱為java語言的反射機制。
- 想要解剖一個類,必須先要擷取到該類的位元組碼檔案對象。
- 而解剖使用的就是Class類中的方法,是以先要擷取到每一個位元組碼檔案對應的Class類型的對象。
- a:Object類的getClass方法,判斷兩個對象是否是同一個位元組碼檔案
- b:靜态屬性class,鎖對象
- Class類中靜态方法forName(),讀取配置檔案
public class Test {
public static void main(String[] args) throws ClassNotFoundException{
Class clazz1 = Class.forName("Test.Person");
Class clazz2 = Person.class;
Person p = new Person();
Class clazz3 = p.getClass();
System.out.println(clazz1 == clazz2); //true
System.out.println(clazz1 == clazz3); //true
System.out.println(clazz2 == clazz3); //true
}
}
27.04_反射(class.forName()讀取配置檔案舉例)
- 榨汁機(Juicer)榨汁的案例
- 分别有水果(Fruit)蘋果(Apple)香蕉(Banana)桔子(Orange)榨汁(squeeze)
package Test;
import java.io.BufferedReader;
import java.io.FileReader;
public class Test {
public static void main(String[] args) throws Exception{
//沒有用到反射,用到多态
//Juicer j = new Juicer(); // 購買榨汁機
//j.run(new Orange());
//j.run(new Apple());
//用反射和配置檔案
BufferedReader br = new BufferedReader(new FileReader("config.properties"));
Class clazz = Class.forName(br.readLine());
Fruit f = (Fruit)clazz.newInstance(); //相當于父類引用指向子類對象,水果引用指向蘋果對象
Juicer j = new Juicer();
j.run(f);
//要建立不同的對象隻需要修改配置檔案config.properties中的類名即可
}
}
interface Fruit {
public void squeeze();
}
class Apple implements Fruit{
public void squeeze() {
System.out.println("榨出一杯蘋果汁");
}
}
class Orange implements Fruit{
public void squeeze() {
System.out.println("榨出一杯桔子汁");
}
}
class Juicer {
public void run(Fruit f) {
f.squeeze();
}
}
27.05_反射(通過反射擷取帶參構造方法并使用)
- Class類的newInstance()方法是使用該類無參的構造函數建立對象,如果一個類沒有無參的構造函數,就不能這樣建立了,可以調用Class類的getConstructor(String.class,int.class)方法擷取一個指定的構造函數然後再調用Constructor類的newInstance(“張三”,20)方法建立對象
package Test;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("Test.Person");
//Person p = (Person)clazz.newInstance(); //通過無參構造建立對象
//Person類沒有空參構造時報錯,因為newInstance需要有無參的構造方法
//System.out.println(p);
Constructor c = clazz.getConstructor(String.class,int.class);
//擷取有參構造
Person p = (Person)c.newInstance("張三",23);
//通過有參構造建立對象
System.out.println(p);//name=張三,age=23
}
}
27.06_反射(通過反射擷取成員變量并使用)
- Class.getField(String)方法可以擷取類中的指定字段(可見的),如果是私有的可以用getDeclaedField(“name”)方法擷取,通過set(obj,“李四”)方法可以設定指定對象上該字段的值,如果是私有的需要先調用setAccessible(true)設定通路權限,用擷取的指定的字段調用get(obj)可以擷取指定對象中該字段的值
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("Test.Person");
//Person p = (Person)clazz.newInstance(); //通過無參構造建立對象
//Person類沒有空參構造時報錯,因為newInstance需要有無參的構造方法
//System.out.println(p);
Constructor c = clazz.getConstructor(String.class,int.class);
//擷取有參構造
Person p = (Person)c.newInstance("張三",23);
//通過有參構造建立對象
//System.out.println(p);//name=張三,age=23
//Field f = clazz.getDeclaredField("name");//拿到name字段
//f.set(p,"李四"); //把對象p的name字段改成李四
Field f = clazz.getDeclaredField("name");//暴力反射擷取字段
f.setAccessible(true); //去除私有權限
f.set(p,"李四");
System.out.println(p);
}
}
27.07_反射(通過反射擷取方法并使用)
- Class.getMethod(String,Class…)和Class.getDeclaredMethod(String,Class…)方法可以擷取類中的指定方法,調用invoke(Object,Object…)可以調用該方法,Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10)
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("Test.Person");
Constructor c = clazz.getConstructor(String.class,int.class);//擷取有參構造
Person p = (Person) c.newInstance("張三",23);//通過有參構造建立對象
Method m = clazz.getMethod("eat");//擷取eat方法 無參
m.invoke(p);
Method m2 = clazz.getMethod("eat",int.class);//擷取eat方法 有參
m2.invoke(p,10);
}
}
27.08_反射(通過反射越過泛型檢查)
- ArrayList的一個對象,在這個集合中添加一個字元串資料,如何實作呢?
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws Exception{
//泛型隻在編譯期有效,在運作期會被擦除掉
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
Class clazz = Class.forName("java.util.ArrayList"); //位元組碼檔案是在運作期
//擷取位元組碼對象
Method m = clazz.getMethod("add",Object.class); //擷取add方法
m.invoke(list,"abc");
System.out.println(list); //[111, 222, abc] 泛型反射
}
}
27.09_反射(通過反射寫一個通用的設定某個對象的某個屬性為指定的值)
- public void setProperty(Object obj, String propertyName, Object value){},此方法可将obj對象中名為propertyName的屬性的值設為value。
package Test;
import java.lang.reflect.Field;
public class Tool {
public void setProperty(Object obj, String propertyName, Object value) throws Exception{
Class clazz = obj.getClass(); //擷取位元組碼對象
Field f = clazz.getDeclaredField(propertyName); //暴力反射擷取字段(屬性)
f.setAccessible(true); //去除權限
f.set(obj, value);
}
}
package Test;
public class Test {
public static void main(String[] args) throws Exception{
Student s = new Student("張三",23);
System.out.println(s);
Tool t = new Tool();
t.setProperty(s,"name","李四");
System.out.println(s);
}
}
27.10_反射(練習)
package Test;
public class DemoClass {
public void run() {
system.out.println("Welcome");
}
}
- (1) 寫一個Properties格式的配置檔案,配置類的完整名稱。
- (2) 寫一個程式,讀取這個Properties配置檔案,獲得類的完整名稱并加載這個類,用反射的方式運作run方法。
- xxx.properties
package Test;
public class Tool {
public void run() throws Exception{
System.out.println("Welcome");
}
}
package Test;
import java.io.BufferedReader;
import java.io.FileReader;
public class Test {
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new FileReader("xxx.properties"));
//建立輸入流關聯配置檔案
Class clazz = Class.forName(br.readLine()); //讀取配置檔案中類名,擷取位元組碼對象
Tool t = (Tool)clazz.newInstance(); //通過位元組碼建立對象
t.run();
}
}
27.11_反射(動态代理的概述和實作)
- 代理:本來應該自己做的事情請了别人來做,被請的人就是代理對象
- 舉例:春節回家買票讓人代買
- 動态代理:在程式運作過程中産生的這個對象,而程式運作過程中産生對象其實就是剛才反射的内容,是以,動态代理其實就是通過反射來生成一個代理
- 在Java中java.lang.reflect包下提供了提供了一個Proxy類和一個InvocationHandler接口,通過使用這個類和接口就可以生成動态代理對象。JDK提供的代理隻能針對接口做代理。我們有更強大的代理cglib,Proxy類中的方法建立動态代理類對象
- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
- 最終會調用InvocationHandler的方法
- InvocationtionHander Object invoke(Object proxy, Method method, Object[] args)
package com.heima.動态代理;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("權限校驗");
method.invoke(target, args); //執行被代理target對象的方法
System.out.println("日志記錄");
return null;
}
}
27.12_設計模式(模闆(Template)設計模式概述和使用)
- 模闆方法模式就是定義一個算法的骨架,而将具體的算法延遲到子類中來實作
- 優點
- 使用模闆方法模式,在定義算法骨架的同時,可以很靈活地實作具體的算法,滿足使用者靈活多變的需求
- 缺點
- 1.裝飾
- 2.單例
- 3.簡單工廠
- 4.工廠方法
- 5.擴充卡
- 6.模闆
package com.heima.模版方法設計模式;
public class Demo1_Template {
public static void main(String[] args) {
/*long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
System.out.println("x");
}
long end = System.currentTimeMillis();
System.out.println(end - start);*/
Demo d = new Demo();
System.out.println(d.getTime());
}
}
abstract class GetTime {
public final long getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
return end - start;
}
public abstract void code();
}
class Demo extends GetTime {
@Override
public void code() {
int i = 0;
while(i < 100000) {
System.out.println("x");
i++;
}
}
}
27.13_JDK5新特性(自己實作枚舉類)
- 是指将變量的值一一列出來,變量的值隻限于列舉出來的值的範圍内。舉例:一周隻有7天,一年隻有12個月等。
- B:回想單例設計模式:單例類是一個類隻有一個執行個體
- 那麼多例類就是一個類有多個執行個體,但不是無限個數的執行個體,而是有限個數的執行個體。這才能是枚舉類。
public class Week {
public static final Week MON = new Week();
public static final Week TUE = new Week();
public static final Week WED = new Week();
private Week(){} //私有構造,不讓其他類建立本類對象
}
public class Week2 {
public static final Week2 MON = new Week2("星期一");
public static final Week2 TUE = new Week2("星期二");
public static final Week2 WED = new Week2("星期三");
private String name;
private Week2(String name){
this.name = name;
} //私有構造,不讓其他類建立本類對象
public String getName() {
return name;
}
}
public abstract class Week3 {
public static final Week3 MON = new Week3("星期一") {
public void show() {
System.out.println("星期一");
}
};
public static final Week3 TUE = new Week3("星期二"){
public void show() {
System.out.println("星期二");
}
};
public static final Week3 WED = new Week3("星期三"){
public void show() {
System.out.println("星期三");
}
};
private String name;
private Week3(String name){
this.name = name;
} //私有構造,不讓其他類建立本類對象
public String getName() {
return name;
}
public abstract void show();
}
public class Demo1_Enum {
public static void main(String[] args) {
//demo1();
//demo2();
Week3 mon = Week3.MON;
mon.show();
}
public static void demo2() {
Week2 mon = Week2.MON;
System.out.println(mon.getName());
}
public static void demo1() {
Week mon = Week.MON;
Week tue = Week.TUE;
Week wed = Week.WED;
System.out.println(mon);
}
}
- 1.自動拆裝箱
- 2.泛型
- 3.可變參數
- 4.靜态導入
- 5.增強for循環
- 6.互斥鎖
- 7.枚舉
27.14_JDK5新特性(通過enum實作枚舉類)
public enum Week {
MON,TUE,WED;
}
public enum Week2 {
MON("星期一"),TUE("星期二"),WED("星期三");
private String name;
private Week2(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return name;
}
}
public enum Week3 {
MON("星期一"){
public void show() {
System.out.println("星期一");
}
},TUE("星期二"){
public void show() {
System.out.println("星期二");
}
},WED("星期三"){
public void show() {
System.out.println("星期三");
}
};
private String name;
private Week3(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract void show();
}
27.15_JDK5新特性(枚舉的注意事項)
- 定義枚舉類要用關鍵字enum
- 所有枚舉類都是Enum的子類
- 枚舉類的第一行上必須是枚舉項,最後一個枚舉項的分号是可以省略的,但如果枚舉類有其他的東西,這個分号不能省略,建議不要省略
- 枚舉類可以有構造器,但必須是private的,它預設的也是private的
- 枚舉類也可以有抽象方法,但是枚舉項必須重寫該方法
- 枚舉在switch語句中的使用
27.16_JDK5新特性(枚舉類的常見方法)
- int ordinal()
- int compareTo(E o)
- String name()
- String toString()
- T valueOf(Class type, String name)
- values()
- 此方法雖然在JDK文檔中查找不到,但每個枚舉類都有該方法,它周遊枚舉類的所有枚舉值非常友善
public class Demo2_Enum {
/**
* int ordinal()
* int compareTo(E o)
* String name()
* String toString()
* <T> T valueOf(Class<T> type,String name)
* values()
* 此方法雖然在JDK文檔中查找不到,但每個枚舉類都具有該方法,它周遊枚舉類的所有枚舉值非常友善
*/
public static void main(String[] args) {
//demo1();
// Week2 mon = Week2.valueOf(Week2.class, "MON"); //通過位元組碼對象擷取枚舉項
// System.out.println(mon);
Week2[] arr = Week2.values();
for (Week2 week2 : arr) {
System.out.println(week2);
}
}
public static void demo1() {
Week2 mon = Week2.MON;
Week2 tue = Week2.TUE;
Week2 wed = Week2.WED;
/*System.out.println(mon.ordinal()); //枚舉項都是有編号的
System.out.println(tue.ordinal());
System.out.println(wed.ordinal());
System.out.println(mon.compareTo(tue)); //比較的是編号
System.out.println(mon.compareTo(wed));*/
System.out.println(mon.name()); //擷取執行個體名稱
System.out.println(mon.toString()); //調用重寫之後的toString方法
}
}
27.17_JDK7新特性(JDK7的六個新特性回顧和講解)
- A:二進制字面量0b001:System.out.println(0b110);
- B:數字字面量可以出現下劃線:100_000
- C:switch語句可以用字元串
- D:泛型簡化,菱形泛型
- E:異常的多個catch合并,每個異常用或|
- F:try-with-resources語句,1.7版本标準的異常處理
27.18_JDK8新特性(JDK8的新特性)
- 接口中可以定義有方法體的方法,如果是非靜态,必須用default修飾
- 如果是靜态的就不用了