JAVA Class25
学习内容:
1.反射
反射目的是在不修改代码的情况下,只需修改外部配置文件,实现调用不同类的不同方法。
(1)类的载入
当我们需要使用一个类时,我们要将这个类载入JVM,这里就要用到类载入的方法:
在我们实例化一个对象时,类会自动载入,另外,实例化一个子类对象会导致父类自动载入
public class Person { public String name; private int age; static {//注意这个静态代码块! System.out.println("静态代码块"); } public Person() { super(); } public Person(String name,int age) { super(); this.name = name; this.age = age; } }
public class TestPerson { @SuppressWarnings({"rawtypes","unchecked"})//忽略多类型警告 public static void main(String[] args) { try { Class c1 = Class.forName("edu.java.reflection.Person");//包名.类名 Class c2 = new Person().getClass(); //上述两种方式都会初始化静态属性,且只会执行一次! Class c3 = Person.class;//不会初始化静态属性 //在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。 //注: 准确的讲是一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。 System.out.println(c1==c2);//true System.out.println(c1.equals(c2));//true System.out.println(c1==c3);//true System.out.println(c1.equals(c3));//true } }
(2)构造器的获取与利用构造器实例化对象:
public class TestPerson { @SuppressWarnings({"rawtypes","unchecked"}) public static void main(String[] args) { try { Class c1 = Class.forName("edu.java.reflection.Person");//包名.类名 //获取造器数组 Constructor[] con = c1.getConstructors();//获取所有公共的构造器 for(Constructor s:con) { System.out.println(s); } //获取空参构造器 Constructor con = c1.getConstructor(); //空参构造器实例化对象 Object obj = con.newInstance(); Person p = (Person)obj;//newInstance()返回的是Object,向下强转Person //有参构造器 Class[] v = {String.class,int.class}; Constructor con = c1.getConstructor(v); Object obj = con.newInstance("张三",20); Person p = (Person)obj; System.out.println(p); Constructor[] all = c1.getDeclaredConstructors();//获取所有构造器,包括私有的 for(Constructor con:all) { System.out.println(con); } Constructor con = c1.getDeclaredConstructor(String.class);//获取私有的有参构造器 con.setAccessible(true);//取消变量权限检查,可以访问私有化变量 Object obj = con.newInstance("李四"); Person p = (Person)obj; System.out.println(p); //空参构造快速生成对象 Object obj = c1.newInstance(); Person p = (Person)obj; } }
(3)获取字段值
public class TestPerson { @SuppressWarnings({"rawtypes","unchecked"}) public static void main(String[] args) { try { Class c1 = Class.forName("edu.java.reflection.Person");//包名.类名 Field[] f = c1.getFields();//获取所有公共字段 for(Field i:f) { System.out.println(i); } Constructor con = c1.getConstructor(String.class,int.class); Object obj = con.newInstance("张三",20); Person p = (Person)obj; Field f = p.getClass().getField("name");//获取对象的指定字段值 f.set(p, "李四"); System.out.println(p); Field age = p.getClass().getDeclaredField("age");//获取私有变量 age.setAccessible(true); age.set(p, 30); System.out.println(p); } catch(Exception e) { e.printStackTrace(); } } }
(4)通过类获取方法
public class TestPerson { @SuppressWarnings({"rawtypes","unchecked"}) public static void main(String[] args) { try { Class c1 = Class.forName("edu.java.reflection.Person");//包名.类名 Constructor con = c1.getConstructor(String.class,int.class); Object obj = con.newInstance("张三",20); Person p = (Person)obj; Field f = p.getClass().getField("name");//获取对象的指定字段值 f.set(p, "李四"); System.out.println(p); Field age = p.getClass().getDeclaredField("age");//获取私有变量 age.setAccessible(true); age.set(p, 30); System.out.println(p);*/ //获取所有方法 Method[] all = c1.getMethods(); for(Method mm:all) { System.out.println(mm); } Method m1 = c1.getMethod("eat"); m1.invoke(p); Method m2 = c1.getDeclaredMethod("sleep",String.class,int.class,double.class);//获取私有带参方法 m2.setAccessible(true); m2.invoke(p,p.name,30,90); } catch(Exception e) { e.printStackTrace(); } } }
2.泛型擦除
JAVA的泛型是”假泛型“,不进class文件,所以利用反射,我们可以突破泛型限制添加数据
public class TestPerson { @SuppressWarnings({"rawtypes","unchecked"}) public static void main(String[] args) { try { //泛型擦除 ArrayList<String> list = new ArrayList<String>(); list.add("abc"); Class c = list.getClass(); Method method = c.getMethod("add", Object.class); method.invoke(list, 1); System.out.println(list);//数字1被成功添加,JAVA的泛型是”假泛型“,不进class文件 System.out.println(list.get(0)); System.out.println(list.get(1));//但是获取会报类型转换失败错误 } catch(Exception e) { e.printStackTrace(); } } }
3.泛型应用
public class Test { @SuppressWarnings({ "rawtypes", "unchecked" })//忽略多类型警告 public static void main(String[] args) throws Exception { //从spring.txt中获取类名称和方法名称 File sp = new File("e:/java/spring/spring.txt"); Properties sc = new Properties(); sc.load(new FileInputStream(sp)); String classname = (String)sc.get("class"); //String methodname = (String)sc.get("method"); String methodname = sc.getProperty("method"); //根据类名称获取类对象 Class cl = Class.forName(classname); //根据方法名称,获取方法对象 Method m = cl.getMethod(methodname); //获取构造器 Constructor c = cl.getConstructor(); //根据构造器,实例化出对象 Object service = c.newInstance(); //调用对象的指定方法 m.invoke(service); //如果要切换类及方法,只改变外部的txt文件内容即可,代码不用改 } }
利用这一思想,我们可以对之前用过的DButil包做一下改动:
配置文件 DButil.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/Hero?characterEncoding=UTF-8
user=root
password=123456
做改动:
public class DButil { private DButil() {}; public static Connection getConn() { try { Properties pro = new Properties(); pro.load(new FileInputStream("properties/DButil.properties")); String driver = pro.getProperty("driver"); String url = pro.getProperty("url"); String user = pro.getProperty("user"); String password = pro.getProperty("password"); Class.forName(driver); Connection conn = DriverManager.getConnection(url, user, password); return conn; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("连接数据库失败!"+e); } } /** * 用来连接数据库 * 返回Connection对象 * */ public static void close(Statement sta,Connection conn) { if(sta!=null) { try { sta.close(); } catch (Exception e) { e.printStackTrace(); } } if(conn!=null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * 用来关闭Statement Connection * 接收一个Statement对象、一个Connection对象 * */ public static void close(ResultSet rs,Statement sta,Connection conn) { if(rs!=null) { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } } close(sta,conn); } /** * 用来关闭ResultSet Statement Connection * 接收一个ResultSet对象、一个Statement对象、一个Connection对象 * */ }