Java注解
在 java 中以 @
符号开头标在类上、函数上、属性等等上的就是 java 中的注解 ( Annotation ),他是在 JDK5 引进的技术,我们在初学 java 的时候就接触过注解,只是没有留意过罢了。
常见的注解
常见注解
作用描述
@Override
表示该方法是复写父类/接口的方法
@Deprecated
表示该方法为过期方法
@SuppressWarnings
忽略代码警告
@FunctionalInterface
表示当前接口是函数式接口,JDK8开始支持
代码举例
1 2 3 4 @FunctionalInterface public interface Demo1Interfase { void func () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Demo1 implements Demo1Interfase { @Override public void func () { System.out.println("复写Demo1Interfase接口的方法" ); } @Deprecated public void print () { System.out.println("我已经过期了" ); } @SuppressWarnings(value = "all") public void good () { String hello = "hello" ; System.out.println("good morning" ); } public static void main (String[] args) { Demo1 demo1 = new Demo1 (); demo1.print(); demo1.func(); Demo1Interfase d = ()-> System.out.println("函数式接口" ); d.func(); } }
元注解介绍
如果说注解是为我们写的类、函数、属性提供的功能,那么 ==元注解就是为注解提供的功能==,他规定了注解的一些使用规则。
元注解名称
作用描述
@Target
限制注解可以在什么位置使用,例如类,函数,属性等
@Retention
限制注解保存级别,例如运行时,编译时或者源码时
@Document
如果使用该元注解,可以被 javadoc 此类的工具文档化
@Inherited
可以让子类继承父类中的注解
@Repeatable
表示当前注解可以在同一个类,接口,函数中多次使用,JDK8 开始支持
@target
的属性值是 ElementType
得枚举类型,用于约束作用范围:
TYPE
: 可以作用在类,接口,注解,枚举类上
FIELD
:可以作用在类的属性上
METHOD
:可以作用在方法上
CONSTRUCTOR
:可以作用在构造方法中
PARAMETER
:可以作用在函数的参数列表中
LOCAL_VARIABLE
:可以作用在方法内的局部变量中
ANNOTATION_TYPE
:可以作用在注解上,TYPE中已经包含了
PACKAGE
:可以作用在包上
TYPE_USE
:能标注任何类型上,JDK8 开始支持
@Retention
可以限制注解的保存级别,值为 RetentionPolicy
的枚举类型:
SOURCE
:可以保存在源代码中
CLASS
:可以在编译时保存在 class 文件中
RUNTIME
:在运行时保存,==一般情况下使用这个就好了==
自定义注解
好了,现在我们已经知道什么时注解了,并且知道了用于约束注解的元注解,接下来就来基于这些约束来创建自定义的注解
创建自定义注解
创建自定义注解其实很简单,首先我们先创建一个接口,然后在 interfase
关键字前面加上 @
符号,这样一个注解就创建好了
1 2 @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno1 { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @MyAnno1 public class Demo2 { @MyAnno1 String user = "张" ; @MyAnno1 public void func1 () { @MyAnno1 String name = "涵哲" ; System.out.println(this .user + name +":func1 function run" ); } public static void main (String[] args) { new Demo2 ().func1(); } }
基于元注解加一些约束
可以看到,上面的注解已经创建成功了,接下来我们让注解只能在函数上生效
1 2 3 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno1 { }
注解参数
现在我们的注解需要接收一些参数,在注解中添加参数列表也很简单,就像接口中书写方法一样就可以了
1 2 3 4 5 6 7 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno1 { String user () ; String[] name(); String gender () default "男" ; }
需要注意的是,在接口中定义参数列表后,使用该注解的时候必须将值填写完整,不然会报错,如果使用了 dufault
设置默认值的话可以不用传值,写法如下:
1 2 @MyAnno1(user = "张", name = {"涵哲", "哈哈"}) public void func1 () { ... }
如果数组 name 只需要一个参数的话,那么花括号也可以省略不写
1 2 @MyAnno1(user = "张", name = "涵哲") public void func1 () { ... }
如果注解中只有一个参数的话,建议变量名命名为 value,这样的话在传参的时候可以省略不写,代码如下:
1 2 3 4 5 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno2 { String value () ; }
1 2 @MyAnno2("哈哈哈哈") public class Demo2 { ... }
收尾工作
目前来说注解就已经学完了,但是我们现在学的注解都是 “鸡肋”,都是 “空架子”,他光有注解的型但是没用功能,想要让我们的注解含有功能,我们需要依赖于反射技术。
Java反射
java 的反射机制允许程序在运行过程中获取任何类的完整结构信息 ( 注意,是类的信息,不是实例的信息 ),并可以操作其属性以及方法。
在 java 中针对反射机制也提供了一个类:Class
,可以通过 class 实例进行相关操作
获取Class实例
获取到某个类的 class 实例,有大概四种方法,如下所示
通过实例的 getClass()
函数获取
通过目标类的 class
属性获取
通过Class类的静态函数 forName()
获取
如果是基本类型包装类可以用 TYPE
属性获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Demo1 { public static void main (String[] args) throws Exception{ Person p = new Person (); Class c1 = p.getClass(); Class c2 = Person.class; Class c3 = Class.forName("site.hanzhe.entity.Person" ); Class c4 = Integer.TYPE; System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c4); } }
这里需要注意的是,java 中每种类型只有一个 class 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Demo2 { public static void main (String[] args) throws Exception{ Person p = new Person (); Class c1 = p.getClass(); Class c2 = Person.class; Class c3 = Class.forName("site.hanzhe.entity.Person" ); System.out.println(c1.hashCode()); System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); } }
java 中几乎所有类型都可以获取到 class 实例对象
类,数组,枚举的 class 实例打印出来是以 class 开头的,且数组后面的 [
根据维度进行变化的
接口,注解打印的 class 实例打印出来是以 interface 开头的
void 就是 void
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo3 { public static void main (String[] args) { Class c1 = Class.class; Class c2 = Person.class; Class c3 = Runnable.class; Class c4 = int [].class; Class c5 = int [][].class; Class c6 = Deprecated.class; Class c7 = ElementType.class; Class c8 = void .class; System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c4); System.out.println(c5); System.out.println(c6); System.out.println(c7); System.out.println(c8); } }
通过Class实例获取相关信息
我们可以通过以上四种方法获取到某个类的 class 实例,获取到 class 实例之后我们可以通过该实例获取到类中所有的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Demo4 { public static void main (String[] args) throws Exception { Class<?> c1 = Class.forName("site.hanzhe.entity.Person" ); for (Field field : c1.getFields()) System.out.println("获取所有属性: " + field); for (Field field : c1.getDeclaredFields()) System.out.println("获取全部属性(含私有): " + field); System.out.println("获取指定属性" + c1.getField("name" )); System.out.println("获取指定属性(含私有)" + c1.getDeclaredField("age" )); System.out.println("------------------------------------------------------" ); for (Method method : c1.getMethods()) System.out.println("获取所有函数: " + method); for (Method method : c1.getDeclaredMethods()) System.out.println("获取所有函数(含私有): " + method); System.out.println("获取指定函数: " + c1.getMethod("getName" )); System.out.println("获取指定函数(带参数): " + c1.getMethod("setName" , String.class)); System.out.println("获取指定函数(含私有): " + c1.getDeclaredMethod("show" )); System.out.println("------------------------------------------------------" ); for (Constructor<?> constructor : c1.getConstructors()) System.out.println("获取所有构造器: " + constructor); for (Constructor<?> constructor : c1.getDeclaredConstructors()) System.out.println("获取所有构造器(含私有): " + constructor); System.out.println("获取指定函数(带参数): " + c1.getConstructor(String.class, int .class)); System.out.println("获取指定函数(含私有): " + c1.getDeclaredConstructor(int .class)); } }
class.get***
:获取目标类 ( 包括父类 ) 的 字段/函数
class.getDeclared***
:获取目标类 ( 包含私有但不包含父类 ) 的 字段/函数
动态创建执行
刚刚我们已经可以通过 class 实例获取到所有的函数和属性了,既然可以获取那么就一定可以调用了
通过反射获取类的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class UseInstance { public static void main (String[] args) throws Exception{ Class clazz = Class.forName("site.hanzhe.entity.Person" ); Object o = clazz.newInstance(); System.out.println(o); System.out.println("======================================================" ); Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class); Object o1 = constructor.newInstance("张" , "涵哲" ); System.out.println(o1); } }
invoke:获取到类中的函数并执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class UseMethod { public static void main (String[] args) throws Exception{ Class<?> clazz = Class.forName("site.hanzhe.entity.Person" ); Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class); Object obj = constructor.newInstance("张" , "涵哲" ); Method setName = clazz.getDeclaredMethod("setName" , String.class); setName.invoke(obj, "三" ); System.out.println(obj); } }
get-set:获取到类中的属性进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class UseField { public static void main (String[] args) throws Exception{ Class<?> clazz = Class.forName("site.hanzhe.entity.Person" ); Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class); Object obj = constructor.newInstance("张" , "涵哲" ); Field name = clazz.getDeclaredField("name" ); name.set(obj, "麻子" ); System.out.println(name.get(obj)); } }
我们的代码是没有问题的,但是执行的时候却抛出了异常,我们来看一下异常信息:
1 java.lang.IllegalAccessException: Class site.hanzhe.reflect.UseField can not access a member of class site .hanzhe.entity.Person with modifiers "private"
获取访问权限
上面获取变量的时候抛出了 IllegalAccessException
异常,提示我们没有足够的权限操作 private
私有属性,这时我们可以通过 取消权限检查 来暴力获取到属性的权限
方法名
作用
isAccessible()
获取到是否拥有访问权限的相关信息,返回一个boolean值
setAccessible( boolean flag)
设置访问权限,true 为可访问,false 为不可访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class UseField { public static void main (String[] args) throws Exception{ Class<?> clazz = Class.forName("site.hanzhe.entity.Person" ); Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class); Object obj = constructor.newInstance("张" , "涵哲" ); Field name = clazz.getDeclaredField("name" ); System.out.println("是否具有权限操作:" + name.isAccessible()); name.setAccessible(true ); System.out.println("是否具有权限操作:" + name.isAccessible()); name.set(obj, "麻子" ); System.out.println(name.get(obj)); } }
高级扩展
Java内存分析
当程序使用某个类的时候会去内存中检查,如果这个类还没有被加载到内存中,则系统会通过以下三个步骤对类进行初始化加载:
1.【类的加载】 就是编译好的 .class 文件读入虚拟机
**2.【类的链接】**类的链接大概可以分为三个流程:
**验证:**检查 class 是否符合 JVM 规范,检查安全问题防止破坏 JVM
**准备:**为该类中的 static 修饰的成员变量设置初始值 ( 例如 int 为0,String 为 null 之类的 )
**解析:**符号引用转换为直接引用,这个…以后在研究
**3.【初始化】**这里会执行类构造器 <clinit>
,他会自动收集类中所有变量的赋值动作和静态代码块融在一起产生的方法,会按照类中的编码的顺序从上到下的流程执行
类加载器
上面简单学习了一下关于类加载的过程,知道了类想要使用必须要先加载到内存中,在 java 中类加载器就是负责将类加载到内存中的,java 中的类加载器分为三种:
**1.【系统类加载器】**系统类加载器是最常用的类加载器,负责加载 classpath 下的类或加载 java.class.path
所指的目录下的 jar 包装入,我们所写的 Person 类,User 类都是通过系统类加载器加载到内存中去的
**2.【扩展类加载器】**扩展类加载器加载的是 jre 目录下 lib/ext 下的 jar 包或 java.ext.path
目录下的 jar 包
**3.【引导类加载器】**主要负责加载核心的类,例如 String,Scanner 等等,处于这个状态下 java 是没法工作的,所以这个类加载器使用 C 编写的,也就意味着 java 获取不到这个类加载器
获取类加载器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Demo4 { public static void main (String[] args) throws Exception{ ClassLoader loader1 = ClassLoader.getSystemClassLoader(); System.out.println(loader1); ClassLoader extLoader = loader1.getParent(); System.out.println(extLoader); ClassLoader bootstrapLoader = extLoader.getParent(); System.out.println(bootstrapLoader); System.out.println("---------------------------------------" ); Class c1 = Class.forName("site.hanzhe.examle.Demo4" ); ClassLoader loader = c1.getClassLoader(); System.out.println(loader); } }