Java基础知识_反射
---恢复内容开始---
什么是反射?
反射是运行状态中,对任意一个类,都能知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息以及动态调用方法的功能称为Java语言的反射机制。
什么是Java序列化?什么情况下需要序列化?
序列化是为了保存各自对象在内存中的状态,并且可以把保存对象状态再读取出来
以下情况需要Java序列化
1、想把内存中的对象状态保存到一个文件中或者数据库中的时候
2、想用套接字在网络上传送对象的时候
3、想用RMI(远程方法调用)传送对象的时候
在实际开发中,经常需要将信息保存到磁盘中便于检索,但是通过输入输出流的方法逐一对对象属性信息进行操作,很频繁而且容易出错,而序列化提供了轻松解决这个问题的快捷方法。
简单的说,序列化就是将对象的状态存储到特定介质中的过程,也就是将对象状态转换为可保持或传输格式的过程,在序列化过程中,会将对象的公有成员,私有成员包括类名,转换成字节流,然后再把字节流写入数据流,存储到介质(文件)中。
使用序列化的意义在于,将对象序列化后,可以将其转换为字节序列,这样字节序列就可以保存在磁盘上,也可以借助网络进行传输,同时序列化后的对象是二进制状态,这样实现了平台无关性。
使用序列化保存对象信息
序列化机制允许实现了序列化的Java对象转换为字节序列,这个过程需要借助IO流实现
Java中只有实现了java.io.Serializable接口的类才能被序列化,Serializable表示可串行的、可序列化的,所以序列化有的时候也被称作串行化。JDK中的String类,包装类,Date类都实现了Serializable接口。
实例,引入java.io.Serializable,创建一个Student类,实现Serializable接口
1 package com.demo; 2 3 import java.io.Serializable; 4 5 public class Student implements Serializable{ 6 private String name; 7 private int age; 8 private String gender; 9 private transient String password;//transient表示这个字段不用序列化 10 public Student(String name, int age, String gender, String password) { 11 super(); 12 this.name = name; 13 this.age = age; 14 this.gender = gender; 15 this.password = password; 16 } 17 public Student(String name, int age, String gender) { 18 super(); 19 this.name = name; 20 this.age = age; 21 this.gender = gender; 22 } 23 public String getName() { 24 return name; 25 } 26 public void setName(String name) { 27 this.name = name; 28 } 29 public int getAge() { 30 return age; 31 } 32 public void setAge(int age) { 33 this.age = age; 34 } 35 public String getGender() { 36 return gender; 37 } 38 public void setGender(String gender) { 39 this.gender = gender; 40 } 41 public String getPassword() { 42 return password; 43 } 44 public void setPassword(String password) { 45 this.password = password; 46 } 47 48 49 }
引入相关包,创建对象输出流,调用writeObject()方法将对象写入文件。最后关闭输出流
1 package com.demo; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 9 import java.io.ObjectOutputStream; 10 11 public class SerializableObj { 12 13 public static void main(String[] args) { 14 ObjectInputStream ois = null; 15 ObjectOutputStream oos = null; 16 17 try { 18 oos = new ObjectOutputStream(new FileOutputStream("F:\\javawork\\stu.txt")); 19 Student stu = new Student("安娜",30,"女","aaaa"); 20 System.out.println("姓名为:"+stu.getName()); 21 System.out.println("年龄为:"+stu.getAge()); 22 System.out.println("性别为:"+stu.getGender()); 23 System.out.println("密码为:"+stu.getPassword()); 24 oos.writeObject(stu); 25 26 27 28 ois = new ObjectInputStream(new FileInputStream("F:\\javawork\\stu.txt")); 29 Student stu1 = (Student)ois.readObject(); 30 System.out.println("姓名为:"+stu1.getName()); 31 System.out.println("年龄为:"+stu1.getAge()); 32 System.out.println("性别为:"+stu1.getGender()); 33 System.out.println("密码为:"+stu1.getPassword()); 34 35 } catch (FileNotFoundException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } catch (IOException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } catch (ClassNotFoundException e) { 42 // TODO Auto-generated catch block 43 e.printStackTrace(); 44 }finally{ 45 46 if (oos != null) { 47 try { 48 oos.close(); 49 } catch (IOException e) { 50 // TODO Auto-generated catch block 51 e.printStackTrace(); 52 } 53 } 54 if (ois != null) { 55 try { 56 ois.close(); 57 } catch (IOException e) { 58 // TODO Auto-generated catch block 59 e.printStackTrace(); 60 } 61 } 62 } 63 64 } 65 }

上面的例子可以将一个学生的信息保存到文件中,如果保存多个学生信息,可以使用集合保存多个学生信息,再将集合添加到输出流
注意一个问题,实例化对象时是有给密码属性赋值的,但序列化时并没有保存该对象的密码属性值,原因是被声明为tansient的password属性不会被序列化。
在对象序列时需要注意:
序列化之后保存的是类的信息
被声明为transient的属性不会被序列化(出于安全考虑),这就是transient关键字的作用
被声明为static的属性不会被序列化,序列化保存的是对象的状态,但static修饰的是属于类的而不是属于变量的,因此序列化的时候不会序列化它。
如果向文件中使用序列化写入多个对象,则反序列化恢复对象时必须按照写入顺序读取
如果一个可序列化的类有多级父类,那么这些父类要么也是可序列化的,要么有无参构造器,否则会抛出异常。
使用反序列化获取对象信息
反序列化就是从特定存储介质中读取数据并重新构成对象的过程,大致分为2步:
1、创建一个对象输入流,它可以包装一个其他类型的输入流
2、通过对象输入流readObject方法读取对象。它返回一个Object类型的对象,需要进行强制转换成真实的对象类型
对象引用的序列化
如果一个类的成员中包含其他类的对象,如果班级类中包含学生类型的对象,那么要序列化班级类时,则必须保证班级类和学生类都是可序列化的。即当需要序列化某个特定对象时,它的各个对象也必须是可序列化的。
序列化的算法规则如下:
1、所有保存到磁盘的对象都有一个序列号
2、当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被转换成序列输出
3、如果对象已经被序列化,则程序直接输出一个序列化编号,而不再重新序列化
Serializable:使用简单,无需实现方法;缺点是使用了反射,序列化的过程较慢。这种机制会在序列化的时候创建许多的临时对象,容易触发垃圾回收。
二、Java中的反射机制
1、反射概述
Java反射机制是Java特性之一,反射机制是构建框架的基础所在
反射机制是指在运行状态中,动态的获取信息和动态调用对象的功能。
Java反射有三个动态性质
运行期间生成对象实例
运行期间调用方法
运行时更改属性
由Java程序的执行过程可知,要想Java程序能够运行,Java类必须被Java虚拟机加载,运行的程序都是在编译时就已经加载了所需的类
Java反射机制在编译时并不确定哪个类被加载了,而是在程序运行时才加载,探知,使用,这样的特点就是反射。
使用Java反射机制可以实现以下的功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的方法和属性
在运行时调用任意一个对象的方法
使用反射虽然会在很大程度上提高代码的灵活性,但是不能滥用反射,因为通过反射创建对象时性能,因为通过反射创建对象时性能要稍微低一点
实际上只有当程序需要动态创建某个类得对象时才会考虑使用反射,通常在开发通用性比较强得框架,基础平台时可能会大量使用反射,因为很多Java框架中都需要根据配置文件来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串来创建相关的实例,就必须使用反射。
在实际开发中,没有必要使用反射来访问以已知类的方法和属性,只有当程序需要动态创建某个类的对象时才会考虑使用。
2、Java中反射常用API
使用Java反射基数常用的类
Class类:反射的核心类,反射所有的操作都是围绕该类生成的,它可以获取类的属性,方法等内容信息
Field类:表示类的属性,可以获取和设置类中属性的值
Method类:表示类的方法,它可以用来获取类中方法的信息,或者执行方法
Constructor类:表示类的构造方法
一般情况下,我们使用反射获取一个对象的步骤
1、获取Class对象实例 Class clz = Class.forName("com.zhenai.api.Apple");
2、根据Class对象实例获取Constructor对象 Constructor appleConstructor = clz.getConstructor();
3、使用Constructor对象的newInstance方法获取反射类对象: Object appleObj = appleConstructor.newInstance();
而如果要调用某个方法,则需要经过下面的步骤
1、获取方法的Method对象 Method setPriceMethod = clz.getMethod("setPrice",int.class);
2、利用invoke方法调用方法 setPriceMethod .invoke(appleObj,10);
在JDK中,反射相关的API可以分为下面几个方面,获取反射的Class对象、通过反射创建类对象,通过反射获取类属性方法以及构造器
package com.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Phone {
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public static void main(String[] args) {
Phone phone = new Phone();
phone.setPrice(5000);
System.out.println("手机价格"+phone.getPrice());
System.out.println("-------------------------");
try {
Class clz = Class.forName("com.demo.Phone");
Constructor p = clz.getConstructor();
Phone phone2 = (Phone) p.newInstance();
Method setPriceMethod = clz.getMethod("setPrice", int.class);
Method getPriceMethod = clz.getMethod("getPrice");
setPriceMethod.invoke(phone2, 5000);
System.out.println(getPriceMethod.invoke(phone2));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

反射的应用
获取类的信息
分为2步,首先获取Class对象,然后通过Class 对象获取信息
1、获取Class对象
每个类被加载后,系统就会为该类生成一个对应的Class对象,通过Class对象就可以访问Java虚拟机的信息。获取Class对象通常有三种方式
调用对象的getClass方法 属于java.lang.Object的一个方法,所有对象都可以调用
调用类的class属性 ,调用某个类的class属性可以获取该类对应的class对象,这种方式需要在编译期就知道类的名称。
使用Class类的forName静态方法,该方法需要传入字符串作为参数,其值是某个类的全名,即类名前加上完善的包名
后两种方式都是直接根据类来获取该类的Class对象,相比之下调用某个类的class属性来获取该类对应的Class对象这种方式更有优势,原因如下。
代码更安全,程序在编译期就可以检查需要访问的Class对象是否存在。
程序性能更高,因为这种方法无需调用方法,所以性能更好
从Class对象获取信息
在获取了某个类对象后,就可以调用Class对象的方法来获取该类的详细信息。Class类提供了大量实例方法。
在java.lang.reflect包下还提供了一个Array类,此Array类的对象可以代表所有数组。程序可以通过Array类来动态的创建数组、操作数组元素等。
1 package com.demo; 2 import java.lang.reflect.Array; 3 public class ReflectArray { 4 public static void main(String[] args) { 5 Object instance = Array.newInstance(String.class, 10); 6 Array.set(instance, 5, "5bbb"); 7 Object arr5 = Array.get(instance, 5); 8 System.out.println(arr5); 9 } 10 }


浙公网安备 33010602011771号