序列化
把Java对象转换为字节序列的过程
对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。
反序列化
把字节序列恢复为Java对象的过程
客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象
实现Serializable接口的类支持序列化操作
eg:
首先准备一个JavaBean类
public class Student implements Serializable {
private String name;
private int age;
private String gender;
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
}
实现序列化
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class ObjectOutputStreamDemo {
public static void main(String[] args) {
// 创建一个Student对象
Student student = new Student("Tom", 18, "male");
// 序列化对象
try {
FileOutputStream fos = new FileOutputStream("student.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(student);
oos.close();
fos.close();
System.out.println("Student对象已经被序列化到student.ser文件中");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化对象
try {
FileInputStream fis = new FileInputStream("student.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Student newStudent = (Student) ois.readObject();
ois.close();
fis.close();
System.out.println("从student.ser文件中反序列化出来的Student对象:");
System.out.println("Name: " + newStudent.getName());
System.out.println("Age: " + newStudent.getAge());
System.out.println("Gender: " + newStudent.getGender());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
这个例子中,我们创建了一个Student类,并实现了Serializable接口。然后我们创建了一个Student对象,将其序列化到一个文件中,然后再从文件中反序列化出来一个新的Student对象,并输出其属性。注意,序列化和反序列化对象时,都需要使用ObjectOutputStream和ObjectInputStream类来进行操作。
反射
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
通俗一点就是可以通过写代码来知道我们用代码写的类有哪些成员变量,成员方法和构造方法
1.获取class对象
- 最常用的
Class class = Class.getName("全类名");
- 常当作参数传递
Class class = 类名.class;
- 当有了类的实例对象时才能用
Student s = new Student();
Class class = s.getClass();
方法上加了“s”的就是返回多个对象
declared 公开宣布的,不带declared只能获取public修饰的
2.获取字段(成员变量 Filed)
Class中用于获取成员变量的方法
返回所有公共成员变量对象的数姐
Field[] getFields();
所有成员变量对象的数组
Field[] getDeclaredFields();
返回单个公共成员变量对象
Field getField(String name);
返回单个成员变量对象
Field getDeclaredField(String name);
Field 类中用于创建对象的方法
void (object obj,object value) //赋值
Object get(object obj) //获取值
再获取到成员变量后,还可以通过以下方法获取权限修饰符、变量名、变量类型、变量值
class在这里表示获取到的变量
getModifiers() 注意,该类型为int型,用数字代表访问权限
class.getName()
class.getType()
class.get("类的实例对象")
在获取单个方法时,参数类型要对应上,因为方法会有重载现象
3.获取构造方法(Constructor)
parameterTypes是参数类型 例如String.class
//获取所有公共构造方法(带了s,是复数,所以可以获取多个方法)
Constructor<?>[] getConstructors()
//获取所有构造方法,不管私用的还是公共的,也是复数,可以获取多个
Constructor<?>[] getDeclaredConstructors()
//获取单个(没有加s)公共构造方法
Constructor<T> getConstructor(Class<?>...parameterTypes)
//获取单个(没有加s)构造方法,不限定修饰符的类型
Constructor<T> getDeclaredConstructor(class<?>...parameterTypes)
获取构造方法可以利用newInstance初始化对象
//eg:在com.demo包下得有Student类,并且有相应构造方法
//获取class对象
Class c = Class.getName("com.demo.Student");
//获取构造方法
Constructor con = c.getDeclareConstructor(String.class, int.class);
//临时取消权限校验
con.setAccessible(true);
//创建对象
Student stu = (Student) con.newInstance("张三", 23);
4.获取成员方法(Method)
Class类中用于获取成员方法的方法
返回所有公共成员方法对象的数组,包括继承的
Method[] getMethods();
返回所有成员方法对象的数组,不包括继承的
Method[] getDeclaredMethods();
返回单个公共成员方法对象
Method getMethod(String name,Class<?>.parameterTypes);
返回单个成员方法对象
Method getDeclaredMethod(String name,,Class<?>.parameterTypes);
Method类中用于创建对象的方法(在动态代理中会用到)
Object invoke(Object obj,Object..args);运行方法
invoke方法 可以在运行时通过方法的名称和参数来调用一个方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
动态代理
需求
在维护代码时,要增强某些方法的功能时,原来的代码不宜轻易改动,不然一不小心代码就会无法运行,因此动态代理就出现了
动态代理可以在不改动原来代码得基础上,拓展方法的功能,充当一个中间人,在代理里拓展功能,然后调用原方法
实现
通过对象和代理现实相同的接口,接口中就是被代理的所有方法
需要代理的方法要统一写在一个接口中,在代理类中拓展该方法的功能
最重要的是第三个参数InvocationHandler,一般用匿名内部类的写法,在这里面写功能代码
接口名 对象名 = (接口名) Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
/*
ClassLoader loader
参数1:用于指定一个类加载器,固定写法: 当前类名.class.getClassLoader()
Class<?>[] interfaces
参数2:指定生成的代理长什么样子,也就是有哪些方法,即声明是哪个接口
InvocationHandler h
参数3:用来指定生成的代理对象要干什么事情
真正要写具体代码逻辑的地方,需要代理执行什么拓展功能
一般用匿名内部类的写法
*/
new InvocationHandler( {
@Override//回调方法
public Object invoke(Object proxy,Method method,Object[] args){
//代理对象要做的事情,会在这里写代码
//会用到反射获取方法名,根据方法不同,执行不同的代码
}
)
注解
就是Java代码里的特殊的标记,让其他程序根据注解信息来决定如何运行程序,相当于做个标记
自定义注解
- @Target 注解作用域(类、方法...)
- @Retention 注解有效时间范围
@Target()
@Retention()
public @interface 注解名称{
public 属性名称 属性名() default 默认值;
}
解析注解
仅添加自定义的注解不够,程序还不知道加了该注解要怎么做,所以就需要解析注解,告诉计算机加了该注解的方法或类应该做什么
//1.首先获得Class对象
Class c = Demo.class; //Demo为有需要解析注解的类
//2.解析类上的注解
//要利用反射获取相应的类和类上的方法
//判断类上是否包含某个注解
c.isAnnotationPresent(注解名.class);
//判断方法上是否包含某个注解
Method m = c.getDeclaredMethod("方法名");
m.isAnnotationPresent(注解名.class);
//可在此处对加了注解的类或方法进行需要的处理
//获取注解对象
Annotation a = c.getDeclaredAnnotation(注解名.class);//类上
Annotation a = m.getDeclaredAnnotation(注解名.class);//方法上
//获取注解属性值
a.属性();
具体例子: