程序员_Java高新技术<二>
2013-05-05 13:30
489 查看
一、
内省:Introspector(对javaBean进行操作)-->JavaBean:特殊的java类
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。
这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?
JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?
如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
setId()的属性名?id
isLast()的属性名?last
setCPU的属性名是什么??CPU
getUPS的属性名是什么??UPS
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。
牛人巨作:
1.!!!利用BeanUtils工具包操作JavaBean!牛!!
* 需要引进beanutil.jar和logging.jar包
即
打开提供的工具包,在两个工具包中分别找到commons-beanutils.jar和commons-logging.jar,
在工程下新建lib文件夹,把jar包拷贝到里面,jar包右键-Build Path-Add to BuildPath添加到里面。
2.!!!利用PropertiesUtils工具操作JavaBean。
二者区别是:前者参数传递字符串类型,后者int型
注解:Annotation
注解:相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,告诉编译器某种信息,
没加,则等于没有某种标记,以后javac编译器,开发工具盒其他程序可以用反射来了解你的类
及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,字段,方法,
方法的参数以及局部变量上。
三种基本的注解:@Deprecated 标记过时
@SuppressWarnings 标记禁止警告
@Override
标记覆盖
/*注解的定义与反射调用*/
注解的应用结构图:
@interface A{}:注解类
@AClass B{}:应用了”注解类“的类
对应用了“注解类的类”进行反射操作的类
Class C{
B.class.isAnnotationPresent(A.class);
A a=B.class.getAnnotation(A.class);
}
生命周期有三个阶段:Source(源程序阶段),class(类文件阶段),Runtime(内存运行阶段)
而注解的默认生命周期到class阶段。所以要想让注解一直执行到运行是,需要在注解类
前加上注解@Retention(RententionPolicy.RUNTIME);//保持(保持策略.运行时)
例如:
//思考:@Override,@SuppressWarnings,@Deprecated这三个注解的属性值分别是什么?即这三种注解保持到什么时期
@Deprecated:一直保持到运行是,都是过时的,所以注解属性值是Runtime
@SupppressWarnings:忽略警告,是告诉编译器的,编译完就没用了,该注解只在源文件时有用,所以其属性值是Source
@Override:同样是告诉编译器的,所以属性值是Source
----------------------------------------------------------
@Target元注解:表示该注解是加在哪个位置上。
Target的默认为任何元素,设施Target等于ElementType.METHOD,原来加在类上的注解就报错了,因为此注解是表示加在方法上,
改为用数组设置{ElementType.METHOD,ElementType.TYPE}就可以了,之所以是.TYPE,是因为TYPE是Java中类,接口,注解,枚举的父类。
例如:
@Retention(RetentionPolicy.RUNTIME)//元注解,注解类的注解
@Target({ElementType.METHOD,ElementType.TYPE})//表明此注解是可加在方法上,可加在类上。
public @interface ItcastAnnotation {
}
---------------------------------------------------------------------------
为注解增加基本属性:
在注解类中可加入一些属性:
//注解类:
/*泛型:*/
使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,
这样更安全;并且当从集合获取一个对象时,编译器也可知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
注意:泛型是提供给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>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:下面的代码会报错误吗?
Vector v1 = new Vector<String>(); //正确,参数化类型可以赋给原始类型
Vector<Object> v = v1;//正确,因为泛型只在编译时检查,在编译时,v1是Vector型,还没有将式1的右边赋给左边,所以编译器认为,式2是将一个原始类型赋给Object型。
//泛型中的?通配符
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,
可以调用与参数化无关的方法,不能调用与参数化有关的方法。
例如:
泛型中的?通配符的扩展
限定通配符的上边界:
正确:Vector<?extends Number> x=new Vector<Integer>();
//左边接收的类型必须是Number及其子类
错误:Vector<?extends Number> x=new Vector<String>();
//String不是Number的子类
限定通配符的下边界:
正确:Vector<?super Integer> x=new Vector<Number>();
//左边接收的类型必须是Integ或其父类
错误:Vector<?super Integer> x=new Vector<Byte>();
//Byte不是Integer的父类
--------------------------------------------------------------------------
//泛型集合的综合应用案例
//自定义泛型方法:
思考:为什么在示例1中进行了自动装箱拆箱操作,而在示例二中没有呢?
因为在示例2中,new[3]本身就已经是对象了,你想要的可能就是int型数组呢?
他装箱岂不是弄巧成拙了,得不到int型数组了。所以编译器不会进行自动拆装箱功能。
//泛型小结:
1.只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
2.除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,
例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
3.普通方法、构造方法和静态方法中都可以使用泛型。
4.也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
5.在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
-------------------------------------------------------------------------
自定义泛型方法的练习和类型推断的总结:
-------------------------------------------------------------------
类型参数的类型判断:
编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,
这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) ? static <E> void swap(E[] a, int i, int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,
如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) ? static <T> T add(T a, T b)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,
且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) ? static <T> void fill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,
这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ? static <T> T add(T a, T b)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,
而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) ? static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) ? static <T> void copy(Collection<T> a , T[] b);
------------------------------------------------------------------------------------------------------
自定义泛型类的应用:
1.如果类的实例对象中的多处要用到一个泛型参数,即这些地方引用的泛型类型要保持 同一个实际类型时,这时候要采用泛型类型的方式定义,
也就是类级别的泛型,语法格式如下:
public class GenericDao<T>
{
private T field1;
public void sava(T obj){};
public T getById(int id){};
}
2.类级别的泛型是根据引用该类名时指定的类型信息来参数化 类型变量的,例如,如下两种方式都可:
GenericDao<String> dao=null;
new GeneriDao<String>();
注意:
1.在对泛型类型进行参数化时,类型参数的实例必须是引用类型。不能是基本类型。
2.当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被
静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,
例如:public static void update(T obj){};错误
GenericDao <ReflectPoint> dao=new GenericDao<ReflectPoint>();
也就是静态方法存在时,对象有可能还不存在,泛型当然也就没被实例化,T就不知道是什么类型。
问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
类级别的。
----------------------------------------------------------------------------
通过反射获得泛型的实际类型参数:
类加载器及其委托机制的深入分析:
1. Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,
每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
2. 类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,
显然必须有第一个类加载器不是不是java类,这正是BootStrap。
3. Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,
需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器之间的父子关系和管辖范围:
BootStrap------------>JRE/lib/rt.jar
|
ExtClassLoader------->JRE/lib/ext/*.jar
|
System classLoader--> AppClassLoader-------->CLASSPATH指定的所有jar或目录 ||
:MyClassLoader ItcastClassLoader-->指定的特殊目录
自己的类加载器:
/*类加载器的委托机制:*/
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,
不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
父类-->loadClass(委托机制找父类)/findClass(自己的加载器加载类)
---->definClass()---->将class转换成字节码
自己模拟加密解密类加载器:
/*可以手动选定哪个类加载器:要运行的类-右键-export-JAR file-JAR file选择要用的加载器:
如C:\Program Files\Java\jdk1.7.0_15\jre\lib\ext\itcast.jar
如果此时该类继承了另一个类,那么该类也就默认由该类的加载器及该加载器的父类加载,但不会由该加载器的子类加载器加载
内省:Introspector(对javaBean进行操作)-->JavaBean:特殊的java类
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。
这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?
JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?
如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
setId()的属性名?id
isLast()的属性名?last
setCPU的属性名是什么??CPU
getUPS的属性名是什么??UPS
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">public class IntroSpectorTest { /** * @param args * @throws IntrospectionException */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub ReflectPoint pt1=new ReflectPoint(4,5); String propertyName="x"; //"x"-->"X"-->"getX"-->MethodGetX--> /*get方法*/ Object retVal = getProperty(pt1, propertyName); System.out.println(retVal); /*Set方法 */ /*为了让Refact中的抽象成方法功能识别当中的参数,将传入的值7拿出来*/ Object value=7; setProperties(pt1, propertyName, value); System.out.println(pt1.getX()); } //以下两方法是右键-Refact-Extract Method的抽取方法功能自动生成的。 private static void setProperties(ReflectPoint pt1, String propertyName, Object value) throws IntrospectionException, IllegalAccessException, InvocationTargetException { //PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性 //构造方法接收两个参数:属性名,类的字节码。 PropertyDescriptor pd2=new PropertyDescriptor(propertyName,pt1.getClass());//属性名,哪个类 Method methodSetX=pd2.getWriteMethod();//得到x的读方法 methodSetX.invoke(pt1,value);//调用x的get方法。 } private static Object getProperty(Object pt1, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { /* PropertyDescriptor pd=new PropertyDescriptor(propertyName,pt1.getClass());//属性名,哪个类 //PropertyDescriptor:属性描述类 Method methodGetX=pd.getReadMethod();//得到x的读方法 Object retVal=methodGetX.invoke(pt1);//调用x的get方法。 */ /*另方法:较复杂*/ /*Introspector 类为通过工具学习有关受目标 Java Bean 支持的属性、事件和方法的知识提供了一个标准方法。 * * Introspector.getBeanInfo(Class<?> beanClass) 在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件 */ BeanInfo beanInfo=Introspector.getBeanInfo(pt1.getClass());//内省Introspector方式,查看JavaBean属性 PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors(); Object retVal=null; for(PropertyDescriptor pd:pds) { if(pd.getName().equals(propertyName)) { Method methodGetX=pd.getReadMethod(); retVal=methodGetX.invoke(pt1); break; } } return retVal; } }</span>
牛人巨作:
1.!!!利用BeanUtils工具包操作JavaBean!牛!!
* 需要引进beanutil.jar和logging.jar包
即
打开提供的工具包,在两个工具包中分别找到commons-beanutils.jar和commons-logging.jar,
在工程下新建lib文件夹,把jar包拷贝到里面,jar包右键-Build Path-Add to BuildPath添加到里面。
2.!!!利用PropertiesUtils工具操作JavaBean。
二者区别是:前者参数传递字符串类型,后者int型
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">System.out.println(BeanUtils.getProperty(pt1, "x")); BeanUtils.setProperty(pt1, "x", "9");//注意9是字符串形式 System.out.println(pt1.getX()); BeanUtils.setProperty(pt1, "birthday.time", 111);//在类中有个birthday属性,在birthday //属性中有个time属性,该工具的方法支持级联操作,直接操作time System.out.println(BeanUtils.getProperty(pt1,"birthday.time")); /* BeanUtils工具包里还提供了JavaBean与map的对应关系: JavaBean-->map 方法:describe(java.lang.Object bean) map-->JavaBean 方法:Populate(java.lang.Object bean,java.util.Map properties) //java7新特性 Map map={name:"zxx",age:18}; BeanUtils.setProperty(map,"name","lhm");//用BeanUtils工具设置map */ //PropertyUtils工具操作JavaBean PropertyUtils.setProperty(pt1, "x", 9);//9是int型 System.out.println(PropertyUtils.getProperty(pt1, "x")); </span>
注解:Annotation
注解:相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,告诉编译器某种信息,
没加,则等于没有某种标记,以后javac编译器,开发工具盒其他程序可以用反射来了解你的类
及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,字段,方法,
方法的参数以及局部变量上。
三种基本的注解:@Deprecated 标记过时
@SuppressWarnings 标记禁止警告
@Override
标记覆盖
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">@SuppressWarnings("deprecation")//禁止警告过时 public static void main(String[] args) { // TODO Auto-generated method stub System.runFinalizersOnExit(true); //该方法已过时,加上以上注解,编译器就不提示过时了 } @Deprecated//说明以下方法已过时 public static void sayHello() { System.out.print("hi,传智播客"); } @Override//此注解告诉编译器你在覆盖方法,当你出错时,编译器就会告诉你 public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ReflectPoint other = (ReflectPoint) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } </span>
/*注解的定义与反射调用*/
注解的应用结构图:
@interface A{}:注解类
@AClass B{}:应用了”注解类“的类
对应用了“注解类的类”进行反射操作的类
Class C{
B.class.isAnnotationPresent(A.class);
A a=B.class.getAnnotation(A.class);
}
生命周期有三个阶段:Source(源程序阶段),class(类文件阶段),Runtime(内存运行阶段)
而注解的默认生命周期到class阶段。所以要想让注解一直执行到运行是,需要在注解类
前加上注解@Retention(RententionPolicy.RUNTIME);//保持(保持策略.运行时)
例如:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">//注解类 @Retention(RetentionPolicy.RUNTIME)//元注解,注解类的注解,让该自定义注解保持到运行时 public @interface ItcastAnnotation { } //带有注解类的注解 @ItcastAnnotation public class AnnotationTest { //对应用了注解类的类进行反射操作 @ItcastAnnotation public class AnnotationTest { public static void main(String[] args) { //对应用了注解类的类进行反射操作 if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)) { ItcastAnnotation annotation=(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class); System.out.println(annotation); } } </span>
//思考:@Override,@SuppressWarnings,@Deprecated这三个注解的属性值分别是什么?即这三种注解保持到什么时期
@Deprecated:一直保持到运行是,都是过时的,所以注解属性值是Runtime
@SupppressWarnings:忽略警告,是告诉编译器的,编译完就没用了,该注解只在源文件时有用,所以其属性值是Source
@Override:同样是告诉编译器的,所以属性值是Source
----------------------------------------------------------
@Target元注解:表示该注解是加在哪个位置上。
Target的默认为任何元素,设施Target等于ElementType.METHOD,原来加在类上的注解就报错了,因为此注解是表示加在方法上,
改为用数组设置{ElementType.METHOD,ElementType.TYPE}就可以了,之所以是.TYPE,是因为TYPE是Java中类,接口,注解,枚举的父类。
例如:
@Retention(RetentionPolicy.RUNTIME)//元注解,注解类的注解
@Target({ElementType.METHOD,ElementType.TYPE})//表明此注解是可加在方法上,可加在类上。
public @interface ItcastAnnotation {
}
---------------------------------------------------------------------------
为注解增加基本属性:
在注解类中可加入一些属性:
//注解类:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">@Retention(RetentionPolicy.RUNTIME)//元注解,注解类的注解 @Target({ElementType.METHOD,ElementType.TYPE}) public @interface ItcastAnnotation { String color() default "blue";//注解的属性。类似一个方法,返回的是String类型 String value(); int[] arrayAttr() default{2,3,4};//数组类型 的属性 EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.GREEN;//枚举类型的属性。 } //加注解的注解类 @ItcastAnnotation(color="red",value="abc",arrayAttr={1,2,3}) public class AnnotationTest { @ItcastAnnotation("xyz")//当注解类中只有一个需要初始化的属性时,括号内不需要写属性名称。 public static void main(String[] args) { if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)) { ItcastAnnotation annotation=(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class); System.out.println(annotation.color()); System.out.println(annotation.value()); System.out.println(annotation.arrayAttr().length); System.out.println(annotation.lamp().nextLamp()); } } </span>
/*泛型:*/
使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,
这样更安全;并且当从集合获取一个对象时,编译器也可知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">import java.lang.reflect.Constructor; import java.util.ArrayList; public class GenericTest { /** * @param args */ public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub ArrayList collection1=new ArrayList(); collection1.add(1); collection1.add(1L); collection1.add("abc"); // int i=(Integer)collection1.get(1); //取出的数据不能进行Integer转换,报错,所以考虑到用泛型 //集合中用到了泛型 ArrayList<String> collection2=new ArrayList<String>(); //collection2.add(1); //collection2.add(1L); collection2.add("abc"); String element=collection2.get(0); //在反射中也用到了泛型 Constructor<String> constructor1=String.class.getConstructor(StringBuffer.class);//泛型指定是谁的构造方法 String str2=(String)constructor1.newInstance(new StringBuffer("abc")); System.out.println(str2.charAt(2));//c } } </span>
注意:泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住
源程序中的非法输入,编译器编译带类型说明的集合时会去除掉类型信息,使程序
运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型
完全一样,由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以
往某个泛型集合中加入其它类型的数据,例如:用反射得到集合,再调用其add方法即可
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">//集合中用到了泛型 ArrayList<String> collection2=new ArrayList<String>(); //collection2.add(1); //collection2.add(1L); collection2.add("abc"); String element=collection2.get(0); //以下证明泛型只在编译时有用 ArrayList<Integer> collection3=new ArrayList<Integer>(); System.out.println(collection3.getClass()==collection2.getClass()); //返回结果为true,说明在运行时,collection2与collection3的字节码是一样的,即泛型只在编译时有用。 //那么就可以用反射的方式绕过泛型这一特点,使只接收Integer类型的集合,接收字符串 collection3.getClass().getMethod("add", Object.class).invoke(collection3,"abc"); System.out.println(collection3.get(0)); </span>
了解泛型:
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>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:下面的代码会报错误吗?
Vector v1 = new Vector<String>(); //正确,参数化类型可以赋给原始类型
Vector<Object> v = v1;//正确,因为泛型只在编译时检查,在编译时,v1是Vector型,还没有将式1的右边赋给左边,所以编译器认为,式2是将一个原始类型赋给Object型。
//泛型中的?通配符
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">错误方式: public static void printCollection(Collection<Object> cols) { for(Object obj:cols) { System.out.println(obj); } /* cols.add("string");//没错 cols = new HashSet<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>(); }</span>
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,
可以调用与参数化无关的方法,不能调用与参数化有关的方法。
例如:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">public static void printCollection(Collection<?> collection) { //collection.add(1);错,因为不知道传过来的集合是什么类型,所以不能存值进去 System.out.println(collection.size()); for(Object obj :collection) { System.out.println(obj); } }</span>
泛型中的?通配符的扩展
限定通配符的上边界:
正确:Vector<?extends Number> x=new Vector<Integer>();
//左边接收的类型必须是Number及其子类
错误:Vector<?extends Number> x=new Vector<String>();
//String不是Number的子类
限定通配符的下边界:
正确:Vector<?super Integer> x=new Vector<Number>();
//左边接收的类型必须是Integ或其父类
错误:Vector<?super Integer> x=new Vector<Byte>();
//Byte不是Integer的父类
--------------------------------------------------------------------------
//泛型集合的综合应用案例
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">HashMap<String,Integer> maps=new HashMap<String,Integer>(); maps.put("ljx", 23); maps.put("zxz", 24); maps.put("xyk", 24); Set<Map.Entry<String,Integer>> entrySet=maps.entrySet(); for(Map.Entry<String,Integer> entry:entrySet) { System.out.println(entry.getKey()+"::"+entry.getValue()); } </span>
//自定义泛型方法:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">//示例1: public static void main(String[] args) { add(3,5);//只有引用类型才能作为泛型方法的实际参数. //此处进行了自动装箱拆箱功能,将int型变为Integer型 Number x1=add(3.5,3);//int float型的父类是Number Object x2=add(3,"abc");//int 字符串的父类是Object型 } private static <T> T add(T x,T y){//<T>:表示该方法声明了一个泛型,相当于告诉编译器T是什么!add前的T是add方法的返回类型 return null; } 示例2: 注意:只有引用类型才能作为泛型方法的实际参数 //自定义泛型方法 swap(new String[]{"abc","xyz","ljx"},1,2); //swap(new int[]{1,2,3},1,2);报错,只有引用类型才能作为泛型方法的实际参数,int型不是引用类型 } private static <T> void swap(T[]a,int i,int j){ T temp=a[i]; a[i]=a[j]; a[j]=temp; } </span>
思考:为什么在示例1中进行了自动装箱拆箱操作,而在示例二中没有呢?
因为在示例2中,new[3]本身就已经是对象了,你想要的可能就是int型数组呢?
他装箱岂不是弄巧成拙了,得不到int型数组了。所以编译器不会进行自动拆装箱功能。
//泛型小结:
1.只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
2.除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,
例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
3.普通方法、构造方法和静态方法中都可以使用泛型。
4.也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
5.在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
-------------------------------------------------------------------------
自定义泛型方法的练习和类型推断的总结:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">//1.编写泛型方法,自动将Objec类型的对象转换成其他类型。 private static <T> T autoConvert(Object obj) { return (T)obj; } Object obj="abc"; String x3=autoConvert(obj);//要求String类型的,antoConvert就返回String类型的数据 //2.定义一个方法,可以将任意类型的数组中的所有元素填充为相应的类型的某个对象。 private static <T>void fillArray(T[]a,T obj) { for(int i=0;i<a.length;i++) { a[i]=obj; } } //3.采用自定义泛型方法打印出任意参数化类型的集合中的所有内容。 public static<T>void printCollection3(Collection<T> collection) { for(Object obj:collection) { System.out.println(obj); } } //4.定义一个方法,把任意参数类型的集合中的数据安全的复制到 相应类型的数组中。 public static <T>void copy1(Collection<T> dest,T[]src) { } //定义一个方法,把任意参数类型的数组 的数据安全的复制到 相应类型的数组中。 public static <T>void copy2(T[] dest,T[]src) { } copy1(new Vector<String>(),new String[10]); copy2(new Date[10],new String[10]); /copy1(new Vector<Date>(),new String[10]);/ //此处会报错,因为泛型具有传递性,在new Vector<Date>时,泛型认为是Date型, //泛型为Date型,那么在调用copy1方法时,T就认为是Date类型,当传进String类型时,就会出错。</span>
-------------------------------------------------------------------
类型参数的类型判断:
编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,
这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) ? static <E> void swap(E[] a, int i, int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,
如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) ? static <T> T add(T a, T b)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,
且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) ? static <T> void fill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,
这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ? static <T> T add(T a, T b)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,
而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) ? static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) ? static <T> void copy(Collection<T> a , T[] b);
------------------------------------------------------------------------------------------------------
自定义泛型类的应用:
1.如果类的实例对象中的多处要用到一个泛型参数,即这些地方引用的泛型类型要保持 同一个实际类型时,这时候要采用泛型类型的方式定义,
也就是类级别的泛型,语法格式如下:
public class GenericDao<T>
{
private T field1;
public void sava(T obj){};
public T getById(int id){};
}
2.类级别的泛型是根据引用该类名时指定的类型信息来参数化 类型变量的,例如,如下两种方式都可:
GenericDao<String> dao=null;
new GeneriDao<String>();
注意:
1.在对泛型类型进行参数化时,类型参数的实例必须是引用类型。不能是基本类型。
2.当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被
静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,
例如:public static void update(T obj){};错误
GenericDao <ReflectPoint> dao=new GenericDao<ReflectPoint>();
也就是静态方法存在时,对象有可能还不存在,泛型当然也就没被实例化,T就不知道是什么类型。
问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
类级别的。
----------------------------------------------------------------------------
通过反射获得泛型的实际类型参数:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">Vector<Date> v1=new Vector<Date>(); //怎么获得上式中v1实际装载的数据的类型呢?因为泛型编译阶段就消失了,显然不能在运行是直接获得 //可把要求的参数类型放在一个方法中,通过反射获得泛型的实际类型参数,因为方法字节码提供了一个得到参数类型的方法。 public static void applyVector(Vector<Date> v1) { } 然后通过以下方法: Method applyMethod=GenericTest.class.getMethod("applyVector", Vector.class); Type[] types=applyMethod.getGenericParameterTypes();//该方法可获得指定方法的参数类型 ParameterizedType pType=(ParameterizedType)types[0];//ParameterizedType是参数类型 System.out.println(pType.getRawType());//结果:Vector,getRawType()得到原始类型 System.out.println(pType.getActualTypeArguments()[0]);//,getActualTypeArguments得到实际化类型,因为可能有多个,如HashMap<T,E>,是个数组 public static void applyVector(Vector<Date> v1) { }</span>
类加载器及其委托机制的深入分析:
1. Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,
每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
2. 类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,
显然必须有第一个类加载器不是不是java类,这正是BootStrap。
3. Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,
需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器之间的父子关系和管辖范围:
BootStrap------------>JRE/lib/rt.jar
|
ExtClassLoader------->JRE/lib/ext/*.jar
|
System classLoader--> AppClassLoader-------->CLASSPATH指定的所有jar或目录 ||
:MyClassLoader ItcastClassLoader-->指定的特殊目录
自己的类加载器:
public class ClassLoaderTest { public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName()); //类加载器:sun.misc.Launcher$AppClassLoader System.out.println(System.class.getClassLoader()); //System类的加载器不是通天的类加载器,是BootStrap(不是一个类,而是在虚拟机内核里的),所以该行的返回值是null ClassLoader loader=ClassLoaderTest.class.getClassLoader(); while(loader!=null) { System.out.println(loader.getClass().getName()); loader=loader.getParent(); } System.out.println(loader); } }
/*类加载器的委托机制:*/
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,
不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
父类-->loadClass(委托机制找父类)/findClass(自己的加载器加载类)
---->definClass()---->将class转换成字节码
自己模拟加密解密类加载器:
//加密解密类: public class MyClassLoader extends ClassLoader { public static void main(String[] args) throws Exception { //对srcPath路径的类进行加密,输出到destDir 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(); } //加密算法,将输入流异或0xff后,写到输出流中。 private static void cypher(InputStream ips,OutputStream ops) throws IOException { 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"; try { FileInputStream fis=new FileInputStream(classFileName); ByteArrayOutputStream bos=new ByteArrayOutputStream(); 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(); } return super.findClass(name); } public MyClassLoader(String classDir) { this.classDir=classDir; } } //测试类: public class ClassLoaderTest { public static void main(String[] args) throws Exception { Class clazz=new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment"); Date d1=(Date)clazz.newInstance(); /*ClassLoaderAttachment d1=(Date)clazz.newInstance(); 不能写成这样,因为此时ClassLoaderAttachment是加密后乱码的文件, 编译器加载时会出错,应改成他的父类(在写该类时应该继承父类)就可以了。 */ System.out.println(d1); } }
/*可以手动选定哪个类加载器:要运行的类-右键-export-JAR file-JAR file选择要用的加载器:
如C:\Program Files\Java\jdk1.7.0_15\jre\lib\ext\itcast.jar
如果此时该类继承了另一个类,那么该类也就默认由该类的加载器及该加载器的父类加载,但不会由该加载器的子类加载器加载
相关文章推荐
- 程序员_Java高新技术<三>代理
- 程序员_Java初级<二> 关键字、标识符、运算符、进制
- 程序员_Java高新技术<一>
- 程序员_Java基础<二>-面向对象中
- 黑马程序员 java高新技术<二>--java5的枚举、反射的深入讲解
- 程序员修炼之道<二>
- Java面向对象的基本知识<二>
- 黑马程序员 JAVA基础<二> 面向对象之封装 继承 多态
- EBS OAF开发中的Java 实体对象(Entity Object)<二>
- Java 嵌套类和内部类演示样例<二>
- 黑马程序员 java概述与基础知识<二>
- 程序员修炼之路(六)编写自己的工具(JAVA注册系统级快捷键源码>jintellitype<)
- 程序员_Java初级<四>数组、进制转换函数
- 程序员_Java基础之<七>-集合框架
- 黑马程序员 高新技术<二>---> 反射
- java微信网页授权获取用户信息以及JSSDK自定义分享等功能<二>
- 程序员_Java基础之<十>-IO流<1>
- 黑马程序员 java高新技术<三>--java5的注解、java5的泛型
- 程序员_Java基础之<五>-线程
- 程序员_Java基础之<十一>-IO流<2>File流