-----------android培训、java培训、java学习型技术博客、期待与您交流!------------
学习前先想一下下面的问题:
(1)我们为何需要反射?
(2)如何使用反射?
(3)Class对象的三种获取方式?
java反射概念
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态
调用对象的方法的功能称为java语言的反射机制。 (反射技术大大提高了程序的扩展性)
反射的一般操作方式:
1)加载这个类。
2)创建该类的对象。
3)调用该类中的内容。
应用程序使用的类不确定时,可以通过提供配置文件,让使用者将具体的子类存储到配置文件中。然后该程序通过反射技术,对指定的类进行内容的获取。
反射的经单总结: 反射就是把Java类中的各种成分映射成相应的java类。 ----张孝祥老师的某个学生
二、反射的基石——Class类
类的加载
当源程序中用到类时,首先要从硬盘把这个类的那些二进制代码,一个类编译成class放在硬盘上以后,就是一些二进制代码,要把这些二进制代码加载到内存中里面来,再用这些字节码去复制出一个一个对象来。
2、Class和class的区别
1)class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。
2)Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。例如人对应的是Person类,Java类对应的就是Class。Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。
Class对象的三种获取方式:
(1) 运用getClass() (注:每个class 都有此函数)
(2) 运用Class.forName()(最常被使用)
(3) 类名.class 语法
1、九个预定义的Class:
1)包括八种基本类型(byte、short、int、long、float、double、char、boolean)+ void.class
2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
2、只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class。数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型的。
用Class对象来获取类实例对象的做法之一:
1)查找并加载指定名字的字节码文件进内存,并被封装成Class对象。
2)调用newInstance()方法会去使用该类的空参数构造函数进行初始化。
如:
String className="包名.Person";
Class clazz=Class.forName(className);
Object obj=clazz.newInstance();
这和之前我们学过的类的创建方式有很大区别! 即使拿到实例对象后,也不能直接去调用内部字段或方法··。
注意:如果指定的类中没有空参构造函数,那么就必须用到Constructor这个类来获取指定类中存在的构造方法,然后再调用次构造函数去创建新对象
反射中最常用到的三个类:Constructor类 Field类 Method类
注意:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法
import java.lang.reflect.*;
import java.lang.*;
/*
定义一个Person的类,为后面反射用!
*/
public class Person
{
private String name; //注意: name定义的为私有
public int age;
public Person()
{
System.out.println("Person is run");
}
public Person(String name,int age)
{
this.age=age;
this.name=name;
}
public String toString()
{
return name+":"+age;
}
}
/*=======================main==========================*/
public class reflectClass {
public static void main(String[] args) throws Exception
{
createPersonClass(); //空参实例对象
createPersonClass_2(); //带参实例对象
getPersonField(); //获得成员字段,并为其设值
getPersonMethod(); //通过反射获取类中的方法
}
/*=======================空参实例对象==========================*/
/*
如果类中 带有 空参构造函数,那么可以直接用newInstance()来创建对象
*/
//通过Class对象创建类实例方法
public static void createPersonClass() throws Exception{
//获取Person类的Class对象
// String className=Person.class;
Class<? extends Person> clazz=Person.class;
//通过newInstance方法获取类的无参构造函数实例
Person p=(Person)clazz.newInstance();
}
/*=======================带参实例对象==========================*/
/*
如果类中 没有 空参构造函数,那么必须先通过Class类获得带参数的Constructor类的对象,
然后再用Constructor对象的带参方法创建出新的person对象
*/
public static void createPersonClass_2() throws Exception{
//获取Person类的Class对象
//String className="Person";
Class<? extends Person> clazz=Person.class;
//Class clazz=Person.class;
//获取指定构造函数的类实例
Constructor<? extends Person> con=clazz.getConstructor(String.class,int.class);
Person p=(Person) con.newInstance("liuliu",25);
System.out.println(p.toString());
}
/*=======================获取类中的字段==========================*/
//获取Person对象的成员变量
public static void getPersonField() throws Exception{
//如果想要给该变量赋值,必须先要有对象。
//Class clazz=Class.forName("Person")
Class<? extends Person> clazz=Person.class;
Person p=(Person)clazz.newInstance();
//获取所以的成员变量
Field[] fs=clazz.getDeclaredFields();
for(Field f:fs){
System.out.println("field--"+f);
}
//获取指定的成员变量
Field fage=clazz.getField("age");
Field fname=clazz.getDeclaredField("name");
//显示改变后的值
fage.set(p, 20);
System.out.println(fage.get(p));
//暴力访问私有变量
fname.setAccessible(true);
fname.set(p, "zhangsan");
System.out.println(fname.get(p));
}
/*=======================获取类中的方法==========================*/
//获取Person类中的方法
public static void getPersonMethod() throws Exception{
//如果想要获取方法,必须先要有对象。
Class<? extends Person> clazz=Person.class;
//Person p=(Person)clazz.newInstance();
Constructor<? extends Person> con=clazz.getConstructor(String.class,int.class);
Person p=(Person) con.newInstance("liuliu",25);
//获取所以方法
Method[] mes=clazz.getMethods();//只获取公共的和父类中的。
//mes=clazz.getDeclaredMethods();//获取本类中包含私有。
for(Method me:mes){
System.out.println("method:::"+me);
}
//获取单个方法
Object objs=null;
Method me=clazz.getMethod("toString",new Class[0]); //注意这里的写法
Object returnVaule=me.invoke(p,new Object[]{});
System.out.println(returnVaule);
}
}
练习题: 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
import java.lang.reflect.Method;
//定义一个测试类
class Test
{
public static void main(String[] args)
{
for(String arg : args)
{
System.out.println(arg);
}
}
}
//用反射方式根据用户提供的类名,去执行该类中的main方法。
public class PerformedMain{
public static void main(String[] args) throws Exception
{
//普通方式
Test.main(new String[]{"lai","hei","ma"});
//分割符
System.out.println("******************************");
//拿到对应的Class对象
String className="Test";
Class clazz=Class.forName(className);
//拿到主函数方法
Method methodMain=clazz.getMethod("main",String[].class);
//强制转换为超类Object,不用拆包
methodMain.invoke(null, (Object)new String[]{"ai","hei","ma"});
//将数组打包,编译器拆包后就是一个String[]类型的整体
methodMain.invoke(null, new Object[]{new String[]{"jiushiai","hei","ma"}});
}
usb.properties
usb1=cn.itheima.test.MouseUSB
usb2=cn.itheima.test.KeyUSB
注:此练习中,配置文件要存在工程中:工程名——>新建File
数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
2、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
3、如何得到某个数组中的某个元素的类型,
例:
int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
无法得到某个数组的具体类型,只能得到其中某个元素的类型,
如:
Obj[0].getClass().getName()得到的是java.lang.String。
4、Array工具类用于完成对数组的反射操作。
Array.getLength(Object obj);//获取数组的长度
Array.get(Object obj,int x);//获取数组中的元素
5、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
基本判断:
import java.util.Arrays;
public class SuZuDemo {
public static void main(String[] args) {
int[] a = new int[3];
int[] b = new int[4];
int[][] c = new int[3][4];
String[] s1 = new String[3];
System.out.println(a.getClass() == b.getClass());// true
//System.out.println(a.getClass()==c.getClass());//编译失败
//System.out.println(a.getClass()==s1.getClass());//编译失败
/**
* 获得父类的字节码,从结果知道,他们的父类的是Object
*/
System.out.println(a.getClass().getSuperclass().getName());
System.out.println(s1.getClass().getSuperclass().getName());
int[] a1 = new int[] { 1, 2, 3 };
String[] s2 = new String[] { "a", "b", "c" };
System.out.println(Arrays.asList(a1));// 在1.5后,此此方法会把整形数组当作一个参数处理
System.out.println(Arrays.asList(s2));// 会把String数组拆分,分别作为参数来处理
}
}
结果:
true
java.lang.Object
java.lang.Object
[[I@1f66cff]
[a, b, c]
反射数组:
import java.lang.reflect.Array;
import java.util.Arrays;
public class SuZuDemo {
public static void main(String[] args) throws Exception {
String[] s = { "abc", "def", "hig" };
String ss = "hello";
print(s);
print(ss);
}
public static void print(Object ob) {
Class classzz = ob.getClass();// 反射
if (classzz.isArray()) {// 判断的是否是数组
int len = Array.getLength(ob);// 获取长度
for (int i = 0; i < len; i++) {
System.out.println(Array.get(ob, i));// 获得数组中的元素
}
} else {
System.out.println(ob);
}
}
}
结果:
abc
def
hig
hello
所以就有这样的说法:如果两个对象equals相等的话,你应该让他们的hashCode也相等。如果对象存入的不是根据hash算法的集合中,就不需要复写hashCode方法。
4、当一个对象存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,
否则对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,调用
contains方法或者remove方法来寻找或者删除这个对象的引用,就会找不到这个对象。从而导
致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。(程序中某一些对象不再被使用,
以为被删掉了,但是没有,还一直在占用内存中,当这样的对象慢慢增加时,就会造成内存泄露。)
补充:
内存泄露:某些对象不再使用了,占用着内存空间,并未被释放,就会导致内存泄露;也就是说当程序不断增加对象,修改对象,删除对象,日积月累,内存就会用光了,就导致内存溢出。