黑马程序员——反射

来源:互联网 发布:软件脱壳是什么意思 编辑:程序博客网 时间:2024/06/03 17:32

------- android培训、java培训、期待与您交流! ----------

相关概念

Class类

1)反射的基石:Class类。Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class;

2)如何得到各个字节码对应的实例对象(Class类型),三种方式:

    a、类名.class:如System.class

    b、对象.getClass():如new Date().getClass()

    c、Class.forName(“类名”) :如Class.forName(“java.util.Date”)

3)做反射时,一般用Class.forName(“类名”) 。因为在写源程序时,还不知道类的名字,在运行时,传递给我的字符串包含了一个类的名字,再将字符串传入方法中;

4)通过Class类可以得到一个类的方方面面的信息,且只要是在源程序中出现的类型;

5)8个基本类型也有对应的类型对象,void也有对应的void.Classs,这是9个预定义的Class对象;
    a、方法isPrimitive()查看是不是基本类型的字节码;
    b、Integer.TYPE方法指Integer所包装的基本类型的字节码;

        int[].class == Integer.class;为false,而int.class ==Integer.TYPE;为true;说明TYPE是Class类和别的所有类的统称;

    c、数组类型的Class实例对象:Class.isArray()。如int[].class.isArray();返回true;一个int[]数组的类型打印为:class [I。

字节码

什么是字节码?

1)当我们在源程序里用到一个类时,首先要从硬盘上将这个类的二进制代码编译成的class以后,把这些二进制代码加载到内存里面,才可以用这个类创建一个对象;

2)即首先要将类的字节码加载到内存里,再用这些字节码,复制出一个个对象来;

3)若内存里面有N份字节码,那每一份字节码就是一个Class的实例对象;
4)如Person.class就是代表Person类的字节码,Person p = new Person;就是字节码创建出来的对象,可以用p.getClass()可以得到p属于哪个字节码,即类名。

5)小结:

    a、可以指定一个类的字节码的完成名称,forName也是得到一个类的字节码;

    b、得到一个类的字节码分2种情况:

        字节码已经加载,现在待在虚拟机里;

2、字节码还没加载,则用类加载器去加载,再将字节码缓存,forName返回刚才加载进来字节码。

反射

    黑马学员冯伟立听完反射后的一句话总结:“反射就是把Java类中的各种成分映射成相应的Java类”。很透彻、精辟。

1)反射就是把Java类中各种成分映射成相应的Java类。如一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量、方法、构造方法、包等信息也用一个个的Java类来表示;表示Java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造方法、修饰符、包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Constructor、Package等;

2)一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。反射的要点就是:通过得到的这些实例对象来实现一些功能;

3)不管是什么方法,都可以都可以用一个类型Method的实例来表示;Method代表类型、类别,而其对象代表一个具体的方法;得到一个Method对象,即得到了一个类的一个方法;

4)通过查看Jdk源代码,看出反射比较占用时间和性能,导致程序性能严重下降。


知识点与代码

成员变量反射

1)Field类:代表字节码的一个变量,不代表某个对象的变量;
2)代码演示:将任意一个对象中的所有Sting类型的成员变量所对应的字符串内容中的"b"改成"a";
    a、为了代码好看,又定义个方法;
    b、扫描一个对象上的所有字段,且字节码用“==”比较更专业;
3)参见反射的基础代码。

方法的反射Method类

1)Method类代表某个类中的一个成员方法:得到类中的某一个方法:

    Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);

2)调用方法:

    a、通常方式:System.out.println(str.charAt(1));

    b、反射方式:System.out.println(charAt.invoke(str, 1));

3)JDK1.4和JDK1.5的invoke方法的区别:

    a、JDK1.5:public Object invoke(Object, Object... args)

    b、JDK1.4:public Object invoke(Object obj, Object[] args),即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应一个参数。

4)静态方法调用时,不需要对象,则传入null。

Constructor类

1)Constructor类代表某个类中的一个构造方法。得到某个类所有的构造方法:

    Constructor[] constructors = Class.forName("java.lang.String").getConstructor();

2)得到某一个构造方法:

    Constructor constructor = Constructor[] constructors = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

3)创建实例对象:

    a、通常方式:String str = new String(new StringBuffer("abc");

    b、反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));

4)JDK1.5新特性,接收可变参数列表(若没有新特性,可通过传入数组来实现可变参数):

    String.class.getConstructor(Class<?> ... parameterTypes);

5)newInstance();方法:

    注意2重点:a、获得方法时,需要类型;b、调用方法时也要传同样类型的对象newInstance返回的是Object;

6)Constructor c = String.class.getConstructor(StringBuffer.class);  String str = (String)c.newInstance(new StringBuffer("abc"));

    a、在运行时才知道你对应的是哪个构造方法;要区别清楚编译时、运行时,错误错在哪个阶段,一定要搞清楚;

    b、第一个StringBuffer表示选择哪个构造方法,第二个StringBuffer表示用这个构造时,还得传一个StringBuffer对象进去;编译时只知道你是个构造方法,但不知道是哪个类的构造方法,只有在运行才知道具体的构造方法,所以要强制转型(这里是给编译器强制转型的);

7)Class类也有个newInstance方法,它和上面的Constructor类的newInstance方法有什么区别呢?

    相比之下,Class类的newInstance方法直接省略了中间步骤,封装了过程,底层调用了Constructor的newInstance方法。

main方法与反射

1)我们为什么要用反射的方式调用别的类的main方法?因为我们在使用反射时,并不知道这个类的名字;
2)用Eclipse中得到main方法步骤:等同于在dos窗口下执行java ReflectTest cn.itcast.day1.TestArguments
    复制类名——eclipse空白处右键——Run As——Run Comfigurations——Arguments——粘贴;相当于下图操作:运行一个类时,传递一个字符串(是另外一个类的完整名字),搞出这个字符串类的字节码,就得到了main方法,就可以施行下一步操作;

小结

    不要把传入的数组参数拆包的两种方案:
1)每一个数组的父类都是Object,下面代码很重要:
String startingClassName = args[0];
Method mainMethod =
Class.forName(startingClassName).getMethod("main",String[].class);
//如果不加Object[],Java没把你当成一个数组,而是认为收到3个参数
mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});
2)或者下面的做法,因为数组也是一个Object:
String startingClassName = args[0];
Method mainMethod =
Class.forName(startingClassName).getMethod("main",String[].class);
//mainMethod.invoke();
mainMethod.invoke(null,(Object){new String[]{"111","222","333"});

数组与Object及其反射

数组是什么类型?
1)具有相同的元素类型和相同的维度,就是同一种类型,即具有相同的Class实例对象;
2)代表数组的Class实例对象的getSuperClass()方法返回的父类为Object对应的Class,int[] String[] int[][]的父类都是Object;
3)基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用;
4)Arrays.asList()方法处理int[]和String[]时存在差异;
5)可以得到数组某个元素的类型,但是没法得到整个数组元素的类型,至少现在张老师认为没有办法;如果我们要自己动手开发框架,就要对这些知识很清楚;
3)参见反射的基础代码。

反射的基础代码

ArrayList、HashSet比较及hashCode分析

1)ArrayList是在数组位置中依次放入对象的引用,而不是放入对象;

2)而HashSet每次放的时候,是先判断集合中有没有,有就存放失败(而不是覆盖),对于其元素比较的是Hashcode值,会被HashSet本身的equals判断为不等;

    a、若想通过特别的方式比较的元素相等,就要复写equals方法和hashCode方法;

    b、如:HashSet中的两个元素new ReflectionPoint(3, 3);和new ReflectionPoint(3, 3);,通过HashSet本身的equals方法比较返回为相等;就可以通过复写equals方法和hashCode方法实现相等。

面试题

hashCode方法的作用?

1)以前这个题面试概率低,现在高,现在单位面试时也开始越来越深入面试底层;
2)存储的集合是哈希算法的集合,hashCode才有价值;

3)为了让“相等”的对象放在哈希值相同的区域,则如果equals方法比较是相同,也应该让它们的hashCode也要相同;

4)如果这个对象不需要存在哈希集合中,那么也不需要hashCode;

5)下面2个图的内容很重要;

6)下面的图3也可以帮助理解hashCode与equals方法:

    pt1的计算hashCode的字段被改了,就被存到了另外的哈希值区域,这样去remove的时候就找不到这个对象返回false(日积月累,内存越来越泄露)。


图1:hashCode方法、equals方法以及HashSet关系。


图2:详解hashCode方法与HashSet类。

Java中有内存泄露吗?

1)概念:内存泄露,有个对象或东西在内存中,但是一直都不再用了,结果它还没有被释放掉,就是内存泄露;

2)下图中造成内存泄露:pt1的计算hashCode的字段被改了,就被存到了另外的哈希值区域,这样去remove的时候就找不到这个对象返回false(日积月累,内存越来越泄露)。


图3:内存泄露实例,同时理解对象通过hashCode存储。

反射技术开发框架原理

分析

1)房子就是框架(提前做好了),锁是一个工具类;我用了两个东西:房子和锁都是别人做的,但是用法不一样;门窗被房子调用,锁被门窗调用;

2)框架和工具类的区别:

    a、都是别人写的,一个是别人调用你(框架),一个是你调用别人(锁);struts就是一个像房子的半成品,所以单位招人要求用框架,因为利用框架做事效率高;

    b、使用别人写的类有两种使用方式:你去调用别人的类;别人的类调用你的类(但是都是你在使用别人的类);

3)反射在前面内容的应用:调用某个类的main方法,到底调用哪个类的?开始是不知道的,在程序运行时才知道;也就是:我先写好了调用你的类,但是你还没有被写出来,这也是反射的一个好处。

反射的作用:实现框架功能

1)框架:

    我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架;用户需要使用我的框架,把门窗插入我提供的框架中;框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类;

2)框架要解决的核心问题:

    a、我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢。我写的框架程序怎样能调用到你以后写的类(门窗)呢?

    b、因为在写程序时无法知道要调用的类名,所以在程序中无法直接new某个类的实例对象了,而要用反射方式来做;

3)综合案例:

    a、先直接用new语句来创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashCode方法,比较两个集合的运行结果差异;

    b、然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异;

    c、引入eclipse对资源文件的管理方式的讲解。

用类加载方法管理资源和配置文件

分析

1)在实际开发中,一般是将.class打包成jar包,再给别人,而不是给源文件;所以不存在给别人配置文件做法;

2)InputStream is = new FileInputStream("config.properties");,一般也不会像这样使用相对路径,而是用绝对路径;

    a、而使用相对路径也是使用getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties")的方式;

    b、其中“cn/”,cn前面不能加斜杠;但是这种方式没有OutputStream,因为这里只读;未来要学的技术的配置文件都是放在classPath指定的目录下(回头把classPath搞的很清楚):用的就是类加载文件的方式;

3)InputStream is = new FileInputStream("d:\\config.properties");,但也会出现小问题,d:\\是去配的、运算出来的,不是写死的,所以用方法getRealPath();

4)如:金山词霸 / 内部,则可以得到其绝对位置,再拼上后面的文件路径;

5)用自己的类去加载的时候(类提供的简便方法),不是用类加载器时,可以用相对路径,也可以用绝对路径;

    a、资源跟包有关系,用相对路径;

    b、资源跟包没关系,用绝对路径;

    c、但是底层都是用getClassLoader;

6)代码演示见下面代码。

得到资源配置文件两种方式

1)InputStream is = new FileInputStream("config.properties");这种方式,可以得到一个FileInputStream或者FileOutputStream;具体操作是:

在eclipse中,window——Preferences————General——Open mode下面打勾或者不打勾;如果要存,还要一定得到配置文件的完整路径(一定要放在程序的内部)。

2)还有个方法得到资源文件的方式,最常用的,但是不能替代上面的方法:

    a、类加载器把.class文件加载进来,那么它也可以把普通资源文件加载,在指定目录下逐一查找要加载的文件;

    b、eclipse有个功能,会自动把窗口包下的文件编译,并且其下的像配置文件也就是存在包的目录下;那么有的文件不放在classPath路径下,则可以放在eclipse的源目录下。

代码

JavaBean

内省(IntroSpector)和JavaBean

1)内省IntroSpector,主要对JavaBean操作;
2)JavaBean是特殊的Java类:以小写set、get开头的方法,JavaBean有什么属性是根据get、set方法来推断的,而不是根据其属性(private)来看出来的;方法上去掉set、get,就是属性名,如:Age,如果第二个字母是小的,则把第一个字母编程小写——>age、gettime——>time、setTime——>time、getCPU——>CPU;
int getAge();
void setAge(int age);
    总之,一个类被当做JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java类内部的成员变量;
3)JavaBean主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,切方法名符合某种命名规则;
4)如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO);
5)一个符合JavaBean特点的类可以当做普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处:
    a、在JavaEE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,被人都这么用和要求这么做,那你就没什么挑选的余地;
    b、JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己通过getX方法来访问私有的x,有一定难度吧?用内省这套API操作JavaBean比用普通类的方式更方便;
6)eclipse中快捷封装方法操作:快速封装方法:选中代码——右键——重构Refactor——抽取方法Extract Method——输入方法名——Ok。

JavaBean复杂内省操作

1)直接new一个PropertyDescriptor对象的方式,来了解JavaBean的API的价值;先用一段代码读取JavaBean的属性,然后再用一段代码设置JavaBean的属性;
2)演示用eclipse将读取属性和设置属性的代码分别抽取成方法:
    a、只要调用这个方法,并给这个方法传递一个对象、属性名和设置值,它就能完成属性修改的功能;
    b、得到BeanInfo最好采用”obj.getClass()“方式,而不要采用”类名.class“方式,这样程序更通用;
3)采用遍历BeanInfo的所有属性方式来查找和设置某个ReflectPoint对象的x属性。在程序中把一个类当做JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到BeanInfo对象封装了把这个类当作JavaBean看的结果信息;
4)简单说来,IntroSpector.getBeanInfo :将一个类当成JavaBean,然后封装到BenaInfo里;但是不能得到单个的属性描述,只能得到所有的属性描述;
5)以后经常要写的代码:从集合中找到需要的元素,但是上面的Set、getProperty方法更简便;而麻烦方式也是很有价值:我们做开发,可能永远不是最好最棒的,只要在规定时间内,拿出结果;生存是第一,然后再在能力内追求最好。

代码

面试

写一个JavaBean。(见上面代码)

小结

配置文件

1)右键工程名——new——File(config.properties);

2)客户没有运行(javac)程序的时候,别人就不用改Java源程序,只用改这个properties文件

3)Properties在HashMap的基础上扩展了功能,可以把自己内存里面的键值对存放到硬盘上文件里去,也可以在初始化的时候把键值对加载进来;

4)容器有时得一个个填入值,而Properties可以从文件里面搞到一堆键值对。

关闭资源习惯
1)良好的习惯关闭资源(输入输出流等),不然马上就有小小的内存泄露;
2)两个资源的区别:不是输入流对象不被释放,而是这个对象关联的系统资源没有被释放;
3)关闭的本对象还在,但是在给操作系统说:把操作系统的资源关掉,如在eclipse窗口下写java;
4)Java资源即使关了(资源被gc管理),eclipse窗口却还在(eclipse被操作系统管理),所以先把对象关联的物理资源关掉。

总结

面试

Class.forName作用

问题:Class.forName的作用?

回答:返回字节码,而返回的方式有两种:

1)字节码已经加载,现在它待在虚拟机里;

2)字节码还没加载,则用类加载器去加载,再将字节码缓存,forName返回刚才加载进来字节码。

面向对象设计

1)画圆:人发出信号,其实是圆自己在调用draw方法,circle.draw();

2)人关门,司机刹车——变量在谁身上,方法就在谁身上(专家设计模式)。

0 0
原创粉丝点击