文章目录
1、什么是注解?
-
Annotation是从 JDK5.0 开始引入的新技术。
-
Annotation的作用:
不是程序本身,可以对程序做出解释。(这一点和注释(comment)没什么区别)
可以被其他程序(比如:编译器等)读取。
-
Annotation的格式:
以 “@注解名” 在代码中存在的,还可以添加一些参数值。
如:@SuppressWarnings(value=“unchecked”)
-
Annotation在哪里使用?
在 package、class、method、field 等上面使用。相当于给他们添加了额外的辅助信息。
我们可以通过反射机制编程实现实现对这些元数据的访问。
2、内置注解
@Override
定义在 java.lang.Override 中,此注释只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明。
@Deprecated
定义在 java.lang.Deprecated 中,此注解可以修饰方法、属性、类,表示不鼓励程序员使用这些元素(但能使用),通常是因为它很危险或者存在更好的选择。
当调用被该注解修饰的方法、属性、类时,弹出的提示或打出的方法、属性、类会被画上横线。
@SuppressWarnings
定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息。此注解可以修饰方法、类。
没加注解之前有黄色警告:
加了之后,警告会被抑制:
与前两个注解有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好的,只需选择性的使用就好了。
@SuppressWarnings(“all”)
@SuppressWarnings(“unchecked”)
@SuppressWarnings(value={“unchecked”,“deprecation”})
…
3、元注解
-
元注解的作用就是负责注解其他注解,Java 定义了4个标准的 meta-annotation 类型,他们被用来提供对其他 annotation 类型作说明。
-
这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。
@Target
用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
//测试元注解
@MyAnnotation
public class Test02 {
@MyAnnotation
public void test(){
}
}
//自定义一个注解
//表示该注解可用于方法、类、接口上
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@interface MyAnnotation{
}
ElementType源代码:
package java.lang.annotation;
public enum ElementType {
/** 类,接口(包括注释类型)或枚举声明 */
TYPE,
/** 字段声明(包括枚举常量) */
FIELD,
/** 方法声明 */
METHOD,
/** 形式参数声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注释类型声明 */
ANNOTATION_TYPE,
/** 包声明 */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention
表示需要在什么级别保存该注释信息,用于描述注解的生命周期。 (SOURCE < CLASS < RUNTIME)
相当于注解在什么地方还有效。(一般默认使用:RUNTIME)
//自定义一个注解
//表示该注解可用于方法、类、接口上
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//表示该注解在运行时还有效(通常使用)
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation{
}
RetentionPolicy源代码:
package java.lang.annotation;
public enum RetentionPolicy {
/**
* 源码级别,会被编译器丢弃。
*/
SOURCE,
/**
* 注释将由编译器记录在类文件中,
* 但 VM 不必在运行时保留它们。
* 这是默认行为。
*/
CLASS,
/**
* 注释将由编译器记录在类文件中,
* 并在运行时由 VM 保留,
* 因此可以通过反射方式读取它们。
*/
RUNTIME
}
@Documented
表示将该注解包含在javadoc(Java文档)中
//自定义一个注解
//表示该注解可用于方法、类、接口上
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//表示该注解在运行时还有效(通常使用)
@Retention(value = RetentionPolicy.RUNTIME)
//表示将该注解包含在javadoc(Java文档)中
@Documented
@interface MyAnnotation{
}
@Inherited
说明子类可以继承父类中的该注解
//自定义一个注解
//表示该注解可用于方法、类、接口上
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//表示该注解在运行时还有效(通常使用)
@Retention(value = RetentionPolicy.RUNTIME)
//表示将该注解包含在javadoc(Java文档)中
@Documented
//说明子类可以继承父类中的该注解
@Inherited
@interface MyAnnotation{
}
4、自定义注解
使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口。
分析:
- @interface 用来声明一个注解,格式:public @interface 注解名{定义内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为 value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值 。
package com.htl.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class Test03 {
/** 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值! */
@MyAnnotation2(name = "韩天乐",schools = {"江海大"})
public void test1(){
}
/**
* 如果注解只有一个参数,且只有当参数为 value 时,可以省略 value 直接写值!
* 所以如果注解只有一个参数,建议使用 value 命名。
*/
@MyAnnotation3("张三")
public void test2(){
}
//如:注解只有一个参数,但参数为 name,不能省略 name。
@MyAnnotation4(name = "李四")
public void test3(){
}
//参数为枚举类型
@MyAnnotation5(Sex.MAN)
public void test4(){
}
}
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
/** 注解的参数:参数类型 + 参数名(); */
//String name();
String name() default "";
int age() default 0;
int id() default -1; //如果默认值为-1,代表不存在。
String[] schools();
}
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
String value();
}
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation4{
String name();
}
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation5{
Sex value();
}
//枚举
enum Sex{
MAN,
WOMAN
}
5、反射概述
Reflection(反射)是 Java 被视为动态语言的关键。反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性与方法。
Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
-
优点:
可以实现动态创建对象和编译,体现出很大的灵活性。
-
缺点:
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM ,我们希望做什么并且它满足我们的要求。这类操作总是慢于 直接执行相同的操作。
反射相关的主要 API:
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- …
5.1 Class 类
在 Object 类中定义了以下的方法,此方法将被所有子类继承。
public final Class getClass()
以上的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
5.2 class 类常用方法
1. 获取成员变量:
Field[] getFields() : 获取所有 public 修饰的成员变量
Field getField(String name) : 获取指定名称的 public 修饰的成员变量
Field[] getDeclaredFields() : 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name) : 获取指定名称的成员变量,不考虑修饰符
2. 获取构造方法们:
Constructor<?>[] getConstructors() : 获取所有 public 修饰的构造方法
Constructor<T> getConstructor(类<?>... parameterTypes) : 获取指定的 public 修饰的构造方法(区分构造方法:看参数)
Constructor<?>[] getDeclaredConstructors() : 获取所有构造方法,不考虑修饰符
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) : 获取指定的构造方法,不考虑修饰符
3. 获取成员方法们:
Method[] getMethods() : 获取所有的 public 修饰的方法
Method getMethod(String name, 类<?>... parameterTypes) : 获取指定名称的 public 修饰的方法
Method[] getDeclaredMethods() : 获取所有的方法,不考虑修饰符,但不包括继承的方法。
Method getDeclaredMethod(String name, 类<?>... parameterTypes) : 获取指定名称的方法,不考虑修饰符。
4. 获取全类名:
String getName()
5. 创建此 Class 对象所表示的类的一个新实例:
T newInstance()
Field:成员变量
操作:
1. 设置值
void set(Object obj, Object value)
2. 获取值
get(Object obj)
3. 忽略访问权限修饰符的安全检查
setAccessible(true) : 暴力反射
Constructor:构造方法
创建对象:
T newInstance(Object... initargs)
如果使用空参数构造方法创建对象,操作可以简化:Class对象的 newInstance 方法
Method:方法对象
执行方法:
Object invoke(Object obj, Object... args)
获取方法名称:
String getName:获取方法名
测试代码:
//实体类:
package com.htl.test;
public class Person {
public String id;
protected String name;
int age;
private String a;
public Person() { }
public Person(String id, String name, int age, String a) {
this.id = id;
this.name = name;
this.age = age;
this.a = a;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
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;
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
@Override
public String toString() {
return "Person{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", a='" + a + '\'' +
'}';
}
public void eat(){
System.out.println("吃黄焖鸡吧。。");
}
public void eat(String food){
System.out.println("食"+food+"啦你!");
}
}
package com.htl.test;
import java.lang.reflect.Field;
public class test1 {
public static void main(String[] args) throws Exception {
//获取 Person类的Class对象
Class personClass = Person.class;
/** 1、Field[] getFields() :获取所有public修饰的成员变量。 */
Field[] fields = personClass.getFields();
for (Field field : fields){
System.out.println(field);// public java.lang.String com.htl.test.Person.id
}
System.out.println("===========================1===============================");
/** 2、Field getField(String name) :获取指定名称的 public 修饰的成员变量。*/
Field id = personClass.getField("id");
System.out.println(id); // public java.lang.String com.htl.test.Person.id
//获取成员变量id的值
Person person = new Person();
Object obj = id.get(person);
System.out.println(obj); // null
//设置a的值
id.set(person,"123");
System.out.println(id.get(person)); // 123
System.out.println(person); // Person{id='123', name='null', age=0, a='null'}
System.out.println("===========================2===============================");
/** 3、Field[] getDeclaredFields() :获取所有的成员变量,不考虑修饰符。*/
Field[] declaredFields = personClass.getDeclaredFields();
for (Field field : declaredFields){
System.out.println(field);
}
System.out.println("===========================3===============================");
/** 4、Field getDeclaredField(String name) :获取指定名称的成员变量,不考虑修饰符。*/
Field a = personClass.getDeclaredField("a");
System.out.println(a); // private java.lang.String com.htl.test.Person.a
// 忽略访问权限修饰符的安全检查(暴力反射)
a.setAccessible(true);
Object object = a.get(person);
System.out.println(object); // null
a.set(person,"HTL");
System.out.println(a.get(person)); // HTL
System.out.println(person); // Person{id='123', name='null', age=0, a='HTL'}
}
}
package com.htl.test;
import java.lang.reflect.Constructor;
public class test2 {
public static void main(String[] args) throws Exception{
//获取 Person类的Class对象
Class<Person> personClass = Person.class;
/** 1、Constructor<?>[] getConstructors() : 获取所有 public 修饰的构造方法。*/
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor constructor : constructors){
System.out.println(constructor);
}
System.out.println("=================================1==========================================");
/** 2、Constructor<T> getConstructor(类<?>... parameterTypes) : 获取指定的 public 修饰的构造方法(区分构造方法:看参数) */
Constructor constructor1 = personClass.getConstructor(String.class, String.class, int.class, String.class);
Constructor constructor2 = personClass.getConstructor();
System.out.println(constructor1);
System.out.println(constructor2);
System.out.println("=================================2==========================================");
/**
* 创建对象:( Class 和 Constructor 里面都有该方法。都可以使用!)
* T newInstance(Object... initargs)
*/
// Constructor里面的
Object person = constructor1.newInstance("2", "李四", 10, "4");
System.out.println(person); // Person{id='2', name='李四', age=10, a='4'}
// Class里面的
Person person1 = personClass.newInstance();
System.out.println(person1);
}
}
package com.htl.test;
import java.lang.reflect.Method;
public class test3 {
public static void main(String[] args) throws Exception {
//获取 Person类的Class对象
Class<Person> personClass = Person.class;
/** 1、Method[] getMethods() : 获取所有的 public 修饰的方法 */
Method[] methods = personClass.getMethods();
for (Method method : methods){
System.out.println(method);
}
System.out.println("====================================================================");
/** 2、Method getMethod(String name, 类<?>... parameterTypes) : 获取指定名称的 public 修饰的方法 */
Method eat = personClass.getMethod("eat");
System.out.println(eat); // public void com.htl.test.Person.eat()
System.out.println("=====================================1===============================");
Method eat1 = personClass.getMethod("eat", String.class);
System.out.println(eat1); // public void com.htl.test.Person.eat(java.lang.String)
System.out.println("=====================================2===============================");
/** 执行方法:Object invoke(Object obj, Object... args) */
Person p = new Person();
eat.invoke(p); //食屎啦你!
System.out.println("=====================================3===============================");
eat1.invoke(p,"屎");
System.out.println("=====================================4===============================");
/** 获取方法名称:String getName:获取方法名 */
String name = eat.getName();
System.out.println(name); //eat
String name1 = eat1.getName();
System.out.println(name1); //eat
}
}
5.3 获取 Class 类的实例
- 若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高。
Class c1 = Student.class;
- 已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象。
Class c2 = person.getClass();
- 已知一个类的全类名,且该类在类路劲下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException 异常。
Class c3 = Class.forName("com.htl.reflection.Student");
- 内置基本数据类型可以直接用类名.Type
- 还可以利用 ClassLoader 。
package com.htl.reflection;
public class Test02 {
//测试Class类的创建方式有那些
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("这个人是:"+person.name);
//方式1:通过类名.class获得
Class c1 = Student.class;
System.out.println(c1.hashCode());
//方式2:通过对象获得
Class c2 = person.getClass();
System.out.println(c2.hashCode());
//方式3:forName获得
Class c3 = Class.forName("com.htl.reflection.Student");
System.out.println(c3.hashCode());
//方式4:基本内置类型的包装类都有一个Type属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获得父类类型
Class c5 = c2.getSuperclass();
System.out.println(c5);
}
}
class Person{
public String name;
public Person() { }
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends Person{
public Student() {
this.name = "学生";
}
}
5.4 哪些类型可以有 Class 对象?
5.5 所有类型的 Class 对象
package com.htl.reflection;
import java.lang.annotation.ElementType;
//所有类型的 Class 对象
public class Test03 {
public static void main(String[] args) {
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; //二维数组
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚举
Class c7 = Integer.class; //基本数据类型
Class c8 = void.class; //void
Class c9 = Class.class; //class
System.out.println(c1); //class java.lang.Object
System.out.println(c2); //interface java.lang.Comparable
System.out.println(c3); //class [Ljava.lang.String;
System.out.println(c4); //class [[I
System.out.println(c5); //interface java.lang.Override
System.out.println(c6); //class java.lang.annotation.ElementType
System.out.println(c7); //class java.lang.Integer
System.out.println(c8); //void
System.out.println(c9); //class java.lang.Class
//只要元素类型与维度一样,就是同一个 Class
int[] a = new int[10];
int[] b = new int[20];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
}
}
5.6 类加载内存分析
Java内存分析
堆
存放new的对象和数组
可以被所有的线程共享,不会存放别的对象引用
栈
存放基本变量类型(会包含这个基本类型的具体数值)
引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区
可以被所有的线程共享
包含了所有的 class 和 static 变量
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
类的加载与ClassLoader的理解
- 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象。
- 链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。
- 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题。
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器 < clinit>() 方法的过程。类构造器 < clinit>() 方法是由:编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的 < clinit>() 方法在多线程环境中被正确加锁和同步。
package com.htl.reflection;
public class Test04 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/**
* 1、加载到内存,会产生一个类对应 Class 对象。
* 2、开始链接,链接结束后,m = 0
* 3、初始化:
* <clinit>(){
* m = 100;
* System.out.println("A类静态代码块初始化");
* m = 300;
* }
* 4、最终:m = 300
*/
}
}
class A{
public A() {
System.out.println("A类的无参构造初始化");
}
static int m = 100;
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
}
5.7 什么时候会发生类初始化?
package com.htl.reflection;
//测试类什么时候会初始化
public class Test05 {
static {
System.out.println("Main类被初始化");
}
public static void main(String[] args) throws ClassNotFoundException {
//1、主动引用
// Son son = new Son();
//反射也会产生主动引用
// Class.forName("com.htl.reflection.Son");
//2、被动引用
// System.out.println(Son.b);
//通过数组定义类引用,不会发生类初始化
// Son[] array = new Son[3];
//引用常量不会发生类初始化
System.out.println(Son.n);
}
}
class Father{
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
m = 20;
}
static int m = 10;
static final int n = 1;
}
5.8 类加载器的作用
package com.htl.reflection;
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 获取系统类加载器的父类加载器 --> 扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
// 获取扩展加载器的父类加载器 --> 根加载器(C/C++)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
// 测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.htl.reflection.Test06").getClassLoader();
System.out.println(classLoader);
// 测试jdk内置的Object是谁加载的
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
}
}
结果: