java 反射,注解,泛型,内省(高级知识点)

时间:2025-01-18 22:34:14

 Java反射

1.Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs    取得任何一个已知名称的class的内部信息,

包括其modifiers(诸如public, static 等)、    superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,    并可于运行时改变fields内容或唤起methods。

2.获取类的Class对象的几种方式:

(1)类名.class。例:Boolean.class。   (2)对象.getClass()。例:new Date().getClass();   (3)Class.forName(“类名”);例:Class.forName("java.util.Date");

3.Constructor(获取类的构造方法)

(1)得到某个类的所有构造方法。例:

  1. Constructor  []constructors =  Class.forName("java.lang.String").getConstructors();

(2)得到某个类的某一个构造方法。例:

  1. Constructor constructor =  Class.forName("java.lang.String")
  2. .getConstructo(StringBuffer.class);

(3)利用默认的构造方法创建Class对象所表示的类的新实例。例:

  1. String str = (String)Class.forName("java.lang.String").newInstance();

4.Field(获取类的成员变量)

(1)getField(""), 获取指定名称的公共成员变量。

(2)getFields(),获取所有的公共成员变量。

(3)getDeclaredField(“”),获取指定名称的声明变量,包含私有的。

(4)getDeclaredFields(),获取所有的声明变量,包含私有的。

示例代码如下:

  1. Order order = new Order(Long.parseLong("1001"),
  2. "订单1",
  3. Double.parseDouble("150.80"));
  4. order.setName("1001");
  5. // 获取成员变量Id,getField("")获取的成员变量必须是公共成员变量,否则抛异常。
  6. Field field = order.getClass().getField("id");
  7. // 获取成员变量name,getDeclaredField("")获取声明的所有成员变量,包含私有的。
  8. Field field2 = order.getClass().getDeclaredField("name");
  9. // 私有成员变量需采用暴力反射才能访问应用。
  10. field2.setAccessible(true);
  11. System.out.println(field2.get(order));

注意:获取到的成员变量是类上面的,而不是对象上的,可通过成员变量.get(实例对象)获取到指定实例对象的成员变量值。

5.Method(获取类中的成员方法)

(1)getMethod(String name, Class<?>... parameterTypes),获取指定方法名称和参数类型的公共成员方法。

(2)getMethods(),获取所有的公共成员方法。

(3)getDeclaredMethod(String name,Class<?>... parameterTypes),

获取指定方法名称和参数类型的声明成员方法,包含私有成员方法在内。

(4)getDeclaredMethods(),获取所有声明的成员方法,包含私有成员方法在内,但不包括继承的方法。

示例代码如下:

  1. Method  substring = Class.forName("java.lang.String")
  2. .getMethod("substring", int.class);
  3. String strAt = (String) substring.invoke(str, 3);
  4. // 获取私有的成员方法
  5. Method getHello = order.getClass().getDeclaredMethod("getHello");
  6. // 私有成员方法需暴力反射才能访问调用
  7. getHello.setAccessible(true);
  8. // 实例对象为null,表示该方法是static方法。
  9. System.out.println(getHello.invoke(null));

 6.其它说明

(1)基本类型的一维数组可以被当做Object类型使用,不能当作Object[]类型使用,非基本类型的一维数组既可以被当做Object类型使用,又可以当作Object[]类型使用。

(2)框架与工具类的区别在于,工具类被用户的类调用,而框架则是调用用户提供的类,由于在设计框架时无法知道用户提供的类,无法实例化具体的对象,需采用反射的方式来实现。

二、内省

1、内省是 Java 语言对 Bean 类属性、事件的一种缺省处理方法。也就是说给定一个javabean对象,我们就可以得到/调用它的所有的get/set方法。

例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性,这就是默认的规则。

Java 中提供了一套 API 用来访问某个属性的 getter/setter方法,通过这些API可以使你不需要了解这个规则,这些API存放于包java.beans中。

例:通过类 Introspector 来获取某个对象的 BeanInfo信息,然后通过 BeanInfo来  获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性  对应的 getter/setter 方法,然后我们就可以通过反射机制来调用这些方法。

  1. Order order = new Order();
  2. order.setName( "ZX001" );
  3. //如果不想把父类的属性也列出来的话,那getBeanInfo的第二个参数填写父类的信息
  4. BeanInfo bi = Introspector.getBeanInfo(order.getClass(), Object.class);
  5. PropertyDescriptor[] props = bi.getPropertyDescriptors();
  6. for(int i=0;i<props.length;i++){
  7. System.out.println(props[i].getName()+ "=" +
  8. props[i].getReadMethod().invoke(order, null ));
  9. }

2、Apache的commons包封装了很多对Java对象的基本操作,例如BeanUtils,PropertyUtils等, 可使用其对Java对象进行操作。

三、 注解

1、注解相当于一种标记,注解可以加在包,类,字段,方法,方法的参数以及局部变量上。 注解使得Java源代码中不但可以包含功能性的实现代码,还可以添加元数据。

注解的功能类似于代码中的注释, 所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。

2、java1.5起默认的三个annotation类型:   一个是@Override:只能用在方法之上的,用来告诉别人这一个方法是改写父类的。

一个是@Deprecated:建议别人不要使用旧的API的时候用的,编译的时候会用产生警告信息,   可以设定在程序里的所有的元素上.   一个是@SuppressWarnings:这一个类型可以来暂时把一些警告信息消息关闭.

3、自定义注解类(@interface)

  1. * RetentionPolicy.RUNTIME 内存中的字节码
  2. * RetentionPolicy.SOURCE Java源文件
  3. * RetentionPolicy.CLASS  Class文件
  4. */
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Target({ElementType.METHOD,ElementType.TYPE})
  7. public @interface MyAnnotation {
  8. // 注解属性(注解属性也可以是数组,枚举,注解等)
  9. String color() default "blue";
  10. String value();
  11. }

@Retention用于描述注解的生命周期: SOURCE代表的是这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,

Annotation的数据就会消失,并不会保留在编译好的.class文件里面。  ClASS的意思是这个Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,

系统默认值是CLASS.  RUNTIME,表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的.

@Target用于描述注解的使用范围:  TYPE(类型), FIELD(属性), METHOD(方法), PARAMETER(参数), CONSTRUCTOR(构造函数), LOCAL_VARIABLE(局部变量), ANNOTATION_TYPE,PACKAGE(包),

其中的TYPE(类型)是指可以用在Class,Interface,Enum和Annotation类型上.

4、注解类的反射调用,例:

  1. @MyAnnotation(value = "test")
  2. public class MyAnnotationTest {
  3. public static void main(String []args){
  4. // 判断是否加上MyAnnotation类型的注解
  5. if(MyAnnotationTest.class.isAnnotationPresent(MyAnnotation.class)){
  6. MyAnnotation annation = MyAnnotationTest.class.getAnnotation(MyAnnotation.class);
  7. System.out.println(annation.color()+"#####"+annation.value());
  8. }
  9. }
  10. }

四、 泛型

1.泛型(Generic type或者generics)是对Java语言的类型系统的一种扩展,以支持创建可以按类型 进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数 是运行时传递的值的占位符一样。 好处:泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制, 编译器可以在一个高得多的程度上验证类型假设。将类型检查从运行时挪到编译时有助于您更容易找到错误, 并可提高程序的可靠性。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

2.Java编译器编译生成的字节码会去掉泛型的类型信息,只要能通过编译器,就能往某个泛型集合中   加入其它类型的数据。事例代码如下:

  1. public static void main(String[] args)  throws Exception{
  2. ArrayList<Integer> collection1=new ArrayList<Integer>();
  3. collection1.add(1);
  4. collection1.add(2);
  5. collection1.add(3);
  6. collection1.getClass().getMethod("add",
  7. Object.class).invoke(collection1, "abcdefg");
  8. System.out.println(collection1.get(3));
  9. }

3.泛型相关术语: ArrayList<E>,泛型类型。 ArrayList<E>中的E称为类型变量或类型参数。 ArrayList<Integer>称为参数化的类型。 ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数。 ArrayList<Integer>中的<>称为typeof。 ArrayList称为原始类型。 参数化类型与原始类型可相互引用,但会出现警告。例:

  1. Map<String,String> map = new HashMap();
  2. Map map = new HashMap<String,String>();

参数化类型不考虑类型参数的继承关系,例:

  1. Map<String,String> map= new HashMap<String,Object>();//编译器错误

数组元素不能使用参数化的类型,可通过Array.newInstance(Classtype,int size)的方式来创建数组。

4.泛型通配符? 使用?通配符可以引用其它各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法, 不能调用与参数化有关的方法。例:

  1. public static void printCollection(Collection<?> cols){
  2. for(Object col:cols){
  3. System.out.println(col);
  4. }
  5. //cols.add("hello");// 编译错误,?通配符不能调用与参数化有关的方法
  6. cols.size();// ?通配符可以调用与参数化无关的方法
  7. cols = new HashSet<Date>();// 引用通配符可以引用各种其它参数化的类型
  8. }

 (1) 限定通配符的上边界

  1. // 正确
  2. List<? extends Number> list= new ArrayList<Integer>();
  3. // 错误
  4. List<? extends Number> list2= new ArrayList<String>();

 (2)限定通配符的下边界

  1. // 正确
  2. List<? super Integer> list= new ArrayList<Number>();
  3. // 错误
  4. List<? super Integer> list2= new ArrayList<Byte>();

5、泛型方法 例:

  1. static <E> void swap(E[] a,int i,int j){
  2. E t = a[i];
  3. a[i]= a[j];
  4. a[j] = t;
  5. }

(1)用于放置泛型的类型参数的尖括号应出现在方法的其它所有修饰符之后和方法的返回类型之前,        也就是紧邻返回值之前。类型参数通常用单个大写字母表示。 (2)只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5)会报编译错误。 (3)泛型不仅在应用时可以使用extends限定符,在定义泛型时也可以使用extends限定符,         并且可以用&来指定多个边界,如<E extends Serializable & Cloneable> void method(){} (4)泛型也可以用类型变量来表示异常,称为参数化的异常,也可以用于方法的throws列表中,但不能用于catch子句中。 (5)在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分开,例:  public static <K,V> V getValues(K key){}

6.类型参数的类型推断

编译器判断泛型方法的实际参数的过程称为类型推断。根据调用泛型方法时实际传递的参数类型或返回类型来推断,具体规则如下: (1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用      类型来确定。例:swap(new String[3],3,4)->static<E> void swap(E[] a,int i,int j) (2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时多处的实际应用类型都     对应同一种类型,则根据实际应用类型来确定。例:add(3,5)->static<T> T add(T a,T b)  (3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时多处的实际应用类型     对应到了不同的类型,且没有返回值,这时取多个参数中的最大交集类型。例:下面语句对应的类型就是Number了。     Fill(new Integer[3],3.5f)->static<T> void fill(T[] a,T v)  (4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时多处的实际应用类型     对应到了不同的类型,并且使用返回值,这时优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,     编译器报告错误,将变量类型改为float,对比编译器的错误提示,再将变量类型改为Number。 (5)参数类型的类型推断具有传递性(a=c,b=c推出a=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)

7、泛型类型

(1)如果类的实例对象中的多处都要用到同一个泛型参数,这时采用泛型类型的方式进行定义,也就是类级别的泛型。    以下示例为将方法级别的泛型改进为类级别的泛型:例:

  1. public class GenericDao {
  2. public <T> void  save(T po){}
  3. public <T> T getById(String id){return null;}
  4. }

改进为:

  1. public class GenericDao<T> {
  2. public T field1;
  3. public void  save(T po){}
  4. public T getById(String id){return null;}
  5. }

(2)类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,以下两种方式都可以:

  1. GenericDao<String> dao = null;
  2. new GenericDao<String>();

(3)在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。当一个变量被声明为泛型时,    只能被实例变量和方法调用,不能被静态变量和静态方法调用。

8、利用反射获得泛型的参数化类型

  1. public class GenericReflection {
  2. private Vector<Date> dates = new Vector<Date>();
  3. public void setDates(Vector<Date> dates) {
  4. this.dates = dates;
  5. }
  6. public static void main(String args[]){
  7. Method []methods = GenericReflection.class.getMethods();
  8. for(Method method:methods){
  9. if(method.getName().equals("setDates")){
  10. ParameterizedType paramType = (ParameterizedType)
  11. method.getGenericParameterTypes()[0];
  12. // 获得参数类型
  13. System.out.println(((Class)paramType.getRawType()).getName());
  14. // 获得泛型的参数化类型
  15. System.out.println(((Class)paramType.getActualTypeArguments()[0]).getName());
  16. }
  17. }
  18. }
  19. }