1、多线程

1.什么是进程?什么是线程?
进程是一个应用程序(一个进程就是一个软件)
线程一个进程中的执行场景或者执行单元。
一个进程可以启动多个线程。
2.对于java程序来说,当在DOS命令窗口中输入:
java HolleWorld回车之后
会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

3.进程和线程是什么关系?举个例子。
进程可以看做是现实生活中的公司,
线程可以看做是公司当中的某个员工。
注意:进程A和进程B的内存不共享。

那么线程A和线程B呢?
在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执性各自的,这就是多线程并发。
java之所以有多进程机制,目的就是为了提高程序的处理效率。

4.思考一个问题:

使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。

main方法结束只是主线程结束了,主栈空了,其他栈(线程)可能还在压栈弹栈。

区分 java 后台进程 java进程是什么_java


5.分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?

对于单核的CPU电脑来说,真正的多线程并发是没有问题的。

4核CPU表示在同一个时间点上,可以真正的4个进程并发执行。

什么是真正的多线程并发?
t1线程执行t1的,t2线程执行t2的,t1不会影响t2,t2不会影响t1。这叫做真正的多线程并发。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某个时间节点上实际上只能处理一件事,但是由于CPU处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做。
电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度后,人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像一个钢针扎到手上,到最终感觉到疼,这个过程是需要“很长时间”的,在这个期间计算机可能进行亿万次的循环。所以计算机的执行速度很快。

6.java语言中实现线程有两种方式,哪两种方式呢?
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。

//定义线程类
public class MyThread extends  Thread{
	public void run(){}
}

//创建线程对象
MyThread t = new MyThread();
//启动线程
t.start();

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

//定义一个可运行的类
public class MyRunnable implements Runnable{
	public void run(){}
}

//创建线程对象
Thread t = new Thread(new MyRunnable());
//启动线程
t.start();

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。

区分 java 后台进程 java进程是什么_多线程_02

7.关于线程的生命周期?

新建状态、就绪状态、运行状态、阻塞状态、死亡状态

区分 java 后台进程 java进程是什么_区分 java 后台进程_03


关于线程的sleep方法:

static void sleep(long millis)

(1)静态方法:Thread.sleep(1000);

(2)参数是毫秒

(3)作用:让当前线程进入休眠,进入阻塞状态,放弃占用的CPU时间片,让给其他线程使用。

这行代码出现在A线程中,A线程就会进入休眠

这行代码出现在B线程中,B线程就会进入休眠

(4)Thread.sleep()方法,可以做到这种效果:

间隔特定的时间,去执行一段特定的代码,每个多久执行一次。

sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程呢?
注意:这个不是终断线程的执行,是终止线程的睡眠。

重点:run方法当中的异常不能throws,只能try catch
因为run方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。

在java中强行终止线程的执行:stop
这种方式存在很大的缺点:容易丢失数据,因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。

怎么合理的终止一个线程的执行,这种方式很常用:
在Runnable可运行方法内打一个布尔标记,想要什么时候终止t的执行,那么你把标记修改为false就结束了。

Thread.yield();让位,当前线程暂停,回到就绪状态,让给其他线程。
Thread.join();线程合并,t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。

2、线程调度

1.(这部分内容属于了解内容)关于线程的调度
1.1、常见的线程调度模型有哪些?
抢占式调度模型:每个线程的优先级比较高,抢到的CPU时间概率就高一些/多一些。java采用的就是抢占式调度模型。
均分式调度模型:平均分配CPU时间片,每个线程占用的CPU时间片时间长度都一样。平均分配一切平等。有一些编程语言线程调度模型采用的是这种方式。

1.2、java中提供了哪些方法与线程调度有关系?
实例方法:
void setPriority(int newPriority):设置线程的优先级
int getPriority():获取线程的优先级
最低优先级1
默认优先级是5
最高优先级是10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的)

静态方法:
static void yield()让位方法:
暂停当前正在执行的线程对象,并执行其他线程。
yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。

实例方法:
void join()合并线程

Class MyThread extends Thread{
	public void doSome(){
		MyThraed2 t = new MyThread2();
		t.join();//当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以继续
	}
}
Class MyThread2 extends Thread{}

3、关于多线程并发环境下,数据的安全问题。

1.为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放在一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发环境下是否安全的。(****)

2.什么时候数据在多线程并发的环境下会存在安全问题 ??
三个条件:
条件1:多线程并发
条件2:有共享数据
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。

3.怎么解决线程安全问题呢?
在多线程并发的环境下,有数据共享,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)用排队执行解决线程安全问题。这种机制被称为:线程同步机制。专业术语叫做:线程同步,实际上就是线程不能并发,,线程必须排队执行。
怎么解决线程安全问题?
使用线程同步机制。
线程同步就是线程排队,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才能谈效率。数据不安全,就没有效率的事儿。

4.说到线程同步这块,涉及到两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高),异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说t2线程执行的时候,必须等待t1线程执行结束。两个线程发生等待关系,这就是同步编程模型,效率较低。线程排队执行。同步就是排队。

银行账户:不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题。

5.线程同步机制的语法:

synchronized(){
 //线程同步代码块
 }


synchronized后面小括号中传的这个数据是相当关键的,这个数据必须是多线程共享的数据,才能达到多线程排队。
()中写什么??
那要看你想要让哪些线程同步。假设t1,t2,t3,t4,t5有5个线程,你只希望t1,t2,t3排队,t4,t5不需要排队,怎么办?你一定要在()中写一个t1,t2,t3共享对象,而这个共享对象对于t4,t5来说不是共享的。

6.在实例方法上可以使用synchronized吗?可以
synchronized出现在实例方法上只能是this,没得挑,只能是this,不能是其他对象了,所以这个方式不灵活。
另外还有一个缺点:synchronized出现在实例方法上,表示整个方法都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低,所以这种方式不常用。

synchronized使用在实例方法上 有什么优点?
代码写的少了,节俭了。

如果共享对象就是this,并且需要同步的方法块是整个方法体。建议使用这种方式。

4、java中三大变量【重点】

实例变量:在堆中
静态变量:在方法区
局部变量:在栈中
以上三大变量中:局部变量永远不会存在线程安全问题。因为局部变量不共享(一个线程一个栈),局部变量在栈中,所以局部变量永远不会共享。
实例变量在堆中,堆只有一个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享,所以可能存在线程安全问题。

局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。

5、如果使用局部变量的话,建议使用StringBuilder

因为局部变量不存在线程安全问题,选择StringBuilder。
StringBuffer效率较低。

ArrayList是非线程安全的
vector是线程安全的
HashMap HashSet是非线程安全的。
Hashtable是线程安全的

6、总结

synchronized有两种的写法:
第一种:同步代码块
synchronized(线程共享对象){
同步代码块
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this,并且同步代码块是整个方法。
第三种:在静态方法上使用synchroized
表示找类锁,类锁永远只有1把,就算创建100个对象,那类锁也只有1把。
对象锁:1个对象1把锁,100个对象100个锁
类锁:100个对象,也可能只有1把类锁。

7、死锁

区分 java 后台进程 java进程是什么_多线程并发_04


死锁代码要会写,一般面试官要求写,只有会写,才会在以后开发中注意这个事。因为死锁很难调试。

synchronized在开发中最好不要嵌套使用,一不小心可能会导致死锁现象的发生。

public class deadlock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //t1和t2两个线程共享o1,o2
        Thread t1 = new MyClass1(o1, o2);
        Thread t2 = new MyClass2(o1, o2);

    }
}
class MyClass1 extends Thread{
    Object o1;
    Object o2;
    public MyClass1(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}
class  MyClass2 extends Thread{
    Object o1;
    Object o2;
    public MyClass2(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

8、聊一聊,我们在以后的开发中应该怎么解决线程安全的问题?

是一上来就选择线程同步吗?
不会,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择同步机制。
第一种方案:尽量使用局部变量来代替“实例变量和静态变量”
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量中的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享就没有数据安全 问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了,线程同步机制。

9、线程这块还有哪些内容呢?列举一下

1.守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程
其中具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程

守护线程用在什么地方?
每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,守护线程自动退出,没有必要进行数据备份。
启动线程之前,将线程设置守护
t.setDaemon(true);

2.定时器
定时器的作用:间隔特定的时间,执行特定的程序。
比如:每周要进行银行账户的总账操作;每天都要进行数据备份操作。

在实际开发中,每隔多久执行一段特定的程序,这种需求是很常见的。
那么在java在其实采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠的时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前开发中也很少用,因为现在很多高级框架都是支持定时任务。
在实际开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只需要进行简单的配置,就可以完成定时器任务。

3.实现线程的第三种方式:FutureTask方式,实现Callable接口。(JDK8新特性)
这种实现方式的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程的返回值,因为run方法返回void。

思考:系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式。
这种方式的优点是:可以获取线程的执行结果。
这种方式的缺点是:效率较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

4.关于Object类中的wait和notify方法。(生产者和消费者模式)

第一:wait方法不是通过线程对象调用的。

不是这样的:t.wait(),也不是这样的:t.notify()

第二:wait()方法作用?

Object o= new Object();

o.wait();

表示:让正在对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。

o.wait();方法的调用,会让当前线程(正在o对象上活动的线程)进入等待状态。

第三:notify()方法作用?

Object o = new Object();

o.notify();

表示:唤醒正在o对象上等待的线程。

还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程。

区分 java 后台进程 java进程是什么_java_05


区分 java 后台进程 java进程是什么_线程安全_06


4.1、使用wait方法和notify方法实现“生产者和消费者模式”

4.2、什么是生产者和消费者模式?

生产线程负责生产,消费线程负责消费。

生产线程和消费线程要达到均衡。

这是一种特殊的业务需求,在这种特殊的情况下使用wait方法和notify方法。

4.3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

4.4、wait和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,有线程安全问题。

4.5、wait方法作用:o.wait()让正在o对象上获取的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

4.6、notify方法作用:o.notify()让正在o对象上等待线程唤醒,只是通知,不会释放o对象上之前占有的锁。

4.7、模拟这样一个需求:

仓库我们采用List集合。List集合中假设只能存储1个元素,1个元素就表示仓库满了。如果List集合中元素个数是0,表示仓库空了。

保证List集合中永远都是最多存储1个元素。

必须做到这种效果:生产1个消费1个。

10、反射机制(比较简单,只要会查帮助文档就可以)

1.反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。有点类似于黑客。(可以读和改字节码文件)
通过反射机制可以操作代码片段(class文件),可以让程序更加灵活。
2.反射机制的相关类在哪个包下?
java.lang.reflect.*;
3.反射机制相关的类有哪些?
java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码,代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码,代表类中的构造方法。
java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量(静态变量)

java.lang.Class:
public class User{
	//Field
	int no;
	
	//Constructor
	public User{}
	public User(int no){
		this.no=no;
	}
	
	//Method
	public void setNo(int no){
		this.no=no;
	}
	public int getNo(){
		return no;
	}
}

4.要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
三种方式:
第一种:Class c = Class.forName(“完整类名带包名”);
第二种:Class c = 对象.getClass();
第三种:Class c = 任何类型.class;

Class.forName():
(1)静态方法
(2)方法的参数是一个字符串
(3)字符串需要的是一个完整类名
(4)完整类名必须自带包名,java.lang包也不能省略。

5.获取到class能干什么?
通过Class的newInstance()方法来实例化对象
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造方法存在才可以。如果没有这个无参数构造方法会出现“实例化异常”。

6.验证反射机制的灵活性
java代码写一遍,在不改变java源代码的基础上 ,可以做到不同对象的实例化。非常之灵活。(符合OCP开闭原则:对扩展开发,对修改关闭)

后期要学习的高级框架:ssh, ssm, Spring SpringMVC Mybatias Spring Struts Hibernate…
这些高级框架底层实现原理:都采用了反射机制。所以反射机制还是很重要的。
学会反射机制有利于剖析框架底层的源代码。

7.研究一下:Class.forName()发生了什么?
记住重点:如果你只是希望一个类的静态代码块执行,其他代码一律不执行,你可以使用:
Class.forName(“完整类名”);
这个方法的执行导致类加载时,静态代码块执行。
对该方法的返回值不感兴趣,注意是为了使用“类加载”这个动作。
提示:后面JDBC技术的时候我们还需要。

8.关于路径问题:
接下来说一种比较通用的一种路径,即使代码换位置了,这样编写仍然是通用的。
注意:使用以下通用方式的前提是:这个文件必须在类路径下。
什么是类路径?凡是在src下的都是类路径。【记住它】src是类路径
解释:
Thread.currentThread()当前对象
getContextClassLoader()是线程对象的方法,可以获取当前线程的类加载器对象。
getResource()【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。

String path = Thread.currentThread().getContextClassLoader().getResource("从src出发的相对路径").getPath();

这种方式是为了获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响)
但是该文件要求放在类路径下,换句话说:放在src下面。
直接以流的形式返回:

InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsAtream("test.properties");

9.资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容。
使用以下这种方式的时候,属性配置文件xxx.properties必须放在类路径下。

public class ResourceBuddleTest{
	public static void main(String[] args){
		//资源绑定器,只能绑定xxx.properties文件
		//并且这个文件必须在类路径下,文件扩展名也必须是properties
		//在写路径的时候,路径后面的扩展名不能写
		ResourceBuddle bundle = ResourceBuddle.getBundle("classinfo");
		String className = bundle.getString("className");
		System.out.println(className);
	}
}

必须掌握:
怎么通过反射机制访问一个java属性?
给属性赋值set
获取属性的值get

//使用反射机制怎么去访问一个属性的值
Class studentClass = Class.forName("bean.Student");
//obj就是Student对象(底层调用无参数构造方法)
Object obj = studentClass.newInstance();
//获取no属性(根据属性名称来获取属性)
Field noField = studentClass.getDeclaredField("no");
//给obj对象(Student对象)的no属性
noField.set(obj,222);
/*
	虽然使用了反射机制,但是三要求还是缺一不可
	要素1:obj对象
	要素2:no属性
	要素3:赋值222
*/
//读取属性
//两个要素:获取obj对象的no属性的值
System.out.println(noField.get(obj));

//可以访问私有属性吗?
Field nameField = studentClass.getDeclaredField("name");
//打破封装(反射机制的缺点,打破封装可能会给不法分子留下机会!)
//这样设置完之后,在外部也可以访问private
nameField.setAccessible(true);
//给name属性赋值
nameField.set(obj, "jack");
System.out.println(nameField.get(obj));

重点:必须掌握,通过反射机制怎么调用一个对象的方法?
反射机制,让代码很具有通用性,可变化的内容都是写到配置文件中,将来修改配置文件之后,创建对象不一样了,调用的方法也不同了。但是java代码不需要做任何改动,这就是反射机制的魅力。

//使用反射机制来调用一个对象的方法
Class userServiceClass = Class.forName("service.UserService");
//创建对象
Object obj = userServiceClass.newInstance();
//获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
//调用方法,也需要四要素
//反射机制中最重要的一个方法,必须记住
/*
	loginMethod方法
	obj对象
	"admin""123"实参
	retValue返回值
*/
Object retValue = loginMethod.invoke(obj,"admin",,"123");
System.out.println(retValue);

通过反射机制调用构造方法实例化java对象(这个不是重点)

//使用反射机制怎么创建对象了?
Class c = Class.forName("bean.Vip");
//调用无参数构造方法
Object obj = c.newInstance();
System.out.prntln(obj);
//调用有参数构造方法怎么办?
//第一步:先获取到这个有参数的构造方法
Constructor con = c.getDeClearedConstructor(int.class, String.class, String.class, boolean.class);
//第二步:调用构造方法new对象
Object newObj = con.newInstance(111, "jackson", "1990-01-01", true);
System.out.println(newObj);
//获取无参数构造方法
Constructor cons = c.getDeclaredConstructor();
Object obj1 = cons.newInstance();
System.out.println(obj1);

重点:给你 一个类,怎么获取这个类的父类已经实现了哪些接口?

//String举例
Class stringClass = Class.forName("java.lang.String");
//获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass.getName());
//获取String类实现的所有接口(一个类可以实现多个接口)
Class[] interfaces = stringClass.getInterfaces();
for(Class interf : interfaces){
	System.out.println(interf.getName());
}

11、关于JDK中自带的类加载器

(不需要掌握,知道最好)
1.什么是类加载器?
专门负责加载类的命令/工具:ClassLoader

2.JDK中自带了3个类加载器:
启动类加载器:rt.jar
扩展类加载器:ext/*.jar
应用类加载器:classpath

3.假设有这样一段代码:
String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载,注意:启动类加载器专门加载:F:\java\jre\lib\rt.jar,rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,会通过“扩展类加载器”加载,注意:扩展类加载器专门加载:F:\java\jre\lib\ext*.jar。
如果“扩展了加载器”没有加载到,那么会通过“应用类加载器”加载,注意:应用类加载器专门加载:classpath中的jar包(class文件)

4.java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用加载器中加载,直到加载到位置。

12、注解

1.注解,或者叫做 注释类型,英文单词是:Annotation
2.注解Annotation是一种引用数据类型,编译之后也是生成xxx.class文件
3.怎么自定义注解呢?语法格式?
[修饰符列表] @interface 注解类型名{}
4.注解怎么使用?用在什么地方?
第一:注解使用是的语法格式是:@注解类型名
第二:注解可以出现在类上,属性上,方法上,变量上等,注解还可以出现在注解类型上。

5.JDK内置哪些注解呢?
java.lang包下的注释类型
掌握:
Deprecated用@DepreCated注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择。
//deprecated这个注解标注的元素已过时
//这个注解主要是向其他程序员传达一个信息,告知已过时,有更好的解决方案存在。
掌握:
Override 表示一个方法声明打算重写超类中的另一个方法声明。
关于JDK lang包下的Override注解:
public @interface Override{}
标识性注解,给编译器做参考的。
编译器看到方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法。
如果没有重写,报错。
这个注解只是在编译阶段起作用,跟运行阶段无关。
不需要掌握:
SuppressWarnings指示应该在注解元素(以及包含在该注解元素中的所有程序元素)中取消显示指定的编译器警告。

如果一个注解当中有属性,必须给属性赋值,除非该属性使用default指定了默认值
如果一个属性的名字叫做value的话,并且只有一个属性的话,属性名可以省略。
注解当中的属性可以是哪一种类型?
byte,short,int,long,float,double,boolean,char,String,Class,枚举类型,以及以上每一种类型的数组。

13、元注解

1.什么是元注解?
用来标注“注解类型”的注解,称为元注解。
2.常用的元注解有哪些?
Target, Retention
3.关于Target注解:
这是一个元注解,用来标注“注解类型”的注解
这个Target注解用来标注“被标注的注解”可以用在哪些位置上
@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
表示该注解可以出现在:构造方法上,字段上,局部变量上,方法上…类上
4.关于Retention注解:
这是一个“元注解”,用来标注“注解类型”的“注解”
这个Retention注解用来标注“被标注的注解”最终保存在哪里
@Retention(RetentionPolicy.SOURCE):表示该注解只能被保留在java源文件中。
@Retention(RetentionPolicy.Class):表示该注解被保留在class文件中。
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。

Retention的源代码

//元注解
public @interface Retention{
	//属性
	RetentionPolicy value();
}

//RetentionPolicy的源代码
public enum RetentionPolicy{
	SOURCE,
	CLASS,
	RUNTIME
}

@Retention(value = RetentionPolicy.SOURCE)

判断类中是否有某个注解:

//获取这个类
Class c = Class.forName("annotation2.MyAnnotationTest");
//判断这个类是否有MyAnnotation
System.out.println(c.isAnnotationPresent(MyAnnotation.class));
if(c.isAnnotationPresent(MyAnnotation.class)){
	//获取注解对象
	MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
	System.out.println("类上面的标注对象" + MyAnnotation);
	//获取注解上的属性怎么办?和调接口没区别
	String value = myAnnotation.value();
	System.out.println(value);
}

判断方法上是否有某个注解,并获取这个方法中的属性值:

//获取MyAnnotationTest的doSome方法上面的注释信息
Class c = Class.forName("annotation6.MyAnnotationTest");
//获取doSome方法
Method doSomeMethod = c.getDeclearedMethod("doSome");
//判断该方法上是否存在这个注解
if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)){
	MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
	System.out.println(myAnnotation.username());
	System.out.println(myAnnotation.password());
}

判断某个类中是否有Id注解,要求类中必须存在int类型的id属性,如果没有就报异常:

Class userClass = Class.forName("annotation7.User");
//判断类上是否存在Id注解
boolean isOk = false;//给一个默认的标记
if(userClass.isAnnotationPresent(Id.class)){
	//当一个类上有Id注解的时候,要求类中必须存在int类型的id属性
	//如果没有Id属性就报异常
	//获取类的属性
	Field[] fields = userClass.getDeclearedFields();
	for(Fields field : fields){
		if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
			//表示这个类是合法的类,有id注解,则这个类中必须有int类型的id属性
			isOk = true;//表示合法
			break;
		}
	}
}
//判断是否合法
if(!isOk){
	throw new HashNotIdPropertyException("被@Id注解标注的类中必须有int类型的id");
}
//HashNotIdPropertyException自定义异常

14、注解在开发中有什么作用?

需求:假设有这样一则注解,叫做:@Id
这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这个类中必须有一个int类型的id属性,如果这个属性就报异常。如果没有这个属性则正常执行。