------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
黑马程序员——28,反射
反射:反射的含义就是把java类中的所有成分解析成对应的java类。
反射的基础是Class类,注意这里前面的C是大写字母。
一:Class类---->
一个类被类加载器加载到内存中,占据了一定的空间,里面存放的就是类的字节码文件。不同类的字码文件是不一样的,比如Person类和Student类两个不同的类被调用加载进内存,然后产生的字节码文件是不一样的。这些字码文件是实在存在的东西,所以可以看做是Class类的实例对象。简单来说:某个类被载入内存,就会产生字节码文件,它就用来创建这个类的所有对象。既然字节码文件可以看做是对象,那么就可以通过调用某种方法得到字节码文件。
有三种办法可以得到字节码对应的实例对象:
1,类名.class 注意这里类名后面的c是小写字母
举例:Person.class 这是Person类的字节码文件
2,对象.getClass()
举例:Person p=new Person();
Class pcls= p.getClass();
获取了对象p对应的字节码文件
3,Class.forName(类名)
举例:Class cls=Class.forName("System.lang.String"); 注意写的时候要把类所在的包名写全。
如果类已经被调用,被加载进内存时候产生了字节码文件, 那么再次调用该类时就不会产生该类的第二个字节码文件。
基本数据类型:boolean,byte,char,short,int,long,float,double被调用的时候也会产生对应的boolean.class, byte.class, char.class, short.class, int.class, long.class, float.class, double.class 而如果源文件中调用了void,那么也会产生对应的void.class。
数组也被映射成了Class类的对象的一个类,也会有对应的数组.class
调用isPrimitive方法可以判断该字节码文件是否是基本数据类型所产生对应的字节码:
举例:int.class.isPrimitive();
该句判断int.class这个字节码文件是否是基本数据类型所产生的字节码文件,是的话返回true否则false
class Fs1
{
public static void main(String[] args)throws ClassNotFoundException
{
String a="bvunoieb";
Class cl=a.getClass();
Class cl2=String.class;
Class cl3=Class.forName("java.lang.String");//该句会抛出异常,所以在方法上声明
sop(cl==cl2);//true
sop(cl==cl3);//true
//说明cl,cl2,cl3三个都是指向同一个对象!
Class cl4= Integer.class;
Class cl5= int.class;
sop(cl4==cl5);//false
//说明Integer与int是不同的类型!
sop(int.class==Integer.TYPE);//true
//Integer.TYPE表示Integer里面封装的数据类型(的字节码)
sop(int[].class.isPrimitive());//false
//数组不是基本数据类型!
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
二:构造方法的反射应用---->
Class类放在 java.langt包中。java.lang.reflect包是关于反射的包。用Class类实例化其他类的对象,能够被Class类实例化的类的构造函数要被public修饰,暴露出去。
用Class类实例化其他类的对象的步骤:
1,用Class类中的静态方法forName(类名)返回一个Class类对象,就是定义需要操作哪一个类的字节码文件
2,用getConstructor(构造函数参数列表的class类型)方法返回一个Constructor对象,这也是确定了该类的某个构造函数。
3,用newInstance(构造函数参数列表的实例对象)方法返回一个Object对象。
其中2和3步骤中的括号里面构造函数的参数列表需要一致,不同的只是步骤2括号里面的是构造函数参数列表的class类型,步骤3里面的是构造函数参数列表的实例对象
Class类里面也有自己的newInstance方法:
1,先从内部获取不带参数的构造函数,
2,然后可以用这个构造函数建立实例对象,举例:
Stringstr=(String)Class.forName("java.lang.String").newInstance() ;
当然,通过Class来获取其他类的所有构造函数:
Class<?> cla=Class.forName("huhu.Person");
//Class类和Constructor类最好写上泛型限定
Constructor<?> con[]= cla.getConstructors();
package huhu;
import java.lang.reflect.*;
class Xy
{
public static void main(String[] args) throws Exception
{
method2() ;
}
//获取需要操作的类中特定某个构造函数
public static void method() throws Exception
{
Class<?> cla=Class.forName("huhu.Person");//Class类和Constructor类最好写上泛型限定
Constructor<?> con= cla.getConstructor(String.class, int.class);//构造函数参数列表的class类型
Person p= (Person)con.newInstance("李四",32);
//newInstance返回的是Object所以要类型转换
sop(p.get());
}
//获取需要操作的类中所有构造函数
public static void method2() throws Exception
{
Class<?> cla=Class.forName("huhu.Person");//Class类和Constructor类最好写上泛型限定
Constructor<?> con[]= cla.getConstructors();
Person p0= (Person)con[0].newInstance();
Person p1= (Person)con[1].newInstance("李四",32);
sop("p0---"+p0.get());
sop("p1---"+p1.get());
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class Person
{
private String name;
private int age;
public Person()
//特别注意:这里的构造函数一定要是public,暴露出去才可以被Constructor方法访问到
{
this.name="学霸";
this.age=100;
}
public Person(String name,int age)
//这里的构造函数一定要是public,暴露出去才可以被Constructor方法访问到
{
this.name=name;
this.age=age;
}
public String get()
{
return name+"---"+age;
}
}
三:利用反射获取类的成员变量---->
1,确定需要操作的类的字节码文件
2,调用getField方法获取类中的public修饰的成员变量,返回一个Field类对象
这里注意:是类中的成员变量,不是对象的变量,
要注意是否有初始化。
getDeclaredField方法则是无论公有还是私有变量可以获取,推荐使用该方法。
3,用get方法确定该变量在哪一个对象上的所对应的值,该方法用于public修饰的变量上,
如果先用setAccessible(true)方法设定好可以暴力反射,
那么再用get方法可以暴力获取该变量在哪一个对象上的所对应的值。
注意:get 返回的是Object类型
总结:无论是公有还是私有成员变量,都可以统一以以下步骤获取成员变量
1,确定需要操作的类的字节码文件
2,用getDeclareField获取类中的变量
3,利用setAccessible(true)设定为暴力反射
4,用get方法可以暴力获取该变量在哪一个对象上的所对应的值
import java.lang.reflect.*;
class Fs2
{
public static void main(String[] args)throws Exception
{
// 确定需要操作的类的字节码文件
Teacher tea=new Teacher("张道德",15,"上海");
Class cls= tea.getClass();
// 调用getField方法获取类中的成员变量,返回一个Field类对象
Field fieldname=cls.getDeclaredField("name");
//设定暴力反射
fieldname.setAccessible(true);
//确定该变量在哪一个对象上的所对应的值
// String s=(String)fieldname.get(tea);
//打印对象的值
sop(fieldname.get(tea));// 上海
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class Teacher
{
private String name;//注意name被private修饰
public int age;//注意age被public修饰
String address;//注意address为默认权限
Teacher(String name,int age,String address)
{
this.name=name;
this.age=age;
this.address=address;
}
public String get()
{
return name+"---"+age+"---"+address;
}
public void sop(Object obj)
{
System.out.println(obj);
}
}
利用反射获取成员变量的练习:
将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"k"变成"d"
import java.lang.reflect.*;
class Fs3
{
publicstatic void main(String[] args) throws Exception
{
Person p=new Person();
Class cla=p.getClass();
Field[] fields= cla.getDeclaredFields();//暴力获取所有成员变量
for(Field field:fields)
{
if(field.getType()==String.class)//判断是否是String类生成的字节码
{
field.setAccessible(true);
String olds= (String)field.get(p);
String news=olds.replaceAll("k","d");
field.set(p,news);//设置对象p的该变量的值
}
}
sop(p.get());
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class Person
{
public String name="kkkmowankk003";
public String address="bvskkkuvervkkkjqlvjkkie";
public int age=56;
public String get()
{
return name+"---"+age+"---"+address;
}
}
四:利用反射获取类的方法---->
1,确定要被操作的类的字节码
2,用getMethod(方法名,参数列表的.class形式) 方法获取该方法
getMethod方法返回的是一个Method类的变量
3,调用Method类中的invoke(类的实例对象,实际参数)方法
来获取Method类对象对应的该方法在类的对象上对应的结果
import java.lang.reflect.*;
class Fs4
{
public static void main(String[] args) throws Exception
{
czuo();
}
public static void sop(Object obj)
{
System.out.println(obj);
}
//获取一个类中某个方法
public static void czuo() throws Exception
{
/*
获取Person类的xiaoshi方法来使用
Person p=new Person();
p.xianshi(23,15);
*/
//确定要被操作的类的字节码
Person p=new Person();
Class<?> cla=p.getClass();
//用getMethod(方法名,参数列表的.class形式) 获取该方法
Method met=cla.getMethod("xianshi",int.class,int.class);
//调用Method类中的invoke(类的实例对象,实际参数) 来获取在类的对象上调用该方法的对应的结果
met.invoke(p,23,15);//23---15
/*
这里能够这样自由的传导参数,是因为jdk1.5新版本可变参数特性,
如果是jdk1.4版本,就是这样写了(传导的是一个Object数组):
met.invoke(p, new Object[]{23,15});
*/
//获取一个类中的静态方法
Method met2= cla.getMethod("sleep",int.class);
met2.invoke(null,20);//null表示不需要实际的对象
}
}
class Person
{
public void xianshi(int x,int y)
{
System.out.println(x+"---"+y);
}
public void jishuang(String s)
{
System.out.println("想要做的事情是:"+s);
}
public static void sleep(int x)
{
System.out.println("睡觉时间:"+x+"分钟");
}
}
调用一个类中的main方法:
外部把Person类的类名传进args[0],程序员不知道传进来的名字是什么,这也是使用反射的原因。程序员只是知道传进来的类中有一个main方法,里面传的是一个String数组, 需要调用这个静态的main方法.
package momo;
import java.lang.reflect.*;
public class Fs4
{
public static void main(String[] args)throws Exception
{
String s=args[0];
Class<?> cla= Class.forName(s);
Method me= cla.getMethod("main",String[].class);
/*
me.invoke(null,new String[]{"利益","小说","jgkgfdr","xue学习",});
由于jdk1.5版本包含了jdk1.4规则,所以该句编译不通过,
因为虚拟机会认为把newString[]{"利益","小说","jgkgfdr","xue学习",}拆成4个参数,
那么虚拟机认为传进来的对应的args[0]就是字符串"利益"
*/
me.invoke(null,new Object[]{newString[]{"利益","小说","jgkgfdr","xue学习"}});
//通过外面加一层new Object[]{}使得虚拟机认为String数组是一个整体
/*
me.invoke(null, (Object)new String[]{"利益","小说","jgkgfdr","xue学习"});
当然还有这种写法也可以的,直接把String数组类型转化成Object类
*/
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
package momo2;
public class Person
{
public static void main(String[] args)
{
for(int x=0;x<args.length;x++)
{
System.out.println(args[x]);
}
}
}
但是测试程序的时候,怎么从外部把Person类的类名传进args[0]来呢?
1,在代码中右键打开,选择Run As--àRun Configurations…
图1
2,在弹出来的Run Configurations对话框内选择Arguments一栏,再把需要操作的类的类名写在指定位置,点击Apply按钮,再点击Run按钮,就可以编译运行了。
图2
3,编译运行结果如图3所示:
图3
五:数组与Object关系及其反射类型---------》
数组字节码的比较:维度相同类型相同的数组的字节码才是相同的,其中sop是打印方法。
public static void sop(Object obj)
{ System.out.println(obj);
}
int[] i1= new int[12];
int[] i2= new int[6];
int[][] i3=new int[4][5];
String[] i4=new String[14];
sop(i1.getClass()==i2.getClass());//true
//sop(i1.getClass() ==i3.getClass());//false
//不同维度的数组的字节码不相同
//sop(i1.getClass()==i4.getClass());//false
//相同维度的不同类型的数组的字节码不同
然后观察数组与Object类之间的关系:
sop(i1.getClass().getSuperclass().getName());//java.lang.Object
sop(i2.getClass().getSuperclass().getName());//java.lang.Object
sop(i3.getClass().getSuperclass().getName());//java.lang.Object
sop(i4.getClass().getSuperclass().getName());//java.lang.Object
//为了方便理解:数组在反射中被映射成Class类实例, 其与Object类有父子关系
但是,利用多态的时候就会出现一些小问题
Object obj1=i1;//一维数组整体就是一个对象
//Object[] obj1=i1;//该句编译不通过
//因为一维数组i1中的元素是基本数据类型,而一维Object数组中的元素是Object对象!
Object[] obj3= i3;//该句编译运行通过
//i3是二维数组,付给Object数组的时候,可以把i3看做是一维数组,其中的每个元素都是一维数组
Object[] obj4=i4;//该句编译运行通过
sop(Arrays.asList(i1)); //[[I@544a5ab2]
//对于基本数据类型的一维数组,使用Arrays的asList方法转化成列表的时候,虚拟机认为数组是一个整体,所以就把哈希值打印出来的。
sop(Arrays.asList(i3));//[[I@544a5ab2,[I@5d888759, [I@2e6e1408, [I@3ce53108]
String[] i5=new String[]{"huhu","jkl"};
sop(Arrays.asList(i5));//[huhu, jkl]
String[][] s=new String[2][3];
sop(Arrays.asList(s));//[[Ljava.lang.String;@6af62373,[Ljava.lang.String;@459189e1]
//只要是二维数组,即使是用Arrays的asList方法打印也只是打印哈希值,因为虚拟机会认为一维数组里面的元素都是一维数组
六:数组类型的反射---->
Array类用于用于数组的反射操作
package momo;
import java.lang.reflect.*;
public class Fs6
{
public static void main(String[] args)
{
fs(new int[]{12,62,3});
fs(new String[]{"huhu","hjkk"});
fs(new Object[]{new int[]{15,65}});//[I@7150bd4d
}
public static void fs(Object obj)
{
Class cla=obj.getClass();
if(cla.isArray())//如果是数组的话就拆出来打印
{
int i= Array.getLength(obj);
for(int x=0;x<i;x++)//逐个获取元素打印
{
sop(Array.get(obj,x));//打印数组对象的第x位
}
}
else//如果不是数组直接打印
{
sop(obj);
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
七: 内存泄露---->
无法调用无法删除的元素,占用资源:修改与哈希值相关变量,使得元素位置移动,那么删除的时候虚拟机还是会去元素刚开始存放的位置上查找,所以,删除不会成功,这个元素无法删除而占用了资源,导致了内存的泄露。
package momo;
import java.util.*;
public class Fs7
{
public static void main(String[] args)
{
//建立HashSet集合
Set<Person> li=new HashSet<Person>();
Person p1=new Person(10);
Person p2=new Person(12);
//放进元素
li.add(p1);
li.add(p2);
//更改与哈希值相关的变量
p1.x=59;
//删除其中一个元素
li.remove(p1);
//打印集合中元素个数
sop(li.size());//2
// 元素个数为2就表明p1没有删除成功
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class Person
{
public int x;
Person(int x)
{
this.x=x;
}
public int hashCode()//提供哈希值
{
return x;
}
}
八:框架与反射---->
如果需要调用某个类的成员,但是不知道这个类的名字,就要用到反射。所谓的框架就是先写好的类,以后别人写好自己的类之后可以被这个事先写好的类调用,为了方便起见,一般写框架的时候,会加载一个配置文件,读取里面的键值对,获取值,这个值就是类名,那么就可以在框架中用反射方式来写下去了。后面有其他人写好自定义的类之后想要提供给框架使用的话,就只需要修改配置文件上的值为自定义的类名就可以了。(注意如果有包的话,要写全)
以下例子中的配置文件是放在我个人的f盘上的自定义配置文件.txt,里面写有的键值对是className=java.util.HashSet
package momo;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
public class Fs8
{
public static voidmain(String[] args)throws Exception
{
FileInputStream fis=new FileInputStream("f:\\自定义配置文件.txt");
Properties pro= new Properties();
pro.load(fis);//加载配置文件
fis.close();//注意要关闭资源
//获取类的名字
String className= pro.getProperty("className");
//获取构造函数
Constructor con= Class.forName(className).getConstructor();
//建立对象
Collection jihe= (Collection)con.newInstance();
//使用对象的功能
jihe.add(new Student(12));
jihe.add(new Student(20));
sop(jihe.size());//2
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class Student
{
int x;
Student(int x)
{
this.x=x;
}
}
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------