获得Class类的实例的几种方法:如有一个Person类,这个类有个实例为person实例
1、若已知具体的类,通过类的class属性获取,该方法最安全可靠,程序性能最高
1Class cls1 = Person.class;
2、已知某个类的实例,调用该实例的getClass()方法获取Class对象
1Class cls2 = person.getClass();
3、已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundExcetion
1Class cls3 = Class.forName("com.wick.demo1.Student"); //前面为该类的存放路径
4、内置基本数据类型可以直接用类名.Type
1Class cls4 = Integer.TYPE;
5、还可以利用ClassLoader
反射的目的是为了获得某个实例的信息。因此,当我们拿到某个Object
实例时,我们可以通过反射获取该Object
的class
信息
1
2
3void printObjectInfo(Object obj) { Class cls = obj.getClass(); }
要从Class
实例获取获取的基本信息,参考下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Main { public static void main(String[] args) { printClassInfo("".getClass()); printClassInfo(Runnable.class); printClassInfo(java.time.Month.class); printClassInfo(String[].class); printClassInfo(int.class); } static void printClassInfo(Class cls) { System.out.println("Class name: " + cls.getName()); System.out.println("Simple name: " + cls.getSimpleName()); if (cls.getPackage() != null) { System.out.println("Package name: " + cls.getPackage().getName()); } System.out.println("is interface: " + cls.isInterface()); System.out.println("is enum: " + cls.isEnum()); System.out.println("is array: " + cls.isArray()); System.out.println("is primitive: " + cls.isPrimitive()); } }
运行结果:
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
26
27
28
29
30
31
32
33Class name: java.lang.String Simple name: String Package name: java.lang is interface: false is enum: false is array: false is primitive: false Class name: java.lang.Runnable Simple name: Runnable Package name: java.lang is interface: true is enum: false is array: false is primitive: false Class name: java.time.Month Simple name: Month Package name: java.time is interface: false is enum: true is array: false is primitive: false Class name: [Ljava.lang.String; Simple name: String[] is interface: false is enum: false is array: true is primitive: false Class name: int Simple name: int is interface: false is enum: false is array: false is primitive: true
如果获取到了一个Class
实例,我们就可以通过该Class
实例来创建对应类型的实例:
1
2
3
4// 获取String的Class实例: Class cls = String.class; // 创建一个String实例: String s = (String) cls.newInstance();
上述代码相当于new String()
。通过Class.newInstance()
可以创建类实例,它的局限是:只能调用public
的无参数构造方法。带参数的构造方法,或者非public
的构造方法都无法通过Class.newInstance()
被调用。
动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:
1
2
3
4
5
6
7
8
9
10
11
12// Main.java public class Main { public static void main(String[] args) { if (args.length > 0) { create(args[0]); } } static void create(String name) { Person p = new Person(name); } }
当执行Main.java
时,由于用到了Main
,因此,JVM首先会把Main.class
加载到内存。然而,并不会加载Person.class
,除非程序执行到create()
方法,JVM发现需要加载Person
类时,才会首次加载Person.class
。如果没有执行create()
方法,那么Person.class
根本就不会被加载。
获取字段值
利用反射拿到字段的一个Field
实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
例如,对于一个Person
实例,我们可以先拿到name
字段对应的Field
,再获取这个实例的name
字段的值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// reflection import java.lang.reflect.Field; public class Main { public static void main(String[] args) throws Exception { Object p = new Person("Xiao Ming"); Class c = p.getClass(); Field f = c.getDeclaredField("name"); Object value = f.get(p); System.out.println(value); // "Xiao Ming" } } class Person { public String name; //如果是private就不行,如果一定要用,就在调用Object value = public Person(String name) { // f.get(p);前,先写一句: this.name = name; //f.setAccessible(true); } }
有童鞋会问:如果使用反射可以获取private
字段的值,那么类的封装还有什么意义?
答案是正常情况下,我们总是通过p.name
来访问Person
的name
字段,编译器会根据public
、protected
和private
决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
此外,setAccessible(true)
可能会失败。如果JVM运行期存在SecurityManager
,那么它会根据规则进行检查,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不允许对java
和javax
开头的package
的类调用setAccessible(true)
,这样可以保证JVM核心库的安全。
substring() 方法返回字符串的子字符串。
1
2
3
4
5public String substring(int beginIndex) 或 public String substring(int beginIndex, int endIndex)
调用方法
当我们获取到一个Method
对象时,就可以对它进行调用。我们以下面的代码为例:
1
2String s = "Hello world"; String r = s.substring(6); // "world"
如果用反射来调用substring
方法,需要以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// reflection import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // String对象: String s = "Hello world"; // 获取String类的 substring(int)方法,参数为int: Method m = String.class.getMethod("substring", int.class); // 在s对象上调用该方法并获取结果: String r = (String) m.invoke(s, 6); // 打印调用结果: System.out.println(r); //输出 world System.out.println(s); //输出 Hello world } }
当我们获取到某个Class
对象时,实际上就获取到了一个类的类型:
1
2Class cls = String.class; // 获取到String的Class
还可以用实例的getClass()
方法获取:
1
2
3String s = ""; Class cls = s.getClass(); // s是String,因此获取到String的Class
最后一种获取Class
的方法是通过Class.forName("")
,传入Class
的完整类名获取:
1
2Class s = Class.forName("java.lang.String");
这三种方式获取的Class
实例都是同一个实例,因为JVM对每个加载的Class
只创建一个Class
实例来表示它的类型。
动态代理
我们来看静态代码怎么写:
定义接口:
1
2
3
4public interface Hello { void morning(String name); }
编写实现类:
1
2
3
4
5
6public class HelloWorld implements Hello { public void morning(String name) { System.out.println("Good morning, " + name); } }
创建实例,转型为接口并调用:
1
2
3Hello hello = new HelloWorld(); hello.morning("Bob");
这种方式就是我们通常编写代码的方式。
还有一种方式是动态代码,我们仍然先定义了接口Hello
,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()
创建了一个Hello
接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
一个最简单的动态代理实现如下:
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
26
27import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning")) { System.out.println("Good morning, " + args[0]); } return null; } }; Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), // 传入ClassLoader new Class[] { Hello.class }, // 传入要实现的接口 handler); // 传入处理调用方法的InvocationHandler hello.morning("Bob"); } } interface Hello { void morning(String name); }
最后
以上就是含糊白云最近收集整理的关于JAVA的反射学习的全部内容,更多相关JAVA内容请搜索靠谱客的其他文章。
发表评论 取消回复