02-Java反射机制

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
    JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

----------------------------------

获取类的出处的三种方法:

1.调用Object类中的getClass()方法;

 

package cn.mldn.demo;
import java.util.Date;
public class TestDemo {
	public static void main(String[] args) {
		Date date = new Date() ;
		Class<?> cls = date.getClass() ;
		System.out.println(cls); 
	}
}

 

2.使用“.class”取得;(以后HibernateMyBatisSpring会使用到)

 

package cn.mldn.demo;
import java.util.Date; //先有类
public class TestDemo {
	public static void main(String[] args) {
		Class<?> cls = Date.class ;
		System.out.println(cls);  //class java.util.Date
	}
}

3.调用Class提供的一个方法。(这个方法在开发之中用得最多

package cn.mldn.demo;
public class TestDemo {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("java.util.Date") ;
		System.out.println(cls);  //class java.util.Date
	} 
}

利用反射解决使用new关键字实例化对象带来的耦合问题

如果有了Class类对象,就可以利用反射来实现对象实例化操作:

> 实例化对象方法:

public T newInstance() throws InstantiationException,  IllegalAccessException

范例:利用反射实例化对象

package cn.mldn.demo;
class Book {
	public Book() {
		System.out.println("******* Book类的无参构造方法  ********");
	}
	@Override
	public String toString() {
		return "这是一本书" ;
	}
}
public class TestDemo {
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class<?> cls = Class.forName("cn.mldn.demo.Book") ;
		Object obj = cls.newInstance() ; // 相当于使用new调用无参构造实例化
		Book book = (Book) obj ;
		System.out.println(book); 
	} 
}

    所以,实例化对象可以使用关键字new来实现,也可以使用反射实现,但并不代表new关键被完全取代了,使用比new关键更加复杂的反射方式实现实例化对象操作的原因是:new关键字是造成耦合的最大元凶,即:一切耦合都起源于new

范例:观察工厂设计模式

package cn.mldn.test;
interface Fruit {
	public void eat() ;
}
class Apple implements Fruit {
	@Override
	public void eat() {
		System.out.println("吃苹果!");
	}
}
class Factory {
	public static Fruit getInstance(String className) {
		if ("apple".equals(className)) {
			return new Apple() ;
		}
		return null ; 
	}
}
public class TestFactory {
	public static void main(String[] args) {
		Fruit f = Factory.getInstance("apple") ;
		f.eat();
	}
}

如果要增加一个水果类,那么就要修改Factory类:

class Orange implements Fruit {
	@Override
	public void eat() {
		System.out.println("吃橘子!");
	}
}
class Factory {
	public static Fruit getInstance(String className) {
		if ("apple".equals(className)) {
			return new Apple() ;
		} else if ("orange".equals(className)) {
			return new Orange() ;
		} else {
			return null ;
		}
	}
} 

    观察以上代码发现,每增加一个水果类就要去修改工厂类,那么如果随时都可能增加水果类呢?结果就是:工厂类要一直被修改。而工厂类是通过new关键字来取得水果类对象的,所以new就成了所有问题的关键。所以要利用反射来解决这个问题。

package cn.mldn.test;
interface Fruit {
	public void eat() ;
}
class Apple implements Fruit {
	@Override
	public void eat() {
		System.out.println("吃苹果!");
	}
}
class Orange implements Fruit {
	@Override
	public void eat() {
		System.out.println("吃橘子!");
	}
}
class Factory {
	public static Fruit getInstance(String className) {
		Fruit f = null ;
		try {
			f = (Fruit) Class.forName(className).newInstance() ;
		} catch (Exception e) {} 
		return f ; 
	}
} 
public class TestFactory {
	public static void main(String[] args) {
		Fruit f = Factory.getInstance("cn.mldn.test.Orange") ;    // 这里传入不同类的字符串路径,就可以调用类的不同方法
		f.eat();
	}
}

此时,完成了解耦合的目的,而且代码的扩展性非常强。反射的意义:解决new关键字带来的耦合问题

反射调用类的构造方法

范例:观察当前程序的问题

package cn.mldn.po;
public class Book {
	private String title ;
	private double price ;
	public Book(String title ,double price) {
		this.title = title ;
		this.price = price ;
	}  
	@Override
	public String toString() {
		return "图书名称:" + this.title + ",价格:" + this.price ; 
	}
}
package cn.mldn.demo;
public class TestDemo {
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class<?> cls = Class.forName("cn.mldn.demo.Book") ;
		Object obj = cls.newInstance() ; // 相当于使用new调用无参构造实例化
		System.out.println(obj); 
	} 
}

 由于此时Book类没有提供无参构造方法,所以以上代码是错误的:

Exception in thread "main" java.lang.InstantiationException: cn.mldn.po.Book
	at java.lang.Class.newInstance(Unknown Source)
	at cn.mldn.demo.TestDemo.main(TestDemo.java:5)
Caused by: java.lang.NoSuchMethodException: cn.mldn.po.Book.<init>()
	at java.lang.Class.getConstructor0(Unknown Source)
	... 2 more

因为没有无参构造,所以对象无法实例化,这时候只能明确地调用有参构造方法。

Class类里面提供有一个方法可以取得构造方法:

1.取得全部构造:

public Constructor<?>[] getConstructors() throws SecurityException

2.取得一个指定参数顺序的构造:

public Constructor<T> getConstructor(Class<?>... parameterTypes)   throws NoSuchMethodException,SecurityException

以上两个方法返回的都是java.lang.reflect.Constructor”类对象。在这个类中提供有一个明确传递有参构造内容的实例化对象方法:

public T newInstance(Object... initargs) throws  
        InstantiationException,IllegalAccessException,
        IllegalArgumentException,InvocationTargetException

范例:明确调用类中的有参构造

package cn.mldn.demo;
import java.lang.reflect.Constructor;
public class TestDemo {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("cn.mldn.po.Book") ; 
		Constructor<?> con = cls.getConstructor(String.class , double.class) ;//找的是类型
		Object obj = con.newInstance("Java开发" , 79.8) ; //实例化对象
		System.out.println(obj);
	} 
}

简单Java类的开发之中,不管提供有多少个构造方法,请至少保留无参构造。

反射调用类的普通方法

类中的普通方法只有在一个类产生实例化对象之后才可以调用,并且实例化对象的方式有三种:new关键字、克隆clone(),反射。

范例:定义一个类

package cn.mldn.po;
public class cn.mldn.po{
	private String title ;
	public void setTitle(String title) {
		this.title = title;
	}
	public String getTitle() {
		return title;
	}
}

由于这个类中已经有无参构造方法(没有有参构造,系统默认定义一个无参构造方法),所以实例化对象的时候可以直接利用Class类中的newInstance()方法完成。

Class类中提供有以下几个取得类中Method的操作:

> 取得一个类中的全部方法:

public Method[] getMethods() throws SecurityException

> 取得指定方法:

public Method getMethod(String name,Class<?>... parameterTypes)  throws NoSuchMethodException,SecurityException

以上两个操作返回的是java.lang.reflect.Method”类的对象,在这个类之中,重点关注一个方法:

> 调用方法: public Object invoke(Object obj, Object... args)   throws IllegalAccessException,IllegalArgumentException,   InvocationTargetException

范例:反射调用方法

package cn.mldn.demo;
import java.lang.reflect.Method;
public class TestDemo {
	public static void main(String[] args) throws Exception {
		String fieldName = "title" ;  //要操作的成员
		Class<?> cls = Class.forName("cn.mldn.po.Book") ; 
		Object obj = cls.newInstance() ; //必须给出实例化对象
		Method setMet= cls.getMethod("set" + initcap(fieldName), String.class) ;
		Method getMet = cls.getMethod("get" + initcap(fieldName)) ;
		setMet.invoke(obj, "Java开发");//等价于:Book类对象.setTitle("Java开发")
		System.out.println(getMet.invoke(obj)); 
	} 
	public static String initcap(String str) {
		return str.substring(0,1).toUpperCase() + str.substring(1) ;
	}
} 

输出Java开发,此时完全看不见具体的操作类型,即:利用反射可以实现任意类的指定方法的调用。以上只是调用了一个方法,如果开发之中有成批量的方法需要使用到反射来实现方法的调用,就可以比较方便。

反射调用成员

类中的属性一定要在本类实例化对象之后才可以分配内存空间。在Class类里面提供了取得成员的方法:

> 取得全部成员:

public Field[] getDeclaredFields() throws SecurityException

> 取得指定成员:

public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException

返回的类型是:java.lang.reflect.Field类,这个类里面有两个重要的方法:

> 取得属性内容:

public Object get(Object obj) throws IllegalArgumentException,  IllegalAccessException

> 设置属性内容:

public void set(Object obj, Object value) throws  IllegalArgumentException,IllegalAccessException

java.lang.reflect.AccessibleObject类下面(JDK1.8之后修改):

> Executable:下面继续继承了ConstructorMethod ;

> Field:在这个类里面提供更有一个方法:public void setAccessible(boolean flag) throws SecurityException,设置是否封装

范例:现在提供有以下的类

package cn.mldn.po;
public class Book {
	private String title ;
}

这个类中定义了一个私有属性,按照原始的做法,它一定不能被外部所使用

范例:反射调用

package cn.mldn.demo;
import java.lang.reflect.Field;
public class TestDemo {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("cn.mldn.po.Book") ; 
		Object obj = cls.newInstance() ; //必须给出实例化对象
		Field titleField = cls.getDeclaredField("title") ;
		titleField.setAccessible(true);  //取消封装
		titleField.set(obj, "Java开发");//相当于:Book类对象.title = "Java开发" ;
		System.out.println(titleField.get(obj)); //相当于:Book类对象.title
	}
} 

以上代码中发现,虽然title使用private进行封装,但是可以使用setAccessible进行取消封装,之后可以直接调用这个属性,同理,构造方法与普通方法也可以取消封装,只不过很少这样做,而且对于属性的访问还是建议使用settergetter方法完成,这样的做法比较标准。

 

 

 

 

 

 

 

 

这个类中定义了一个私有属性,按照原始的做法,它一定不能被外部所使用

posted @ 2017-08-15 12:33  半生戎马,共话桑麻、  阅读(121)  评论(0)    收藏  举报
levels of contents