jdk1.5高新技术(三) 内省,泛型

来源:互联网 发布:unity3d场景漫游 编辑:程序博客网 时间:2024/05/17 22:16


7.内省---了解JavaBean

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种类有私有的成员变量,只能通过个get/set方法来操作字段。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问.JavaBean的属性,因为对外无法看到内部的成员变量名,所以只能根据其中的setter和getter方法来确定的,而不是根据其中的成员变量.

例如:
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:

public class Demo {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;
<span style="white-space:pre"></span>}
}
import java.beans.BeanDescriptor;import java.beans.BeanInfo;import java.beans.IntrospectionException;import java.beans.Introspector;import java.beans.PropertyDescriptor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class IntrospectorTest {public static void main(String[] args) throws Exception {BeanInfo bi = Introspector.getBeanInfo(Demo.class);Demo demo = (Demo) Demo.class.newInstance();String propertyName = "ame";Object arg = "a";
<span style="white-space:pre"></span><pre name="code" class="java"><span style="white-space:pre"></span>//<span style="color:#ff0000;">获得所有字段的PropertyDescriptor</span>PropertyDescriptor[] pds = bi.getPropertyDescriptors();//<span style="color:#ff0000;">可以通过new PropertyDescriptor(propertyName,Demo.class)方式获得只指定的PropertyDescriptor</span>setProperty(arg, demo, propertyName, pds);getProperty(demo, propertyName, pds);}/** <span style="color:#ff0000;">*@param demo 要操作的bean * @param propertyName 字段名字(是set*的*号部分 不是成员变量的名字!!!) * @param pds bean内所有字段的PropertyDescriptor对象数组</span>*/protected static void getProperty(Demo demo, String propertyName,PropertyDescriptor[] pds) throws IllegalAccessException,InvocationTargetException {for (PropertyDescriptor p : pds) {//System.out.println(p.getDisplayName());if (p.getDisplayName().equals(propertyName)) {<span style="white-space:pre"></span>//得到get方法<span style="white-space:pre"></span>Method set = p.getReadMethod();

<span style="white-space:pre"></span>//<span style="color:#ff0000;">获取字段名(不是成员变量名)</span>System.out.println(p.getDisplayName()+":"+set.invoke(demo, null));System.out.println("get成功");}}}/** *  * @param arg 要给字段设定的值 * @param demo * @param propertyName * @param pds */protected static void setProperty(Object arg, Demo demo,String propertyName, PropertyDescriptor[] pds)throws IllegalAccessException, InvocationTargetException {for (PropertyDescriptor p : pds) {//System.out.println(p.getPropertyType().getSimpleName());if (p.getDisplayName().equals(propertyName)) {Method set = p.getWriteMethod();set.invoke(demo, arg);System.out.println("set成功");}}}}</span>

}}

Eclipse小技巧:

        选择要变为方法的代码,右键——>Refactor——>Extract Method,然后就会生成一个方法了。

BeanUtils工具包

package Introspector;import org.apache.commons.beanutils.BeanUtils;public class BeanutilTest {public static void main(String[] args) throws Exception {Demo demo = (Demo) Demo.class.newInstance();String propertyName="age";  //用BeanUtils工具包的方法         <span style="white-space:pre"></span>System.out.println(BeanUtils.getProperty(demo, propertyName));//get        <span style="white-space:pre"></span>BeanUtils.setProperty(demo, propertyName, <span style="color:#ff0000;">"9"</span>);//set <span style="color:#ff0000;"> age是int型的却可以传进去string类型 因为beanutils内部进行了转换</span>        <span style="white-space:pre"></span><span style="color:#ff0000;">//"x"-->"X"-->"getX"-->MethodGetX--> </span>        <span style="white-space:pre"></span>          <span style="white-space:pre"></span> System.out.println(demo.getAge());                    <span style="white-space:pre"></span> <span style="color:#ff0000;">//对于JavaBean中的属性是对象的操作  </span>       <span style="white-space:pre"></span> BeanUtils.setProperty(demo, "demo", <span style="color:#ff0000;">new Demo()</span>);//set         <span style="white-space:pre"></span> System.out.println(BeanUtils.getProperty(demo, "demo"));//get  }}
需要导入的包:

commons-beanutils.jar

commons-logging.jar

注意:

1.BeanUtils会对JavaBean的属性的类型进行转换,如属性本身是integer,会转换为String

2.而在BeanUtils工具包中还有一个PropertyUtils也能对javaBean进行操作 不过PropertyUtils以属性本身的类型进行操作。


8.注解

什么是注解?

注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

看java.lang包,可看到JDK中提供的最基本的annotation。 

@SuppressWarnings //消除警告

@Deprecated //不建议使用 、过时的

@Override  //方法的重写  注意public boolean equals(Objet other) 如果参数不是Object类型的,这是方法的重载!别大意。


自定义注解及其应用

定义一个最简单的注解:public @interface MyAnnotation {}
把它加在某个类上:

<span style="font-size:14px;">@MyAnnotationpublic class Test {public static void main(String[] args) {//isAnnotationPresent()方法判断是否有对象是否有注解System.out.println(Test.class.isAnnotationPresent(MyAnnotation.class));}}</span>
<span style="font-size:14px;"><strong>结果:false</strong></span>
明明已经加了注解为什么回返回false呢?:

因为注解有它所作用的范围

引出元注解的概念:注解类上的注解

Retention元注解

Retetion:用于说明注解保留在哪个阶段(即注解的生命周期)。

        一个注解的生命周期包含java源程序--(javac)-->class文件--(类加载器)-->内存中的字节码

Retetion的三种取值

 RetetionPolicy.SOURSEjava源文件时期)(编译),如@Overried@SuppressWarning

                RetetionPolicy.CLASS class文件时期(默认阶段)

                RetetionPolicy.RUNTIME:运行时期,如@Deprecated

不加Retetion 则默认 RetetionPolicy.CLASS

@Target元注解
Target的默认值为任何元素,设置Target等于ElementType.METHOD,原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。

Target的取值为:

                 PACKAGE(包声明)

                 FIELD(字段声明)

                 ANNOTATION_TYPE(注释类型声明)

                 CONSIRUCTOR(构造器声明)

                 METHOD(方法声明)

                 PARAMETER(参数声明)

                 TYPE(类、接口(包含注释类型)或枚举声明)

                 LOCAL_VARIABLE(局部变量声明)


元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,或者直接看java.lang.annotation包下面的类。


@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {}

在自定义注解中加上
<span style="color:#ff0000;">@Retention(RetentionPolicy.RUNTIME)</span>

声明了注解的生效范围 结果输出:

true


为注解增加基本属性


什么是注解的属性?
一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为:@MyAnnotation(color="red")
定义基本类型的属性和应用属性:
在注解类中增加String color();
@MyAnnotation(color="red")
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color());
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象
还为属性指定缺省值:

String color() default "yellow";
value属性:
String value() default "zxx"; 
如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。


为注解增加高级属性


组类型的属性:
定义:int [] arrayAttr() default {1,2,3};
应用:@MyAnnotation(arrayAttr={2,3,4})
如果数组属性中只有一个元素,这时候属性值部分可以省略大括号
枚举类型的属性:

定义:EnumTest.TrafficLamp lamp() ;
应用:@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
注解类型的属性:
定义:MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
应用:@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
MetaAnnotation ma =  myAnnotation.annotationAttr();
System.out.println(ma.value());

Class类型的属性:

        定义:Class cls();

        应用:@MyAnnotation(cls=AnnotationDemo.class)

注:这里的.class必须是已定义的类,或是已有的字节码对象


注解的详细语法可以通过看java语言规范了解,即看java的language specification。



9.泛型

在jdk1.5之前 对于一个集合可以存放任意类型的对象

这对我们做取出操作很不方便(要强制转换类型),而且还容易出错

如:

ArrayList collection = new ArrayList();
collection.add(1);
collection.add(1L);
collection.add("abc");
int i = (Integer) collection.get(1);//强转 且运行出错



Jdk 1.5的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据
ArrayList<Integer> collection2 = new ArrayList<Integer>();
collection2.add(1);
/*collection2.add(1L);
collection2.add(“abc”);*///这两行代码编译时就报告了语法错误
int i2 = collection2.get(0);//不需要再进行类型转换
泛型是提供给javac编译器使用的(只是作用于编译阶段),可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可(暴力添加)。

          <span style="font-size:14px;">      ArrayList<Integer> collection2 = new ArrayList<Integer>();Method add=ArrayList.class.getMethod("add", Object.class);add.invoke(collection2, "String");System.out.println(collection2.get(0));</span>
<span style="font-size:14px;"><strong>结果:String</strong></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;

不会报错

注意:

Vector v1 = new Vector<String>(); 

v1.add((Integer)1);//正确
Vector<Object> v = v1;

正确

Vector<String> v1 = new Vector<String>(); 

v1.add((Integer)1);//错误
Vector<Object> v = v1;//相当于Vector<Object> v =new Vector<String>(); 错误

错误

上述两例 可以看出jvm只检查集合的声明泛型 不检查实际泛型


问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?

错误方式:
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>();
}
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。


泛型中的?通配符的扩展
1.限定通配符的上边界
正确:Vector<? extends Number> x=new Vector<Integer>();
错误:Vector<? extends Number> x=new Vector<String>();
2.限定通配符的下边界:
正确:Vector<? super Integer> x=new Vector<Numner>();
错误:Vector<? super Integer> x=new Vector<Byte>();
提示:
限定通配符总是包括自己。
?只能用作引用,不能用它去给其他变量赋值
Vector<? extends Number> y = new Vector<Integer>();
Vector<Number> x = y;
上面的代码错误,原理与Vector<Object > x11 = new Vector<String>();相似,
只能通过强制类型转换方式来赋值。


泛型方法

交换数组的两个元素的位置的泛型方法语法定义如下:

  static<E> void swap(E []a,int i,int j){

      E t=a[i];

      a[i]=a[j];

      a[j]=t;

}

只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
普通方法、构造方法和静态方法中都可以使用泛型。
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}


应用:

public static <T> T autoConvertType(Object obj)
{
return (T)obj;
}
测试代码:
Object xxx = "abc";
String yyy = autoConvertType(xxx);
//根据String来得到确定T



编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
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);



泛型类

如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getById(int id){}
}
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();

注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。



<span style="color:#ff0000;">示例代码:</span>Class GenericalReflection {  private Vector<Date> dates = new Vector<Date>();  public void setDates(Vector<Date> dates) {    this.dates = dates;  }  public static void main(String[] args) {
<span style="white-space:pre"></span>//获得方法<span style="white-space:pre"></span>   Method methodApply = GenericalReflection.class.getDeclaredMethod("applyGeneric", Vector.class);   ParameterizedType pType = (ParameterizedType)                    (methodApply .getGenericParameterTypes())[0];            System.out.println("setDates("                    + ((Class) pType.getRawType()).getName() + "<"                    + ((Class) (pType.getActualTypeArguments()[0])).getName()                    + ">)" );  }}泛型DAO的应用:<pre name="code" class="java">public class DaoImpl<E> implements DaoInter<E>{Class clazz; public DaoImpl() { ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
<span style="white-space:pre"></span>//获取第一个类型参数的真实类型this.clazz = (Class) pt.getActualTypeArguments()[0];System.out.println("clazz = " + this.clazz);}public static void main(String[] args) {new aaa();}}class aaa extends DaoImpl<String>{ }

























0 0
原创粉丝点击