前言
反射是java语言一个非常重要的高级特性,很多优秀的框架诸如Spring的Ioc和mybatis等等都运用了Java的反射技术。反射使java插上了动态性的翅膀。
学好反射可以为后面java各种技术的学习打下坚实的基础
创建对象的几种方式
在介绍反射之前,我们先来看看在java中如何创建一个对象
1.使用new
关键字创建
T name = new T();
优点:使用简单,易于理解
缺点:使程序显得固定,较死板
2.通过克隆创建
package 反射.关键类;
/**
* @author pony
* @date 2020/5/1
*/
public class CreateClassTest {
public static void main(String[] args) throws CloneNotSupportedException {
D d = new D();
d.print();
D d1 = (D)d.clone();
d1.print();
System.out.println(d1==d); //false
System.out.println(d1.equals(d)); //true
}
}
//要想能具备克隆功能,必须实现Java的Cloneable接口
class D implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void print(){
System.out.println("hello in D");
}
}
优点:创建对象的速度比较快
缺点:必须要求要克隆的类实现Cloneable接口,另外一个就是深度克隆和浅克隆的问题
3.使用序列化创建
序列化:就是将一个对象输出成一个文件流,变成一个文件。当要创建对象时,通过读取这个文件来创建对象。
优点:不用调用构造函数
却点:需要实现Serialization接口序列化由于需要和外部文件交互导致安全问题将不再被JDK所支持
4.通过反射创建
package 反射.关键类;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @author pony
* @date 2020/5/1
*/
public class CreateClassTest {
public static void main(String[] args) throws Exception {
//通过反射创建
D d1 = (D)Class.forName("反射.关键类.D").newInstance();
Method m = Class.forName("反射.关键类.D").getMethod("print");
m.invoke(d1);
//通过反射的构造器来创建
Constructor<D> constructor = D.class.getConstructor();
D d = constructor.newInstance();
d.print();
}
}
优点:
- 使程序可以访问、检测和修改它自身的状态和行为。
- 可以在程序运行时加载、探索和使用编译期间完全未知的类
- 在运行时查看和操作对象:
- 基于反射自由创建对象
- 基于反射构建无法访问的类
- set和get无法访问的变量
- 调用不可访问的方法
缺点:不容易理解算一点把😂
反射关键类
Class类
类型标识:jvm为每个对象都保留其类型标识信息
package 反射.关键类;
/**
* @author pony
* @date 2020/5/1
*/
public class ClassTest {
public static void main(String[] args) throws ClassNotFoundException {
//获取类字节码的三种方式
String s1="abc";
Class<? extends String> c1 = s1.getClass();
System.out.println(c1.getName());
Class<?> c2 = Class.forName("java.lang.String");
System.out.println(c2.getName());
Class<String> c3 = String.class;
System.out.println(c3.getName());
}
}
输出结果:
Field类
可以获取到类中的属性信息
package 反射.关键类;
import java.lang.reflect.Field;
/**
* @author pony
* @date 2020/5/1
*/
public class FieldTest {
public static void main(String[] args) throws IllegalAccessException {
A a = new A(100, "20");
Class<? extends A> aClass = a.getClass();
Field[] fs1 = aClass.getFields(); //获取类及其父类的所有public字段
System.out.println(fs1[0].getName()+":"+fs1[0].get(a));
System.out.println("------------------------------");
Field[] fs2 = aClass.getDeclaredFields(); //获取本类所有自定义属性
for (Field fs:fs2){
fs.setAccessible(true);
System.out.println(fs.getName()+":"+fs.get(a));
}
}
}
class A{
public int age;
private String name;
public A(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果:
Method类
package 反射.关键类;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author pony
* @date 2020/5/1
*/
public class MethodTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
B b = new B();
Class<? extends B> bClass = b.getClass();
Method[] m1 = bClass.getMethods(); //得到所有包括父类的public方法
for(Method m:m1){
if("f1".equals(m.getName())){
m.invoke(b);
}
}
Method[] m2 = bClass.getDeclaredMethods(); //得到所有自定义方法
for(Method m:m2){
if("f2".equals(m.getName())){
m.setAccessible(true);
String s = (String) m.invoke(b, "123");
System.out.println(s);
}
}
}
}
class B{
public void f1(){
System.out.println("in B.f1()");
}
private String f2(String s){
System.out.println("in B.f2()");
return s;
}
}
Constructor类
package 反射.关键类;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author pony
* @date 2020/5/1
*/
public class ConstructorTest {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
C c = new C();
Class<C> cClass = C.class;
Constructor<?>[] constructors = cClass.getConstructors(); //得到所有构造函数
for(Constructor con:constructors){
if(con.getParameterCount()>0){
C obj = (C)con.newInstance(10);
obj.printNum();
}
else {
C obj = (C) con.newInstance();
obj.printNum();
}
}
}
}
class C{
private int num;
public C(){}
public C(int num) {
this.num = num;
}
public void printNum(){
System.out.println(num);
}
}
反射的应用
反射的应用很广泛,而且很多地方的使用都很精妙。这里简要的介绍一些反射的应用,主要目的是认识到反射的强大之处和应用范围的宽广。起到抛砖引玉的效果
- 数据库连接
- JDBC:
Class.forName("com.mysql.jdbc.Driver");
在实际开发时,字符串一般是放在配置文件中的,这样就不用修改代码而实现不同数据库驱动的连接
- 数组扩容
package 反射.关键类.应用;
import java.lang.reflect.Array;
/**
* @author pony
* @date 2020/5/8
*/
public class test {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
int[] b = (int[])goodCopy(a,5);
for(int i:b){
System.out.println(i);
}
}
public static Object goodCopy(Object oldArray,int newLength){
//Array类型
Class<?> aClass = oldArray.getClass();
//获取数组中单个元素类型
Class<?> componentType = aClass.getComponentType();
//获取旧数组长度
int oldLength = Array.getLength(oldArray);
//通过反射创建新数组
Object newArray = Array.newInstance(componentType, newLength);
//拷贝数据
System.arraycopy(oldArray,0,newArray,0,oldLength);
return newArray;
}
}
运行结果:
- Java和Json对象的互转
- Tomcat的Servlet创建
- Mybatis的OR/M
- Spring的Bean容器
编译器API
上面我们了解java的反射,但反射的前提是class文件要存在。而即将介绍的编译器API可以避免这个问题
编译器API作用:
- 可以对.java文件进行即时编译,甚至可以将字符串进行编译
- 监听编译过程中的warning和error
- 在代码中运行编译器
javaComplier位于javax.tools包中,主要有两大方法:
- run()方法:较简单,可以编译java源文件,但不能指定输出路径,可以监控错误信息。调用后生成的.class文件就在源文件目录下
package 反射.编译器API;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
/**
* @author pony
* @date 2020/5/8
*/
public class SimpleJavaComplier {
public static void main(String[] args) {
successComplie();
failComplie();
}
public static void successComplie(){
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
int rs = complier.run(null, null, null, "D:\\Desktop\\Hello1.java", "D:\\Desktop\\Hello2.java");
System.out.println(0==rs? "success":"fail");
}
public static void failComplie(){
ByteArrayOutputStream err = new ByteArrayOutputStream();
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
int rs = complier.run(null, null, err, "D:\\Desktop\\Hello3.java");
System.out.println(0==rs? "success":"fail");
}
}
运行结果:
2. getTask():更强大的功能,可以编译内存中字符串生成.clas文件