1.反射机制有什么用?
通过Java语言中的反射机制可以操作字节码文件(读和修改字节码文件),类似于黑客。通过反射机制可以操作代码片段。
2.反射机制的相关类在哪个包下?java.lang.reflect.*
3.反射机制相关的重要的类有哪些?
java.lang.Class:代表字节码文件;
java.lang.reflect.Method:代表字节码中的方法字节码;
java.lang.reflect.Constructor:代表字节码中的构造方法字节码;
java.lang.reflect.Field:代表字节码中的属性字节码。
4.操作一个类的字节码,首先需要获取到这个类的字节码,怎么获取java.lang.Class实例呢?
(1)第一种方式:Class c = Class.forName(“完整类名带包名”);
/*
Class.forName()
1.静态方法
2.方法的参数是一个字符串
3.字符串需要的是一个完整类名
4.完整类名必须带有包名
*/
public class ReflectTest {
public static void main(String[] args) {
try {
Class c1 = Class.forName("java.lang.String"); //c1代表String.class文件,或者说c1代表String类型
Class c2 = Class.forName("java.util.Date"); //c2代表Date.class文件
Class c3 = Class.forName("java.lang.Integer"); //c3代表Integer.class文件
Class c4 = Class.forName("java.lang.System"); //c4代表System.class文件
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
(2)第二种方式:Class c = 对象.getClass();
java中任何一个对象都有一个方法:getClass()。
String s = "abc";
Class x = s.getClass();
//System.out.println(c1==x); 结果为true
Date t = new Date();
Class y = t.getClass();
//System.out.println(c2==y); 结果为true
(3)java语言中任何一种类型,包括基本数据类型,都有.class属性。
Class z = String.class;
//System.out.println(x==z); 结果为true
Class k = Date.class;
//System.out.println(y==k); 结果为true
Class f = int.class;
Class e = double.class;
5.获取到Class之后,能干什么?
通过反射机制,获取Class,通过Class来实例化对象。
package com.lilanlan.java.reflect;
public class ReflectTest01 {
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("com.lilanlan.java.bean.User");
Object obj = c.newInstance(); //newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建。
System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
package com.lilanlan.java.bean;
public class User {
public User() {
System.out.println("User的无参构造方法执行");
}
}
注意:newInstance()方法内部实际上调用了无参构造方法,因此必须保证无参构造方法存在!
6.为什么要使用反射创建对象呢?反射更加灵活!
假设在模块目录下创建一个文件classinfo.properties,在文件中输入className=com.lilanlan.java.bean.User
package com.lilanlan.java.bean;
public class User {
public User() {
System.out.println("User的无参构造方法执行");
}
}
package com.lilanlan.java.reflect;
import java.io.FileReader;
import java.util.Properties;
public class ReflectTest02 {
public static void main(String[] args) throws Exception{
FileReader reader = new FileReader("ch01test/classinfo.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
String className = pro.getProperty("className");
Class c = Class.forName(className);
Object o = c.newInstance();
System.out.println(o);
}
}
这样来看的话,通过反射机制创建对象,可以在不改变Java源代码的基础之上,做到不同对象的实例化。 符合OCP原则:对扩展开放,对修改关闭。
7.执行Class.forName()会发生什么?
package com.lilanlan.java.reflect;
public class ReflectTest03 {
public static void main(String[] args) {
try {
Class.forName("com.lilanlan.java.reflect.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
static {
System.out.println("静态代码块执行!");
}
}
//运行ReflectTest03,结果输出:静态代码块执行!
这说明,Class.forName()这个方法的执行会导致:类加载。 如果只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(“完整类名带包名”);
8.一种比较通用的路径使用方式
这种路径即使在代码更换位置时也通用,但是该方法的使用前提是文件必须在类路径下(src是类的根路径)。
String path1 = Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();
System.out.println(path1);
String path2 = Thread.currentThread().getContextClassLoader().getResource("com/lilanlan/java/bean/db.properties").getPath();
System.out.println(path2);
/*
【解释】
Thread.currentThread():当前线程对象。
getContextClassLoader():线程对象的方法,可以获取到当前线程的类加载器对象。
getResource():类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
*/
//结果输出:/D:/software/JetBrains/IdeaProjects/proxy-test/out/production/ch01test/classinfo2.properties
采用以上代码可以拿到一个文件的绝对路径。
9.以流的形式直接返回
/*
String path = Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();
FileReader reader = new FileReader(path);
*/
//直接以流的形式返回
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo2.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
10.资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。使用这种方式的时候,属性配置文件xxx.properties必须放在类文件下。
ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className");
System.out.println(className);
属性配置文件扩展名必须是.properties,并且在写路径的时候,路径后面的扩展名不能写。
11.关于JDK中自带的类加载器
JDK中自带了三个类加载器:启动类加载器、扩展类加载器、应用(系统)类加载器。
假设有这样一段代码:String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,以上代码类加载器会找到String.class文件,找到就加载,那么怎样进行加载呢?
- 首先通过“启动类加载器”加载。注意:启动类加载器专门加载java\jdk1.8.0_101\jre\lib\rt.jar
(rt.jar中都是JDK最核心的类库) - 如果通过“启动类加载器”加载不到的时候,会通过“扩展类加载器”加载。注意:扩展类加载器专门加载java\jdk1.8.0_101\jre\lib\ext\*.jar
- 如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。注意:应用类加载器专门加载classpath中的类。
双亲委托机制: java中为了保证类加载的安全,使用了双亲委托加载机制。优先从启动类加载器中加载(父),启动类加载器无法加载到,再从扩展类加载器(母)中加载。如果两者都加载不到,才会考虑从应用类加载器中加载。若这三者都无法加载到,则抛出ClassNotFoundException。
12.Field
(1)获取Field
public class Student {
public int no;
private String name;
protected int age;
boolean sex;
}
获取Student类中的属性:
public class ReflectTest02 {
public static void main(String[] args) throws Exception {
Class studentClass = Class.forName("com.bean.student");
//获取类中所有public修饰的Field
Field[] fs1 = studentClass.getFields();
System.out.println(fs1.length);
System.out.println(fs1[0].getName());
System.out.println("-----------------------");
//获取类中所有的Field
Field[] fs2 = studentClass.getDeclaredFields();
System.out.println(fs2.length);
for(Field f:fs2){
System.out.println(f.getName());
}
}
}
结果输出:
1
no
-----------------------
4
no
name
age
sex
(2)反编译Field
public class ReflectClass03 {
public static void main(String[] args) throws Exception{
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("java.lang.Integer");
s.append(Modifier.toString(studentClass.getModifiers())+" class "+studentClass.getSimpleName()+"{\n");
Field[] fields = studentClass.getDeclaredFields();
for(Field f:fields){
s.append("\t");
s.append(Modifier.toString(f.getModifiers()));
s.append(" ");
s.append(f.getType().getSimpleName());
s.append(" ");
s.append(f.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s.toString());
}
}
结果能输出:
(3)通过反射机制访问对象属性
public class ReflectTest04 {
public static void main(String[] args) throws Exception{
//以前访问对象
Student student = new Student();
student.no = 1111;
System.out.println(student.no);
System.out.println("------------------------");
//通过反射机制访问对象
Class studentClass = Class.forName("com.bean.Student");
Object obj = studentClass.newInstance();
Field nofield = studentClass.getDeclaredField("no");
nofield.set(obj,3187);
System.out.println(nofield.get(obj));
}
}
(4)通过反射机制访问私有属性
//通过反射机制访问私有属性
Class studentClass = Class.forName("com.bean.Student");
Object obj = studentClass.newInstance();
Field namefield = studentClass.getDeclaredField("name");
namefield.setAccessible(true);
namefield.set(obj,"Jackson");
System.out.println(namefield.get(obj));
13.可变长度参数
语法:类型…
public class ArgsTest {
public static void main(String[] args) {
m1(1);
m1(10,"lll");
m1(10,"111","222");
m1(10,"111","222","333");
m2("zs","ls","ww");
m2(new String[]{"我","是","中","国","人"});
}
public static void m1(int arg1,String... arg2){
System.out.println("m方法执行了!");
}
public static void m2(String... arg){
for(int i=0;i<arg.length;i++){
System.out.println(arg[i]);
}
}
}
注意:
- 可变长度参数要求参数个数可以是0~n个;
- 可变长度参数在参数列表中必须在最后,且可变长度参数在参数列表中只能有一个;
- 可变长度参数可以被当做一个数组来看待,可变长度参数有length属性,可以进行循环遍历;
- 一个数组可以作为可变长度参数的实参传递。
14.Method
(1)获取method
public class UserService {
public boolean login(String name,String password){
if("admin".equals(name) && "123".equals(password)){
return true;
}
return false;
}
public void logout(){
System.out.println("系统已经安全退出!");
}
}
public class ReflectTest06 {
public static void main(String[] args) throws Exception{
Class userServiceClass = Class.forName("com.lilanlan.java.service.UserService");
Method[] methods = userServiceClass.getDeclaredMethods();
System.out.println(methods.length);
for(Method m:methods){
System.out.println(Modifier.toString(m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
Class[] parameterTypes = m.getParameterTypes();
for(Class p:parameterTypes){
System.out.println(p.getSimpleName());
}
}
}
}
结果输出:
(2)使用反射机制调用对象方法
public class ReflectTest07 {
public static void main(String[] args) throws Exception{
//以前访问对象方法
UserService userService = new UserService();
boolean loginSuccess = userService.login("admin","123");
System.out.println(loginSuccess?"登陆成功":"登陆失败");
System.out.println("---------------------------");
//使用反射机制访问对象方法
Class userServiceClass = Class.forName("com.lilanlan.java.service.UserService");
Object obj = userServiceClass.newInstance();
//java中通过方法名和参数列表区分方法
Method loginMethod = userServiceClass.getDeclaredMethod("login",String.class,String.class);
//调用方法的四要素:对象、方法、实参列表、返回值
Object retvalue = loginMethod.invoke(obj,"admin","123");
System.out.println((boolean)retvalue?"登陆成功":"登陆失败");
}
}
结果输出:
15.Constructor
(1)获取constructor
public class ReflectTest08 {
public static void main(String[] args) throws Exception{
Class vipClass = Class.forName("com.lilanlan.java.bean.Vip");
Constructor[] constructors = vipClass.getDeclaredConstructors();
System.out.println(constructors.length);
for(Constructor c:constructors){
System.out.print(Modifier.toString(c.getModifiers())+" ");
System.out.print(vipClass.getSimpleName());
System.out.print("(");
//参数列表
Class[] parameters = c.getParameterTypes();
for(Class p:parameters){
System.out.print(p.getSimpleName()+" ");
}
System.out.println(")\n");
}
}
}
(2)反射机制调用构造方法
public class ReflectTest09{
public static void main(String[] args) throws Exception{
//使用反射机制怎样创建对象呢?
Class vipClass = Class.forName("com.lilanlan.java.bean.Vip");
//调用无参数构造方法
Object obj = vipClass.newInstance();
System.out.println(obj);
System.out.println("----------------------------");
//调用有参数构造方法
//第一步:先获取到这个有参数的构造方法
Constructor con = vipClass.getDeclaredConstructor(int.class,String.class,String.class,boolean.class);
//第二步:调用构造方法new对象
Object newObj = con.newInstance(110,"Jackson","1990-10-11",true);
System.out.println(newObj);
}
}
结果输出: