前言
介绍Java实现多线程的机制,以及Java的反射机制。同时还包括Java的注解。
目录
一、Java线程概述
启动JVM时就是创建了一个进程,而一个进程又可以创建多个线程。运行Java程序时JVM至少有两个线程,一个是主线程去执行main( )方法,另一个进行垃圾回收。
Java中,进程之间的内存不共享,线程之间栈区的内存不共享,堆区和方法区的内存共享。
二、Java创建线程的方式
1、Java创建线程的三种方式
(1)、编写类继承java.lang.Thread,并重写run( )方法,然后创建对象,调用对象的start( )方法
//创建线程的第一种方式:
//创建一个类继承Thread,重写run()方法,然后创建该对象,调用对象的start()方法启动线程
//注意Thread是java.lang包下的,所以不需要导包
class ThreadTest1 extends Thread{
@Override
public void run() {
System.out.println("新的线程创建了");
}
}
public class Test01 {
public static void main(String[] args) {
//创建线程对象,调用线程的start()方法
ThreadTest1 t = new ThreadTest1();
t.start();
System.out.println("这是主线程");
}
}
运行一次的输出结果是:
这是主线程
新的线程创建了因为是不同的线程,所以这两句话的输出的先后顺序不一定。不过由于线程的创建和调用方法需要时间,因此这个例子中main( )方法中的输出会先进行。可以在输出之前加个循环,循环几十万次,然后再输出。
调用start( )方法启动线程时,会创建一个新的栈空间。不过start( )方法仍然是在当前栈也就是主栈完成的,start( )方法完成,创建好栈空间后,会自动将run( )方法压栈,调用该方法。
(2)、实现java.lang.Runnable接口,也是重写run( )方法,然后创建对象,传入Thread的有参构造方法中,再调用创建的Thread对象的start( )方法
//创建对象的第二种方式:
//实现Runnable接口,重写run()方法。然后创建对象传入Thread的有参构造中,调用Thread对象的start()方法
class ThreadTest2 implements Runnable{
@Override
public void run() {
System.out.println("利用第二种方式创建线程");
}
}
public class Test02 {
public static void main(String[] args) {
ThreadTest2 rt = new ThreadTest2();
Thread t = new Thread(rt);
//也是调用start()方法创建新的栈然后调用run()方法
t.start();
System.out.println("主线程");
}
}
(3)、实现Callable接口,重写call()方法。创建对象传入FutureTask的有参构造中,然后将FutureTask再传入Thread的有参构造,调用start()方法
//创建线程的第三种方式:
//实现Callable接口,重写call()方法。创建对象传入FutureTask的有参构造中,然后将FutureTask再传入Thread的有参构造,调用start()方法
//该接口在java.util包下
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class ThreadTest3 implements Callable {
@Override
public Object call() throws Exception {
System.out.println("要重写call()方法");
return "返回值";
}
}
public class Test03 {
public static void main(String[] args) {
ThreadTest3 ct = new ThreadTest3();
//需要传入到未来任务类FutureTask的有参构造中
FutureTask ft = new FutureTask(ct);
//然后将FutureTask再传入Thread的有参构造,调用start()方法
Thread t = new Thread(ft);
t.start();
Object o = null;
//这种方式创建的线程可以拿到返回值
//注意,是用传入Thread中的未来任务类FutureTask的get()方法拿到的Callable中call()方法的返回值
try {
o = ft.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
System.out.println(o);//返回值
}
}
注意,该类创建线程的方式要导入java.util包。
这种方式创建线程的特点在于可以得到线程的返回值,使用未来任务类FutureTask的get( )方法可以的到Callable中call( )方法的返回值。但是这个方法会造成当前线程阻塞。
2、对线程的一些操作
(1)、设置和获取线程的名字
使用setName( )和getName( )方法修改和得到线程的名字
(2)、获取当前线程对象
使用Thread.currentThread( )方法返回一个线程对象
(3)、让线程睡眠的方法
用Thread.sleep( )方法让当前线程睡眠 ,用实例方法interraput( )方法终止睡眠。注意,终止睡眠只能是终止别的线程,不能终止当前线程。
(4)、终止线程的方法
在线程类中添加一个布尔类型的标记属性
class ThreadTest4 extends Thread{
//为线程终止设置一个标记
private boolean run = true;
@Override
public void run() {
for(int i = 1;i<100;i++){
if(run){
//让当前线程睡眠的方法
try {
Thread.sleep(100);//传入的是毫秒数,该方法有异常
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("支线程第"+i+"次执行");
}
else{
return;
}
}
}
public boolean isRun() {
return run;
}
public void setRun(boolean run) {
this.run = run;
}
}
public class Test04{
public static void main(String[] args) {
ThreadTest4 t1 = new ThreadTest4();
t1.start();
//设置和得到线程名字
System.out.println(t1.getName());//Thread-0
t1.setName("线程2");
System.out.println(t1.getName());//线程2
//获取当前线程
Thread t2 = Thread.currentThread();
System.out.println(t2.getName());//main
//让当前线程睡眠的方法
try {
Thread.sleep(5000);//传入的是毫秒数,该方法有异常
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程睡眠,注意不能终止当前线程的睡眠
t1.interrupt();
//用标记终止线程
t1.setRun(false);//只执行到45次就停止了
}
}
三、线程调度
1、Java线程调度的机制和常用方法
Java的线程调度采用的是抢占式的方式,各线程具有优先级可用线程对象的getPriority( )方法和setPriority( )方法来获取和设置线程执行的优先级。数字越大,代表优先级越高。最高优先级为10,最低为1,默认优先级为5。
静态方法Thread.yield( )方法会暂停当前对象,将当前对象状态由执行转换为就绪,而不是阻塞。并开始其它就绪线程。
实例方法join( )会将调用该方法的线程对象合并到当前线程,会使原来的线程阻塞。
public class Test05 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1;i<100;i++){
System.out.println("支线程第"+i+"次执行");
}
}
});
t.start();
//默认优先级是5
System.out.println(t.getPriority());//5
//将t线程合并到当前线程,该方法有异常
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 1;i<10;i++){
System.out.println("主线程第"+i+"次执行");
}
}
}
2、多线程并发
线程并发时并共享数据时,就一定会涉及到数据安全的问题。解决方法就是使用线程同步机制,将涉及到共享数据的内容排队依次执行。
Java中的线程同步机制使用synchronized关键字,它有三种语法格式:
(1)、线程同步代码块:
格式:synchronized( 共享的对象,共享对象的属性也可以){
对共享数据的处理
}
括号里放共享的数据,共享数据的某个属性值也可以。只要能造成等待,因为每个对象就只有一把锁,找到属性锁也是锁了该对象。
当遇到synchronized同步代码块,就会放弃抢到的时间片去锁池找这把锁,没找到就进入锁池等待,找到了就重新进入就绪状态。
注意,局部变量和常量没有数据安全的问题,只有实例变量和静态变量才有。
(2)、synchronized修饰实例方法
放在返回值类型前面,是对象锁,锁某个对象。但是synchronized修饰方法会扩大锁住的范围,导致效率低,因此不常用。
public synchronized void fun1(){
}
(3)、synchronized修饰静态方法
表示寻找类锁,类锁每个类只有一把, 保护静态变量的安全。
public static synchronized void fun2(){
}
Java中的PV操作:
简单来说,PV操作就是同步过程中,对共享资源和数据状态进行访问和修改的两个通用手段。Java中的PV操作就是每个对象都有的wait( )和notify( )方法,也就是等待和唤醒。
下面用一个例子来说明如何用线程同步机制和wait( )和notify( )方法,来实现对资源的互斥访问:
import java.util.ArrayList;
import java.util.List;
/**
* 模拟实现简单的生产者和消费者模式
* 规定只有一个生产者和一个消费者
* 有一个共享的缓冲区存放资源,最多放一个
*/
/**
* 生产线程
*/
class Producer implements Runnable{
private List sour;
public List getSour() {
return sour;
}
public void setSour(List sour) {
this.sour = sour;
}
public Producer(List sour) {
this.sour = sour;
}
public Producer() {
}
@Override
public void run() {
while(true){
synchronized(sour){
if(sour.size()>0){
try {
sour.wait();//wait()方法会进入等待并释放锁,会结束线程同步代码块
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//进入到这说明缓冲区是空的
sour.add(new Object());
System.out.println("生产者生产");
sour.notify();//唤醒其它共享该缓冲区的线程
}
}
}
}
class Customer implements Runnable{
private List sour;
public List getSour() {
return sour;
}
public void setSour(List sour) {
this.sour = sour;
}
public Customer() {
}
public Customer(List sour) {
this.sour = sour;
}
@Override
public void run() {
while(true){
synchronized(sour){
if(sour.size()==0){
try {
sour.wait();//wait()方法会进入等待并释放锁,会结束线程同步代码块
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//进入到这说明缓冲区是空的
sour.remove(0);
System.out.println("消费者消费");
sour.notify();//唤醒其它共享该缓冲区的线程
}
}
}
}
public class Test06 {
public static void main(String[] args) {
List sour = new ArrayList<>();
Thread pt = new Thread(new Producer(sour));
Thread ct = new Thread(new Customer(sour));
pt.start();
ct.start();
}
}
Java中的死锁:
非常容易出现死锁的原因是,synchronized线程同步代码块的嵌套,例如:
synchronized(o1){ synchronized(o2){ } }当有一个线程先拿到o1的锁,另一个线程先拿到o2的锁时,就会出现死锁。
3、守护线程和定时器
Java中的线程分两种:用户线程和守护线程
我们通常自定义的线程就是用户线程,守护线程是指在后台执行的线程,主要是为用户线程服务。比如垃圾回收器GC。守护线程一般都是死循环,当用户线程结束时,守护线程就结束。自定义守护线程的方法是启动线程前,将线程对象的setDaemon( )方法传true.就可以变成守护线程。
class ProtectedThread implements Runnable{
@Override
public void run() {
while(true){
System.out.println("守护线程守护中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Test07 {
public static void main(String[] args) {
Thread t = new Thread(new ProtectedThread());
//线程启动前调用setDaemon()方法,传入true设置为守护线程
t.setDaemon(true);
t.start();
for(int i = 1;i<31;i++){
System.out.println("这是主线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//用户线程结束,守护线程结束
}
}
定时器:
定时器,顾名思义就是特定的时间执行特定的任务。
sleep( )方法是最原始的定时器,Java中的定时器是java.util包下的Timer类,该类有个方法:public void schedule(TimerTask task, Date firstTime, long period),TimerTask task是个接口,也是要将执行的任务写进run( )方法中, Date firstTime是定时器启动的时间, long period是执行任务间隔的时间,单位是毫秒。
计时器无需用statrt( )开启线程。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
public class Test08 {
public static void main(String[] args) {
Timer timer = new Timer();
//匿名类实现接口
TimerTask task = new TimerTask() {
private int count = 0;
@Override
public void run() {
count++;
System.out.println("特定的时间,执行特定的程序,程序第"+count+"次执行");
}
};
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
Date firstTime = null;
try {
firstTime = format.parse("2024-04-28 01:51");
} catch (ParseException e) {
e.printStackTrace();
}
timer.schedule(task,firstTime,3000);
}
}
四、Java反射机制
1、反射机制主要的四大类:
java.lang.Class:代表整个类的字节码
java.lang.reflect.Field:代表属性的字节码
java.lang.reflect.Constructor:代表构造方法的字节码
java.lang.reflect.Method:代表除构造方法外的方法的字节码
2、获取类的三种方式:
要想的到一个类的属性或者方法,必须首先得到这个类。Java中获取类有三种方式:
(1)、Class.forName(String className )
该静态方法会导致类加载,需要传入带包名的完整类名。该方法有ClassNotFoundException异常。
(2)、任何一个对象都有getClass( )方法,可以得到该对象的类
(3)、任何一种类型都有class属性,也可以得到该类型
/**
* 获取类的三种方式
*/
public class Test9 {
public static void main(String[] args) {
//1、调用Class.forName()方法,传入完整类名获取,有异常
Class c1 = null;
{
try {
c1 = Class.forName("java.lang.Thread");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
System.out.println(c1);//class java.lang.Thread
//2、调用对象的getClass()方法
String s = "abc";
Class c2 = s.getClass();
System.out.println(c2);//class java.lang.String
//3、使用类型的class属性
Class c3 = Integer.class;
System.out.println(c3);//class java.lang.Integer
}
}
通过反射机制创建对象:
//默认创建的是Object类型对象
String s2 =(String) c2.newInstance();
Class对象有两个方法可用来获取类名:
public String getName( ):获取带包名的完整类名
public String getSimpleName( ):获取简单类名
获取类路径文件下的绝对路径:
IDEA下的类路径相当与src下,但是不可以获取Java文件的绝对路径
//获取类路径下的文件的绝对路径
String path = Thread.currentThread().getContextClassLoader().getResource("datatest.txt").getPath();
System.out.println(path);///D:/Javatest/out/production/blogtest2/datatest.txt
//直接得到一个输入流
InputStream ips = Thread.currentThread().getContextClassLoader().getResourceAsStream("datatest.txt");
资源绑定器:
Java的properties文件的读取有两种主要方式:
(1)、用集合Properties的实例方法load( )传入路径
(2)、用资源绑定器ResourceBundle
import java.io.*;
import java.util.*;
/**
* 访问资源配置文件properties的两种主要方式
*/
public class Test10 {
public static void main(String[] args) {
//1、集合Propertoes的load()方法
InputStream fis = null;
{
try {
fis = new FileInputStream("D:\\Javatest\\blogtest\\blogtest2\\test.properties");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
Properties p = new Properties();
try {
p.load(fis);
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println(p.getProperty("name"));//wangwu
//2、资源绑定器:ResourceBundle
//注意不要加后缀properties
//放在类路径下可只写名称
ResourceBundle bundle = ResourceBundle.getBundle("test");
System.out.println(bundle.getString("age"));//20
}
}
3、反射机制获取字段
在通过反射机制获取Class对象后,对字段操作的主要的方法有:
1、public Field[ ] getFields( ):通过得到的Class对象获取该类所有的公开的属性
2、public Field[ ] getDeclaredFields( ):通过得到的Class对象获取该类所有的属性,包括私有
3、public Field getDeclaredField(String name ):通过指定的属性名,返回指定属性对象,该方法有异常
4、public Class getType( ):通过得到的Field字段类得到该属性类型对应的Class类
5、public int getModifiers( ):通过得到的Field字段类得到修饰符列表的修饰符代号,然后通过调用工具类Modifier.toString( )方法,将代号传进去,就可以得到修饰符列表对应的字符串
6、public void set( Object obj,Object value):某个Field调用该方法给该属性赋予某值,第一个参数是Class对应的类创建的对象,第二个参数就是该属性要赋予的值,该方法有异常
7、public Object get(Object obj):某个Field调用该方法,返回Class对应的类创建的对象上的该属性的值
8、public String getName( ):Field调用该方法获取该属性名字
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import com.classtest.SimpleClass;
/**
* 由于包的问题,因此将SimpleClass复制在这,下面就是SimpleClass的复制版
*/
class SimpleClassCopy{
public String name;
private int age;
public SimpleClassCopy() {
}
public SimpleClassCopy(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge(){
return age;
}
private void fun(){
System.out.println("私有方法");
}
}
public class Test01 {
public static void main(String[] args) {
try {
//先通过反射机制获取类对应的Class对象
Class sc = Class.forName("com.classtest.SimpleClass");
//得到该类对应的属性
//getFields()方法得到所有的公开属性
Field[] fs1 = sc.getFields();
for(int i = 0;i<fs1.length;i++){
System.out.println(fs1[i].getName());//Field的getName()方法可获取该属性名
}
//输出结果是一个name
//调用Class的getDeclaredName()方法得到所有的属性,包括私有属性
Field[] fs2 = sc.getDeclaredFields();
for(int i = 0;i<fs2.length;i++){
System.out.println(fs2[i].getName());//Field的getName()方法可获取该属性名
}
//输出结果是name和age
//Field的getType()方法得到该属性的类型对应的Class类
Class type = fs2[1].getType();
System.out.println(type.getSimpleName());//调用Class的getSimpleName()方法获取属性的简单类名
//结果是int
//Field的getModifiers()方法得到该属性的修饰符列表代号
//再调用Modifier.toString()方法得到该属性的修饰符列表
int k = fs2[1].getModifiers();
String modifier = Modifier.toString(k);
System.out.println(modifier);//private
//调用Class的getDeclaredField()方法得到指定的Field对象
Field f = sc.getDeclaredField("age");
//用Field给对应类对象的属性赋值
//Field的set()方法给某个对象的属性赋值,get()方法获取该属性对应的值
//但是注意,只能是对公有属性,如果要操作私有属性,要先打破封装
f.setAccessible(true);//setAccessible(true)打破封装
SimpleClass c =new SimpleClass();;
f.set(c,20);
System.out.println(f.get(c));//20
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
注意: 用Field的set()方法给某个对象的属性赋值,get()方法获取该属性对应的值。但是注意,只能是对公有属性,如果要操作私有属性,要先打破封装。用Field的setAccessible(true)打破封装。
4、反射机制获取方法
在通过反射机制获取Class对象后,可以通过它获取该类对应的方法(不包括构造方法)
1、 public Method[ ] getDeclaredMethods( ):通过Class对象获取对应的所有方法,getMethods( )返回所有公开方法
2、public String getName( ):Method的方法,返回这个Method的名称
3、public Class getReturnType( ):Method的方法,返回这个Method的返回值类型
4、public int getModifiers( ):Method的方法,返回这个Method的修饰符列表对应的代号,之后传入Modifier.toString( )中可得到该修饰符列表的字符串
5、public Class[ ] getParameterTypes( ):Method的方法,返回这个Method的参数类型
6、利用反射机制调用方法:
首先得到方法对象Method:使用Class的getDeclaredMethod(方法名,参数类型Class1,参数类型Class2...),
然后调用Method的invoke( 该Class类创建的对象,实参1,实参2...),私有方法也要先打破封装
(上面可传入不定个数的参数,是因为使用了可变长参数,格式是:类型... 参数名,如int... args,相当于一个数组,且有length属性。
import java.lang.reflect.*;
import com.classtest.SimpleClass;
/**
* 由于包的问题,因此将SimpleClass复制在这,下面就是SimpleClass的复制版
*/
class SimpleClassCopy{
public String name;
private int age;
public SimpleClassCopy() {
}
public SimpleClassCopy(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge(){
return age;
}
private void fun(int i){
System.out.println("私有方法");
}
}
public class Test01 {
public static void main(String[] args) {
try {
//先通过反射机制获取类对应的Class对象
Class sc = Class.forName("com.classtest.SimpleClass");
//得到该类对应的方法
//得到所有的公开方法
Method[] ms1 = sc.getMethods();
for(int i = 0;i<ms1.length;i++){
System.out.println(ms1[i].getName());//获取该方法名
}
//输出结果没有fun
//得到所有的方法,包括私有方法
Method[] ms2 = sc.getDeclaredMethods();
for(int i = 0;i<ms2.length;i++){
System.out.println(ms2[i].getName());
}
//输出结果有fun
//Method的getReturnType()方法得到返回值类型
Class type = ms2[1].getReturnType();
System.out.println(type.getSimpleName());//调用Class的getSimpleName()方法获取属性的简单类名
//结果是void
//Method的getModifiers()方法得到该属性的修饰符列表代号
//再调用Modifier.toString()方法得到该属性的修饰符列表
int k = ms2[1].getModifiers();
String modifier = Modifier.toString(k);
System.out.println(modifier);//private
//利用反射机制调用方法
Method m = sc.getDeclaredMethod("fun",int.class);
//私有方法也要先打破封装
m.setAccessible(true);//setAccessible(true)打破封装
SimpleClass c =new SimpleClass();
m.invoke(c,1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
6、反射机制获取构造方法和继承的基类及接口
1、public Constructor[ ] getDeclaredCounstructors( ):利用Class获取对应的所有构造方法
2、public Class[ ] getParameterTypes( ):Constructor的方法,获取该构造方法所有的参数类型
3、public String getName( ):Constructor的方法,获取该构造方法的名称
4、利用反射机制创建对象:
首先利用getDeclaredConstroctor( )获得指定的构造方法Constroctor,传入要指定构造方法的参数类型。
然后调用Class的newInstance( )方法,传入该Comstructor,就创建了该Class对应的对象
5、public Class getSupeclass( ):用Class的该方法获取该类的基类
6、public Class[ ] getInterfaces( ):用Class的该方法获取所有的所有实现的接口
import java.lang.reflect.*;
//由于包的问题,这里把类和接口都复制一份
interface ACopy{
}
class SimpleClass2Copy implements ACopy {
private String name;
private int id;
public SimpleClass2Copy() {
}
public SimpleClass2Copy(String name, int id) {
this.name = name;
this.id = id;
}
}
public class Test02 {
public static void main(String[] args) {
try {
//首先获取对应的Classfo
Class sc = Class.forName("com.classtest.SimpleClass2");
//获取所有的构造方法
Constructor[] cs = sc.getDeclaredConstructors();
for(int i=0;i<cs.length;i++){
System.out.println(cs[i].getName());//Constructor的getName()方法获取构造方法名
Class[] c = cs[i].getParameterTypes();
for(int j=0;j<c.length;j++){
System.out.println(c[i].getName());
}
}
//调用指定构造方法创建对象
Constructor test = sc.getConstructor(String.class,int.class);
Object obj = sc.newInstance();//Class没有泛型没有指定时,返回对象类型默认Object
//获取该类的基类和实现接口
Class f = sc.getSuperclass();
System.out.println(f.getName());
Class[] is = sc.getInterfaces();
for(int i=0;i<is.length;i++){
System.out.println(is[i].getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
五、Java注解
1、Java注解简介
注解(Annatation),就是写在类或者方法,或者变量之前的,给编译器参考的一类像注释一样的东西,如@Override。在Java中,注解也是一种类,编译后也生成字节码文件。
自定义注解格式:
[修饰符列表] @interface 注解名{
...
}
Java常见注解:
Deprecated:表示不鼓励这样的元素使用,因为可能有点危险,或已过时
Override:最常见的一种,表示重写了该方法
SuppressWarnings:取消指定编译器警告
Java元注解:
主要有两个:
Target:用来指定该注解用在哪儿,可以是类上,可以是方法,可以是属性,也可以是局部变量。
Retention: 用来标志该注解最终保存在哪
@Retention(RetentionPolitcy.SOURCE):表示保存在Java源文件中
@Retention(RetentionPolicy.CLASS):表示保存在class文件中,但不可以被反射机制获取
@Retention(RetentionPolicy.RUNTIME):表示保存在class文件中,且可以被反射机制获取
import java.lang.annotation.*;
//元注解Targe用来指定该注解修饰的地点
//ElementType.TYPE表示可以修饰类
//ElementType.FIELD表示可以修饰属性
//ElementType.METHOD表示可以用来修饰方法
//元注解Retention用来指定该注解最终保存在哪
//RetentionPolicy.SOURCE表示保存在源代码中
//RetentionPolicy.CLASS表示保存在class文件中,但是不可以被反射机制获取
//RetentionPolicy.RUNTIME表示保存在class文件中,可以被反射机制获取
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
2、注解的属性
注解当中可以添加属性,格式是:
类型 属性名( );
注意,定义注解的属性后,使用时必须给属性赋值。
用法:@注解名(属性名=属性值)
但是,如果在定义注解时,使用default给属性赋默认值,就可以不用再给该属性赋值
当属性名称是value,可以直接在括号中赋值,不用再写属性名;给数组赋值时,如果只有一个元素可以不用加{ }
属性的类型可以是基本数据类型、String、枚举和Class以及它们对应的数组。
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
int id() default 1;
int value();
String[] name();
}
//如果在定义注解时,使用default给属性赋默认值,就可以不用再给该属性赋值
//当属性名称是value,可以直接在括号中赋值,不用再写属性名;给数组赋值时,如果只有一个元素可以不用加{ }
@MyAnnotation(value=6,name="ww")
public class Test03 {
public static void main(String[] args) {
}
}
3、反射注解
(1)、查看某个类是否有注解
public boolean isAnnotationPresent(注解的Class):使用某个类的Class查看是否有某个注解
(2)、获得注解类对象
public Annotation getAnnotation(注解的Class):获取某个类的某个注解
public Annotation[ ] getAnnotations( ):获取某个类的所有注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int id() default 1;
int value();
String[] name();
}
import com.classtest.*;
import java.lang.annotation.Annotation;
public class Test03 {
public static void main(String[] args) {
try {
//首先获取SimpleClass的Class对象
Class sc = Class.forName("com.classtest.SimpleClass");
//首先判断是否有某个注解
if(sc.isAnnotationPresent(MyAnnotation.class)){
//获取某个特定的注解
MyAnnotation m =(MyAnnotation) sc.getAnnotation(MyAnnotation.class);
//获取注解的的属性值的格式是:
//注解对象.属性名()
//注意只有注解的Retention指定RUNTIME时,才可以获取到
int id = m.id();
System.out.println(id);//1
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
注意:获取属性值时一定要带上括号,但不是使用方法而是属性。
如有错误,希望能批评指正,谢谢。