反射
为什么需要反射?
反射指的是在运行时根据名字获取一个类的属性和方法信息,创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展 。尤其在一些框架中,框架的作者事先并不知道使用者要创建什么对象,所以由使用者告诉框架类的名称,然后框架通过反射来创建对象,调用方法。
注意:通过反射创建对象与直接new产生对象相比,反射创建对象的效率是很低的。
通过反射获取对象共有三种方式:
- Object ——> getClass();
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性。
- 通过Class类的静态方法:forName(String className)(常用)。
这里我以String类为例:
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//基本数据类型通过反射获取对象
Class obj=int.class;
//通过反射获取对象有三种方式
String str=new String("hello");
//第一种
Class obj1=str.getClass();
//第二种
Class obj2=String.class;
//第三种:注意className需要些全限定名
Class obj3=Class.forName("java.lang.String");
System.out.println(obj1==obj2);//true
System.out.println(obj2==obj3);//true
}
}
笔记:
反射案例一:如何在泛型String的List列表中添加Integer类型的数据?
思考:只要让程序跳过编译器即可,因为泛型是编译期行为,而反射是运行期行为,所以可以利用反射技术实现这一需求。但是实际的编程当中并不会这么做,因为一个集合中的元素一般放相同的类型。
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<String> ls=new ArrayList<>();
ls.add("1234");
//通过反射技术向集合中添加int类型的数据
Class li=ls.getClass();
Method m=li.getMethod("add",Object.class);//获取到add方法
//让此方法执行
m.invoke(ls, 123);
System.out.println(ls);
}
执行结果:
[1234, 123]
反射案例二:我们知道利用反射可以创建对象,那么单例是不是就没有意义了呢?
单例模式代码如下:
public class Lazy {
private static Lazy lazy=null;
private Lazy() {
super();
}
public static Lazy getInstance(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
}
利用反射创建对象代码如下:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//通过类名获取对象
Lazy lazy1=Lazy.getInstance();
System.out.println(lazy1);
//通过反射获取Lazy的对象
Class clz=Lazy.class;
//单例的构造器为private,所以我们使用getDeclaredConstructor()获取
Constructor c=clz.getDeclaredConstructor(); //获取Lazy类的构造器
//值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
//值为 false 则指示反射的对象应该实施 Java 语言访问检查。
//因为构造器为private修饰,所以需要取消java语言的类型检查
c.setAccessible(true);
Lazy lazy=(Lazy) c.newInstance();//使用构造器进行创建对象
//用完之后应该关闭,设置为false
c.setAccessible(false);
System.out.println(lazy);
}
打印结果:
ref.Lazy@15db9742
ref.Lazy@6d06d69c
由结果可知:反射在一定程度上破坏了单例,那么怎么单例防反射。
思考:反射是利用构造器来创建对象的,那么我们是不是只要在构造器进行判断单例对象是不是空就可以了呢?
改进程序:
public class Lazy {
private static Lazy lazy=null;
private Lazy() {
//判断lazy是否为空,如果不为空,那么通过反射创建对象就会抛出运行时异常
if(lazy!=null){
throw new RuntimeException("小样,反我!");
}
}
public static Lazy getInstance(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
//防序列化
public Object readResolve(){
return lazy;
}
}
思考:以上程序在单例lazy已经创建的情况下有效,也就是说如果通过反射创建对象先于类中lazy创建对象,那么上面代码防反射还是无效的,也就是说单例对象没有创建,通过反射还是可以创建对象。这种情况应该怎么解决呢?
改进程序:
public class Lazy {
private static Lazy lazy=null;
//设置标志位,如果对象已经创建就修改标志位
private static boolean flag=false;
private Lazy() {
if(flag==false){
flag=true;
}else{
throw new RuntimeException("单例模式被破坏");
}
}
public static Lazy getInstance(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
//防序列化
public Object readResolve(){
return lazy;
}
}
分析:程序改进之后,此单例模式就只能创建一个对象,如果先通过反射创建了对象,那么通过类的getInstance()方法获取对象还是再次通过反射获取对象都会抛出运行时异常。
反射案例三:我们利用apache提供的poi组件,通过程序创建excel表,并往表中添加数据
版本一: 使用poi创建xsl文件,并往当中填数据
public class Test {
public static void main(String[] args) throws IOException {
test();
}
public static void test() throws IOException{
//创建流对象
OutputStream os=new FileOutputStream("d:\\student.xls");
//创建工作簿
Workbook wb=new HSSFWorkbook();
//创建sheet
Sheet sheet1=wb.createSheet();
//创建行
Row row=sheet1.createRow(0);
//通过行指定单元格
Cell cell=row.createCell(0);
//填充值
cell.setCellValue("张三");
//写出
wb.write(os);
wb.close();
}
}
版本二:使用poi创建xsl文件,并往当中填对象数据
操作工作簿代码如下:
public class POIExport {
public static void export(String path,String fileName,List<?> ls,String[] titles,Class<?> clz) throws IllegalArgumentException, IllegalAccessException, IOException{
//创建工作簿
Workbook wb=createWB(fileName,titles);
//获取需要填写的数据的sheet对象
Sheet sheet=wb.getSheetAt(0);
//准备填充数据
for(int i=0;i<ls.size();i++){
//创建行
Row r=sheet.createRow(i+1);
//创建单元格
for(int j=0;j<titles.length;j++){
//创建cell
Cell c=r.createCell(j);
//此处必须要使用反射技术
Field[] fs=clz.getDeclaredFields();
//填充值
fs[j].setAccessible(true);
c.setCellValue(fs[j].get(ls.get(i)).toString());
fs[j].setAccessible(false);
}
}
write(wb,path,fileName);
}
public static void write(Workbook wb,String path,String fileName) throws IOException{
//构建流
OutputStream os=new FileOutputStream(path+fileName);
//写出
wb.write(os);
wb.close();
os.close();
}
/**
* 创建工作簿
* @param fileName sheet名称
* @param tiltes 标题的名称
* @return 工作簿对象
*/
public static Workbook createWB(String fileName,String[] tiltes){
//创建工作 簿
Workbook wb=new HSSFWorkbook();
//创建sheet
Sheet sheet=wb.createSheet();
//构建第一行数据
Row tilteRow=sheet.createRow(0);
//构建单元格,往单元格中填充数据
for(int i=0;i<tiltes.length;i++){
Cell cell=tilteRow.createCell(i);
cell.setCellValue(tiltes[i]);
}
return wb;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, IOException {
List<Student> ls=new ArrayList<>();
Collections.addAll(ls,
new Student("张三",18),
new Student("张三",18),
new Student("张三",18),
new Student("张三",18),
new Student("张三",18)
);
POIExport.export("d:\\","student.xls",ls,new String[]{"姓名","分数"},Student.class);
}
}
class Student{
private String name;
private int score;
public Student() {
super();
}
public Student(String name, int score) {
super();
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
以上创建工作簿的代码还存在问题:
1、在xls中编写标题 作为第一行数据
输入一个标题数组,在数组中存放了标题的内
但是出现问题:
a、增加了调用者的复杂度,如果一个用户调用时,传入List<Student> ,也必须要传入一个String[],
String数组中存放Student中的属性对应的中文名 {"姓名","年龄"}
2、解决方案:
通过一个Map集合存储元素:
Map<String,String>-->Map<"name","姓名">。。。。。程序启动时,让当前map集合加载 初始化集合。
调用者传入一个Class对象,这个Class对象时 List<Student> 中的Student.class
通过Class对象 --> getDXXXFields. --> count 当前属性的个数
循环个数 在map集合中通过对应的属性名称获取 对应的值
Field[] fs = getDXXXFields;
String[] title = new String[fs.count()];
for(int i = 0;i<fs.count();i++){
titles[i] = map.get(fs.getName);
}
但是又会有新的问题:
1、如果属性过多 导致map集合越来越大 uname->"姓名" tname->"姓名" sname->"姓名"
如果将name作为map集合中的键,就导致获取不到对应的标题。
解决方案:此处可以通过两个<key,value><key,value>解决
第一个<key,value>存放<name,"姓名"><age,"年龄">....
第二个<key,value>存放<uname,name><tname,name><sname,name>解决。先查找uname对应的value值
2、如果说属性修饰符是public 没必要关闭验证
3、如果当前类中的中的属性有一部分是通过父类继承过来的,导致通过Class对象获取到的属性不对。导致填充值有问题。(此问题未处理)
版本三:针对版本二进行改进
操作工作簿代码如下:
public class POIExport2 {
//创建一个map集合
private static Map<String,String> map;
private static Map<String,String> map2;
static{
//表格的标题
map=new HashMap<>();
map.put("name", "姓名");
map.put("age", "年龄");
map2=new HashMap<>();
map2.put("sname", "name");
map2.put("tname", "name");
map2.put("age","age");
}
public static void export(String path,String fileName,List<?> ls,Class<?> clz) throws IllegalArgumentException, IllegalAccessException, IOException{
//创建工作簿
Workbook wb=createWB(fileName,clz);
//获取需要填写的数据的sheet对象
Sheet sheet=wb.getSheetAt(0);
//准备填充数据
for(int i=0;i<ls.size();i++){
//创建行
Row r=sheet.createRow(i+1);
//创建单元格
for(int j=0;j<clz.getDeclaredFields().length;j++){
//创建cell
Cell c=r.createCell(j);
//此处必须要使用反射技术
Field[] fs=clz.getDeclaredFields();
//填充值
//如果属性值为private则进行关闭验证,否则不需要关闭验证
if("private".equals(Modifier.toString(fs[j].getModifiers()))){
fs[j].setAccessible(true);
c.setCellValue(fs[j].get(ls.get(i)).toString());
fs[j].setAccessible(false);
}else{
c.setCellValue(fs[j].get(ls.get(i)).toString());
}
}
}
write(wb,path,fileName);
}
public static void write(Workbook wb,String path,String fileName) throws IOException{
//构建流
OutputStream os=new FileOutputStream(path+fileName);
//写出
wb.write(os);
wb.close();
os.close();
}
/**
* 创建工作簿
* @param fileName sheet名称
* @param tiltes 标题的名称
* @return 工作簿对象
*/
public static Workbook createWB(String fileName,Class<?> clz){
//创建工作 簿
Workbook wb=new HSSFWorkbook();
//创建sheet
Sheet sheet=wb.createSheet();
//构建第一行数据
Row tilteRow=sheet.createRow(0);
Field[] fs=clz.getDeclaredFields();
String[] titles=new String[fs.length];
for(int i=0;i<fs.length;i++){
titles[i]=map.get(map2.get(fs[i].getName()));
}
//构建单元格,往单元格中填充数据
for(int i=0;i<titles.length;i++){
Cell cell=tilteRow.createCell(i);
cell.setCellValue(titles[i]);
}
return wb;
}
}
Teacher类:
public class Teacher extends Person{
private String tname;
private int age;
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher(String tname, int age) {
super();
this.tname = tname;
this.age = age;
}
public Teacher() {
super();
}
}
Student类:
public class Student extends Person{
private String tname;
private int age;
public Student() {
super();
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String tname, int age) {
super();
this.tname = tname;
this.age = age;
}
}
测试类:
public class Test {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, IOException {
List<Student> ls=new ArrayList<>();
Collections.addAll(ls,
new Student("张三1",18),
new Student("张三2",18),
new Student("张三3",18),
new Student("张三4",18),
new Student("张三5",18)
);
POIExport2.export("d:\\","student.xls",ls,Student.class);
//在不需要改创建xls工具类的情况下就可以添加Teacher表格
List<Teacher> lt=new ArrayList<>();
Collections.addAll(lt,
new Teacher("张三1",22),
new Teacher("张三2",22),
new Teacher("张三3",22),
new Teacher("张三4",22),
new Teacher("张三5",22)
);
POIExport2.export("d:\\","teacher.xls",lt,Teacher.class);
}
}
总结:对于以上创建xls文件的案例,存在很多的缺陷,写此案例意在了解反射的重要性,以及它给我们带来的便利之处,在这个案例中,如果没有反射技术是无法实现的。
反射总结:
反射的基础知识难度不大,学习反射应该学习怎么使用它,多写一些案例,体会它的独特之处。