认识Class对象以前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RTTI的说法则是源于《Thinking in Java》一书,其做用是在运行时识别一个对象的类型和类的信息,这里分两种:html
在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码以下:java
public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement { private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000; private static native void registerNatives(); static { registerNatives(); } /* * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM建立该类) * This constructor is not used and prevents the default constructor being * generated. */ private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
Class类被建立后的对象就是Class对象,注意,Class对象表示的是本身手动编写类的类型信息,好比建立一个Shapes类,那么,JVM就会建立一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。程序员
实际上在Java中每一个类都有一个Class对象,每当咱们编写而且编译一个新建立的类就会产生一个对应Class对象而且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为何须要这样一个Class对象呢?是这样的,当咱们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,而后JVM再根据这个类型信息相关的Class对象建立咱们须要实例对象或者提供静态变量的引用值。须要特别注意的是,手动编写的每一个class类,不管建立多少个实例对象,在JVM中都只有一个Class对象,即在内存中每一个类有且只有一个相对应的Class对象,挺拗口,经过下图理解(内存中的简易现象图):编程
到这咱们也就能够得出如下几点信息:数组
前面咱们已提到过,Class对象是由JVM加载的,那么其加载时机是?安全
实际上全部的类都是在对其第一次使用时动态加载到JVM中的,当程序第一次调用类的静态成员时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操做符建立类的新实例对象也会被看成调用类的静态成员(构造函数也是类的静态方法),由此看来Java程序在它们开始运行以前并不是被彻底加载到内存的,其各个部分是按需加载,因此在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象建立时依据Class对象中类型信息完成的),若是尚未加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏而且不包含不良Java代码(这是java的安全机制检测),彻底没有问题后就会被动态加载到内存中,此时至关于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时Class对象也就能够被用来建立这个类的全部实例对象。框架
下面经过一个简单例子来讲明Class对象被加载的时机问题(例子引用自Thinking in Java):dom
class Candy { static { System.out.println("Loading Candy"); } } class Gum { static { System.out.println("Loading Gum"); } } class Cookie { static { System.out.println("Loading Cookie"); } } public class SweetShop { public static void print(Object obj) { System.out.println(obj); } public static void main(String[] args) { print("inside main"); new Candy(); print("After creating Candy"); try { Class.forName("Gum"); } catch(ClassNotFoundException e) { print("Couldn't find Gum"); } print("After Class.forName(\"Gum\")"); new Cookie(); print("After creating Cookie"); } }
在上述代码中,每一个类Candy、Gum、Cookie都存在一个static语句(static初始化是在类加载时进行的),这个语句会在类第一次被加载时执行,这个语句的做用就是告诉咱们该类在何时被加载,执行结果:编程语言
inside main Loading Candy After creating Candy Loading Gum After Class.forName("Gum") Loading Cookie After creating Cookie
从结果来看,new一个Candy对象和Cookie对象,构造函数将被调用,属于静态方法的引用,Candy类的Class对象和Cookie的Class对象确定会被加载,毕竟Candy实例对象的建立依据其Class对象。比较有意思的是:ide
Class.forName("Gum");
其中forName方法是Class类(全部Class对象都属于这个类)的一个static成员方法。Class对象和其余对象同样,咱们能够获取并操做它的引用(这就是类加载器的工做)。这里经过forName方法,咱们能够获取到Gum类对应的Class对象引用。从打印结果来看,调用forName方法将会致使Gum类被加载(前提是Gum类历来没有被加载过)。
经过上述的案例,咱们知道Class.forName()方法的调用将会返回一个对应类的Class对象,所以若是咱们想获取一个类的运行时类型信息并加以使用时,能够调用Class.forName()方法获取该类对应的Class对象的引用,这样作的好处是无需经过持有该类的实例对象引用而去获取Class对象,以下的第2种方式是经过一个实例对象获取一个类的Class对象,其中的getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。
public static void main(String[] args) { try{ //经过Class.forName获取Gum类的Class对象 Class clazz=Class.forName("Gum"); System.out.println("forName=clazz:"+clazz.getName()); }catch (ClassNotFoundException e){ e.printStackTrace(); } //经过实例对象获取Gum的Class对象 Gum gum = new Gum(); Class clazz2 = gum.getClass(); System.out.println("new=clazz2:"+clazz2.getName());
}
注意调用forName方法时须要捕获一个名称为ClassNotFoundException的异常,由于forName方法在编译器是没法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,若是不存在就会抛出ClassNotFoundException异常。
在Java中存在另外一种方式来生成Class对象的引用,它就是Class字面常量,以下:
//字面常量的方式获取Class对象 Class clazz = Gum.class;
这种方式相对前面两种方法更加简单,更安全。由于它在编译器就会受到编译器的检查同时因为无需调用forName方法效率也会更高,由于经过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不只能够应用于普通的类,也能够应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时颇有帮助,关于反射技术稍后会分析,因为基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换以下,通常状况下更倾向使用.class的形式,这样能够保持与普通类的形式统一。
boolean.class = Boolean.TYPE; char.class = Character.TYPE; byte.class = Byte.TYPE; short.class = Short.TYPE; int.class = Integer.TYPE; long.class = Long.TYPE; float.class = Float.TYPE; double.class = Double.TYPE; void.class = Void.TYPE;
上面的=表示等价的意思
前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里咱们可能须要简单了解一下类加载的过程,以下:
由此可知,咱们获取字面常量的Class引用时,触发的应该是加载阶段,由于在这个阶段Class对象已建立完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面经过小例子来验证这个过程:
import java.util.*; class Initable { //编译期静态常量 static final int staticFinal = 47; //非编期静态常量 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); static { System.out.println("Initializing Initable"); } } class Initable2 { //静态成员变量 static int staticNonFinal = 147; static { System.out.println("Initializing Initable2"); } } class Initable3 { //静态成员变量 static int staticNonFinal = 74; static { System.out.println("Initializing Initable3"); } } public class ClassInitialization { public static Random rand = new Random(47); public static void main(String[] args) throws Exception { //字面常量获取方式获取Class对象 Class initable = Initable.class; System.out.println("After creating Initable ref"); //不触发类初始化 System.out.println(Initable.staticFinal); //会触发类初始化 System.out.println(Initable.staticFinal2); //会触发类初始化 System.out.println(Initable2.staticNonFinal); //forName方法获取Class对象 Class initable3 = Class.forName("Initable3"); System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } }
执行结果:
After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74
从输出结果来看,能够发现,经过字面常量获取方式获取Initable类的Class对象并无触发Initable类的初始化,这点也验证了前面的分析,。
同时发现调用Initable.staticFinal变量时也没有触发初始化,这是由于staticFinal属于编译期静态常量,在编译阶段经过常量传播优化的方式将Initable类的常量staticFinal存储到了一个称为NotInitialization类的常量池中,在之后对Initable类常量staticFinal的引用实际都转化为对NotInitialization类对自身常量池的引用,因此在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要缘由。
但在以后调用了Initable.staticFinal2变量后就触发了Initable类的初始化,注意staticFinal2虽然被static和final修饰,但其值在编译期并不能肯定,所以staticFinal2并非编译期常量,使用该变量必须先初始化Initable类。
Initable2和Initable3类中都是静态成员变量并不是编译期常量,引用都会触发初始化。
至于forName方法获取Class对象,确定会触发初始化,这点在前面已分析过。到这几种获取Class对象的方式也都分析完,到此这里能够得出小结论:
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:
因为Class的引用老是指向某个类的Class对象,利用Class对象能够建立实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,咱们能够利用泛型来表示Class对象更具体的类型,即便在运行期间会被擦除,但编译期足以确保咱们使用正确的对象类型。以下:
public class ClazzDemo { public static void main(String[] args){ //没有泛型 Class intClass = int.class; //带泛型的Class对象 Class<Integer> integerClass = int.class; integerClass = Integer.class; //没有泛型的约束,能够随意赋值 intClass= double.class; //编译期错误,没法编译经过 //integerClass = double.class } }
从代码能够看出,声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,若是存在错误只有在运行时才得以暴露出来。可是经过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性,实际上Integer.class就是一个Class<Integer>类的对象。面对下述语句,确实可能使人困惑,但该语句确实是没法编译经过的。
//编译没法经过 Class<Number> numberClass=Integer.class;
咱们或许会想Integer不就是Number的子类吗?然而事实并不是这般简单,毕竟Integer的Class对象并不是Number的Class对象的子类,前面提到过,全部的Class对象都只来源于Class类,看来事实确实如此。固然咱们能够利用通配符“?”来解决问题:
Class<?> intClass = int.class; intClass = double.class;
这样的语句并无什么问题,毕竟通配符指明全部类型都适用,那么为何不直接使用Class还要使用Class<?>呢?这样作的好处是告诉编译器,咱们是确实是采用任意类型的泛型,而非忘记使用泛型约束,所以Class<?>老是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。固然咱们还可使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题:
//编译经过! Class<? extends Number> clazz = Integer.class; //赋予其余类型 clazz = double.class; clazz = Number.class;
上述的代码是行得通的,extends关键字的做用是告诉编译器,只要是Number的子类均可以赋值。这点与前面直接使用Class<Number>
是不同的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。
下面的示例使用了泛型类语法。它存储了一个类引用,稍后又产生了一个List,填充这个List的对象是使用newInstance()方法,经过该引用生成的:
import java.util.*; class CountedInteger{ private static long counter; //每次建立一个实例,都会执行一次初始化id=counter++ private final long id = counter++; public String toString(){ return Long.toString(id); } } public class FilledList<T>{ private Class<T> type; public FilledList(Class<T> type){ this.type = type; } public List<T> create(int nElements){ List<T> result = new ArrayList<T>(); try{ for(int i=0;i<nElements;i++){ //CountedInteger必须提供默认的构造函数,经过CountedInteger类的Class对象建立一个实例对象 result.add(type.newInstance()); } }catch(Exception e){ throw new RuntimeException(e); } return result; } public static void main(String[] args){ FilledList<CountedInteger> f1 = new FilledList<CountedInteger>(CountedInteger.class); System.out.println(f1.create(15)); } }
输出以下:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
在Java SE5中新增一种使用Class对象进行类型转换的方式,即cast()方式:
class Building{} class House extends Building{} public class ClassCasts { public static void main(String[] args) { Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); h = (House)b; } }
利用Class对象的cast()方法,其参数接收一个参数对象并将其转换为Class引用的类型。固然,若是仔细观察上面的代码,则会发现,与实现了相同功能的main()中最后一行相比,这种转型好像作了不少额外的工做。新的转型语法对于没法使用普通类型的状况显得很是有用,在编写泛型代码时,若是存储了Class引用,并但愿之后经过这个引用来执行转型,这种状况就会时有发生。
关于instanceof 关键字,它返回一个boolean类型的值,意在告诉咱们对象是否是某个特定的类型实例。以下,在强制转换前利用instanceof检测obj是否是Animal类型的实例对象,若是返回true再进行类型转换,这样能够避免抛出类型转换的异常(ClassCastException)
public void cast(Object obj){ if(obj instanceof Animal){ Animal animal= (Animal) obj; } }
而isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的,看个简单例子:
public void cast2(Object obj){ //isInstance方法 if(Animal.class.isInstance(obj)){ Animal animal= (Animal) obj; } }
事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceof是关键字只被用于对象引用变量,检查左边对象是否是右边类或接口的实例化。若是被测对象是null值,则测试结果老是false。通常形式:
//判断这个对象是否是这种类型 obj instanceof className
而isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,若是obj是调用这个方法的class或接口的实例,则返回true。若是被检测的对象是null或者基本类型,那么返回值是false;通常形式以下:
//判断这个对象能不能被转化为这个类 className.class.inInstance(obj)
最后这里给出一个简单实例,验证isInstance方法与instanceof等价性:
class A {} class B extends A {} public class Instance { public static void print(String msg) { System.out.println(msg); } public static void test(Object x) { print("Testing x of type: " + x.getClass()); print("x instanceof A: " + (x instanceof A)); print("x instanceof B: "+ (x instanceof B)); print("A.isInstance(x): "+ A.class.isInstance(x)); print("B.isInstance(x): " + B.class.isInstance(x)); print("x.getClass() == A.class " + (x.getClass() == A.class)); print("x.getClass() == B.class " + (x.getClass() == B.class)); print("x.getClass().equals(A.class)) "+ (x.getClass().equals(A.class))); print("x.getClass().equals(B.class)) " + (x.getClass().equals(B.class))); } public static void main(String[] args) { test(new A()); test(new B()); } }
输出以下:
Testing x of type: class A x instanceof A: true x instanceof B: false A.isInstance(x): true B.isInstance(x): false x.getClass() == A.class true x.getClass() == B.class false x.getClass().equals(A.class)) true x.getClass().equals(B.class)) false Testing x of type: class B x instanceof A: true x instanceof B: true A.isInstance(x): true B.isInstance(x): true x.getClass() == A.class false x.getClass() == B.class true x.getClass().equals(A.class)) false x.getClass().equals(B.class)) true
到此关于Class对象相关的知识点都分析完了,下面将结合Class对象的知识点分析反射技术。
反射机制是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一块儿对反射技术进行了全力的支持。在反射包中,咱们经常使用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它能够在运行时动态建立对象、Field表示Class对象所表示的类的成员变量,经过它能够在运行时动态修改为员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,经过它能够动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是经过Class类中的方法获取的,Class类与Constructor相关的主要方法以下:
方法返回值 | 方法名称 | 方法说明 |
static Class<?> | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象 |
Constructor<T> | getConstructor(Class<?>... parameterTypes) | 返回指定参数类型、具备public访问权限的构造函数对象 |
Constructor<?>[] | getConstructors() | 返回全部具备public访问权限的构造函数的Constructor对象数组 |
Constructor<T> | getDeclaredConstructor(Class<?>... parameterTypes) | 返回指定参数类型、全部声明的(包括private)构造函数对象 |
Constructor<?>[] | getDeclaredConstructor() | 返回全部声明的(包括private)构造函数对象 |
T | newInstance() | 建立此 Class 对象所表示的类的一个新实例。 |
下面看一个简单例子来了解Constructor对象的使用:
import java.io.Serializable; import java.lang.reflect.Constructor;
import java.lang.reflect.Type; class User { private int age; private String name; //无参数构造 public User() { super(); } public void setName(String name) { // TODO Auto-generated method stub this.name = name; } public void setAge(int i) { // TODO Auto-generated method stub this.age = i; } //一个参数构造 public User(String name) { super(); this.name = name; } /** * 私有构造 * @param age * @param name */ @SuppressWarnings("unused") private User(int age, String name) { super(); this.age = age; this.name = name; } @Override public String toString() { // TODO Auto-generated method stub return "My name is " + name + ", age is " + age; } } @SuppressWarnings("serial") public class ReflectDemo implements Serializable{ public static void main(String[] args) throws Exception { Class<?> clazz = null; //获取User类对应的Class对象的引用 clazz = Class.forName("User"); //第一种方法,实例化默认构造方法,User必须有无参构造函数,不然将抛异常 User user = (User) clazz.newInstance(); user.setAge(20); user.setName("Rollen"); System.out.println("user:" + user); System.out.println("--------------------------------------------"); //获取带String参数的public构造函数 Constructor cs1 = clazz.getConstructor(String.class); //建立User User user1= (User) cs1.newInstance("Tom"); user1.setAge(22); System.out.println("user1:" + user1); System.out.println("--------------------------------------------"); //取得指定带int和String参数构造函数,该方法是私有构造private Constructor cs2 = clazz.getDeclaredConstructor(int.class,String.class); //因为是private必须设置可访问 cs2.setAccessible(true); //建立user对象 User user2= (User) cs2.newInstance(25,"Like"); System.out.println("user2:"+user2); System.out.println("--------------------------------------------"); //获取全部构造包含private Constructor<?>[] cons = clazz.getDeclaredConstructors(); //查看每一个构造方法须要的参数 for (int i = 0; i < cons.length; i++) { //获取构造函数参数类型 Class<?> clazzs[] = cons[i].getParameterTypes(); System.out.println("构造函数["+i+"]:"+cons[i].toString() ); System.out.print("参数类型["+i+"]:("); for (int j = 0; j < clazzs.length; j++) { if (j == clazzs.length - 1) System.out.print(clazzs[j].getName()); else System.out.print(clazzs[j].getName() + ","); } System.out.println(")"); } } }
输出以下:
user:My name is Rollen, age is 20 -------------------------------------------- user1:My name is Tom, age is 22 -------------------------------------------- user2:My name is Like, age is 25 -------------------------------------------- 构造函数[0]:private User(int,java.lang.String) 参数类型[0]:(int,java.lang.String) 构造函数[1]:public User(java.lang.String) 参数类型[1]:(java.lang.String) 构造函数[2]:public User() 参数类型[2]:()
关于Constructor类自己一些经常使用方法以下(仅部分,其余可查API),
方法返回值 | 方法名称 | 方法说明 |
Class<T> | getDeclaringClass() | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)。 |
Type[] | getGenericParameterTypes() | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 |
String | getName() | 以字符串形式返回此构造方法的名称。 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型。 |
T | newInstance(Object... initargs) | 使用此 Constructor对象表示的构造函数来建立新实例。 |
String | toGenericString() | 返回描述此 Constructor 的字符串,其中包括类型参数。 |
在上面程序追加以下内容:
System.out.println("--------------------------------------------"); //Constructor类自己一些经常使用方法 System.out.println("------getDeclaredClass----------"); Class uclazz = cs2.getDeclaringClass(); //Constructor对象表示的构造方法的类 System.out.println("构造方法的类:" + uclazz.getName()); System.out.println("------getGenericParameterTypes--------"); //按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型 Type[] tps = cs2.getGenericParameterTypes(); for(Type tp:tps) { System.out.println("参数名称tp:" + tp); } System.out.println("------getParameterTypes--------"); //按照声明顺序返回一组 Class 对象,返回的就是 Constructor对象构造函数的形参类型 Class[] clazzs = cs2.getParameterTypes(); for(Class claz:clazzs) { System.out.println("参数名称tp:" + claz.getName()); } System.out.println("------getName-------"); //以字符串形式返回此构造方法的名称 System.out.println("getName:" + cs2.getName()); System.out.println("------toGenericString-------"); //返回描述此 Constructor 的字符串,其中包括类型参数。 System.out.println("toGenericString:" + cs2.toGenericString());
输出以下:
-------------------------------------------- ------getDeclaredClass---------- 构造方法的类:User ------getGenericParameterTypes-------- 参数名称tp:int 参数名称tp:class java.lang.String ------getParameterTypes-------- 参数名称tp:int 参数名称tp:java.lang.String ------getName------- getName:User ------toGenericString------- toGenericString:private User(int,java.lang.String)
其中关于Type类型这里简单说明一下,Type 是 Java 编程语言中全部类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。getGenericParameterTypes 与 getParameterTypes 都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,因为Type是顶级接口,Class也实现了该接口,所以Class类是Type的子类,Type 表示的所有类型而每一个Class对象表示一个具体类型的实例,如String.class仅表明String类型。由此看来Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已。固然Type还有其余子类,如:
经过以上的分析,对于Constructor类已有比较清晰的理解,利用好Class类和Constructor类,咱们能够在运行时动态建立任意对象,从而突破必须在编译期知道确切类型的障碍。
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段多是一个类(静态)字段或实例字段。一样的道理,咱们能够经过Class类的提供的方法来获取表明字段信息的Field对象,Class类与Field对象相关方法以下:
方法返回值 | 方法名称 | 方法说明 |
Field | getDeclaredField(String name) | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
Field[] | getDeclaredFields() | 获取Class对象所表示的类或接口的全部(包含private修饰的)字段,不包括继承的字段 |
Field | getField(String name) | 获取指定name名称、具备public修饰的字段,包含继承字段 |
Field[] | getFields() | 获取修饰符为public的字段,包含继承字段 |
下面的代码演示了上述方法的使用过程:
import java.lang.reflect.*; class Person{ public int age; public String name; } class Student extends Person{ public String desc; private int score; } public class ReflectField { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException { Class<?> clazz = Class.forName("Student"); //获取指定name名称、具备public修饰的字段,包含继承字段 Field field = clazz.getField("age"); System.out.println("field:" + field); System.out.println("-----------------------"); //获取修饰符为public的字段,包含继承字段 Field[] fields = clazz.getFields(); for(Field f:fields) { System.out.println("field:" + f); } System.out.println("-----------------------"); //获取指定name名称的(包含private修饰的)字段,不包括继承的字段 Field field2 = clazz.getDeclaredField("desc"); System.out.println("field2:" + field2); System.out.println("-----------------------"); //获取Class对象所表示的类或接口的全部(包含private修饰的)字段,不包括继承的字段 Field[] fields2 = clazz.getDeclaredFields(); for(Field f:fields2) { System.out.println("field:" + f); } System.out.println("-----------------------"); } }
输出结果以下:
field:public int Person.age ----------------------- field:public java.lang.String Student.desc field:public int Person.age field:public java.lang.String Person.name ----------------------- field2:public java.lang.String Student.desc ----------------------- field:public java.lang.String Student.desc field:private int Student.score -----------------------
上述方法须要注意的是,若是咱们不指望获取其父类的字段,则需使用Class类的getDeclaredField()/getDeclaredFields()方法来获取字段便可,假若须要连带获取到父类的字段,那么请使用Class类的getField()/getFields(),可是也只能获取到public修饰的的字段,没法获取父类的私有字段。下面将经过Field类自己的方法对指定类属性赋值,代码演示以下:
Student st = (Student)clazz.newInstance(); //获取父类public字段并赋值 Field ageField = clazz.getField("age"); ageField.set(st, 18); Field nameField = clazz.getField("name"); nameField.set(st, "王杰文"); //只获取当前类的字段,不获取父类的字段 Field descField = clazz.getDeclaredField("desc"); descField.set(st, "I'm a Student!"); Field scoreField = clazz.getDeclaredField("score"); //设置为可访问,score是private scoreField.setAccessible(true); scoreField.set(st, 88); System.out.println(st);
输出以下:
name:王杰文 age:18 desc:I'm a Student! score:88
其中的set(Object obj, Object value)
方法是Field类自己的方法,用于设置字段的值,相反get(Object obj)
则是获取字段的值,固然关于Field类还有其余经常使用的方法以下:
方法返回值 | 方法名称 | 方法说明 |
void | set(Object obj, Object value) | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 |
Object | get(Object obj) | 返回指定对象上此 Field 表示的字段的值 |
Class<?> | getType() | 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 |
boolean | isEnumConstant() | 若是此字段表示枚举类型的元素则返回 true;不然返回 false |
String | toGenericString() | 返回一个描述此 Field(包括其通常类型)的字符串 |
String | getName() | 返回此 Field 对象表示的字段的名称 |
Class<?> | getDeclaringClass() | 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 |
void | setAccessible(boolean flag) | 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |
上述方法多是较为经常使用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不所有列出了,须要时查API文档便可。须要特别注意的是被final关键字修饰的Field字段是安全的,在运行时能够接收任何修改,但最终其实际值是不会发生改变的。
注意:使用反射在final字段上的修改是安全的,运行时系统会在不抛异常的状况下接受任何修改尝试,可是实际上不会发生任何修改。
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法多是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:
方法返回值 | 方法名称 | 方法说明 |
Method | getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的方法,该方法能够是公共、保护、默认(包)访问或者私有方法,但不能够是继承的方法 |
Method[] | getDeclaredMethod() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的全部方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法 |
Method | getMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的public方法,包括继承的方法 |
Method[] | getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的全部public方法 |
一样经过案例演示上述方法:
import java.lang.reflect.*; class Shape{ public void draw() { System.out.println("draw"); } public void draw(int count,String name) { System.out.println("draw" + name + ",count=" + count); } } class Circle extends Shape{ private void drawCircle() { System.out.println("drawCircle"); } public int getAllCount() { return 100; } } public class ReflectMethod { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException { Class clazz = Class.forName("Circle"); //返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的public方法,包括继承的方法 Method method = clazz.getMethod("draw", int.class,String.class); System.out.println("method:" + method); System.out.println("-----------------------------------------"); //返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的全部public方法 Method[] methods = clazz.getMethods(); for(Method m:methods) { System.out.println("method:" + m); } System.out.println("-----------------------------------------"); //返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的方法,该方法能够是公共、保护、默认(包)访问或者私有方法,但不能够是继承的方法 Method method1 = clazz.getDeclaredMethod("drawCircle"); System.out.println("method:" + method1); System.out.println("-----------------------------------------"); //返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的全部方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法 Method[] methods1 = clazz.getDeclaredMethods(); for(Method m:methods1) { System.out.println("method:" + m); } System.out.println("-----------------------------------------"); } }
输出以下:
method:public void Shape.draw(int,java.lang.String) ----------------------------------------- method:public int Circle.getAllCount() method:public void Shape.draw() method:public void Shape.draw(int,java.lang.String) method:public final void java.lang.Object.wait() throws java.lang.InterruptedException method:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException method:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException method:public boolean java.lang.Object.equals(java.lang.Object) method:public java.lang.String java.lang.Object.toString() method:public native int java.lang.Object.hashCode() method:public final native java.lang.Class java.lang.Object.getClass() method:public final native void java.lang.Object.notify() method:public final native void java.lang.Object.notifyAll() ----------------------------------------- method:private void Circle.drawCircle() ----------------------------------------- method:private void Circle.drawCircle() method:public int Circle.getAllCount() -----------------------------------------
在经过getMethods()方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod()/getDeclaredMethods()方法都只能获取当前类的方法。咱们在使用时根据状况选择便可。下面将演示经过Method对象调用指定类的方法:
//建立对象 Circle circle = (Circle) clazz.newInstance(); //经过Method对象的invoke(Object obj,Object... args)方法调用 method.invoke(circle,15,"圈圈"); //修改私有方法的访问标识 method1.setAccessible(true); method1.invoke(circle); //对有返回值得方法操做 Method method2 = clazz.getDeclaredMethod("getAllCount"); Integer count = (Integer) method2.invoke(circle); System.out.println("count:" + count);
输出以下:
draw:圈圈,count=15
drawCircle
count:100
在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)
第一个参数表明调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。
方法返回值 | 方法名称 | 方法说明 |
Object | invoke(Object obj, Object... args) | 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法 |
Class<?> | getReturnType() | 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型 |
Type | getGenericReturnType() | 返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组 |
Type[] | getGenericParameterTypes() | 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型 |
String | getName() | 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 |
boolean | isVarArgs() | 判断方法是否带可变参数,若是将此方法声明为带有可变数量的参数,则返回 true;不然,返回 false |
String | toGenericString() | 返回描述此 Method 的字符串,包括类型参数 |
getReturnType()方法/getGenericReturnType()方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就是参数类型信息。
而getParameterTypes()/getGenericParameterTypes()也是一样的道理,都是获取Method对象所表示的方法的参数类型,其余方法与前面的Field和Constructor是相似的。
在Java的java.lang.reflect包中存在着一个能够操做数组的类,Array,它提供了建立和访问Java 数组的方法。Array容许在执行 get()或 set()操做进行取值和赋值。在Class类中与数组关联的方法是:
方法返回值 | 方法名称 | 方法说明 |
Class<?> | getComponentType() | 返回表示数组元素类型的Class对象,即数组的类型 |
boolean | isArray() | 断定此 Class 对象是否表示一个数组类 |
java.lang.reflect.Array中的经常使用静态方法以下:
方法返回值 | 方法名称 | 方法说明 |
static Object | set(Object array, int index) | 返回指定数组对象中索引组件的值 |
static int | getLength(Object array) | 以 int 形式返回指定数组对象的长度 |
static object | newInstance(Class<?> componentType, int... dimensions) | 建立一个具备指定类型和维度的新数组 |
static Object | newInstance(Class<?> componentType, int length) | 建立一个具备指定的组件类型和长度的新数组 |
static void | set(Object array, int index, Object value) | 将指定数组对象中索引组件的值设置为指定的新值 |
下面经过一个简单例子来演示这些方法:
import java.lang.reflect.*; public class ReflectArray { public static void main(String[] args) throws ClassNotFoundException { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //获取数组类型的Class对象 即int.class Class<?> clazz = array.getClass().getComponentType(); //建立一个具备指定的组件类型和长度的新数组。 //第一个参数:指定了数组中的每一个元素应该是什么类型,第二个参数:数组的长度 Object newArr = Array.newInstance(clazz, 15); //获取原数组的长度 int co = Array.getLength(array); //赋值原数组到新数组 System.arraycopy(array, 0, newArr, 0, co); for (int i:(int[]) newArr) { System.out.print(i+","); } //建立了一个长度为10 的字符串数组, //接着把索引位置为6 的元素设为"hello world!",而后再读取索引位置为6 的元素的值 Class clazz2 = Class.forName("java.lang.String"); //建立一个长度为10的字符串数组,在Java中数组也能够做为Object对象 Object array2 = Array.newInstance(clazz2, 10); //把字符串数组对象的索引位置为6的元素设置为"hello" Array.set(array2, 6, "hello world!"); //得到字符串数组对象的索引位置为5的元素的值 String str = (String)Array.get(array2, 6); System.out.println(); System.out.println(str);//hello } }
输出结果:
1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
hello world!
经过上述代码演示,确实能够利用Array类和反射相结合建立数组,也能够在运行时获取和设置数组中元素的值,其实除了上的set()/get()外Array还专门为8种基本数据类型提供特有的方法,如setInt()/getInt()、setBoolean()/getBoolean(),其余依次类推,须要使用是能够查看API文档便可。除了上述修改数组长度或者建立数组或获取值或设置值外,能够利用泛型建立泛型数组以下:
import java.lang.reflect.*; public class ReflectArray { /** * 接收一个泛型数组,而后建立一个长度与接收的数组长度同样的泛型数组, * 并把接收的数组的元素复制到新建立的数组中, * 最后找出新数组中的最小元素,并打印出来 * @param a * @param <T> */ public static <T extends Comparable<T>> void FindMinValue(T[] a) { //经过反射机制建立相同类型的数组 T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length); for(int i=0;i<a.length;i++) { b[i] = a[i]; } T min = null; boolean flag = true; for(int i=0;i<b.length;i++) { if(flag) { min = b[i]; flag = false; } if(b[i].compareTo(min) < 0) { min = b[i]; } } System.out.println(min); } public static void main(String[] args) throws ClassNotFoundException { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //获取数组类型的Class对象 即int.class Class<?> clazz = array.getClass().getComponentType(); //建立一个具备指定的组件类型和长度的新数组。 //第一个参数:指定了数组中的每一个元素应该是什么类型,第二个参数:数组的长度 Object newArr = Array.newInstance(clazz, 15); //获取原数组的长度 int co = Array.getLength(array); //赋值原数组到新数组 System.arraycopy(array, 0, newArr, 0, co); for (int i:(int[]) newArr) { System.out.print(i+","); } //建立了一个长度为10 的字符串数组, //接着把索引位置为6 的元素设为"hello world!",而后再读取索引位置为6 的元素的值 Class clazz2 = Class.forName("java.lang.String"); //建立一个长度为10的字符串数组,在Java中数组也能够做为Object对象 Object array2 = Array.newInstance(clazz2, 10); //把字符串数组对象的索引位置为6的元素设置为"hello" Array.set(array2, 6, "hello world!"); //得到字符串数组对象的索引位置为5的元素的值 String str = (String)Array.get(array2, 6); System.out.println(); System.out.println(str);//hello String[] strs = {"za","cb","ca","d","e","f"}; FindMinValue(strs); } }
输出以下:
1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, hello world! ca
毕竟咱们没法直接建立泛型数组,有了Array建立泛型数组的问题也就迎刃而解了。
//无效语句,编译不通 T[] b = new T[a.length];
到这反射中几个重要而且经常使用的类咱们都基本介绍完了,但更重要是,咱们应该认识到反射机制并无什么神奇之处。当经过反射与一个未知类型的对象打交道时,JVM只会简单地检查这个对象,判断该对象属于哪一种类型,同时也应该知道,在使用反射机制建立对象前,必须确保已加载了这个类的Class对象,固然这点彻底没必要由咱们操做,毕竟只能JVM加载,但必须确保该类的”.class”文件已存在而且JVM可以正确找到。关于Class类的方法在前面咱们只是分析了主要的一些方法,其实Class类的API方法挺多的,建议查看一下API文档,浏览一遍,有个印象也是不错的选择,这里仅列出前面没有介绍过又可能用到的API:
/** * 修饰符、父类、实现的接口、注解相关 */ //获取修饰符,返回值可经过Modifier类进行解读 public native int getModifiers(); //获取父类,若是为Object,父类为null public native Class<? super T> getSuperclass(); //对于类,为本身声明实现的全部接口,对于接口,为直接扩展的接口,不包括经过父类间接继承来的 public native Class<?>[] getInterfaces(); //本身声明的注解 public Annotation[] getDeclaredAnnotations(); //全部的注解,包括继承获得的 public Annotation[] getAnnotations(); //获取或检查指定类型的注解,包括继承获得的 public <A extends Annotation> A getAnnotation(Class<A> annotationClass); public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass); /** * 内部类相关 */ //获取全部的public的内部类和接口,包括从父类继承获得的 public Class<?>[] getClasses(); //获取本身声明的全部的内部类和接口 public Class<?>[] getDeclaredClasses(); //若是当前Class为内部类,获取声明该类的最外部的Class对象 public Class<?> getDeclaringClass(); //若是当前Class为内部类,获取直接包含该类的类 public Class<?> getEnclosingClass(); //若是当前Class为本地类或匿名内部类,返回包含它的方法 public Method getEnclosingMethod(); /** * Class对象类型判断相关 */ //是不是数组 public native boolean isArray(); //是不是基本类型 public native boolean isPrimitive(); //是不是接口 public native boolean isInterface(); //是不是枚举 public boolean isEnum(); //是不是注解 public boolean isAnnotation(); //是不是匿名内部类 public boolean isAnonymousClass(); //是不是成员类 public boolean isMemberClass(); //是不是本地类 public boolean isLocalClass();
动态代理看起来好像是个什么高大上的名词,但其实并无那么复杂,直接从字面就很容易理解。动态地代理,能够猜想一下它的含义,在运行时动态地对某些东西代理,代理它作了其余事情。先不去搞清楚这个动态代理真正的含义,咱们来举个生动的例子来理解下它到底作了什么。
一个程序员Developer,他会开发code,他调试debug:
interface Developer{ void code(); void debug(); }
程序员有不少分类,其中有Java程序员JavaDeveloper,他会开发Java代码,会调试Java代码。
class JavaDeveloper implements Developer{ private String name; public JavaDeveloper(String name) { this.name = name; } @Override public void code() { // TODO Auto-generated method stub System.out.println(this.name + " is coding java"); } @Override public void debug() { // TODO Auto-generated method stub System.out.println(this.name + " is debugging java"); } }
其实咱们没有必要去定义他,由于他是后天养成的,咱们应该在这个程序员的成长期去实现这个特性,而不是在他出生以前定义。咱们来看下代码是怎么实现的:
public class JavaDynamicProxy { public static void main(String[] args) { JavaDeveloper zack = new JavaDeveloper("Zack"); zack.code(); zack.debug(); //建立动态代理 Developer zackProxy = (Developer)Proxy.newProxyInstance( zack.getClass().getClassLoader(), //类加载器 zack.getClass().getInterfaces(), //但愿被代理的接口列表 (proxy,method,agrs) -> { if(method.getName().equals("code")) { System.out.println("Zack is praying for the code"); } if(method.getName().equals("debug")) { System.out.println("Zack's have no bug! No need to debug!"); } //return method.invoke(proxy, agrs); return null; } ); zackProxy.code(); zackProxy.debug(); } }
若是Zack只是一个普通的Java程序员,那么他的开发结果是:
Zack is coding java
Zack is debugging java
可是真正的Zack(代理后):
Zack is praying for the code Zack's have no bug! No need to debug!
回看下上面是如何使用动态代理的使用。生成一个实例对象zack,而后用Proxy的newInstance方法对这个实例对象代理生成一个动态代理对象zackProxy。
//建立动态代理 Developer zackProxy = (Developer)Proxy.newProxyInstance( zack.getClass().getClassLoader(), //类加载器 zack.getClass().getInterfaces(), //但愿被代理的接口列表 (proxy,method,agrs) -> { if(method.getName().equals("code")) { System.out.println("Zack is praying for the code"); } if(method.getName().equals("debug")) { System.out.println("Zack's have no bug! No need to debug!"); } //return method.invoke(proxy, agrs); return null; } );
看下newProxyInstance()的接口定义:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
这三个参数具体的含义来看看注解是怎么描述的:
所以能够修改第三个参数为InvacationHandler接口的一个实现,代码以下:
import java.lang.reflect.*; interface Developer{ void code(); void debug(); } class JavaDeveloper implements Developer{ private String name; public JavaDeveloper(String name) { this.name = name; } @Override public void code() { // TODO Auto-generated method stub System.out.println(this.name + " is coding java"); } @Override public void debug() { // TODO Auto-generated method stub System.out.println(this.name + " is debugging java"); } } //调用处理器 class DynamicProxyHandlerDeveloper implements InvocationHandler{ private Object proxied; //传入被代理对象 public DynamicProxyHandlerDeveloper(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub if(method.getName().equals("code")) { System.out.println("Zack is praying for the code"); } if(method.getName().equals("debug")) { System.out.println("Zack's have no bug! No need to debug!"); } //return method.invoke(proxied, agrs); //被代理对象zack,这里能够将请求转发给本来方法; //return method.invoke(proxy, agrs); //代理对象zackProxy,会陷入死循环 return null; } } public class JavaDynamicProxy { public static void main(String[] args) { JavaDeveloper zack = new JavaDeveloper("Zack"); zack.code(); zack.debug(); //建立动态代理 Developer zackProxy = (Developer)Proxy.newProxyInstance( zack.getClass().getClassLoader(), //类加载器 zack.getClass().getInterfaces(), //但愿被代理的接口列表 new DynamicProxyHandlerDeveloper(zack) ); zackProxy.code(); zackProxy.debug(); } }
loader和interfaces基本就是决定了这个类究竟是个怎么样的类。而h是InvocationHandler,决定了这个代理类究竟是多了什么功能。因此动态代理的内容重点就是这个InvocationHandler。
InvocationHandler做用就是,当被代理对象zack的本来方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代本来方法的结果返回:
InvocationHandler接收三个参数
在上面的例子里:
(proxy,method,agrs) -> { if(method.getName().equals("code")) { System.out.println("Zack is praying for the code"); } if(method.getName().equals("debug")) { System.out.println("Zack's have no bug! No need to debug!"); } //return method.invoke(proxy, agrs); return null; }
若是最后的return语句改为:
return method.invoke(proxy, agrs);
动态代理的好处咱们从例子就能看出来,它比较灵活,能够在运行的时候才切入改变类的方法,而不须要预先定义它。
动态代理通常咱们比较少去手写,但咱们用得其实很是多。在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。
这种场景的使用是动态代理最佳的落地点,能够很是灵活地在某个类,某个方法,某个代码点上切入咱们想要的内容,就是动态代理其中的内容。
参考文章
[1] 深刻理解Java类型信息(Class对象)与反射机制(转载)
[2]Java编程思想
[3] 【译】1. Java反射——引言
[4] 你真的彻底了解Java动态代理吗?看这篇就够了(转载)