本菜鸡之前只是听过这几个概念,但是具体是做什么的却不太明白,因此对这几个概念进行了学习,并记录做区分理解,如果您发现其中的错误,请指出,谢谢~
1、反射的概述
Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对任意一个对象,都能够调用他的任意方法和属性;这种 “动态获取信息以及动态调用对象的属性方法” 的功能就是Java的反射机制。
Java文件编译后产生字节码文件 .class,类加载器通过二进制流,从文件系统中加载class文件,在执行程序的时候,将字节码文件读到JVM中,然后在自动在内存中创建一个java.lang.Class对象(一个类只会产生一个Class对象),这个对象会被放入到字节码信息中,这个Class对象就对应加载的字节码信息,这个对象将被作为程序访问方法区中这个类各种数据的一个接口。
那么,反射其实就是通过类的Class对象,反向获取这个类创建的对象的各种信息。
说明:在运行期间如果要产生某个类的对象,JVN会检查该类的Class对象是否已经被加载,如果没有被加载,会根据类的名称找到对应的.class 文件并加载,一旦某个类的Class对象已经被加载,就可以用他来产生该类型的所有对象。
2、Class类
其实也就是利用面向对象的思维将其他的类向上抽取,提炼成的一个类。
如:student类中有他的属性、方法等,computer类也有他的属性、方法等,将他们抽取,形成一个Class类,那么通过Class 类 可以创建 student实例,然后通过student实例对象获得他相应的属性或者方法。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
3、获取Class对象的四种方式
最常用的就是利用Class类的静态方法 forName(“包名加类名组成的字符串”)
package fanshe;
/**
* 获取Class对象的四种方式
*/
public class testFanshe {
public static void main(String[] args) throws ClassNotFoundException {
//第一种方式 ----> 已经有了对象,就不需要进行反射获取对象了,一般不用
Student student = new Student();
Class stuClass1 = student.getClass();
System.out.println(stuClass1);
//第二种方式 ----> 需要导包,依赖性强,不导包会编译报错
Class stuClass2 = Student.class;
System.out.println(stuClass2);
//第三种方式 ----> 最常用的方式,可以传入字符串(包名加类名),也可以写在配置文件中调用配置之后的字符串
Class stuClass3 = Class.forName("fanshe.Student");
System.out.println(stuClass3);
//第四种方式----->类的加载器(了解即可)
ClassLoader classLoader = testFanshe.class.getClassLoader();
Class stuClass4 = classLoader.loadClass("fanshe.Student");
System.out.println(stuClass4);
// 可以看出,三种方法创建出来的Class 对象是同一个对象
System.out.println(stuClass1 == stuClass2);
System.out.println(stuClass2 == stuClass3);
System.out.println(stuClass3 == stuClass4);
}
}
调用了无参的构造方法
class fanshe.Student
class fanshe.Student
class fanshe.Student
class fanshe.Student
true
true
true
4、Class类可以构造的具体实例有哪些?
接口,类(内部类,外部类),注解,数组,基本数据类型,返回值等
package fanshe;
public class testInstance {
public static void main(String[] args) {
Class c1 = Student.class; // 类
Class c2 = Runnable.class; // 接口
Class c3 = Override.class; // 注解
Class c4 = int.class; // 基本数据类型
Class c5 = void.class; // 返回值类型
int[] arr1 = {1, 2, 3};
int[] arr2 = {3, 4, 5, 6, 7, 8};
Class c6 = arr1.getClass(); // 数组, 数组只能用 getClass()获取
Class c7 = arr2.getClass();
System.out.println(c6 == c7); // true ,同一个维度,同一个元素类型,得到的Class对象就是同一个
}
}
5、通过反射获取构造方法
首先创建一个Student 方法,包含六个构造器,其中三个public;从代码里可以看出:
- getConstructors()方法只能获取到public修饰的构造器
- getDeclaredConstructors() 可以获取到所有的构造器,包括private类型
- getConstructor(Class<?>... parameterTypes):可变参数构造器,只能是public类型的
- 传入一个null值/或者不传,可以获取到无参构造器(注意不带s,无参构造器只有一个)
- 传入对应的parameterTypes,可以获取到相应的构造器
- getDeclaredConstructor(Class<?>... parameterTypes):可以获取到非public类型的构造器
- 注意调用私有构造器的时候,要调用declaredConstructor.setAccessible(true);这个方法,不然会有异常。
package fanshe;
import java.lang.reflect.Constructor;
public class testConstructor {
public static void main(String[] args) throws Exception {
// 加载Class对象,字符串传值应为真是路径,包名.类名
Class clazz = Class.forName("fanshe.Student");
// 获取构造方法
System.out.println("****************公有的构造方法***********************");
Constructor[] constructors = clazz.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
System.out.println("****************所有的构造方法***********************");
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println(c);
}
System.out.println("****************公有,无参构造方法***********************");
Constructor constructor = clazz.getConstructor(null); //注意方法调用是getConstructor 不是 getConstructors
System.out.println(constructor);
Object o = constructor.newInstance();// 通过newInstance()方法创建对象 --->构造方法调用,对象创建成功
System.out.println("****************公有,有参数构造方法***********************");
Constructor constructor1 = clazz.getConstructor(char.class);
System.out.println(constructor1);
Object o3 = constructor1.newInstance('男');
System.out.println("****************protected,有参数构造方法***********************");
Constructor constructor2 = clazz.getDeclaredConstructor(boolean.class); //注意调用方法
System.out.println(constructor2);
Object o1 = constructor2.newInstance(true);
System.out.println("****************private,有参数构造方法***********************");
Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
declaredConstructor.setAccessible(true); //不调用这个方法,会抛出异常信息。java.lang.IllegalAccessException
Object o2 = declaredConstructor.newInstance(18);
}
}
使用类:
package fanshe;
public class Student {
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + ", phoneNum='" + phoneNum + '\''
+ '}';
}
//定义了六个构造方法,三个公有
public Student() {
System.out.println("调用了无参的构造方法");
}
Student(String str) {
System.out.println("默认的构造方法+" + str);
}
public Student(char name) {
System.out.println("调用了char的构造方法 +" + name);
}
public Student(String name, int age) {
System.out.println("多个参数的构造方法 : 姓名:" + name + "年龄:" + age);
}
protected Student(boolean n) {
System.out.println("受保护的构造方法 n = " + n);
}
private Student(int age) {
System.out.println("私有的构造方法 年龄:" + age);
}
//**************成员方法***************//
public void show1(String s) {
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2() {
System.out.println("调用了:受保护的,无参的show2()");
}
void show3() {
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age) {
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
public static void show5() {
System.out.println("静态方法执行了");
}
}
6、通过反射获取属性
Student类里面定义了四个属性:
- getFields():获取public中修饰的属性,包含父类的public属性
- getDeclaredFields():获取的运行时类的所有属性
- getField(String name):获取指定的public属性,name为属性的命名
- getDeclaredField(String name):获取指定的属性,name为属性的命名
- 私有属性要setAccessible方法,并赋值为true
- 给属性设置的时候,必须要有对象。
package fanshe;
import java.lang.reflect.Field;
public class testFileds {
public static void main(String[] args) throws Exception {
//1 获取Class 对象
Class stuClass = Class.forName("fanshe.Student");
System.out.println("******************获取所有的公共字段****************");
//2 获取所有的公共字段 只有一个name
Field[] fields = stuClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("******************获取所有字段,包括私有****************");
//3 获取所有字段,包括私有
Field[] declaredFields = stuClass.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
System.out.println("*************获取公有字段**并调用************************");
Field name = stuClass.getField("name");
System.out.println(name);
// 创建一个对象
Student o = (Student) stuClass.getConstructor().newInstance();
// 给对象的name 属性赋值
name.set(o, "zhangSan");
System.out.println(o.name);
System.out.println("*************获取私有字段**并调用************************");
Field age = stuClass.getDeclaredField("age");
age.set(o, 18);
Field sex = stuClass.getDeclaredField("sex");
sex.set(o, 'M');
Field phoneNum = stuClass.getDeclaredField("phoneNum");
phoneNum.setAccessible(true); // 私有属性要setAccessible方法,并赋值为true
phoneNum.set(o, "1234687848");
System.out.println(o.toString());
}
}
7、通过反射获取方法
Student类里面定义了五个方法,四种类型一样一个,和一个static类的
- getMethods():获取类中所有公共方法,包括父类,和继承的接口
- getDeclaredMethods():获取类中声明的所有方法,只包含本类的声明方法,不包括构造方法
- getMethod(String name, Class<?>... parameterTypes):第一个参数是方法名,第二个是参数类型
- getDeclaredMethod(String name, Class<?>... parameterTypes):第一个参数是方法名,第二个是参数类型
package fanshe;
import java.lang.reflect.Method;
public class testMethod {
public static void main(String[] args) throws Exception {
// 获取Class 对象
Class stu = Class.forName("fanshe.Student");
System.out.println("***************获取所有的”公有“方法*******************");
System.out.println("***************包含继承过来的公共方法*******************");
// 获取所有的公共方法
Method[] methods = stu.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("***************获取所有的”声明“方法*******************");
System.out.println("***************只包含本类的声明方法,不包括构造方法*******************");
Method[] declaredMethods = stu.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
System.out.println("***************获取公共方法show1*******************");
Method show1 = stu.getMethod("show1", String.class);
System.out.println(show1);
Student o = (Student) stu.getConstructor().newInstance();
// o.show1("sss");
show1.invoke(o, "zhangSan");
System.out.println("***************获取私有方法show4*******************");
Method show4 = stu.getDeclaredMethod("show4", int.class);
System.out.println(show4);
show4.setAccessible(true); // 通过这个设置可以调用私有方法
show4.invoke(o, 18);
Method show5 = stu.getDeclaredMethod("show5");
show5.invoke(null, null);// 因为方法是静态的,所以第一个传参可以为null,因为是无参函数,所以第二个参数为null
}
}
8、通过反射获取其他的信息
- Class superClass = stuClass1.getSuperclass(); //调用父类的信息
- Package aPackage = stuClass1.getPackage(); // 调用当前类的包
- Annotation[] annotations = stuClass1.getAnnotations(); //调用当前类的注解,只能获取RUNTIME类型的注解,不能获取SOURCE 类型的
- Class[] interfaces = stuClass1.getInterfaces();//调取当前类的接口
9、反射是否破坏了面向对象的封装性?
封装是指对外隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式,私有的方法其实是为了让其他的类无法直接使用这个方法,因为可能会实现不了想要的功能;通过反射,虽然是可以获取私有属性和私有方法的,但是,通过反射调用私有方法后,这个方法仍然是私有的,仍然在子类中不可见,从这个角度而言,其实封装性并没有被破坏,而且在写代码时也没有必要去反射私有方法。(仅仅展示了反射功能的强大)
10、反射创建对象的效率高还是new 创建对象的效率高?
通过new 创建的效率高,因为通过反射创建对象时,首先要查找类资源,使用类加载器,过程相对繁琐,效率低。
11、反射的优缺点?
- 优点:反射可以动态的创建对象和编译,最大程度的发挥了Java的灵活性
- 缺点:对性能有影响,通过反射创建对象时,首先要查找类资源,使用类加载器,过程相对繁琐,效率低。
12、哪些地方会用到反射机制?
- JDBC连接数据库的时候使用Class.forName()通过反射加载数据库的驱动程序;
- Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:
- 将程序内所有 XML 或 Properties 配置文件加载入内存中;
- Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;
- 使用反射机制,根据这个字符串获得某个类的Class实例;
- 动态配置实例的属性;
13、对象流,ObjectInputStream 和 ObjectOutputStream
用于存储和读取基本数据类型或者对象的处理流,他的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
java中的ObjectInputStream和ObjectOutputStream的使用_我自是年少韶华倾负的博客-CSDN博客
14、Java中的序列化和反序列化
序列化原版意图是希望对一个Java对象做一下“变换”,变成字节序列,这样一来方便持久化存储到磁盘中,避免程序运行结束后对象就从内存中消失,另一个就是变成字节序列也更加有利于在网络中的传输。
- 序列化:将Java对象转换为字节序列
- 反序列化:将字节序列恢复为原来的Java对象
说到序列化,就会引出Serializable接口,如果不实现Serializable接口,那么就会抛出一个NotSerializableException;但是其实Serializable接口是一个空的接口,没有任何的实现,因为Serializable接口其实只是作为一个标记,他告诉代码,只要是实现了Serializable接口的类都是可以被序列化的,然而真正的序列化动作是不需要靠它来完成。
15、serialVersionUID
假设现在有一种场景:有一个person类,创建一个person对象,然后对他进行序列化,将对象写入到文件中,然后反序列化的时候发现person类中没有重写toString方法,不符合要求,因此在person类中添加toString方法,再次对之前序列化的文件进行反序列化,发现抛出一个异常:InvalidCastException;
解决方法,就是在类中添加一个序列号,serialVersionUID,序列化版本标识符静态常量。
- private static final long serialVersionUID=xxxx;
- serialVersionUID表示类的不同版本之间的兼容性,换句话说,他的目的就是以序列化对象进行版本控制,判断各个版本反序列化时是否兼容
- 如果没有显示定义这个静态变量,他的值是Java运行时环境根据类的内部细节自动生成的,如果类的实例变量做了修改,serialVersionUID就有可能发生变化,因此要显示的声明。
- 简单来说,Java序列化的机制就是通过在运行时判断serialVersionUID来验证版本的一致性的,反序列化的时候,JVM会把传来的字节流中的serialVersionUID鱼本地相应的实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)
16、不会被序列化的两种特殊情况
- 被static修饰的字段:序列化保存的是对象的状态而不是类的状态,因此会忽略掉static静态域
- 被transient修饰的字段:序列化某个类对象的时候,如果不希望某个字段被序列化(如密码等),就可以用transient修饰符进行修饰,比如 private transient String password;那么序列化类对象的时候,password字段就会设置为默认值null。
17、序列化的一些细节
- 被序列化的类的内部所有属性都必须是可序列化的(基本数据类型是可序列化的,如果类中包含了其他类,那么被包含的类也要实现Serializable接口)
- static,transient修饰的字段不可序列化(见16)