Java反射、Java注解、Java泛型

版权声明:站在巨人的肩膀上学习。 https://blog.csdn.net/zgcr654321/article/details/83216412

Java反射:

class类:

Java除基本类型外其他都是class(包括interface)。

class的本质是数据类型(Type)。

没有继承关系的数据类型无法赋值。

class/interface的数据类型都是Class。

每加载一个class,JVM就为其创建一个Class类型的实例,并关联起来。JVM持有的每个Class实例都指向一个数据类型(class或interface)。

如:

一个class实例包含了该class的完整信息。

如:

JVM为每个加载的class创建对应的Class实例,并在实例中保存该class的所有信息。如果获取了某个Class实例,则可以获取到该实例对应的class的所有信息。

通过Class实例获取class信息的方法称为反射(Reflection)。

反射的目的是当获得某个Object实例时,我们可以获取该Object的class的所有信息。

如何获取一个class的Class的实例?

Type.class;

getClass();

Class.forName();

如:

Class实例在JVM中是唯一的。我们可以用==比较两个Class实例。

如:

Class实例比较和instanceof的区别:

instanceof比较不但匹配当前类型,还匹配当前类型的子类。

Class实例比较只匹配当前类型。

如:

通常我们用instanceof来判断。

从Class实例可以获取下列class信息:

getName();//获得完整类名

getSimpleName();//获得类名

getPackage();//获得包名

如:

从Class实例还可以判断class类型:

isInterface();

isEnum();

isArray();

isPrimitive();//判断是不是一个基本类型

利用JVM动态加载class的特性,我们可以在运行期间根据条件加载不同的实现类。

如:

Class.forName()判断一个类是否存在。

总结:

JVM为每个加载的class创建对应的Class实例来保存class的所有信息;

获取一个class对应的Class实例后,就可以获取该class的所有信息;

通过Class实例获取class信息的方法称为反射(Reflection);

JVM总是动态加载class,可以在运行期间根据条件控制加载class。

访问字段:

我们通过Class实例可以获取field信息:

getField(name):获取某个public的field(包括父类);

getDeclaredField(name):获取当前类的某个field(不包括父类);

getFields():获取所有public的field(包括父类);

getDeclaredFields():获取当前类的所有field(不包括父类)。

Field对象包含一个field的所有信息:

getName();//获得名称

getType();//获得类型

getModifiers();//获得修饰符

获取field的值:

get(Object);//获取一个实例的该字段的值

如:

设置field的值:

set(Object, Object);//设置一个实例的该字段的值

如:

总结:

Field对象封装了字段的所有信息;

通过Class实例的方法可以获取Field实例;

通过Field实例的方法可以获取字段信息;

通过Field实例可以读取或设置某个对象的字段;

通过设置setAccessible(true)来访问非public字段。

调用方法:

通过Class实例获取方法Method信息:

getMethod(name, Class...):获取某个public的method(包括父类);

getDeclaredMethod(name, Class...):获取当前类的某个method(不包括父类);

getMethods():获取所有public的method(包括父类);

getDeclaredMethods():获取当前类的所有method(不包括父类);

Method对象包含一个method的所有信息:

getName();//返回方法名称

getReturnType();//返回方法的返回类型

getParameterTypes();//返回方法的参数类型

getModifiers();//返回方法的修饰符

如:

调用无参数的Method:

Object invoke(Object obj)

如:

调用带参数的Method:

Object invoke(Object obj, Object... args)

如:

我们再看一个例子:

多态:

从Person.class获取的Method,作用于Student实例时:

实际调用方法使Student覆写的方法,这保证了多态的正确性。

如:

总结:

Method对象封装了方法的所有信息;

通过Class实例的方法可以获取Method实例;

通过Method实例可以获取方法信息;

通过Method实例可以调用某个对象的方法;

通过设置setAccessible(true)来访问非public方法。

调用构造方法:

Class.newInstance()只能调用public无参数构造方法。

如:

Integer没有定义无参数的构造方法。

Constructor对象包含一个构造方法的所有信息,可以创建一个实例:

通过Class实例获取Constructor信息:

getConstructor(Class...):获取某个public的Constructor;

getDeclaredConstructor(Class...):获取某个Constructor;

getConstructors():获取所有public的Constructor;

getDeclaredConstructors():获取所有Constructor;

总结:

Constructor对象封装了构造方法的所有信息;

通过Class实例的方法可以获取Constructor实例;

通过Constructor实例可以创建一个实例对象;

通过设置setAccessible(true)来访问非public构造方法。

获取继承关系:

获取父类的Class:

Class getSuperclass();

Object的父类是null;

interface的父类是null;

如:

获取当前类直接实现的interface:

Class[] getInterfaces();

不包括间接实现的interface;

没有interface的class返回空数组;

interface返回继承的interface;

如:

判断一个向上转型是否成立:

bool isAssignableFrom(Class)

如:

总结:

通过Class对象可以获取继承关系:getSuperclass()、getInterfaces();

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否正确。

Java注解:

使用注解:

注解(Annotation)是放在Java源码的类、方法、字段、参数前的一种标签。

如:

注解本身对代码逻辑没有任何影响,如何使用注解由工具决定。

编译器可以使用的注解:

@Override:让编译器检查该方法是否正确实现覆写;

@Deprecated:告诉编译器该方法已经被标记为“作废”,其他地方引用将会出现编译警告;

@SuppressWarnings。

如:

注解可以定义配置参数:

配置参数由注解类型定义;

配置参数可以包括:所有基本类型、String、枚举类型、数组;

配置参数必须是常量。

如:

使用注解时,缺少某个配置参数将使用默认值;如果只写常量,相当于省略了value参数;如果只写注解,相当于全部使用默认值。

如:

总结:

注解(Annotation)是Java语言用于工具处理的标注;

注解可以配置参数,没有指定配置的参数将使用默认值;

如果参数名称是value,可以省略参数名称。

定义注解:

使用@interface定义注解(Annotation):

注解的参数类似无参数方法;

可以设定一个默认值(推荐);

把最常用的参数命名为value(推荐)。

如:

元注解:可以修饰其他注解的注解。

使用@Target定义Annotation可以被应用于源码的哪些位置:

类或接口:ElementType.TYPE;

字段:ElementType.FIELD;

方法:ElementType.METHOD;

构造方法:ElementType.CONSTRUCTOR;

方法参数:ElementType.PARAMETER。

如:

使用@Retention定义Annotation的声明周期:

仅编译器:RetentionPolicy.SOURCE;//此时Annotation在编译器编译时直接丢弃

仅class文件:RetentionPolicy.CLASS;//此Annotation仅存储在class文件中

仅运行期:RetentionPolicy.RUNTIME;//在运行期间可以读取该Annotation

如果@Retention不存在,则该Annotation默认为CLASS。通常自定义的Annotation都是RUNTIME。

如:

使用@Repeatable定义Annotation是否可重复(JDK>=1.8)。

如:

使用@Inherited定义子类是否可继承父类定义的Annotation:

仅针对@Target为TYPE类型的Annotation;

仅针对class的继承,对interface的继承无效。

如:

定义Annotation的步骤:

1、用@interface定义注解;

2、用元注解(meta annotation)配置注解:

Target:必须设置
Retention:一般设置为RUNTIME
通常不必写@Inherited, @Repeatable等等

3、定义注解参数和默认值。

如:

总结:

使用@interface定义注解;

可定义多个参数和默认值,核心参数使用value名称;

必须设置@Target来指定Annotation可以应用的范围;

应当设置@Retention为RUNTIME便于运行期间读取该Annotation。

处理注解:

如何读取RUNTIME类型的注解?

Annotation也是class;

所有Annotation继承自java.lang.annotation.Annotation;

使用反射API判断Annotation是否存在:

Class.isAnnotationPresent(Class)

Field.isAnnotationPresent(Class)

Method.isAnnotationPresent(Class)

Constructor.isAnnotationPresent(Class)

如:

使用反射API获取Annotation:

Class.getAnnotation(Class)

Field.getAnnotation(Class)

Method.getAnnotation(Class)

Constructor.getAnnotation(Class)

getParameterAnnotations()

如:

读取方法参数的Annotation:

总结:

我们可以在运行期间通过反射读取RUNTIME类型的注解(注意不要漏写@Retention(RetentionPolicy.RUNTIME));

我们可以通过工具处理注解来实现相应的功能:对JavaBean的属性值按规则进行检查;JUnit会自动运行@Test注解的测试方法。

Java泛型:

什么是泛型:

泛型(Generic)就是定义一种模板,例如ArrayList<T>。

为什么需要泛型?

如:

JDK提供了ArrayList,可以看做“可变长度”的数组:比数组要方便。

但是如果用ArrayList存储String类型,则需要强制转型,不方便也容易出错。

我们可以通过单独为String编写一种ArrayList来解决。

如:

但是新的问题是,我们还要为Integer单独编写一种ArrayList,还需要为其他所有class单独编写一种ArrayList。

这是非常麻烦的。

我们必须把ArrayList变成一种模板:

ArrayList<T>,T可以是任意的class,这样就实现了编写一个模板就可以存储各种类型的class。

所以,泛型(Generic)就是定义一种模板,例如ArrayList<T>。

在代码中为用到的类创建对应的ArrayList<类型>:

如:

ArrayList<String> strList = new ArrayList<String>();

编译器会针对泛型类型作检查。 我们传入的参数只能是String。

如:

泛型的继承关系:

ArrayList<T>实现了List<T>接口,可以向上转型。

如:

注意向上转型时class类型不能变,原来是String,向上转型也得是String。

AArrayList<Number>和ArrayList<Integer>两者没有继承关系!!

如:

总结:

泛型就是编写模板代码来适应任意类型;

不必对类型进行强制转换;

编译器将对类型进行检查;

注意泛型的继承关系(T不能变!)。

使用泛型:

不使用泛型时,List的接口变为Object类型,list方法的类型都变为Object。

定义一个泛型类型<String>时,List<T>的泛型接口变为强类型:void add(String);String get(int)。

如:

使用泛型时,我们可以省略编译器能自动推断出的类型。

如:

例子:

Arrays.sort()可以对Object元素排序。

待排序的元素需要实现Comparable<T>泛型接口:

总结:

使用泛型时,我们要把泛型参数<T>替换成需要的class类型;

我们可以省略编译器能自动推断出的类型;

不指定泛型参数类型时,编译器会给出警告,且只能将<T>视为Object类型。

编写泛型:

泛型(Generic)一般用在集合类中。

如何编写一个泛型类:

按照某种类型(如String)编写类;

标记出所有的特定类型(如String);

把特定类型替换为T,并申明<T>。

如:

注意:

编写泛型时,需要定义泛型类型<T>:

public class Pair<T> { … }

静态方法不能引用泛型类型<T>,这会导致编译错误,编译器无法在静态字段或静态方法中使用泛型类型<T>。

如:

我们可以使用另一个类型<K>,就可以把静态方法单独改写为“泛型”方法。

如:

即形式:public static <K> Pair<K> create(K first, K last) { … }

泛型可以同时定义多种类型<T, K>。

总结:

编写泛型时,需要定义泛型类型<T>:public class Pair<T>{...};

静态方法不能引用泛型类型<T>,必须定义其他类型<K>来实现“泛型”:public static <K> Pair<K> create(K first, K last) { … };

泛型可以同时定义多种类型<T, K>:public class Pair<T,K>{...}。

擦拭法:

Java的泛型(Generic)是采用擦拭法(Type Erasure)实现的。

在泛型代码编译时,编译器实际上把所有泛型类型<T>统一视为Object类型。

如:

编译器根据<T>实现安全的强制转型。

擦拭法的局限:

<T>不能是基本类型,例如int;

Object字段无法持有基本类型;

无法取得带泛型的Class;

如:

所有泛型类型,无论T是什么,返回的都是同一个类型的class。

无法判断带泛型的Class;

不能实例化 T 类型,因为擦拭后实际上是new Object();

如:

实例化T类型必须借助Class<T>;

如:

泛型的继承:

可以继承自泛型类:

public class IntPair extends Pair<Integer> {
}

父类的类型是Pair<Integer> ,子类的类型是IntPair。子类可以获取父类的泛型类型Integer。

如:

继承关系:

总结:

Java的泛型采用擦拭法实现;

擦拭法决定了泛型<T>:

不能是基本类型,例如int;

不能取得带泛型的Class;

不能判断带泛型的Class;

不能实例化 T 类型;

泛型方法要防止重复定义方法;

子类可以获取父类的泛型类型<T>。

extends通配符:

前面提到泛型的继承关系:

Pair<Integer>不是Pair<Number>的子类;

add()不接受Pair<Integer>;

如:

这样一来就不太方便。

我们可以使用<? extends Number>使方法接收所有泛型类型为Number或Number子类的Pair类。

如:

当对Pair<? extends Number>调用getFirst()方法:

方法签名:? extends Number getFirst();

因此调用getFirst方法我们可以安全赋值给Number类型的变量:Number x=p.getFirst();

但是我们不能预测实际类型就是Integer:Integer x=p.getFirst()。

如:

实际传入的是Double类型。

如果我们对Pair<? extends Number>调用setFirst()方法:

方法签名为:void setFirst(? extends Number);

无法传递任何Number类型给setFirst(? extends Number)。

如:

编译会报错。

因此,<? extends Number>的通配符:

允许调用get方法获得Number的引用;

不允许调用set方法传入Number的引用;

唯一例外:可以调用setFirst(null)。

<T extends Number>的通配符:

限定定义Pair<T>时只能是Number或Number的子类。

如:

总结:

使用类似<? extends Number>通配符作为方法参数时表示:

方法内部可以调用获取Number引用的方法;

方法内部无法调用传入Number引用的方法(null除外);

使用类似<T extends Number>定义泛型类时表示:

泛型类型限定为Number或Number的子类。

super通配符:

泛型的继承关系:

Pair<Integer>不是Pair<Number>的子类;

set()不接受Pair<Number>;

如:

我们可以使用<? super Integer>使方法接收所有泛型类型为Integer或Integer超类的Pair类。

如:

对Pair<? super Integer>调用setFirst()方法:

方法签名:void setFirst(? super Integer);

可以安全传入Integer类型的变量:p.setFirst(new Integer(123));

如:

对Pair<? super Integer>调用getFirst()方法:

方法签名:?super Integer getFirst();

无法赋值给Integer类型的变量。

如:

<? super Integer>的通配符:

允许调用set方法传入Integer的引用;

不允许调用get方法获得Integer的引用;

唯一例外:可以获取Object引用Object o=p.getFirst()。

类似地,如果List使用<? super Integer>参数:

<T super Integer>的通配符:

限定定义Pair<T>时只能是Integer或Integer的超类。

如:

方法参数<? extends T>和<? super T>的区别:

<? extends T>允许调用方法获取T的引用;

<? super T>允许调用方法传入T的引用。

如:

如果反过来定义,则:

无限定通配符<?>:

?既包含extends的限制,又包含super的限制;

不允许调用set方法(null除外);

只能调用get方法获取Object引用;

Pair<?>和Pair不同。

如:

可以引用泛型参数<T>来消除<?>。

总结:

使用类似<? super Integer>的通配符作为方法参数时表示:

方法内部允许调用set方法传入Integer的引用;

方法内部不允许调用get方法获得Integer的引用;

唯一例外:可以获取Object引用Object o=p.getFirst()。

使用类似<T super Integer>定义泛型时表示:

泛型类型限定为Integer或Integer的超类。

无限定通配符<?>很少使用,可以用<T>替换。

泛型和反射:

部分反射API是泛型:

Class<T>是泛型;

如:

Constructor<T>也是泛型;

如:

我们可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型。

如:

我们必须通过强制转型实现带泛型的数组。

如:

不安全地使用带泛型的数组:

安全地使用带泛型的数组:

带泛型的数组实际上是编译器的类型擦除:

我们不能直接创建T[]数组,因为擦拭后代码变为new Object[5],我们必须借助Class<T>。

如:

我们还可以利用可变参数创建T[]数组:

@SafeVarargs消除编译器警告。

如:

总结:

部分反射API是泛型:Class<T>和Constructor<T>;

可以声明带泛型的数组,但是不能直接创建带泛型的数组,必须强制转型;

可以用过Array.newInstance(Class<T>,int)创建T[]数组,需要强制转型。

猜你喜欢

转载自blog.csdn.net/zgcr654321/article/details/83216412