目录
java.lang.reflect.Constructor:
类加载器机制
1、什么是类加载器?
专门负责类加载的命令/工具。(ClassLoader)
2、JDK中自带3个类加载器
启动类加载器: rt.jar
扩展类加载器: ext/*.jar
应用类加载器: classpath
3、假设有这样一段代码:
String s = "abc";
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载会找到String.class文件,找到就加载
那么是怎么进行加载的呢?
首先,通过“启动类加载器”加载。
注意:启动类加载器专门加载:jdk根路径\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,
会通过“扩展类加载器”加载。
注意:扩展加载器专门加载:jdk根路径\jre\lib\ext\*.jar
如果“扩展类加载器”没有加载到,那么会通知“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的类。
4、java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”,“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载,知道加载到为止。
反射机制
1、反射机制有什么用?
通过java语言中的反射机制可以操作(读和修)字节码文件。
通过反射机制可以操作代码片段。下(class文件。)
2、反射机制的相关类在 java.lang.reflect.*; 包下
3、反射机制相关的重要的类和方法?
java.lang.Class:
代表整个字节码,代表一个类型。
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
三种方式:
第一种:Class c = Class.forName("完整类名带包名");
try {
Class c1 = Class.forName("java.lang.String");
Class c2 = Class.forName("java.util.Date"); // c2代表Date类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
解析:c1代表String.class文件,或者说c1代表String类型。而c2代表Date类型。
第二种:Class c = 对象.getClass();
String s = "abc";
Class x = s.getClass();
Date time = new Date();
Class y = time.getClass();
解析:x代表String.class字节码文件,x代表String类型。
【注意】:String.class 字节码文件对应内存地址是一致的。
System.out.println(c1 == x); // true
附加内存图
第三种:Class c = 任何类型.class;
Class z = String.class; //z代表String类型
Class k = Date.class; // k代表Dare类型
注意:java语言中任何一种类型,包括基本数据类型,他都有.class属性。
获取到class后,可以通过Class的newInstance()方法来实例化对象。
创建User类
public class User {
public User() {
System.out.println("无参构造方法!");
}
}
普通方式创建对象
User user = new User();
System.out.println(user);
使用反射机制创建对象
try {
Class c = Class.forName("test.User"); // c代表User类型
Object obj = c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
【注意】:
newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造存在才可以。
forName()方法参数是类名的全限定名称,简单点 就是包名 + "类名"。
如果你只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用:
Class.forName("完整类名");
这个方法的执行会导致类加载,类加载时,静态代码块执行。
public class MyClass {
// 静态代码块在类加载时执行,并且只执行一次。
static {
System.out.println("MyClass类的静态代码块执行了!");
}
}
【重点】:获取一个类的父类,已经实现了哪些接口
try {
Class stringClass = Class.forName("java.lang.String");
// 获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass);
// 获取String类实现的所有接口(一个类可以实现多个接口)
Class[] interfaces = stringClass.getInterfaces();
for (Class in : interfaces) {
System.out.println(in.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
java.lang.reflect.Method:
代表字节码中的方法字节码。代表类中的方法。
可变长度参数
int... args 这就是可变长度参数
语法是:类型... (注意:一定是3个点。)
1、可变长度参数要求的参数个数是:0~N个。
2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。
public static void m12(int... args2, String... args1){}
上面这样是错误的定义方式。
3、可变长度参数可以当做一个数组来看待。(args有length属性,说明args是一个数组。)
public class ArgsTest {
public static void main(String[] args) {
m2(100);
m2(200, "abc");
m2(200, "abc", "def");
String[] strs = {"a", "b", "c"};
m3(strs);// 也可以传1个数组
// 直接传1个数组
m3(new String[]{"我", "是", "好", "人"}); // 没必要
m3("我", "是", "好", "人");
}
public static void m2(int a, String... args1){
}
public static void m3(String... args){
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
【重要】通过反射机制调用一个对象的方法
反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动,这就是反射机制的好处。
创建UserService类
public class UserService {
public boolean login(String name, String password){
if ("admin".equals(name) && "123".equals(password)){
return true;
}
return false;
}
// java中怎么区分一个方法,依靠方法名和参数列表。
public void login(int i){
}
}
使用一般方式调用对象方法
UserService userService = new UserService();
boolean loginSuccess = userService.login("admin", "123");
System.out.println(loginSuccess ? "登录成功" : "登录失败");
使用反射机制调用
try {
Class userServiceClass = Class.forName("test.UserService");
Object obj = userServiceClass.newInstance();
// 获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
//Method loginMethod = userServiceClass.getDeclaredMethod("login", int.class);
// 调用方法
Object retValue = loginMethod.invoke(obj, "admin", "123");
System.out.println(retValue);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
【记住】:invoke(); 这个方法很重要。
java.lang.reflect.Constructor:
代表字节码中的构造方法字节码。代表类中的构造方法。
通过反射机制调用构造方法实例化java对象。
创建Vip类
public class Vip {
int no;
String name;
String birth;
boolean sex;
public Vip() {
}
public Vip(int no, String name, String birth, boolean sex) {
this.no = no;
this.name = name;
this.birth = birth;
this.sex = sex;
}
@Override
public String toString() {
return "Vip{" +
"no=" + no +
", name='" + name + '\'' +
", birth='" + birth + '\'' +
", sex=" + sex +
'}';
}
}
使用一般方式调用构造方法
Vip v1 = new Vip();
Vip v2 = new Vip(110, "zhangsan", "200-20-11", true);
使用反射机制调用构造方法的步骤
第一步:先获取到这个有参数的构造方法
第二步:调用构造方法new对象
try {
Class c = Class.forName("test.bean.Vip");
// 调用无参数构造方法
Object obj = c.newInstance();
// 调用有参数的构造方法
// 第一步:先获取到这个有参数的构造方法
Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
// 第二步:调用构造方法new对象
Object newObj = con.newInstance(110, "jackson", "199-10-11", true);
System.out.println(newObj);
// 获取无参数构造方法
Constructor con2 = c.getDeclaredConstructor();
Object newObj2 = con2.newInstance();
System.out.println(newObj2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
java.lang.reflect.Field:
代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
通过反射机制访问一个java对象的属性?
给属性赋值set;获取属性的值get
创建Student类
public class Student {
public int no; // Field对象
private String name;
protected int age;
boolean sex;
public static final double MTH_PI = 3.1415926;
}
使用一般方式操作属性
Student s = new Student();
s.no = 1111;
System.out.println(s.no);
使用反射机制
try {
Class studentClass = Class.forName("test.Student");
Object obj = studentClass.newInstance();
// 获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");
noField.set(obj, 2222); // 给obj对象的no属性赋值2222
System.out.println(noField.get(obj));// 读取属性的值
// 访问私有的属性
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装,可能会变得不安全)
// 这样设置完之后,在外部也是可以访问private的。
nameField.setAccessible(true);
nameField.set(obj, "jackson");// 给name属性赋值
System.out.println(nameField.get(obj));// 获取name属性的值
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
注意:反射机制让代码复杂了,但是少了一个“灵活”,这也是值得的。
方法私有的属性,需要打破封装,可能会变得不安全。
加载资源文件
创建classinfo.properties文件
className=java.util.Date
文件路径的问题
获取一个文件的绝对路径
FileReader reader = null;
try {
reader = new FileReader("src/classinfo.properties");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
上面这种方式的路径的缺点是:可移植性差,在IDEA中默认的当前路径是project的根。这个代码假设离开了IDEA。换到其他的位置,可能当前路径就不是project的根了,这时这个路径就无效了。
以下讲解的这种方式是通用的,但前提是:文件需要在类路径下,才能用这种方式。
【注】:src是类的根路径。
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
解析:Thread.currentThread() 当前线程对象
getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载默认从类的根路径下加载资源。
classinfo.properties文件位于src下(从类的根路径下作为起点开始)
加载文件
方式一
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
FileReader reader = null;
Properties pro = new Properties();
try {
reader = new FileReader(path); // 路径有中文会报错。
pro.load(reader);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
方拾二:直接以流的形式返回
InputStream reader = Thread.currentThread()
.getContextClassLoader().getResourceAsStream("classinfo.properties");
Properties pro = new Properties();
try {
pro.load(reader);
} catch (IOException e) {
e.printStackTrace();
}
// 通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className");
System.out.println(className);
使用以上这种方式的时候,属性配置文件xxx.properties必须放到类路径下,并且在写路经的时候,路径后面的扩展名不能写。