04.注解与反射

注解与反射

注解(Annotation)与反射是所有框架的底层:相当于Delphi的编译宏
Mybatis,Spring, Mapper 都是以注解

1. 注解简介

注解 Annotataion Type

  1. 从JDK5.0开始引入。

  2. 不是程序本身,有点象注释(comment),但可以被其他程序读取。

  3. 注解是以“@注释名”在代码中存在的,还可以添加一些参数值。
    eg.
    @SuppressWarnings(value="unchecked"). 抑制警告,参数可以用"all",使得所有警告消息消失。
    @Override 重写,
    @Deprecated 已废弃,不推荐使用。 同@Override一样,在定义时使用。调用时系统会出现废弃线。
    以上三个注解属于内部注解。

  4. 其他注解
    @functionInterface 函数式接口
    java定义了4个标准的meta-annotation,对其他的注解类型作为说明。
    @Target 用于描述注解的使用范围。
    @Retention 表示需要再什么级别保存该注释信息,限定所描述注解的生命周期。
    source<class<runtime
    @Documented 说明该注解将被包含在javadoc中。
    @Inherited 说明子类可以继承父类中的该注解。

  5. 自定义注解
    源注解修饰(@Target, @Retention, @Documented, @Inherited 无须分号结尾
    @interface 自定义注解名
    Target 参数是一个枚举sets,包括:
    public enum ElementType{
    TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,
    ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE}

@Target(value={ElementType.METHOD, ElementType.Type}) 注解可以出现在方法和类定义上。
Retention 参数也是一个枚举类型(非Sets),包括:
public enum RetentionPolicy{SOURCE, CLASS, RUNTIME} //源码级,类级,运行级
@Retention (value=RetentionPolicy.RUNTIME) //注解是运行级
Documented,Deprecated 这个不加参数,直接生效。
注解如果只有一个参数,那么默认参数名就是value,调用的时候可以省略value=

2. 反射: java.Reflection

先了解,什么是动态语言:Object-c, c#, javaScript, PHP, Python 。静态语言:java, c, c++,delphi
动态语言的特点,就是在运行过程中,还可以动态改变代码,就类似于动态SQL。
java.lang.Class
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect Constructor

反射的核心,就是Class类,这相当于Delphi中的类中类Class of 父类名 手法,Class的对象就是一个类。它可以从Class众多类方法中获得类。
Class.forName("java.lang.String"); 从java已有的包.类中获取类结构。
对象.getClass(); 从对象中获取一个类结构,这个方法来自Object: public final Class getClass();
c1.hashCode(); 可以唯一确定类结构。其中c1是用Class定义的对象。

一个加载的类在JVM中值会有一个Class实例,也就是说,不管用何种方法得到的对象类结构,存储在Class定义对象中后,其实都是一个地址。
一个Class定义对象对应的是一个加载到JVM中的一个class文件
每一个类的实例,都会记得自己是由哪个Class实例生成的。 即:对象Object出现的时候,getClass的值就固定了。

看看Class类的常用方法:

  • static ClassforName(String name) 返回指定类名name的Class对象,即用类名得到对象。
  • Object newInstance() 用缺省的构造函数,返回Class对象的一个实例。
  • getName() 返回Class对象所表示的实体(类,接口,数组 或void)的名称,其实还是个类名。
  • Class getSuperClass() 返回当前Class对象的父类的Class对象。
  • Class[] getinterfaces() 获得当前Class对象的接口,因为一个类可能有多个接口,所以这里是用的类数组。
  • ClassLoader getClassLoader() 返回该类的类加载器。
  • Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组。 这说明数组也有构造方法?
  • Method getMethod(String name, Class.. T) 返回一个Method对象,此对象的形参类型为paramType
  • Field[] getDeclaredFields() 返回Field对象的一个数组。
    其他类方法
  • getSimpleName() 相比getName,少了包路径,只显示类名(无扩展名)。
  • getFields() 找到当前的public属性。
  • getDeclarefields() 找到全部的属性。
  • getMethods() 返回public方法
  • getDeclaredMethods() 获得此类的所有方法。如果带参数即可获得指定方法。 参数如String.class
  • getConstructors() 获得构造器
  • getDeclaredConstructors() 获得所有构造器。如果带参数,则可以获得指定构造器。参数如null, int.class
    上述方法,返回的都是数组,需要用数组变量来接收,如:Field[], Method[], Constructor[]
    如果要使用得到的方法Method meth 需要用到invoke方法: meth.invoke(某对象名,参数);
    使用Field字段,给其赋值: Field f. f.set(对象名,属性值); 这里不能用赋值=号。
    而f.setAccessible(true)方法,可以关闭掉对应属性或方法的private权限,直接访问。

3. 如何获取Class类的实例:

  • Class claci =Person.class; // 每个类都自带class属性。
  • Class claci =person.getClass(); //每个实例都代有getClass()方法
  • Class claci =Class.forName("全路径类名'); //直接使用类方法forName
    基础类型的TYPE
  • ClassLoader ???
    这里需要注意的是,如果用父类变量获取子类实例,得到的对象用getClass方法,直接返回的是子类Class。即,可以认为getClass返回的是实际占用空间所用的类。跟在对象前加一个(子类名)强转符再getClass()的效果是一样的。

拥有Class对象的类型:

  1. class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。Object.class Class.class.
  2. interface : 接口 . Comparable.class
  3. []:数组 String[].class; int[][].class; 我们可以认为,只要数组的元素类型和维度数是一样的,那么就一定是同一种Class类型。
  4. enum : 枚举 ElementType.class
  5. annotation : 注解 @interface. Override.class
  6. primitive type :基本数据类型。 Interger.class
  7. void. void.class;
    • Class c1= Object.class; class java.lang.Object
    • Class c2= Comparable.class; interface java.lang.Comparable
    • Class c3 =String[].class; class java.lang.String;
    • Class c4 =int[][]class; class [[I
    • Class c5 = Override.class; interface java.lang.Override
    • Class c6 = ElementType.class; class java.lang.annotation.ElementType
    • Class c7 = Integer.class; class java.lang.Integer
    • Class c8 = void.class; void
    • Class c9 = Class.class; class java.lang.Class

      getSuperclass继承树分析:。
       Button<--Component<--Object<--null
       Canvas<--Component<--Object<--null
       Checkbox<--Component<--Object<--null
       CheckboxMenuItem<--MenuItem<--MenuComponent<--Object<--null
       Choice<--Component<--Object<--null
       Dialog<--Window<--Container<--Component<--Object<--null
       FileDialog<-Dialog<--Window<--Container<--Component<--Object<--null
       Frame<--Window<--Container<--Component<--Object<--null
       Label<--Component<--Object<--null
       List<--Component<--Object<--null
       Menu<--MenuItem<--MenuComponent<--Object<--null
       MenuItem<--MenuComponent<--Object<--null
       Panel<--Container<--Component<--Object<--null
       PopupMenu<--Menu<--MenuItem<--MenuComponent<--Object<--null
       Scrollbar<--Component<--Object<--null
       ScrollPane<--Container<--Component<--Object<--null
       TextArea<--TextComponent<--Component<--Object<--null
       TextField<--TextComponent<--Component<--Object<--null
       String<--Object<--null
       Integer<--Number<--Object<--null
       所有的基础数据类型类,都没有父类。而对应的方法类,如:Integer, Double,Float.都来自Number类。
       Boolean,String方法类除外,它们是直接来自Object. 所有数组的类,也是来自Object.
       byte , short, int , long, float, double, char, boolean <--null 
    

4. java程序执行时的内存活动:

  1. 加载:负责把class文件中的字节码内容,放进内存里。并将这些静态数据转换成方法区的运行时数据结构。然后生成一个代表这个类的Class对象。
  2. 链接:将java类的二进制代码合并到JVM的运行状态中。确保这些信息符合JVM规范;为类变量(static)分配内存,并初始化;将常量池常量名替换为常量地址。
  3. 初始化:

​ <1>. 执行()方法,类构造器方法(clinit)是由编译期自动收集类中所有类变量的赋值和静态代码中的语句合并产生(用类构造类信息,与对象无关)
​ <2>. 初始化类的时候,追踪其父类,并对其父类进行初始化。
​ <3>. 虚拟机会保证一个类的构造器方法在多线程环境中被正确的枷锁并同步。

  1. 当代码中定义完一个类,但整个过程中并没有使用这个类,那么就未必会对这个类进行初始化。 在以下过程中会对这个类进行初始化:
  • 当虚拟机启动时,包含main方法的类,会被初始化。
  • 当使用new 去分配一个类的对象的时候,这个对象所在的类会被初始化。
  • 当调用了类的静态成员或静态方法(final常量除外)。
  • 使用java.lang.reflect包的方法对类进行反射调用(即好几种取得Class对象的方法,都会引起类初始化)。
  • 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类。 换句话说,一个类的子类被初始化时,这个类也会被初始化。
  1. 不会初始化类的情况:(父类静态,类数组,类常量)
  • 当访问一个类的静态域的时候,类不会被初始化。例如通过子类引用父类的静态变量,不会导致子类初始化。
  • 通过数组来定义类引用,不会触发此类的初始化(泛型?)。
  • 引用常量不会触发类的初始化。(因为常量在类链接阶段就存如调用类的常量池中了)

5. 类加载器的作用

1,将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的
java.lang Class对象,作为方法区中的类数据访问入口。

2,类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载一段时间(缓存),但会
通过JVM的垃圾回收机制来回收这些Class对象。

  • 引导类加载器: 核心库rt.jar,该加载器无法直接获取。 Bootstap Classloader

  • 扩展类加载器:jre/lib/ext目录下的jar包。 或 -D java.ext.dirs目录下的jar包。 Extension Classloader

  • 系统类加载器:java -classpath 或 -D java.class.path目录下的类与jar包装入,最常用。System Classloader.

  • 可以用下面的ClassLoader类,来访问当前的类加载器状态。常用类方法: getSystemClassLoader(); getParent();

  • 用当前自定义类.getClassLoader()可以得到该类的加载器。

     ClassLoader systemCL = ClassLoader.getSystemClassLoader();
     ClassLoader parentCL = systemCL.getParent();
     ClassLoader parentCL1 = parentCL.getParent();
    
     System.out.println(systemCL);
     System.out.println(parentCL);
     System.out.println(parentCL1);  //如果返回null其实就是指向了根加载器。
           
     Class.forName("java.lang.Object").getClassLoader(); //返回null可以看出JDK的内置类是由核心根加载器加载的。
    

    可以用System.out.println(System.getProperty("java.class.path")); 查看当JDK的核心库路径。例如MACOS的:

/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/deploy.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/dnsns.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/jaccess.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/localedata.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/nashorn.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/sunec.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/ext/zipfs.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/javaws.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/management-agent.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/plugin.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/jre/lib/rt.jar:
/Volumes/Bak/Java_mp/GUIStudy/GUI_Study/out/production/GUI_Study:
/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar

java编译的时候,会优先去找内部加载器的包名,如果有包名冲突的用户包,则会自动跳过,只执行JDK的内部包。
System.currentTimeMillis()得到当前的系统时间。

ORM概念Object relationship Mapping 对象关系映射。
使用注解和反射,完成类和表结构的映射关系。

6. 泛型:Generics

泛型,相当于一个类型数组,省去了强行装换的麻烦。

  1. 新增的泛型类型: 这几种类型,不能归一到Class类中的类型,所以这几个类的.class.getSuperclass()返回null.
    ParameterizedType : 表示一种参数化类型,如Collection
    GenericArrayType : 表示一种元素类型是参数化类型 或者 类型变量的数组类型。
    TypeVariable : 各种类型变量的公共父接口。
    WildcardType : 代表一种通配符类型表达式。

    注意: 泛型的类型,不能用基础类型,必须使用引用类型,因为Object跟基础类型不兼容。例如int.class.getSuperclass()值是null
    java.util.Map中的Map类型是一个标准的泛型,参数K, V代表着Key+Value
    用方法可以返回其返回值类型: Type rType = Method mthd.getGenericReturnType();
    接着可以用instanceof 来判断: rType instanceof ParameterizedType //是否是泛型参数类型。

  2. 泛型的意义: 泛型其实就是一个指定元素类型的List,其元素类型必须是引用类型,不支持基础类型。
    而泛型本身也是一种类,并且不属于Class类。
    使用泛型的好处,就是在这个List的上固定其元素的类型,不需要作后期的数据强行转换。 但是,实际的意义也是在java的编译期进行的,一到JVM时期,所有的泛型依旧是强行类型转换,不过这时候已经是安全的了。

  3. java泛型的定义:
    先看一种最常用的泛型:ArrayList<类名> 泛型变量 = new ArrayList<类名>();
    这个其实是由一个ArrayList类跟泛型的结合。书写的时候,后面的类名可以省略掉,因为前面已经明示过类名了。

  • ArrayList的类关系: ArrayList<--AbstractList<--AbstractCollection<--Object.
  • ArrayList本身是带有很多对象方法,基本能满足常规的使用。
  • add 增加元素。
  • get(int index) 按序号查找元素。
  • addAll(Collection<? extends 类名> c) 增加一个子类的集合。当然,ArrayList本身也是一种Collection
  • clear() 清空元素.
  • clone()
  • contains(Object o) boolean; 元素中是否含有某对象。
  • ensureCapacity(int minCapacity) 预分配容量,避免多次扩容。
  • forEach
  • indexOf(Object o) int 查找位置
  • isEmpty() boolean; 是否为空。
  • lastIndexOf(Object o) int; 查找某对象在元素列表最后出现位置。
  • remove(int index) Object 移除某元素并返回。
  • removeAll(Collection<?> c) boolean;
  • removeIf(Predicate<? super类> filter) boolean;
  • replaceAll()
  • retainAll(Collection<?> c) boolean;
  • set(int index, 类名 元素) Object;
  • size() int sort(); toArray() Object[]; trimToSize();
  • iterator() listIterator() listIterator(int index) spliterator() subList(int fromIndex, int toIndex)

注意:泛型的类名,可以是多个,也就是多个类型元素一起使用,但是在使用过程中,必须成套出现,不允许参差不齐。
ArrayList也可以使用其他多种类名来取代(例如Pair), 这个位置的类名在泛型中也称为原型。

  1. 类型擦除:Type Erasure . 由于泛型在虚拟机中都会变为普通类型,并进行强行转换,所以很多类型比较对泛型来说是没意义的。
    eg.
       List<Object> list1 = new ArrayList<Object>();
             List<String> list2 = new ArrayList<String>();
             System.out.println(list1.getClass());
             System.out.println(list2.getClass());
             //以上的输出都是: class java.util.ArrayList
    
    我们没办法比较泛型变量是否符合哪个类,例如下面这个会编译报错
           List<Integer> p = new ArrayList<>();
           if ( p instanceof List<String>) {}//报错
    
    对于参数为泛型的方法,用method.getGenericReturnType()会得到完成泛型类型。如:java.util.List<包.类名>然后可以用instanceof 与ParameterizedType进行比较, 此类完整路径: interface java.lang.reflect.ParameterizedType

注意:泛型是不支持向上转型的,就是用子类实例 去初始化父类变量。原因也是擦除!以下的写法是错的:List

posted @ 2026-06-26 11:45  莫霏  阅读(11)  评论(0)    收藏  举报