(19)基础加强-反射-类加载器的使用

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------详细请查看: http://edu.csdn.net

黑马程序员基础加强-反射-类加载器的使用

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,八种基本类型(byteshortintlongfloatdoublecharboolean)的字节码对象和一种返回值为void类型的void.class。

2Integer.TYPE是Integer类的一个常量,(但是int.classInteger.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对象锁代表的类的指定公共成员方法;

7Method[] getMehtods();//返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。

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

9String getSuperclass()返回此Class所表示的类的超类的名称

10boolean isArray()判定此Class对象是否表示一个数组

11boolean 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类只能利用无参构造函数创建实例对象。


Fieldjava.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);
	}
}


Methodjava.lang.reflect.Method

Method类代表某个类中的一个成员方法

简单的说:

实际上我们是使用反射的方式拿到指定类的字节码里面的方法,再用这个方法去作用于某个对象。

常用方法:

1, Method[] getMethods();//只获取公共和父类中的方法;

2, Method[] getDeclaredMethods();//获取本类中所有方法包含私有

3, Method getMethod(“方法名”,参数.class);//若是空参数可以写null;因为重载,参数列表和类型可能不一致,参数也可接收可变参数。

4, Object invoke(Object obj,Object…obj(参数));//调用方法,

注意invoke调用方法对象身上的方法:

砖家模式:谁拥有这个数据谁就是调用它的砖家,那么就应该把方法分配给它!

比如停车:

调用者:对象是车,只有车知道该怎么停下来,是车在调用刹车的动作;

指挥者:人在指挥车停车的动作,给车发出信号让车去执行,停车的动作只有车才能做到,车拥有这个停车的动作,它就是这个的砖家,那么就应该把停车方法分配给车,而不是给人;

思考:

当方法对象身上的方法即:invoke(null1)第一个参数是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.4JDK1.5invoke方法的区别:

Jdk1.5public Object invoke(Object objObject…obj)

Jdk1.4public Object invoke(Object objObject[] 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)静态方法获取长度

4for循环遍历数组并用Array类的静态方法get(objindex)获取数组中的对应角标的元素

示例接上面:

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);
		}
	}


ArrayListHashSet的比较及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培训、期待与您交流! ----------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值