目录
类加载器机制
反射机制
java.lang.Class:
java.lang.reflect.Method:
可变长度参数
java.lang.reflect.Constructor:
java.lang.reflect.Field:
加载资源文件
文件路径的问题
加载文件
资源绑定器
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实例?
三种方式:
第一种: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(); }
代表字节码中的方法字节码。代表类中的方法。
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对象。
创建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对象的属性?
给属性赋值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必须放到类路径下,并且在写路经的时候,路径后面的扩展名不能写。