1、注解
java中的注解我们已经见过非常多了,现在来深入了解一下。
什么是注解(Annotation)
Annotation是从JDK5.0开始引入的新技术.(现在不算新了)
Annotation的作用:
- 不是程序本身,可以对程序作出解释(这一点和注释(comment)没什么区别
- 可以被其他程序(比如:编译器等)读取.
Annotation的格式:
- 注解是以"@注释名"在代码中存在的,还可以添加一些参数值﹐例如:@SuppressWarnings(value=“unchecked”).
Annotation在哪里使用?
- 可以附加在package , class , method , field等上面相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问
常见的内置注解
@Override:(重写)
- 定义在java.lang.Override中,此注释只适用于修辞方法﹐表示一个方法声明打算
重写超类中的另一个方法声明.
@Deprecated:(注明该元素已过时)
- 定义在java.lang.Deprecated中,此注释可以用于修辞方法﹐属性﹐类﹐表示不鼓励程序员使用这样的元素﹐通常是因为它很危险或者存在更好的选择.
@suppressWarnings:(镇压警告)
- 定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息.
与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了.
- @SuppressWarnings(“all”)
- @SuppressWarnings(“unchecked”)
- @SuppressWarnings(value={“unchecked”,“deprecation”})
- 等等…
1.2、元注解
元注解
-
元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明. -
这些类型和它们所支持的类在java.lang.annotation包中可以找到.(@Target , @Retention ,@Documented , @lnherited )
重点了解**@Target , @Retention**
@Target :
用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
点开@Target的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
那么它的参数都有哪些呢,点开ElementType,发现这是一个枚举类型。
它的参数可以有里面这些,代表它描述该注解可以用在什么地方
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
例如下面这段代码,自定义了一个注解MyAnnotation,
@Target(value = ElementType.METHOD)
@interface MyAnnotation{
}
public class Test02 {
@MyAnnotation
public void test(){
System.out.println("test!!!");
}
}
如果想让它在多个地方生效,那么可以这样,多加几个属性
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention:
表示需要在什么级别保存该注释信息﹐用于描述注解的生命周期
? (SOURCE<CLASS < RUNTIME)
点开@Retention
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
@Document:说明该注解将被包含在javadoc中
? 表示是否将我们的注解生成在JAVAdoc中
@Inherited:说明子类可以继承父类中的该注解
1.3、自定义注解
自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:
-
interface用来声明一个注解﹐格式:public @interface注解名{定义内容} -
其中的每一个方法实际上是声明了一个配置参数. -
方法的名称就是参数的名称. -
返回值类型就是参数的类型(返回值只能是基本类型,Class , String , enum ) -
可以通过default来声明参数的默认值 -
如果只有一个参数成员,一般参数名为value -
注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
String value();
int id() default 0;
String[] hobby() default "";
}
public class Test03 {
@MyAnnotation2(id = 3,value = "ss",hobby = {"北京烤鸭","章鱼小丸子"})
public void test(){
System.out.println("ttt");
}
}
2、反射
java是静态语言,因为有了反射机制,Java变成了准动态语言
了解基础点击JAVA的反射机制
那些类型有class对象
import java.lang.annotation.ElementType;
public class Test01 {
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;
Class c9 = Class.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);
System.out.println(c9);
}
}
2.1、类加载内存分析
类的加载过程
加载
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象.
链接
- 将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
个人理解
加载:
在加载的时候会为不同的类产生对应的Class对象加载到内存中,相同对象只有一个Class对象,这个Class包含了对象的所有东西;
连接:
为静态的变量分配内存,并给一个默认的初始值;
static方法合并,先后执行,后面的会覆盖前面的;
初始化:
new一个对象的时候,直接去找这个Class对象,将里面的东西拿过来,然后赋值,就产生了一个新的该对象。
2.2、分析类的初始化
什么时候会发生类的初始化
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导
致子类初始化 - 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化**(常量在链接阶段就存入调用类的常量池中了)**
测试:
class Father{
static int m =10;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static int b=9;
static final int B=200;
static {
System.out.println("子类被加载");
}
}
测试加载
public class Test06 throws ClassNotFoundException{
static {
System.out.println("主类被加载");
}
public static void main(String[] args) {
Son son = new Son();
Class.forName("com.reflection.Son");
System.out.println(Son.m);
Son[] sons = new Son[5];
System.out.println(Son.B);
}
}
2.3、类加载器
类加载器的作用
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存∶
标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象,当他不被需要的时候。
类加载器有三个
引导类加载器:
用C++编写的,是JVM自带的类加载器,负责Java平台核心库(rt.jar),用来加载核心类库。该加载器无法直接获取
扩展类加载器:
负责jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
系统类加载器:
负责java -classpath或-D Java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器。
(把所指定一些项目里的jar包加载进来)
三个类的关系:
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
ClassLoader classLoader = Class.forName("com.classloader.Test01").getClassLoader();
System.out.println(classLoader);
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
System.out.println(System.getProperty("java.class.path"));
}
}
扩展知识:双亲委派机制
作用,防止包同名、检测安全性。
防止同名包、类与 jdk 中的相冲突,实际上加载类的时候,先通知 appLoader(系统加载器),看 appLoader 是否已经缓存,没有的话,appLoader 又委派给他的父类加载器(extLoader)询问,看他是不是能已经缓存加载,没有的话,extLoader 又委派他的父类加载器(bootstrapLoader)询问,BootstrapLoader看是不是自己已缓存或者能加载的,有就加载,没有再返回 extLoader,extLoader 能加载就加载,不能的话再返回给 appLoader 加载,再返回的路中,谁能加载,加载的同时也加缓存里。
2.4、获取类运行时的结构
JAVA的反射机制文章的最下面那一部分。
有了Class对象,能做什么?
创建类的对象:调用Class对象的newlnstance()方法
- 类必须有一个无参数的构造器。
- 类的构造器的访问权限需要足够
Class c1 = Class.forName("com.reflection.User");
User user = (User) c1.newInstance();
System.out.println(user);
难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤如下:
- 通过Class类的getDeclaredConstructor(Class …parameterTypes)取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
- 通过Constructor实例化对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user1 = (User) constructor.newInstance("张三", 001, 18);
System.out.println(user1);
调用指定的方法
Object invoke(Object obj,Object … args)
- Object对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参Object obj可为null-
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为privat e,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
User user3 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user3, "李四");
System.out.println(user3.getName());
setAccessible
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible作用是启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问
- 参数值为false则指示反射的对象应该实施Java语言访问检查
User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true);
name.set(user4,"王五");
System.out.println(user4.getName());
检测性能对比:
package com.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test10 {
public static int num=1000000000;
public static void test01(){
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < num; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式耗时"+(endTime - startTime) + "ms");
}
public static void test02() throws Exception {
User user = new User();
Class c1 = user.getClass();
Method method = c1.getDeclaredMethod("getName",null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < num; i++) {
method.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("不关安全检测耗时"+(endTime - startTime) + "ms");
}
public static void test03() throws Exception {
User user = new User();
Class c1 = user.getClass();
Method method = c1.getDeclaredMethod("getName",null);
method.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < num; i++) {
method.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("关闭检测耗时"+(endTime - startTime) + "ms");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
}
(博主自己的机子,机子不同,时间会有差异)
分析结果:
最快的是使用普通方法,效率是反射的几百倍
关闭检测的执行效率是不关闭的两倍左右
2.5、反射操作注解
常用的两个方法:
- getAnnotation
- getAnnotations
先了解一个概念:ORM(Object Relationship Mapping),对象关系映射
什么是对象关系映射?
就比如java的实体类里面的属性和字段一一对应数据库里的
在Java中有
class Student{
int id;
String name;
int age;
}
数据库为
利用注解和反射完成表和表结构的映射关系
package com.reflection;
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test12 {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.reflection.Student2");
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
TableDB annotation = (TableDB) c1.getAnnotation(TableDB.class);
System.out.println(annotation.value());
Field f = c1.getDeclaredField("name");
FieldDB fAnnotation = f.getAnnotation(FieldDB.class);
System.out.println(fAnnotation.columnName());
System.out.println(fAnnotation.type());
System.out.println(fAnnotation.length());
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface TableDB{
String value();
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface FieldDB{
String columnName();
String type();
int length();
}
@TableDB("db_student")
class Student2{
@FieldDB(columnName = "id",type = "int",length = 10)
private int id;
@FieldDB(columnName = "id",type = "varchar",length = 10)
private String name;
@FieldDB(columnName = "id",type = "int",length = 10)
private int age;
public Student2() {
}
public Student2(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int 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;
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
许多框架里面都有大量的注解,我们通过反射去读取这些注解,生成注解里面的信息。
我们就可以根据注解的信息定义类或者数据库里面的信息,方便操作增删改查
|