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 }

 

  

 

posted @ 2019-09-03 16:28  chyblogs  阅读(293)  评论(0)    收藏  举报