废话不多说,直切进入正题。先来一张反射机制的图-->
import java.util.Date;//先有类 public class ReflectTest1 { public static void main(String[] args) { Date date = new Date();//后有对象 System.out.println(date); } }
import java.util.Date; public class ReflectTest2 { public static void main(String[] args) { Date date = new Date(); System.out.println(date.getClass()); } } "class java.util.Date"
发现调用了getClas()方法后的输出,就输出了类的完整名称,等于找到了对象的出处。
java.lang.Class是一个类,这个类是反射操作的源头,即:所有的反射都要从类开始进行,而最关键的是这个类有三种实例化方法:
1.调用Object类中的getClass()方法:
import java.util.Date; public class ReflectTest3 { public static void main(String[] args) { Date date = new Date(); Class<?> cls = date.getClass(); System.out.println(cls); } } "class java.util.Date"
2. 使用“类.class”取得:
import java.util.Date; public class ReflectTest4 { public static void main(String[] args) { Class<?> cls = Date.class; System.out.println(cls); } } "class java.util.Date"
之前是在实例化了Date对象之后取得的Class类对象,但是现在并没有实例化对象的产生。这种方法用在Hibernate、Mybatis、Spring等。
3. 调用Class类提供的一个方法--实例化Class对象(在java.lang.Object下public static
Class<?> forName(String className) throws ClassNotFoundException):
public class ReflectTest5 { public static void main(String[] args) throws ClassNotFoundException { Class<?> cls = Class.forName("java.util.Date"); System.out.println(cls); } } "class java.util.Date"
此时可以不使用import语句导入一个明确的类,而类的名称是采用字符串的形式进行描述的。
当拿到一个类的时候,肯定要直接使用关键字new 进行对象实例化操作这属于我们习惯性的做法。如果拿到Class类对象,那么就可以做到利用反射来实例化对象操作:
public T newInstance() throws InstantiationException,IllegalAccessException
范例: 利用反射来实例化对象
/*正常实例化*/ class Book{ public Book(){ System.out.println("*** Book的构造方法 ***"); } @Override public String toString() { return "--- 这是Book方法 ---"; } } public class ReflectTest6 { public static void main(String[] args) throws ClassNotFoundException { Book b = new Book(); System.out.println(b); } } "*** Book的构造方法 *** --- 这是Book方法 ---"
package com.jkx.lzh.test; /*反射实例化*/ class Book{ public Book(){ System.out.println("*** Book的构造方法 ***"); } @Override public String toString() { return "--- 这是Book方法 ---"; } } public class ReflectTest7 { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException{ Class<?> cls = Class.forName("com.jkx.lzh.test.Book"); Object obj = cls.newInstance(); } } "*** Book的构造方法 ***"
class Book{ public Book(){ System.out.println("*** Book的构造方法 ***"); } @Override public String toString() { return "--- 这是Book方法 ---"; } } public class ReflectTest8 { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException{ Class<?> cls = Class.forName("com.jkx.lzh.test.Book"); Object obj = cls.newInstance(); Book b = (Book) obj; System.out.println(b); } } "*** Book的构造方法 *** --- 这是Book方法 ---"
有了反射之后,以后进行对象化实例化的操作不在只是单独的依靠关键字new完成了,反射也同样可以,但是这并不表示new就被完全取代了。
这样有人就会说:“下面两行代码相当于:Book b = new Book();这样不是更复杂了,么“。
Class<?> cls = Class.forName("com.jkx.lzh.test.Book"); Object obj = cls.newInstance();
PS: 在任何的开发之中,new是造成耦合的最大元凶。一切的耦合都起源于new。
范例:观察工厂模式-->
interface Fruit{ void eat(); } class Apple implements Fruit{ @Override public void eat() { System.out.println("* 吃苹果 *"); } } class Factory{ public static Fruit getInstance(String className){ if("apple" == className){ return new Apple(); } return null; } } public class TestFactory { public static void main(String[] args) { Fruit f = Factory.getInstance("apple"); f.eat(); } } "* 吃苹果 *"
代码如上,但是此时,如果我们要增加一个Fruit接口子类“orange”,就意味着我们就要修改工厂模式的方法。
class Orange implements Fruit{ @Override public void eat() { System.out.println("* 吃橘子 *"); } } class Factory{ public static Fruit getInstance(String className){ if("apple" == className){ return new Apple(); }else if("orange" == className){ return new Orange(); } return null; } }
由此可见,每增加一个Fruit接口子类,就要修改工厂类,那么如果随时需要增加子类呢?
因为现在工厂类都是new关键字直接实例化的,所以new就造成了所有问题的关键点。要想解决这一问题,就只能依靠反射完成。
修改工厂模式的方法如下:
package com.jkx.lzh.test; interface Fruit{ 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) { e.printStackTrace(); } return f; } } public class TestFactory { public static void main(String[] args) { Fruit f = Factory.getInstance("com.jkx.lzh.test.Apple"); f.eat(); } }
此时的程序就真正的完成了解耦合的目的,而且可扩展性非常强。
在之前所编写的代码中,我们都默认调用了类中的无参构造方法。可是类中也有可能不提供无参构造方法。
范例:观察当前程序的问题
Book.java
package com.jkx.lzh.po; public class Book { private String title; private double price; public Book(String title,double price){ this.title = title; this.price = price; } public String toString(String title,double price) { return "书名:"+ this.title + "价格: " + this.price ; } }
ReflectTest9.java
package com.jkx.lzh.test; public class ReflectTest9 { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException{ Class<?> cls = Class.forName("com.jkx.lzh.po.Book"); Object obj = cls.newInstance(); System.out.println(obj); } }
以上代码由于Book没有无参构造方法(<init>()),所以抛出以下异常:
Exception in thread "main" java.lang.InstantiationException: com.jkx.lzh.po.Book at java.lang.Class.newInstance(Class.java:427) at com.jkx.lzh.test.ReflectTest1.main(ReflectTest1.java:7) Caused by: java.lang.NoSuchMethodException: com.jkx.lzh.po.Book.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.newInstance(Class.java:412) ... 1 more
那么怎么解决这个问题呢?
在Class类中提供了这么一个构造方法可以取得构造:
以上两个方法返回都是“java.lang.reflect.Constructor”类的对象。在这个类中我们提供一个明确传递有参构造内容实例化对象方法:
public T newInstance(Object... initargs) throws InstantiationException,IllegalAccessException,IllegalAccessException,IllegalArgumentException,InvocationTargetException;
范例:明确调用类中的构造方法
ReflectTest9.java修改如下:
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class ReflectTest9 { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{ Class<?> cls = Class.forName("com.jkx.lzh.po.Book"); //public Book(String title,double price);Constructor传的是有参构造的参数类型 Constructor<?> con = cls.getConstructor(String.class,double.class); Object obj = con.newInstance("JAVA 反射,",82.22); System.out.println(obj); } } "书名:JAVA 反射价格: 82.22"
以上给我一个警示:在我们的简单java类的开发中不管提供多少构造方法,我们至少要保留无参构造方法。
类中普通方法只有在这个类实例化对象之后才能被调用。并且实例化方法有三种(new,conle,反射)
范例:定义一个类
public class Book { private String title; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
这个类有无参构造方法,所以实例化对象的时候可以直接利用Class提供的newInstance方法
在Class类里面提供有以下取得类中Method()的操作:
1. 取得一个类中的全部方法: 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;
范例:反射调用方法
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectTest10 { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{ String fileName = "title";//要操作的成员 Class<?> cls = Class.forName("com.jkx.lzh.po.Book"); Object obj = cls.newInstance();//必须实例化对象 Method setMet = cls.getMethod("set"+ inticap(fileName) , String.class); Method getMet = cls.getMethod("get"+ inticap(fileName)); setMet.invoke(obj, "JAVA 反射"); System.out.println(getMet.invoke(obj)); } public static String inticap(String str){ return str.substring(0,1).toUpperCase() + str.substring(1); } } "JAVA 反射"
此时我们完全看不到操作类型,也就是说利用反射可以实现任意类的指定方法调用。
1. 取得全部成员:public Filed[] getDeclaredFileds() throws SecurityException;
2. 取得指定成员:public Filed getDeclaredFiled(String name) throws NoSuchMethodException,SecurityException;
1. 取得属性的内容:public Object getObject(Object obj) throws IllegalAccessException,IllegalArgumentException
2. 设置属性的内容:public void Object getObject(Object obj,Object values) throws IllegalAccessException,IllegalArgumentException
1. Executable下面继承了Constructor,Method;
2. Filed: 在这个类中提供有一个方法:public void setAccessible(boolean flag) throws SecurityException;设置是否取消封装
范例:现在提供如下类
package com.jkx.lzh.po; public class Book { private String title; }
这个类中定义了一个私有属性,按照我们原始的做法,此时它一定无法被外部所使用。
范例: 但是使用setAccessible取消封装就可以使用了
import java.lang.reflect.Field; public class ReflectTest11 { public static void main(String[] args) throws NoSuchFieldException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException { Class<?> cls = Class.forName("com.jkx.lzh.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; } } "JAVA 反射"
构造方法和普通方法同样可以取消,只不过我们很少这样去做,而且对于属性的访问还是建议使用settr、gettr