反射
概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。(以上来自百度)
反射也称为对类的解剖。把类的各个组成部分映射成一个个相应的Java类。
例:一个类有:成员变量,方法,构造方法,包等等信息。利用反射技术可以对一个类进行解剖。
其实只要拿到Java类的字节码对应的Class对象,就等于拿到了Java类中的各个成分。
反射的基石就是Class。
字节码
把一个类编译成Class文件在硬盘上就是一些二进制的代码,也就是字节码。
每一个字节码就是一个类的实例对象。
Class类
反射的基石是Class。Java程序中的各个类都属于同一类事物,描述这类事物的Java类名就是Class.
Class和class的区别
class:java中的类,用于描述一类事物的共性,描述该类事物。该事物有什么属性,有什么方法。至于这个事物属性的值是什么,
则由此类的实例对象确定,不同的实例对象有不同的属性值。
Class:指的是Java程序中的各个Java类是属于同一类事物,描述这类事物的Java类名就是Class.
例:描述人的是Person类,描述Person类的就是Class类。 Class是Java程序中各个Java类的总称。
获取Class对象的方法
1. 根据类名多的Class文件
类名 . class
例:Class cls1 = Person.class ; //Person.class就是Person类的字节码。
2. 对象.getClass()
例:Data.getClass()
3. 通过Class类中方法(主要用这个方法)
static Class<?> forName(String className) 用法:Class.forName(“包名.类名”)
例:Class.forName(“java.util.Arrays”);
九个预定义的Class对象
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void也表示为 Class 对象。
其中Class类中的boolean isPrimitive() 用于判断Class对象是否表示一个基本类型。
注意:
int . class == Integer.TYPE //这个用法是正确的,TYPE代表包装类所有包装的基本类型的字节码
数组也是一种类型,但不是基本数据类型,判断Class对象是否为数组,用Class类中方法。
boolean isArray();
总之
只要是在源程序中出现的类型,都有各自的Class类实例对象。方便操作。
演示代码
public class ReflectDemo {
public static void main(String[] args) {
System.out.println("判断int类型和Integer字节码是否相同 :"+(int.class == Integer.TYPE));
System.out.println("判断int.class是不是基本类型 :"+int.class.isPrimitive());
System.out.println("判断是不是数组类型 :"+int[].class.isArray());
}
}
演示结果
反射就是把Java类中的各种成分映射成相应的java类。
例如:一个java类用一个Class类对象来表示一个类中的组成成分:成员变量,方法,构造函数,包等信息,也用一个个的java类来表示。
就像汽车是一个类,汽车中的发动机,变速箱等也是一个一个的类。表示java类的Class类显然要提供一系列的方法,
用于获得其中的一般方法,构造方法,修饰符,包等信息。这些信息就是用相应的类的实例对象来表式的。
如:Field,Constructor,Package等。
个人理解
对于上述描述的个人理解。比如Person类,中有一个成员方法。先得到它的Person类的Class对象,
然后用Class类中的获取Method的方法获取类中的Method,用Method类型接收。就可以得到Person类中的成分的对象,
也就是Method类的对象。Method代表函数这个类型。而Method的对象代表的是一个个具体的方法。
下面我们就来介绍类中成员的描述类
Constructor
实例对象代表某个类中的一个构造方法
得到Constructor对象的方法
1. 得到某个类中所有的构造方法
Constructor<?> []getConstructors()
例:Constructor[] cons = Class.forName(“java.lang.String”) .getConstructors();
2. 得到某个类中的一个构造方法.
Constructor<T> getConstructor(Class<?>…parameter Types)
例:Constructor con = Class.forName(“java.lang.String”).getConstructors(StringBuffer.class)
参数中传入要获取的构造方法的参数类型的字节码
(就是说这个构造方法本来要传入的参数类型转成字节码传入这个方法就行了,绕口吧,用起来就好了)
1.5版本之后有一个新特性就是可变参数,所以现在写起来还好,1.5版本之前要在参数内传入一个数组。
通过得到的构造函数对象创建类的实例对象
得到了构造函数的对象,现在要通过构造函数的对象,来创建类的实例对象。
通过Constructor类中的方法
T newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
例:String str =(String)con.newInstance(new StringBuffer(“abc”));
个人理解:当已经得到类中一个构造方法的实例对象,调用newInstance()方法创建类对象时,
传入的参数中要和这个构造方法正常使用时要传入的参数列表一样。
Class类中也有newInstance()方法,但是Class类中的newInsrance()方法只能利用无参数的构造函数创建对象,
而用Constructor类来创建类的实例对象可以获取其他的构造函数,这也是Constructor类的好处之一。
Class类中newInstance()方法的用法
Class c1 = Class.forName(“包名.类名”); //先得到类的字节码
Object obj = c1.newInstance(); //通过newInstance方法获得类的午餐构造函数创建的对象。
记住
1. 得到构造方法时需要参数类型的字节码
2. 调用构造方法时需要参数类型的对象(1和2的类型是一样的)
Contructor类中方法演示
import java.lang.reflect.Constructor;
/*
* 反射知识中Constructor类演示
* */
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
//1.获得一个类的字节码
Class class1 = Class.forName("java.lang.String");
//2.获得这个类的一个构造函数的对象,并把要获取的构造函数的参数的字节码传入
Constructor constructor = class1.getConstructor(StringBuffer.class);
//3.调用newInstance()方法创建类的对象。参数传入构造函数要传入的参数
String string = (String)constructor.newInstance(new StringBuffer("我要去黑马!!"));
//演示结果,获取字符串中的第三个字符
System.out.println("字符串中的第三个字符:"+string.charAt(3));
}
}
运行结果
Field
Field对象代表某个类中的一个成员变量
获取Field对象的方法
1. Field getField(String name)
返回一个 Field 对象,它反映此 Class对象所表示的类或接口的指定公共成员字段。
(如果想要用这个方法返回私有成员变量对象会发生异常,有别的办法获取,请看下面的方法)
例:Field fieldx = 类的对象的字节码.getFiled(“x”) //其中x为要获取的变量名
2. Field[] getFields()
返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
意思就是说可以返回所有的公有变量的集合。
例:Field[] fields = 类对象的字节码.getFields();
3. Field getDeclaredField(Stringname)
返回一个Field对象,用法和getField()方法一样,但它可以返回私有的成员变量。
4. Field[] getDeclaredFields()
返回Class对象表示的类或接口的所有成员变量,公有私有都返回。
注:直接使用getDeclardField()方法获取私有成员变量是会发生异常的,所以这时我们要调用Field父类AccessibleObject中的方法:
void setAccessible(boolean flag) 传入true让私有的成员可以被反射。这个做法也称之为暴力反射,
也就是使用setAccessible(true)使private类型的成员变量也可以被获取值
Field中方法
1. Object get(Object obj)
返回指定对象上此Field
表示的字段的值。
例:int x = filedx.get(类对象)
参数中传入的是类的对象。
理解来说就是:要获取这个对象中的fieldx对应的变量的值。
fieldx不是对象上的变量,而是类上的变量,要用它去获取每个对象上对应的值。
类中描述了x变量,类的每个对象x的值也不同,所以x是固定的,对象是不固定的,参数就要传入对象。
2. void set(Object obj, Objectvalue)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
例:filedx.set(类对象,要转变的值)
Field中方法演示
import java.lang.reflect.Field;
/*
* Filed中方法演示
* */
//创建一个类,用于演示
class Person{
//定义一个私有成员变量,一个公有成员变量
public String name;
private int age;
//定义构造函数
public Person(String name , int age){
this.name = name;
this.age = age;
}
//输出方法
public String toString(){
return name+"::"+age;
}
}
public class FieldDemo {
public static void main(String[] args)throws Exception {
//创建Person对象
Person p1 = new Person("zhangsan", 20);
//获得类的字节码
Class class1 = p1.getClass();
//获取成员变量对象
//先获取公有的
Field fieldname = class1.getField("name");
//获取私有成员变量对象
Field fieldage = class1.getDeclaredField("age");
//要记得调用setAccessible方法让私有的成员可以被反射,不然会发生异常
fieldage.setAccessible(true);
//使用get方法获取
//使用get方法获取p1对象中name的值并输出
System.out.println(fieldname.get(p1));
//获取p1对象中的age的值并输出
System.out.println(fieldage.get(p1));
//给p1对象的成员变量设置值
fieldname.set(p1, "王五");
fieldage.set(p1, 80);
//输出设置之后的结果
System.out.println(p1.toString());
}
}
运行结果
Field练习
import java.lang.reflect.Field;
/*
* Field练习
*
* 需求
* 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"123"通过反射改成"我要去黑马"。
*
* 步骤
* 1.得到所有成员变量的数组
* 2.遍历数组
* 3.判断数组内的元素是否是String类型的,用==判断,可以用getType方法获得Field对象的类型的class
* 然后使用==来判断getType返回的class是否与String.class相同,从而判断数组中元素属否是String类型的
* 4.得到变量的旧的字符串123
* 5.将就字符串中的123替换成我要去黑马,获取新的字符串
* 6.将新的字符串赋给成员变量。
*
* */
//先创建一个类,用于演示
class ReflectPoint{
//定义成员变量
public String s1 = "123哈哈哈";
public String s2 = "123";
public String s3 = "哈哈哈";
//输出方法
public String toString(){
return s1+"---"+s2+"---"+s3;
}
}
public class FieldTest {
public static void main(String[] args)throws Exception {
//创建ReflectPoint对象
ReflectPoint rp = new ReflectPoint();
//输出没有修改之前的rp对象中的成员变量
System.out.println(rp);
//调用修改成员变量值的方法
ChangsStringValue(rp);
//输出修改之后的rp对象中的成员变量。
System.out.println(rp);
}
//修改成员变量值的方法
public static void ChangsStringValue(Object obj)throws Exception{
//先获取类中所有的成员变量
Field[] fields = obj.getClass().getFields();
//遍历数组
for(Field field : fields){
//判断数组内的成员是否是String类型
if(field.getType() == String.class){
//如果是,就得到变量对应的字符串
String oldString = (String)field.get(obj);
//将字符串中的123替换成我要去黑马!并返回一个新的字符串
String newString = oldString.replace("123", "我要去黑马!");
//将新的字符串赋给obj对象中对应的成员变量
field.set(obj, newString);
}
}
}
}
运行结果
Method
实例对象代表某个类中的某个方法。
获取Method对象的方法
1. Method getMethod(String name , Class<?> … prameterTypes)
name是方法名,prameterTypes是方法的参数的字节码
例:Method methodCharat = String.class.getMethod(“charAt” , int.class);
获取String类中的charAt方法,这个方法中要传入的参数是int类型参数。
2. Method[] getMethods()
获取类中所有公有方法和父类中的方法的集合
3. Method[] getDeclaredMethods()
获本类中所有方法,不管是公有还是私有
调用获取的方法
通过Method类中的 方法
Object invoke(Object obj , Object... args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
obj为使用这个方法的对象,args为要传入的参数
例:methodcharAt . invoke(str, 1);
str字符串,要获取第一个字符内容。
当invoke方法传入的第一个参数为null,说明这个方法是静态方法, 因为不需要对象来调用。
代码演示
import java.lang.reflect.Method;
/*
* 演示Method类中方法
* */
public class MethodDemo {
public static void main(String[] args)throws Exception {
//就用我前面演示写的Person类了,创建Person类对象
Person person = new Person("赵六", 26);
//获取person对象的字节码,然后调用getMethod方法获得Person类中的toString方法,因为该方法不用传入参数
//所以是,所以第二个参数为null
Method methodToString = person.getClass().getMethod("toString", null);
//使用invoke方法调用获取到的方法,person对象调用,不用传入参数
System.out.println(methodToString.invoke(person, null));
}
}
运行结果
使用反射调用类中的main方法
首先要明确为何要用反射
在写源程序时,并不知道使用者要传入的类名是什么,但虽然传入的类名不知道,但我们知道的是这个类中会有main方法。
所以利用反射的方式,通过使用者传入的类名(可定义String类型变量来代表类名)获取其main方法,然后执行相应的内容。
此时会出现下面的问题:
通过反射方式来调用main方法时,第一个参数要传入null,因为main方法是静态方法,不需要对象调用,类名调用即可。
那么如何为invoke方法传递第二个参数呢?
main方法接受的参数是一个数组,按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数。
当把一个字符串数组作为参数传递给invoke方法时,编译器会到底按照哪种语法进行处理呢?
jdk1.5会适应jdk1.4的语法,按jdk1.4的语法进行处理,把数组每个元素都当做一个参数。
所以,在给invoke方法传递第二个参数时,不能这种方式mainMethod . invoke(null,new String[]{“xxx”}),
编译器会把它当作jdk1.4的语法处理,数组中每个元素都会是一个参数,会出现异常。
解决办法:
mainMethod . invoke(null , new Object[]{new String[]{"xxx"}});
把String[] 作为Object[] 的第一个元素,这样数组拆开了也是我们需要的元素
mainMethod . invoke(null , (Object)new String[]{"xxx"});
直接把String[] 转成了Object类型,使编译可以通过
在写根据类名调用main类时可能会出现的问题及解决方法
该程序可能会出现数据角标越界异常,因为当args[0]代表类名时,args中并没有元素。需要我们传入元素,
在DOS命令行中,就直接用,如 :java MethodTest 要执行的类名 这种方法即可
那在eclipse中如何实现呢?
在代码区:右键→RunAs→RunConfigurations→Arguments→Program arguments中添加要执行的类名
代码演示
为演示方法创建的类
/*
* 这是为了演示通过反射调用其他类的main方法的类
* */
public class Test {
public static void main(String[] args) {
//写点儿代码,方便演示
System.out.println("Test类的main方法在被调用~");
//遍历main方法的参数
for(String arg : args){
System.out.println(arg);
}
}
}
演示代码
import java.lang.reflect.Method;
/*
* 使用反射方法调用main方法演示
*
* */
public class MethodTest {
public static void main(String[] args) throws Exception{
//获取要调用的类的类名,args数组的第一个元素
String className = args[0];
//获得className类的字节码
Class class1 = Class.forName(className);
//获得要被调用的类的main方法的对象
Method mainMethod = class1.getMethod("main", String[].class);
//调用被调用类的main方法
mainMethod.invoke(null, (Object)new String[]{"哈哈哈","嘿嘿嘿"});
}
}
运行结果
数组与Objcet的关系及其反射类型
具有相同维数和元素的数组属于同一个类型,即具有相同的Class的实例对象
数组和Object到底是什么关系呢?
Class类中有一个方法Class<? super T> getSuperclass() 返回这个方法的父类的字节码
通过数组的字节码调用此方法,得到的是Object的字节码。
所以说,数组的父类是Object,查看API也是这样的,Array是Object的子类
下面通过一段代码来看数组合Object的关系。
public class ArrayDemo {
public static void main(String[] args) {
//先来定义三个数组
int[] a1 = new int[2];
int[][] a2 = new int[2][3];
String[] a3 = new String[5];
//Object和数组的关系
Object obj1 = a1; //可以,因为所有数组都是Object的子类
Object obj2 = a3; //可以,不是基本数据类型的数组也是Object的子类
//Object[] obj3 = a1; //不可以,因为a1是int类型的以为数组,int不是Object类型的,所以编译不通过
Object[] obj4 = a2; //可以,因为a2是二维数组,第一维度是数组是Object的子类,第二维度还是数组,所以可以
Object[] obj5 = a3; //可以,因为String是Object类型的
}
}
Arrays.asList()方法处理int[] 和 String[]的差距
打印Array.asList(int类型数组) 的话是直接会打印出地址值来的
打印Array.asList(String类型数组) 的话可以打印出数组中的内容
发生以上的情况是为什么呢?
这是因为此方法在JDK1.4版本中,接收的Object类型的数组,
而String[]可以作为Object数组传入,因为String是Object类型的,
但是int[]不可以作为Object数组传入,int不是Object类型的,所以只能按照JDK1.5版本来处理。
在JDK1.5版本中,需要传入的是一个可变参数,所以int[]就被当作是一个object,
也就是一个参数,而不是数组传入,所以打印的结果还是跟直接打印int[]一样。
代码演示
/*
* Arrays类中的asList()方法在打印int[] 和 String[] 的不同效果
*
* */
import java.util.Arrays;
public class Demo {
public static void main(String[] args) {
//定义一个int类型数组,一个String类型数组
int[] a1 = new int[]{1,5,8,9,};
String[] a2 = new String[]{"sdf","fd","sdf","fsd"};
//int类型数组用asList方法输出的是地址值
System.out.println(Arrays.asList(a1));
//String类型数组用asList方法输出的就是数组内容。
System.out.println(Arrays.asList(a2));
System.out.println("int类型数组用遍历方法输出");
//所以int类型数组要用遍历方法输出
for(int a : a1){
System.out.print(a+" ");
}
}
}
运行结果
数组的反射应用
定义一个方法,用于输出数组
思考:定义的这个方法参数接收数组,就算使用isArray()方法判断了是否是数组,也不知道是什么类型的数组,
所以要用Array类中的方法来获取数组中的信息
无法得到一个数组的元素类型,因为有可能数组是这样的
Object[] a = new Object[](“a” ,1);
不能说清楚这数组到底是什么类型的,不过可以得到数组中某个元素的类型
a[0] . getClass() . getName();
代码示例
运行结果
/*
* 数组的反射应用,定义一个方法,用于输出数组
* */
import java.lang.reflect.Array;
public class ArrayTest {
public static void main(String[] args) {
//定义要输出的数组
int[] a1 = new int[]{1,8,5,};
String[] a2 = new String[]{"我","要","去","黑","马"};
String s = "一定去";
//调用输出数组方法
printArray(a1);
printArray(a2);
printArray(s);
}
//用于输出数组的方法,因为数组是Object的子类,所以参数接收Object类型对象,多态
public static void printArray(Object obj){
//先获取传入对象的字节码
Class class1 = obj.getClass();
//判断传入的对象是否是数组
if(class1.isArray()){
//如果是,就用Array中的getLenth方法,获取数组长度
int len = Array.getLength(obj);
//建立循环
for(int x=0 ; x<len ; x++){
//通过Array类中的get方法获得数组中的元素,并打印
System.out.println(Array.get(obj, x));
}
//如果传入的不是数组
}else{
//那就直接输出obj
System.out.println(obj);
}
}
}
运行结果
ArrayList和HashSet的比较和HashCode的分析
先来看一段代码
import java.util.ArrayList;
import java.util.HashSet;
//演示类
class HashCodeDemo{
private int x;
private int y;
public HashCodeDemo(int x , int y){
this.x = x;
this.y = y;
}
}
public class ArrayListHashCodeDemo {
public static void main(String[] args) {
//创建HashCodeTest对象,专门用于演示
HashCodeDemo h1 = new HashCodeDemo(1, 2);
HashCodeDemo h2 = new HashCodeDemo(4, 7);
HashCodeDemo h3 = new HashCodeDemo(1, 2);
//创建ArrayList数组,将HashCodeTest对象作为对象传入
ArrayList<HashCodeDemo> aList = new ArrayList<HashCodeDemo>();
aList.add(h1);
aList.add(h2);
aList.add(h3);
aList.add(h1);
//获取alist数组的长度
System.out.println("ArrayList数组长度为:"+aList.size());
//创建HashSet数组,将HashCodeTest对象作为对象传入
HashSet<HashCodeDemo> hashSet = new HashSet<HashCodeDemo>();
hashSet.add(h1);
hashSet.add(h2);
hashSet.add(h3);
hashSet.add(h1);
//获取hashSet数组的长度
System.out.println("HashSet数组长度为:"+hashSet.size());
}
}
运行结果
我们可以从以上代码中看到,ArrayList数组和HashSet数组中传入的对象是一样的,但ArrayList数组中有四个元素,HashSet数组中只有三个元素,
这是因为ArrayList允许重复元素的存在,而HashSet数组不允许。
可是问题又来了,h1 和 h2 传入的参数相同,所以对象应该是相同的。但为什么两个参数相同的对象也能加入到HashSet数组中呢?
就因为一个方法hashCode()方法。
hashCode方法
hashCode方法的作用
比如在Hash算法这种类型的集合中,有一万个元素,要放进一个新的元素,不可能一个一个的去集合中查找,
看集合中有没有相同的并且已经存在的元素。所以会根据HashCode值给元素分区。
得到要加进集合的新元素的HashCode值之后去它对应的区中查找,这样就快很多了。
如果想要HashSet集合中不存在参数相同的元素,要做两步
1. 在元素对象的类中复写equals方法
2. 在元素对象的类中复写hashCode方法
复写equals方法是为了判断传入的参数是否一样。
复写hashCode方法是为了让传入参数一样的对象HashCode值一样,这样在对象存入集合时,就不会存在重复元素了。
如果两个对象通过equals判断相等的话,应该让它们的HashCode也相等
如果没有复写hashCode方法,对象的hashCode值是按照内存地址计算的。
这样即使两个对象的内容是相等的,但是存入集合中的内存地址值不同,导致hashCode值也不同,被存入的区域也不同。
所以两个内容相等的对象,就可以存入集合中。
复写的equals方法和hashCode方法有这样的特点
1. 如果两个对象相同,那他们的hashCode值也相同
2. 如果两个对象的hashCode相同,它们不一定相同
比如:”BB”"Aa"equals不同但hashCode相同
内存泄露
当一个对象存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,
否则对象修改后的哈希值与最初存储进HashSet集合时的哈希值就不同了。在这种情况下,调用remove方法来删除这个对象的引用,
会失败,因为hashCode值改变了,就找不到这个对象了。从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
某些对象不再使用了,占用着内存空间,并未被释放,就会导致内存泄露;也就是说当程序不断增加对象,
修改对象,删除对象,日积月累,内存就会用光了,就导致内存溢出。
Eclipse是一个强大的编程软件,而复写equals和hashCode又是我们经常要做的操作,所以eclipse提供了快捷键:
文字区右键→source→Generate hashcode() and equals()…
下面我们来看看HashSet集合中还能否存入复写过equals和hashCode方法的类的相同的对象。
import java.util.ArrayList;
import java.util.HashSet;
//演示类
class HashCodeTest {
private int x;
private int y;
public HashCodeTest(int x , int y){
this.x = x;
this.y = y;
}
//复写equals和hashCode方法,这里我就不用eclipse软件自带的功能了,我自己来复写
public boolean equals(Object obj){
//先判断传图的对象是否是HashCodeTest或者它的子类
if(!(obj instanceof HashCodeTest))
//如果不是抛出异常
throw new ClassCastException("类型不匹配");
//如果是
else{
//先将obj转成HashCodeTest类
HashCodeTest hashCodeTest = (HashCodeTest)obj;
//然后判断调用这个方法的对象的参数和传入的对象的参数是否相同,并返回结果
return this.x == x && this.y == y;
}
}
//复写hashCode方法
public int hashCode(){
//使用参数,来计算hashCode值,使参数相同的Hashcode值相同
return x*18+y;
}
}
public class ArrayListHashCode {
public static void main(String[] args) {
//创建HashCodeTest对象,专门用于演示
HashCodeTest h1 = new HashCodeTest(1, 2);
HashCodeTest h2 = new HashCodeTest(4, 7);
HashCodeTest h3 = new HashCodeTest(1, 2);
//创建ArrayList数组,将HashCodeTest对象作为对象传入
ArrayList<HashCodeTest> aList = new ArrayList<HashCodeTest>();
aList.add(h1);
aList.add(h2);
aList.add(h3);
aList.add(h1);
//获取alist数组的长度
System.out.println("ArrayList数组长度为:"+aList.size());
///创建HashSet数组,将HashCodeTest对象作为对象传入
HashSet<HashCodeTest> hashSet = new HashSet<HashCodeTest>();
hashSet.add(h1);
hashSet.add(h2);
hashSet.add(h3);
hashSet.add(h1);
//获取hashSet数组的长度
System.out.println("HashSet数组长度为:"+hashSet.size());
}
}
运行结果
框架的概念及用反射技术开发框架的原理
框架
通过反射调用Java类的一种方式。
如房地产开发商造房子给用户住,门窗和空调等等内部都是由用户自己安装,房子就是框架,用户需使用此框架,
安好门窗等放入到房地产商提供的框架中。
框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。
框架建立要解决的核心问题
我们在写框架(造房子)的时候,被调用的类(安装的门窗等)还未出现,框架无法知道要被调用的类名,
所以在程序中无法直接new某个类的实例对象,所以要用反射来实现。
简单框架程序的创建步骤
1. 创建配置文件
Eclipse软件中在工程名上右键→New→File 配置文件名.properties文件里面的内容以键值对的形式存在
2. 通过字节输入流读取properties文件中的内容
3. 定义Properties对象
4. 通过Properties类中的load方法,将Properties和输入流对象关联
5. 通过Properties类中的getProporyt方法,通过key获取value
6. 创建对象,通过使用获取到的类名,创建对象
Class.forName(ClassName) . newInstance();
注意:要把类型转换一下,等号左边,加要创建的对象的类名
如下面程序所示
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
/*
* 利用反射技术开发框架
*
* 就用上一个ArrayList和HashSet的例子,通过修改配置文件的信息,来创建不同的集合,演示效果
* */
public class ReflectTest {
public static void main(String[] args)throws Exception {
//配置文件已创建完
//定义字节输入流,读取配置文件信息
InputStream is = new FileInputStream("123.properties");
//定义Properties对象
Properties prop = new Properties();
//通过load方法,将prop和读取流相关联
prop.load(is);
//关闭流资源
is.close();
//使用getProporty方法获取通过键获取值
String className = prop.getProperty("className");
//通过获取的类名,创建对象,因为我们要创建的是集合对象,所以用Collection类型变量接收,父类引用指向子类对象,多态。
Collection collection = (Collection)Class.forName(className).newInstance();
//给数组传入元素
collection.add("黑马程序员");
collection.add("我来了");
collection.add("黑马程序员");
//输出集合长度
System.out.println("集合长度为:"+collection.size());
}
}
当配置文件为className=java.util.ArrayList时,输出的值为3
当配置文件为className=java.util.HashSet时,输出的值为2
用输入流读取配置文件,不管是绝对路径还是相对路径,都有可能找不到配置文件而发生异常。
或者当我们完成一个项目,不可能把整个工程全给用户,一般都是只把源文件打包给用户,所以要把配置文件放到源文件目录下。
所以这时我们就要换一种方法来进行资源文件的加载。
资源文件的加载:使用类加载器。
1. 一个类加载器能加载.class文件,那这个加载器也能捎带手加载classpath环境下的其他文件,
它只能加载classpath环境下的那些文件。注意:直接使用类加载器时,传入的文件名不能以/打头。
2. 资源文件由类加载器ClassLoader来加载进内存
先用getClassLoader()方法获取类加载器
然后用类加载器中的getResourceAsStream(Stringname)方法,将配置文件(资源文件)加载进内存。
利用类加载器来加载资源文件,要把配置文件放置的包名一起写上。
3. Class类也提供getResourceAsStream方法来加载资源文件,其实它内部就是调用了类加载器的getResourceAsStream方法。
这时,配置文件是相对类文件的当前目录的,也就是说用这种方法,资源文件前面可以省略包名。
如:类名.class.getResourceAsStream(“资源文件名”)
代码演示
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
/*
* 利用反射技术开发框架
*
* 就用上一个ArrayList和HashSet的例子,通过修改配置文件的信息,来创建不同的集合,演示效果
* */
public class ReflectTest {
public static void main(String[] args)throws Exception {
//配置文件已创建完
//定义字节输入流,读取配置文件信息
//InputStream is = new FileInputStream("123.properties");
//方法一:使用类加载器通过绝对路径加载配置文件
//InputStream is =
//ReflectTest.class.getClassLoader().getResourceAsStream("blog/Demo/123.properties");
//方法二:使用Class方式进行加载,通过相对路径
//InputStream is = ReflectTest.class.getResourceAsStream("123.properties");
//方法三:利用Class方法进行加载,通过绝对路径
//跟方法一不同的是,它前面要加\
InputStream is = ReflectTest.class.getResourceAsStream("/blog/Demo/123.properties");
//定义Properties对象
Properties prop = new Properties();
//通过load方法,将prop和读取流相关联
prop.load(is);
//关闭流资源
is.close();
//使用getProporty方法获取通过键获取值
String className = prop.getProperty("className");
//通过获取的类名,创建对象,因为我们要创建的是集合对象,所以用Collection类型变量接收,父类引用指向子类对象,多态。
Collection collection = (Collection)Class.forName(className).newInstance();
//给数组传入元素
collection.add("黑马程序员");
collection.add("我来了");
collection.add("黑马程序员");
//输出集合长度
System.out.println("集合长度为:"+collection.size());
}
}
三种方法都可以演示出结果 亲测有效~
谢谢大家的观看~!
-----------android培训、java培训、java学习型技术博客、期待与您交流!---------