關注公衆號:小李不禿
Java 分編譯期和運行期html
編譯方式說明:java
- 靜態編譯:在編譯時肯定類型 & 綁定對象。如常見的使用
new
關鍵字建立對象- 動態編譯:運行時肯定類型 & 綁定對象。動態編譯體現了
Java
的靈活性、多態特性 & 下降類之間的耦合性
咱們帶着如下幾個問題去學習今天的知識程序員
反射(Reflection)是 Java 的特性之一,它可讓運行中的 Java 程序獲取自身的信息,而且能夠操做類或者對象的內部屬性。數據庫
Oracle 官方對反射的解釋是:設計模式
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.api
經過反射,咱們能夠在程序運行時得到程序集中每個類型的成員和成員信息。咱們平時所用的 new 去建立的對象的類型,是在編譯期就肯定下來了。而 Java 反射能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。因此咱們能夠經過反射機制建立對象,即便這個對象的類型在編譯期是未知的。數組
反射的核心是 JVM 在運行時纔會動態加載類、調用方法和訪問屬性,它不須要在編譯期知道運行的對象是誰。安全
Java 反射主要提供如下的功能:app
咱們能夠在運行時取到「任意」你想要的類、對象、變量、方法等。框架
注:反射是在運行時操做的,而不是編譯時。
優勢:
缺點:
由於反射包括了一些動態類型,因此 JVM 沒法對這些代碼進行優化。所以,反射操做的效率要比那些直接調用慢的多。因此儘可能避免在常常被執行的代碼或者對性能要求很高的程序中使用反射。
(反射大概比直接調用慢 50 ~ 100 倍,可是須要在執行 100 萬遍的時候纔會有所感受)
使用反射技術要求程序必須在一個沒有安全限制的環境中運行。
反射容許代碼執行一些在正常狀況下不被容許的操做(訪問私有的屬性或方法),因此使用反射可能會致使意料以外的反作用 —— 代碼有功能上的錯誤,下降可移植性。反射破壞了代碼的抽象性,所以當平臺發生改變的時候,代碼的行爲就可能也隨着變化。
提問:Java 反射能夠訪問和修改私有成員變量,那封裝成 private 還有意義麼?
既然小偷能夠訪問和搬走私有成員傢俱,那封裝成防盜門還有意義麼?這是同樣的道理,而且 Java 從應用層給咱們提供了安全管理機制——安全管理器,每一個 Java 應用均可以擁有本身的安全管理器,它會在運行階段檢查須要保護的資源的訪問權限及其它規定的操做權限,保護系統免受惡意操做攻擊,以達到系統的安全策略。
因此其實反射在使用時,內部有安全控制,若是安全設置禁止了這些,那麼反射機制就沒法訪問私有成員。
Class 存放着對應類型的運行時信息。在 Java 程序運行時,Java 虛擬機爲全部類型維護一個 java.lang.Class 對象。該 Class 對象存放着全部關於該對象的運行時信息。
那咱們如何獲取想要的 Class,look this Code
public static void main(String[] args) {
// 方式1:Object.getClass()
// Object 類的 getClass() 方法返回一個 Class 類實例
String name = "不是禿頭的小李程序員";
Class<?> classType = name.getClass();
System.out.println("Object.getClass() classType: " + classType);
// 方式2:T.Class
// T 是任意 Java 類型
Class<?> classType2 = String.class;
System.out.println("T.Class classType: " + classType2);
// 方式3:Class.forName
try {
Class<?> classType3 = Class.forName("java.lang.String");
System.out.println("Class.forName classType: " + classType2);
// 根據 className 沒有找到類,會拋出 ClassNotFoundException 異常
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
獲取 Class 的三種方法
這三種用法須要根據具體的場景靈活去使用,好比 JDBC 獲取鏈接的數據庫類型,就經過 Class.forName(「類路徑」)。
經過 getSuperclass() 方法獲取父類的 Class,示例以下:
Class<?> superclass = Integer.class.getSuperclass();
System.out.println(superclass);
System.out.println(superclass.getSuperclass());
System.out.println(superclass.getSuperclass().getSuperclass());
System.out.println(superclass.getSuperclass().getSuperclass().getSuperclass());
運行結果
class java.lang.Number
class java.lang.Object
null
Exception in thread "main" java.lang.NullPointerException
能夠看到 Integer 的父類是 Number,Number 的父類是 Object,Object 就沒有父類了,因此在 null 以後會拋出空指針的異常。
獲取到想要的 Class 了,就能夠獲取到它的一切信息。
在獲取想要的信息前,不妨先了解個知識點。
方法中帶與不帶「Declared」的區別
- 不帶「Declared」的方法支持取出包括繼承、公有(public)的 Field、Method 和 Constructor。
- 帶「Declared」的方法支持取出包括當前類全部的構造函數(包括公有和私有的,不包括繼承)的 Field、Method 和 Constructor。
如何經過 Class 實例獲取字段信息。Class 類提供瞭如下幾個方法來獲取字段:
快快快,show me code
public class FiledTest1 {
public static void main(String[] args) throws NoSuchFieldException {
Class stdClass = Student.class;
// 獲取 public 字段 "score"
System.out.println(stdClass.getField("score"));
// 獲取繼續的 public 字段 "name"
System.out.println(stdClass.getField("name"));
// 獲取 private 字段 "grade"
System.out.println(stdClass.getDeclaredField("grade"));
}
}
class Student extends Person{
public int score;
private int grade;
}
class Person{
public String name;
}
運行結果:
public int com.javastudy.reflection.Fields.Student.score
public java.lang.String com.javastudy.reflection.Fields.Person.name
private int com.javastudy.reflection.Fields.Student.grade
一個 Filed 對象包含了一個字段的全部信息:
The java.lang.reflect.Method.getModifiers() method returns the Java language modifiers for the method represented by this Method object, as an integer. The Modifier class should be used to decode the modifiers.getmodifiers()方法以整數的形式返回該方法對象所表示的方法的 Java 語言修飾符。應該使用修飾詞類來解碼修飾詞。
public class FieldTest2 {
private final String name = "不是禿頭的小李程序員";
public static void main(String[] args) throws NoSuchFieldException {
Class c = FieldTest2.class;
Field field = c.getDeclaredField("name");
int mod = field.getModifiers();
System.out.println("name: " + field.getName());
System.out.println("type: " + field.getType());
System.out.println("final: " + Modifier.isFinal(mod));
System.out.println("public: " + Modifier.isPublic(mod));
System.out.println("protected: " + Modifier.isProtected(mod));
System.out.println("private: " + Modifier.isPrivate(mod));
System.out.println("static: " + Modifier.isStatic(mod));
}
}
運行結果:
name: name
type: class java.lang.String
final: true
public: false
protected: false
private: true
static: false
咱們拿到了 Field,該經過 Field 獲取該字段對應的值。咱們仍是用上面的例子來獲取 name 值。
public class FieldTest3 {
private final String name = "不是禿頭的小李程序員";
public static void main(String[] args) throws Exception {
Object object = new FieldTest3();
Class c = FieldTest3.class;
Field field = c.getDeclaredField("name");
Object value = field.get(object);
System.out.println(value);
}
}
運行結果:
不是禿頭的小李程序員
咱們經過 get() 來獲取 Field 的值,那麼我們在看一個下面的例子:
public class FieldTest4 {
public static void main(String[] args) throws Exception {
Object animal = new Animal("不是禿頭的小李程序員 Animal111");
Class c = Animal.class;
Field field = c.getDeclaredField("name");
Object value = field.get(animal);
System.out.println(value);
// Animal animal = new Animal();
// animal.testFiled();
}
}
class Animal {
private String name;
public Animal() {
}
public Animal(String name){
this.name = name;
}
public void testFiled() throws Exception {
Object animal = new Animal("不是禿頭的小李程序員 Animal222");
Class c = Animal.class;
Field field = c.getDeclaredField("name");
Object value = field.get(animal);
System.out.println(value);
}
}
運行結果:
Exception in thread "main" java.lang.IllegalAccessException: Class com.javastudy.reflection.Fields.FieldTest4 can not access a member of class com.javastudy.reflection.Fields.Animal with modifiers "private"
WTF?居然出現異常了,小李你是在玩我麼,第一次就能夠,第二次又提示沒有權限,到底能不能獲取我想要的值。固然能,不能我就禿給你看。
咱們只須要在 filed.get() 的上一步加上下面的代碼就能夠了。管你是 public 仍是 private.
field.setAccessible(true);
那咱們想想爲何第一次不加上面的代碼也能訪問呢?
由於是在本身的類裏訪問的,就拿你本身想想,你有一個鼻子和兩個耳朵,它們就是你私有的(private),你能夠隨便摸他們和摳他們(嘔),可是別人想碰的時候,必需要通過你的贊成才行(setAccessible(true))。這麼理解下剛纔的代碼,你就明白了。若是還不明白能夠打開上面的兩段註釋:
Animal animal = new Animal();
animal.testFiled();
運行結果:
不是禿頭的小李程序員222
經過 Field 實例既能獲取指定實例的字段值,也能夠設置字段的值。
設置字段值經過 Field 的 set 方法實現。
// 第一個參數是指定的實例
// 第二個參數是待修改的值
void set(Object obj, Object value)
示例代碼以下:
public class FieldTest5 {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher("不是禿頭的小李程序員");
Class c = teacher.getClass();
Field field = c.getDeclaredField("name");
field.setAccessible(true);
field.set(teacher,"小李不禿頭");
System.out.println(field.get(teacher));
}
}
class Teacher{
private String name;
public Teacher(String name){
this.name = name;
}
public String getName() {
return name;
}
}
打印結果:
小李不禿頭
爲了避免禿頭,我容易麼(此處嚶嚶嚶)。嚶完就該提問題了
Field 在 get 和 set 的 obj 參數有什麼做用?回答:咱們能夠經過查看 api 的註釋,瞭解到這兩個方法在獲取靜態實例的時候,obj 能夠傳 null,若是要獲取對象的實例 obj 參數就不能爲空,不然會返回 NullException。
Java 的反射 API 提供的 Field 類封裝了字段的全部信息:
經過 Class 實例獲取全部 Method 的信息,Class 類提供瞭如下幾個方法來獲取方法:
看一下示例代碼:
public class MethodTest1 {
public static void main(String[] args) throws Exception{
Class c = Student.class;
// 獲取public方法getScore,參數爲String;
System.out.println(c.getMethod("getScore",String.class));
// 獲取繼承的public方法getName,無參數;
System.out.println(c.getMethod("getName"));
// 獲取private方法getGrade,參數爲int;
System.out.println(c.getDeclaredMethod("getGrade",int.class));
}
}
運行結果:
public int com.javastudy.reflection.Methods.Student.getScore(java.lang.String)
public java.lang.String com.javastudy.reflection.Methods.Person.getName()
private int com.javastudy.reflection.Methods.Student.getGrade(int)
一個 Method 對象包含了一個方法的全部信息:
示例以下:
public class MethodTest2 {
public static void main(String[] args) throws Exception{
Class c = Student.class;
Method method= c.getDeclaredMethod("getGrade",int.class);
System.out.println("name : " + method.getName());
System.out.println("returnType : " + method.getReturnType());
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println("paramaterTypes的長度 : " + parameterTypes.length);
for (Class parameterType : parameterTypes){
System.out.println("paramaterTypes : " + parameterType);
}
}
}
運行結果:
name : getGrade
returnType : int
paramaterTypes的長度 : 1
paramaterTypes : int
先看示例:
public class MethodTest3 {
public static void main(String[] args) throws Exception {
String s = "不是禿頭的小李程序員";
Method method = String.class.getMethod("substring", int.class);
Method method2 = String.class.getMethod("substring", int.class, int.class);
String result = (String) method.invoke(s,7);
String result2 = (String) method2.invoke(s,1,9);
System.out.println(result);
System.out.println(result2);
}
}
運行結果:
程序員
是禿頭的小李程序
分析下程序員小李是如何禿頭的:
調用靜態方法,無需指定實例對象,invoke 方法傳入的第一個參數永遠是 null 或者空值 」「,咱們看下面的例子:
public class MethodTest4 {
public static void main(String[] args) throws Exception{
// 獲取Integer.parseInt(Stirng)方法,參數是String
Method method = Integer.class.getMethod("parseInt", String.class);
// 調用靜態方法獲取結果
// Integer result = (Integer)method.invoke("", "12345");
Integer result = (Integer)method.invoke(null, "12345");
System.out.println(result);
}
}
運行結果:12345
對於非public方法,咱們能夠經過 Class.getDeclaredMethod() 獲取,可是調用的時候會拋出一個IllegalAccessException。爲了調用非public方法,經過Method.setAccessible(true)容許其調用:
public class MethodTest5 {
public static void main(String[] args) throws Exception{
Person p = new Person();
Method method = p.getClass().getDeclaredMethod("setName", String.class);
method.setAccessible(true);
method.invoke(p,"不是禿頭的小李程序員");
System.out.println(p.name);
}
}
此外,setAccessible(true)
可能會失敗。若是JVM運行期存在SecurityManager
,那麼它會根據規則進行檢查,有可能阻止setAccessible(true)
。例如,某個SecurityManager
可能不容許對java
和javax
開頭的package
的類調用setAccessible(true)
,這樣能夠保證JVM核心庫的安全。
若是一個 Person 定義了 hello() 方法,而且它的子類Student也重寫了該方法,那麼咱們從 Person.class 獲取的 Method 做用於 Student 實例時,調用的方法是哪一個?
public class MethodTest6 {
public static void main(String[] args) throws Exception{
// 獲取Person的hello方法
Method method = Person.class.getMethod("hello");
// 對Student實例調用hello方法
method.invoke(new Student());
}
}
public class Person {
public void hello(){
System.out.println("Person:hello");
}
}
public class Student extends Person {
public void hello(){
System.out.println("Student:hello");
}
}
1.4.3 .5運行結果
Student:hello
發現打印出的是Student:hello,因此使用反射調用方法時,仍遵循多態原則:即老是調用實際類型的覆蓋方法。
上述的反射代碼:
Method m = Person.class.getMethod("hello");
m.invoke(new Student());
至關於:
Person p = new Student();
p.hello();
Java 的反射 API 提供的 Method 對象封裝了方法的全部信息:
經過 Class 實例獲取全部 Constructor 的信息,Class 類提供瞭如下幾個方法來獲取方法:
Constructor 老是當前類定義的構造方法,和父類無關,所以不存在多態的問題。
示例以下:
public class ContructorTest1 {
public static void main(String[] args) throws Exception{
Class c = Person.class;
Person p = (Person) c.newInstance();
Constructor cons1 = c.getConstructor(int.class);
Person p1 = (Person)cons1.newInstance(30);
Constructor cons2 = c.getDeclaredConstructor(String.class);
cons2.setAccessible(true);
Person p2 = (Person)cons2.newInstance("不是禿頭的小李程序員");
Constructor cons3 = c.getConstructor(String.class, int.class);
Person p3 = (Person)cons3.newInstance("不是禿頭的小李程序員-35",35);
}
}
Person.class
public class Person {
private String name;
private int age;
public Person() {
System.out.println("Person");
}
public Person(int age) {
this.age = age;
System.out.println("Person age:" + age);
}
private Person(String name) {
this.name = name;
System.out.println("Person name:" + name);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person toString:" + toString());
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
運行結果:
Person
Person age:30
Person name:不是禿頭的小李程序員
Person toString:Person{name='不是禿頭的小李程序員-35', age=35}
經過上面的結果,得出如下結論:
咱們能夠經過 Class 的 getInterfaces() 獲取當前類實現的全部接口,示例以下:
public class ReflectionInterfaceTest {
public static void main(String[] args) {
Class s = Integer.class;
Class[] interfaces = s.getInterfaces();
for (Class c:interfaces){
System.out.println(c);
}
}
}
運行結果:
interface java.lang.Comparable
經過本文你大概瞭解了反射,讓咱們再複習一下:
下一節我帶大家看看反射的原理,讓咱們更進一步的瞭解它。
https://blog.csdn.net/carson_ho/article/details/80921333
https://zhidao.baidu.com/question/1639472919001036380.html
https://www.liaoxuefeng.com/wiki/1252599548343744/1264803033837024