#Java 注解和反射

注解(Annotation)


举个例子就是你重写方法时候上面哪个东西----> (@Override)

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了" + i + "只鸡");
        }
    }
}

我们看一下注解源码



这里我们可以知道注解定义的格式是

public @interface 注解名 {
}

方便我们下面自定义注解。格式上面那两个注解是元注解,后面会讲到

内置注解

3个常见的内置注解



当你的代码有警告时,可以使用@SuppressWarnings()来去除警告



元注解(meta-annotation)

作用:注解其他的注解


@Target()

需要传一个参数,打开源码我们可以看到能传进去的参数有很多



补充:
1.TYPE表示可以使用在类上,接口上...(自己看注释),METHOD表示可以使用在方法上
2.写的格式(value可省略)

@Target(value = {ElementType.TYPE})
@Target(value = {ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention()

同样需要参数


作用范围大小:RUNTIME>CLASS>SOURCE
我们自定义注解一般都写RUNTIME
写的格式和上面一样就不多写了,只不过要把ElementType换成@Retention()对应的RetentionPolicy
补充:自己敲的时候不用写ElementType,RetentionPolicy,直接敲参数就行了

@Documented

不需要参数,作用上面有解释

@Inherited

不需要参数,作用上面有解释

自定义注解


格式:

@interface 注解名 {
}

定义了参数不写参数就会报错,跟元注解一样,但可以设定默认值

public class Test01 {
    @MyAnnotation(name = "xiaoMing")
    public void test(){

    }
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
    //注解的参数 : 参数类型 + 参数名 + ();
    String name();
    int id() default  -1;
    String[] school() default {"五邑大学"};
}

如果只有一个参数,建议使用value命名,因为使用value命名,且只有一个参数的时候,显示赋值的时候可以不写value = xxx(参数),直接写参数名就好了,这也解释了上面元注解为什么不用写value的原因

public class Test01 {
    @MyAnnotation2("xiaoHong")
    public void test(){

    }

}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    String value();
}

反射(Reflectoin)

静态语言和动态语言

Java Reflection

反射的优点和缺点

反射主要API


这里的方法是Class,C大写,我们定义一个类的时候是class,c是小写的

public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取类的Class对象
        //要抛出异常,因为程序不知道返回什么类
        Class c1 = Class.forName("com.nomad.java.User");
        System.out.println(c1);
        
    }

}

//实体类
class User{
    private String name;
    private int id;
    private int age;

    public User() {

    }

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

可以发现有很多方法


重点:一个类在内存中只有一个Class对象,一个类被加载后,类的整个结构都会被封装在Class对象中
他已经帮你new好这个对象了,并且只有一个对象,所以你自己无论创建多少个引用都是指向同一个对象

Class c2 = Class.forName("com.nomad.java.User");
Class c3 = Class.forName("com.nomad.java.User");
Class c4 = Class.forName("com.nomad.java.User");

System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());

发现打印的hashCode结果都一样,证明是同一个对象


补充:
1.自动匹配变量快捷键:alt+enter

Class类



正常是写一个类,然后new一个对象(从上往下)
反射就是通过对象来获得你这个类的信息(从下往上)

Class类的常用方法

获取Class类实例的几种方法


自己写代码练习一下

public class Test03 {

    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);
        //方法2,已知某个类的实例,通过实例.getClass()
        Class c2 = person.getClass();
        System.out.println(c2);
        //方法3,通过forName方法,但你得知道包名
        Class c3 = Class.forName("com.nomad.java.Student");
        System.out.println(c3);
        //方法4,基本内置类型的包装类都有一个TYPE属性
        Class c4 = Integer.TYPE;
        System.out.println(c4);
        //补充,获得父类类型
        Class c5 = c1.getSuperclass();//c2,c3都可以,证明了只要是指向子类的Class对象都可以通过.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 = "学生";
    }
}

class Teacher extends Person{
    public Teacher() {
        this.name = "老师";
    }
}

补充:重写toString方法-->在你直接打印对象名时候有用,方便查看属性

可以有Class对象的类型

public class Test04 {
    public static void main(String[] args) {
        //通过类名.class来查看是否有Class对象

        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);
        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);
        
    }
}

运行结果

class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class

类加载的过程

加载,链接,初始化


注意:
1.链接的准备过程,此时会给静态变量赋默认值(int类型 = 0,String类型 = null等等)
2.初始化时会合并类中所有类变量的赋值动作和静态代码块的语句,就像这样

3.静态代码块和静态代码执行取决于编写顺序

类加载时内存图

类的初始化

public class Test05 {
    static {
        System.out.println("main加载");
    }

    public static void main(String[] args) {
        //1.主动引用
        Son son = new Son();
        //2.反射
        Class.forName("com.nomad.java.Son");
        //3.不会初始化的操作
        System.out.println(Son.b);

        Son[] sons = new Son[10];

        System.out.println(Son.n);
    }

}

class Father{

    static int b = 100;

    static {
        System.out.println("父类加载");
    }
}

class Son extends Father{
    static{
        System.out.println("子类加载");
    }

    static int m = 100;

    static final int n = 200;


}

1和2的执行结果(new对象和反射执行结果一样,但是只会执行一次,因为这些信息只会加载一次)

main加载
父类加载
子类加载

3.的执行结果

System.out.println(Son.b);---->main加载,父类加载,100

Son[] sons = new Son[10];---->main加载

System.out.println(Son.n);---->main加载,200

总结:一般是new对象和反射时才会初始化

类加载器(加载那一步)

类加载器的类型


左边那个东西叫做双亲委派机制

双亲委派机制,是防止同名包、类与 jdk 中的相冲突,实际上加载类的时候,先通知 appLoader,看 appLoader 是否已经缓存,没有的话,appLoader 又委派给他的父类加载器(extLoader)询问,看他是不是能已经缓存加载,没有的话,extLoader 又委派他的父类加载器(bootstrapLoader)询问,BootstrapLoader看是不是自己已缓存或者能加载的,有就加载,没有再返回 extLoader,extLoader 能加载就加载,不能的话再返回给 appLoader 加载,再返回的路中,谁能加载,加载的同时也加缓存里。正是由于不停的找自己父级,所以才有 Parents 加载机制,翻译过来叫 双亲委派机制。

获取运行时类的结构


首先得创建你要获得的类的Class对象

Class c1 = Class.forName("com.nomad.java.User");

1.获得类的名字

//获得包名 + 类名
c1.getName()
//获得类名
c1.getSimpleName();

2.获得类的属性

//获得全部的公共属性(public)
c1.getFields();
//获得全部属性
c1.getDeclaredFields();
//获得指定的公共(public)属性
c1.getField("name");
//获得任意指定属性
c1.getDeclaredField("name");

3.获得类的方法

//获得本类以及父类的所有公共方法(public)
c1.getMethods();
//获得本类全部方法
c1.getDeclaredMethods();
//获得指定方法,如果有方法有参数就要填参数类型.class,没有就填null
c1.getMethod("getName", null);
c1.getMethod("setName", String.class);

4.获得类的构造器

//获得公共构造器
c1.getConstructors();
//获得全部构造器
c1.getDeclaredConstructors();
//获得指定构造器,要哪个构造器就传对应的参数进去
c1.getDeclaredConstructor(String.class, int.class, int.class);

还有其他的,比如获得注解啊啥的,就不写了

Class对象的具体应用


invoke

setAccessible


上代码

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //通过反射动态创建对象
        Class c1 = Class.forName("com.nomad.java.User");
        //一开始是这样的--->Object o = c1.newInstance();(通过alt + 回车的方式补充匹配变量的话)
        //因为我们知道是什么类型的,所以强转就好了
        //用这个方法有要求1.有无参构造器2.访问权限要够
        User user = (User) c1.newInstance();//本质上是调用了类的无参构造器
        System.out.println(user);


        //不需要无参构造器的方法--->自己调用一个构造器就好了
        //通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        //同样需要强转
        User user1 = (User) constructor.newInstance("xiaoHong", 18, 100);
        System.out.println(user1);


        //通过反射调用方法
        User user2 = (User) c1.newInstance();
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke:激活,传入的参数(对象,"方法的参数")
        setName.invoke(user2, "xiaoMing");//调用方法的语句
        System.out.println(user2);

        //通过反射调用属性
        User user3 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        //这里直接运行会报错,因为不能直接设置私有的属性和方法,通过setAccessible方法就可以(设置为true)
        name.setAccessible(true);
        name.set(user3, "xiaoHua");
        System.out.println(user3);

    }

}

反射方式调用方法的性能分析

通过不停的调用同一个方法,看运行的时间

public class Test08 {
    //普通方式调用
    public static void test01(){
        User user = new User();
        //获取当前时间
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("普通方式需要" + (endTime - startTime) + "ms");
    }


    //反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName", null);
        //获取当前时间
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("普通方式需要" + (endTime - startTime) + "ms");
    }


    //关闭检测的反射方式调用
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        //获取当前时间
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("普通方式需要" + (endTime - startTime) + "ms");
    }


    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        test01();
        test02();
        test03();
    }
}

执行结果

普通方式需要3ms
反射方式需要2634ms
关闭检测的反射方式需要1020ms

结论:关闭检测可使得效率变高

通过反射操作泛型


先跳过了

通过反射操作注解

练习ORM



Java中的类和数据库中的表的映射

public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.nomad.java.Student2");

        //通过反射获得注解(这里是连注解以及注解的值一起获取了)
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }


        //获得注解value的值
        //Annotation annotation = c1.getAnnotation(TableMad.class);
        //一开始是上面那样,强转一下就好了
        TableMad tableMad = (TableMad) c1.getAnnotation(TableMad.class);
        //通过对象.value可以获取value的值
        String value = tableMad.value();
        System.out.println(value);


        //获得指定的注解
        //先获得指定的属性
        Field name = c1.getDeclaredField("id");
        //获得该指定属性的注解
        FieldMad annotation = name.getAnnotation(FieldMad.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());


    }

}


@TableMad("db_Student")//数据库表名
class Student2{
    @FieldMad(columnName = "db_id", type = "int", length = 10)
    private int id;
    @FieldMad(columnName = "db_age", type = "int", length = 10)
    private int age;
    @FieldMad(columnName = "db_name", type = "varchar", length = 3)
    private String name;

    public Student2() {
    }

    public Student2(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableMad{
    //数据库表名
    String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldMad{
    //数据库列名
    String columnName();
    //数据库类型
    String type();
    //数据库长度
    int length();

}

执行结果

@com.nomad.java.TableMad("db_Student")
db_Student
db_id
int
10

补充:增强for快捷键

数组名.for

看一天终于看完了,迷迷糊糊的,有空多复习几遍,感觉就是学了很多方法,但是方法具体实现原理不知道
自己总结的一点心得:
不同类型的引用也能调用相同的方法,因为那个引用的类重写了相同的方法

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容