黑马程序员——java---反射

来源:互联网 发布:淘宝是独立于阿里巴巴 编辑:程序博客网 时间:2024/05/05 02:40
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

反射的概念

 

反射的引入:

Object obj = new Student();

若程序运行时接收到外部传入的一个对象,该对象的编译类型是Object,但程序又需要调用该对象运行类型的方法:

1.若编译和运行类型都知道,使用 instanceof判断后,强转。

2.编译时根本无法预知该对象属于什么类,程序只能依靠运行时信息来发现对象的真实信息,这时就必须使用反射了。

3.要是想得到对象真正的类型,就得使用反射。

 

 

什么是反射机制?  

        简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。

 

反射机制的优点与缺点:  

        为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念,  

     静态编译:在编译时确定类型,绑定对象,即通过。  

     动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。  

     一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发

它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

 

Class类和Class类实例

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class类。

对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

人   Person

Java类  Class

对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等;

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的;

用类来描述对象,类:描述数据的结构

用元数据来描述ClassMetaData(元数据):描述数据结构的结构

反射就是得到元数据的行为

备注:一个类在虚拟机中只有一份字节码;

获得Class对象

 

如何得到各个字节码对应的实例对象?

每个类被加载后,系统会为该类生成对应的Class对象,通过Class对象可以访问到JVM中的这个类,

3种方式:

1调用某个类的class属性获取Class对象,如Date.class会返回Date类对应的Class对象(其实就是得到一个类的一份字节码文件)

2使用Class类的forName(String className)静态方法,className表示全限定名;如String的全限定名:java.lang.String;

3调用某个对象的getClass()方法。该方法属于Object类;

Class<?> clz = new Date().getClass();

示例:

public class DeMo1 {/** * 获取calss对象的方法(三种) * @throws ClassNotFoundException  */public static void main(String[] args) throws ClassNotFoundException {// TODO Auto-generated method stub//1.调用属性。Class<String> clazz = String.class;Class<String> clazz1 = String.class;System.out.println(clazz1);//返回的是true因为一个类在虚拟机中只有一份字节码。System.out.println(clazz==clazz1);//2.使用forname()必须使用全限定名不然会报错。Class<String> clazz2 = (Class<String>) Class.forName("java.lang.String");System.out.println(clazz2);//3.利用对象调用object的getclass方法。Class<String> clazz3 = (Class<String>) new String().getClass();System.out.println(clazz3);}}


 

我的总结:获取Class对象最常用的是利用属性的方法!

九个预定义Class对象

 

基本的 Java 类型(booleanbytecharshortintlongfloat 和 double)和关键字 void通过class属性也表示为 Class 对象;

Class类中boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。

包装类和Void类的静态TYPE字段;

Integer.TYPE == int.class ;  

Integer.class == int.class;

public class DeMo2 {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubClass<?> in = int.class;System.out.println(in);Class<?> in1 = Integer.class;Class<?> in2 = Integer.TYPE;//结果为false,in1指定的是一个包装类。System.out.println(in1==in2);//结果为true,包装类都有一个常量type用来表示其基本数据类型字节码。System.out.println(in2==in);}}


 

 数组类型的Class实例对象:

Class<String[]> clz = String[].class; 

数组的Class对象如何比较是否相等数组的维数和数组的类型;

Class类中 boolean isArray() :判定此 Class 对象是否表示一个数组类型。

Class类中的方法

static Class forName(String className)

返回与给定字符串名的类或接口的相关联的Class对象。

Class getClass()

返回的是Object运行时的类,即返回Class对象即字节码对象

Constructor getConstructor()

返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。

Field getField(String name)

返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。

Field[] getFields()

返回包含某些Field对象的数组,表示所代表类中的成员字段。

Method getMethod(String name,Class… parameterTypes)

返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。

Method[] getMehtods()

返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。

String getName()

以String形式返回此Class对象所表示的实体名称。

String getSuperclass()

返回此Class所表示的类的超类的名称

boolean isArray()

判定此Class对象是否表示一个数组

boolean isPrimitive()

判断指定的Class对象是否是一个基本类型。

T newInstance()

创建此Class对象所表示的类的一个新实例。

Class中得到构造方法Constructor、方法Method、字段Field

 

常用方法:

Constructor类用于描述类中的构造方法:

Constructor<T> getConstructor(Class<?>... parameterTypes) 

返回该Class对象表示类的指定的public构造方法

Constructor<?>[] getConstructors()

返回该Class对象表示类的所有public构造方法

Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

返回该Class对象表示类的指定的构造方法,和访问权限无关;

Constructor<?>[] getDeclaredConstructors() 

返回该Class对象表示类的所有构造方法,和访问权限无关;

 

Method类用于描述类中的方法:

Method getMethod(String name, Class<?> ... parameterTypes) 

返回该Class对象表示类和其父类的指定的public方法

Method[] getMethods():  

返回该Class对象表示类和其父类的所有public方法

Method getDeclaredMethod(String name, Class<?>... parameterTypes) 

返回该Class对象表示类的指定的方法。和访问权限无关,但不包括继承的方法;

Method[] getDeclaredMethods(): 获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法;

import java.lang.reflect.Constructor;import java.lang.reflect.Method;class Person{String str;public Person(){}public Person(String str){this.str = str;}public void show(){System.out.println("无参的方法");}public void show(String str){System.out.println(str+"的方法");}}public class DeMo3 {/** * @param args * @throws IllegalAccessException  * @throws InstantiationException  * @throws NoSuchMethodException  * @throws SecurityException  */public static void main(String[] args) throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException {// TODO Auto-generated method stub//通过类的的属性获取字节码。Class<Person> clazz = Person.class;//因为是我自己定义的类所以这里我知道是person类。Constructor[] cons = clazz.getConstructors();for(Constructor con : cons){System.out.println(con);}Method[] method = clazz.getMethods();for(Method me : method){System.out.println(me);}}}

利用反射创建对象

 

创建对象:

1使用Class对象的newInstance()方法创建该Class对象的实例,此时该Class对象必须要有无参数的构造方法。

2使用Class对象获取指定的Constructor对象,再调用ConstructornewInstance()方法创建对象类的实例,此时可以选择使用某个构造方法。如果这个构造方法被私有化起来,那么必须先申请访问,将可以访问设置为true

示例:

import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import org.junit.Test;class Student {String name;int age;public Student() {}public Student(String name) {this.name = name;}private Student(String name, int age) {this.name = name;this.age = age;}public String toString() {return name + ":" + age;}}public class DeMo4 {/** * @param args * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws InstantiationException * @throws IllegalArgumentException */@Test// 无参的空参数构造函数。public void test1() throws SecurityException, NoSuchMethodException,IllegalArgumentException, InstantiationException,IllegalAccessException, InvocationTargetException {Class<Student> clazz = Student.class;Constructor c = clazz.getConstructor(null);Student s = (Student) c.newInstance(null);System.out.println(s);}@Test// 构造函数参数为string的。public void test2() throws SecurityException, NoSuchMethodException,IllegalArgumentException, InstantiationException,IllegalAccessException, InvocationTargetException {Class<Student> clazz = Student.class;Constructor c = clazz.getConstructor(String.class);Student s = (Student) c.newInstance("lisi");System.out.println(s);}@Test// 暴力反射。public void test3() throws SecurityException, NoSuchMethodException,IllegalArgumentException, InstantiationException,IllegalAccessException, InvocationTargetException {Class<Student> clazz = Student.class;Constructor c = clazz.getDeclaredConstructor(String.class, int.class);c.setAccessible(true);Student s = (Student) c.newInstance("zhansan", 10);System.out.println(s);}}

使用反射调用方法

 

每个Method的对象对应一个具体的底层方法。获得Method对象后,程序可以使用Method里面的invoke方法来执行该底层方法。

Object invoke(Object obj,Object ... args):obj表示调用底层方法的对象,后面的args表示传递的实际参数。

如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null,想想为什么

如果底层方法所需的形参个数为 0,则所提供的 args 数组长度可以为 或 null。 

不写,null,或 new Object[]{}

若底层方法返回的是数组类型,invoke方法返回的不是底层方法的值,而是底层方法的返回类型;

示例:

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import org.junit.Test;class stu {String name;int age;public void show1() {System.out.println("aaa");}public void show2(String name) {System.out.println("name=" + name);}private void show3(String name, int age) {System.out.println("name=" + name + ";age=" + age);}}public class DeMo5 {/** * 反射方法 *  * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InstantiationException */@Test// 空参数的方法。public void test1() throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException,InvocationTargetException, InstantiationException {// TODO Auto-generated method stubClass<stu> clazz = stu.class;stu stu = clazz.newInstance();Method me = clazz.getMethod("show1", null);me.invoke(stu, null);}@Test// 指定参数类型为string的方法。public void test2() throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException,InvocationTargetException, InstantiationException {// TODO Auto-generated method stubClass<stu> clazz = stu.class;stu stu = clazz.newInstance();Method me = clazz.getMethod("show2", String.class);me.invoke(stu, "lisi");}@Test// 指定两个参数类型的方法。public void test3() throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException,InvocationTargetException, InstantiationException {// TODO Auto-generated method stubClass<stu> clazz = stu.class;stu stu = clazz.newInstance();Method me = clazz.getDeclaredMethod("show3", String.class, int.class);me.setAccessible(true);me.invoke(stu, "wangwu", 20);}}

使用反射调用可变参数方法

 

使用反射操作对象-调用可变参数方法

 

要把可变参数都当做是其对应的数组类型参数;

如 show(XX... is)作为show(XX[] is)调用;

 

若可变参数元素类型是引用类型:

JDK内部接收到参数之后,会自动拆包取出参数再分配给该底层方法,为此我们需要把这个数组实参先包装成一个Object对象或把实际参数作为一个Object一维数组的元素再传递。

 

若可变参数元素类型是基本类型:

JDK内部接收到参数之后,不会拆包,所以可以不必再封装.不过封装了也不会错.所以建议,不管基本类型还是引用类型都使用Object[]封装一层,保证无误.

示例:

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import org.junit.Test;class Vary {public static void show1(int... args) {System.out.println("基本类型!");}public static void show2(String... args) {System.out.println("引用数据类型!");}}public class DeMo6 {/** * @param args * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */@Testpublic void test1() throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException,InvocationTargetException {Class<Vary> clazz = Vary.class;Method me = clazz.getMethod("show1", int[].class);me.invoke(null, new int[] { 1, 3, 4 });}@Testpublic void test2() throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException,InvocationTargetException {Class<Vary> clazz = Vary.class;Method me = clazz.getMethod("show2", String[].class);// 方法一接受的应该是一个string数组。强转成Object。me.invoke(null, (Object) new String[] { "1", "2", "5" });// 方法二把原来数组封装成object数组。双重封装。me.invoke(null, new Object[] { new String[] { "1", "2", "5" } });}}

使用反射操作字段

 

Field提供两组方法操作字段:

xxx getXxx(Object obj):获取obj对象该Field的字段值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用,Object get(Object obj);

void setXxx(Object obj,xxx val):obj对象的该Field字段设置成val值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用,void set(Object obj, Object value);

示例:

import java.lang.reflect.Field;import org.junit.Test;class Cat {String name = "aaaaa";private String color;}public class DeMo7 {/** * @param args * @throws NoSuchFieldException * @throws SecurityException * @throws IllegalAccessException * @throws InstantiationException */@Test// 非私有。public void test1() throws SecurityException, NoSuchFieldException,InstantiationException, IllegalAccessException {Class<Cat> clazz = Cat.class;Cat c = clazz.newInstance();Field f = clazz.getDeclaredField("name");// 设置字段的值。f.set(c, "ddddd");// 获取字段的值。Object obj = f.get(c);System.out.println(obj);}@Test// 私有。public void test2() throws SecurityException, NoSuchFieldException,InstantiationException, IllegalAccessException {Class<Cat> clazz = Cat.class;Cat c = clazz.newInstance();Field f = clazz.getDeclaredField("color");// 暴力反射f.setAccessible(true);// 设置字段的值。f.set(c, "lisi");// 获取字段的值。Object obj = f.get(c);System.out.println(obj);}}


 

反射的作用:

实现小框架功能。在实际开发中,配置文件是不能用相对路径的。当运行程序的时候,是相对于当前路径下运行的,这个当前路径是不断变化的。所以配置文件最好写绝对路径。

0 0
原创粉丝点击