一:反射
类加载器的概念
类的加载分为三步:
1 加载:通过类加载器将class文件读到内存,并创建Class对象
2 连接:先验证类的内部结构是否正确,再给静态成员分配内存并默认初始化,然后解析类中的符号引用
3 初始化:对非静态的成员初始化类加载器的组成
Bootstrap ClassLoader :根类加载器,也叫引导类加载器,负责Java核心类的加载。
Extension ClassLoader :扩展类加载器,负责Java的扩展目录中jar包的加载。
System ClassLoader : 系统类加载器,负责在JVM启动时加载来自Java命令的class文件以及环境变量指定的jar包和类路径。Java反射机制:
Java反射机制是在运行状态中,对于任意一个类都能否直到这个类的所有属性和方法。
对于任意一个对象,都能否调用它的任意一个方法和属性,
这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制。反射简单讲就是,通过class文件对象去使用文件中的成员变量和方法。
获取class文件对象的3种方式:
1 Object 类的getClass()方法。对于一个类的getClass()方法返回的Class对象只有一个。
2 只要是数据类型,就可以拿到该类型的Class对象。
3 Class类中的静态方法: forName()。注意要写类名的全路径。
一般第二种比较简单方法,但开发时多使用第三种,可以获得一个字符串形式的类名,方便应用。对于一个Person类,可以通过3种方法获取class文件对象。
Person p=new Person(); Class c1=p.getClass(); Class c2=Person.class; Class c2=Class.forName("package.Person");
工具:先定义一个类Person供使用。
class Person { private String name; private int age; private Person(){} public Person(String name,int age) { this.name=name; this.age=age; } private void show() { System.out.println("show run"); } public String toString() { return name+" : "+age; } }
举例一: 通过反射获取构造方法并使用。
import java.lang.reflect.Constructor; class Demo { public static void main(String [] args) { //获取class文件对象 Class c=Class.forName("Person"); //获取构造方法 Constructor[] cons=c.getConstructors(); for (Constructor con : cons) { System.out.println(con); } Constructor con=c.getDeclaredConstructor(); //获取Person的单个构造方法 Object oo=con.newInstance(); //获取Constructor对象的新实例 System.out.println(oo); //可以不导入Person类就打印Person对象 Constructor con_2=c.getConstructor(String.class,int.class); //获取指定参数的构造方法 Object oo_2=con_2.newInstance("haha",12); //创建新实例 System.out.println(oo_2); //打印Person对象 Constructor con_3=c.getDeclaredConstructor(int.class); //获取私有的构造方法 con_3.setAccessible(true); //设置不进行访问检查,暴力访问。 Object oo_3=con_3.newInstance(13); //创建新实例 System.out.println(oo_3); //打印Person对象 } }
注意:
getConstructors方法获取的是Person类中的公有构造函数
getDeclaredConstructors方法获取Person类所有构造函数
当要获取的构造方法不是共有的,就要用带Declared的方法获取,否则获取不到构造方法。
暴力访问可以不让Java进行访问权限检查,不安全。举例二:通过反射获取成员变量并使用。
import java.lang.reflect.Constructor; import java.lang.reflect.Field; class Demo { public static void main(String[] args) throws Exception { Class c=Class.forName("Person"); //获取所有成员变量 Field[] fields=c.getDeclaredFields(); for (Field field : fields) { System.out.println(field); } //要先有对象 Constructor con=c.getDeclaredConstructor(String.class,int.class); Object oo=con.newInstance("hh",1); System.out.println(oo); //获取单个成员变量 Field field=c.getDeclaredField("age"); //获取成员变量age field.setAccessible(true); //对私有的成员设置暴力访问 field.set(oo,11); //设置对象的age值为指定值。 System.out.println(oo); //打印修改后的值 } }
注意:
获取成员变量要传入字符串名称。
对成员变量的值使用时需要先创建对象。举例三:通过反射获取成员方法并使用。
class Demo { public static void main(String[] args) throws Exception { Class c=Class.forName("Person"); //获取本类所有成员方法 Method[] methods=c.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); } //创建对象 Constructor con=c.getDeclaredConstructor(String.class,int.class); Object oo=con.newInstance("hh",1); //获取单个方法并使用 Method method=c.getDeclaredMethod("show"); //获取单个私有方法。 method.setAccessible(true); method.invoke(oo); //对指定对象调用该方法。 //获取带参数方法 Method m=c.getMethod("toString"); //如果有参数,要加:参数.class Object o=m.invoke(oo); //如果有返回值,要用Object接收 System.out.println(o); } }
注意:
getMethods方法会获取本类和父类的所有公共方法。示例:通过反射运行配置文件内容。
配置文件properties.ini 含有键值对,className=methodName
如果修改了配置文件的键与值,就可以改变程序的运行结果。
所以不用修改源码,动态的加载类文件就可以完成程序修改。class Demo { public static void main(String [] args) throws Exception { //读取配置文件 Properties prop=new Properties(); FileReader fr=new FileReader("properties.ini"); prop.load(fr); fr.close(); //获取键与值 String key=prop.getProperty("className"); String value=prop.getProperty("methodName"); //通过反射获取 Class c=Class.forName(key); Constructor con=c.getConstructor(); Object oo=con.newInstance(); Method m=c.getDeclaredMethod(value); m.invoke(oo); } }
通过反射越过泛型检查。
对指定泛型的集合添加非指定类型的数据。
泛型是给编译器看的,真正运行时,没有泛型概念。import java.util.*; class Demo { public static void main(String [] args) throws Exception { //新建ArrayList对象 ArrayList<Integer> arrlist=new ArrayList<Integer>(); //获取集合对象的class文件对象 Class c=arrlist.getClass(); //通过反射获取class文件对象的Object类型的add方法 Method m=c.getDeclaredMethod("add",Object.class); //调用arrlist对象的add方法,传入字符串值。 m.invoke(arrlist,"hello"); System.out.println(arrlist); } }
动态代理
在Java.lang.reflect包下提供了 Proxy 类和 InvocationHandler 接口,可以生成代理对象,
JDK提供的代理只能针对接口做代理。用cglib做代理更强大。Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
Proxy 类中方法创建代理对象:
public static Object newProxyInstance(ClassLoader loader, class<?>[] interface, InvocationHandler handler)
InvocationHandler 中的唯一方法 :
Object invoke(Object proxy, Method method, Object[] args)
举例:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.lang.reflect.Method; interface User { void regist(); void login(); } class UserImpl implements User { public void regist() { System.out.println("注册"); } public void login() { System.out.println("登录"); } } class MyInvocationHandler implements InvocationHandler { //私有一个目标对象 private Object target; public MyInvocationHandler(Object target) { this.target=target; } //实现接口中的唯一方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("权限校验"); Object oo=method.invoke(target,args); System.out.println("日志记录"); return oo; } } class Demo { public static void main(String [] args) throws Exception { User u=new UserImpl(); u.regist(); u.login(); System.out.println("-----------------"); //创建动态代理对象 MyInvocationHandler myHandler =new MyInvocationHandler(u); User proxy=(User)Proxy.newProxyInstance(u.getClass().getClassLoader(), u.getClass().getInterfaces(), myHandler); proxy.regist(); //调用该方法会自动调用MyInvocationHandler接口中的invoke方法。 proxy.login(); } }
注意:如果需要对其他类实现代理,只需要再创建其他类的代理对象。
二:Eclipse 常用快捷键
ctrl + D :删除当前行
ctrl + shift + F :常规格式化
代码的布局格式:变量与符号的空格添加,括号的排位。
ctrl + shift + O :导包 当多个包中有相同类时,需要手动选择哪一个
ctrl + / : 单行注释 取消单行注释再按一次快捷键
ctrl + shift + / :多行注释
ctrl + shift + \ :取消多行注释
alt + 上/下 :选中的代码上下移动
ctrl + alt + 上/下 :选中代码复制到上下行
ctrl + 点击 :查看源码
ctrl + O :快速显示Outline大纲
ctrl + / (除号) :折叠所有代码
ctrl + * (乘号) :展开所有代码
alt + / :内容辅助
自动生成构造方法:
alt + shift + S + c:无参构造函数
alt + shift + S + o:全参构造函数
alt + shift + S + r :选参构造函数
Debug 调试程序中断点的使用:
在程序中需要调试的有效语句的最左边双击,代表标记一个断点,可以在Debug视图中,单步走程序流程。
删除断点,在BreakPoint中点双叉,去掉所有断点。