JavaSE(线程+反射+注解)

JavaSE基础巩固

线程

线程与进程的关系

  • 进程是一个应用程序;线程是一个进程中的执行单元,一个进程可以启动多个线程

  • 对于Java程序来说,运行文件时,会先启动JVM虚拟机,JVM虚拟机再启动一个垃圾回收线程负责看护,然后执行main方法的主线程

  • 两个进程之间是独立的,不共享资源;两个线程,堆内存和方法区内存资源共享,栈内存独立,一个线程一个栈

多线程机制

  • Java使用多线程机制,就是为了提高程序的处理效率

  • 使用了多线程机制后,main主方法结束了,主栈空了,其他的线程(栈)还是有可能在继续执行

  • 单核CPU不能做到真正意义上的多线程并发,但是可以给人一个“多线程并发”的错觉

实现线程的方式

  • 第一种方式:编写一个类,继承java.lang.Thread,重写run方法

    class ThreadTest{
    public static void main(String[] args){
       //创建线程对象
       MyThread t1 = new MyThread();
       //启动线程
       t1.start();
    }
    }
    ​
    class MyThread extends Thread {
     //run方法当中的异常不能throws,只能try...catch
    @Override
     public void run(){
       super.run();
    }
    }
    • 启动线程方法:start(),作用是在JVM中新开辟一个栈内存空间,供t1线程使用,开辟完即方法结束,启动成功的线程会由JVM虚拟机自动调用run方法

  • 第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法

    class ThreadTest{
    public static void main(String[] args){
       //创建可运行对象
       MyRunnable r = new MyRunnable();
       //将可运行对象封装成一个线程对象
       Thread t1 = new Thread(r);
       //启动线程
       t1.start();
    }
    }
    ​
    class MyRunnable implements Runnable {
     //run方法当中的异常不能throws,只能try...catch
     @Override
     public void run(){
       super.run();
    }
    }
  • 使用匿名内部类改写第二种方式

    class ThreadTest{
    public static void main(String[] args){
       //创建可运行对象
       MyRunnable r = new MyRunnable();
       //将可运行对象封装成一个线程对象
       Thread t1 = new Thread(new Runnable(){
         //run方法当中的异常不能throws,只能try...catch
         @Override
    public void run(){
       super.run();
    }
    });
       //启动线程
       t1.start();
    }
    }
  • 第三种方式:新建一个“未来任务类”对象,实现Callable接口

    public class ThreadTest01 {
     public static void main(String[] args) throws ParseException, ExecutionException, InterruptedException {
       //创建一个给Callable接口实现类的对象
       FutureTask task = new FutureTask(new Callable() {
         @Override
         public Object call() throws Exception {//call方法相当于有返回值的run方法
           return new Object();
        }
      });
       Thread t = new Thread(task);
       t.start();
       //获取线程t的返回值   
       //这里获取返回值的操作可能会造成当前线程阻塞,因为需要先执行完call方法才能得到返回值
       Object o = task.get();
       System.out.println("线程t返回的是:" + o);
      }
    }

线程的生命周期

 

线程的常用方法

  • 设置线程名称:void setName(String name),通过构造方法也可以设置线程名称

  • 获取线程名称:String getName()

  • 获取当前线程:static Thread currentThread()

  • 使当前线程暂停执行一定时间(进入阻塞状态):static void sleep(long millis)

  • 改变中断状态:void interrupt(),不会中断线程的运行,通过抛出InterruptedException中断异常来改变wait、sleep、join方法的中断状态,使之中断,依靠了java的异常处理机制

  • 终止一个线程的执行

    class ThreadTest{
    public static void main(String[] args){
       //创建可运行对象
       MyRunnable r = new MyRunnable();
       //将可运行对象封装成一个线程对象
       Thread t1 = new Thread(r);
       //启动线程
       t1.start();
       //模拟5秒
       try{
         Thread.sleep(1000*5);
      }catch(InterruptedException e){
         e.printStackTrace();
      }
       //终止线程
       t1.run = false;
    }
    }
    ​
    class MyRunnable implements Runnable {
     //布尔标记
     boolean run = true;
     //run方法当中的异常不能throws,只能try...catch
     @Override
     public void run(){
       for(int i = 0; i < 100; i++){
         if(run){
           System.out.print(Thread.currentThread().getName() + "--->" + i);
           try{
             Thread.sleep(1000);
          }catch(InterruptedException e){
         e.printStackTrace();
       }
        }else{
           //终止线程前进行其他工作
           //save()...
           return;
        }
      }
    }
    }

线程调度概述

  • 线程的两种调度模型

    • 抢占式调度模型:优先级高的线程抢占时间片的概率要高些,如果线程的优先级相同,则随机抢占时间片,Java使用的是抢占式调度模型

    • 分时调度模型(均分式调度模型):所有线程轮流使用CPU的使用权,平均分配每个线程相同的CPU时间片

  • 与线程调度有关的方法

    • 设置线程优先级:final void setPriority(int newPriority)

    • 获取线程优先级:final int getPriority()

      • 默认线程优先级为5,范围为1-10

    • 线程让位方法:static void yield(),暂停当前正在执行的线程对象,并执行其他线程

      • 运行状态--->就绪状态,回到就绪状态会继续开始抢CPU时间片

    • 等待该线程死亡:void join(),调用方法线程与当前线程合并,当前线程受阻塞,待调用方法线程死亡后,当前线程继续执行

      • 当前线程:运行状态--->阻塞状态,待调用方法线程:运行状态--->死亡状态后,当前线程:阻塞状态--->就绪状态

线程安全

  • 何时出现线程安全问题

    • 是否是多线程环境

    • 是否有共享数据

    • 是否有多条语句操作共享数据

  • 如何解决线程安全问题

    • 使用线程同步机制:使之线程排队执行,不能并发

  • 同步、异步编程模型

    • 同步编程模型:线程t1与线程t2,等待对方执行完后才会开始执行,存在等待关系,同步就是排队,效率较低

    • 异步编程模型:线程t1与线程t2互不影响,各自执行各自的,不存在任何关系,异步就是并发,效率较高

  • 线程同步机制的语法

    synchronized(){
    //线程同步代码块
    }
    • synchronized传的形参必须是多线程共享的数据

  • 线程同步机制代码执行原理

    • Java语言中,每一个对象都有“一把锁”,线程t1和线程t2并发,当t1先执行并遇到synchronized时,会释放掉之前占有的CPU时间片,然后自动去锁池(相当于一种阻塞状态)中找“共享对象”的对象锁,找到后并占有这把锁,执行同步代码块中的程序。在t1占有这把锁时,线程t2遇到了synchronized,t2也会释放之前占有的时间片,并去锁池中找这把锁,而这把锁此时被t1占有,所以t2线程对象只能在锁池中等待t1归还这把锁。直到t1执行完同步代码块代码,这把锁才会被释放回锁池中,而t1也会返回到就绪状态去抢夺CPU时间片,t2会占有这把锁执行同步代码块中的程序,这样就达到了线程排队执行

  • Java中三大变量,只有局部变量永远不存在线程安全问题,因为局部变量存在于栈内存中,数据不共享,而实例变量存储在堆内存中,堆内存只有一个,多线程数据共享,静态变量存储在方法区中,方法区只有一个,多线程数据共享

    • 成员变量存储于堆内存,存在线程安全问题

    • 常量不可修改,不存在线程安全问题

  • 同步实例方法

    • 格式:修饰符 synchronized 返回值类型 方法名(方法参数){ }

    • synchronized出现在实例方法上,对象锁一定是this

    • 缺点:1.对象锁只能是this,不灵活 2.整个方法体都需要同步,扩大同步范围,降低效率

  • 同步静态方法

    • 格式:static synchronized 返回值类型 方法名(方法参数){ }

    • synchronized出现在静态方法上,表示找类锁,类锁永远只有一个

  • 线程安全的类

    • StringBuffer

      • 线程安全,可变的字符序列

      • 从JDK5开始,被StringBuilder替代,StringBuilder线程不安全,效率高

    • Vector

      • 线程安全,当Vector的容量需要增加时,Vector会将容量增加100%,而ArrayList只会增加50%

      • Vector方法是同步的,所以效率低,ArrayList是异步的,效率要高

      • 由Collections类下的synchronizedList方法来代替Vector

      //static <T> List<T> synchronizedList(List<T> list) 返回由指定列表支持的同步(线程安全)列表
      List<String> list = Collections.synchronizedList(new ArrayList<String>());
    • Hashtable

      • 该类实现了一个哈希表,它将键映射到值,任何非null对象都可以用作键和值

      • Hashtable方法是同步的,所以效率低,HashMap是异步的,效率高

  • 死锁

    • 当线程t1锁对象o1时,线程t2锁对象o2,如果t1还要去锁对象o2,而t2要去锁对象o1,则两个线程都会进入死锁状态,不出现异常,不出现错误,僵持在那里

      public class ThreadTest01 {
         public static void main(String[] args) {
             Object o1 = new Object();
             Object o2 = new Object();
      ​
             MyThread1 t1 = new MyThread1(o1,o2);
             MyThread2 t2 = new MyThread2(o1,o2);
      ​
             t1.start();
             t2.start();
        }
      }
      class MyThread1 extends Thread{
         Object o1;
         Object o2;
      ​
         public MyThread1(Object o1, Object o2) {
             this.o1 = o1;
             this.o2 = o2;
        }
      ​
         @Override
         public void run() {
             synchronized (o1){
                 try {
                     Thread.sleep(1000);
                } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                }
                 synchronized (o2){
                     System.out.println("我已经找到o2锁了");
                }
            }
        }
      }
      class MyThread2 extends Thread{
         Object o1;
         Object o2;
      ​
         public MyThread2(Object o1, Object o2) {
             this.o1 = o1;
             this.o2 = o2;
        }
      ​
         @Override
         public void run() {
             synchronized (o2){
                 try {
                     Thread.sleep(1000);
                } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                }
                 synchronized (o1){
                     System.out.println("我已经找到o1锁了");
                }
            }
        }
      }

守护线程

  • Java语言中线程分为两大类:用户线程和守护线程(最具有代表性的例子:垃圾回收线程)

  • 守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束(主线程main方法是一个用户线程)

  • 启动线程之前,将线程设置为守护线程

    Thread t = new MyThread();
    //设置线程为守护线程
    t.setDaemon(true);
    t.start();

定时器

  • 作用:间隔特定的时间,执行特定的程序

  • 多种方式实现

    • 可以使用sleep方法睡眠,设置睡眠时间,执行任务(最原始的定时器)

    • java的类库中存在一个定时器类:java.util.Timer,很多高级框架的底层实现原理就是这个类

    void schedule(TimerTask task, Data firstTime, long period);
    //安排指定的任务(task)在指定的时间(firstTime)开始进行重复的固定延迟(period)执行
    • 开发中,使用最多的是Spring框架提供的SpringTask框架,实现定时器的任务

public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer = new Timer();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2022-10-11 13:27:00");
        timer.schedule(new TimerTask(){
          @Override
    			public void run() {
        		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        		String strTime = sdf.format(new Date());
        		System.out.println(strTime + ":成功完成了一次数据备份");
        }, firstTime, 1000 * 10);
    }
}

生产者和消费者模式

  • wait方法和notify方法是Object类的方法,不是线程对象调用,而是线程作用于的对象调用

  • wait()方法

    Object o = new Object();
    o.wait();
    //表示让正在o对象上活动的线程进入等待状态,直到被唤醒为止
  • notify()方法

    Object o = new Object();
    o.notify();
    //表示唤醒正在o对象上等待的线程,继续活动
  • notifyAll()方法

    Object o = new Object();
    o.notifyAll();
    //表示唤醒正在o对象上等待的所有线程,继续活动
  • wait方法和notify方法建立在synchronized线程同步的基础上

  • o.wait()会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁

  • o.notify()会让在o对象上等待的线程唤醒,只会通知,不会释放之前占有的o对象的锁

反射机制

反射概述

  • 作用:通过反射机制可以操作字节码文件(读/写),通过反射机制可以操作代码片段(class文件)

  • 反射机制相关类:

    • 代表整个字节码,代表整个类:java.lang.Class

    • 代表字节码中的方法字节码,代表类中的方法:java.lang.reflect.Method

    • 代表字节码中的构造方法字节码,代表类中的构造方法:java.lang.reflect.Constructor

    • 代表字节码中的属性字节码,代表类中的成员变量(静态变量/实例变量):java.lang.reflect.Field

反射机制实例化对象

  • 操作一个类的字节码,需要先获取到这个类的字节码,获取java.lang.Class实例的方法:

    • 返回与带有给定字符串名的类货接口相关联的Class对象:static Class<?> forName(String className)

      • 这个方法的执行会导致类加载,类加载(类初始化阶段)时,静态代码块执行

    • java.lang.Object类中方法返回此Object的运行时类:Class<?> getClass()

    • java语言中任何一种类型,都有.class属性

  • 通过反射机制,获取Class来实例化对象

    • Class c = Class.forName("java.lang.String");
      //newInstance()方法会调用String这个类的无参构造方法,完成对象的创建,JDK9后已过时
      Object obj = c.newInstance();
  • 利用properties文件来创建类对象,在不改变源代码的基础上,可以做到不同对象的实例化,更加灵活(符合OCP开闭原则:对扩展开放,对修改关闭)

    //通过IO流读取classinfo.properties文件
    FileReader reader = new FileReader("src/java/classinfo.properties");
    //创建属性类对象Map,key和value都是String
    Properties prop = new Properties();
    //加载配置文件
    prop.load(reader);
    //关闭流
    reader.close();
    //通过key获取value
    String className = prop.getProperty("className");
    //通过反射机制实例化对象
    Class c = Class.forName(classname);
    Object obj = c.newInstance();
    • 当.properties文件放在类路径下(src目录下)时,有一个通用文件路径方式替代原文件路径

    /*	
    	以下代码可以拿到一个文件的绝对路径
    	Thread.currentThread()	当前线程对象
    	getContextClassLoader()	 线程对象的方法,可以获取到当前线程的类加载器对象
    	getResource()	 类加载器对象的方法,获取资源,当前线程的类加载器默认从类的跟路径下加载资源
    */
    String path = Thread.currentThread().getContextClassLoader().getResource("java/classinfo.properties").getPath();
    FileReader reader = new FileReader(path);
    //以下代码等同于上方代码,可替代,直接以流的形式直接返回
    InputStream reader = Thread.currentTHread().getContextClassLoader().getResourceAsStream("java/classinfo.properties");
  • java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容

    ResourceBundle bundle = ResourceBundle.getBundle("java/classinfo.properties");
    String className = bundle.getString("className");
    • 使用资源绑定器这种方式时,属性配置文件必须放在类路径下,只能绑定.properties文件

    • 写路径时,路径后面的扩展名不能写

反射属性(类)

Field

  • 获取类中所有public修饰的Field对象

    //获取整个类(自定义Student类)
    Class c = Class.forName("com.java.Student");	
    //获取类中所有public修饰的Field对象
    Field[] fields = c.getFields();
  • 获取类中所有的Field对象

    //获取类中所有public修饰的Field对象
    Field[] fields = c.getDeclareFields();
  • 获取所有Field对象的名字

    //获取所有Field对象的名字
    for(Field field : fields){
      String fieldName = field.getName();
    }
  • 获取所有Field对象的类型

    //获取所有Field对象的类型
    for(Field field : fields){
      //获取Field对象的类型列表
      Class fieldType = field.getType();
      //Class类下的方法:getName(),获取类的全类名
      String fieldName = fieldType.getName();		
      //Class类下的方法:getSimpleName(),获取类的简类名
      String fieldSimpleName = fieldType.getSimpleName();
    }
  • 获取所有Field对象的修饰符

    //获取所有Field对象的修饰符
    for(Field field : fields){
      //获取属性的修饰符列表
      int i = field.getModifiers();
      //将i这个“代号”转换成字符串格式,Modifier类下的toString()方法
      String modifierStr = Modifier.toString(i);
    }
  • 获取一个指定的属性

    //获取类中一个指定的属性(根据属性名指定,自定义Student类中的name属性)
    Field nameField = c.getDeclareFields("name");
  • 给一个实例对象的指定属性赋值(private修饰属性除外)

    //实例化Student类对象
    Object stu = c.newInstance();
    //给stu对象的name属性赋值
    nameField.set(stu,"大帅哥");
  • 给一个实例对象的指定private修饰属性赋值

    //获取类中一个指定的属性(根据属性名指定,自定义Student类中的userID属性,userID属性被private修饰)
    Field userIDField = c.getDeclareFields("userID");
    //打破封装(反射机制缺点:打破封装,不安全)
    userIDField.setAccessible(true);
    //给stu对象的userID属性赋值
    userIDField.set(stu,66);
  • 获取一个实例对象的指定属性值

    //获取stu对象的name属性值
    String nameStu = nameField.get(stu);

反编译Field

StringBuilder sb = new StringBuilder();
Student stu = new Student();
Class studentClass = Class.forName("com.java.Student");
sb.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + "{\t");
Field[] fields = studentClass.getDeclaredFields();
for (Field field : fields) {
  sb.append(Modifier.toString(field.getModifiers()));
  sb.append(" ");
  sb.append(field.getType().getSimpleName());
  sb.append(" ");
  sb.append(field.getName());
  sb.append(";\n");
}
sb.append("}");
System.out.println(sb);

Method

  • 可变长度参数

    public static void method(int... args){ }
    • 可变长度参数只能出现在最后且只能有一个

    • 可变长度参数是一个数组,长度为0—n

  • 获取所有的Method对象

    //获取整个类(自定义Student类)
    Class c = Class.forName("com.java.Student");	
    //获取类中所有的Method对象
    Method[] methods = c.getDeclareMethods();
  • 获取所有Method对象的名字

    //获取所有Method对象的名字
    for(Method method : methods){
      String methodName = method.getName();
    }
  • 获取所有Method对象的返回值类型

    //获取所有Method对象的返回值类型
    for(Method method : methods){
      //获取Field对象的类型列表
      Class methodType = method.getReturnType();
      //Class类下的方法:getName(),获取类的简类名
      String methodSimpleName = methodType.getSimpleName();
    }
  • 获取所有Method对象的修饰符

    //获取所有Method对象的修饰符
    for(Method method : methods){
      //获取方法的修饰符列表
      int i = method.getModifiers();
      //将i这个“代号”转换成字符串格式,Modifier类下的toString()方法
      String modifierStr = Modifier.toString(i);
    }
  • 获取所有Method对象的参数

    //获取所有Method对象的参数
    for(Method method : methods){
      //获取方法参数的类型列表
      Class[] parameterTypes = method.getParameterTypes();
      //获取方法参数的简类名
     	String parameterTypeSimpleName = parameterTypes.getSimpleName();
    }

反射机制调用方法

  • 使用Method类下的Object invoke(Object obj, Object... args)方法

    • Object:返回值类型

    • Object obj:调用对象

    • Object... args:方法参数列表

//使用反射机制来调用一个对象的方法
//获取class对象
Class c = Class.forName("com.java.Student");
//创建stu对象
Object stu = c.newInstance();
//获取指定login方法的Method对象
Method loginMethod = c.getDeclareMethod("login",String.class,String.class);
//调用invoke方法来调用指定login方法
Object retValue = loginMethod.invoke(stu,"大帅哥","123456")

Constructor:同上

反射机制调用构造方法来实例化对象

//使用反射机制调用构造方法来实例化对象
//获取class对象
Class c = Class.forName("com.java.Student");
//调用无参构造方法来创建stu1对象(JDK9后已过时)
Object stu1 = c.newInstance();
//获取无参构造方法
Constructor constructor1 = c.getDeclareConstructor();
//调用无参构造方法来创建stu2对象
Object stu2 = constructor1.newInstance();
//获取有参构造方法
Constructor constructor2 = c.getDeclareConstructor(int.class,String.class,String.class,boolean.class);
//调用有参构造方法来创建stu3对象
Object stu3 = constructor2.newInstance(1,"大帅哥","2000-1-1",true);

父类与接口

  • 获取Class类的父类

    //获取Class对象
    Class c = Class.forName("java.lang.String");
    //获取CLass对象的父类Class对象
    Class superClass = c.getSuperclass();
  • 获取Class类的接口

    //获取Class对象
    Class c = Class.forName("java.lang.String");
    //获取CLass对象的接口Class对象列表
    Class[] interfaces = c.getInterfaces();

类加载器

  • ClassLoader(类加载器):专门负责加载类的命令/工具

  • JDK中自带了三个类加载器

    • Bootstrap classLoader 启动类加载器:rt.jar(父加载器)

    • ExtClassLoader 扩展类加载器:ext / *.jar(母加载器)

    • AppClassLoader 应用类加载器:classpath

  • 双亲委派机制

     

    • 有一段代码 String s = "abc"; ,代码开始执行前,会将所需要类全部加载到JVM中,通过类加载器加载,会去寻找与代码对应的class文件,以上代码会去找String.class文件

      • 首先通过“启动类加载器”加载(启动类加载器中都是JDK最核心的类库)

      • 其次如果通过“启动类加载器”加载不到时,会通过“扩展类加载器”加载

      • 最后如果通过“扩展类加载器”加载不到时,会通过“扩展类加载器”加载(应用类加载器专门加载classpath中的类(src目录下的类))

类加载机制

 

注解

注解概述

  • 注解 Annotation,又叫做注释

  • 注解是一种引用数据类型,编译之后生产.class文件

  • 注解的作用:一种提示标识符

  • 注解使用语法格式:@注解类型名

  • 默认情况下,注解可以出现在任意位置

JDK内置注解

元注解

  • 用来标注“注解类型”的“注解”,称为元注解

  • 常见的元注解

    • Target注解

      • 源代码:

        //元注解
        @Documented
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.ANNOTATION_TYPE)
        public @interface Target {
            /**
             * Returns an array of the kinds of elements an annotation interface
             * can be applied to.
             * @return an array of the kinds of elements an annotation interface
             * can be applied to
             */
            //枚举类型属性
            ElementType[] value();
        }
        • ElementType[] value()源代码:

          //枚举类型
          public enum ElementType {
              TYPE,
              FIELD,
              METHOD,
              PARAMETER,
              CONSTRUCTOR,
              LOCAL_VARIABLE,
              ANNOTATION_TYPE,
              PACKAGE,
              TYPE_PARAMETER,
              TYPE_USE,
              MODULE,
              RECORD_COMPONENT;
          }
          
      • 这个注解用来标注“被标注的注解”可以出现在哪些位置上

      • @Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上

    • Retention注解

      • 源代码:

        //元注解
        @Documented
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.ANNOTATION_TYPE)
        public @interface Retention {
            /**
             * Returns the retention policy.
             * @return the retention policy
             */
          	//枚举类型属性
            RetentionPolicy value();
        }
        • RetentionPolicy value()源代码:

          //枚举类型
          public enum RetentionPolicy {
              SOURCE,
              CLASS,
              RUNTIME
          }
      • 这个注解用来标注“被标注的注解”最终保存在哪里

      • @Retention(RetentionPolicy.SOURCE):表示“被标注的注解”只能保留在java源文件中

      • @Retention(RetentionPolicy.CLASS):表示“被标注的注解”被保存在class文件中

      • @Retention(RetentionPolicy.RUNTIME):表示“被标注的注解”被保存在class文件中,并且可以被反射机制所读取

Override注解

  • 表示一个方法声明打算重写超类中的另一个方法声明,只能注解方法

  • 这个注解是标识性注解,给编译器参考的,跟运行阶段无关

    • 凡是java中的带有这个注解的方法,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错

  • 源代码:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override{ }

Deprecated注解

  • 表示被标注的元素已过时

  • 主要向程序员传达一个信息,告知已过时,存在更好的解决方案

自定义注解

  • 语法格式:修饰符 @interface 注解类型名{ }

  • 注解属性:修饰符 数据类型 属性名() default 默认值;

    • 注解属性,如果没有设置默认值,则在引用注解时,必须为其指定值

    • 如果注解中有且只有一个属性,属性名为value时,引用注解可以省略“value=”

    • 属性的类型可以是:byte、short、int、long、float、double、boolean、char、String、Class、枚举类型,以及以上每一种类型的数组形式

    • 如果属性是一个数组,且数组中只有一个元素,引用注解时,可以省略大括号

反射注解

  • 判断类上面是否有指定的注解

    //获取Class对象
    Class c = Class.forName("java.lang.String");
    //判断类上面是否有指定的注解(方法/属性等其他元素同样的操作)
    if(c.isAnnotationPresent(Target.class)){
      //获取该注解对象
      Target target = (Target) c.getAnnotation(Target.class);
      //获取注解对象的属性
      ElementType[] value = Target.value();
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiangXi响希

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值