------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------
反射
1.什么叫反射
获取字节码文件的三种方式:
//获取字节码文件的三种方法
class GetClassFile
{
public static void main(String[] args) throws Exception
{
Class cls1 = String.class;
Class cls2 = "helong".getClass();
Class cls3 = Class.forName("java.lang.String");
if(cls1==cls2 && cls2==cls3)
System.out.println("这说明了同一个类的字节码文件内存中只存在一份");
}
}
运行图:
2.类的成分
构造函数Constructor:
/*
测试反射中构造函数的应用
*/
class ConstructorDemo
{
public static void main(String[] args) throws Exception
{
//普通方式
String str1 = new String(new StringBuffer("helong"));
//反射方式
String str2 = (String)Class.forName("java.lang.String")
.getConstructor(Class.forName("java.lang.StringBuffer")).newInstance(new StringBuffer("helong"));
//Class的newInstance方法,使用默认构造函数
String str3 = (String)Class.forName("java.lang.String").newInstance();
System.out.println("str1:"+str1);
System.out.println("str2:"+str2);
System.out.println("str3:"+str3);
}
}
运行图:
属性字段Field:
/*
测试:Field成分的使用以及暴力反射问题。
*/
import java.lang.reflect.*;
class Student
{
public String name;
private int age;
Student(String name,int age)
{
this.name=name;this.age=age;
}
}
class FieldDemo
{
public static void main(String[] args) throws Exception
{
Student stu = new Student("helong",22);
//获取并打印Student的name和age属性
//get方法需要指定对象,因为Field只是代表一个字段,并没有与具体对象相关
String name=(String)Class.forName("Student").getField("name").get(stu);
System.out.println("Name:"+name);
//int age=(int)Class.forName("Student").getField("age").get(stu);
//第一次报错:NoSuchFieldException:当成员为private时,
//我们不能通过getField来获取了,而是使用getDeclaredFiled(str)
//第二次报错:can not access member “age”,当成员为私有时,需要先设置
//使其能够被访问到,使用方法setAccessible(boolean flag)
Field age = Class.forName("Student").getDeclaredField("age");
age.setAccessible(true);
int ageV = (int)age.get(stu);
System.out.println("Age:"+ageV);
}
}
运行图:
在这里我们发现个小知识点,那就是利用反射,我们可以获取到类的字段,而且通过getDeclaredField方法和setAccessible方法,我们可以访问到私有字段,这就是所谓的暴力反射。虽然在某些时候会派上用场,但是不得不说它也破坏了封装性,因此使用需谨慎。
/*
获取一个对象的所有String属性,并将这些属性的值中的'b'改成'a',利用反射
*/
import java.lang.reflect.*;
class Student
{
public String name;
public String hobby;
public String address;
public int age;
Student(String name,String hobby,String address,int age)
{this.name=name;this.hobby=hobby;this.address=address;this.age=age;}
}
class FieldTest
{
public static void main(String[] args) throws Exception
{
Student stu = new Student("booby","ball","tangshan",22);
//1.获取所有字段
Field[] fields = Class.forName("Student").getFields();
//2.循环获取其中String类型,先打印,进行值的改变,再打印
for(int i=0;i<fields.length;i++)
{
//这句代码作用是排除那些非String类型的字段
//为什么用==呢?这是因为对于String来说,它的字节码文件在内存只有一份,因此地址也就一份
//因此可以用==,而不用使用equals
if(!(fields[i].getGenericType() == String.class))continue;
String name = fields[i].getName();
String value = (String)fields[i].get(stu);
System.out.println(name+":"+value);
value = value.replaceAll("b","a");
fields[i].set(stu,value);
value = (String)fields[i].get(stu);
System.out.println(name+":"+value);
}
}
}
运行图:
方法Method:
/*
测试:成员方法的反射应用
*/
import java.lang.reflect.*;
class MethodDemo
{
public static void main(String[] args) throws Exception
{
//调用String.charAt方法
String str = "helong";
//普通调用
System.out.println(str.charAt(3));
//反射调用
System.out.println(String.class.getMethod("charAt",int.class).invoke(str,3));
//反射调用静态方法
System.out.println(String.class.getMethod("valueOf",boolean.class).invoke(null,true));
}
}
运行图:
/*
通过反射调用一个类的main方法(当然了其他方法也是可以的)
*/
class MethodTest
{
public static void main(String[] args) throws Exception
{
//普通调用
//普通调用的问题在于:我们必须提前知道该类的名字及其中方法等
//要是我们不知道呢?或者说这些信息都是通过当前main方法传递进来的呢?那就必须使用反射了。
Demo.main(new String[]{"helong","ldy","xiaoming"});
new Demo().show();
//反射调用
//通过args[0]传递类名进来
Class.forName(args[0]).getMethod("main",String[].class).
//invoke方法在JDK1.5后在可变参数位置如果传入一个数组,它会打开数组,获取
//里面的各个元素,作为该处的可变参数组,但是我们知道main本身需要一个数组为
//参数,那么问题就是如何避免新特性将数组拆包呢?
//解决方式1:再加一层包装:new Object[]{new String[]{.....}}这样即便拆了一层,得到的也是个数组
//解决方式2:使参数看上去不再是个数组,也就不会拆包:(Object)new String[]{.....}
invoke(null,(Object)new String[]{"xiaoming","ldy","helong"});
Class cls = Class.forName(args[0]);
cls.getMethod("show").invoke(cls.newInstance());
}
}
class Demo
{
public static void main(String[] args)
{
for(int i=0;i<args.length;i++)
{
System.out.println(args[i]);
}
}
public void show()
{
System.out.println("show is run");
}
}
运行图:
小知识点:invoke方法在JDK1.5后在可变参数位置如果传入一个数组,它会打开数组,获取里面的各个元素,作为该处的可变参数组,但是我们知道main本身需要一个数组为参数,那么问题就是如何避免新特性将数组拆包呢?
解决方式1:再加一层包装:new Object[]{new String[]{.....}}即便拆了一层,得到还是个数组
解决方式2:使参数看上去不再是个数组,也就不会拆包:(Object)new String[]{.....}
3.数组的反射
我们知道,数组的父类是Object,那么数组和Object的关系式怎么样的呢?
/*
数组和Object的关系
*/
import java.util.*;
class ArrayDemo
{
public static void main(String[] args)
{
int[] a1=new int[]{2,3};
int[] a4=new int[4];
int[][] a2=new int[2][3];
String[] a3=new String[]{"helong","ldy"};
//只有元素类型相同,且同一维度的数组对象的字节码文件才相同
System.out.println(a1.getClass().equals(a2.getClass()));
System.out.println(a1.getClass().equals(a3.getClass()));
System.out.println(a1.getClass().equals(a4.getClass()));
//Object[] obj1=a1;//false,因为obj1数组中装的元素类型是Object,而a1中的是int,不匹配。
Object[] obj2=a2;//true.
Object[] obj3=a3;//true.
System.out.println(a1);
System.out.println(a3);
//由于a1和Object[]不匹配,因此a1被当做一整个元素存入列表,而不是一组数据。
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a3));
}
}
运行图:
观察运行图,我们可以知道,首先确实只有元素类型相同,且维度相同的数组的字节码文件是同一个class文件。其次,当我们如下调用时:Arrays.asList(a1);由于需要兼容1.4因此两个都匹配一下看看那个asList更合适,我们知道int[]和Object[]是不匹配的,因此使用可变参数的asList方法,也就是将int[]作为一个元素传给了asList,因此得到的List只有一个元素,就是int[]。而Arrays.asList(a3);就没有这个问题,由于String[]和Object[]匹配,因此a3使用的是asList(Object[])方法,也就是将a3作为数组传给asList,得到的List中的元素就是String类型。
/*
数组的反射应用:定义一个方法接收Object,如果传入一个元素,那么就打印一个元素,
如果传入数组,那么就挨个打印数组中元素。
*/
import java.lang.reflect.*;
class ArrayTest
{
public static void main(String[] args) throws Exception
{
printObject(123);
printObject("helong");
printObject(new String[]{"str1","str2","str3"});
}
private static void printObject(Object obj)
{
Class cls = obj.getClass();
if(cls.isArray())//判断该对象所属类是否为数组类
{
for(int i=0;i<Array.getLength(obj);i++)
{
System.out.print(Array.get(obj,i)+" ");
}
System.out.println();
}
else
System.out.println(obj);
}
}
运行图:
4.反射在框架中应用一个小例子
/*
演示:
框架中反射的作用:某些类是以后出现的,因此在程序中不出现该类
的名字,而是从配置文件中获取其名字,并使用反射调用它方法。
*/
import java.util.*;
import java.lang.reflect.*;
import java.io.*;
class Student
{
private String name;
private int age;
Student(String name,int age)
{this.name=name;this.age=age;}
public int hashCode()
{
return name.hashCode()+age*11;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
throw new RuntimeException("类型不匹配");
Student stu=(Student)obj;
return name.equals(stu.getName())&&age==stu.getAge();
}
public String getName()
{return name;}
public int getAge()
{return age;}
}
class FrameTest
{
public static void main(String[] args) throws Exception
{
//从配置文件中获取使用的集合类的名字
InputStream is = new FileInputStream("config.properties");
Properties prop = new Properties();
prop.load(is);
is.close();
String className=prop.getProperty("className");
Class cls = Class.forName(className);
Collection col = (Collection)cls.newInstance();
col.add(new Student("helong",22));
col.add(new Student("helong",19));
col.add(new Student("ldy",22));
col.add(new Student("helong",22));
System.out.println(col.size());
}
}
运行图:
5.反射心得总结
-------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------