黑马程序员——高新技术---反射

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1.概述

 1.概述

      JAVA反射机制是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意      一个方法和属性;这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。
    动态获取类中信息,就是java反射。可以理解为对类的解剖。
    

 2.应用场景      

       

        如果想要对指定名称的字节码文件进行加载并获取其中的内容并调用,这时就使用到了反射技术。 

        一般在框架中广泛使用反射技术。(下边第三节会详细阐述)

     


2.反射涉及到的对象

1.概述

      上边我们说反射就是对类的解剖,什么意思呢?其实就是简单说把java中的一个类的各个组成部分也分别用类来表示。
       反射就是把Java类中的各种成分映射成相应的java类。
       
      用于描述字节码的类就是Class类,创建对象,可以提取字节码文件中的内容,如字段、构造函数、一般函数。该类就可以获取字节码文件中的所有内容,那么反射就是依靠该类完成的。想要对一个类文件进行解剖,只要获取到该类的字节码文件对象即可。

2.反射的基石

1.概述

         所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。

         Class类中就包含属性有field(字段)、method(方法)、construction(构造函数)。

        而field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装称为一个对象。用来获取类中field的内容,这个对象的描述叫Field同理方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必须要先获取该字节码文件的对象。该对象是Class类型。反射就是把Java类中的各种成分映射成相应的java类。

        Class类描述的信息:类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等。每一个字节码就是class的实例对象。             如:class cls=Data.class;


2.Class和class的区别

        1)class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定, 不同的实例对象有不同的属性值。

        2)Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。例如人对应的是Person类,Java类对应的就是Class。Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。


3.获取Class对象的三种方式

        ·一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class。

·如何得到各个字节码对应的实例对象(Class类型)?有三种方式。

方式一:有对象

        通过对象的getClass方法进行获取。

        如:Class clazz=new Person().getClass();//Person是一个类名

        麻烦之处:每次都需要具体的类和该类的对象,以及调用getClass方法。


方式二:有类名

        任何数据类型都具备着一个静态的属性class,这个属性直接获取到该类型的对应Class对象。

        如:Class clazz=Person.class;//Person是一个类名

        比第一种较为简单,不用创建对象,不用调用getClass方法,但是还是要使用具体的类,和该类中的一个静态属性class完成。


      特殊的:Int.class == Integer.TYPE。


方式三:类名

        这种方式较为简单,只要知道类的名称即可。不需要使用该类,也不需要去调用具体的属性和行为。就可以获取到Class对象了。

        如:Class clazz=Class.forName("包名.Person");//Person是一个类名

        这种方式仅知道类名就可以获取到该类字节码对象的方式,更有利于扩展。


4.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对象所表示的类的一个新实例。


 5.通过Class对象获取类实例         

      通过查看API我们知道,Class类是没有构造方法的, 因此只能通过方法获取类实例对象。之前我们用的已知类,创建对象的做法:

        1)查找并加载XX.class文件进内存,并将该文件封装成Class对象。

        2)再依据Class对象创建该类具体的实例。

        3)调用构造函数对对象进行初始化。

             如:Person p=new Person();

    

      现在用Class对象来获取类实例对象的做法:

        1)查找并加载指定名字的字节码文件进内存,并被封装成Class对象。

        2)通过Class对象的newInstance方法创建该Class对应的类实例。

        3)调用newInstance()方法会去使用该类的空参数构造函数进行初始化。

             如:

                     String className="包名.Person";

                     Class clazz=Class.forName(className);

                     Object obj=clazz.newInstance();

//Person类
package cn.itheima;

public class Person {
	private String name;
	public int age;
	public Person(){
		System.out.println("Person is run");
	}
	public Person(String name,int age){
		this.age=age;
		this.name=name;
	}
	
	public String toString(){
		return name+":"+age;
	}
}
//示例
package cn.itheima;

public class CreateClassDemo {
	public static void main(String[] args) throws Exception {
		createPersonClass();
	}
	//通过Class对象创建类实例方法
	public static void createPersonClass() throws Exception{
		//获取Person类的Class对象
		String className="cn.itheima.Person";
		Class clazz=Class.forName(className);
		//通过newInstance方法获取类的无参构造函数实例
		Person p=(Person)clazz.newInstance();
	}
}

3.Constructor

1.概述

        如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?

这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。就必须先获取这个构造函数


Constructor代表某个类的构造方法,Constructor对象上会有的方法有:得到方法名字,得到所属于的类,产生实例对象。

2.获取构造方法

        1)得到这个类的所有构造方法:如得到上面示例中Person类的所有构造方法

              Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();

        2)获取某一个构造方法:

              Constructor con=Person.class.getConstructor(String.class,int.class);//参数要用Class实例来表示

            注意:一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型,例如                                                               Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。


3.创建实例对象

         1)通常方式:Person p = new Person(“lisi”,30);

         2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);//这里就是通过指定的con构造函数的newInstance方法创建的

注:

        1、创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。

         2newInstance():构造出一个实例对象,每调用一次就构造一个对象。

         3、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。

示例:

//接上面的示例
//通过Constructor对象来创建类实例方法
public static void createPersonClass_2() throws Exception{
	//获取Person类的Class对象
	String className="cn.itheima.Person";
	Class clazz=Class.forName(className);
	//Class clazz=Person.class;
		
	//获取指定构造函数的类实例
	Constructor con=clazz.getConstructor(String.class,int.class);
	Person p=(Person) con.newInstance("lisi",30);
	System.out.println(p.toString());
}


4.Field


1Field类代表某个类中一个成员变量

2、方法

       我们得到的Field对象,是对应到类上的成员变量,而不是对应到对象上的成员变量。


        Field getField(String s);//只能获取公有和父类中公有成员变量,如果是私有的会出现java.lang.NoSuchFieldException异常


        那么私有成员变量如何获取呢?继续查看API我们发现下面的方法:

        Field getDeclaredField(String s);//获取该类中任意成员变量,包括私有。

         但是直接使用会抛出java.lang.IllegalAccessException 异常,这时候我们就需要用到setAccessible(ture);

        setAccessible(ture); //如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。


        set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。

        Object get(Object obj);//返回指定对象上Field表示的字段的值。

示例一:

package com.blog.part4.反射;
import java.lang.reflect.*;
//定义一个演示类
class Person 
{
    private String name;
    public int age ;
    public Person(String name, int age)
    {
          super();
          this.name=name;
          this.age = age;
   }

}

public class TestFiledDemo 
{

       public static void main(String[] args) throws Exception 
       {

    	   Person  p = new Person ("张三", 5);

            Field fname = p.getClass().getDeclaredField("name");
            /*
             * 得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
             * 类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?
             * 所以字段fname 代表的是name的定义,而不是具体的name变量。
             * */
            fname.setAccessible(true);//如果补解放权限的话会报错
            System. out.println(fname.get(p));//具体对象p的字段值

      }

}


示例二:

/*
演示Field类中常用方法
*/
import java.lang.reflect.*;
class Student
{
	//定义两个私有成员变量
	private String name;
	private int age;

  //定义默认权限的成员变量
	public int ID;
	//构造函数
	public Student(){}
	public Student(String name ,int age, int ID)
		{
		  this.name=name;
		  this.age=age;
		  this.ID=ID;
	    }
}
class FieldDemo 
{
	public static void main(String[] args) throws Exception
	{
		getStudent();
	}
	public static void getStudent() throws Exception
	{
		/*
		//根据指定构造函数创建对象
		Constructor con=Student.class.getConstructor(String.class,int.class,int.class);
		Student s=(Student)con.newInstance("xiaoming",20,001);
      
		*/

                Class cls=Class.forName("Student");
		Student s=(Student)cls.newInstance();

     //获取所有成员变量
	   Field[] fs=cls.getFields();
	   for(Field f:fs)
	      System.out.println(f);

	    //获取指定成员变量
    Field fage=cls.getDeclaredField("age");  
    Field fname=cls.getDeclaredField("name");  
	Field fid=cls.getField("ID"); 

	  //显示改变后的值  
    fid.set(s, 001);  
    System.out.println(fid.get(s));  

	   //暴力访问私有变量  
  fname.setAccessible(true);  
  fname.set(s, "zhangsan");  
  System.out.println(fname.get(s));  

	}
}

运行结果:


5.Method类


1.概述

     Method类代表某个类中的一个成员方法。想要调用某个对象身上的方法,要先得到方法,再针对某个对象调用。

          方法本身知道如何实现执行的过程,也就是“方法对象”调用方法,才执行了方法的每个细节的。
2.方法
          类似于成员变量,成员方法也有权限之分

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

        Method[] getDeclaredMethods();//获取本类中包含私有。

        Method   getMethod("具体方法名",参数.class(如果是空参可以写null));


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

         如果方法是静态,invoke方法中的对象参数可以为null


         JDK1.4和1.5的invoke方法的区别:
             Jdk1.5:public Object invoke(Object obj,Object... args)
             Jdk1.4:public Object invoke(Object obj,Object[] args)
  

例子:

          获取某个类中的某个方法:(如String str =”abc”,获取str中的第2个字符,调用charAt方法)

        1)通常方式:str.charAt(1)//直接调用

        2)反射方式:

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

                                 //这里先拿到了String类的charAt方法对象

                                  charAtMethod.invoke(str,1);//然后让方法对象根据具体对象str1进行方法调用

                                  charAt.invoke("str", new Object[]{1})//这是按照JDK1.4该写的等效方法

        说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法

package com.blog.part4.反射;

import java.lang.reflect.*;

public class ReflectTest 
{

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
	{
		String s="abc";
		
          Method m=String.class.getMethod("charAt", int.class);
          sop(m.invoke(s,1));//结果为:b       JDK1.5
          sop(m.invoke(s, new Object[]{1}));//结果为:b     JDK1.4
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}

}
      特殊:main函数反射调用

        有时候我们需要调用一个类的Main方法,也可说是执行这个类的代码。但是这时候这个类我们还没有写好,或者这个类是通过网络运行时传给我们的,我们就不可能在程序中知道我们将要运行的类的名字,这时候我们可以利用java的反射机制去调用main方法,只要在我们执行的时候,将我们需要执行的类的名字传递进去就可以了。这个方法在进行网络编程的时候,有时候我们需要接收一个类,我们只有等到接收到了才知道类的名字叫什么,显然现在在写程序不实际,我们就可以利用反射的方法。

package com.blog.part4.反射;
import java.lang.reflect.Method;

public class ReflectTest
{
       public static void main(String[] args) throws Exception 
       {
            String startingClassName = args[0];//程序运行时候传递的第一个参数 我们人为是类的名字  
            Method mainMethod = Class.forName(startingClassName).getMethod( "main", String[].class);
            
            mainMethod.invoke(null, (Object)new String[]{"111" ,"222" ,"333" });
            mainMethod.invoke(null, new Object[]{new String[]{"111" ,"222" ,"333" }});
            
            //但是下边的方法不正确,因为TestArguments主函数要接收的参数是一个数组,但是在JDK中其实是接收到参数后有一个
            // 拆开的操作,拆开以后再分别作为参数传递给TestArguments,所以我们需要给他包一层,像上边那样。
           // mainMethod.invoke(null,new String[]{"111","222","333"}); 
            
            
       }

}
//要运行这个类的主函数
class TestArguments
{
       public static void main(String[] args) 
       {
             for(String arg : args)
             {
                  System. out.println(arg);
            }
      }
}
      

        注意:记得运行时配置传入参数,要运行的main所在类的方法全名。





6.数组的反射

     1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

        数组字节码的名字: 有[和数组对应类型的缩写,如int[]数组的名称为:[I

       


     2.String是Object的子类,但是String[]不是Object[]的子类,所以 Object x =“abc”能强制转换成String x =abc”。

        但是,new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”};


     3.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。


4、Array工具类用于完成对数组的反射操作。(注意不是Arrays工具类)

        Array.getLength(Object obj);//获取数组的长度

        Array.get(Object obj,int x);//获取数组中的元素

示例:

package com.blog.part4.反射;
import java.lang.reflect.Array;

public class ReflectTest
{
       public static void main(String[] args) throws Exception {
           //定义数组a1和字符串a2
            String[] a1 = new String[]{"a" ,"b" ,"c" };
            String a2 = "xyz";

            printObject(a1);
            printObject(a2);

       }

      
    //定义打印方法
       public static void printObject(Object obj)
       {

         Class clazz = obj.getClass();//获得参数对应的Class类
         
         if(clazz.isArray())//如果是数组
	         {
	               int len = Array.getLength(obj);
	               for(int i = 0; i < len; i++)
	               {
	                     System. out.println(Array.get(obj, i));//分别打印元素
	               }
	
	         } 
         else
               System. out.println(obj);
       }

}



5、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;

     非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

/**
*需求:演示数组反射的常见问题
*      关于Arrays.asList的使用,在我另一篇blog中有详细解释
*
*/
import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayReflect
{
	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().equals(a2.getClass()));//true,指向同一个对象
			System.out.println(a1.getClass().equals(a3.getClass()));//false 维度不同
			System.out.println(a1.getClass().equals(a4.getClass()));//false 基本数据类型与非基本数据类型
			System.out.println(a1.getClass().getName());//[I
			System.out.println(a4.getClass().getName());//[Ljava.lang.String;
			System.out.println(a1.getClass().getSuperclass());//class java.lang.Object
			System.out.println(a4.getClass().getSuperclass());//class java.lang.Object
		
			//都可以当作Object使用
			Object obj1=a1;
			Object obj2=a3;
			Object obj3=a4;
		
			Object[] obj11=a1;//这样是不行的,因为a1中的元素是int类型,基本数据类型不是Object
			Object[] obj13=a3;
			Object[] obj14=a4;//这样可以,因为String数组中的元素属于Object
			
			System.out.println(a1);//[I@4caaf64e
			System.out.println(a4);//[Ljava.lang.String;@6c10a234

			System.out.println(Arrays.asList(a1));//[I@4caaf64e
			System.out.println(Arrays.asList(a4));//[a, b, c]
		
		/* Arrays.asList()方法处理int[]和String[]结果不同,为什么?

            从上边代码我们可以看到
		    打印Arrays.asList(a1);还是跟直接打印a1是一样的
	            打印Arrays.asList(a4);就会把a3的元素打印出来。

		    这是因为此方法在JDK1.4版本中,asList方法接收的Object类型的数组,在JDK1.5版本中,传入的是一个可变参数,
		    比较a1和a4,我们知道a1是基本类型数组,JDK1.4中的asList方法处理不了,JDK1.5可以处理。<span style="font-size: 10pt;">但是JDK1.5将</span> <span style="font-size: 10pt;">int</span><span style="font-size: 10pt;">数组整体作为                      一个参数进行处理。<span style="font-size: 10pt;">因此最终结果就是将</span> <span style="font-size: 10pt;">int</span><span style="font-size: 10pt;">[]进行了封装,结果类型也就成了[[I。</span></span>
			
		 */
		
		//Array工具类用于完成对数组的反射操作。如打印任意数值
		printObject(a1);
		printObject(a4);
		printObject("abc");
		
	}

	//针对上边发现的不能打印数组元素的问题,我们设计一个方法实现打印任意类型数组
	private static void printObject(Object obj)
    {
		Class clazz=obj.getClass();
		//如果传入的是数组,则遍历
		if(clazz.isArray())
		  {
			int len =Array.getLength(obj);//Array工具类获取数组长度方法
			for(int x=0;x<len;x++)
				{
					System.out.println(Array.get(obj, x));//Array工具获取数组元素
			    }
		  }
		else
			System.out.println(obj);
	}
}

3.反射的作用——>实现框架的功能

1.概述

    1.框架:通过反射调用Java类的一种方式。

   

        打个比方:如房地产商造房子用户住,门窗和空调等等内部都是由用户自己安装,房子就是框架,用户需使用此框架,安好门窗等放入到房地产           商提供的框架中。

        框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。


     2.框架机器要解决的核心问题:

        我们在写框架(造房子的过程)的时候,调用的类(安装的门窗等)还未出现,那么,框架无法知道要被调用的类名,所以在程序中无法直接             new其某个类的实例对象,而要用反射来做。


    3.简单框架程序的步骤:

        1)右击项目File命名一个配置文件如:config.properties,然后写入配置信息。

              如键值对:className=java.util.ArrayList,等号右边的配置键,右边是值。            

              注 意: config.properties文件直接放在工程根目录下。

        2)代码实现,加载此文件:

                ①将文件读取到读取流中,要写出配置文件的绝对路径。

                    如:InputStream is=new FileInputStream(“配置文件”);

                ②用Properties类的load()方法将流中的数据存入集合。

                ③关闭流:关闭的是读取流,因为流中的数据已经加载进内存。


        3)通过getProperty()方法获取className,即配置的值,也就是某个类名。

        4)用反射的方式,创建对象newInstance()。

        5)执行程序主体功能

 

/**
 * 需求:利用反射技术,演示框架使用配置文件的原理
 * 思路:配置文件,结合前边学到的IO流中的知识,可以用来存储要运行但是可能还没设计好的类的信息
 *           通过IO流获取配置文件中的信息,获得具体的类名,然后利用反射获取类的实例。
 *           可以利用前边学过的ArrayList和hashSet集合的特点,进行演示,将两个集合对应的类写到配置文件中
 * @author jinlong
 * */
package com.blog.part4.反射;

import java.util.*;
import java.io.*;

//自定义个对象类
class Person
{
		private String name;
		private int ID;
		
		Person(String name,int ID)
		{
			this.name=name;
			this.ID=ID;
		}
		
		//get和set方法
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public int getID() {
			return ID;
		}
		public void setID(int iD) {
			ID = iD;
		}
		
		//重写hashcode和equals方法
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ID;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Person other = (Person) obj;
			if (ID != other.ID)
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}
}
public class ReflectTestDemo
{
	public static void main(String[] args) throws Exception
	{
		/*我们前边学过两种集合的区别,这里不再详细讨论
			Collection col= new ArrayList();//结果是4
			Collection col= new HashSet();//结果是3
		*/
		
		//我们现在有一种需求,就是源代码中不定义具体的集合类型,运行时根据配置文件来确定
		
		InputStream is = new FileInputStream("config.properties" );//关联配置文件
		
        Properties props = new Properties();//Properties对象
        props.load(is);
        is.close();//上一行代码已经将流中信息写入了内存,所以可以关闭流了
        
      String className = (String)props.get( "className");
        Collection col= (Collection)Class.forName(className).newInstance();//根据配置文件信息来创建对象
		
        Person p1 = new Person("张三", 1);
        Person p2 = new Person("李四", 2);
        Person p3 = new Person("王五", 2);
        Person p4 = new Person("张三", 1);
        
         col.add(p1);
         col.add(p2);
         col.add(p3);
         col.add(p1);
        System. out.println(col.size());
	}
}
也就是说,我们不需要在改动源码,而只需要修改配置文件就可以

比如,当配置文件如下


运行结果:4


当配置文件改为如下:


返回结果为:3


2.类加载器

     1.简述:类加载器是将.class的文件加载进内存,也可将普通文件中的信息加载进内存。

     2.文件的加载问题:

        1)eclipse会将源程序中的所有.java文件编译成.class文件,然后放到classPath指定的目录中去。并且会将非.java文件原封不动的复制到.class指              定的目录中去。在运行的时候,执行的是.class文件。

        2)将配置文件放到.class文件目录中一同打包,类加载器就会一同加载。


     3.资源文件的加载:是使用类加载器。

        1)由类加载器ClassLoader来加载进内存,即用getClassLoader()方法获取类加载器,然后用类加载器的getResourceAsStream(String name)              方法,将配置文件(资源文件)加载进内存。利用类加载器来加载配置文件,需把配置文件放置的包名一起写上。这种方式只有读取功能。

       2)Class类也提供getResourceAsStream方法来加载资源文件,其实它内部就是调用了ClassLoader的方法。这时,配置文件是相对类文件的当               前目录的,也就是说用这种方法,配置文件前面可以省略包名。

       如:类名.class.getResourceAsStream(“资源文件名”)


    4.配置文件的路径问题:

        1)用绝对路径,通过getRealPath()方法运算出来具体的目录,而不是内部编码出来的。

               一般先得到用户自定义的总目录,再加上自己内部的路径。可以通过getRealPath()方法获取文件路径。对配置文件修改是需要要储存到配置               文件中,那么就要得到它的绝对路径才行,因此,配置文件要放到程序的内部。

        2)name的路径问题:

                ①如果配置文件和classPath目录没关系,就必须写上绝对路径,

                ②如果配置文件和classPath目录有关系,即在classPath目录中或在其子目录中(一般是资源文件夹resource),那么就得写相对路径,因                    为它自己了解自己属于哪个包,是相对于当前包而言的。


示例:还用上边的例子,我们删掉工程中的配置文件,自己加载

/**
 * 需求:利用反射技术,演示框架使用配置文件的原理
 * 思路:配置文件,结合前边学到的IO流中的知识,可以用来存储要运行但是可能还没设计好的类的信息
 *           通过IO流获取配置文件中的信息,获得具体的类名,然后利用反射获取类的实例。
 *           可以利用前边学过的ArrayList和hashSet集合的特点,进行演示,将两个集合对应的类写到配置文件中
 * @author jinlong
 * */

import java.util.*;
import java.io.*;

//自定义个对象类
class Person
{
		private String name;
		private int ID;
		
		Person(String name,int ID)
		{
			this.name=name;
			this.ID=ID;
		}
		
		//get和set方法
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public int getID() {
			return ID;
		}
		public void setID(int iD) {
			ID = iD;
		}
		
		//重写hashcode和equals方法
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ID;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Person other = (Person) obj;
			if (ID != other.ID)
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}
}
public class ReflectTestDemo
{
	public static void main(String[] args) throws Exception
	{
		/*我们前边学过两种集合的区别,这里不再详细讨论
			Collection col= new ArrayList();//结果是4
			Collection col= new HashSet();//结果是3
		*/
		/*
		//我们现在有一种需求,就是源代码中不定义具体的集合类型,运行时根据配置文件来确定。根目录配置文件
		
		InputStream is = new FileInputStream("config.properties" );//关联配置文件
		*/

         /*------------------------代码设置配置文件加载--------------------------------*/

         //方式一:采用类加载器进行加载,使用相对路径的方式

         //InputStream is = ReflectTestDemo.class.getClassLoader().getResourceAsStream("config.properties");


         //方式二:利用Class方式进行加载,使用相对路径的方式

         InputStream is = ReflectTestDemo.class.getResourceAsStream("config.properties");

        

         //方式三:利用Class方式进行加载,使用绝对路径的方式

       // InputStream is = ReflectTestDemo.class.getResourceAsStream("D:/a/config.properties");

        Properties props = new Properties();//Properties对象
        props.load(is);
        is.close();//上一行代码已经将流中信息写入了内存,所以可以关闭流了
        
        String className = (String)props.get( "className");
        Collection col= (Collection)Class.forName(className).newInstance();//根据配置文件信息来创建对象
		
        Person p1 = new Person("张三", 1);
        Person p2 = new Person("李四", 2);
        Person p3 = new Person("王五", 2);
        Person p4 = new Person("张三", 1);
        
         col.add(p1);
         col.add(p2);
         col.add(p3);
         col.add(p1);
        System. out.println(col.size());
	}
}
运行结果:






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值