黑马程序员基础加强-反射-类加载器的使用
Java反射
反射基础分析
概述总结:
反射就是把java类中的各个成员分别映射成相应的java类;
例如,
一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
反射的基石——Class类
Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于属性的值是什么,则是由这个类的实例对来确定的;不同的实例对象有不同的属性值,Java程序中的各个Java类是否属于同一事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class;
Class类描述了类的名字,类的访问属性,类所属包名,字段名称列表,方法名称列表等等;学习反射,首先要明白Class类。
如:
很多的人用什么类标示?那么很多的Java类用什么标示?
人—Person
Java类—Class
Person代表人,他的实例对象就是张三,李四等这样一个个具体的人;Class类代表Java类,他的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码等等;
一个类被类加载器加载到内存中,占用一定的存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间可分别用一个个对象来标示,这些对象具体相同的类型。
什么是字节码?
当源程序中用到某个类时,首先要从硬盘把这个类的那些二进制代码即:一个类编译成class放在硬盘上以后,就是一些二进制代码,要把这些二进制代码加载到内存中里面来,再用这些字节码去复制出一个一个对象来。
如何得到各个字节码对应的实例对象(Class类型),有三种方法:
1, 类名.class;例如System.class;
2, 对象.getClass();例如:new Data().getClass();
3, Class.forName(“包名.类名”);例如:Class.forName(“java.util.Data”);// 这种方式较为简单,只要知道类的完整名称即可。不需要使用该类,也不需要去调用具体的属性和行为。就可以获取到Class对象了;仅知道类名就可以获取到该类字节码对象的方式,更有利于扩展
九个预定义的Class:
1,八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。
2,Integer.TYPE是Integer类的一个常量,(但是int.class和Integer.class是不相等的false,它们是两个不同的字节码)它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。
基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
数组类型的Class实例对象用的方法是:Class.isArray();
注意:
只要是源程序中出现的类型,都有各自的Class实例对象,例如:int[],void…都是代表一个类型。
Class类中常用方法列举:
1, static Class forName(String className);//返回给定字符串名的类或接口相关类的Class对象;
2, Class getClass();//返回的是Object运行时的类,即返回Class对象即字节码对象;
3, Constructor getConstructor();//返回Constructor对象,反映此Class对象所代表的类指定公共构造方法;
4, Field getField(String name);//返回Filed对象,标示Class对象所代表的的类或接口指定公共成员字段;
5, Field[] getField();//返回Field对象数组,代表类中成员字段;
6, Method getMethod(String name,Class…parameterTypes);//返回一个Method对象,标示次Class对象锁代表的类的指定公共成员方法;
7,Method[] getMehtods();//返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
8,String getName()以String形式返回此Class对象所表示的实体名称。
9,String getSuperclass()返回此Class所表示的类的超类的名称
10,boolean isArray()判定此Class对象是否表示一个数组
11,boolean isPrimitive()判断指定的Class对象是否是一个基本类型。
12,T newInstance()创建此Class对象所表示的类的一个新实例。
通过Class对象来获取类实例
因为Class类没有构造方法,所以只能通过该类方法来获取类的实例对象
具体步骤:
1, 查找并加载指定名字的字节码文件存储进内存,并封装成Class对象;
String className = “包名.Person”;
Class clas = Class.forName(className);
2, 通过Class对象的newInstance()方法创建该Class对应类的实例对象;
Person p = (Person)clas.newInstance();
3,调用构造函数进行初始化
Person p = new Person();
示例:
package 加强Day01.黑马;
class Person{
private String name;
private int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return name+":"+age;
}
}
public class ReflectDemo {
public static void main(String[] args) throws Exception{
Class cla = Class.forName("加强Day01.黑马.Person");//获取Person类的Class对象
Person p = (Person)cla.newInstance();//通过newInstance方法获取对应类对象。
System.out.println(p.toString());
}
}
构造方法的反射应用
Constructor类
Constructor类代表某个类的构造方法;
如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定构造函数进行初始化,这是怎么办呢?
首先不能再使用Class类中newInstance方法,因为要通过指定的构造函数进行对象的初始化,那么就要用到Constructor来获取构造函数
获取构造方法例子:
1, 得到某个类所有的构造方法:
Constructor[] constructors = Class.forName(“java.lang.String”).getConstructor();
2, 得到某一个构造方法:
Constructor constrctor =
Class.forName(“java.lang,String”).getConstructor(StringBuffer.class);
//获取方法时要用到的类型。
小知识点:
在getConstructor(可变参数)方法中接收的参数没有限制,为什么呢?
因为可以传入1.5新特性可变参数。
再问:那么没有可变参数再来接收多个参数怎么写呢?
我们可以通过数组类获取
得到Constructor对象后干什么呢?
查阅API文档java.lang.reflect.Constructor<T>可参见该类的方法
有了对象就可以创建实例对象(接上面示例):
1,通常方式:String str = new String(new StringBuffer(“abc”));
2,反射方式:String str = (String)constrctor.newInstance(new StringBuffer(“abc”));
Class.newInstance()方法:
例子:String obj = (String)Class.forName(“java.lang.String”).newInstance();
该方法内部是先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部具体代码怎么写的呢?
实际上用到了缓存机制类保存默认构造方法的实例对象。
总结:
1, 在创建实例时候,newInstance()方法中的参数列表必须和getConstructor()方法中的参数列表一致。
2, newInstance()方法在构造一个实例对象时,每调用一次就构造一个对象;
3, 利用Constructor类来创建实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建实例对象。
Field类java.lang.reflect.Field
Field类代表某个类中的一个成员变量;
常用方法:
1, Field getField(String 变量);//只能获取公有和父类中公有的变量;
2, Field getDeclaredField(String 变量);//获取该类中的任意类型的变量,包括私有的;
3, setAccessible(true);//若是私有的字段,即之前变量是私有的,该方法可以将私有字段进行取消权限获取,也称为暴力访问;
4, set(Object obj,Object value);//将指定对象变量上的Field对象标示的字段设置为指定的新值;
5, Class<?> getType();//返回一个Class对象。
6, Object get(Object obj);//返回指定对象上Field表示的字段的值;
示例:
package 加强Day01.黑马;
public class ReflectDemoxy {
public int x;
private int y;
//使用快捷键快速生成构造函数
public ReflectDemoxy(int x, int y) {
super();//
this.x = x;
this.y = y;
}
}
public class ReflectDemo {
public static void main(String[] args) throws Exception{
Reflect();
}
public static void Reflect()throws Exception{
ReflectDemoxy ref = new ReflectDemoxy(3,5);
Field fid1 = ref.getClass().getField("x");//通过获取Class对象,得到一个字段,公有的;
//fid1它不代表一个值,不是某个变量上的某个值,它代表一个变量。
//作用是类上要用它去取是某个对象对应的值。
//若要取出这个变量值,我们应该明确是在某个类身上的值:fid1.get(ref)
System.out.println(fid1.get(ref));//取出指定类身上的变量。
Field fid2 = ref.getClass().getDeclaredField("y");
//这时可以看到任何类型的变量,但是对于私有字段不能取出,所以需要Field里的方法setAccessible();暴力访问;
fid2.setAccessible(true);
System.out.println(fid2.get(ref));
}
}
练习:把一个类里所有的String类型的字段里的值b变a;
package 加强Day01.黑马;
public class ReflectDemoxy {
public int x;
private int y;
public String str1 = "ball";
public String str2 = "abstract";
public String str3 = "haohaoxuexi";
//使用快捷键快速生成构造函数
public ReflectDemoxy(int x, int y) {
super();//
this.x = x;
this.y = y;
}
public String toString(){
return str1+":"+str2+":"+str3;
}
}
package 加强Day01.黑马;
import java.lang.reflect.Field;
public class ReflectTest {
public static void main(String[] args) throws Exception{
// new一个对象
ReflectDemoxy ref = new ReflectDemoxy(3,4);
//获取所有字段
Field[] fields = ref.getClass().getFields();
//对所有字段遍历并判断是否与String类的字节码相等;
for(Field field: fields){
//if(field.getType().equals(String.class))
if(field.getType() == String.class){
//获取对应字段的值的字符串;
String oldValue = (String)field.get(ref);
//将字符串里的b换成a
String newValue = oldValue.replace('b', 'a');
//改对象的字段
field.set(ref,newValue);
}
}
//打印替换后的结果:
System.out.println(ref);
System.out.println(ref);
System.out.println(ref);
}
}
Method类java.lang.reflect.Method
Method类代表某个类中的一个成员方法;
简单的说:
实际上我们是使用反射的方式拿到指定类的字节码里面的方法,再用这个方法去作用于某个对象。
常用方法:
1, Method[] getMethods();//只获取公共和父类中的方法;
2, Method[] getDeclaredMethods();//获取本类中所有方法包含私有
3, Method getMethod(“方法名”,参数.class);//若是空参数可以写null;因为重载,参数列表和类型可能不一致,参数也可接收可变参数。
4, Object invoke(Object obj,Object…obj(参数));//调用方法,
注意invoke是调用方法对象身上的方法:
砖家模式:谁拥有这个数据谁就是调用它的砖家,那么就应该把方法分配给它!
比如停车:
调用者:对象是车,只有车知道该怎么停下来,是车在调用刹车的动作;
指挥者:人在指挥车停车的动作,给车发出信号让车去执行,停车的动作只有车才能做到,车拥有这个停车的动作,它就是这个的砖家,那么就应该把停车方法分配给车,而不是给人;
思考:
当方法对象身上的方法即:invoke(null,1)第一个参数是null,不通过对象就调用了,说明这个方法是静态的,因为静态方法是不需要对象的。
示例:
package 加强Day01.黑马;
import java.lang.reflect.Method;
public class MethodTest {
public static void main(String[] args)throws Exception {
//获取字符串中某一个字符
String str = "abc";
//通常方式
System.out.println(str.charAt(1));
//反射方式
Class cla = Class.forName("java.lang.String");
Method charAtMethod = cla.getMethod("charAt",int.class);
System.out.println(charAtMethod.invoke(str, 1));
}
}
JDK1.4和JDK1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object…obj);
Jdk1.4:public Object invoke(Object obj,Object[] args);
安装jdk1,4的语法,需要见那个一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码页可以用jdk1.4改写为charAt.invoke(“str”,new Object[]{1})的形式。
接上面示例:
System.out.println(charAtMethod.invoke(str, new Object[]{2}));
用反射方式执行某个类中的main方法
首先明确为什么要使用反射?
因为在写源程序时,或程序写好后,不知道使用者将传入什么类,类名是什么等,但是我们知道这个类中的方法有main这个方法。这时我们可以通过反射的方式把使用者传入的类名(可定义字符串变量作为传入类名的入口,通过这个变量代表类名),在内部通过传入类名来获取main方法,然后执行相应内容。
需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法;
分析:
在启动Java程序的main方法参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?
按jdk1.5的语法,整个数组是一个参数,而按照jdk1.4的语法,数组中的每个元素对象对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会执行哪种语法来处理呢?
Jdk1.5兼容jdk1.4语法,所以会按照1.4语法去处理,即把数组打散成若干个单独的参数,所以在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“…”})javac只有把它当做jdk1.4语法进行理解,而不会当做jdk1.5语法来解释,因此会出现参数类型不对的问题。
解决办法:
1, mainMethod.invoke(null,new Object[]{new String[]{“xxx”}});
2, mainMethod.invoke(null,(Object)new String[]{“xxx”});。编译器会做特殊处理,不会吧它当数组看,也就不会数组打散成若干个参数了。
示例:
测试类
package 加强Day01.黑马;
public class MainTest {
public static void main(String[] args) {
// 测试类
for(String arg:args){
System.out.println(arg);
}
}
}
package 加强Day01.黑马;
import java.lang.reflect.Method;
//写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法
public class ReflectMain{
public static void main(String[] args)throws Exception{
//普通方式,因为静态所以直接类名.main()
MainTest.main(new String[]{"111","222","333"});
System.out.println(".............");
//反射方式:
String strName = args[0];//传进来的参数不确定类型不确定;
Class classMain = Class.forName(strName);//获取字节码对象
Method method = classMain.getMethod("main",String[].class);
//调用方法对象的方法invoked,去执行
//方法一:使用Object超类
method.invoke(null,(Object)new String[]{"111","222","333"});
//方法二:将数组打包,那个编译器拆包后还一个String[]数组。
method.invoke(null, new Object[]{new String[]{"111","222","333"}});
}
}
数组的反射
1,具有相同位数和元素类型的数组属于同一个类型,即具有相同的Class示例对象。
示例:
<span style="white-space:pre"> </span>int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
System.out.println(a1.getClass()==a2.getClass());
System.out.println(a1.getClass()==a3.getClass());
System.out.println(a1.getClass()==a4.getClass());
System.out.println(a1.getClass().getName());
System.out.println(a4.getClass().getName());
打印结果是:
True
False
False
[I
[Ljava.lang.String;
分析结果:
从这里可以看出,当同是一维数组或二维数组,并都是int类型相同,所以为true。由于字节码一定是唯一的并且字节码文件就代表这个类,所以getClass()获取的字节码文件对象是一个,地址值也是一样。
数组类型的父类Object
任何元素类型的数组他们的直接父类就一个java.lang.Object;
我们可以通过Class类里面的方法getSuperclass();返回超类
示例:
package 加强Day01.黑马;
import java.lang.reflect.*;
import java.util.Arrays;
public class ReflectArraysTest {
public static void main(String[] args) {
int[] a1 = new int[]{1,2,3,};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass()==a2.getClass());
//System.out.println(a1.getClass()==a3.getClass());
//System.out.println(a1.getClass()==a4.getClass());
System.out.println(a1.getClass().getName());
System.out.println(a4.getClass().getName());
//获取超类类名
System.out.println(a1.getClass().getSuperclass());
System.out.println(a4.getClass().getSuperclass());
//判断是否成立?是否属于对应类型
Object obj1 = a1;
Object obj2 = a4;
//Object[] obj3 = a1;不能从int转Object,类型不匹配,基本数据类型不能转
Object[] obj4 = a3;//等效于数组里面装是一个数组,而该数组是属于Object的。
Object[] obj5 = a4;//String是属于Object的
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
/* Arrays.asList()方法处理int[]和String[]时的差异。
* 打印Arrays.asList(a1);还是跟直接打印a1是一样的
打印Arrays.asList(a4);就会把a4的元素打印出来。
这是因为此方法在JDK1.4版本中,接收的Object类型的数组,
而a4是String类型可以作为Object数组传入。但是a1是基本数据类型不可以作为Object数组传入,所以只能按照JDK1.5版本可变参数来处理。
在JDK1.5版本中,传入的是一个可变参数,所以a1就被当作是一个object,也就是一个参数,
而不是数组传入,所以打印的结果还是跟直接打印a1一样,就无法直接显示数组中的元素。
*/
}
}打印结果:
true
[I
[Ljava.lang.String;
class java.lang.Object
class java.lang.Object
[[I@1bab50a]
[a, b, c]
数组反射应用
若我们需要获取数组里面的值,或设置值,得到数组的长度,用反射怎么做?
在Class类中有Array这个类
演示数组反射的作用:
步骤:
1,先获取Class对象
2,用Class类的isArray()方法判断是否是数组
3,不是数组就直接打印,是数组就用Array类的getLength(obj)静态方法获取长度
4,for循环遍历数组并用Array类的静态方法get(obj,index)获取数组中的对应角标的元素
示例接上面:
ArrayTest(a4);
ArrayTest("abc");
}//数组的反射应用:
public static void ArrayTest(Object obj){//可以接收一堆(数组)或一个类型
Class clazz = obj.getClass();//通过元素获取Class对象
//判断是否是数组类型的字节码
if(clazz.isArray()){
int len = Array.getLength(obj);//通过Array的里的方法获取长度。
for(int x=0; x<len;x++){
System.out.println(Array.get(obj, x));
}
}
else{
System.out.println(obj);
}
}
ArrayList与HashSet的比较及HashCode分析(面试重点)
ArrayList是一个有顺序的集合,相当于一个数组,当我们放一个对象进去的时候,放进去的是对象的引用,然后再次放对象。是先后顺序放进去,不管相同。
HashSet是放之前判断集合里面是否有相同的引用变量,若有就不放,若要放就必须将原来的那个删除掉;
HashCode方法的作用(面试常问)
比如:我们在查找一个集合中是否包含某个即将插入的对象,通常做法是逐一将每个每个元素与要查的对象进行比较,当发现某个元素和要查找的对象进行equals比较相等时,就停止查找并返回信息;但是这样做的效率很低,这样就有了哈希算法!
有人发明的了哈希算法来提高集合中去查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码值,将哈希码分组,每组分别对应某个区域,再根据要查找的哈希码就可以确定该对象应该存储到哪个区域;如下图:
注意:
1, 要想HashCode方法有价值的话,前提是对象存入的是hash算法这种类型的集合当中才有价值。如果不存入是hashCode算法的集合中,则不用复写此方法。
2, 如果没有复写hashCode方法,对象的hashCode值是按照内存地址进行计算的。这样即使两个对象的内容是想等的,但是存入集合中的内存地址值不同,导致hashCode值也不同,被存入的区域也不同。所以两个内容相等的对象,就可以存入集合中。
3, 当一个对象存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,调用contains方法或者remove方法来寻找或者删除这个对象的引用,就会找不到这个对象。从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。(程序中某一些对象不再被使用,以为被删掉了,但是没有,还一直在占用内存中,当这样的对象慢慢增加时,就会造成内存泄露。)
示例:
package 加强Day01.黑马;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public class HashCodeDemo {
public static void main(String[] args) {
//Collection collection =new ArrayList();
Collection collection =new HashSet();
HashCodeTest hct1=new HashCodeTest(1,2);
HashCodeTest hct2=new HashCodeTest(3,4);
HashCodeTest hct3=new HashCodeTest(1,2);
collection.add(hct1);
collection.add(hct2);
collection.add(hct3);
collection.add(hct1);
//hct1.setX(5);
//collection.remove(hct1);
System.out.println(collection.size());
}
}
//测试类
class HashCodeTest{
private int x;
public int y;
public HashCodeTest(int x,int y){
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HashCodeTest other = (HashCodeTest) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
public void setY(int y) {
this.y = y;
}
public String toString() {
return "HashCodeTest [x=" + x + ", y=" + y + "]";
}
}
反射的作用--》实现框架功能
框架与框架要解决的核心问题
比如;开发商建设房子给用户,然后由用户自己安装门窗大家电等;
开发商做的房子就是框架,用户使用框架,把门窗插入到框架中。
框架与工具类的区别:
框架是调用用户提供的类,工具是被用户的类调用;
框架要解决的核心问题
比如我在写框架时候,你这个用户可能还在上小学,还不会写程序,那么我写的框架程 序怎么才能调用你以后写的类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射的方式来做Class.forName();
简单的框架程序代码实现:
分析步骤:
1, 定义配置文件config.properties;,在配置文件中定义好配置信息如:
className=java.util.ArrayList,等号左边是键,右边是值;
2, 创建文件读取流读取配置文件的信息,写出配置文件的绝对路径;
3, 通过Properties类的load方法将配置信息存储到集合中;
4, 关闭读取流;
5, 通过Properties类中的getProperty()方法获取value值即某个类名;
6, 后面步骤就可以通过反射的方式用Class类来获取实例
示例:
在源程序中不出现这个类,而通过配置文件来配,在再通过反射来获取该类的实例对象
package 加强Day01.黑马;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class ReflectDemo3
public static void main(String[] args)throws Exception{
// 创建文件读取流,读取配置文件信息
InputStream ins = new FileInputStream("config.Properties");
//将流中的数据利用集合Properties类中的load方法存储进集合
Properties pro =new Properties();
pro.load(ins);
//关闭读取流对象;
ins.close();
//通过集合getProperty(键)方法获取value 即某类名
String className = pro.getProperty("className");
//获取Class对象,通过反射方式创建实例对象;
Class clazz = Class.forName(className);
Collection coll = (Collection)clazz.newInstance();
}
}
类加载器
概述:
类加载器就是将.class文件加载进内存,也可以将普通文件中的信息加载进内存;
具体加载方式:
Java源文件(.java)在经过java编译器编译后被转换成java字节码(.class文件)。类加载器就负责读取java字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个java类,再通过该实例的newInstance()方法就可以创建出该类的一个对象。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。
Java.lang.ClassLoader类介绍
作用:
1, 就是根据一个指定的类的名称,找到或生成其对应的字节码,然后从这些字节码中定义一个java类,即Class类实例对象。
2, ClassLoader还负责加载java应用所需的资源,比如图像文件和配置文件等,将配置文件放到.class文件目录中一同打包,类加载器就会一同加载。
加载资源文件
方法一:资源文件是由类加载器ClassLoader来加载进内存,具体就是使用getClassLoader()方法获取加载器,然后用类加载器的getResourceAsStream(String name)方法,将配置文件即资源文件加载进内存。
注意:利用类加载器来加载配置文件,需要把配置文件放置的包名一起写上,不然会报异常,这种方法只有读取功能。
方法二:在java.lang.Class类中也提供了getResourceAsStream(String name)方法来加载资源文件,这个时候是配置文件是相对类文件的当前目录,所以不用写包名。其实该方法内部也还是调用了ClassLoader的方法的。
如:Person.class.getResourceAsStream(“配置文件名”);
常用方法:
getParent();// 返回该类加载器的父类加载器。
loadClass(String name);// 加载名称为name的类,返回Class实例对象
findClass(String name);// 查找名为name的类,返回的也是Class实例。
findLoadedClass(String name);// 查找名为name的已被加载过的类。
defineClass(String name,bytr[] b,int off,int len);//把字节数组b中的类容转换成java类。
关于配置文件存放路径的疑问
根据上面示例,和配置文件的加载,我们可以了解到:
1,配置文件如果和classPath目录没有联系,那么要写上绝对路径;
2,配置文件如果和classPath目录有联系,比如是在该目录中或其子目录中,那么可以省略前面路径写文件名即可。
示例:加载资源文件
package 加强Day01.黑马;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class ReflectClassLoader {
public static void main(String[] args) throws Exception{
// 首先创建一个Properties对象来存储配置文件
Properties pro = new Properties();
//简单的相对性路径读取流演示,即当配置文件和.java在同一目录
//InputStream ins1 = new FileInputStream("config.properties");
//类加载器能加载.class文件,那么在classPath环境变量下附带的配置文件必须也能加载,
//注意它也只能在该环境变量下加载,且使用类加载器时候不能以“/”打头;
//先将配置文件转为字节码文件对象,使用Class类中的getClassLoader返回该类的类加载器,
//再在classPath目录下逐一的查找给定名称的资源配置文件,使用方法getResourceAsStream(Name),返回InputStream
//InputStream ins2 =
//ReflectClassLoader.class.getClassLoader().getResourceAsStream("加强Day01/黑马/config.Properties");
//另外Class类还提供了一个方法,用加载类的加载器去加载相同包目录下的文件;
InputStream ins3 = ReflectClassLoader.class.getResourceAsStream("/加强Day01/黑马/propertes/configProperties");
//将流里的数据存储到定义好的缓冲区
pro.load(ins3);
//关闭读取流对象
ins3.close();
//通过键获取值即文件类型
String className = pro.getProperty("className");
Class clazz = Class.forName(className);
Collection collection = (Collection)clazz.newInstance();
HashCodeTest hct1=new HashCodeTest(1,2);
HashCodeTest hct2=new HashCodeTest(3,4);
HashCodeTest hct3=new HashCodeTest(1,2);
collection.add(hct1);
collection.add(hct2);
collection.add(hct3);
collection.add(hct1);
System.out.println(collection.size());
}
}
----------------------
ASP.Net+Android+IOS开发、
.Net培训、期待与您交流! ----------------------