黑马程序员_高新技术二(java内省注解泛型)

时间:2022-03-06 11:31:05

----------- android培训java培训、java博客、java学习型技术博客、期待与您交流! --------------

 

 本章主要的知识点总结:1、特殊类JavaBean及对其简单、复杂的内省(IntroSpector)操作、

                                               BeanUtils工具包的使用

                                         2、注解Annotation的定义和使用范围,几种基本注解,注解的生命周期,

                                             @Target元注 解的使用,注解的各种属性

                                        3、泛型Generic的作用泛型  ? 通配符的使用及限定通配符的上、下边界。

                                        4、自定义泛型方法、自定义泛型类的应用以及通过反射获得泛型的实际类型参数。

                               
                             
            


一.JavaBean


1、JavaBean:
(1)JavaBean是一种特殊的java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的
  字段,且方法名符合某种命名规则。
(2)如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象
     通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,用set()和get()

  方法来设置和获取。

(3)JavaBean的get和set方法应该声明为public的,而成员变量应该声明为private的。
        JavaBean的属性是根据get和set方法来推断出来的。而不是根据你JavaBean内部成员变量的名称。它根本看不到java类内部的成员变量。如果第二个字母是小写的,则把第一个字母变成小写的
如:

class Person {

 private int x;

 public int getAge() {

  return x;

 }

 public void setAge(int age) {

  this.x = age;

 }

}

(4)符合JavaBean特点的类可以当做一个JavaBean来使用,比普通类操作更方便,快捷。


2、内省

        内省(IntroSpector):主要对JavaBean进行操作。JDK提供了对JavaBean进行操作的一些API,这套API称为内省。

               对JavaBean的简单内省操作:java.beans.PropertyDescriptor属性描述符类。

       new PropertyDescriptor(propertyName, class);

       PropertyDescriptor类主要有方法getReadMethod()和getWriteMethod()。

对JavaBean的复杂内省操作:java.beans.Introspector.getBeanInfo(class);这种方法复杂一些。


(1)内省的简单使用见如下例子

import java.beans.BeanInfo;

import java.beans.IntrospectionException;

import java.beans.Introspector;

import java.beans.PropertyDescriptor;

import java.lang.reflect.Method;

public class IntroSpectorTest {

 public static void main(String[] args) throws Exception  {

  PointInIntro point = new PointInIntro(3, 9);

  String propertyName = "x";

  PropertyDescriptor pd = new PropertyDescriptor(propertyName, point.getClass());

  Method methodGetX = pd.getReadMethod();

  Object retVal = methodGetX.invoke(point);

  System.out.println(retVal);

  Method methodSetX = pd.getWriteMethod();

  methodSetX.invoke(point, 88);

  System.out.println(point.getX());

  
  
(2)对JavaBean的复杂内省操作
代码示例:  

  BeanInfo beanInfo = Introspector.getBeanInfo(point.getClass());
  PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
  for(PropertyDescriptor pd1 : pds) {
   if(pd1.getName().equals(propertyName)) {
    Method methodGetX1 = pd1.getReadMethod();
    Object retVal1 = methodGetX1.invoke(point);
    System.out.println(retVal1);
    break;
   }
  }
 }

  PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

  for(PropertyDescriptor pd1 : pds) {

   if(pd1.getName().equals(propertyName)) {

    Method methodGetX1 = pd1.getReadMethod();

    Object retVal1 = methodGetX1.invoke(point);

    System.out.println(retVal1);

    break;

   }

  }

 }

}

class PointInIntro {

 private int x;

 private int y;

 public PointInIntro(int x, int y) {

  this.x = x;

  this.y = y;

 }

 public int getX() {

  return x;

 }

 public void setX(int x) {

  this.x = x;

 }

 public int getY() {

  return y;

 }

 public void setY(int y) {

  this.y = y;

 } 

}

(3)BeanUtils工具包
        使用BeanUtils工具包操作JavaBean,需下载和导入beanutils和logging包。使用BeanUtils操作JavaBean方便快捷。BeanUtils类中的方法全是静态方法。
         注意,用setProperty()设置属性值的时候,参数应该用字符串类型,而getProperty()返回的值也是一个字符串类型的对象。
          BeanUtils的setProperty()和getProperty()方法支持属性的级联
         如:BeanUtils.setProperty(pt1, "birthday.time", "111");   BeanUtils.getProperty(pt1, "birthday.time");
        因为示例类中的birthday属性是属于Date类型,而Date类有一个方法为setTime(),所以级联属性为birthday.time可以设置其中的值。

     BeanUtils类还可以Map(key-value)集合进行操作,跟操作JavaBean一样的操作。

     java7中的新特性:Map类型可以这样定义:Map map = {name: "zxx", age: 18};
       BeanUtils.setProperty(map,"name","zhangsan");

   BeanUtils类还提供了对JavaBean和Map集合的相互转换

PropertyUtils和BeanUtils的区别;

  BeanUtils接收返回的都是String类型,可自动完成属性的类型转换。

  PropertyUtils是以属性本身的类型进行操作。

 

二.注解Annotation(重要)


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

2.java提供的几个基本注解
  (1)@SuppressWarnings压制警告注解
   使提示不再出现。如:@SuppressWarnings("deprecation")//不再提示过时
  (2)@Deprecated使过时注解
   标识某个方法或类等已过时,以告诉新人以后尽量不要再使用该方法,同时又 不会影响以前的老程序。
  (3)@Override覆盖注解
   在子类覆盖父类方法时,将非法的覆盖标识出来(或使不正确的覆盖报错),以便修正。

3.注解的应用结构图
  注解类
  @interface A
  { }

  应用了"注解类"的类
  @A
  class B
  { }


  对"应用了注解类的类"进行反射操作的类
  class C
  {
   B.class.isAnnotationPresent(A.class);
   A a =(A) B.class.getAnnotation(A.class);

  }


4.注解的生命周期
   java源文件--->class文件--->内存中的字节码

   @Retention元注解可定义注解的存在阶段。其取值有三个:
         RetentionPolicy.SOURCE(Java源文件),RetentionPolicy.CLASS(默认class文件),Retentionpolicy.RUNTIME(保留到运行期间)
   
   SuppressWarnings --- SOURCE
   Override --- SOURCE

   Deprecated --- RUNTIME  (RUNTIME的类型为:Enum RetentionPolicy的字段)


@Target元注解描述注解的所适用的程序元素的种类,如:TYPE,METHOD,FIELD等。
        JDK1.5以后出现的类型TYPE定义包括:class,interface,@interface,Enum等。Type比class更加精准 ,Class的父类是interface Type(java.lang.reflect包)。

  枚举ElementType(java.lang.annotation )

           程序元素类型。此枚举类型的常量提供了 Java 程序中声明的元素的简单分类。 
  这些常量与Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的。
   @Target(ElementType.METHOD):应用在方法上

   @Target({ElementType.METHOD,ElementType.TYPE}):应用在方法和类上


5、注解的各种属性:
(1)定义基本属性和应用属性

  String color();---->@MyAnnotation(color="red");读取的时候还是用方法的形式调用(.color())。

      如果只有一个value属性需要指定,那么赋值的时候可以省略“属性名=”,直接("属性值")即可。

如果有其他一个属性,指定其默认值, String color() default "blue";,也可以直接("属性值")。

 ( 2)数组类型的属性
  int[]  arrayAttr() default {1,5,2};--->@MyAnnotation(arrayAttr={5,8,9}); 
  若数组属性中只有一个元素,属性值部分可以省略大括号,如arrayAttr=1;
  (3)枚举类型的属性
  EnumTest.TrafficLamp lamp();--->@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN);
  (4)注解类型的属性:
  MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");--->
  @MyAnnotation(annotationAttr=@MetaAnnotation("yyy"));

(5)枚举和注解都是特殊的类,不能用new创建它们的实例对象,创建枚举的实例对象就是在其中增加元素。

  在程序中创建注解的实例对象,就是直接用@放上一个标记即可。

         注意:为注解增加各种属性可以是如下类型:基本数据类型,String类型,Class类型,枚举类型,注解类型,或者是前面所列举的类型的数组。如果返回的是其他类型的数据,编译器就会报错。



三.泛型Generic


1.泛型概述

        没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。使用泛型集合,可以将 一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全,并且当从集合获到 一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
   注:在JDK1.5中,若按原来的方式将各种不同类型的数据装到一个集合中,编译器会报告unchecked警告。 
          泛型中,只有“引用类型”才能做为泛型方法的实际参数。

          泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,
  编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,    getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据。
 


2.泛型内部原理及更深应用

(1)ArrayList类定义和ArrayList类引用中涉及如下术语:
   整个称为ArrayList<E>泛型类型
   ArrayList<E>中的E称为类型变量或类型参数
    整个ArrayList<Integer>称为参数化的类型
   ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
   ArrayList<Integer>中的<>念着typeof
   ArrayList称为原始类型

  (2)参数化类型与原始类型的兼容性:
  参数化类型可以引用一个原始类型的对象,编译报告警告,如:
  Collection<String> c = new Vector();//可不可以,就是编译器一句话的事。
  原始类型可以引用一个参数化类型的对象,编译报告警告,如:
  Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去。
  (3)参数化类型不考虑类型参数的继承关系:
  Vector<String> v = new Vector<Object>();//错误//不写<Object>没错,写了就是明知故犯
  Vector<Object> v = new Vector<String>();//也错误观点
  (4)思考题:下面的代码会报错吗?  不会报错。
  Vector v1 = new Vector<String>();
  Vector<Object> v = v1;


3.泛型  ? 通配符

    (1)使用? 通配符可以使用其它各种参数化的类型,通配符定义·的变量主要用作引用,可以调用参数化无关的方法,不能调用参数化有关的方法。

  (2)限定通配符的上边界:------Number及其子类
  Vector<? extends Number> x = new Vector<Integer>();//正确//Number指八种基本类型类
  Vector<? extends Number> x = new Vector<String>();//错误//String不属于基本类型
  (3)限定通配符的下边界:-------Integer及其父类
  Vector<? super Integer> x = new Vector<Number>();//正确
  Vector<? super Integer> x = new Vector<Byte>();//错误


   代码示例:

  1. HashMap<String,Integer> maps = new HashMap<String,Integer>();  
  2.   maps.put("zxx"28);  
  3.   maps.put("1hm"35);  
  4.   maps.put("flx"30);  
  5.     
  6.   Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();  
  7.   for(Map.Entry<String, Integer> entry : entrySet)  
  8.   {  
  9.    System.out.println(entry.getKey()+":"+entry.getValue());  
  10.   } 


4.自定义泛型方法及其应用

     java的泛型是由C++借鉴而来的
  (1) C++模板
  int add(int x,int y)
  {
   return x+y;
  }
  float add(float x,float y)
  {
   return x+y;
  }
  double add(double x,double y)
  {
   return x+y;
  }

  用C++用模板函数解决,只写一个通用的方法,以适应各种类型:
  template<class T>
  <T>T add(T x,T y)
  {
   return(T)(x+y);
  }


      (2)  java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术叫擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商

  升级其JVM造成难以逾越的障碍,所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
    (3)泛型的位置规范:
       用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
 (  4)普通方法,构造方法和静态方法中都可以使用泛型。编译器不允许创建类型变量的数组。
   (5)只有引用类型才能作为泛型方法的实际参数,对于add方法,使用基本类型的数据进行测试没问题,是因为自动装箱和拆箱了。而swap(new int[3],3,5);报告编译错误,是因为编译器不会对new int[3]中的int自动拆箱和装箱了,new int[3]本身已经是对象了,也许你想要的就是int数组呢,它装箱便会弄巧成拙。
  (6)也可用类型变量表示异常,称为参数化的异常,可用于方法的throws列表中, 但是不能用于catch子句中。
  

5.自定义泛型类的应用
  (1)如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型。  要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
   public class GenericDao<E>
   {
    private E field1;
    public void add(TEobj){}
    public E getById(int id){}
   }
  
   在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
   当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能 被静态变量和静态方法调用,因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。

7.通过反射获得泛型的实际类型参数
(1)Type[] getGenericParameterTypes() 
    按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的.JDK1.5  
    Class<?>[] getParameterTypes() 
          按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。 
(2)>ParameterizedType
  Type[] getActualTypeArguments() 
      返回表示此类型实际类型参数的 Type 对象的数组。 实际类型参数
   Type getRawType() 
      返回 Type 对象,表示声明此类型的类或接口。 原始类型
 
(3) Map<String,Integer> map = new Map<String,Integer>();   如何知道Map中的泛型参数类型?
  
 代码实例:
   copy
    1. // 只能把Map放到一个方法中的参数中,获取方法的参数。  
    2.    public static void applyVector(Map<String,Integer> vector){  }  
    3.     Method applyMethod =   
    4.             GenericTest.class.getMethod("applyVector", Map.class);  
    5.    Type[] types = applyMethod.getGenericParameterTypes();   
    6.    //ParameterizedType是Type的子类。  
    7.    ParameterizedType pType = (ParameterizedType) types[0];   
    8.    System.out.println(pType.getRawType());  //得到Map类型。  
    9.                 methodGetX.invoke(pt1);  
    10.         }  
    11.    }