增强for循环
泛型
基本数据类型的拆箱与装箱,自动数据类型转换 枚举
可变参数
注解
线程池 Executors
lock
出现StringBuilder
1.7新特性,BeanUtils可以设置Map //Map map =(name:"zhang"),age:18; //BeanUtils.setProperty(map, "name", "zhang"); 1、启动eclipse ,进程中javaw.exe 2、一些java单词缩写 java ee——Java Platform,Enterprise Edition ide——itegrity development environment,集成开发环境
jms——Java消息服务(Java Message Service) jmx——Java Management Extensions,即Java管理扩展 jndi——(Java Naming and Directory Interface,Java命名和目录接口 Dao——data access object crud——对数据库的增删改查 3、IDE开发工具都支持使用工程化方式管理一个项目的程序开发过程,一般来说一个相对独立的项目就是一个工程,一个项目中涉及的多个java文件。资源文件等用一个工程进行管理。在不适用工程管理的情况下,如果一个项目中包括多个java源文件,编程人员需要精心维护这些源文件之间、以及源文件与其他文件的目录关系,需要逐一编译这些源文件,需要手工启动运行编译后的结果。如果将一个程序的所有源文件用一个工程来组织,开发工具能对所有源文件几种管理,记住每个源文件的位置和相互关系。工程中有哪几个源文件、启动类是哪个、启动参数设置等配置信息在工程中都记录。 一个workspace可以包含多个project,一个workspace保留了eclipse的一套环境选项的配置,例如,锁使用的javac和java命令,等等。如果eclipse再配置一套环境选项,可以再创建一个workspace。package explorer视图窗口中的filters菜单项,可以显示空的父包。
一个Perspective代表了若干个view的集合,如何显示各种view
4、常用菜单--配置 window--preferences--Java--Compiler(编译时用的工具,JRE)
window--preferences--Java--Installed JRE(运行时用的工具,JRE) window--preferences--General--keys(配置快捷键) 5、切换workspace File--SwitchWorkspace--选择想要的workspace(如果不存在,会创建),重选workspace需要重启eclipse
perspective透视图 view视图。 如何调试?首先打断点——右键-debug as 进入调试视图 ——选中变量右键watch 可以查看变量值——step over键运行下一步,点一下继续往下运行一行——Resume直接运行完毕后面所有程序 eclipse右上角 有java debug 点击可以切换透视图
如果误关了透视图内的小窗口,可以在window--show View中找到
6、高版本的java能运行低版本的javac编译的程序 低版本的java不能运行高版本的javac编译的程序
因为:高版本java融入了许多新的特性,这些特性在低版本上运行不了。
错误提示:java.lang.UnsupportedclassVersinoError:Bad version number in.class file
因此这里要注意:包的运行环境的jdk版本(工程,右键,properties)要与myeclipse配置的jdk版本一致
7、代码模板,如trycatch synchronized 等,选中代码右键surrendwith 即可 自定义模板:如try finally
步骤:window--preferences--java-editor-templates-new
输入name和Pattern ,try{ 内容:在这里右键Insert Variable ,选择 line_selection}finally{光标在这里,右键insert variable 选择cursor}
try { ${line_selection} } finally { ${cursor} }8、导入已有的工程 左侧栏,右键import--General--Existing Projects into Workspace
如果导入的工程与现有workspace所用JRE不一致,则可以右键 工程--build path --configure build path--Libraries 删掉该JRE 然后add Libraries--user Libraries---add jars。(Add jars 只导入一个jar包,而Add libraries 可以导入多个jar包)
9、静态导入 import语句可以导入一个类或某个包中的所有类,不能导入包中的包中的类
import static 语句导入一个类中的某个静态方法或所有静态方法。比如类Math中的Max方法
可以这样:import static java.lang.Math.*;表示导入math类中的静态方法,这样的话,在程序中就可以直接使用max方法了,而不需要Math.Max()这样调用
package day1; //import static java.lang.Math.max;导入类中的一个静态方法 import static java.lang.Math.*;//导入类中所有的静态方法 public class StaticImport { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int i=1; try { i++; } finally { System.out.println(i); } System.out.println(max(3, 6));//导入后,可以直接用该静态方法了 System.out.println(Math.abs(3-6)); } }10、可变参数 问题:一个方法接收的参数个数不固定
可变参数特点:
只能出现在参数列表的最后
...位于变量类型和变量名之间,前后有无空格都可以
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数
overload重载
override重写
练习:可变参数
package day1;
public class VariableParameter { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(add(1,2,3,4)); } public static int add(int x,int...args)//int...args可变参数只能放在参数列表最后 { int sum = x; for(int i = 0;i<args.length;i++) { sum =sum+args[i]; } return sum; } }
11、增强for循环 语法:for(type 变量名:集合变量名){}
注意:迭代变量必须在()中定义,集合变量可以是数组或实现了iterable接口的集合类
package day1; public class VariableParameter { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(add(1,2,3,4)); } public static int add(int x,int...args)//int...args可变参数只能放在参数列表最后 { int sum = x; for(int arg:args) { sum =sum+arg; } return sum; } }
12、基本数据类型的拆箱与装箱 Integer intobj = 12;//自动装箱
System.out.println(intobj+12);//自动拆箱,
package studybase;
public class HelloWorld3 {
/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Integer io = 3; System.out.println(io+6); Integer in1 = 3; Integer in2 = 3; System.out.println(in1==in2);//true,-128至127之间 String s1 = "abc"; String s2 = "abc"; System.out.println(s1==s2);//true //数据范围在-128 127之前时,将对象放在缓存中,如果下次生成新对象时,还是使用这个对象, //因为数据很小,用的频率比较高,没有必要每生成一个对象就生成一个新的对象,节省缓存空间。 //这就是享元设计模式flyweight //很多小的对象,具有非常多相同的属性,只有少量属性不同,而且这些小对象用的频率非常高, //那么我们就可以把这些小对象变成一个对象,把那些不得属性定为外部属性或方法参数以示区别。 Integer in11 = 2233; Integer in22 = 2233; System.out.println(in11==in22);//false Integer int1=Integer.valueOf(128); Integer int2 = Integer.valueOf(128); System.out.println(int1==int2);//false Integer int11=Integer.valueOf(127); Integer int22 = Integer.valueOf(127); System.out.println(int11==int22);//true }
}
13、枚举 为什么要有枚举?
问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday=0;
枚举就是要让某个类型的变量的取值,只能为若干个固定值中的一个,否则,编译器就会报错,枚举可以让编译器在编译时就可以控制源程序中填写的非法制,普通变量的方式在开发阶段无法实现这一目标。
用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能
私有的构造方法
每个元素分别用一个public的静态成员变量表示
可以有若干公有方法或抽象方法,例如,要提供nextDay方法必须是抽象的
枚举的基本应用
举例:定义一个Weekday的枚举
扩展:枚举类的values, valueOf, name ,toString ,ordinal等方法
总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象
采用抽象方法定义nextDay()就将大量的ifelse语句转移成了一个个独立的类
如果想在一个类中编写完各个枚举类和测试调用类,那么可以将枚举类定义成调用类的内部类 注意:注意:注意:枚举类一被加载,枚举对象就被创建了 用普通方法定义枚举类: package studybase;
public abstract class WeekDay {private WeekDay(){}
public abstract String toString();//定义抽象方法,让子类实现该方法
public abstract WeekDay nextDay();//定义抽象方法,让子类实现该方法public final static WeekDay SUN = new WeekDay(){//匿名内部类,实现抽象方法public WeekDay nextDay(){return MON;}public String toString(){return "SUN";}};public final static WeekDay MON = new WeekDay(){public WeekDay nextDay(){return TUE;}public String toString(){return "MON";}};public final static WeekDay TUE = new WeekDay(){public WeekDay nextDay(){return WED;}public String toString(){return "TUE";}};public final static WeekDay WED = new WeekDay(){public WeekDay nextDay(){return THU;}public String toString(){return "WED";}};public final static WeekDay THU = new WeekDay(){public WeekDay nextDay(){return FRI;}public String toString(){return "THU";}};public final static WeekDay FRI = new WeekDay(){public WeekDay nextDay(){return SAT;}public String toString(){return "FRI";}};public final static WeekDay SAT = new WeekDay(){public WeekDay nextDay(){return SUN;}public String toString(){return "SAT";}};
}
package studybase; class EnumText {
/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubWeekDay weekday = WeekDay.SUN;System.out.println(weekday.nextDay());}
} 枚举就相当于一个类,其中也可以定义构造发方法、成员变量、普通方法和抽象方法 枚举元素必须放在枚举体重的最开始部分,枚举元素列表的后要有逗号与其他成员分离。把枚举中的成员方法或变量等放在枚举元素的前面,编译会出错。 带构造方法的枚举 构造方法必须定义为private
如果有多个构造方法,需要在枚举元素后加()或不加()以来确定调用哪个构造方法
枚举元素MON和MON()的效果是一样的,都是调用默认的构造方法
带抽象方法的枚举 需要在枚举元素后加{实现该抽象方法},其实就是匿名内部类
枚举只有一个成员时,就可以作为一种单例的实现方式 用关键字enum定义枚举类 package studybase;
public class EnumText {
/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubWeekDay weekDay = WeekDay.FRI;System.out.println(weekDay.name());System.out.println(weekDay);System.out.println(weekDay.ordinal());//自己的排行,5,System.out.println(weekDay.getClass());//返回自己所属于的类System.out.println(WeekDay.valueOf("SUN").toString());//把字符串变成对应的枚举类对象System.out.println(WeekDay.values().length);//将枚举元素装到数组中,可以对数组进行遍历}
//枚举中的元素就是该类的一个对象,即下面的SUN,MON,TUE,WED,THU,FRI,SAT都是//类WeekDay的对象public enum WeekDay//定义枚举类 关键字enum{SUN,MON,TUE,WED,THU,FRI,SAT;}}
内部类可以用public private 修饰,外部类只能是public或默认
示例:带参数构造方法,带抽象方法的枚举 package studybase;
import java.util.Date;
public class EnumText {
/** * @param args */public static void main(String[] args) {new Date(300){};//new子类的实例对象,并调用父类带参数的构造方法}
//枚举中的元素就是该类的一个对象,即下面的SUN,MON,TUE,WED,THU,FRI,SAT都是//类WeekDay的对象public enum WeekDay//定义枚举类 关键字enum{SUN,MON,TUE(),WED,THU(),FRI,SAT(3);//注意:构造方法只能是privateprivate WeekDay()//创建SUN,MON,TUE,WED,FRI实例时,默认调用该方法{}//带参数的构造方法private WeekDay(int day)//创建THU(1),SAT(3)时调用该方法{}}//带参数构造方法,带抽象方法的枚举类public enum Trafficlamp{//用匿名内部类实现该抽象方法RED(30){public Trafficlamp nextLamp(){return GREEN;}},GREEN(45){public Trafficlamp nextLamp(){return YELLOW;}},YELLOW(5){public Trafficlamp nextLamp(){return RED;}};public abstract Trafficlamp nextLamp();//抽象方法private int time ;private Trafficlamp(int time){this.time = time;}}}
14、反射——Class类 java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例来确定的,不同的实例对象有不同的属性值。java程序中的各个java类,它们是否属于同于类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要助于与小写的class关键字区别。Class类描述了哪些方面的信息?类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等等。学习反射就要了解Class。
理解:
Person p1 = new Person();//首先在硬盘上编译成二进制代码,加载进内存,再用字节码创建出一个个对象
Person p2 = new Person();
Date
Math
Class cls1 = 字节码1;//每个类的字节码就是Class的对象
Class cls2 = 字节码2;
第一种得到字节码的方法:jvm中已有的字节码 Class cls3 = Person.class;
Class cls4 = Math.class;
第二种得到字节码方法:对象.getClass(),如new Date().getClass(); 第三种得到字节码的方法:调用静态方法forName得到jvm中没有的字节码 Class.forName("java.lang.String");
九个预定义Class实例对象,void boolean short byte int long float double char 判断这些对象是否属于基本数据类型字节码,用Class 的isPrimitive()方法
数组类型的Class实例对象,判断用isArray();
总之:只要是在源程序中出现的类型,都有各自的Class实例对象,如int[] 、 void等
15、反射 反射就是把java类中的各种成分映射成相应的java类。例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等也是一个个的类。表示java类的Class类显然要提供一些列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是响应类的实例对象来表示,他们是Field/Method /Constructor/package等等
一个类中的每个成员都可以用响应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用?
16、Constructor类 Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
Constructor[] constructor=
Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
Constructor constructor =
Class.forName("java.lang.String").getConstructor(StringBuffer.class);
获得方法时要用到类型
创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
创建实例对象时也要用到类型
Class.newInstance()方法
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象
该方法内部的具体代码是怎么写的?用到了缓存机制来保存默认构造方法的实例对象。
package studybase;
import java.lang.reflect.Constructor;
public class ReflectTest {
/** * @param args */public static void main(String[] args) throws Exception{// TODO Auto-generated method stubString str1 = "abc";Class cls1 = str1.getClass();Class cls2 = String.class;Class cls3 = Class.forName("java.lang.String");System.out.println(cls1==cls2);//trueSystem.out.println(cls2==cls3);//trueSystem.out.println(cls1.isPrimitive());//false,判断是不是基本数据类型字节码System.out.println(int.class.isPrimitive());//true,判断int字节码是不是基本数据类型字节码System.out.println(int.class ==Integer.class);//false,二者属于不同类型 System.out.println(int.class == Integer.TYPE);//true,TYPE表示其基本类型 System.out.println(int[].class.isPrimitive());//false,数组也是一种类型System.out.println(int[].class.isArray());//true,判断数组字节码是否属于数组String.class.getConstructor(StringBuffer.class);//表示得到参数为StringBuffer类型的构造方法。//new String (new StringBuffer("abc"));//用反射方法写上面的语句Constructor constructor1 = String.class.getConstructor(StringBuffer.class);String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));System.out.println(str2.charAt(2));}
}
17、Field类 Field类代表某个类中的一个成员变量
演示:用eclipse自动生成java类的构造方法
问题:得到Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联哪个对象呢?所以字段field X代表的是x的定义,而不是具体的x变量
package studybase;
import java.lang.reflect.Constructor;import java.lang.reflect.Field;
public class ReflectTest {
/** * @param args */public static void main(String[] args) throws Exception{ReflectPoint pt1 = new ReflectPoint(3,5);Field fieldy = pt1.getClass().getField("y");//fieldy不代表y的值//fieldy不是对象身上的变量,而是类上。要取得y的值,用get方法System.out.println(fieldy.get(pt1));//x是private的因此需要用getDeclaredField(),该方法不管你是否private,都能看到Field fieldx = pt1.getClass().getDeclaredField("x");//setAccessible(true)表示我能够拿到变量,暴力反射fieldx.setAccessible(true);System.out.println(fieldx.get(pt1));}
} package studybase;
public class ReflectPoint {
/** * @param args */private int x;public int y;public ReflectPoint(int x, int y) {super();this.x = x;this.y = y;}
}
反射练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的”b"改成"a"
package studybase;
import java.lang.reflect.Constructor;import java.lang.reflect.Field;
public class ReflectTest {
/** * @param args */public static void main(String[] args) throws Exception{// TODO Auto-generated method stubString str1 = "abc";Class cls1 = str1.getClass();Class cls2 = String.class;Class cls3 = Class.forName("java.lang.String");System.out.println(cls1==cls2);//trueSystem.out.println(cls2==cls3);//trueSystem.out.println(cls1.isPrimitive());//false,判断是不是基本数据类型字节码System.out.println(int.class.isPrimitive());//true,判断int字节码是不是基本数据类型字节码System.out.println(int.class ==Integer.class);//false,二者属于不同类型System.out.println(int[].class.isPrimitive());//false,数组也是一种类型System.out.println(int[].class.isArray());//true,判断数组字节码是否属于数组String.class.getConstructor(StringBuffer.class);//表示得到参数为StringBuffer类型的构造方法。//new String (new StringBuffer("abc"));//用反射方法写上面的语句Constructor constructor1 = String.class.getConstructor(StringBuffer.class);String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));System.out.println(str2.charAt(2));ReflectPoint pt1 = new ReflectPoint(3,5);Field fieldy = pt1.getClass().getField("y");//fieldy不代表y的值//fieldy不是对象身上的变量,而是类上。要取得y的值,用get方法System.out.println(fieldy.get(pt1));//x是private的因此需要用getDeclaredField(),该方法不管你是否private,都能看到Field fieldx = pt1.getClass().getDeclaredField("x");//setAccessible(true)表示我能够拿到变量,暴力反射fieldx.setAccessible(true);System.out.println(fieldx.get(pt1));changeStringValue();}
private static void changeStringValue(Object obj) throws Exception{// TODO Auto-generated method stub//获得obj的所有成员变量Field[] fields = obj.getClass().getFields();//对存有成员变量的数组进行遍历for(Field field:fields){//判断成员变量所属类字节码是否与String相同if(field.getType() == String.class){//获得变量值String oldValue = (String)field.get(obj);//将变量值中‘b' 修改为'a'String newValue = oldValue.replace('b','a');//将新值设置到obj中field.set(obj,newValue);}}}
}
package studybase;
public class ReflectPoint {
/** * @param args */private int x;public int y;public String str1 ="ball";public String str2 ="basketball";public String str3 ="itcast";public ReflectPoint(int x, int y) {super();this.x = x;this.y = y;}
public static void main(String[] args) {// TODO Auto-generated method stub
}public String toString(){return str1+"...."+str2+"...."+str3;}}
18、Method类 Method类代表某个类中的一个成员方法
得到类中某一个方法:
Method charAt =
Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式:System.out.println(charAt.invoke(str,1));
如果传递给Method对象的 invoke()方法的一个参数为null,说明该Method对象对应的是一个静态方法
jdk1.4 与 1.5中invoke方法的区别
1.5:public Object invoke(Object obj,Object...args)
1.4:public Object invoke(Object obj,Object[] args),即按1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码页可以用jdk1.4改写为
charAt.invoke("str",new Object[]{1})形式
package day1; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectTest { /** * @param args * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws InstantiationException * @throws IllegalArgumentException */ public static void main(String[] args) throws Exception { Constructor constructor1 = String.class.getConstructor(StringBuffer.class); String str2 = (String)constructor1.newInstance(new StringBuffer("abc")); System.out.println(str2.charAt(2)); ReflectPoint pt1 = new ReflectPoint(3,5); Field fieldY = pt1.getClass().getField("y"); System.out.println(fieldY.get(pt1)); Field fieldX = pt1.getClass().getDeclaredField("x"); fieldX.setAccessible(true); System.out.println(fieldX.get(pt1)); changeStringValue(pt1); System.out.println(pt1); Method methodCharAt = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt.invoke(str2, 1)); System.out.println(methodCharAt.invoke(null, 1));//这里是null 表示静态方法 } private static void changeStringValue(Object obj) throws Exception { Field[] fields = obj.getClass().getFields(); for(Field field:fields) { if(field.getType()==(String.class)) { String oldValue = (String)field.get(obj); String newValue = oldValue.replace('b', 'a'); field.set(obj, newValue); } } } } 19、用反射方式执行某个类中的main 方法 目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类的main方法
问题:启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] aargs),通过反射方式来调用这个main方法时,如何为invoke方法传递参数?按jdk1.5的语法,整个数组时一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke时,javac到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按照jdk1.4的语法进行处理,即把数组打散称为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码:mainMethod.invoke(null,new String[]{"xxx"}),javac只把它当做jdk1.4的语法进行理解,而不把它当做jdk1.5的语法理解,因此会出现参数类型不对的问题
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});//把数组打包成一个数组
mainMethod.invoke(Object)new String[]{"xxx"}),编译器会做特殊处理,编译时不把参数当做数组看待,也就不会把数组打散成若干个参数了
package studybase;
import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception{//通常方法:调用main方法TestArguments.main(new String[]{"123","434","ede"});//反射方法:String startingClassName = args[0];Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);mainMethod.invoke(null, new Object[]{new String[]{"123","434","ede"}});//或者下面这条语句mainMethod.invoke(null, (Object)new String[]{"123","434","ede"});}}
class TestArguments{public static void main(String[] args){for(String arg:args){System.out.println(arg);}}}
20、数组与Object 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
基本类型的一维数组可以被当做Object类型使用,不能当做Object[] 类型使用,非基本类型的一维数组,既可以当做Object类型来使用,又可以当做Object[] 类型使用
Arrays.asList()方法处理intp[] 和String[] 时的差异
记住一个类Array(不是Arrays),该类是用于数组的反射,记住一个方法get(arrayname,arrayindex),用于得到指定数组指定下标的内容,如System.out.println(Array.get(a1, 2));
package studybase;
import java.util.Arrays;
public class ArrayReflectTest {
/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubint[] a1 = new int[]{1,2,3};int[] a2 = new int[5];int[][] a3 = new int[3][5];String[] a4 = new String[]{"a","b","c"};sop(a1.getClass() == a2.getClass());//true//sop(a1.getClass() == a3.getClass());//编译不通过//sop(a1.getClass() == a4.getClass());//编译不通过sop(a1.getClass().getName());//结果:[I [表示数组 I表示intsop(a1.getClass().getSuperclass().getName());sop(a4.getClass().getSuperclass().getName());Object aobj1 = a1;//int[] 数组当做对象Object aobj2 = a4;//可以理解为String[]数组当做一个对象//Object[] aobj3 = a1;//int不能当做Object来使用Object[] aobj4 = a3;//数组Objcet[] 中的每个元素是一维数组int[],因为int[] 是对象Object[] aobj5 = a4;//数组Object[] 中的每个元素都是String[] 数组中的每个元素sop(a1);//[I@459189e1sop(a4);//[Ljava.lang.String;@55f33675 //用public static List asList(Object[] a)方法将数组转为List sop(Arrays.asList(a1));//[[I@459189e1],原因asList接收的是Object,而int不是objectsop(Arrays.asList(a4));//[a, b, c]
//数组的反射
// Object obj = null;//printObject(obj); printObject(a1);
printObject(a4);
}private static void printObject(Object obj) {// TODO Auto-generated method stubClass clazz = obj.getClass();//得到字节码if(clazz.isArray())//判断是否数组{int len = Array.getLength(obj);for(int i = 0;i<len;i++){sop(Array.get(obj, i));}}else{sop(obj);}}public static void sop(Object obj){System.out.println(obj);}}
获得数组中的元素类型 不能获得aa的类型,但能获得各个元素的类型 Object[] aa = new Object[]{"a",1}; aa[0].getClass().getName();
21、反射的作用---》实现框架功能 框架与框架要解决的核心问题 我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题 我在写框架时,你这个用户可能还在上小学,还不回写程序,那么我写的框架程序怎么能调用你以后写的类?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做。
综合案例: 先直接new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。 然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异
一般方式:
package studybase;
import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.HashSet;
public class ReflectTestf2 {
public static void main(String[] args) {// TODO Auto-generated method stubCollection collections = new HashSet();//集合中有三个元素//hashSet中无序,不能存入相同对象,但是pt1 pt3虽然指向同一个对象,二者hashcode值不同//用equals比较pt1 pt3时,jvm认为二者不同,因此可以存入hashSet//如果两个对象相等的话 那就让二者的hashcode也相等(在hashset中)/* 当一个对象被存储间HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了, 否则,对象修改后的哈希值与最初存储进HashSet集合中哈希值就不同了,在这种情况下 即使在contains方法使用该对象的当前引用作为的参数区HashSet集合中检索对象, 也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露 通常来将,一个类的两个*对象用equals()方法比较的结果相等时,他们的话细致也必须相等,但反之则不成立 即equals方法比较结果不相等的对象可以具有相同的哈希值,或者说哈希值相同的两个对象的equals方法比较的结果可以不等 例如,字符串"BB"和"Aa"的equals方法结果肯定不相等,但他们的hashCode方法返回值却相等 *///Collection collections = new ArrayList();//集合中有四个元素//ArrayList 中元素的存入有序,因此同一个对象可以存多个引用ReflectPoint pt1 = new ReflectPoint(3,3);ReflectPoint pt2 = new ReflectPoint(5,5);ReflectPoint pt3 = new ReflectPoint(3,3);//pt1和pt3,指向同一个对象,但是二者hashcode值不同,因此用equals比较时,不同//我么可以重写hashcode()方法,让二者值相等,那么equals就相同了collections.add(pt1);collections.add(pt2);collections.add(pt3);collections.add(pt1);//内存泄露 内存溢出pt1.y = 8;//改变已经存储在collections中的对象的值,改变以后,pt1的hashcode值就改变了//collections.add(pt1);//集合中三个元素了//collections.remove(pt1);//无法删除,因为hashcode已变,删除的是现在的哈希值,而原有的哈希值找不到了System.out.println(collections.size());}
}
用反射方式:就是说不出现具体类的名字,而是从配置文件中读出来的
package studybase;
import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.HashSet;import java.util.Properties;
public class ReflectTestf2 {
public static void main(String[] args) throws Exception{/* 创建配置文件:
右键工程名--new--File
给文件起名字:config.properties 向文件中添加内容:className=java.util.ArrayList */ //方式一:读写配置文件都可以 InputStream ips = new FileInputStream("config.properties");//连接配置文件
//方式二:用类加载器方式,但只能读配置文件 //InputStream ips = //ReflectTest2.class.getClassLoader().getResourceAsStream("javainhance2/day1/config.properties"); //方式三:用类加载 (其实内部也是用的类加载器) // InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");Properties props = new Properties();props.load(ips);//将配置文件内容加载到props中ips.close();String className = props.getProperty("className");//读取配置文件中的className值,此值为类名Collection collections =(Collection)Class.forName(className).newInstance(); //根据className创建字节码文件,并根据字节码创建该类对象,并把该类对象转为Collection类型
//这样就不需要修改代码,直接修改配置文件中的className值就可以了,比如className=java.util.HashSet
ReflectPoint pt1 = new ReflectPoint(3,3);ReflectPoint pt2 = new ReflectPoint(5,5);ReflectPoint pt3 = new ReflectPoint(3,3);//pt1和pt3,指向同一个对象,但是二者hashcode值不同,因此用equals比较时,不同//我么可以重写hashcode()方法,让二者值相等,那么equals就相同了collections.add(pt1);collections.add(pt2);collections.add(pt3);collections.add(pt1);//内存泄露 内存溢出pt1.y = 8;//改变已经存储在collections中的对象的值,改变以后,pt1的hashcode值就改变了//collections.add(pt1);//集合中三个元素了//collections.remove(pt1);//无法删除,因为hashcode已变,删除的是现在的哈希值,而原有的哈希值找不到了System.out.println(collections.size());}
}
用类加载器方式管理资源和配置文件。 配置文件的访问: 相对路径:InputStream ips = new FileInputStream("config.properties");//连接配置文件 在这里的文件路径是相对路径,如果将项目发给用户,那么该相对路径可能出问题,找不到 绝对路径:如果这里时绝对路径(如D盘),但是用户没有D盘的话,也会出现问题 getRealPath()方法:此时就用到了一个方法,可以解决这个问题:
getRealPath();得到项目在硬盘上的绝对路径 得到绝对路径后再加上配置文件在项目的相对路径,,这样就成了配置文件的绝对路径 一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。类加载器:将.class文件加载到内存中,也提供将普通文件(这里是配置文件)加载的功能。
一个知识点:eclipse会自动把源文件(src)中的java文件自动编译成.class文件并放到bin(classpath指定的目录)中,会把src中的非java文件按目录原封不动放到bin(classpath指定的目录)中。用户用的时候是在bin中的文件。而我们操作时只需要操作src中的文件
22、内省(IntroSpector)——JavaBean了解——特殊的java类 符合特殊规则:
int getAge()
void setAge(int age)
一般规则:javabean属性通过get set来操作 去掉方法名的get set 那么剩下的就应该是属性名,要全小写的
如:getTime--time
settime--time
getCPU--CPU
class Person { private int age;//不需要向外直接提供访问,private
public int getAge()//向外界提供得到age的方法
{ return age;
}
public void setAge(int age)//为javabean属性赋值
{ this.age = age;
}
}
注意:类javabean可以当做普通类来处理,而且符合javabean规则的类也可以当做javabean来处理
JavaBean是一种特殊的java类,主要用于传递数据信息,这种java类中方法主要用于访问私有的字段,且方法名符合某种命名规则 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个javabean中,这种javabean的实例对象通常称之为值对、对象(Value Object 简称VO ),这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些响应的方法来访问,javabean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名getId,至于你把它存到哪个比变量上,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是系哦啊写的,则把剩余部分的字母该成小写的 总之,一个类被当做javabean使用时,其属性是根据方法名推断出来的,它根本看不到java类的内部成员变量。
一个符合javabean特点的类可以当做普通类一样使用,但把它当做javabean来用肯定需要带来一些额外的好处,我们才会去了解和应用javabean,好处如下: 在java EE开发中,经常要使用到javabean,很多环境就要求按javabean方式进行操作,别人都这么用和要求这么做,那么你就没有挑选的余地
在JDK中提供了对javabean进行操作的一些API,这套API就称为内省,如果你自己去通过getX方法访问私有的x,怎么做?有一定难度,用内省这套API操作javabean比用普通类的方式更方便
一般方式: package studybase;
import java.beans.PropertyDescriptor;import java.lang.reflect.Method;
public class IntroSpectorTest {
/** * @param args */public static void main(String[] args) throws Exception {// TODO Auto-generated method stubjavaBeanTest jbt = new javaBeanTest();String propertyName = "name";PropertyDescriptor pd = new PropertyDescriptor(propertyName,jbt.getClass());//获取值Method methodGetName = pd.getReadMethod();Object retVal = methodGetName.invoke(jbt);System.out.println(retVal);//设置值PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,jbt.getClass());Method methodSetName = pd2.getWriteMethod();methodSetName.invoke(pd2,"zhangsan");System.out.println(pd2.getName());}
}
用beanutils工具包操作javabean 需要导入:commons-beanutils commons-logging
然后add to buildpath
用beanutils操作的属性,数据类型都会转换成String类型 用PropertyUtils操作的javabean属性,保持属性原来的数据类型
package studybase;
import java.beans.PropertyDescriptor;import java.lang.reflect.Method;import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;import org.apache.commons.beanutils.PropertyUtils;
public class IntroSpectorTest {
/** * @param args */public static void main(String[] args) throws Exception {// TODO Auto-generated method stubjavaBeanTest jbt = new javaBeanTest();String propertyName = "name";BeanUtils.setProperty(jbt,"name","zhangsan");String obj = BeanUtils.getProperty(jbt,"name");System.out.println(obj);//java7新特性//Map map = {name:"xx",age:23};//BeanUtils.setProperty(map, "name", "lisi");//propertyUtilsPropertyUtils.setProperty(jbt, "age", 24);}
}
package javainhance;
import java.beans.IntrospectionException;import java.beans.PropertyDescriptor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
import org.apache.commons.beanutils.BeanUtils;import org.apache.commons.beanutils.PropertyUtils;
public class IntroSpectorTest {
/** * @param args * @throws IntrospectionException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws NoSuchMethodException */public static void main(String[] args) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {// TODO Auto-generated method stubReflectPoint pt1 = new ReflectPoint(3,5);String propertyName = "x";//x-->X-->getX-->MethodGetX//方式一:// PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass());// Method methodGetX = pd.getReadMethod();// Object retVal = methodGetX.invoke(pt1);// System.out.println(retVal);//// Method methodSetX = pd.getWriteMethod();// methodSetX.invoke(pt1,7);//// System.out.println(pt1.getX());//方式二:PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass());Method methodGetX = pd.getReadMethod();Object retVal = methodGetX.invoke(pt1);System.out.println(retVal);Method methodSetX = pd.getWriteMethod();methodSetX.invoke(pt1,7);//BeanUtils使用方法/* 下载:commons-beanutils-1.8.3.zip 解压后将commons-beanutils-1.8.3.jar复制,在工程下new-folder(取名lib),粘贴到此处,然后右键-Build Path--Add to Build Path 下载:commons-logging-1.1.3.zip 将诶呀后将commons-logging-1.1.3.jar复制,粘贴到lib中,然后右键-Build Path--Add to Build Path */System.out.println(BeanUtils .getProperty(pt1, "x"));System.out.println(BeanUtils .getProperty(pt1, "x").getClass().getName());//类型是StringBeanUtils.setProperty(pt1, "x", "9");//beanutils中属性值以字符串形式操作,会自动转换类型System.out.println(pt1.getX());BeanUtils.setProperty(pt1, "birthday.time", "111");System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));//java7的新特性//Map map = {name:"zxx",age:18};//BeanUtils.setProperty(map, "name", "lhm");//map与javabean之间可以相互转换//也可以使用PropertyUtils,与BeanUtils的区别在于,set和get时,属性值类型是Integer;BeanUtils是String//因此二者的使用看需求PropertyUtils.setProperty(pt1, "x", 10);System.out.println(PropertyUtils.getProperty(pt1, "x"));System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());//类型为Integer}
}
24、了解注解及java提供的几个基本注解
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相对应的事。标记可以加在包、类、字段、方法、方法的参数以及局部变量上。
package studybase_2;
public class AnnotationTest {
//这就是注解,告诉编译器。。。。一些内容//一个注解其实就是一个类的实例对象//告诉编译器我已经知道runFinalizersOnExit已过时@SuppressWarnings("deprecation")public static void main(String[] args) {// TODO Auto-generated method stubSystem.runFinalizersOnExit(true);}//告诉编译器,此方法sayHello已过时,以后再被调用时就会提示@Deprecatedpublic static void sayHello(){System.out.println("hello");}
//表示重写@Overridepublic static String toString(){return "fdf";}}
25、注解的应用结构图
步骤1:注解类
@interface A
{ }
步骤2:应用了注解类的类
@A
Class B
{
}
步骤3:对“应用了注解类的类”进行反射操作的类
Class C
{ B.class.isAnnotionPresent(A.class);
A a = B.class.getAnnotion(A.class);
}
注解类三个生命周期: 源文件:@Retention(RetentionPolicy.SOURCE) .class阶段 : @Retention(RetentionPolicy.CLASS) 内存运行:@Retention(RetentionPolicy.RUNTIME) 因此在使用时可以明确该注解类的生命周期在哪儿,默认是.class阶段
@Override SOURCE阶段 @SuppressWarnings SOURCE阶段 @Deprecated RUNTIME阶段
@Target() 该注解说明该注解类可以使用在哪儿。
package studybase_2;
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
//元注解,注解的注解//元数据//元信息
//表示一直保留到运行时期@Retention(RetentionPolicy.RUNTIME)//表示只能放到方法上//@Target(ElementType.METHOD)//如果是多个可以这么写@Target({ElementType.METHOD,ElementType.TYPE})//参数为ElementType[]public @interface ItcastAnnotation {
}
26、为注解增加基本属性 什么是注解的属性,如果一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则就不是;如果还想区分是哪个班的学生,这时候可以为胸牌再增加一个属性来进行区分。加了属性的标记效果为:@MyAnnotation(olor="red")
定义基本类型的属性和应用属性
在注解中增加String color();
@MyAnnotation(color = "red")
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color());
可以认为上面这个@MyAnnotation是MyAnnotation类的一个实例对象
为属性指定缺省值
String color() default "yellow";
value 属性
String value() default "zhx";
如果注解中有一个名为value的属性,且你指向设置value属性(即其他属性都采用默认值或者只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lnm");
27、为注解增加高级属性 数组类型的属性
int[] arryAttr() default{1,3,2};
@MyAnnotation(arrayAttr={2,3,4});
如果数组属性中只有一个元素,这时候属性值部分可以省略大括号
枚举类型的属性
EnumTest.TrafficLamp();
@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
注解类型的属性
MetaAnnotation annotationAttr() default @MetaAnnotation('xxxx');
@MyAnnotation(annotationAttr = @MetaAnnotation('yy'))
可以认为上面这个恶MyAnnotation是MetaAnnotation类的一个实例对象,同样的道理,可以认为上面这个
@MetaAnnotation是MetaAnnotation类的一个实例对象
用代码如下:
MetaAnnotation ma = MyAnnotation.annotationAttr();
System.out.println(ma.value());
示例代码:
package studybase_2;
public @interface MetaAnnotation {String value();}
package studybase_2;
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
//元注解//元数据//元信息,注解的注解
//表示一直保留到运行时期@Retention(RetentionPolicy.RUNTIME)//表示只能放到方法上//@Target(ElementType.METHOD)//如果是多个可以这么写@Target({ElementType.METHOD,ElementType.TYPE})public @interface ItcastAnnotation {String color() default "blue";//是一个属性,在调用该注解时,需要在注解后标出//使用方法:@ItcastAnnotation(color = "red")//default "blue" 表示默认值。此时@ItcastAnnotation即可,不用再加color了String value();int[] arrayAttr() default {3,4,2};//EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;MetaAnnotation annotationAttr() default @MetaAnnotation("vale");}
package studybase_2;
@ItcastAnnotation(annotationAttr = @MetaAnnotation("fanglixun"),color = "red",value="fdf",arrayAttr={5,6,7})public class AnnotationTest {
//这就是注解,告诉编译器。。。。一些内容//一个注解其实就是一个类的实例对象//告诉编译器我已经知道runFinalizersOnExit已过时@SuppressWarnings("deprecation")public static void main(String[] args) {// TODO Auto-generated method stubSystem.runFinalizersOnExit(true);if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);System.out.println(annotation);System.out.println(annotation.color());System.out.println(annotation.value());System.out.println(annotation.arrayAttr().length);//System.out.println(annotation.lamp().nextLamp().name());System.out.println(annotation.annotationAttr().value());}}//告诉编译器,此方法sayHello已过时,以后再被调用时就会提示@Deprecatedpublic static void sayHello(){System.out.println("hello");}}
另一示例:package day2;public @interface MetaAnnotation { String value();}
package day2;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType;//元注解,注解的注解import day1.EnumTest;@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})public @interface ItcastAnnotation { String color() default "bule";//设置默认值 //String color(); String value(); int[] arrayAttr();//数组类型 //int[] arrayAttr() default {3,4,5}; //枚举 EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED; //注解 MetaAnnotation annotationAttr() default @MetaAnnotation("llj");}
package day2;@ItcastAnnotation(annotationAttr=@MetaAnnotation("flx"),color ="red",value="abd",arrayAttr={1,2,3})public class AnnotationTest { /** * @param args */ //警告 注解,告诉JVM忽略过时“deprecation”,仍然使用该方法 @SuppressWarnings("deprecation") public static void main(String[] args) { // TODO Auto-generated method stub System.runFinalizersOnExit(true);//deprecated,过时的方法 if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)) { ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class); System.out.println(annotation); System.out.println(annotation.color()); System.out.println(annotation.value()); System.out.println(annotation.lamp().nextLamp()); System.out.println(annotation.annotationAttr().value()); } } //标明该方法过时 @Deprecated public static void sayHello() { System.out.println("hello"); } //标明该方法覆盖父类方法 @Override public String toString() { return "hello"; }}注解理解:1、注解就是给类、成员或方法等添加标记,在编译运行时会检查是否有标记,若有就执行相关操作。2、注解三个生命周期,SOURCE、CLASS和RUNTIME,表示该注解执行到哪一步,然后就不再具有作用了。3、注解涉及到三个类,注解类A,应用注解类A的类B,对B进行反射操作的类C。4、注解类可以有属性,且属性需有值,如果在注解类A中设置属性默认值,那么在B中,A就不需要再对属性赋值了,当然如果需要的话,可以修改属性的值;如果不在A中设置属性默认值,那么就必须在B中,A对属性赋值。在C中对B反射可以得到A的实例对象,通过该对象可以获得A实例对象的属性值。注解的属性可以是基本数据类型,数组、字符串、枚举,另外注解也可以是注解类的属性
29、泛型,是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时,会去除掉“类型"信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
ArrayList <E>类定义和ArrayList<Integer>类引用中涉及如下术语: 整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>读作typeof
ArrayList称为原始类型
参数化类型和原始类型的兼容性 参数化类型可以引用一个原始类型的对象,编译报告警告,例如
Collection<String> c = new Vector();
原始类型可以引用一个参数化类型的对象,编译报告警告,例如
Collection c = new Vector<String> ();
参数化类型不考虑类型参数的继承关系 Vector<String> v = new Vector<Object>();错误
Vector<Object> v = new Vector<String>(); 错误
在创建数组实例时,数组的元素不能使用参数化类型 Vector<Integer> vectorList[]= new Vector<Integer>[10];错误
判断对错:原因,编译器严格按语法检查,不考虑执行时候 Vector v = new Vector<String>(); 编译通过,原始类型=参数化类型
Vector<Object> v1 = v;编译通过,参数化类型= 原始类型
package studybase_2;
import java.util.ArrayList;import java.util.Collection;
public class GenericTest {
/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub/*ArrayList collection1 = new ArrayList();collection1.add(1);collection1.add(1L);collection1.add("abd");int i =(Integer)collection1.get(1);//ClassCastException*/ArrayList<String> collection2 = new ArrayList<String>();collection2.add("abd");collection2.add("zhangshn");collection2.add("lisi");String element = collection2.get(1);System.out.println(element);}public static void printCollection(Collection<?> collection){System.out.println(collection.size());for(Object obj:collection){System.out.println(obj);}}}
另一示例: package day2; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; public class GenericTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //泛型为String的ArrayList内存放Integer数据 List<String> listStr = new ArrayList<String>(); listStr.add("a"); listStr.add("b"); listStr.add("c"); ListIterator<String> li = listStr.listIterator(); while(li.hasNext()) { System.out.println(li.next()); } //通过反射方式得到add方法,调用方法add Integer类型数据 Method methodList = listStr.getClass().getMethod("add", Object.class); methodList.invoke(listStr, 1); methodList.invoke(listStr, 2); methodList.invoke(listStr, 3); methodList.invoke(listStr, 4); System.out.println(listStr); System.out.println(listStr.get(3));//这里报错,因为方法get(index)返回值为String //向泛型为Integer的ArrayList添加String类型数据 List<Integer> listIn = new ArrayList<Integer>(); listIn.add(2); System.out.println(listStr.getClass()==listIn.getClass()); listIn.getClass().getMethod("add", Object.class).invoke(listIn, "ab"); System.out.println(listIn.get(1)); } }
泛型中的?通配符 问题:定义一个方法,该方法用于打印任意参数化类型的集合中的所有数据
错误方式:
public static void printCollection(Collection<Object> cols)
{ for(Object obj:cols)
{ System.out.println(obj);
}
cols.add("String");//没错,因为是Object 所以可以add String
cols = new HashSet<Date>();//错误,上面是Object 下面是Date 不能这样做
正确方式:
public static void printCollection(Collection<?> cols)
{ for(Object obj:cols)
{ System.out.println(obj);
}
cols.add("String");//错误,因为不知道?是什么类型,不一定匹配String类型
cols.size();//正确,此方法与类型参数没有关系
cols = new HashSet<Date>();//正确
//使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用于引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法
}
}
泛型中的?通配符的扩展 限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:
限定通配符总是包括自己。
30、自定义泛型,由C++的模板函数引入自定义泛型 如下函数的结构很相似,仅类型不同
int add(int x,int y)
{ return x+y;
}
int add(float x,float y)
{ return x+y;
} int add(double x,double y)
{ return x+y;
}
交换数组中的两个元素的位置的泛型方法语法定义如下: static <E> void swap(E[] a,int i ,int j) { E t = a[i];
a[i] = a[j];
a[j] = t;
}
用于防止泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回值类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。 只有引用类型才能作为泛型方法的实际参数,swap(new int[2]{3,2},1,2),语句会报编译错误 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如: Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,如 <V extends Serializable&cloneable> void method(){}
普通方法、构造方法和静态方法中都可以使用泛型 也可以用类型变量来表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch后的()中 在泛型中可以同时又多个类型参数,在定义他们的<>中用逗号分开,例如: public static<K,V> V getValue(K key){return map.get(key);}
java中的泛型类型类似于c++中的模板,但是这种相似性仅限于表面,java语言中的泛型基本上完全是在编译器中用于编译器执行类型检查和类型推断,然后生成普通的非凡行的字节码,这种实现技术为擦除erasure(编译器使用泛型类型保证类型安全,然后在生成字节码之前将其清除),这是因为扩展虚拟机指令来支持泛型被认为是无法接受的,这回为java厂商升级其jvm造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
package studybase_2;
import java.util.ArrayList;import java.util.Collection;
public class GenericTest {
/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub/*ArrayList collection1 = new ArrayList();collection1.add(1);collection1.add(1L);collection1.add("abd");int i =(Integer)collection1.get(1);//ClassCastException*/ArrayList<String> collection2 = new ArrayList<String>();collection2.add("abd");collection2.add("zhangshn");collection2.add("lisi");String element = collection2.get(1);System.out.println(element);//Class<String.class.asSubclass(Number.class);//Class<String> x = Class.forName("java.lang.String");add(3,5);
//类型的推断,二者最小公约Number x = add(3,3.3);//返回值类型为二者最小所在集,int float ---NumberObject s =add(3,"abc");//int String------Objectswap(new String[]{"a","fdf","fdfggg"},1,2);swap(new int[]{1,2,3},1,2);//错误,T应该是引用型类型,必须是对象猜想}public static void printCollection(Collection<?> collection){System.out.println(collection.size());for(Object obj:collection){System.out.println(obj);}}private static <T> T add(T x,T y){return null;}private static<T> void swap(T[] a,int i,int j){T tmp = a[i];a[i] = a[j];a[j] = tmp;}}
自定义泛型类的应用 如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下: public class GenericDao<T>
{ private T field1;
public void save(T obj)
{
}
public T getById(int id)
{
}
}
类级别的泛型时根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都课可以 GenericDao<String> dao = null;
new GenericDao<String>();
注意:在堆泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型 当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类锁共享的,所以静态成员不应该有类级别的类型参数。
public static <E> void update2()//可以,给该静态方法加泛型 { } public static void update(T obj)//错误,这里不能调用泛型参数 { } 问题:类中只有一个方法需要使用泛型,是使用类级别的翻新还是使用方法级别的泛型?方法级别。
package studybase_2;
import java.util.Set;
public class GenericDao<T> {
/** * @param args */public void add(T x){}public T findById(int id){return null;}public void delete(T obj){}public void delete(int id){}public void update(T obj){}public Set<T> findByConditions(String name){return null;}public T findByUserName(String name){return null;}public static <E> void update2()//可以,给该静态方法加泛型{}public static void update(T obj)//错误,这里不能调用泛型参数{}}
31、通过反射获得泛型的实际类型参 得到参数化类型的方法,通过反射得到该方法的字节码,然后得到该方法中参数的泛型类型 public static void main(String[] args){//通过反射得到泛型实际参数的类型Method applyMethod = GenericDao.class.getMethod("applyVector",Vector.class);Type[] type = applyMethod.getGenericParameterTypes();ParameterizedType pType = (ParameterizedType)types[0];System.out.println(pType.getRawType());//得到 VectorSystem.out.println(pType.getActualTypeArguments());//得到Date}public static void applyVector(Vector<Date> v1){//有个方法可以根据提供的变量获得该变量的泛型的实际参数}
32、类加载器 java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap ,ExtClassLoader,AppClassLoader
类加载器也是java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap
java虚拟机中的所有类加载器采用具有斧子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其制定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载器。
类加载器之间的斧子关系和管辖范围
BootStrap——JRE/lib/rt.jar | ExtClassLoader——JRE/lib/ext/*.jar |System classLoader——AppClassLoader——CLASSPATH指定的所有jar或目录 / \
MyClassLoader ItcastClassLoader——传播智客制定的特殊目录
示例: package day2; public class ClassLoaderTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println( ClassLoaderTest.class.getClassLoader().getClass().getName()); System.out.println(System.class.getClassLoader()); // null ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while(loader != null) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); } System.out.println(loader); System.out.println(System.class.getClassLoader()); System.out.println( ClassLoaderTest.class.getClassLoader().getClass().getName() ); ClassLoader loader1 = ClassLoaderTest.class.getClassLoader(); while(loader1 != null) { System.out.println(loader1.getClass().getName()); loader1 = loader1.getParent(); } System.out.println(loader1); } }
33、类加载器的委托机制
当java虚拟机要加载一个类时, 到底派出哪个类加载器去加载? 首先当前线程的类加载器去加载线程中的第一个类
如果类A中引用了类B,java虚拟机将使用加载类A的类加载器来加载类B
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
每个类加载器加载类时,又先委托给上级类加载器 当所有祖宗类加载器没有加载到类(先祖后父再自己),回到发起者来加载器,还加载不了,则抛出ClassNotFoundException,,不是再去找发起者类加载器的子类加载器,因为没有getChild方法。
分析:由发起者父类加载,而不是由发起者子类去加载,原因一:发起者可能有多个子类,那么不确定由哪个子类去加载,而发起者只有一个父类,因此可以确定由父类加载器去加载。原因二:由父类加载器加载那么产生一份字节码文件,而由多个子类加载,有可能出现多份相同字节码,浪费内存空间。
34、编写自己的类加载器 知识讲解:
自定义的类加载器必须继承ClassLoader
loadClass方法与findClass方法
defineClass方法
父类--loadClass/findClass--得到class文件的转换成字节码--definClass(),简单的说就是,继承ClassLoader,覆盖findClass()方法,findClass()中用definClass()方法将字节数组转为字节码。(loadClass作用是逐级委托父类加载器去加载类,因此不用覆盖) 编程步骤
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类加载器,可实现对加密过的类进行装载和解密
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以处理使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName
实验步骤:
对不带包名的class文件进行加密,加密结果存放到另一个木,例如java MyClassLoader MyTest.class F:\itcast 运行加载类的程序时,结果能够被正常加载,但打印出来的类加载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类加载器装载失败
删除CLASSPATH环境下定的类文件,再执行上一步操作就可以了。
package day2; import java.util.Date; public class ClassLoaderAttachment extends Date { public String toString() { return "hello ,itcast"; } }
package day2; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MyClassLoader extends ClassLoader{ /** * @param args */ public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub String srcPath = args[0];//待加密的字节码文件 String destDir = args[1]; //加密后文件的存放位置 FileInputStream fis = new FileInputStream(srcPath); String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1); String destPath = destDir+"\\"+destFileName; FileOutputStream fos = new FileOutputStream(destPath); cypher(fis,fos); fis.close(); fos.close(); } private static void cypher(InputStream ips,OutputStream ops) throws Exception { int b = -1; while((b=ips.read()) != -1) { ops.write(b^0xff); } } private String classDir; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classFileName = classDir+"\\"+name+".class"; FileInputStream fis ; try { fis = new FileInputStream(classFileName); ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { cypher(fis,bos); fis.close(); byte[] bytes = bos.toByteArray(); return defineClass(bytes,0,bytes.length); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } public MyClassLoader(){} public MyClassLoader(String classDir) { this.classDir = classDir; } }
package day2; import java.util.Date; public class ClassLoaderTest { /** * @param args * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { // TODO Auto-generated method stub System.out.println( ClassLoaderTest.class.getClassLoader().getClass().getName()); System.out.println(System.class.getClassLoader()); // null ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while(loader != null) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); } System.out.println(loader); System.out.println(System.class.getClassLoader()); System.out.println( ClassLoaderTest.class.getClassLoader().getClass().getName() ); ClassLoader loader1 = ClassLoaderTest.class.getClassLoader(); while(loader1 != null) { System.out.println(loader1.getClass().getName()); loader1 = loader1.getParent(); } System.out.println(loader1); System.out.println(new ClassLoaderAttachment().toString()); Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment"); Date d1 = (Date) clazz.newInstance(); System.out.println(d1); } }
35、编写一个能打印出自己的类加载器名称和当前类加载器的斧子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassLoader 把MyServlet.class文件打成jar包,放到ext目录下,重启tomcat,发现找不到HttpServlet错误
把tomcat中的servlet.jar也放到ext目录中,问题解决,打印结果ExtclassLoader
package cn.itcast1.itcastweb1.web.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyServlet extends HttpServlet { /** * The doGet method of the servlet. <br> * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); ClassLoader loader = this.getClass().getClassLoader(); while(loader != null) { out.println(loader.getClass().getName()); loader = loader.getParent(); } out.close(); } }
36、代理的概念与作用 程序中的代理
要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、、日志、计算方法的运行时间、事务管理等等,该如何做》
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类,这样以后很容易切换,比如,想要日志功能时就配置代理类,否则配置目标类,这样增加系统功能很容易,以后运行一段时间,又想去掉系统功能也很容易。
AOP Aspect oriented program 面向方面的编程
动态代理技术 要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情,写成百上千个代理类
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
如果目标类没有实现一个接口,那么CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以便用CGLIB库
代理类的各个方法中通常除了要调用目标的响应方法和对外返回目标返回的结果,还可以在代理方法中的如下四个位置加上系统功能代码:
在调用目标方法之前
在调用目标方法之后
在调用目标方法前后
在处理目标方法异常的catch块中
分析JVM动态生成的类 创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数
编码列出动态类中的所有构造方法和参数名
编码列出动态类中所有的方法和参数名
创建动态类的实例对象
用反射获得构造方法
编写一个最简单的invocationHandler类
调用构造方法创建动态类的实例对象,并将编写的invocationHandler类的实例对象传进去
打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常
将创建动态类的实例对象代理该次匿名内部类的形式填写,
总结思考,让jvm创建动态类及其实例对象,需要给它提供哪些信息
三个方面:
生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知
产生的类字节码必须有个一个关联的类加载器对象
生成的类中的方法的代码是怎么样的,也得由我们提供,把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码,提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了
用Proxy.newInstance方法直接一步就创建出代理对象
猜想分析动态生成的类的内部代码 动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接收InvocationHandler参数的构造方法。
构造方法接收一个InvocationHandler对象,接收对象了要干什么用?该方法内部代码是怎么样实现的?
$Proxy0 implements Collection
{ InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{ this.handler = handler; }
} 实现的Collection接口中的各个方法的代码是怎么样的?InvocationHandler接口中定义的invoke方法接收的三个参数是什么意思? int size()
{ return handler.invoke(this,this.getClass().getMethod("size"),null);
}
说明: Client 程序调用objproxy.add("aaa")方法时,涉及三要素:objProxy对象,add方法,"aaa"参数。 Class Proxy$ { add(Object object)
{ return handler.invoke(Object proxy,Method method,Object[] args),
}
}
示例: package day3; import java.lang.reflect.Constructor; 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 ProxyTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println(clazzProxy1.getName()); System.out.println("-----begin constructors list------"); //$Proxy0() //$Proxy0(InvocationHandler,int) Constructor[] constructors = clazzProxy1.getConstructors(); for(Constructor constructor:constructors) { String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for(Class clazzParam:clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if((clazzParams!= null)&&clazzParams.length !=0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("-----begin methods list------"); //$Proxy0() //$Proxy0(InvocationHandler,int) Method[] methods = clazzProxy1.getMethods(); for(Method method:methods) { String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam:clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if((clazzParams!= null)&&clazzParams.length !=0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("-----begin creat instance list------"); //方式一:用一般方式,类实现InvocationHandler Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); class MyInvocationHandler1 implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } } Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1()); System.out.println(proxy1); System.out.println(proxy1.toString()); proxy1.clear();//清楚集合,返回值为void //System.out.println(proxy1.size());//出现问题,空指针异常,方法size()有返回值 //size()方法返回值类型int,而 invoke方法 return null,不匹配,因此出问题 //方式二:用匿名内部类方式实现 Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); //方式三:用方法newProxyInstance Collection proxy3 = (Collection)Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { ArrayList target = new ArrayList(); public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{ //ArrayList target = new ArrayList(); long beginTime = System.currentTimeMillis(); Object retVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println(method.getName()+"running time of"+(endTime-beginTime)); return retVal; //return method.invoke(proxy3, args);死循环 } } ); proxy3.add("zxx");//每调用一次add方法就调用一次invoke proxy3.add("lhm");//每调用一次add方法就调用一次invoke proxy3.add("bxd");//每调用一次add方法就调用一次invoke System.out.println(proxy3.size());//每调用一次size()方法就调用一次invoke //这里size()为0,因为ArrayList target = new ArrayList();在invoke方法内部,每次调用都是单独的,因此size为0 //将ArrayList target = new ArrayList();挪到方法外就可以了 System.out.println(proxy3.getClass().getName()); //Proxy0,因为只有hashCode() equals()和 toString()三个方法委托给InvocationHandler,其他方法有各自的实现 //上面的add size方法,InvocationHandler对象的invoke方法,用的是toString方法 } }
分析动态代理的工作原理图 怎样将目标类传进去? 直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。 将系统功能代码模块化,即将切面代码页改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供? 把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接受者只要调用这个对象的方法,即等于执行了外界提供的代码。
为bind方法增加一个Advice参数。
package day3; import java.lang.reflect.Method; public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }
package day3; import java.lang.reflect.Method; public class MyAdvice implements Advice{ long beginTime = 0; @Override public void beforeMethod(Method method) { // TODO Auto-generated method stub System.out.println("study"); beginTime = System.currentTimeMillis(); } @Override public void afterMethod(Method method) { // TODO Auto-generated method stub System.out.println("work"); long endTime = System.currentTimeMillis(); System.out.println(method.getName()+"running time of"+(endTime-beginTime)); } }
package day3; import java.lang.reflect.Constructor; 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 ProxyTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println(clazzProxy1.getName()); System.out.println("-----begin constructors list------"); //$Proxy0() //$Proxy0(InvocationHandler,int) Constructor[] constructors = clazzProxy1.getConstructors(); for(Constructor constructor:constructors) { String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for(Class clazzParam:clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if((clazzParams!= null)&&clazzParams.length !=0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("-----begin methods list------"); //$Proxy0() //$Proxy0(InvocationHandler,int) Method[] methods = clazzProxy1.getMethods(); for(Method method:methods) { String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam:clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if((clazzParams!= null)&&clazzParams.length !=0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("-----begin creat instance list------"); //方式一:用一般方式,类实现InvocationHandler Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); class MyInvocationHandler1 implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } } Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1()); System.out.println(proxy1); System.out.println(proxy1.toString()); proxy1.clear();//清楚集合,返回值为void //System.out.println(proxy1.size());//出现问题,空指针异常,方法size()有返回值 //size()方法返回值类型int,而 invoke方法 return null,不匹配,因此出问题 //方式二:用匿名内部类方式实现 Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); // //方式三:用方法newProxyInstance // Collection proxy3 = (Collection)Proxy.newProxyInstance( // Collection.class.getClassLoader(), // new Class[]{Collection.class}, // new InvocationHandler() // { // ArrayList target = new ArrayList(); // public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{ // //ArrayList target = new ArrayList(); // long beginTime = System.currentTimeMillis(); // Object retVal = method.invoke(target, args); // long endTime = System.currentTimeMillis(); // System.out.println(method.getName()+"running time of"+(endTime-beginTime)); // return retVal; // //return method.invoke(proxy3, args);死循环 // } // } // ); // proxy3.add("zxx");//每调用一次add方法就调用一次invoke // proxy3.add("lhm");//每调用一次add方法就调用一次invoke // proxy3.add("bxd");//每调用一次add方法就调用一次invoke // System.out.println(proxy3.size());//每调用一次size()方法就调用一次invoke // //这里size()为0,因为ArrayList target = new ArrayList();在invoke方法内部,每次调用都是单独的,因此size为0 // //将ArrayList target = new ArrayList();挪到方法外就可以了 // System.out.println(proxy3.getClass().getName()); // //Proxy0,因为只有hashCode() equals()和 toString()三个方法委托给InvocationHandler,其他方法有各自的实现 // //上面的add size方法,InvocationHandler对象的invoke方法,用的是toString方法 //方式四:框架 final ArrayList target = new ArrayList(); Collection proxy3 = (Collection)getProxy(target, new MyAdvice()); proxy3.add("zxx");//每调用一次add方法就调用一次invoke proxy3.add("lhm");//每调用一次add方法就调用一次invoke proxy3.add("bxd");//每调用一次add方法就调用一次invoke System.out.println(proxy3.size());//每调用一次size()方法就调用一次invoke //这里size()为0,因为ArrayList target = new ArrayList();在invoke方法内部,每次调用都是单独的,因此size为0 //将ArrayList target = new ArrayList();挪到方法外就可以了 System.out.println(proxy3.getClass().getName()); //Proxy0,因为只有hashCode() equals()和 toString()三个方法委托给InvocationHandler,其他方法有各自的实现 //上面的add size方法,InvocationHandler对象的invoke方法,用的是toString方法 } public static Object getProxy(final Object target,final Advice advice) { Object proxy3 =Proxy.newProxyInstance( target.getClass().getClassLoader(), //new Class[]{Collection.class}, target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{ //ArrayList target = new ArrayList(); // long beginTime = System.currentTimeMillis(); // Object retVal = method.invoke(target, args); // long endTime = System.currentTimeMillis(); // System.out.println(method.getName()+"running time of"+(endTime-beginTime)); // return retVal; //return method.invoke(proxy3, args);死循环 advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } } ); return proxy3; } }
实现AOP功能的封装与配置 1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。 2、BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下: #xxx = java.util.ArrayList xxx = cn.itcast.ProxyFactoryBean xxx.target = java.util.ArrayList xxx.advice = cn.itcast.MyAdvice 3、ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息? 目标通知4、编写客户端应用: 编写实现Advice接口的类和在配置文件中进行配置调用BeanFactory获取对象
示例: package day3; import java.lang.reflect.Method; public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }
package day3.aopframework; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import day3.Advice; public class BeanFactory { Properties props = new Properties(); public BeanFactory(InputStream ips) { try { props.load(ips); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public Object getBean(String name) throws InstantiationException, IllegalAccessException { String className = props.getProperty(name); Object bean = null; try { Class clazz = Class.forName(className); bean = clazz.newInstance(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } if(bean instanceof ProxyFactoryBean) { Object proxy = null; ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean; Object target; try { Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance(); target = Class.forName(props.getProperty(name+".target")).newInstance(); proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); proxy = ((ProxyFactoryBean)bean).getProxy(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return proxy; } return bean; } }
package day3.aopframework; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import day3.Advice; public class ProxyFactoryBean { private Advice advice; 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; } private Object target; public Object getProxy() { // TODO Auto-generated method stub Object proxy3 =Proxy.newProxyInstance( target.getClass().getClassLoader(), //new Class[]{Collection.class}, target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{ advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } } ); return proxy3; } }
配置文件:config.properties xxx=java.util.ArrayList #xxx=day3.aopframework.ProxyFactoryBean xxx.advice=day3.MyAdvice xxx.target=java.util.ArrayList
测试类: package day3.aopframework; import java.io.InputStream; public class AopFrameWorkTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub InputStream ips = AopFrameWorkTest.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(ips).getBean("xxx"); System.out.println(bean.getClass().getName()); } }