java语法(二)正则表达式、线程并发、Juit单元测试、反射机制、注解、动态代理、XML解析、JVM

正则表达式

正则表达式验证网站

在这里插入图片描述

限定符

1、? :表示前边这个字符可以出现0次或者1次。例如下边/used? 既可以匹配use也可以匹配used
在这里插入图片描述
2、*:匹配0个或者多个字符,*号代表前边这个字符可以出现0次或者多次。例如/ab*c可以匹配ac、abc、abbbbc

在这里插入图片描述
3、+:与*号不同的是,+需要前面这个字符出现1次或者多次。当使用+号时,/ab+c就无法匹配ac了,因为b至少的出现一次。

在这里插入图片描述
4、如果想要实现对字符出现次数更加精确的限定,可以使用花括号+数字,指定。
例如:

  • /ab{2,}c:b出现2次以及以上
  • /ab{2,6}c:b出现[2,6]次
  • /ab{6}c:b出现6次

5、如果要匹配多个字符的重复出现次数,可以使用小括号()将字符扩起来。

例如/(ab)+表示前面这个ab出现1次或者多次。
在这里插入图片描述

运算符与字符类

1、或运算符|
在这里插入图片描述
2、字符类。
[abc]+:由abc组成的字符
在这里插入图片描述

-:按照字母顺序的可以使用-
在这里插入图片描述

在这里插入图片描述

3、非运算符^:除了列出字符之外的。
例如:/[^0-9]+:非数字字符。
在这里插入图片描述

元字符

\d:digit,数字字符,等同于[0-9]
\D:非数字字符
\w:word,单词字符,英文、数字及其下划线。
\W:非单词字符
\s:space,空表字符,包含tab和换行符。
\S:非空白字符
.:代表任意字符,但不包含换行符
^:匹配行首的字符:例如/^a,只匹配行首的a
$:匹配行尾的字符:例如/a$,只匹配行尾的a

贪婪匹配与懒惰匹配

默认的匹配规则,是贪婪匹配,即尽可能地匹配多个字符。

在这里插入图片描述
使用?切换贪婪匹配为懒惰匹配。
在这里插入图片描述

案例

1、匹配16进制颜色rgb值。

  • 首先都以#开头。
  • 16进制,因此,字符从0-9,a-f组成。
  • 字符出现次数为6次,使用花括号,指定6次。
  • 使用单词结尾字符\b表示结束。

在这里插入图片描述
2、ipv4地址匹配。
三个点,与四个数字,数字范围为[0,255],可以把点与数字放一块,分为三个数字+点,和一个数字。

数字范围分情况讨论,3位数,2位数,1位数。
在这里插入图片描述

线程并发

多线程

多线程的创建

  1. 方法一:继承Thread类,创建步骤:
    • 定义子类MyThread继承Thread,重写run()方法
    • 创建MyThread类对象
    • 调用线程对象的start()方法启动线程(启动后执行的是run()方法)

这种创建方式因为已经继承了Thread类,无法继承其他类,不利于拓展。

  1. 方法二:声明一个实现Runnable接口的类。
    • 定义定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。
    • 创建MyRunnable对象
    • 把MyRunnable对象交给Thread处理
    • 调用Thread对象的start方法启动

方法二只实现接口,可以继续继承类和实现接口,扩展性更强。但缺点是编程多一层包装(runnable对象还需要再传给thread构造thread对象)
在这里插入图片描述
方法一实现:

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}
 Thread t1 = new MyThread();
 t1.start();

方法二实现:

Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
});
for (int i = 0; i < 10; i++) {
    System.out.println("主线程:" + i);
}

当然,可以用lambda表达式简写。

 Thread t = new Thread(() -> {
     for (int i = 0; i < 10; i++) {
         System.out.println( "子线程" + i);
     }
 });
 for (int i = 0; i < 10; i++) {
     System.out.println("主线程:" + i);
 }
  1. 方式3
    前两种创建方式都存在一个问题:
    • 他们重写的run()方法均不能直接返回结果
    • 不适合需要返回线程执行结果的业务场景

jdk5利用Callable、FutureTask接口实现上述功能。

创建步骤:

  • 定义类实现Callable接口,重写call方法,封装要做的事情。
  • 用FutureTask把Callable对象封装成线程任务对象。
  • 把线程任务对象交给Thread处理
  • 调用Thread的start方法启动线程,执行任务。
  • 线程执行完毕后,通过FutureTask的get()方法去获取任务执行的结果。

方法三的实现:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    public static void main(String[] args) {
        Callable<String> call1 = new MyCallable(100000);
        FutureTask<String> f1 = new FutureTask<>(call1);
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(100000);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            // 如果f1没有执行完毕,那么get这里会等待,直至完成
            String r1 =  f1.get();
            System.out.println("第一个结果:" + r1);
        } catch (Exception e) {
            e.printStackTrace();
        }
            // 如果f2没有执行完毕,那么get这里会等待,直至完成
        try {
            String r2 =  f2.get();
            System.out.println("第二个结果:" + r2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    private int n;
    public MyCallable (int n) {
        this.n = n;
    }
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= n; i++) {
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

Thread常用API

在这里插入图片描述
在这里插入图片描述

线程同步与通信

当多线程访问共享资源时,可能出现线程安全问题。解决线程安全问题的方法有线程同步和线程通信。

线程同步:单例模式的三种写法

线程同步的思想是对共享资源加锁,将多个线程实现先后依次访问共享资源,这样就解决了线程安全问题。

线程同步的方式:

  • 同步代码块
  • 同步方法
  • Lock锁
同步代码块
  • 作用:把出现线程安全问题的核心代码给上锁。
  • 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

synchronized(同步锁对象){
操作共享资源的代码
}

锁对象规范:

  • 建议使用共享资源作为锁对象
  • 对于实例方法建议使用this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步代码块实现的懒汉单例模式:
核心:私有化构造函数,静态化单例成员,在获取单例的静态方法中,如果检测到实例未创建,使用synchronized构建同步代码块,因为是静态方法,所以使用类的字节码(类名.class)对象作为synchronized的锁对象。

class SingleInstance {
    private SingleInstance() {
    }
    private static SingleInstance singleInstance;
    public static SingleInstance getInstance() {
        if (singleInstance == null) {
            synchronized (SingleInstance.class) {
                if (singleInstance == null) {
                    singleInstance = new SingleInstance();
                }
            }
        }
        return singleInstance;
    }
}
同步方法

在这里插入图片描述
基于同步方法实现的懒汉单例:
核心:私有化构造、静态化单例对象
获取实例的静态方法加synchronized修饰。

class Single {
    private Single(){
        
    }
    private static Single single;
    public static synchronized Single GetInstance() {
        if (single == null) {
            single = new Single();
        }
        return single;
    }
}

同步方法的底层其实是隐式锁对象,只是锁的范围是整个方法代码,如果方法是实例方法,同步方法默认用this作为锁对象。

从性能上讲,同步代码块锁的范围更小,性能更高。

java中静态内部类不会自动初始化,只有在调用静态内部类的方法时才会加载静态内部类。

利用这种静态内部类,我们可以实现一个比使用同步代码块和同步方法,性能上更加优秀的懒汉单例。

核心:私有化构造函数,构建静态内部类,类中成员初始化时调用构造函数,使用static决定它的全局性 ,使用final,决定它只会初始化一次。在获取单例的方法中,返回内部类的成员。

class SingleByInner{
    private SingleByInner() {
		
    }
    static class Inner {
        private static final SingleByInner INSTANCE = new SingleByInner();
    }
    public static SingleByInner getInstance() {
        return Inner.INSTANCE;
    }
}
Lock锁
  • 为了更加清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock,更加灵活,方便。
  • Lock实现提供比使用synchronize方法和语句可以获得更广泛的锁定操作。
  • Lock是接口不能直接实例化,这里采用它的实现类ReetrantLock来构建Lock锁对象。

线程通信

什么是线程通信、如何实现?

  • 所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现。
  • 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。

线程通信常见模型

  • 生产者与消费者模式:生产者线程负责生成数据,消费者线程负责消费数据。
  • 要求:生产者生产完数据后,唤醒消费者,然后等待自己;消费者消费完数据后,唤醒生产者,然后等待自己。

线程通信的关键是object类的等待和唤醒方法在这里插入图片描述上述所有方法应该使用当前同步锁对象进行调用。

this.notifyAll();  // 唤醒所有线程
this.wait();  // 锁对象,让当前线程进入等待。

经典面试题:sleep()和wait()的区别是什么?
1、来自不同类:sleep()来着Thread,wait()来自Object。
2、sleep()不会释放锁,wait()会释放锁。
3、使用范围不同:wait,notify和notifyAll只能在同步控制方法或者同步代码块中使用,sleep可以在任何地方使用。

线程池

线程池是一个可以复用线程的技术。因为创建新线程的开销很大,使用线程池可以重复利用线程,可提高程序性能。
在这里插入图片描述

获取线程池对象

JDK5.0起提供了代表线程池的接口:ExecutorService
如何获取线程池对象?

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象,这种方法最为灵活。

  • 方式二:使用Executor(线程池的工具类)调用方法返回不同特点的线程池对象。

ThreadPoolExecutor

在这里插入图片描述
临时线程什么时候创建?

  • 新任务提交时,核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
    什么时候会开始拒绝任务?
  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来时才会开始拒绝任务。

在这里插入图片描述

线程池处理runnable任务

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        Runnable target = () -> {
            try {
                Thread.sleep(100000);
                System.out.println(Thread.currentThread().getName() + "输出");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        // 三个核心线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 五个在等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 等待队列满了,新加两个临时线程
        pool.execute(target);
        pool.execute(target);
        
        // 拒绝任务,抛出异常
        pool.execute(target);

    }

线程池处理callable任务

在这里插入图片描述
execute 执行runnable类任务,submit处理callable任务。

Executors

Executors是jdk内置的线程池工具类,可以返回下边四种类型的线程池:

在这里插入图片描述

定时器

Timer

Timer和TimerTask是用于在后台线程中调度任务的java util类。简单地说,TimerTask是要执行的任务,Timer是调度器。

1.自定义一个类继承于TimerTask的类,并重写其run()方法即可。

2.可以采取匿名类的形式,直接重写其run()方法。

TimeTask有一抽象方法run(),其作用就是用来放我们处理的逻辑任务。

Timer有一schedule()方法,重载参数和另外两个方法如下表:
可以看到schedule()可以接收Data参数,指定某个时刻执行,也可接收long类型的毫秒参数,延迟多少毫秒执行。
在这里插入图片描述

    public static void usingTimer() {
        Timer timer = new Timer("Timer");
        long delay = 2000L;
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Task performed on: " + new Date() + "n" +
                        "Thread's name: " + Thread.currentThread().getName());
            }
        }, delay, 3*1000);  // 每3秒执行一次定时任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(new Date());  
            }
        },delay); // 只执行一次
    }
调度可重复执行任务

有两种方式:

  • 固定延迟:schedule()方法还有两个重载,每个重载都使用一个额外的period参数来表示以毫秒为单位的周期性。
  • 固定频率:有两个scheduleAtFixedRate()方法,它们的周期也是以毫秒为单位的。

注意:
1、如果一个任务的执行时间超过了执行周期,那么无论我们使用固定延迟还是固定速率,它都会延迟整个执行链。
2、更有甚者如果定时调度器中一个定时任务出现异常,会同时影响其他任务的进行。
这就是使用Timer定时器的缺点所在,Timer本身是单线程的,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。可能因为其中的某个任务异常,影响后续任务。

取消定时器

1、调用Timer.cancel()方法。
2、在TimerTask的run()方法中使用cancle取消。

ScheduleExecutorService

ScheduleExecutorService内部是线程池来处理定时任务,如果某个任务失败,那么这个线程会挂掉,不会影响其他线程的任务。
在这里插入图片描述

并发与并行

  • 正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的。

并发的理解:

  • CPU同时处理线程的数量有限。
  • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们感觉这些线程在同时执行,这就是并发。

并行的理解:

  • 在同一个时刻,多个线程同时执行

线程的生命周期

线程的生命周期主要有以下6种状态:

  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed Waiting(计时等待)
  • Terminated(被终止)

在这里插入图片描述
New表示线程被创建,尚未启动的状态,即new Thread(),新建一个线程但是还没有执行start()方法。当执行start()后,就进入了Runnable状态。

Runnable

Java中的Runnable状态对应操作系统线程状态中的两种状态分别是RunningReady,也就是说Java中处于Runnable状态的线程可能是正在执行,也可能是在等待CPU分配资源。

阻塞状态

Blocked(被阻塞)、Waiting(等待)、TimedWaiting(计时等待)这三种状态统称为阻塞状态。

Blocked:没获得锁被阻塞

从Runnable状态进入到Blocked状态只有一种途径,那就是当进入到synchronized代码块中时,未能获得相应的锁。

相应的,当Blocked状态的线程获取到锁时,此线程就会进入到Runnable状态中参与CPUT资源的抢夺。

Waiting等待状态

waiting状态有三种情况:

  • 当线程中调用了没有设置timeout参数的object.wait()方法。
  • 当线程调用了没有设置timeout参数的thread.join()方法。
  • 当线程调用了LockSupport.park()方法。

Blocked与Waiting的区别

  • Blocked是在等待其他线程释放锁
  • Waiting则是在等待某个条件,比如join的线程执行完毕,或者notify()/notifyAll().

Time Waiting计时等待状态

Time Waiting状态与Waiting状态非常相似,其中的区别就在于是否有时间的限制,在Timed Waiting 状态时会等待超时,之后由系统唤醒,或者也可以提前被通知唤醒如notify

进程状态之间的转换

WaitTimeWaiting状态被notify或者notify_all后,如果拿到锁,那么进入Runnable,如果没有拿到锁,则进入BlockedRunnable执行不带时间参数的wait进入waiting状态,执行带时间参数的wait或者sleep后进入TimeWaiting状态。

sleep和wait的区别

  • 来自不同的对象:sleep来自Thread,wait来自Object
  • 使用场景不同:wait只能在同步代码块或者同步方法中使用,执行wait前应该使用notify或者notify_all唤醒其他线程。执行wait后会释放自己的锁。sleep则没有使用场景的限制,sleep执行后并不会释放锁。

Junit单元测试框架

编写Junit测试的步骤:

  1. 一般而言IDEA整合了Junit框架,不需要另外导入,但是如果IDEA没有整合,需要手工导入Junit的两个Jar包。
  2. 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
  3. 在测试方法上使用@Test注解:标注该方法是一个测试方法。
  4. 在测试方法中完成被测试方法的预期正确性测试。
  5. 选中测试方法,选择“Junit运行”,如果测试良好则是绿色,如果测试失败,则是红色。

例子:
对两个业务代码的正确性编写单元测试:
业务用例:

public class UserService {
    public String loginName(String loginName, String passWard) {
        if ("admin".equals(loginName) && "123456".equals(passWard)) {
            return "登录成功";
        } else {
            return "用户或者密码有问题";
        }
    }

    public void selectName() {
        System.out.println(10 / 0);
    }
}

测试案例:

public class TestUserService {
    /**测试方法
     * 1、必须是公开的,无参数,无返回值的方法
     * 2、测试方法必须使用@Test注解标记
     */

    @Test
    public void testLoginName() {
        UserService userService = new UserService();
        String rs = userService.loginName("admin", "123456");
        Assert.assertEquals("用户或者密码有问题", "登录成功", rs);
    }

    @Test
    public void testSelectNames() {
        UserService userService = new UserService();
        userService.selectName();
    }
}

测试结果:
在这里插入图片描述

反射

反射的概述:

  • 反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类的全部成分。
    • 在运行时,可以直接得到这个类的构造对象:Constructor
    • 在运行时,可以直接得到这个类的成员变量对象:Field
    • 在运行时,可以直接得到这个类的成员方法对象:Method
  • 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制

反射的关键:
反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。
HelloWorld.java -> javac -> HelloWorld.class Class c = HelloWorld.class;

反射获取Class类的全部成分

获取Class类对象

一、反射第一步:获取Class对象,有下边三种方法:

在这里插入图片描述

  • 在源代码阶段:Class类中的静态方法:forName(String className)
  • 在Class对象阶段,通过类名.class
  • 在Runtime运行时阶段,通过对象.getClass()

回顾我们在编写同步代码块实现的单例对象时。便是通过了上述的方法二,获取到类对象来作为锁的互斥资源:
synchronized (SingleInstance.class){}

public static SingleInstance getInstance() {
   if (singleInstance == null) {
       synchronized (SingleInstance.class) {
           if (singleInstance == null) {
               singleInstance = new SingleInstance();
           }
       }
   }

下边我们编写一个Student类,尝试用上边的三种方法获取到Class对象:

        // 1、Class.forName(全限名) 全限名:包名 + 类名
        Class C1 = Class.forName("com.heima2.model.Student");
        System.out.println(C1);

        // 2、通过 类名.class
        Class C2 = Student.class;
        System.out.println(C2);
        
        // 3、通过 对象.getClass
        
        Student s = new Student();
        Class C3 = s.getClass();
        System.out.println(C3);

三个打印结果相同,因为编辑器只会编译出一份类对象。

获取构造器(Constructor)、成员(Field)、成员函数(Method)

Constuctor

通过class对象的.getConstructors()获取全部构造器
获取每个构造器,打印它的名字+构造器的参数个数。

        Student s = new Student();
        Class C3 = s.getClass();

        Constructor[] constructors = C3.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c.getName() + "-->" + c.getParameterCount());
        }

使用.getConstructor(T ...)方法按照参数获取指定的构造器并构造对象:

Constructor con1 = C3.getConstructor();  // 获取无参构造器
Student sbycon1 = (Student) con1.newInstance();

Constructor con2 = C3.getConstructor(String.class, String.class);  // 获取有参构造器
Student sbycon2 = (Student) con2.newInstance("张三", "01217");

注意:上边两种方法无法获取到声明为private的构造器

如果要拿到private的构造器,需要使用.getDeclaredConstructor()方法,拿到私有构造器后并不能直接构建对象,需要打开权限:setAccessible(true);

        Constructor con3 = C3.getDeclaredConstructor(Integer.class);
        con3.setAccessible(true);
        Student sbycon3 = (Student) con3.newInstance(12);

Field

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
Method
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

反射的作用

  • 反射可以绕过编译阶段为集合添加数据,此时集合的泛型将不能产生约束,可以为集合添加任意类型的元素。
  • 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段时,类型都是ArrayList,泛型相当于被擦除了。

在这里插入图片描述

  • 反射的另外一个重要作用是,做通用框架的底层实现基础。

在这里插入图片描述
为了实现上述功能,我们需要利用反射构造一个工具类:
这个工具类的主要思路就是接收一个对象,利用反射获取对象的成员名字和成员的值,使用打印流将内容保存到指定位置。

public class MybatisUtils {
    public static void save(Object obj) {
        try(PrintStream ps = new PrintStream(new FileOutputStream("F:\\java\\heima2\\src\\model\\data.txt", true))) {
            Class c = obj.getClass();
            ps.println("=============" + c.getSimpleName() + "=============");
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                String name = field.getName();
                field.setAccessible(true);
                String value = field.get(obj) + "";
                ps.println(name + "=" + value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试代码:

public class mainActivity {
    public static void main(String[] args) throws Exception {
        Student a = new Student("张三", "01217");
        MybatisUtils.save(a);
        Teacher t = new Teacher("李四", 24);
        MybatisUtils.save(t);
    }
}

结果:
在这里插入图片描述

注解

java注解是jdk5引入的一种注释机制,java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注,然后进行特殊处理。

自定义注解

格式:

public @interface 注解名称 {
	public 属性类型 属性名 () default 默认值;
}

特殊属性

  • value属性,如果只有一个value属性的情况下,使用value属性可以省略value的名称
  • 但是如果有多个属性,且属性没有默认值,那么value属性是不能省略的
public @interface Book {
    String value();
}
@Book("fa")

如果处理value属性,其他属性有默认值,这种写法也是正确的。

public @interface Book {
    String value();
    String name() default "aaa";
}
@Book("fa")

元注解

元注解:就是注解的注解

元注解有两个:
@Target:约束自定义注解只能在哪些地方使用
@Retention:申明注解的生命周期
在这里插入图片描述

注解的解析

注解通常需要解析,判断是否存在注解,存在就解析出内容。

与注解解析相关的接口:

  • Annotation:注解的顶级接口,注解都是Annotation类型的对象
  • AnnotatedElement:该接口定义了与注解解析相关的解析方法
  • 所有的类成分Class、Method、Field、Constructor都实现了AnnotatedElement接口,他们都拥有解析注解的功能。

在这里插入图片描述

注解解析案例

在这里插入图片描述
首先,我们编写一个名为Book注解,添加元注解:@Target({ElementType.TYPE, ElementType.METHOD})
使得可以在类型和方法上添加注解
@Retention(RetentionPolicy.RUNTIME)
使得注解一直存在

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    String name();
    String[] authors();
    double price();
}

然后我们编写一个类BookStore,给他加上Book注解:

@Book(name = "《java虚拟机》", authors = {"a","b"}, price = 9.9)
public class BookStore {
    @Book(name = "《C++prime》", authors = {"c","b"}, price = 19.9)
    public void test() {

    }
}

最后我们写个测试案例:获取并解析类上和方法上的这两个注解:

public static void main(String[] args) throws NoSuchMethodException {
        // a、先得到类对象
        Class c = BookStore.class;
        // 判断是否存在注解,如果存在,取出注解内容
        if (c.isAnnotationPresent(Book.class)) {
            Book bookFromClass = (Book) c.getDeclaredAnnotation(Book.class);
            System.out.println("类:---" + c.getSimpleName() + "---的注解");
            System.out.println(bookFromClass.name());
            System.out.println(Arrays.toString(bookFromClass.authors()));
            System.out.println(bookFromClass.price());
        }
        // 由对象获取到方法,再获取方法的注解
        Method m = c.getDeclaredMethod("test");
        if (m.isAnnotationPresent(Book.class)) {
            System.out.println("方法:---" + m.getName() + "---的注解");
            Book bookFromMethod = (Book) m.getDeclaredAnnotation(Book.class);
            System.out.println(bookFromMethod.name());
            System.out.println(Arrays.toString(bookFromMethod.authors()));
            System.out.println(bookFromMethod.price());
        }
    }

在这里插入图片描述

模拟Junit的注解案例

在这里插入图片描述
首先自定义一个注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}

在测试类中,我们给某些方法定义注解,在main函数中,我们通过反射获取到类的方法,检查是否有注解,如果有注解就,通过方法的invoke(Object,...)方法执行该方法。

public class AnnotationDemo {
    @MyTest
    public void test1() {
        System.out.println("===test1===");
    }

    public void test2() {
        System.out.println("===test2===");
    }
    @MyTest
    public void test3() {
        System.out.println("===test3===");
    }

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        AnnotationDemo a = new AnnotationDemo();
        Class c = a.getClass();
        Method[] ms = c.getDeclaredMethods();
        for (Method m : ms) {
            if (m.isAnnotationPresent(MyTest.class)) {
                m.invoke(a);
            }
        }
    }
}

动态代理

代理:某些场景下,对象会找一个代理对象,来辅助自己完成一些工作。
在这里插入图片描述
在java中实现动态代理的步骤:

  • 必须存在接口
  • 被代理对象实现接口
  • 使用Proxy类提供的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)

通过代理调用方法的执行流程:

  • 先走向代理
  • 代理可以为方法额外做一些辅助工作
  • 开始正在触发对象方法的执行
  • 回到代理中,由代理负责返回结果给方法的调用者。

下边是一个例子:
首先有一个接口Skill

public interface Skill {
    void dance();
    void sing();
}

被代理对象Star实现了上述接口:

public class Star implements Skill{
    private String name;
    public Star(String name) {
        this.name = name;
    }
    @Override
    public void dance() {
        System.out.println(name + "跳舞");
    }
    @Override
    public void sing() {
        System.out.println(name + "唱歌");
    }
}

创建代理类,构建一个创建代理的静态方法,该方法返回的是接口对象Skill,入参是我们的被代理对象Star

方法体就是利用Proxy类提供的newProxyInstance代理实例方法,这个方法实际上是通过反射的方法,获取到了被代理类实现的方法,并且在方法体中invoke它。

public class StarAgentProxy {
    public static Skill getProxy(Star s) {
        return (Skill) Proxy.newProxyInstance(s.getClass().getClassLoader(), s.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("收首付款。。。");
                Object rs =  method.invoke(s);
                System.out.println("收尾款。。。");
                return rs;
            }
        });
    }
}

动态代理的案例

在这里插入图片描述

第一步:编写接口

public interface UserService {
    void login(String userName, String passWard);
    String deleteUser();
    String selectUsers();
}

第二步:编写被代理类实现上述接口:

package proxy2;

public class UserServiceImpl implements UserService{
    @Override
    public void login(String userName, String passWard) {
        String rs = "登录名和密码错误";
        if ("admin".equals(userName) && "123456".equals(passWard)) {
            rs = "登录成功";
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String deleteUser() {
        try {
            System.out.println("正在删除用户.....");
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "deleteUser";
    }

    @Override
    public String selectUsers() {
        try {
            System.out.println("正在搜索用户.....");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        return "selectUsers";
    }
}

第三步,编写代理类,返回接口,在代理中调用接口的方法,并对方法进行耗时分析。

    public static UserService getProxy(UserService obj) {
        return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.currentTimeMillis();
                Object rs = method.invoke(obj, args);
                long endTime = System.currentTimeMillis();
                System.out.println(method.getName() + "耗时" + (endTime - startTime) / 1000.0 + "s");
                return rs;
            }
        });
    }

第四步:
编写测试用例,构建被代理类对象,交由代理调用方法;

    public static void main(String[] args) throws Exception {
        UserServiceImpl userService = new UserServiceImpl();
        UserService u = ProxyUtil.getProxy(userService);
        u.login("admin", "123456");
        u.deleteUser();
        u.selectUsers();
    }

动态代理的优点

  • 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用。
  • 简化了编程工作,提高了开发效率,同时提高了软件系统的可拓展性。
  • 可以为被代理对象的所有方法做代理。
  • 非常的灵活,支持接口类型的实现类对象做代理,也可以直接为接口本身做代理。

XML

  • XML是可拓展标记语言(eXtensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。

XML的几个特点和使用场景

  • 一是纯文本,默认使用UTF-8编码,二是可嵌套
  • 如果把XML内容存为文件,那么它就是一个XML文件
  • XML的使用场景,XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息。

XML类似html语言,由可以嵌套的开闭标签构成:

<?xml version="1.0" encoding="UTF-8" ?>
<student>
    <name>张三</name>
    <gender></gender>
    <info>
        <age>24</age>
        <address>武汉</address>
    </info>>
</student>

在这里插入图片描述
为了避免与标记符’>’ '<'冲突,XML语言对于特殊字符有另外的表述方法,当然也可以声明一个区域,CDATA,在这个区域的大于小于符号就不会与标记符号产生冲突。在IDEA中可以直接输入CD然后tab

在这里插入图片描述

JVM

语言发展历史

  1. C/C++
  • 手动管理堆内存:malloc/free / new / delete
  • 可能导致的问题:申请了内存,忘记释放 --> memory leak 内存泄露 --> 内存泄露越来越多时,可用的堆空间越来越小,很有可能进一步导致某次申请空间时,没办法分配 即out of memory 内存溢出
  • 由于编程时需要考虑底层的内存分配,导致开发效率极低。
  1. Java Python Go
  • 方便内存管理的语言
  • 自带GC - Garbage Collector(垃圾回收器),程序运行时自动启动垃圾回收线程,垃圾回收器负责回收在堆中申请的内存
  • 自带垃圾收集器使得程序员业务开发时,不再需要关注底层的内存问题,大大降低了程序员门槛,提高了开发的效率
  • 由于需要额外的垃圾回收线程,执行效率偏低,此外这些语言也没有解决空指针问题,需要程序中额外判断。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值