文章目录
1. 类对象概述
-
类的对象:基于某个类 new创建出来的对象,也称实例对象。
-
类对象:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法)。
每个类加载到内存后都对应一个class对象,每个类有且仅有一个class对象。
-
在程序运行的时候可以通过一个JVM参数来显示类的加载过程:
-verbose:class
。以eclipse为例演示:
public class Demo0 { public static void main(String[] args) { //Person类,封装了name和age两个属性 Person person=new Person(); person.setName("tang"); person.setAge(21); System.out.println(person.getName()+person.getAge()+"岁。"); } }
找到eclipse菜单的“运行/run”或者直接鼠标右击编辑区,选择“运行方式/run as”,在“运行配置/run configurations”中找到“自变量/Arguments”,在“VM 自变量/VM arguments”中输入上述参数,最后“运行/run”,你可以在控制台看到如下结果:
[Opened E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar] [Loaded java.lang.Object from E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar] [Loaded java.io.Serializable from E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar] ...... tang21岁。 [Loaded java.lang.Shutdown from E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar] [Loaded java.lang.Shutdown$Lock from E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar]
控制台显示了程序运行时类的加载过程。
2. 获取类对象
-
通过类的对象,获取类对象:
Person person=new Person(); Class personClass=person.getClass();
-
通过类名获取类对象:
Class class=类名.class;
-
通过静态方法获取类对象:
Class oneClass=Class.forName("包名.类名");
通过代码演示这三种方式:
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
getClazz();
}
public static void getClazz() throws ClassNotFoundException {
Person tang=new Person();
//1.使用类的对象获取类对象。
Class<?> class1=tang.getClass();
System.out.println(class1.toString());
System.out.println(class1.hashCode());//打印类对象的hashcode
//2.使用类名.class属性
Class<?> class2=Person.class;
System.out.println(class2.hashCode());
//3.使用class的静态方法【推荐】
Class<?> class3=Class.forName("cn.lee.demo.Person");
System.out.println(class3.hashCode());
}
}
输出结果如下:
class io.gitee.lazydog036.demo.Person
366712642
366712642
366712642
这三个类的hashcode一样,说明这三个类对象是同一个,而且每个类加载到内存中只对应一个类对象。
在获取类对象时推荐使用第三种方式,前两种看上去很简单但是代码的依赖性太强,假如编译的时候没有Person这个类就会报错 ,而第三种的参数是一个字符串,编译的时候可以没有Person类,只要运行的时候有就行。
3. 反射常见操作
常用方法:
-
public String getName()
获取类对象所代表的类的名字。
-
public Package getPackage()
获取类对象所代表的包的名字。
-
public Class<? super T> getSuperclass()
获取类对象所代表的类的父类。
-
public Class<?>[] getInterfaces()
获取类对象所代表的类或接口实现的接口。
-
public Constructors<?>[] getConstructors()
获取类对象所代表的类的所有公共构造方法。
-
public T newInstance()
创建类对象所代表的类一个新实例。
-
public Method[] getMethods()
获取类对象所代表的类或接口的公共成员方法。
-
public Field[] getFields()
获取类对象所代表的类或接口的公共访问字段。
3.1 方法演示(1)
- 使用反射获取类的名字、包名、父类、接口。
public static void reflectOp1() throws ClassNotFoundException {
Class<?> class1=Class.forName("io.gitee.lazydog036.demo.Person");
//1.获取类的名字
System.out.println(class1.getName());
//2.获取包的名字
System.out.println(class1.getPackage());
//3.获取类的父类
System.out.println(class1.getSuperclass());
//4.获取类的接口
System.out.println(Arrays.toString(class1.getInterfaces()));
}
第四个方法返回的是一个数组,如果该对象表示了一个不实现任何接口的类,则返回一个数组长度为0的数组。运行结果如下:
io.gitee.lazydog036.demo.Person
package io.gitee.lazydog036.demo
class java.lang.Object
[]
3.2 方法演示(2)
- 使用反射获取类的构造方法,并创建对象。
//Person类,拥有两个构造方法
class Person{
private int age;
private String name;
public Person(){
System.out.println("无参构造方法被执行了。");
}
public Person(int age, String name){
this.age = age;
this.name = name;
System.out.println("带参构造方法被执行了。");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}
public static void reflectOp2() throws Exception{
Class<?> class1=Class.forName("io.gitee.lazydog036.demo.Person");
//1.获取类的构造方法Constructor
Constructor<?>[] cnos=class1.getConstructors();
for (Constructor<?> constructor : cnos) {
System.out.println("构造方法:"+constructor.toString());
}
//2.获取类的无参构造方法
Constructor<?> con1=class1.getConstructor();
Person tang=(Person) con1.newInstance();
System.out.println(tang.toString());
//2.1 简便方式:类对象.newInstance();
Person he=(Person) class1.newInstance();
System.out.println(he.toString());
//3.获取类的带参构造方法
Constructor<?> con2=class1.getConstructor(int.class,String.class);
Person yu=(Person) con2.newInstance(21,"yu");
System.out.println(yu.toString());
}
getConstructor返回的是构造方法是一个Constructor对象,参数为构造方法声明的形参类型。使用带参构造方法对象创建实例时需要传递相应的参数,如代码中的第3个演示所示。运行结果如下:
构造方法:public io.gitee.lazydog036.demo.Person()
构造方法:public io.gitee.lazydog036.demo.Person(int,java.lang.String)
无参构造方法被执行了。
Person [age=0, name=null]
无参构造方法被执行了。
Person [age=0, name=null]
带参构造方法被执行了。
Person [age=21, name=yu]
3.3 方法演示(3)
- 使用反射获取类中的方法,并调用方法。
3.3.1 演示getMethods方法
在Person类中添加一些方法:
class Person{
public void study() {
System.out.println(this.getName()+"正在学习。");
}
private void privateMethod() {
System.out.println("这是一个私有方法。");
}
protected void protectedMethod() {
System.out.println("这是一个受保护的方法。");
}
void defaultMethod() {
System.out.println("这是一个默认方法。");
}
}
public static void reflectOp3() throws Exception{
Class<?> class1=Class.forName("io.gitee.lazydog036.demo.Person");
//2.获取方法 Method对象
//2.1 getMethods()
Method[] methods=class1.getMethods();
for (Method method : methods) {
System.out.println(method.toString());
}
}
该方法返回的是一个Method类型的数组。控制台输出如下,为了便于观察结果,我把结果在这里分成了两部分:
public java.lang.String io.gitee.lazydog036.demo.Person.toString()
public java.lang.String io.gitee.lazydog036.demo.Person.getName()
public void io.gitee.lazydog036.demo.Person.setName(java.lang.String)
public void io.gitee.lazydog036.demo.Person.setAge(int)
public void io.gitee.lazydog036.demo.Person.study()
public int io.gitee.lazydog036.demo.Person.getAge()
--------------------------------------
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
可以观察到该方法返回的Method数组中不仅包含Person类的方法,也包含了继承自Object的方法;但是在Person的方法中,没有非public方法,也就是说,该方法获取了类中和继承自父类的公开方法。
3.3.2 演示getDeclaredMethods方法
要获取类中的私有方法,可以使用以下方法:
//2.2 getDeclaredMethods()
Method[] methods=class1.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.toString());
}
打印结果如下:
public java.lang.String io.gitee.lazydog036.demo.Person.toString()
public java.lang.String io.gitee.lazydog036.demo.Person.getName()
public void io.gitee.lazydog036.demo.Person.setName(java.lang.String)
private void io.gitee.lazydog036.demo.Person.privateMethod()
public void io.gitee.lazydog036.demo.Person.setAge(int)
void io.gitee.lazydog036.demo.Person.defaultMethod()
protected void io.gitee.lazydog036.demo.Person.protectedMethod()
public int io.gitee.lazydog036.demo.Person.getAge()
public void io.gitee.lazydog036.demo.Person.study()
可以观察到控制台只打印了该类中所声明的方法,但包括了所有公有的,私有的,受保护的和默认的方法。
3.3.3 获取单个方法
要获取单个方法,使用getMethod(String name,Class<?>... parameterTypes)
,第一个参数是方法名,第二个参数是方法的形参列表的类,没有形参就不写。
获得方法后使用invoke(Object obj,Object... args)
来调用相应的方法,第一个参数是调用这个方法的对象,第二个参数是调用方法的形参列表,没有就不写。
//3.获取单个无参方法
//3.1 study
Method studyMethod=class1.getMethod("study");
Person tangPerson=new Person();
studyMethod.invoke(tangPerson);
//3.2 toString
Method toStringMethod=class1.getMethod("toString");
//此时该方法有返回值,返回值类型为Object
Object object=toStringMethod.invoke(tangPerson);
System.out.println(object);
控制台打印结果如下:
无参构造方法被执行了。
null正在学习。
Person [age=0, name=null]
在Person类中添加一个带参的方法:
public void study(String subject) {
System.out.println(this.getName()+"正在学习"+subject+"。");
}
//3.3 获取单个带参方法
Person tangPerson=new Person();
Method studyMethod=class1.getMethod("study", String.class);
studyMethod.invoke(tangPerson, "如何打游戏");
控制台打印如下:
无参构造方法被执行了。
null正在学习如何打游戏。
在Person类中添加一个静态方法:
public static void staticMethod() {
System.out.println("这是一个静态方法。");
}
//3.4 获取私有方法
Method privateMethod=class1.getDeclaredMethod("privateMethod");
//3.4.1 设置访问权限无效
privateMethod.setAccessible(true);
privateMethod.invoke(tangPerson);
//3.5 获取静态方法
Method staticMethod=class1.getMethod("staticMethod");
staticMethod.invoke(null);
需要注意的是,获取私有方法时需要使用getDeclaredMethod
,并且需要设置访问权限无效才能调用,不然私有方法是不允许在其他类中调用的。
调用静态方法时,invoke中调用方法的对象这个参数写null。控制台打印结果如下:
无参构造方法被执行了。
这是一个私有方法。
这是一个静态方法。
3.4 方法演示(4)
- 使用反射可以实现一个 可以调用任何对象方法的通用方法。
/**
*
* @param obj 调用方法的对象
* @param methodName 调用方法的名字
* @param types 调用方法的形参类
* @param args 调用方法传入对象的形参列表
* @return
* @throws Exception
*/
public static Object invokeAll(Object obj,String methodName,Class<?>[] types,Object...args) throws Exception{
//1.获取类对象
Class<?> class1=obj.getClass();
//2.获取方法
Method method=class1.getMethod(methodName, types);
//3.调用
return method.invoke(obj, args);
}
可以使用该方法来调用任何对象的公开方法:
public static void main(String[] args) throws Exception {
Properties properties=new Properties();
//properties.setProperty("name", "tang");
invokeAll(properties, "setProperty", new Class[]{String.class,String.class}, "username","tang");
System.out.println(properties.toString());
}
注释的内容是正常使用对象变量来调用方法,使用反射调用此方法,结果如下:
{username=tang}
3.5 方法演示(5)
- 使用反射获取类中的属性。
public static void reflectOp4() throws Exception{
Class<?> class1=Class.forName("io.gitee.lazydog036.demo.Person");
//1.获取属性(字段)
Field[] fields=class1.getFields();
System.out.println(fields.length);
}
运行之后控制台打印的结果是“0”,因为getFields()
获取的是公开的和从父类继承的字段,而Person类中的字段(属性)是私有的,想要获取这些属性需要用getDeclaredFields()
方法,该方法返回类声明的所有属性,包括私有、默认、受保护的属性,但不包括继承的。
Class<?> class1=Class.forName("io.gitee.lazydog036.demo.Person");
Field[] fields=class1.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.toString());
}
输出结果如下:
private int io.gitee.lazydog036.demo.Person.age
private java.lang.String io.gitee.lazydog036.demo.Person.name
读写属性分别使用get方法和set方法:
//获取name属性
Field namefield=class1.getDeclaredField("name");
//给属性赋值
Person person=(Person) class1.newInstance();
namefield.set(person, "懒狗");//person.name="懒狗"
//获取值
System.out.println(namefield.get(person));
结果输出报错,因为name属性是私有成员,不允许修改。就像之前调用私有方法一样,在修改属性之前需要设置访问权限:
namefield.setAccessible(true);
最后结果正常运行:
无参构造方法被执行了。
懒狗
4. 设计模式介绍
-
什么是设计模式?
一套被反复使用、多数人知晓的、经过分类编目、代码设计经验的总结。简单理解:特定问题的固定解决方法。
-
好处?
使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性、重用性。
-
在Gof的《设计模式》书中描述了23中设计模式。本文只介绍几种常见的。
4.1 工厂设计模式
- 工厂模式主要负责对象创建的问题。
- 开发中有一个非常重要的原则“开闭原则”,对拓展开放、对修改关闭。
- 可通过反射进行工厂模式的设计,完成动态的对象创建。
工厂模式一般有父类产品、子类产品、工厂和客户。以下演示工厂模式:
/*
* 父类产品
*/
public interface Usb {
void service();
}
/*
* 子类产品
*/
public class Mouse implements Usb{
public void service() {
System.out.println("鼠标开始工作。。");
}
}
/*
* 子类产品
*/
public class KeyBoard implements Usb{
public void service() {
System.out.println("键盘开始工作。。");
}
}
/*
* 子类产品
*/
public class Upan implements Usb{
public void service() {
System.out.println("u盘开始工作。。");
}
}
/*
* 工厂类(负责对象创建)
*/
public class UsbFactory {
public static Usb createUsb(int type) {
Usb usb=null;
if (type==1) {//鼠标
usb=new Mouse();
}else if(type==2){//键盘
usb=new KeyBoard();
}else if (type==3) {//U盘
usb=new Upan();
}
return usb;
}
}
//客户
public class Demo {
public static void main(String[] args) {
System.out.println("请选择产品:1.鼠标 2.键盘 3.U盘");
Scanner in=new Scanner(System.in);
int type=in.nextInt();
Usb usb=UsbFactory.createUsb(type);
if(usb!=null) {
System.out.println("购买成功。");
usb.service();
}else {
System.out.println("产品不存在!");
}
}
}
运行结果如下:
请选择产品:1.鼠标 2.键盘 3.U盘
2
购买成功。
键盘开始工作。。
如果新增了一个产品,比如画板,那么需要新建一个子类产品类,然后在工厂类的if语句中加一个type,在new一个画板类。这么做就破坏了“开闭原则”,修改了源代码。接下来使用反射来优化这个程序。
/*
* 工厂类(负责对象创建)
*/
public class UsbFactory {
public static Usb createUsb(String type) {
Usb usb=null;
try {
Class<?> class1=Class.forName(type);
usb=(Usb) class1.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return usb;
}
}
在工厂类利用反射创建新实例,传递过来的就需要一个完整的“包名.类名”。
但是客户还是输入数字来选择购买哪个产品,我们可以用Properties类来将客户的选择和“包名.类名”存储为键值对,并将它放在单独的配置文件中。
#Usb.properties
1 = io.gitee.lazydog036.pattern.Mouse
2 = io.gitee.lazydog036.pattern.KeyBoard
3 = io.gitee.lazydog036.pattern.Upan
那么就相应地修改客户代码:
public static void main(String[] args) throws IOException {
System.out.println("请选择产品:1.鼠标 2.键盘 3.U盘");
Scanner in=new Scanner(System.in);
String choice=in.next();
Properties properties=new Properties();
//将文件读入到流中
FileInputStream fileInputStream=new FileInputStream("src\\Usb.properties");
//从流中加载数据
properties.load(fileInputStream);
fileInputStream.close();
Usb usb=UsbFactory.createUsb(properties.getProperty(choice));
if(usb!=null) {
System.out.println("购买成功。");
usb.service();
}else {
System.out.println("产品不存在!");
}
}
关于Properties这个集合和IO框架的知识默认你已经知晓了,我的其他博客里也有总结。
执行代码,程序正常运行。还是刚才那个问题,这时候新加了一个产品画板,我们之前需要修改工厂类的代码,而现在我们只需要在Usb.properties
文件中新加一行代码:
4 = io.gitee.lazydog036.pattern.DrawingBoard
不用去修改源代码,再新添加一个DrawingBoard类,然后执行代码,运行结果如下:
请选择产品:1.鼠标 2.键盘 3.U盘
4
购买成功。
画板开始工作。
输入4,正常输出结果,符合了“开闭原则”。
4.2 单例设计模式
-
单例(Singleton):只允许创建一个该类的对象。
要保证整个系统在运行过程中只能创建对象,有两种方式:
- 方式1:饿汉式(类加载时创建,天生线程安全)
//饿汉式单例 public class Singleton { private static final Singleton instance=new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
饿汉式单例一共三个步骤,很简单:
- 首先创建一个该类的常量对象。
- 构造方法设置为私有,类外部不能创建该对象。
- 通过一个公开的方法,返回这个对象。
通过一个多线程的代码来测试其是否是线程安全的:
public class testSingleton { public static void main(String[] args) { for(int i=0;i<3;i++) { new Thread(new Runnable() { @Override public void run() { //调用其hashCode方法 System.out.println(Singleton.getInstance().hashCode()); } }).start(); } } }
关于java多线程默认你已经有所了解,我的其他博客中总结得也很详细。
运行结果如下,三个对象的hashcode一样,说明三个线程获取的是同一个对象:
1307179676 1307179676
1307179676
* **方式2**:**懒汉式**(**使用时创建**,**线程不安全**,**需要加同步**)
```java
//懒汉式单例
public class Singleton2 {
private static Singleton2 instance=null;
private Singleton2() {}
public static Singleton2 getInstance() {
if(instance==null) {
instance=new Singleton2();
}
return instance;
}
}
相比饿汉式先创建一个对象而言,懒汉式只有在调用方法时才创建对象,比饿汉式节省空间。
懒汉式单例同样是三个步骤:
- 首先创造一个对象,赋值为null。
- 构造方法改成私有,类外部不能创建对象。
- 通过一个公开的方法,返回这个对象。
通过一个多线程的代码来测试其是否是线程安全的,测试代码同上,结果如下:
727916939
1612280362
727916939
hashcode不一样,说明在多线程中创建了不同的对象。怎么解决呢?很容易想到的是使用同步方法,或者是使用同步代码块,在方法名前加一个synchronized关键字,或者把需要同步的代码放进同步代码块中:
//括号里不能是instance,因为前面赋值是null,会出现空指针异常
synchronized (Singleton2.class) {
if(instance==null) {
instance=new Singleton2();
}
}
在线程比较多的情况下,每个线程在使用实例时,如果都需要判断是否上锁的话执行效率会大打折扣;其实当第一个线程进入到同步代码块中时就说明实例已经创建了,后来的其他线程执行的只是返回语句,所以可以让后来的线程绕过同步代码块:
if (instance==null) {
synchronized (Singleton2.class) {
if(instance==null) {
instance=new Singleton2();
}
}
}
在同步代码块中再加一个if判断,就可以让后来的线程直接执行下面的返回语句,而不必再去判断是否上锁。这样可以优化执行效率。
-
总结单例模式的两种写法的优缺点:
饿汉式:
- 优点:线程安全,类一加载就创建了实例。
- 缺点:实例对象就算不用也存在于内存中,生命周期长,浪费空间。
懒汉式:
- 优点:只有在调用getInstance才会实例化,生命周期短,节省空间
- 缺点:线程不安全。不过可以使用同步来保证线程互斥访问。
考虑两种方式的优点,我们还可以有第三种方式来实现单例模式:
//使用时创建,线程安全 public class Singleton3 { private Singleton3() {} private static class Holder{ static Singleton3 instance=new Singleton3(); } public static Singleton3 getInstance() { return Holder.instance; } }
在没有使用静态内部类里的内容时,这个静态内部类是不会执行的,只有当调用getInstance时,才会执行内部类的实例化,而instance是使用new来实例化的,这种方式本身就是线程安全的。
通过一个多线程的代码来测试其是否是线程安全的,测试代码同上,结果如下,三个hashcode一样,说明多个线程获取了同一个对象:
1612280362 1612280362 1612280362
5. 枚举
5.1 什么是枚举
-
枚举是一个引用类型,枚举是一个规定了取值范围的数据类型。反映到现实生活中,比如性别就是男和女,一个星期就是从周一到周日等。
-
枚举变量不能使用其他的数据,只能使用枚举中常量赋值,提高程序安全性。
-
定义枚举使用enum关键字。
//创建枚举类
public enum Gender {
MALE,FMALE
}
枚举类中必须包含枚举常量,如上段中的MALE,多个枚举常量之间使用逗号隔开,如果类中只有枚举常量末尾可以不加分号。
枚举类中还可以包含属性,方法,私有构造方法:
public enum Gender {
MALE,FMALE;
public int a;
private String bString;
static HashMap<Integer, String> hashMap;
//构造方法必须私有
private Gender() {
}
public static void show() {
}
}
演示枚举类的使用,结果输出FMALE:
public class testGender {
public static void main(String[] args) {
//构造方法私有,不能使用new来创建
Gender gender=Gender.FMALE;
System.out.println(gender);
}
}
5.2 枚举的本质
在创建枚举类的时候,注意到这个类和以前接触过的类不太一样,枚举常量没有任何修饰关键字,那它是一个怎么的存在?
找到编译生成的Gender.class
文件,借助Xjad小工具将其反编译成java代码,这个小工具在百度上可以找到,得到以下的反编译结果:
public final class Gender extends Enum
{
public static final Gender MALE;
public static final Gender FMALE;
private static final Gender ENUM$VALUES[];
private Gender(String s, int i)
{
super(s, i);
}
public static Gender[] values()
{
Gender agender[];
int i;
Gender agender1[];
System.arraycopy(agender = ENUM$VALUES, 0, agender1 = new Gender[i = agender.length], 0, i);
return agender1;
}
public static Gender valueOf(String s)
{
return (Gender)Enum.valueOf(io/gitee/lazydog036/meiJu/Gender, s);
}
static
{
MALE = new Gender("MALE", 0);
FMALE = new Gender("FMALE", 1);
ENUM$VALUES = (new Gender[] {
MALE, FMALE
});
}
}
可以看到Gender类变成了final修饰的终止类,意味着这个类没有子类,而且它继承了Enum抽象类。Enum是jdk1.5之后新增的一个类,实际上我们所创建的枚举类都隐式地继承了Enum类。而FMALE和MALE都变成了用该类所创建的静态的终止的属性,尽管我们只写了常量名。
还可以看到java编译时还添加了一个带参的私有构造方法和两个静态方法。还有一个静态代码块,在其中对这些静态常量进行了赋值。
所以枚举的本质就是:
- 一个终止类,并继承Enum抽象类。
- 枚举常量是当前类型的静态常量。
5.3 枚举配合switch
在switch语句中,括号里的内容也可以是枚举类型的对象。
public static void main(String[] args) {
Gender gender=Gender.FMALE;
switch(gender) {
case FMALE:
System.out.println("女性");
break;
case MALE:
System.out.println("男性");
break;
default:
break;
}
}
case后面直接写枚举常量。结果如下:
女性
6. 注解
6.1 什么是注解
-
注解(Annotation)是代码里的特殊标记,程序可以读取注解,一般用于替代配置文件。
-
开发人员可以通过注解告诉类如何运行。
在JAVA技术里注解的典型应用是:可以通过反射技术得到类里面的注解,以决定怎么去运行类。
-
常见注解:
@Override
表示重写父类方法,@Deprecated
表示方法已过时。 -
定义注解使用
@interface
关键字,注解中只能包含属性。
//创建注解类型
public @interface MyAnnotation {
//属性,无默认值
String nameString();
//有默认值
int age() default 18;
}
注解类型的属性与其他类有所不同,它需要在属性名后面加一个括号,但不叫方法。
//注解的使用
public class Student {
@MyAnnotation(nameString = "张三")
public void show() {
}
}
使用注解时直接添加到方法上面,也可以添加到类上面;如果属性没有默认值的话,需要在注解名括号里写上。
-
注解基本类型
- String类型
- 基本数据类型
- Class类型
- 枚举类型
- 注解类型
- 以上类型的一维数组
public @interface MyAnnotation { //String类型 String nameString(); //基本类型 int age() default 18; //Class类型 Class<?> class1(); //枚举类型 Gender gender(); //不允许 //ArrayList<String> arrayList(); }
6.2 注解的本质
同上使用Xjad工具反编译生成的MyAnnotation.class
文件,得到如下代码:
public interface MyAnnotation
extends Annotation
{
public abstract String nameString();
public abstract int age();
}
可以看到注解类已经变成了一个接口,而在注解类中定义的两个属性也变成了两个抽象方法。
所以注解的本质其实就是接口。
6.3 反射获取注解信息
//注解类型
public @interface PersonInfo {
String name();
int age();
String hobby();
}
//Person类
public class Person {
//在方法上添加注解
@PersonInfo(age = 21, hobby = "足球", name = "tang")
public void show(String name,int age,String hobby) {
System.out.println("姓名:"+name+" 年龄:"+age+" 爱好:"+hobby);
}
}
public class testPersonInfo {
public static void main(String[] args) throws Exception{
//1.获取类对象
Class<?> class1=Class.forName("io.gitee.lazydog036.annotation.Person");
//2.获取类方法
Method method=class1.getMethod("show", String.class,int.class,String.class);
//3.获取方法上的注解信息
PersonInfo personInfo=method.getAnnotation(PersonInfo.class);
//4.打印注解信息
System.out.println(personInfo.name()+" "+personInfo.age()+" "+personInfo.hobby());
}
}
运行上面的代码,看是否打印成功,得到结果如下:
Exception in thread "main" java.lang.NullPointerException
at io.gitee.lazydog036.annotation.testPersonInfo.main(testPersonInfo.java:14)
发现报了一个空指针异常的错误,这个错误的原因就在于personInfo的值为null。第3步不是已经赋值了吗,为什么还是null?这是因为这个注解信息在运行的时候就没有了,PersonInfo注释类的类名上有一个默认的元注解@RetentionPolicy.CLASS
,使它只作用于编译后的class文件中。
6.4 元注解
元注解:用来描述注解的注解。
@Retention
:用于指定注解可以保留的域。
@RetentionPolicy.CLASS
:注解记录在class文件中,运行Java程序时,JVM不会保留。这是注解的默认值。@RetentionPolicy.RUNTIME
:注解记录在class文件中,运行Java程序中,JVM会保留注解,程序可以通过反射获取该注释。@RetentionPolicy.SOURCE
:编译时直接丢弃这种策略的注释。
解决上一节的问题,只需要在注解类上加一个元注解即可:
@Retention(value = RetentionPolicy.RUNTIME)
public @interface PersonInfo {
//......
}
注意括号里的属性为value。然后再运行一下程序,控制台成功打印:
tang 21 足球
我们也可以将注解里的属性值传入show方法中并进行调用:
//5.调用方法
Person person=(Person) class1.newInstance();
method.invoke(person, personInfo.name(),personInfo.age(),personInfo.hobby());
输出结果如下,方法成功调用:
姓名:tang 年龄:21 爱好:足球
@Target
:元注解,指定注解用于修饰类的哪个成员。
上文演示的代码中PersonInfo注解是放在show方法上的,也可以说修饰了这个方法。在不加这个元注解的时候,注解可以修饰在任何地方。
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.TYPE })
public @interface PersonInfo {
//......
}
@Target
有一个枚举数组ElementType类型的value属性,在赋值的时候需要使用大括号括起来。这个枚举数组中有不同的枚举常量,如上的元注解表示该注解只能放在类上,比如放在show方法上编译器就会报错;如果枚举常量为METHOD,就表示该注解只能放在方法上。
ElementType枚举类型的源码如下(JDK1.8):
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
根据程序需求可以自己选择枚举常量,也可以不写这个元注解,就表示注解可以加在任何地方。