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”取得;(以后Hibernate、MyBatis、Spring会使用到)
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:下面继续继承了Constructor、Method ;
> 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进行取消封装,之后可以直接调用这个属性,同理,构造方法与普通方法也可以取消封装,只不过很少这样做,而且对于属性的访问还是建议使用setter、getter方法完成,这样的做法比较标准。
这个类中定义了一个私有属性,按照原始的做法,它一定不能被外部所使用 |