目录
Class c = Class.forName("完整类名带包名");
反射机制概述
比较简单,因为只要会查帮助文档,就可以了。
1、反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。
优点类似于黑客。(可以读和修改字节码文件。)
通过反射机制可以操作代码片段。(class文件。)
2、反射机制的相关类在哪个包下?
java.lang.reflect.*;
3、反射机制相关的重要的类有哪些?
java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
例如:
java.lang.Class:
public class User{
// Field
int no;// Constructor
public User(){
}
public User(int no){
this.no = no;
}// Method
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
}
获取类的字节码
一共三种方法
第一种:Class c = Class.forName("完整类名带包名");
第二种:Class c = 对象.getClass();
第三种:Class c = 任何类型.class;
Class c = Class.forName("完整类名带包名");
Class.forName()
1、静态方法
2、方法的参数是一个字符串。
3、字符串需要的是一个完整类名。
4、完整类名必须带有包名。java.lang包也不能省略。
try {
Class c1 = Class.forName("java.lang.String"); // c1代表String.class文件,或者说c1代表String类型。
Class c2 = Class.forName("java.util.Date"); // c2代表Date类型
Class c3 = Class.forName("java.lang.Integer"); // c3代表Integer类型
Class c4 = Class.forName("java.lang.System"); // c4代表System类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class c = 对象.getClass();
java中任何一个对象都有一个方法:getClass()
Class c1 = null;
try{
c1 = Class.forName("java.lang.String");
}catch(ClassNotFoundException){
e.printStackTrace();
}
String s = "abc";
Class x = s.getClass(); // x代表String.class字节码文件,x代表String类型。
System.out.println(c1 == x); // true(==判断的是对象的内存地址。)
Class c = 任何类型.class;
Class z = String.class; // z代表String类型
Class k = Date.class; // k代表Date类型
Class f = int.class; // f代表int类型
Class e = double.class; // e代表double类型
System.out.println(x == z); // true
通过反射实例化对象
这是不使用反射机制,创建对象
User user = new User();
System.out.println(user);
下面这段代码是以反射机制的方式创建对象。
try {
// 通过反射机制,获取Class,通过Class来实例化对象
Class c = Class.forName("lit.jxxy.rg.java.bean.User"); // c代表User类型。
// newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
// 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
Object obj = c.newInstance();
System.out.println(obj); // lit.jxxy.rg.java.bean.User@10f87f48
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
注意:
通过Class的newInstance()方法来实例化对象。
newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
----------验证反射机制的灵活性。
首先需要创建一个配置文件,例如:classinfo.properties
##文件内容
className = java.util.Date
通过IO流读取classinfo.properties文件
以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
FileReader reader = new FileReader("chapter25/classinfo2.properties");
// 创建属性类对象Map
Properties pro = new Properties(); // key value都是String
// 加载
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
//System.out.println(className);
// 通过反射机制实例化对象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常之灵活。(符合OCP开闭原则:对扩展开放,对修改关闭。)
后期要学习高级框架,而工作过程中,也都是使用高级框架,
包括: ssh ssm
Spring SpringMVC MyBatis
Spring Struts Hibernate
...
这些高级框架底层实现原理:都采用了反射机制。所以反射机制还是重要的。
学会了反射机制有利于理解剖析框架底层的源代码。
研究Class.forName()发生了什么
public static void main(String[] args) {
try {
// Class.forName()这个方法的执行会导致:类加载。
Class.forName("lit.jxxy.rg.java.reflect.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
class MyClass {
static{
System.out.println("MyClass静态代码块执行!");
}
}
记住,重点:
如果你只是希望一个类的静态代码块执行,其它代码一律不执行,
你可以使用:
Class.forName("完整类名");
这个方法的执行会导致类加载,类加载时,静态代码块执行。
提示:
后面JDBC技术的时候我们还需要。
文件路径
怎么获取一个文件的绝对路径。以下讲解的这种方式是通用的。但前提是:文件需要在类路径下。才能用这种方式。
FileReader reader = new FileReader("chapter25/classinfo2.properties");
这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根。
这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了。
接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。
注意:使用以下通用方式的前提是:这个文件必须在类路径下。
什么类路径下?方式在src下的都是类路径下。【记住它】
src是类的根路径。
String path = Thread.currentThread().getContextClassLoader()
.getResource("lit/jxxxy/ry15/java/thread/threadsafe/demo.properties").getPath();
System.out.println(path);
///D:/Sen%e6%a3%ae/Java/IDEA%20Java/javase/out/production/Thread/lit/jxxxy/ry15/java/thread/threadsafe/demo.properties
解释:
Thread.currentThread() 当前线程对象
getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
修改IO流的方式获取路径
// 获取一个文件的绝对路径了!!!!!
/*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);
资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下。
资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties
并且在写路径的时候,路径后面的扩展名不能写。
ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");
String className = bundle.getString("className");
System.out.println(className);
类加载器(了解)
1、什么是类加载器?
专门负责加载类的命令/工具。
ClassLoader
2、JDK中自带了3个类加载器
启动类加载器:rt.jar
扩展类加载器:ext/*.jar
应用类加载器:classpath
3、假设有这样一段代码:
String s = "abc";
代码在开始执行之前,会将所需要类全部加载到JVM当中。
通过类加载器加载,看到以上代码类加载器会找String.class
文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载。
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,
会通过"扩展类加载器"加载。
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\*.jar
如果“扩展类加载器”没有加载到,那么
会通过“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的类。
4、java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”
“父”无法加载到,再从扩展类加载器中加载,
这个称为“母”。双亲委派。如果都加载不到,
才会考虑从应用类加载器中加载。直到加载
到为止。
获取Field(了解)
代码展示:
// 获取整个类
Class studentClass = Class.forName("lit.jxxy.java.bean.Student");
//lit.jxxy.java.bean.Student
String className = studentClass.getName();
System.out.println("完整类名:" + className);
String simpleName = studentClass.getSimpleName();
System.out.println("简类名:" + simpleName);
// 获取类中所有的public修饰的Field
Field[] fields = studentClass.getFields();
System.out.println(fields.length); // 测试数组中只有1个元素
// 取出这个Field
Field f = fields[0];
// 取出这个Field它的名字
String fieldName = f.getName();
System.out.println(fieldName);
// 获取所有的Field
Field[] fs = studentClass.getDeclaredFields();
System.out.println(fs.length); // 4
System.out.println("==================================");
// 遍历
for(Field field : fs){
// 获取属性的修饰符列表
int i = field.getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号!!!
System.out.println(i);
// 可以将这个“代号”数字转换成“字符串”吗?
String modifierString = Modifier.toString(i);
System.out.println(modifierString);
// 获取属性的类型
Class fieldType = field.getType();
//String fName = fieldType.getName();
String fName = fieldType.getSimpleName();
System.out.println(fName);
// 获取属性的名字
System.out.println(field.getName());
}
通过反射机制,反编译一个类的属性Field(了解一下)
// 创建这个是为了拼接字符串。
StringBuilder s = new StringBuilder();
//Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
Class studentClass = Class.forName("java.lang.Thread");
s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
Field[] fields = studentClass.getDeclaredFields();
for(Field field : fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
通过反射属性给对象赋值
怎么通过反射机制访问一个java对象的属性?
给属性赋值set
获取属性的值get
我们不使用反射机制,怎么去访问一个对象的属性呢?
Student s = new Student();
// 给属性赋值
s.no = 1111; //三要素:给s对象的no属性赋值1111
//要素1:对象s
//要素2:no属性
//要素3:1111
// 读属性值
// 两个要素:获取s对象的no属性的值。
System.out.println(s.no);
使用反射机制,怎么去访问一个对象的属性。(set get)
Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法)
// 获取no属性(根据属性的名称来获取Field)
Field noFiled = studentClass.getDeclaredField("no");
// 给obj对象(Student对象)的no属性赋值
/*
虽然使用了反射机制,但是三要素还是缺一不可:
要素1:obj对象
要素2:no属性
要素3:2222值
注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
*/
noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222
// 读取属性的值
// 两个要素:获取obj对象的no属性的值。
System.out.println(noFiled.get(obj));
可以访问私有的属性吗?
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
// 这样设置完之后,在外部也是可以访问private的。
nameField.setAccessible(true);
// 给name属性赋值
nameField.set(obj, "jackson");
// 获取name属性的值
System.out.println(nameField.get(obj));
可变长度参数
int... args 这就是可变长度参数
语法是:类型... (注意:一定是3个点。)
1、可变长度参数要求的参数个数是:0~N个。
public static void main(String[] args) {
m();
m(10);
m(10, 20);
}
public static void m(int... args){
System.out.println("m方法执行了!");
}
2、可变长度参数在参数列表中必须在最后一个
public static void m2(int... args2, String... args1){ } //编译报错
必须在最后,只能有1个。
public static void main(String[] args){
m2(100);
m2(200, "abc");
m2(200, "abc", "def");
m2(200, "abc", "def", "xyz");
}
public static void m2(int a, String... args1){
}
3、可变长度参数可以当做一个数组来看待
public static void m3(String... args){
//args有length属性,说明args是一个数组!
// 可以将可变长度参数当做一个数组来看。
for(int i = 0; i < args.length; i++){
System.out.println(args[i]);
}
}
//TEST
m3("ab", "de", "kk", "ff");
String[] strs = {"a","b","c"};
// 也可以传1个数组
m3(strs);