1. 程式人生 > >反射、列舉類和內省操作

反射、列舉類和內省操作

反射

  作用:程式可以動態獲取其本身定義的類、方法、欄位等
  應用領域:多數作為程式框架使用,常見為程式碼+配置檔案結構等   

  相關類包

1.獲取類方法

常用的獲取類物件的三種方法(常用Class.forName()來獲取到類物件)

核心方法


 定義Person類

 package org.nihaoi.fan;

 public class Person {   //Person類的全地址名為 org.nihaoi.fan.Person  
 //....
 }

測試程式

 @org.junit.Test
public void test1() throws Exception{
	//1. 使用Class類的靜態方法forName獲取類物件    引數為:類的全地址名
	Class clazz = Class.forName("org.nihaoi.fan.Person");
	
	//2. 通過物件獲取
	Person p = new Person();
	Class clazz0 = p.getClass();
	
	//3. 通過給定類名獲取
	Class clazz1 = Person.class;
		
	System.out.println(clazz.getName());
	System.out.println(clazz0.getName());
	System.out.println(clazz1.getName());
}

輸出結果
org.nihaoi.fan.Person
org.nihaoi.fan.Person
org.nihaoi.fan.Person

依據上述例子, 在配置檔案中只需要給出對應類的全地址名,就可以通過發射機制獲取到這個類。

這樣設計會增強程式的適用性

2.獲取類中的構造器

現在不僅要獲取類物件,同時還要用獲取到的Person類來例項化Person類的物件,此時如果Person中沒有重寫構造器,那隻需要呼叫類物件的newInstance()方法即可。如果Person本身提供了多個構造器,那就需要先獲取其構造器。

核心方法

  對於public構造器 使用getConstructor即可

        對於private構造器 使用getDeclaredConstructor獲取


改寫Person類

public class Person {
	public String secret ="handsome";
	
	public Person(){
	   System.out.println("person constructer null");
	}
	
	public Person(String name,int age){
	    System.out.println("person constructer name:"+name+",age:"+age);
	}
	
	private Person(String name,String des){
              System.out.println("person private constructer :"+name+" is "+des);
	}
}
測試程式
@org.junit.Test
    public void test1() throws Exception{


     //獲取類物件
 Class clazz = Class.forName("org.nihaoi.fan.Person");
 
 //獲取構造器(無參構造器)
    Constructor c0 = clazz.getConstructor(); 
    Object o0 = c0.newInstance(); 	 //使用構造器的newInstance方法例項化物件
 
      //獲取構造器(兩個引數)
          Constructor c1 = clazz.getConstructor(String.class,int.class);
          Object o1 = c1.newInstance("fan",21);
 
       //獲取pirvate構造器
         Constructor c2 = clazz.getDeclaredConstructor(String.class,String.class);
         c2.setAccessible(true);  //做許可權強改
         Object o2 = c2.newInstance("fan","man");
 
 }

測試結果

person constructer null
person constructer name:fan,age:21
person private constructer :fan is man


3.獲取到類的方法

對於public方法使用getMethod即可

對於private方法需要使用getDeclaredMethod方法,使用需要修改access屬性

  


修改Person類

public class Person {
	public String secret ="handsome";
	
	public void eat(){
		System.out.println("person eat");
	}
	
	public String eat(String what){
		System.out.println("person eat:"+what);
		
		return "I what double:"+what+".";
	}

	private void drink(String what){
		System.out.println("person private method drink:"+what);
	}
	
	public static void drink(){
		System.out.println("person static method drink:");
	}

}

測試程式

@org.junit.Test
public void test1() throws Exception{

	//獲取類物件
	Class clazz = Class.forName("org.nihaoi.fan.Person");

	//獲取無參方法
	Method method0 = clazz.getMethod("eat", null);
	method0.invoke(clazz.newInstance(), null);
		
	//獲取帶參方法
	Method method1 = clazz.getMethod("eat", String.class);
	method1.invoke(clazz.newInstance(), "apple");
		
	//獲取private帶參方法
	Method method2 = clazz.getDeclaredMethod("drink", String.class);
	method2.setAccessible(true);
	method2.invoke(clazz.newInstance(), "water");

	//獲取static帶參方法
	Method method3 = clazz.getMethod("drink", null);
	method3.invoke(clazz, null);
}
輸出結果
person eat
person eat:apple
person private method drink:water
person static method drink:



4.獲取欄位

核心方法



Person類

public class Person {
	public String secret ="handsome";
	private String name ="fan";
	public static int age = 21;
}

測試程式

@org.junit.Test
public void test1() throws Exception{

	//獲取類物件
	Class clazz = Class.forName("org.nihaoi.fan.Person");

	//獲取public屬性值
	Field field = clazz.getField("secret");
	System.out.println(field.get(clazz.newInstance()));
	System.out.println(field.getType());

	//獲取private屬性值
	Field field0 = clazz.getDeclaredField("name");
	field0.setAccessible(true);
	field.getType();
	System.out.println(field0.get(clazz.newInstance()));
	System.out.println(field0.getType());

	//獲取static屬性值
	Field field1 = clazz.getField("age");
	System.out.println(field1.get(null));
	System.out.println(field1.getType());
}

測試結果

handsome
class java.lang.String
fan
class java.lang.String
21
int

列舉類

       使用範圍:物件在一定範圍內固定,例如一年的季節,一年的月份,考試成績的等次等

       如何定義:使用關鍵字enum替換class關鍵字。

因為列舉類的例項物件是有固定個數的,所以其構造器必定不能由外部呼叫,故應設定為private屬性,一般編譯器會強制處理。

程式demo

定義一個季節類

         public enum Season {
	           Spring,Summer,Autumn,Winter;
           }

系統預設呼叫private屬性的構造器,其中Spring、Summer等共表示四個例項物件,用逗號隔開

注意在列舉類中,例項物件必須寫在第一行

新增屬性

如果我們需要對每個物件新增對應的屬性值,可以重寫構造器,併為物件屬性封裝set和get方法

public enum Season {	
	Spring("warm"),Summer("hot",2),Autumn("cool"),Winter("cold");    //顯式的例項化物件

	private String mFeeling;
	private int mArray;
	
	//構造器   編譯器會自動處理為private屬性
	Season(String feeling){
		this.mFeeling = feeling;
	}
	
	Season(String feeling,int array){
		this.mArray = array;
	}
	
	//物件的公有方法
	public String getFeeling(){
		return this.mFeeling;
	}

	public int getArray(){
		return this.mArray;
	}
	
	public void setArray(int array){
		this.mArray = array;
	}
	
}

呼叫

System.out.println("Summer array:"+Season.Summer.getArray());
System.out.println("Spring Feeling:"+Season.Spring.getFeeling());
Season.Winter.setArray(20);
System.out.println("Winter setArray:"+Season.Winter.getArray());

結果

Summer array:2
Spring Feeling:warm
Winter setArray:20


含有抽象方法

         上述例子拓展,如果需要物件必須有物件的描述方法,且實現方法可能有所不同,可以為該類新增一個抽象方法,讓每個物件必須去重寫。注意例項化的形式。

例如

public enum Season {
	//顯實的例項化物件
	Spring("warm"){
		@Override
		public void getNormalDes() {
			System.out.println("春日洋洋");
		}
	},
	Summer("hot"){
		@Override
		public void getNormalDes() {
			System.out.println("夏日炎炎");
		}
	},
	Autumn("cool"){
		@Override
		public void getNormalDes() {
			System.out.println("秋風澀澀");
		}
	},
	Winter("cold"){
		@Override
		public void getNormalDes() {
			System.out.println("冬天霧霾大");
		}
	};
	
	private String mFeeling;
	
	//構造器   編譯器會自動處理為private屬性
	Season(String feeling){
		this.mFeeling = feeling;
	}
	
	//物件的公有方法
	public String getFeeling(){
		return this.mFeeling;
	}
	public abstract void getNormalDes();
	
}

呼叫

    Season.Spring.getNormalDes();

結果
    春日洋洋


enum類的方法

繼承樹:

       Q:enum類可否被繼承,可以實現介面嗎?

                 A:自定義的Enum類編譯器會自動新增final關鍵字,故無法被子類繼承。可以實現介面的。

幾個Enum類中常用的方法

           name()方法 返回列舉物件在enum類中宣告的名稱

            ordinal()方法 返回列舉物件在enum類中初始化的序列。從上到下排序(從0開始)

        

                還有使用values()返回Enum類的所有物件, 使用valueOf判斷獲取到的字串是否在Enum類中已宣告等。


內省操作JAVABean

類的屬性可以使用反射技術類獲取。但是反射屬性的操作還是有些複雜,在原類中如果對屬性封裝成JavaBean類,即可使用內省包來獲取屬性。

內省核心方法

getBeanInfo 用於獲取一個類的所有Bean資訊。

使用getPropertyDescriptorsBeanInfo可以從beaninfo中獲取到單個屬性值。


也可以直接例項化一個bean屬性

demo

JavaBean類-Person類

public class Person {
	private String secret ="handsome";
	private String name ="fan";
	private int  age = 21;
	
	public String getAdress() {   //Person並無此屬性,提供此get方法,在獲取BeanInfo是可以獲取到該Bean屬性的
		return "hefei";
	}

	public String getSecret() {
		return secret;
	}
	public void setSecret(String secret) {
		this.secret = secret;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}

測試程式

@org.junit.Test
	public void test1() throws Exception{

		//獲取類物件
		Class clazz = Class.forName("org.nihaoi.fan.Person");
		
		//使用getBeanInfo獲取到所有JavaBean屬性
		BeanInfo beanInfo = Introspector.getBeanInfo(clazz,Object.class);
		PropertyDescriptor[] pDescriptors = beanInfo.getPropertyDescriptors();//獲取到每個屬性的描述
		for (PropertyDescriptor p : pDescriptors) {
			System.out.print("type:"+p.getPropertyType());  //依次獲取型別
			System.out.println("  --->    name:"+p.getName());  //獲取屬性名稱
		}
	}

如果直接例項化一個bean屬性

測試程式

@org.junit.Test
public void test1() throws Exception{
	//獲取類物件
	Class clazz = Class.forName("org.nihaoi.fan.Person");
	
	PropertyDescriptor pd = new PropertyDescriptor("age", clazz);
	System.out.println("type:"+pd.getPropertyType()+",name:"+pd.getName());
}
	


測試結果

type:class java.lang.String  --->    name:adress
type:int  --->    name:age
type:class java.lang.String  --->    name:name
type:class java.lang.String  --->    name:secret

直接例項化bean屬性

type:int,name:age

根據輸出結構可以注意到,只要JavaBean類中封裝對了對應的get方法,JavaInfo中就有獲取到對於的屬性值,無論JavaBean類中是否有該屬性

使用BeanUtils

jar包

下載地址 http://download.csdn.net/detail/u011974639/9666579

需要同時匯入 common包和logging包

核心方法


Person類

public class Person {
	private String secret ="handsome";
	private String name ="fan";
	private int  age = 21;
	
	public String getSecret() {
		return secret;
	}
	public void setSecret(String secret) {
		this.secret = secret;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}

測試程式

@org.junit.Test
public void test1() throws Exception{
	//獲取類物件
	Class clazz = Class.forName("org.nihaoi.fan.Person");
	
	//例項化一個物件
	Object newInstance = clazz.newInstance();
		
	BeanUtils.setProperty(newInstance, "age", "11"); //使用setProperty方法設定屬性
	Field ageField = clazz.getDeclaredField("age");
	ageField.setAccessible(true);
	System.out.println(ageField.get(newInstance)); //檢視屬性是否改變
	
	BeanUtils.setProperty(newInstance, "secret", "delphi"); //設定secret屬性
	Field secretField = clazz.getDeclaredField("secret");
	secretField.setAccessible(true);
	System.out.println(secretField.get(newInstance));
}	

輸出

11
delphi

在上面的程式,我們使用 BeanUtils.setProperty(newInstance, "age", "11"); 對一個int型變數傳入的是一個字串資料,BeanUtils內容為我們做了資料轉換。

但是BeanUtils預設支援8種基本資料型別,如果需要設定其他型別,需要先設定好轉換器。

例如對於一個Date資料做Bean操作 

使用ConvertUtils.register()註冊一個轉換器

     

該轉換器需要一個介面例項,查閱BeanUtil文件,有大量的給定轉換器,需要的時候可以檢視。

demo

Person類中有一個Date屬性

public class Person {
	private Date birthday;
	
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
}


測試程式

@org.junit.Test
public void test1() throws Exception{
	//獲取類物件
	Class clazz = Class.forName("org.nihaoi.fan.Person");
		
	//例項化一個物件
	Object newInstance = clazz.newInstance();
		
	//給定的是String資料 需要轉換為Date儲存,故需要配置一個轉換類(可以用給定的DateLocaleConverter, DateTimeConverter)
	ConvertUtils.register(new Converter() {
		@Override
		public Object convert(Class type, Object value) {
			if (value==null) {
				return null;
			}
			if (!(value instanceof String)) {	//給定引數不為String 丟擲異常
				throw new ConversionException("only String Type.");
			}
				
			String svalue = (String) value;
			if (svalue.trim().equals("")) {
		 		return null;
			}
			SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd");
			try {
				Date date = sd.parse(svalue);
				return date;
			} catch (ParseException e) {
				throw new RuntimeException(e);  //給定格式異常 
			}
		}
	}, clazz);
		
	//給定String資料,轉換為Date資料
	BeanUtils.setProperty(newInstance, "birthday", "1990-01-02");
		
	System.out.println(BeanUtils.getProperty(newInstance, "birthday"));
}

測試結果

1990-01-02

在實際開發中,常常會把資料依照鍵值對形式儲存到map中,可以用BeanUtils.populate(bean,properites)來快速儲存。

核心方法