33.了解和入门注解的应用
@SuppressWarnings("deprecation")
指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告
@Deprecated
不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。
@Override
表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。
//1.SuppresWarnings取消显示指定的编译器警告 @SuppressWarnings( "deprecation") public static void main(String[] args) { System. runFinalizersOnExit(true); } //2.Deprecated 命名为过时方法 @Deprecated public static void sayHello (){ System. out.println("Hello 传智播客" ); } //3. 表示一个方法声明打算重写超类中的另一个方法声明 @Override public String toString(){ return null ; }
注解相当于一种标记, 在程序中加了注解为程序加了某种标记, 你以后Javac编译器,开发工具和其他程序可以用反射来了解你的类以及各种元素有无何种标记, 看你有什么标记, 就去干相应的事. 标记可以加在包,类,字段,方法,方法的参数以及局部变量上.
34.注解的定义与反射调用
override Retetion取值source
suppersswamings Retetion取值source
deprecated Retetion取值runtime
组件Target元注解和Retetion元注解的重要性.会查会用即可.
package itcast.day2; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; //在类的位置使用了注解 @LikaiAnnotation public class D2AnnotationTest { public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("itcast.day2.D2AnnotationTest" ); //用反射进行测试该类是否定义 注释@D2likaiAnnotation. System. out.println(clazz.isAnnotationPresent(LikaiAnnotation .class)); //如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null System. out.println((LikaiAnnotation )clazz.getAnnotation(LikaiAnnotation. class)); } } //定义一个注释, 写法: @interface XXXAnnotation{ }; RetentionPolicyL 此枚举类型的常量描述保留注释的不同策略 @Retention(RetentionPolicy.RUNTIME) //ElementType : 这些常量与 Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的。 @Target(ElementType.TYPE) @interface LikaiAnnotation { }
35.为注解增加各种属性
另可参见Java Language Specification.
@Annotation2(value = 0, strProperty = "likai" , arrProperty = {false ,true,false}, annotationProperty = @yuan(name = "hehe" ), enumProperty = WeekDay.SAT) public class D3AnnotationTest { public static void main(String[] args) { Annotation2 a1 = D3AnnotationTest.class .getAnnotation(Annotation2. class); System. out.println(a1); } } //枚举内部有什么测试 @Retention(RetentionPolicy.RUNTIME) @interface Annotation2 { //只有一个value属性,可以简写为只写值 public abstract int value(); //加入八大基本数据类型中的 double类型 ,且添加default double num() default 7.0; //加入String类型 String strProperty(); //加入array类型 boolean[] arrProperty(); //加入注解类型 yuan annotationProperty() default @yuan (name = "haha"); //加入枚举类型 WeekDay enumProperty(); //加入Class类型 Class endProperty() default float. class; } @interface yuan { String name(); }
36.入门泛型的基本应用
JDK 1.5以前集合类存在的问题:
ArrayList<Integer> al = new ArrayList<Integer>(); al.add(1); al.add(23); //编译要强制转换且运行出错, 而加上泛型, 错误代码在编译时期就会报告 int i = (Integer)al.get(1);
泛型是提供给javac编译器使用, 可以先定集合中的输入类型.让编译器挡住源程序的非法输入,编译器编译带类型说明的集合时会去掉"类型"信息,使程序运行效率不受影响,
ArrayList<Byte> al2 = new ArrayList<Byte>(); // 两者Class实例对象一致, 说明确实泛型只作用于 javac编译器 System. out.println(al.getClass() == al2.getClass());
对于参数化的泛型类型, getClass()方法的返回值与原类型一样. 由于编译生成的字节码去掉了泛型信息, 只要能跳过编译器, 就可以往某个泛型集合中加入其他类型的数据. 例如: 用反射得到集合, 再调用其add方法
37.泛型的内部原理及更深应用
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及到如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数.
ArrayList<Integer>中的<>念做typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
//这两句话可以通过 LinkedList v1 = new LinkedList<String>(); LinkedList<String> v2 = new LinkedList();
参数化类型不考虑类型参数的继承关系:
Vector<String>v = new Vector<Object>(); //错误 Vector<Object>v = new Vector<String>(); //错误
在创建数组实例时, 数组的元素不能使用参数化的类型, 例如, 下面语句有错误
Vector<Integer> vectorList[] = new Vector<Integer>[6] // 错误
思考题: 下面代码会报错误吗? (不会,编译器一行一行扫描,不会)
Vector v1 = new Vector<String>(); Vector<Object> v = v1;
38.泛型的通配符扩展应用
泛型中的? 问号通配符
问题: 定义一个方法, 该方法用于打印任意参数化类型的集合中的数据.
总结: 使用?通配符可以引用其他各种参数化的类型, ?通配符定义变量的主要作用是引用, 可以调用与参数化无关的方法, 不能调用参数化有关的方法.
//问题: 定义一个方法, 该方法用于打印任意参数化类型的集合中的数据. public static void printCollection(Collection<?> cols){ for(Object col: cols){ System. out.println(col); } System. out.println("size=" +cols.size()); //可以调用与参数化无关的方法 cols = new HashSet<String>(); //也是能通过的 }
泛型汇总的 ? 通配符的拓展
限定通配符的上边界
Vector<? extends Number> x = new Vector<Integer>(); //正确
Vector< Number> x = new Vector<? extends Integer>(); //错误
限定通配符的下边界
Vector<? super Integer> x = new Vector<Integer>(); //正确
Vector<? super Integer> x = new Vector<Byte>(); //错误
注意 ? 与 E的区别,
可以 ? = E
但是不可以 E = ?
39.泛型集合的综合应用案例
HashMap<String,Integer> hm = new HashMap<String,Integer>(); hm.put( "zhangsan",22); hm.put( "lisi",25); hm.put( "wangwu",21); //取出方式一 for(Map.Entry<String, Integer> entrySet: hm.entrySet()){ System. out.println(entrySet.getKey()+entrySet.getValue()); } //取出方式二 for(String key: hm.keySet()){ System. out.println(key +hm.get(key)); }
JSP页面中也经常对Set或Map结合进行迭代
<c:forEach items="{$map}" var="entry"> ${eentry.key}:${entry.value} </c:forEach>
40.自定义泛型方法及其应用
//只有引用数据类型才能作为泛型方法的实际参数,不能使基本数据类型
//1. 泛型方法小示例 public static <T> T add(T a, T b){ return null ; } //2. 交换数组中两个元素的位置 public static <T extends Object> void swap(T[] arr, int a, int b){ T temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }
用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和方法的返回类型之前,也就是紧邻返回值之前.按照惯例,类型参数通常用单个大写字母表示.
除了在引用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符, 例如 Class.getAnnotation()方法定义. 并且可以用&来指定多个边界, 如<V extends Serializable & colneable> void method(){}
普通方法, 构造方法, 静态方法中都可以使用泛型, 编译器不允许创建类型变量的数组
也可以用类型变量,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch子句中.
在泛型中可以同时有多个类型参数, 在定义他们的尖括号中逗号分隔.
//3. 下面代码说明异常如何采用泛型 private static <T extends Exception> void sayHello() throws T{ try{ } catch(Exception e){ throw (T)e; } }
41.自定义泛型方法的练习与类型推断总结
//4. 自动将一个Object对象转换为其他指定类型 public static <T> T trans (Object obj){ return (T)obj; } //5. 将任意类型的数组的所有元素填充为相应类型的某个对象 private static <T> void fillArray(T[] arr, T obj) { for(int i=0;i<arr.length;i++){ arr[i] = obj; } } //6. 采用自定义泛型方法的方式打印出任意参数化类型集合中的所有内容 private static <T> void print(Collection<T> c){ for(T t:c) System. out.println(t); } //7. 定义一个方法,把任意参数类型的集合中的数据安全复制到相应类型的数组中 private static <T> void collectionToArray(T[] arr,Collection<T>c){ ...... } //8. 定义一个方法,把任意参数类型的一个数组中的数据复制到另一个相应类型的数组 private static <T> void arrayToArray(T[] des, T[] src){ ...... }
42.自定义泛型类的应用
如果类的实例对象中的多处要用到同一个泛型参数,这时要考虑采用泛型类型的定义方式,也就是类级别的泛型
类级别的泛型是根据引用该类目时指定的类型信息来参数化类型变量
对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型.
当一个变量声明为泛型时,只能被实例变量和方法调用(还有内嵌类),而不能被静态变量和静态方法调用.因为静态成员时被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数.
问题:类中只有一个方法需要使用泛型, 使用类级别的泛型还是使用方法级别的泛型.
答:才一个用方法级别的,多个用类级别的.
43.通过反射获得泛型的实际类型参数
public class D7GenericEnd { //这段代码通过反射获得泛型的实际类型参数 public static void main(String[] args) throws Exception { //首先获取该方法,此方法参数包含该泛型 Method method = D7GenericEnd.class.getMethod("PP" , ArrayList.class); //返回的对象数组 描述了此 Method 对象所表示的方法的形参类型的 Type[] types = method.getGenericParameterTypes(); //强转得到泛型类型了 ParameterizedType pt = (ParameterizedType) types[0]; System. out.println(pt); //返回 Type 对象,表示声明此类型的类或接口 System. out.println(pt.getRawType()); System. out.println(pt.getActualTypeArguments()[0]); } public static void PP(ArrayList<Date> li){ }
44.类加载器及其委托机制的深入分析
类加载器
Java虚拟机可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader, AppClassLoader. Java类的第一个类加载器不是java类,是用C++编写,正是BootStrap.
Java虚拟机中的所有类装载器具有父子关系的树形结果进行组织, 在实例化每个类装载器对象时, 需要为其制定一个父级类装载器对象或默认采用系统类装载器为其加载父类加载器.
类加载器委托机制
当Java虚拟机要加载一个类时:
首先当前线程的类加载器去加载线程中的第一个类
如果类A引用了类B, Java虚拟机将使用加载类A的类装载器来加载类B
还可以直接调用ClassLasd.loadClass()方法来指定某个类加载器去加载某个类
每个类加载器加载类时,又委托给其上级类加载器去加载某个类.
当所有祖宗类加载器没有加载到类,回到发起者类加载器还 加载不了,抛出ClassNotFoundException,而不是去找儿子,因为没有getChiild方法.即使有,但这么多儿子找哪一个呢?
对类加载器层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext下的itcast.jar包中后,运行结果为ExtClassLoader的原因.
//是AppClassLoader加载器加载的. System. out.println( D8ClassLoaderTest.class.getClassLoader().getClass().getName()); //System获得类加载器为null, 说明System是BootStrap加载的. System. out.println( System. class.getClassLoader()); //依次看看类加载的层次结构 ClassLoader cl = D8ClassLoaderTest.class.getClassLoader(); while(cl != null ){ System. out.println(cl.getClass().getName()); cl = cl.getParent(); } //返回该线程的上下文 ClassLoader。 System. out.println(Thread.currentThread().getContextClassLoader().getClass().getName());
45.自定义类加载器的编写原理分析
46.编写对class文件进行加密的工具类
就是对一个class文件 进行每位&0xff的操作.
/*---路径小测试---*/ //相对路径,相对于Project new FileOutputStream("kk.java" ); //相对路径,相对于Project下必须存在 itcast文件夹,才能在它之下新建文件 new FileOutputStream("itcast\\kk.java" ); //相对路径,相对于所在盘符下,例如d:\\建立kk.java new FileOutputStream("\\kk.java" ); //相对路径,相对于所在盘符下,例如d盘存在 itcast文件夹,在其下可建立kk.java new FileOutputStream("\\itcast\\kk.java" ); //绝对路径,例如c:\\ aa\\bb \\kk.java,前提是c盘下存在 aa,bb文件夹 new FileOutputStream("c:\\aa\\bb\\kk.java" );
47.编写和测试自己编写的解密类加载器
48.类加载器的一个高级问题的实验分析
49.分析代理类的作用与原理及AOP概念
若要为已存在的多个具有相同接口的目标类个各个方法增加一些系统功能, 例如,异常处理,日志,计算方法的运行时间,事物管理,等等.
编写一个与目标类具有相同接口的代理类Xproxy,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统的功能代码.
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,还是代理类,这样以后很容易切换,譬如, 想要日志功能时就配置代理类,否则配置目标类,这样,增加功能很容易,以后运行一段时间 , 又想去掉系统功能也很容易.
AOP(面向方面的编程)
动态代理技术
要为系统中的各种借口的类增加代理功能,那将需要太多的代理类,全部从用静态代理模式,将是一件非常麻烦的事.
JVM可以在运行期间动态生成类的字节码, 这种动态生成的类往往被用作代理,即动态代理. JVM生成的动态类必须实现一个或多个接口, 所以 JVM生成的动态类只能用作具有相同接口的目标类的代理.
CGLIB库可以动态生成一个类的子类, 一个类的子列也可以用作该类的代理, 所以 , 如果要为了没有实现接口的类生成动态代理类,南无可以使用CGLIB库.
代理类的各个方法通常处理要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
50.创建动态类及查看其方法列表信息
public static void method1(){ //返回代理类的 java.lang.Class对象,并向其提供类加载器和接口数组。 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);; System. out.println("--构造方法打印--" ); Constructor[] constructors = clazzProxy1.getConstructors(); for(Constructor constructor: constructors){ String name = constructor.getName(); StringBuilder sb = new StringBuilder(name); sb.append( '('); Class[] clazzParams = constructor.getParameterTypes(); for(Class clazzParam:clazzParams){ sb.append(clazzParam.getName()).append( ','); } if(clazzParams.length >0) sb.deleteCharAt(sb.length()-1); sb.append( ')'); System. out.println(sb.toString()); } System. out.println("----方法打印----" ); Method[] methods = clazzProxy1.getMethods(); for(Method method: methods){ String name = method.getName(); StringBuilder sb = new StringBuilder(name); sb.append( '('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam:clazzParams){ sb.append(clazzParam.getName()).append( ','); } if(clazzParams.length >0) sb.deleteCharAt(sb.length()-1); sb.append( ')'); System. out.println(sb.toString()); } }
51.创建动态类的实例对象及调用其方法
分析JVM动态生成的类
创建实现了Collection接口的动态类和查看其名称, 分析Proxy.getProxyClass方法的各个参数.
编码列出动态类中的所有构造方法和参数签名.
编码列出动态类中所有方法和参数签名.
创建动态类的实例对象
用反射获得构造方法
编写一个最简单的InvocationHandler类
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他返回值的方法报告了异常
将创建动态类的实例对象的代理改成匿名类的形式编写,锻炼大家习惯匿名类.
public static void method2() throws Exception{ Class<?> clazzProxy1 = Proxy.getProxyClass(Collection. class.getClassLoader(), Collection.class ); Constructor<?> constructor = clazzProxy1.getConstructor(InvocationHandler. class); Collection proxy1 = (Collection)constructor.newInstance( newInvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null ; } }); //创建实例对象方法二. Collection proxy2 = (Collection)Proxy. newProxyInstance(Collection. class.getClassLoader(), new Class[]{Collection. class}, new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null ; } }); }
52.完成InvocationHandler对象的内部功能
总结思考: 让jvm创建动态类,需要给他提供哪些信息?
三个方面:
生成的类有哪些方法,通过让其实现哪些接口的方式进行告知:
产生的类字节码必须有一个关联的类加载器对象.
生成的类中的方法的代码怎么样,也得有我们提供.把我们的代码写在一个约定好的接口对象的方法中,把对象传给它, 调用我的方法,即相当于插入了我的代码.提供执行代码的对象就是那个InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了.
用Proxy.newInstance方法直接一步就创建出代理对象.
53.分析InvocationHandler对象的运行原理
名称和参数签名与 java.lang.Object 的 hashCode、equals 或 toString 方法相同,那么在代理实例上调用这样的方法.
54.总结分析动态代理类的设计原理与结构
分析动态代理的工作原理图
怎样将目标类传进去?
直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义.
为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了.
让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量.
将创建代理的过程改为一种更优雅的方法,eclipse重构出一个getProxy方法绑定接受目标同时返回代理对象,让调用者更方便,调用者甚至不用接触任何代理的API.
将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
要把执行的代码装到一个对象的某个 方法里, 然后把这个对象作为参数传递, 接收者只要调用这个对象的方法,即等于执行了外界提供的代码?
为bind方法增加一个Advice参数.
55.编写可生成代理和插入通告的通用方法
package itcast.day3; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; public class D302Demo { public static void main(String[] args) throws Exception { final ArrayList target = new ArrayList(); Collection proxy = ( Collection)getProxy(target, new MyAdvice()); proxy.add(123); proxy.add(456); System. out .println(proxy.size()); System. out .println(proxy.toString()); } //把目标抽成一个参数 public static Object getProxy( final Object target,final Advice advice) throws Exception { Class proxyClazz = target.getClass(); Object proxy = Proxy.newProxyInstance(proxyClazz.getClassLoader(), proxyClazz.getInterfaces(), new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } }); return proxy; } } class MyAdvice implements Advice{ long beginTime ; public void beforeMethod() { System. out .println("开始执行beforeMethod方法" ); beginTime = System.currentTimeMillis(); } public void afterMethod(Method method) { System. out .println("after方法结束:" +method.getName()+ "花费时间:"+(System. currentTimeMillis()- beginTime)); } }
56.实现类似spring的可配置的AOP框架
实现aop功能的封装的配置
工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换,其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法则返回的对象
BeanFactory的构造放阿飞接受代表配置文件的输入流对象,配置文件格式如下:
//工厂类BeanFactory负责创建目标类或代理类的实例对象 public class BeanFactory { Properties prop = new Properties(); //接受一个配置文件 public BeanFactory(InputStream is){ try { prop.load(is); } catch (IOException e) {e.printStackTrace();} } /* getBean根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean, 则直接返回该类的实例对象 否则返回该类实例对象的getProxy方法则返回的对象*/ public Object getBean(String name) throws Exception{ String value = prop.getProperty(name); Class clazz = Class. forName(value); //得到要返回的bean Object bean = clazz.newInstance(); //如果是特殊的类就创建代理并且返回代理 if (bean instanceof ProxyFactoryBean){ //返回的的是该实例对象的getProxy方法返回的对象 ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean; Advice advice = (Advice) Class.forName( prop.getProperty(name+ "advice" )).newInstance(); Object target = Class.forName( prop.getProperty(name+ "target" )).newInstance(); proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); Object proxy = proxyFactoryBean.getProxy(); return proxy; } else { return bean; } } }
//生成动态代理的工厂,需要提供参数是: 目标target + 通知Advice public class ProxyFactoryBean { private Advice advice; private Object target; public Advice getAdvice() { return advice ; } public void setAdvice(Advice advice) { this .advice = advice; } public Object getTarget() { return target ; } public void setTarget(Object target) { this .target = target; } public Object getProxy() throws Exception { Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(); Object retVal = method.invoke(target , args); advice.beforeMethod(); return retVal; } }); return proxy; } }