Class对象
在JVM底层原理中我们已经知道了:
当我们实例化一个新的对象时,JVM会先通过classLoader搜寻对应的class文件并创建一个Class对象,一个Class对象包含了关于这个类的详细信息,然后JVM会根据这个Class对象去初始化实例对象。
一个Class对象包含了这个类的 全限定名(name)、包名(package)、父类(super)、实现接口(interface)、成员(fields)、方法(methods) 等信息,这些信息是用来描述这个类本身的元信息 ,反过来说,我们也可以通过得到一个类的Class对象来获取关于这个类的元信息
通过Class获取类的元信息的操作我们叫做反射 (Reflection)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Main { public static void main(String[] args){ Integer n = Integer.valueOf(123); System.out.println(n instanceof Integer); System.out.println(n instanceof Number); System.out.println(n.getClass() == Integer.class); System.out.println(n.getClass() == Number.class); System.out.println(n.getClass().getSuperclass() == Number.class); } } stdout: true //n是Integer,Integer自然是Integer true //n是Integer,自然也是Number true //n是Integer,Integer.class自然是Integer.class false //n是Integer,Integer.class!=Number.class,实际上有的编译器会直接在这一步报错 true //n是Integer,Integer的父类是Number,Number.class自然是Number.class
通过上述代码可以知道,每个类的Class对象都是独立的,即Integer虽然在继承关系上属于Number,但是Integer.class和Number.class是不同的两个Class对象,但我们仍可以通过Integer.class.getSuperclass()
来获取Integer父类的Class对象
获取Class 我们可以通过多种方法得到一个类的Class对象,由于Class对象在JVM中具有唯一性 ,对于同一个类获取的Class对象永远是同一个
声明Class引用 1 Class<String> cls=String.class;
要注意的是,这里我们只是创建了一个class的引用,并没有显式地创建一个新的Class对象
调用实例对象的getClass方法 1 2 String s="123"; Class<? extends String> cls=s.getClass();
调用Class的forName静态方法 1 Class<?> cls = Class.forName("java.lang.String");
如你所见,这种方法需要知道类的全限定名 才能获取
Class提供的方法 在我们获取到一个Class对象后,可以用Class对象的许多get方法获取关于类的信息
有趣的是,当我们试图打印一些数据类型的ClassName时,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Main { public static void main(String[] args) { int[] array=new int[5]; Integer i=Integer.valueOf(5); String[] sArr={"123"}; System.out.println("Class name: " + array.getClass().getName()); System.out.println("Class name: " + i.getClass().getName()); System.out.println("Class name: " + sArr.getClass().getName()); } } stdout: Class name: [I Class name: java.lang.Integer Class name: [Ljava.lang.String;
JVM给不同的数据类型(基本类型、对象、数组)设置了一套描述符,在字节码中用一系列大写字母来表示不同数据类型
描述符
数据类型
描述符
数据类型
B
byte
J
long
C
char
S
short
D
double
V
void
F
float
Z
boolean
I
int
L*
Object
[*
数组类型
于是从上面的输出我们会发现:
[I 说明了这是一个整数类型的数组
第一行输出中大写字母I 指的则是int类型数据,**[** 则指的是这是一个int类型的数组
而Integer的ClassName则是java.lang.Integer,即Integer对象的全限定名
当我们试图输出String[]的ClassName时得到的是[Ljava.lang.String,结合第一点和第二点,实际上这个name分为两部分
[L 说明了这是一个对象类型的数组
java.lang.String则是这个对象的全限定名 的符号引用
字段
Field(字段)对象 是一个用于描述类的成员的对象,如String源码下的private final byte[] value
就是String的一个字段
获取字段 Class提供了如下几个方式来获得某个class对象的指定field
1 2 3 4 public Field getField (String name) public Field getDeclaredField (String name) public Field[] getFields() public Field[] getDeclaredFields()
在获取到一个字段后,我们可以通过一系列get获取到关于该字段的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Main { public static void main (String[] args) throws NoSuchFieldException { String sArr="123" ; Field f=sArr.getClass().getDeclaredField("value" ); System.out.println("getName:" +f.getName()); System.out.println("getType:" +f.getType()); System.out.println("getModifiers:" +f.getModifiers()); System.out.println("isPublic:" +Modifier.isPublic(f.getModifiers())); System.out.println("isPrivate:" +Modifier.isPrivate(f.getModifiers())); } } stdout: getName:value getType:class [B getModifiers:18 isPublic:false isPrivate:true ``` ### 操作字段 在获取到一个字段后,我们就可以对一个对象的该指定字段进行操作 ```java public Object get (Object obj) public void set (Object obj, Object value)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class test { private int a; public test () { a=10 ; } } public class Main { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException { test t=new test (); Field f=t.getClass().getDeclaredField("a" ); f.setAccessible(true ); System.out.println(f.get(t)); f.set(t,20 ); System.out.println(f.get(t)); } } stdout: 10 20
反射 是获取类内成员信息的一种非常规 用法,可以让我们在不知道一个对象内任何信息(即黑盒状态)的情况下,获取/修改一个特定字段 的值,常用于底层框架中。
此外,有一些Java核心类 的SecurityManager会阻止setAccessible,如String的private final byte[] value
字段,在使用setAccessible(true)时则会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Main { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException { String sArr="123" ; Field f=sArr.getClass().getDeclaredField("value" ); f.setAccessible(true ); System.out.println(f.get(sArr)); } } stderr: Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte [] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @16b98e56 at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:387 ) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:363 ) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:311 ) at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:180 ) at java.base/java.lang.reflect.Field.setAccessible(Field.java:174 ) at Main.main(Main.java:9 )
方法
要注意,通过反射获取的方法永远是遵循多态规则的,即总是优先选择被子类覆写的方法
获取方法 和字段同理,我们也可以通过如下几个方式来获得某个class对象的指定field
1 2 3 4 public Method getMethod (String name, Class...) public Method getDeclaredMethod (String name, Class...) public Method[] getMethods() public Method[] getDeclaredMethods()
在获取到一个方法后,我们可以通过一系列get获取到关于该方法的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Main { public static void main (String[] args) throws NoSuchFieldException, NoSuchMethodException { String sArr="123" ; Method f=sArr.getClass().getMethod("charAt" , int .class); System.out.println("getName:" +f.getName()); System.out.println("getParameterTypes:" +f.getParameterTypes()); System.out.println("getParameterTypes:" + Arrays.toString(f.getParameterTypes())); System.out.println("getReturnType:" +f.getReturnType()); System.out.println("getModifiers:" +f.getModifiers()); System.out.println("isPublic:" +Modifier.isPublic(f.getModifiers())); System.out.println("isPrivate:" +Modifier.isPrivate(f.getModifiers())); } } stdout: getName:charAt getParameterTypes:[Ljava.lang.Class;@27d6c5e0 getParameterTypes:[int ] getType:char getModifiers:1 isPublic:true isPrivate:false
操作方法 我们可以通过invoke调用一个对象的指定方法,同理的,我们也可以通过setAccessible来操作private方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main { public static void main (String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { String sArr="123" ; Method f=sArr.getClass().getMethod("charAt" , int .class); Method m=Integer.class.getMethod("valueOf" , int .class); System.out.println(f.invoke(sArr,0 )); System.out.println(f.invoke(sArr,1 )); System.out.println(f.invoke(sArr,2 )); System.out.println(m.invoke(null ,10 )); } } stdout: 1 2 3 10
实际上,invoke非常接近JVM 实际调用非静态方法的情形,当调用一个非静态方法 时,实际上隐藏了一个参数,即当前对象的引用,当我们执行s.charAt(0)
时,JVM看到的实际上是String.charAt(s,0)
;但是在class文件中,static方法并非传入了一个null ,而是压根就没有 这个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class test { static public int get5 () { return 5 ; } } > javap -c -v test.class ··· public static int get5 () ; descriptor: ()I flags: (0x0009 ) ACC_PUBLIC, ACC_STATIC Code: stack=1 , locals=0 , args_size=0 0 : iconst_5 1 : ireturn LineNumberTable: line 8 : 0 } ···
构造器
获取构造器 1 2 3 4 public getConstructor (Class...) public getDeclaredConstructor (Class...) public getConstructors () public getDeclaredConstructors ()
而public newInstance())
这个获取零参构造器的方法在Java9中已经被弃用了
1 2 3 4 @Deprecated(since = "9") @NotNull public T newInstance () throws InstantiationException, IllegalAccessException
调用构造器 我们可以通过newInstance来调用构造器并实例化一个新的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Main { public static void main (String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { String sArr="123" ; Constructor c=sArr.getClass().getConstructor(String.class); System.out.println(sArr); String sAnother= (String) c.newInstance("456" ); System.out.println(sAnother); } } stdout: 123 456