多线程

概念

  • 程序:为了完成某种任务的一组指令集合,是一段静态的代码

  • 进程:程序的一次执行过程,是正在运行的程序,资源分配的基本单位

  • 线程:独立调度和分配的基本单位

    • 每个线程有自己独立的 程序计数器 , 栈
    • 多个线程共享一个进程的 方法区 , 堆
  • 并发: 一个cpuu同时执行多个任务(时间片分片)

  • 并行:多个cpu同时执行多个任务

Java中的线程

  • JVM允许程序运行多个线程,通过java.lang.Thread类实现

  • Thread类特性

    • 每个方法都通过特定Thread对象的**run()方法操作,把run()**方法的主体称为线程体
    • 通过start()方法启动这个线程,而非直接调用run()
  • 构造器

    • Thread()创建新的Thread对象
    • Thread(String threadname)创建线程并指定线程实例名
    • Thread(Runnable target)指定创建线程的目标对象,它实现了Runnable接口中的run方法
    • Thread(Runnable target,String name)创建新的Thread对象
  • JDK1.5之前创建新执行线程有两种办法

    • 继承Thread类
    • 实现Runnable接口

继承Thread类

  • 定义子类继承Thread类
  • 子类重写Thread类中的run()方法
  • 创建Thread子类对象,即创建了线程对象
  • **调用线程对象start()方法:启动线程,调用run方法,**如果手动调用run()方法,则为普通方法,没有启动多线程
  • run()方法由JVM调用,什么时候调用,执行过程都由CPU决定
  • 想启动多线程必须调用start()方法
  • 线程执行方法存在于子类的run()
  • 一个线程只能调用一个start()方法,重复调用会抛出异常IllegalThreadStateExceptionnull

继承Thread类

public class Threadtest extends Thread{
    public String name;
    public Threadtest(String name){
        this.name=name;
    }

    @Override
    public void run() {
        System.out.println(this.name+"开始运行");
        for (int i = 0; i < 20; i++) {
            System.out.println(this.name+"正在执行步骤"+i);
            System.out.println(currentThread().getName());
            System.out.println(Thread.currentThread().getName());
        }
        System.out.println(this.name+"运行结束");
    }
}

public class Threaddemo {
    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        Threadtest th1=new Threadtest("线程1");
        th1.start();
        Threadtest th2=new Threadtest("线程2");
        th2.start();
        Threadtest th3=new Threadtest("线程3");
        th3.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("**********");
                }
            }
        }.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName());
            if(i==10){
                try {
                    th1.join();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }
    }
}

创建Thread匿名内部类

new Thread(){
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("***********");
        }
    }
}.start();

实现Runnable接口

  • 定义子类,实现Runnable接口
  • 子类中重写Runnable接口中的run(),start()方法
  • 通过Thread类含参构造器创建线程对象
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中,创建Thread类的对象
  • 调用Thread类的start()方法:启动线程,调用Runnable子类接口的run()方法
  • 代码存在于接口子类的run()
  • 避免了单继承的局限性
  • 多个线程共享同一个接口实现类对象,适合多个相同进程处理同一份资源
public class MyThreadRunnable implements Runnable {
    private String threadName;
    private Thread t;
    public MyThreadRunnable(String threadName) {
        this.threadName = threadName;
        System.out.println(threadName + "线程创建成功");
    }

    public void run() {
        for (int i = 1; i < 6; i++) {
            System.out.println(threadName + "线程正执行步骤: " + i);
        }
        System.out.println(threadName + "线程任务完成");
    }
}


public class TestThread {
    public static void main(String[] args) {
        MyThreadRunnable mtr1 = new MyThreadRunnable("线程1");
        Thread t1=new Thread(mtr1);
        Thread t2=new Thread(mtr1);
        t1.start();
        t2.start();
    }
}
public class MyRunableTicket implements Runnable {
    private static int ticket=100;
    private static int []a=new int[101];

    @Override
    public void run() {
        while (true){
            if(ticket>0){
                ticket--;
                System.out.println(Thread.currentThread().getName()+":卖出第"+(100-ticket)+"张票");
                a[ticket]=1;
            }
            else{
                System.out.println("票已经售空");
                fff();
                break;

            }
        }
    }
    public void fff(){
        for (int i = 0; i < 101; i++) {
            if(a[i]==0)
            {
                System.out.println("第"+i+"票出现问题");
            }
        }
    }
}
//单对象构造多线程,其中ticket不必为static,自然共享数据
public static void ftest(){
    MyRunableTicket t1=new MyRunableTicket();
    Thread tt1=new Thread(t1);
    Thread tt2=new Thread(t1);
    Thread tt3=new Thread(t1);
    tt1.start();
    tt2.start();
    tt3.start();
}

比较两种实现方式

  • 开发中优先选择实现Runnable接口方式
    • 实现方式,没有类的单继承的局限性
    • 实现的方式更适合处理多个线程共享数据的情况
  • 继承方式想要创建一个新对象必须新建一个Thread子类,调用新对象的start()
  • 实现方式不需要新建对象,直接将同一个对象放在Thread类构造器即可

Thread类相关方法

void start():启动线程,并执行对象的run()方法
run():线程在被调度时执行的操作,需要重写Thread类的run()

    
String getName():返回线程的名称
void setName(String name):设置线程名称 
Threadtest th4=new Threadtest();
th4.setName("hhh");
th4.start();
//给主线程命名,在main函数中
Thread.currentThread().setname("主线程");


static Thread currentThread()//返回当前进程,在Thread子类中为this
System.out.println(currentThread().getName());
System.out.println(Thread.currentThread().getName());


static void yield():线程让步    
//暂停当前执行的线程,把执行机会给优先级相同或更高的线程
//若队伍没有同优先级进程,则忽略该方法
if(i%20==0){
    this.yield();
    //Thread.currentThread().yield();
}    
    
join();//当某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到join方法加入的join线程执行完为止,低优先级的进程也可获得执行
if(i==10){
    try {
        th1.join();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}    
   

static void sleep(long millis)//毫秒
//令当前活动线程在指定时间段放弃对cpu的控制,使其他线程有机会被cpu执行,时间到后重排队
//抛出InterruptedException异常
try {
    sleep(1111);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}    


stop()//强制线程生命期结束,已过时

    
boolean isAlive()//返回boolean,判断线程是都还活着    
System.out.println(th1.isAlive());

线程优先级及分类

线程优先级

  • 同优先级先来先服务,采用时间片策略
  • 高优先级可抢占
  • 线程创建的时候继承父进程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级进程之后才被调用
// 线程优先等级
MAX_PRIORITY: 10
MIN_PRIORITY: 1  
NORM_PRIORITY: 5    

//涉及方法
getPriority()//返回线程优先级
setPriority(int newPriority)//改变线程优先级
    
System.out.println(th1.getPriority());
th1.setPriority(7);
System.out.println(th1.getPriority());   

System.out.println(Thread.currentThread().getPriority());
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

线程分类

  • Java线程分为守护线程与用户线程,区别在于JVM何时离开
  • 守护线程是用来服务用户线程的,通过start方法调用thread.setDaemon(true)将用户线程变成守护线程
  • 用户线程执行完毕,JVM中都是守护线程,JVM停止,不等待守护进程
  • Java垃圾回收,系统信息,操作日志等为常见守护线程

线程生命周期

Thread.State定义的线程的几种状态

  • 新建:当一个Thread类或子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备运行的条件,只是未分配到CPU资源
  • 运行:当就绪的线程被调度并获得CPU资源时,进入运行状态,run()方法定义了线程的操作与功能
  • 阻塞:在某种特殊的情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
  • 死亡:线程完成了全部工作或线程被提前强制性地终止或出现异常导致结束
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

新建—>就绪: 调用start()

就绪—>运行:获得cpu执行权

运行—>就绪:失去cpu执行权 yield()

运行—>死亡:执行完run() 调用线程stop() 出现Error/Exception且没处理

运行—>阻塞:sleep() join() 等待同步锁 wait() suspend()挂起,已过时

阻塞—>就绪:sleep()结束 join()结束 获取同步锁 notify()/notifyAll() resume结束挂起,已过时

线程同步

在Java中通过同步机制,解决线程安全问题,操作同步代码时,但操作同步代码时,只能有一个线程参与,其他线程等待,相当于单线程

方式一 同步代码块

  • synchronized(同步监视器){

    ​ //需要被同步的代码,即操作共享数据的代码

    ​ //共享数据:多个线程同时操作的数据

    ​ //同步监视器: 锁 任何一个类的对象都可以充当锁,多个线程必须共用同一把锁 如Object obj=new Object();

    }

public class MyThreadTicket extends Thread{
    private static int ticket=100;//注意需要设置为static
    private static int []a=new int[101];
    private  static Object obj=new Object();//注意需要设置为static

    @Override
    public void run() {
        while (true){
            synchronized (obj){//MyThreadTicket.class也可,MyThreadTicket.class只会加载一次
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+":卖出第"+(100-ticket)+"张票");
                    a[ticket+1]=1;
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    if(ticket==0)
                    {
                        System.out.println("票已经售空");
                        fff();
                        break;
                    }
                }
                else if(ticket<=0){
                    fff();
                    break;
                }
            }
        }
    }
    public void fff(){
        for (int i = 1; i < 101; i++) {
            if(a[i]==0)
            {
                System.out.println("第"+i+"票出现问题");
            }
        }
    }
}

public static void ff(){
    Object obj=new Object();
    MyThreadTicket t1=new MyThreadTicket();
    MyThreadTicket t2=new MyThreadTicket();
    MyThreadTicket t3=new MyThreadTicket();
    t1.start();
    t2.start();
    t3.start();

}
public class MyRunableTicket implements Runnable {
    private int ticket=100;
    private int []a=new int[101];
    Object obj=new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj){//可以为this
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+":卖出第"+(100-ticket)+"张票");
                    a[ticket+1]=1;
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    if(ticket==0)
                    {
                        System.out.println("票已经售空");
                        fff();
                        break;
                    }
                }
                else if(ticket<=0){
                    fff();
                    break;
                }
            }
        }
    }
    public void fff(){
        for (int i = 1; i < 101; i++) {
            if(a[i]==0)
            {
                System.out.println("第"+i+"票出现问题");
            }
        }
    }
}

在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器**(保证唯一)**

方式二 同步方法

  • 如果操作共享数据的代码完整的声明在一个方法中,可以将这个方法声明为同步的
    • 实现Runnable 非静态的同步方法使用默认同步监视器this
    • 继承Thread需要声明为静态方法 静态的同步方法使用同步监视器XXX.class
public class FunctionThread extends Thread{
    private static int ticket=10000;//注意需要设置为static
    private static int []a=new int[10001];
    private  static Object obj=new Object();//注意需要设置为static

    private static synchronized void show(){//声明为静态synchronized
        if(ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+":卖出第"+(10000-ticket)+"张票");
            a[ticket+1]=1;
            try {
                sleep(0);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(ticket==0)
            {
                System.out.println("票已经售空");
                fff();
                return;
            }
        }
        else if(ticket<=0){
            fff();
            return;
        }

    }

    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    public static void fff(){
        for (int i = 1; i < 101; i++) {
            if(a[i]==0)
            {
                System.out.println("第"+i+"票出现问题");
            }
        }
    }
}

//数据量过小,可能显示为单线程
public class MyFunctionThread implements Runnable {
    private int ticket = 10000;
    private int[] a = new int[10001];


    @Override
    public void run() {
        while (true) {
                myshow();
            }
    }
    private synchronized void myshow(){
        if (ticket > 0) {
            --ticket;
            System.out.println(Thread.currentThread().getName() + ":卖出第" + (10000 - ticket) + "张票");
            a[ticket + 1] = 1;
            try {
                sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (ticket == 0) {
                System.out.println("票已经售空");
                fff();
                return;
            }
        }
        else if (ticket <= 0) {
            fff();
            return;
        }
    }
    public void fff() {
        for (int i = 1; i < 101; i++) {
            if (a[i] == 0) {
                System.out.println("第" + i + "票出现问题");
            }
        }
    }
}

懒汉式单例模式实现线程安全

public class Mybank {

    private static Mybank instance=null;
    public static synchronized Mybank getInstance(){//静态的同步方法使用同步监视器XXX.class
        if(instance==null){
            instance=new Mybank();
        }
        return instance;
    }
}

public class Mybank {

    private static Mybank instance=null;
    public static Mybank getInstance(){
        synchronized (Mybank.class) {//效率较慢
            if(instance==null){
                instance=new Mybank();
            }
            return instance;
        }
    }
}

public static Mybank getInstance() {
    if (instance == null) { 		//高效
        synchronized (Mybank.class) {
            if (instance == null) {
                instance = new Mybank();
            }
        }
    }
    return instance;
}

Ctrl + Alt + T 选择环绕方式

方式三 Lock锁

  • jdk5.0后,通过显式定义同步锁对象来实现同步 , 同步锁使用Lock对象充当 , 实现Lock接口控制多个线程对共享资源的访问

  • private ReentrantLock lock=new ReentrantLock();创建锁对象

    • lock.lock();加锁
    • lock.unlock解锁
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

/**
 * @author lrd
 * @date 2022-07-26 上午8:31
 */
class Window implements Runnable{

    private int ticket=10000;

    //创建实例对象
    private ReentrantLock lock=new ReentrantLock();//ReentrantLock(true)公平锁,先进先出,不写默认为false
    private int []a=new int[10001];
    public void fff(){
        for (int i = 1; i < 10001; i++) {
            if(a[i]==0)
            {
                System.out.println("第"+i+"票出现问题");
            }
        }
    }
    @Override
    public void run() {
        while (true){
            try {
                lock.lock();//调用lock
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+":卖出第"+(10000-ticket)+"张票");
                    a[ticket+1]=1;
                    try {
                        sleep(0);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    if(ticket==0)
                    {
                        System.out.println("票已经售空");
                        fff();
                        break;
                    }
                }
                else if(ticket<=0){
                    fff();
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
public class Locktest {
    public static void main(String[] args) {
        Window w=new Window();
        Thread t1=new Thread(w);
        Thread t2=new Thread(w);
        Thread t3=new Thread(w);

        t1.start();
        t2.start();
        t3.start();
    }
}

synchronizedReentrantLock异同

  • 都可以解决线程安全问题
  • lock需要自定义unlock,synchronized在执行完相应代码后自动释放

死锁

多个线程分别占据对方所需的同步资源不放弃,都在等待对方放弃自己需要的同步资源

出现死锁后,不会出现异常与提示,所有线程处于阻塞状态 , 无法继续

如两个线程,主线程先锁a后锁b,副线程先锁b后锁a,可能出现死锁

线程通信

  • 只能出现在同步代码块或同步方法中
  • 调用者必须是同步代码块或同步方法的同步监视器(错误案例: 同步监视器用obj,方法用this调用,需要都为this或obj,即必须为同步监视器)
  • notify() notifyAll() wait()定义在Object类中
notify();//一旦执行此方法,就会唤醒一个(优先级高优先)被wait()的线程
notifyAll();//全部唤醒
wait();//一旦执行线程就进入阻塞,并释放同步监视器 sleep不释放锁
public class Threadcommunication implements Runnable {
    private int num = 1;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();//唤醒一个(优先级高优先)
                //notifyAll();全部唤醒
                if (num > 10000) {
                    break;
                } else {
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    try {
                        sleep(0);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    num++;
                    try {
                        wait();//进入阻塞,阻塞状态释放锁
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}
public class Mytestdemo {
    public static void main(String[] args) {
        Threadcommunication c=new Threadcommunication();
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.setName("线程1");
        t2.setName("线程2");
        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

sleep()与wait()异同

  • 相同点:
    • 一旦执行都可以使线程进入阻塞
  • 不同点:
    • 声明位置不同,sleep()声明在Thread中,wait()声明在Object中
    • 调用范围不同:sleep()可以在任何需要的场景调用,wait()必须出现在同步代码块或同步方法中
    • 如果两个方法都使用在同步代码块或同步方法中,sleep()不释放锁,wait()释放锁

生产者消费者问题

class Clerk{
    private static final int maxn=20;
    private int nownum=0;
    public synchronized void produceProduct() {
        if(nownum<maxn){
            nownum++;
            System.out.println(Thread.currentThread().getName()+":开始生产产品:"+nownum);
            notify();
        }
        else {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized void consumProduct() {
        if(nownum>0){
            System.out.println(Thread.currentThread().getName()+":开始消费产品:"+nownum);
            nownum--;
            notify();
        }
        else {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Producer extends Thread{
    private Clerk clerk;
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":开始生产产品......");
        while (true){
            try {
                sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            clerk.produceProduct();
        }
    }
}
class Consumer extends Thread{
    private Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":开始消费产品......");
        while (true){
            try {
                sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            clerk.consumProduct();
        }
    }
}
public class Producttest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();
        Producer p1=new Producer(clerk);
        p1.setName("生产者1");
        Producer p2=new Producer(clerk);
        p2.setName("生产者2");
        Consumer c1=new Consumer(clerk);
        c1.setName("消费者1");
        Consumer c2=new Consumer(clerk);
        c2.setName("消费者2");
        p1.start();
        p2.start();
        c1.start();
        c2.start();
    }
}

JDK5.0新增线程创建方式

方式一: 实现Callable接口

  • 与Runnable相比更强大

    • 重写call() , 相比run()方法,可以有返回值
    • 可以抛出异常
    • 支持泛型的返回值
    • 借助FutureTask类,比如获取返回结果
  • Future接口

    • 可以对具体的Runnable,Callable任务执行结果进行取消,查询是否完成,获取结果等
    • FutureTask是Future接口的唯一实现类
    • Future同时实现了Runnable,Future接口,可以作为Runnable被线程执行,又可以作为Future得到Callable的值
  • 步骤

    1. 创建一个实现Callable方法的实现类
    2. 实现call方法,将此线程需要执行的操作声明在call中
    3. 创建Callable接口实现类的对象
    4. 将Callable接口实现类的对象传递到FutureTask的构造器中,创建FutureTask对象
    5. FutureTask对象传递到Thread类中,创建Thread类对象,并调用start()
    6. 获取call()方法返回值 FutureTask对象.get() (可选)
package learn.threadnew;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class NumThread implements Callable{
    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 0; i <= 100; i++) {
            if(i%2==0){
                System.out.println(i);
                sum++;
            }
        }
        return sum;
    }
}
public class Mycallable {
    public static void main(String[] args) {
        NumThread n1=new NumThread();
        FutureTask f1=new FutureTask(n1);
        Thread t1=new Thread(f1);
        t1.start();
        try {
            Object sum= f1.get();//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            System.out.println("Sum is "+sum);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

方式二: 使用线程池

  • 提高响应速度
  • 降低资源消耗
  • 便于线程管理

JDK5.0提供了线程池API ExecutorServiceExecutors

  • ExecutorService真正的线程池接口,常见子类ThreadPoolExecutors
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
    • <T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行 Callable
    • void shutdown() :关闭连接池
  • Executors 工具类,线程池的工厂类,用于创建并返回不同类型的线程池
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
class Numcount implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            if(i%2==0)
            System.out.println(Thread.currentThread().getName() +" "+i);
        }
    }
}
class Numcount1 implements Callable {
    
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 300; i++) {
            if(i%2!=0)
                System.out.println(Thread.currentThread().getName() +" "+i);
        }
        return 1111;
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService service= Executors.newFixedThreadPool(10);//提供指定数量的线程池
        service.execute(new Numcount());//执行指定线程的操作,提供实现Runnable或Callable接口实现类的对象
        service.execute(new Numcount());
        Future<Object> f1=service.submit(new Numcount1());
        Future<Object> f2 =service.submit(new Numcount1());
        try {
            Object sum1= f1.get();//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object sum2= f2.get();
            System.out.println(sum2 +"+++"+sum1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        service.shutdown();
    }
}

设置线程池属性

System.out.println(service.getClass());//可知父类java.util.concurrent.ThreadPoolExecutor

ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
service1.setCorePoolSize(14);
service1.setKeepAliveTime();

单元测试

JUnit是一个非常方便的Java语言单元测试框架,多数Java的开发环境都已经集成了JUnit作为单元测试的工具。

注意:该类所处的包中不能有名为Test的类,不然以下操作会失败

1.在需要测试的方法前输入@Test
2.鼠标选中@Test,点击左侧红色灯泡
3.选中 Add’JUnit5.4’ to classpath
4.勾选 Download,点击 ok
之后会自动添加到外部库中
直接运行test部分即可

public class Stringtest {
    @Test
    public void test1(){
        String s1="abc";
        String s2="abc";
        System.out.println(s1==s2);        
    }
}

Java常用类

Scanner类

java.util.Scanner,可以通过 Scanner 类来获取用户的输入。通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前 使用 hasNext 与 hasNextLine 判断是否还有输入的数据 , 如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取

记得关闭资源sc.close();

import java.util.Scanner; 
 
Scanner scan = new Scanner(System.in);
// 从键盘接收数据
// next方式接收字符串
System.out.println("next方式接收:");
// 判断是否还有输入
if (scan.hasNext()) {
    String str1 = scan.next();
    System.out.println("输入的数据为:" + str1);
}
scan.close();


Scanner scan = new Scanner(System.in);
// 从键盘接收数据
// nextLine方式接收字符串
System.out.println("nextLine方式接收:");
// 判断是否还有输入
if (scan.hasNextLine()) {
    String str2 = scan.nextLine();
    System.out.println("输入的数据为:" + str2);
}
scan.close();

next() 与 nextLine() 区别

next():

  • 1、一定要读取到有效字符后才可以结束输入。
  • 2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
  • 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next() 不能得到带有空格的字符串。

nextLine():

  • 1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  • 2、可以获得空白。

正则表达式

语法

java.util.regex 包主要包括以下三个类:

  • Pattern 类:

    pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。

  • Matcher 类:

    Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。

  • PatternSyntaxException:

    PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

import java.util.regex.*;

String str = "12131lrd1234lrd123";
String pattern = "([0-9])([a-z]+)";

Pattern r=Pattern.compile(pattern);
Matcher m=r.matcher(str);

if(m.find()){
    System.out.println(m.group(0));
    System.out.println(m.group(1));
    System.out.println(m.group());
}
boolean isMatch=m.find();
System.out.println("匹配结果"+isMatch);
import java.util.regex.*;

String str = "https://blog.lrdhappy.com/archives/html";
String pattern = "(\\w+):\\/\\/([^/:]+)(:\\d*)?([^# ]*)";

Pattern r=Pattern.compile(pattern);
Matcher m=r.matcher(str);
boolean isMatch=m.find();//获取匹配结果后m.find会自动重置为false,类似迭代器

m=r.matcher(str);// 想重用结果需要再匹配一次
if(m.find()){
    System.out.println(m.group(0));
    System.out.println(m.group(1));
    System.out.println(m.group(2));
}

System.out.println("匹配结果"+isMatch);

捕获组

​ 捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。例如,正则表达式 (dog) 创建了单一分组,组里包含"d",“o”,和"g"。

捕获组是通过从左至右计算其开括号来编号。例如,在表达式((A)(B(C))),有四个这样的组:

  • ((A)(B©))
  • (A)
  • (B©)
  • ©

​ 可以通过调用 matcher 对象的 groupCount 方法来查看表达式有多少个分组。groupCount 方法返回一个 int 值,表示matcher对象当前有多个捕获组。还有一个特殊的组(group(0)),它总是代表整个表达式。该组不包括在 groupCount 的返回值中。

Matcher类方法

索引方法

提供了有用的索引值,精确表明输入字符串中在哪能找到匹配

方法及说明
public int start() 返回以前匹配的初始索引。
public int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
public int end() 返回最后匹配字符之后的偏移量。
public int end(int group) 返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。

查找方法

查找方法用来检查输入字符串并返回一个布尔值,表示是否找到该模式

方法及说明
public boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配。从序列开头开始匹配若能完全匹配则为true,从序列中间开始匹配或无法匹配为false
public boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列。
public boolean find(int start) 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
public boolean matches() 尝试将整个区域与模式匹配。完全匹配则为true

替换方法

替换方法是替换输入字符串里文本的方法

方法及说明
public Matcher appendReplacement(StringBuffer sb, String replacement) 实现非终端添加和替换步骤。终端为首尾两端
public StringBuffer appendTail(StringBuffer sb) 实现终端添加和替换步骤。
public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列。
public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列。
public static String quoteReplacement(String s) 返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的appendReplacement 方法一个字面字符串一样工作。
int count=0;
String str="Cat is cat,dog is dog!";
String regex="(\b)*(cat)|(Cat)(\b)*";
Pattern pattern=Pattern.compile(regex);
Matcher match=pattern.matcher(str);
while (match.find()){
    count++;
    System.out.println("match: "+count);
    System.out.println(match.group());
    System.out.println("start from "+match.start());
    System.out.println("end at "+match.end());
/*
match: 1
Cat
start from 0
end at 3
match: 2
cat
start from 7
end at 10
*/
int count=0;
String str="Cat is cat,dog is dog!";
System.out.println(str);
String regex="(\b)*(cat)|(Cat)(\b)*";
Pattern pattern=Pattern.compile(regex);
Matcher match=pattern.matcher(str);
while (match.find()){
    count++;
    System.out.println("match: "+count);
    System.out.println(match.group());
}
str=match.replaceAll("dog");
System.out.println(str);
import java.util.regex.Matcher;
import java.util.regex.Pattern;

String REGEX = "a*b";
String INPUT = "aabfooaabfooabfoobkkk";
String REPLACE = "-";
Pattern p = Pattern.compile(REGEX);
// 获取 matcher 对象
Matcher m = p.matcher(INPUT);

StringBuffer sb = new StringBuffer();
while(m.find()){
    m.appendReplacement(sb,REPLACE);
}
m.appendTail(sb);
System.out.println(sb.toString());

//-foo-foo-foo-kkk

字符串

String

  • 声明为final类,不可被继承 ,
  • 代表不可变的字符序列 (当对字符串修改时,不能对原有的value赋值)
  • 字符内容存储在一个final char[]字符数组value[]中
  • 实现了Serializable接口 , 支持序列化
  • 实现了Comparable接口 , 可以比较大小
  • 通过字面量的方式(区别于new)给一个字符串赋值,此时字符串值声明在字符串常量池中
  • 字符串常量值中不会存储相同内容的字符串
public class Stringtest {
    @Test
    public void test1(){
        String s1="abc";//字面量定义方式
        String s2="abc";
        System.out.println(s1==s2);//比较地址值,true
        s1="hello";
        System.out.println(s1==s2);//比较地址值,false
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("**************");

        String s3="abc";
        System.out.println(s2==s3);//比较地址值,true
        s3+="def";
        System.out.println(s3);
        System.out.println(s2==s3);//比较地址值,false
        System.out.println("**************");
        String s4="abc";
        String s5=s4.replace('a','m');
        System.out.println(s5);
        s5=s4.replace("a","vvv");
        System.out.println(s5);
    }
}
字面量定义与new定义区别
@Test
public void test2(){
    //此时s1 s2数据声明在方法区的字符串常量池中
    String s1="java";
    String s2="java";

    //此时s3 s4保存的地址值是,数据在堆空间中开辟空间的地址值指向的常量池地址,间接与字面量创建的相同
    String s3=new String("java");
    String s4=new String("java");

    System.out.println(s1==s2);//true
    System.out.println(s3==s4);//false
    System.out.println(s3==s1);//false
}

@Test
public void test3(){
    Person p1=new Person("tom",12);
    Person p2=new Person("tom",12);
    System.out.println(p1.name.equals(p2.name));//true
    System.out.println(p1.name==p2.name);//true
}

String s4=new String(“java”);在内存中创建了两个对象:堆空间中new结构 char[]对应的常量池的数据

@Test
public void test4(){
    String s1="java";
    String s2="jdbc";
    String s3="javajdbc";//字面量

    String s5="java"+"jdbc";//字面量连接
    String s6="java"+s2;//如果有变量则都为堆的操作
    String s7=s1+"jdbc";
    String s4=s1+s2;
    System.out.println(s3==s4);//false
    System.out.println(s3==s5);//true
    System.out.println(s6==s7);//false
    System.out.println(s5==s6);//false
    
    String s8=s5.intern();//使用常量池中已经存在的数据
    System.out.println(s8==s3);//true
    
    final String s9="java";
    String s10=s9+"jdbc";
    System.out.println(s9==s10);//true 常量间拼接
}

intern();//使用常量池中已经存在的数据赋值

String常用方法
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString) 比较两个字符串的大小//字符串排序
String substring(int beginIndex)返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。//左闭右开区间

boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引,找不到返回-1
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索注:indexOf和lastIndexOf方法如果未找到都是返回-1
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) :使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) :使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
str.toCharArray() //String转换为char[]    
String s = new String(char数组, 起始下标, 长度);    
str="hello.world.java"
String str1=str.split("\\.");
for(int i=0;i<str1.length;i++){
    System.out.println(str1[i]);
}

String与其他结构转换

//字符串转成基本数据类型
int i= new Integer("12");//通过构造器实现
Float f=Float.parseFloat("12.1");//通过包装类的parseXxx(String s)静态方法

//基本数据类型转换成字符串
String fstr=String.valueOf(2.34f);//调用字符串重载的valueOf()方法
String inStr=5+"";
String s=t.toString();

String str="123";
int num=Integer.parseInt(str);

char []a=str.toCharArray();
for (int i = 0; i < a.length; i++) {
    System.out.println(a[i]);
}

String str2=new String(a);
System.out.println(str2);

byte []b=str.getBytes();//使用默认字符集转换
byte []c=str.getBytes("gbk");
System.out.println(Arrays.toString(b));//ascii
String str3= new String(b);
System.out.println(str3);
String str4= new String(c,"gbk");
System.out.println(str4);

StringBuffer StringBuilder

  • 默认多创建16个空char,但是不计入str.length()中
  • 自动扩容,扩容为原容 量2倍+2,将原有数组的元素复制到新数组中
  • 支持方法链操作str.append("eee").append("fff").append("ggg");
StringBuffer StringBuilder额外常用方法
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容//左闭右开
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
                                                       
int indexOf(String str)
String substring(int start,int end)//左闭右开区间
int length()
char charAt(int n)//查询
void setCharAt(int n ,char ch) //修改    
//遍历 fori +charAt                                                       
String StringBuffer StringBuilder异同
  • String :不可变的字符序列

  • StringBuffer:可变的字符序列 :线程安全的,效率偏低

  • StringBuilder:可变的字符序列 :线程不安全,效率高,jdk5.0新增

  • 三者底层均用char[]

//最大相同子串 
@Test
public void test3(){
    String str1="asdghaiudhiuawhdabcdfeeee";
    String str2="fhighaiuefhiabcdffeeee";
    StringBuilder allans=new StringBuilder();
    String maxstr=(str1.length()>=str2.length())?str1:str2;
    String minstr=(str1.length()<str2.length())?str1:str2;
    boolean findmax=false;
    int len=minstr.length();
    for (int i = 0; i < len; i++) {
        for (int x = 0,y=len-i; y <=len; x++,y++) {
            String temp=minstr.substring(x,y);
            if (maxstr.contains(temp)){
                allans.append(temp);
                allans.append(",");
                findmax=true;
            }
        }
        if(findmax)
            break;
    }
    String replace = allans.toString().replaceAll(",$", " ");
    System.out.println(replace);
}
@Test
public void test4() {
    String str = null;
    StringBuffer sb = new StringBuffer();
    sb.append(str);

    System.out.println(sb.length());//4

    System.out.println(sb);//"null"源码字符数组存"null"  jdk17:  return (obj == null) ? "null" : obj.toString();

    StringBuffer sb1=new StringBuffer(str);//异常
    System.out.println(sb1);
}

日期时间

JDK8之前的日期时间API测试

System.currentTimeMillis()

//java.lang.System
try {
    long start = System.currentTimeMillis( );//返回当前距1970.1.1 00:00:00之间的毫秒数,时间戳
    System.out.println(new Date( ) + "\n");
    Thread.sleep(5*60*10);
    System.out.println(new Date( ) + "\n");
    long end = System.currentTimeMillis( );
    long diff = end - start;
    System.out.println("Difference is : " + diff);
} catch (Exception e) {
    System.out.println("Got an exception!");
}

Date

//java.util.Date   java.sql.Date

Date date1= new Date();//构造器一
System.out.println(date1.toString());//toString()可省,显示全部时间信息
System.out.println(date1.getTime());//getTime()获取时间戳
Date date2=new Date(1658902572208l);//构造器二记得加l L ,创建指定毫秒数的对象
System.out.println(date2);

java.sql.Date date3 = new java.sql.Date(1658902572208l);
System.out.println(date3);//2022-07-27

SimpleDateFormat

SimpleDateFormat 是一个以语言环境敏感的方式来格式化和解析Date的类。SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。jdk8前

@Test
public void test5(){
    Date d1=new Date();
    System.out.println(d1);//Wed Jul 27 16:17:24 HKT 2022
    SimpleDateFormat s1=new SimpleDateFormat();
    SimpleDateFormat s2=new SimpleDateFormat("yyyy-MM-dd");
    //格式化
    System.out.println(s1.format(d1));//27/7/22 下午4:17
    System.out.println(s2.format(d1));//2022-7-27

    //解析
    String str="2022-7-27";
    try {
        Date d2= s2.parse(str);
        System.out.println(d2);//Wed Jul 27 00:00:00 HKT 2022
    } catch (ParseException e) {
        throw new RuntimeException(e);
    }
}
字母 描述 示例
G 纪元标记 AD
y 四位年份 2001
M 月份 July or 07
d 一个月的日期 10
h A.M./P.M. (1~12)格式小时 12
H 一天中的小时 (0~23) 22
m 分钟数 30
s 秒数 55
S 毫秒数 234
E 星期几 Tuesday
D 一年中的日子 360
F 一个月中第几周的周几 2 (second Wed. in July)
w 一年中第几周 40
W 一个月中第几周 1
a A.M./P.M. 标记 PM
k 一天中的小时(1~24) 24
K A.M./P.M. (0~11)格式小时 10
z 时区 Eastern Standard Time
文字定界符 Delimiter
" 单引号
//SimpleDateFormat转换sql时间
@Test
public void test6(){
    String str="2022-07-27";
    Date d1=new Date();
    SimpleDateFormat s1=new SimpleDateFormat("yyyy-MM-dd");
    try {
        d1=s1.parse(str);
    } catch (ParseException e) {
        throw new RuntimeException(e);
    }
    java.sql.Date sqldate=new java.sql.Date(d1.getTime());
}

Calendar

Calendar为抽象类

实例化 :

  1. 使用 Calendar.getInstance
  2. 调用子类GregorianCalendar构造器 间接调用GregorianCalendar子类

注意:

  • 月份 一月为0 十二月为1
  • 周日为1,周六为7
@Test
public void test7(){
    /*
    实例化:
    方式一: 使用GregorianCalendar子类
    方式二: 调用Calendar.getInstance()间接调用GregorianCalendar子类
     */
    Calendar c1 = Calendar.getInstance();
    //get
    int day=c1.get(Calendar.DAY_OF_MONTH);//这个月的第几天
    System.out.println(day);
    day=c1.get(Calendar.DAY_OF_YEAR);
    System.out.println(day);

    //set
    c1.set(Calendar.DAY_OF_MONTH,13);//修改当前天
    day=c1.get(Calendar.DAY_OF_YEAR);
    System.out.println(day);

    //add
    c1.add(Calendar.DAY_OF_MONTH,-3);//当前天增加多少天,可以为负数
    day=c1.get(Calendar.DAY_OF_YEAR);
    System.out.println(day);

    //getTime
    Date d1=c1.getTime();
    System.out.println(d1);

    //setTime
    Date d2=new Date();
    c1.setTime(d2);
    System.out.println(c1.get(Calendar.DAY_OF_YEAR));
}

JDK8新增API

LocalDateTime

  • LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。

  • LocalTime表示一个时间,而不是日期。

  • LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。

方法 描述
now() / * now(ZoneId zone) 静态方法,根据当前时间创建对象/指定时区的对象
of() 静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear() 获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek() 获得星期几(返回一个 DayOfWeek 枚举值)
getMonth() 获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear() 获得月份(1-12) /获得年份
getHour()/getMinute()/getSecond() 获得当前对象对应的小时、分钟、秒
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours() 向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/ minusDays()/minusYears()/minusHours() 从当前对象减去几月、几周、几天、几年、几小时
withDayOfMonth()/withDayOfYear()/ withMonth()/withYear() 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
@Test
public void test8(){
    //now 获取当前的日期,时间,日期+时间
    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    LocalDateTime localDateTime = LocalDateTime.now();

    System.out.println(localDate);
    System.out.println(localTime);
    System.out.println(localDateTime);

    //of() 设置指定的年月日时分秒 没有偏移量
    localDateTime=localDateTime.of(2022,7,27,18,26,35);
    System.out.println(localDateTime);

    //getXxxx 获取
    System.out.println(localDateTime.getDayOfYear());
    System.out.println(localDateTime.getMonthValue());

    //withXxxx 设置
    LocalDateTime ldt1=localDateTime.withDayOfYear(25);//不可变性,改返回值,不改本身
    System.out.println(ldt1);

    //plusXxx 加
    LocalDateTime ldt2= localDateTime.plusDays(4);//不可变性,改返回值,不改本身
    System.out.println(ldt2);

    //minusXxxx  减
    LocalDateTime ldt3=localDateTime.minusYears(5);
    System.out.println(ldt3);

}

Instant

  • 时间线上的一个瞬时点 1970年开始,以毫秒为单位 精度为纳秒

  • 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01 日08时00分00秒)起至现在的总秒数

方法 描述
now() 静态方法,返回默认UTC时区的Instant类的对象
ofEpochMilli(long epochMilli) 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象
atOffset(ZoneOffset offset) 结合即时的偏移来创建一个 OffsetDateTime
toEpochMilli() 返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳
@Test
public void test9(){
    //类似Date()
    //实例化
    Instant instant = Instant.now();
    System.out.println(instant);//输出格林威治时间


    instant.atOffset(ZoneOffset.ofHours(8));
    System.out.println(instant);//北京时间

    long milli=instant.toEpochMilli();//获取时间戳
    System.out.println(milli);

    Instant instant1=Instant.ofEpochMilli(milli);//通过给定毫秒数实例化
    System.out.println(instant1);
}

DateTimeFormatter

格式化方法:

  • 预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  • 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
方法 描述
ofPattern(String pattern) 静态方法 , 返回一个指定字符串格式的
DateTimeFormatter format(TemporalAccessor t) 格式化一个日期、时间,返回字符串
parse(CharSequence text) 将指定格式的字符序列解析为一个日期、时间
@Test
public void test10(){
    //格式化或解析 日期时间
    //类似SimpleDateFormat
    //方式一:
    DateTimeFormatter dtm1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    LocalDateTime now=LocalDateTime.now();

    //方式二:
    DateTimeFormatter dtm2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
    String format1 = dtm2.format(now);
    System.out.println(format1);

    //方式三:自定义
    DateTimeFormatter dtm3=DateTimeFormatter.ofPattern("yyyy-dd-MM");
    String format2 = dtm3.format(LocalDateTime.now());
    System.out.println(format2);

    //格式化 日期-->字符串
    String format = dtm1.format(now);
    System.out.println(format);

    //解析  字符串-->日期
    TemporalAccessor parse = dtm1.parse(format);

}

其他API

  • ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris
  • ZonedDateTime:一个在ISO-8601日历系统时区的日期时间,如 2007-12- 03T10:15:30+01:00 Europe/Paris。
  • 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如: Asia/Shanghai等
  • Clock:使用时区提供对当前即时、日期和时间的访问的时钟。
  • 持续时间:Duration,用于计算两个“时间”间隔
  • 日期间隔:Period,用于计算两个“日期”间隔
  • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整 到“下一个工作日”等操作。
  • TemporalAdjusters : 该类通过静态方法 (firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用 TemporalAdjuster 的实现。
//ZoneId:类中包含了所有的时区信息
// ZoneId的getAvailableZoneIds():获取所有的ZoneId
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String s : zoneIds) {
System.out.println(s);
}
// ZoneId的of():获取指定时区的时间
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(localDateTime);
//ZonedDateTime:带时区的日期时间
// ZonedDateTime的now():获取本时区的ZonedDateTime对象
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
// ZonedDateTime的now(ZoneId id):获取指定时区的ZonedDateTime对象
ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(zonedDateTime1)
 
    
//Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());


//Period:用于计算两个“日期”间隔,以年、月、日衡量
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);


// TemporalAdjuster:时间校正器
// 获取当前日期的下一个周日是哪天?
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
System.out.println(localDateTime);
// 获取下一个工作日是哪天?
LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate) temporal;
if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return date.plusDays(3);
} else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return date.plusDays(2);
} else {
return date.plusDays(1);
}
}
});
System.out.println("下一个工作日是:" + localDate);

比较器

  • 自然排序:java.lang.Comparable

  • 定制排序:java.util.Comparator

  • String,包装类实现了Comparable接口,进行了从小到大的排列,重写了compareTo()方法,给出了比较两个对象大小的方法

    • String []arr=new String[]{"AA","BB","EE","HH","DD","CC"};
      Arrays.sort(arr);
      System.out.println(Arrays.toString(arr));
      
    • 重写compareTo()规则:

    • 如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。

    • 对于自定义类,如果需要排序,可以让自定义类实现Comparable接口,重写compareTo方法,指明愈合排序

public class Comparetest {
    @Test
    public void test1(){

        Goods arr[]=new Goods[5];
        arr[0]=new Goods("mouse",123);
        arr[1]=new Goods("keyboard",145) ;
        arr[2]=new Goods("computer",234);
        arr[3]=new Goods("display",57);
        arr[4]=new Goods("musicplayer",500);

        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

public class Goods implements Comparable{
    private String name="default";
    private int price=0;
    public Goods(String name,int price){
        this.name=name;
        this.price=price;
    }
    @Override
    public String toString() {
        return "Goods{name="+name+" price="+price+"}";
    }

    @Override
    public int compareTo(Object o) {
        if(o instanceof Goods){
            Goods goods=(Goods) o;
            //方式一
            if(this.price> goods.price)
                return 1;
            else if(this.price<goods.price)
                return -1;
            else
                return this.name.compareTo(goods.name);//同价格比较名字
            //方式二
            //return Integer.compare(this.price,goods.price);
            //return this.name.compareTo(goods.name);

        }
        throw new RuntimeException("传入数据类型不一致");
    }
}
  • 当元素的类型没有实现Comparable接口而又不方便修改代码, 或者实现了Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
    • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。
    • 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort), 从而允许在排序顺序上实现精确控制。
    • 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的 顺序,或者为那些没有自然顺序的对象 collection 提供排序。
@Test
public void test1(){

    Goods arr[]=new Goods[5];
    arr[0]=new Goods("mouse",123);
    arr[1]=new Goods("keyboard",145) ;
    arr[2]=new Goods("computer",234);
    arr[3]=new Goods("display",57);
    arr[4]=new Goods("musicplayer",500);

    //按照价格从大到小排序
    Arrays.sort(arr, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if((o1 instanceof Goods)&(o2 instanceof  Goods)){
                Goods g1=(Goods) o1;
                Goods g2=(Goods) o2;
                return -Integer.compare(g1.getPrice(),g2.getPrice());
            }
            throw new RuntimeException("数据类型不一致");
        }
    });
    System.out.println(Arrays.toString(arr));
}

//使用泛型
Arrays.sort(arr, new Comparator<Goods>() {
    @Override
    public int compare(Goods g1, Goods g2) {
        return -Integer.compare(g1.getPrice(),g2.getPrice());
    }
});

Arrays.sort(arr, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        if((o1 instanceof Goods)&(o2 instanceof  Goods)){
            Goods g1=(Goods) o1;
            Goods g2=(Goods) o2;
            if(g1.getName().equals(g2.getName())){
                return -Integer.compare(g1.getPrice(),g2.getPrice());
            }
            else {
                return g1.getName().compareTo(g2.getName());
            }
        }
        throw new RuntimeException("数据类型不一致");
    }
});    

创建Comparator对象

@Test
public void test4(){

    Goods arr[]=new Goods[5];
    arr[0]=new Goods("mouse",123);
    arr[1]=new Goods("keyboard",145) ;
    arr[2]=new Goods("computer",234);
    arr[3]=new Goods("display",57);
    arr[4]=new Goods("musicplayer",500);

    Comparator com = new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if ((o1 instanceof Goods) & (o2 instanceof Goods)) {
                Goods g1 = (Goods) o1;
                Goods g2 = (Goods) o2;
                if (g1.getName().equals(g2.getName())) {
                    return -Integer.compare(g1.getPrice(), g2.getPrice());
                } else {
                    return g1.getName().compareTo(g2.getName());
                }

            }
            throw new RuntimeException("数据类型不一致");
        }
    };
    Arrays.sort(arr,com);
    Collections.sort(coll,com);
    System.out.println(Arrays.toString(arr));
}

System

  • System类内部包含in、out和err三个成员变量,分别代表标准输入流 (键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。

  • 方法

native long currentTimeMillis()//该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status)//该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
void gc()//该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
String getProperty(String key)//该方法的作用是获得系统中属性名为key的属性对应的值。
    
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);   

Math

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回 值类型一般为double型。

abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度

BigInteger与BigDecimal

​ java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供 所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。 另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。

BigInteger(String val)构造器根据字符串构建BigInteger对象
public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
BigInteger pow(int exponent) :返回其值为 (thisexponent) 的 BigInteger。    

​ BigDecimal类支持不可变的、任意精度的有符号十进制定点数。

 构造器 
public BigDecimal(double val)
public BigDecimal(String val)
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
     
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));

枚举类

  • 类的对象只有有限个,确定的(一组常量)
  • 如果枚举类中只有一个对象,可以看做单例模式的实现方式
  • 定义枚举类
    • JDK5.0之前,自定义枚举类
    • JDK5.0 使用enum关键字 默认继承于java.lang.Enum
//自定义枚举类
public class Myenum {

    public static void main(String[] args) {
        Season autumn = Season.AUTUMN;
        System.out.println(autumn.toString());
    }
}
class Season{
    //1. 声明类的属性private final
    private final String seasonname;
    private final String description;

    //2. 私有化类的构造器,并给对象赋值
    private Season(String seasonname,String description){
        this.seasonname=seasonname;
        this.description=description;

    }
    //3.提供当前枚举类多个对象
    public static final Season SPRING=new Season("春天","春暖花开");
    public static final Season SUMMER=new Season("夏天","夏日炎炎");
    public static final Season AUTUMN=new Season("秋天","秋高气爽");
    public static final Season WINTER=new Season("冬天","冰天雪地");

    //4.获取类的属性
    public String getSeasonname() {
        return seasonname;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return this.getSeasonname()+" "+ this.getDescription();
    }
}
public class Seasonenum {
    public static void main(String[] args) {
        Season1 autumn = Season1.AUTUMN;
        System.out.println(autumn);
        System.out.println(autumn.getSeason()+" "+autumn.getDesc());
    }
}
enum Season1{
    //1.提供当前枚举类对象,多个对象用,分隔
    SPRING("春天","春暖花开"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","冰天雪地");
    
    private final String season;
    private final String desc;
    
    Season1(String season, String desc) {
        this.season=season;
        this.desc=desc;
    }

    public String getSeason() {
        return season;
    }

    public String getDesc() {
        return desc;
    }
}

enum Status{
        FREE,WORK,VOCATION;
}
  • values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  • valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符 串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException。
  • toString():返回当前枚举类对象常量的名称
Season1 autumn = Season1.AUTUMN;
System.out.println(autumn);//toString()
System.out.println(autumn.getSeason()+" "+autumn.getDesc());
Season1[] values = Season1.values();//values;
System.out.println(Arrays.toString(values));
Season1 winter = Season1.valueOf("WINTER");//valueOf
System.out.println(winter);

实现接口

  • 方式一:整体实现方法
  • 方式二:每个对象分别实现方法
//方式一
interface Info{
    void show();
}
enum Season1 implements Info{
    //1.提供当前枚举类对象,多个对象用,分隔
    SPRING("春天","春暖花开"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","冰天雪地");
    private final String season;
    private final String desc;
    Season1(String season, String desc) {
        this.season=season;
        this.desc=desc;
    }

    public String getSeason() {
        return season;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public void show() {
        System.out.println(this.getSeason()+"   "+this.getDesc());
    }
}
interface Info{
    void show();
}
enum Season1 implements Info{
    //1.提供当前枚举类对象,多个对象用,分隔
    SPRING("春天","春暖花开"){
        @Override
        public void show() {
            System.out.println("1111");
        }
    },
    SUMMER("夏天","夏日炎炎") {
        @Override
        public void show() {
            System.out.println();
        }
    },
    AUTUMN("秋天","秋高气爽") {
        @Override
        public void show() {
            System.out.println();
        }
    },
    WINTER("冬天","冰天雪地") {
        @Override
        public void show() {
            System.out.println("@@@@@");
        }
    };
    private final String season;
    private final String desc;
    Season1(String season, String desc) {
        this.season=season;
        this.desc=desc;
    }
    public String getSeason() {
        return season;
    }

    public String getDesc() {
        return desc;
    }
}

注解

​ 注解Annotation其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

​ Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在Annotation的 “name=value” 对中。

​ 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能, 忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如 用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。

生成文档相关注解

@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
其中:
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个

编译时格式检查(JDK内置的三个基本注解)

@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
    
package com.annotation.javadoc;
public class AnnotationTest{
    public static void main(String[] args) {
        @SuppressWarnings("unused")//抑制编译器警告,未使用的变量
        int a = 10;
    }
    @Deprecated
    public void print(){
        System.out.println("过时的方法");
    }
    @Override
    public String toString() {
        return "重写的toString方法()";
    }    
}

跟踪代码依赖性,实现替代配置文件功能

Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
} }
<servlet>
	<servlet-name>LoginServlet</servlet-name>
	<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>LoginServlet</servlet-name>
	<url-pattern>/login</url-pattern>
</servlet-mapping>

自定义注解

参照@SuppressWarnings定义

  • Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其 方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。
  • 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字
  • 如果只有一个参数成员,建议使用参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值” ,如果只有一个参数成员,且名称为value, 可以省略“value=”
  • 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数 据 Annotation
  • 注意:自定义注解必须配上注解的信息处理流程才有意义。
    1. 注解声明为public @interface
    2. 内部定义成员,通常用value表示
    3. 可以指定成员的默认值 用default定义
    4. 如果自定义注解没有成员,则表明是一个标识作用
    5. 如果注解有成员但无默认值,使用时需要指明成员的值
public @interface Myannotation {

    String value() default "Hello";//多个可以指定为数组
}

@Myannotation(value = "hello")

@Myannotation("hello")

元注解

对现有注解进行解释说明的注解

  • Retention

    • 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:

    • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释 默认行为

    • RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行Java程序时, JVM不会保留注解。 这是默认值

    • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java程序时, JVM会保留注释。程序可以通过反射获取该注释。

    • @Retention(RetentionPolicy.RUNTIME)
      public @interface Myannotation {
      
          String value() default "Hello";//多个可以指定为数组
      }
      
  • Target

    • 用于修饰 Annotation 定义, 用于指定被修饰的Annotation能用于修饰哪些程序元素。 @Target 也包含一个名为value的成员变量。

    • 取值 元素
      CONSTRUCTOR 用于描述构造器
      FIELD 用于描述域
      LOCAL_VARIABLE 用于描述局部变量
      METHOD 用于描述方法
      PACKAGE 用于描述包
      PARAMETER 用于描述参数
      TYPE 用于描述类,接口(包括注解类型)或enum声明
  • Documented

    • 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
    • 定义为Documented的注解必须设置Retention值为RUNTIME。
  • Inherited

    • 被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解。

自定义注解时通常会定义Retention Target

JDK8中注解新特性

可重复注解@Repeatable

JDK8前通过数组解决

@Retention(RetentionPolicy.RUNTIME)
public @interface Myannotation {
    String value() default "Hello";//多个可以指定为数组
}

public @interface MyAnnotations {
    Myannotation []value();
}

@MyAnnotations({@Myannotation(value = "hello"),@Myannotation(value = "world")})

JDK8

@Retention(RetentionPolicy.RUNTIME)//Inherited Retention Target等元注解需要相同
@Repeatable(MyAnnotations.class)
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.LOCAL_VARIABLE,ElementType.ANNOTATION_TYPE,ElementType.PACKAGE})//Inherited Retention Target等元注解需要相同
public @interface Myannotation {

    String value() default "Hello";//多个可以指定为数组
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.LOCAL_VARIABLE,ElementType.ANNOTATION_TYPE,ElementType.PACKAGE})
public @interface MyAnnotations {
    Myannotation []value();
}

@Myannotation(value = "hello")
@Myannotation(value = "world")

类型注解

JDK8之后,关于元注解@Target的参数类型ElementType枚举值多了两个: TYPE_PARAMETER,TYPE_USE。

  • ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
  • ElementType.TYPE_USE 表示该注解能写在任何使用类型的任何语句中。
//注解声明为TYPE_PARAMETER之后,可以修饰泛型,帮助反射
class AAA <@Myannotation T>{
    
}


@Target(ElementType.TYPE_USE)//注解声明为ElementType.TYPE_USE之后,任何类型注解都可用
@interface MyAnnotation {
}

@MyAnnotation
public class AnnotationTest<U> {
    @MyAnnotation
    private String name;
    public static void main(String[] args) {
        AnnotationTest<@MyAnnotation String> t = null;
        int a = (@MyAnnotation int) 2L;
        @MyAnnotation
        int b = 10;
    }
    public static <@MyAnnotation T> void method(T t) {
    }
    public static void test(@MyAnnotation String arg) throws @MyAnnotation Exception {
    }
}