反射reflect
PS:反射破一切【在反射面前没有任何权限修饰符】
类的加载过程
需要创建一个类的对象,先找类,在通过这个类创建对象 --》 类名 对象名 = new 类名();
启动JVM类会被加载成字节码文件【.class文件】–》 在加载之前会进行编译,会将原有【.java】 编译成【.class】
就是因为有这个【.class】文件,所以Java才是一处编译处处运行
除了【正向】创建–》 类名 对象名 = new 类型()之外还可以通过【.class】反向创建对象,这种方式就是反射,通过这种方式不仅可以创建对象,还可以操作类中 属性和方法。
无论是【正向】创建对象,还是【方向】创建对象,这个类都需要进行到JVM中进行加载,这个加载过程就是【类的加载】
当程序启动之后使用某个类的时候,如果该类还未被加载内存中,则系统会通过**【加载、连接、初始化】**这三个步骤对类进行初始化操作
1.类的加载
类加载是之类的class文件【字节码文件】载入到内存中,并创建一个**【java.lang.Class】对象,我们称这个对象为【字节码对象】**
PS:Class 是Java中提供一个类 ,这个类是描述类使用
类的加载过程是有类的加载器【ClassLoader】完成,类的加载器通常是由JVM提供,我们称为【系统类加载器】,我们也可以继承ClassLoader类来提供自己加载器【自定义的类加载器】
PS: ClassLoader类的加载器,加载类的对象
2.类的连接
当类被加载到内存中时,系统会为这个类产生一个对应Class对象,接着把类的二进制数据合并到JRE中
1.验证: 检测被加载的类是否有正确内部结构
2.准备: 负责为类的static变量分配内存空间,并设置默认值
3.解析: 把类的二进制的数据中符号引用提交为直接引用【和内存区域的交换(讲解JVM会说明)】
3.类的初始化
1.如果该类还未被加载和连接,则程序需要先加载并连接该类
2.如果该类的直接父类还为被初始化,则先初始化父类【子类对象的实例化过程(先调用父类构造方法,然后在调用子类的构造方法创建对象)】
3。如果类中初始语句【初始化代码块、静态代码块】,依据系统执行顺序开始执行
什么是类的对象
类的对象:基于某类new出来的对象,我们称为实例对象
例如:
Integer i = new Integer(1); //通过Integer类创建乐意个i对象,这个对象是对当前类的一个引用,也为称为Integer类的实例
类对象:类加载的产物,这个产物中封装着一个类的所有信息【类名,父类,接口,属性,方法和构造方法】
PS:这个加载的产物属于谁,他属于Class【Class描述的是什么,就是这个类中有类名,父类,接口,属性,方法和构造方法】
如何创建Class对象?
package com.qfedu.reflect.Class;
//如果Class对象即 类对象
public class CreateClassInstanceDemo {
public static void main(String[] args) throws ClassNotFoundException {
//通过.class文件进行创建,这个创建需要提供【类的全限定名称】
//类的全限定名如何组成 --》 包名+类名
/*
Class类是有泛型,这个泛型类型是你要同Class类所描述的类对象的类型
例如:需要通过Class创阿金一个Student类的类对象,此时这个Class的泛型是Student
Class类的泛型是唯一可以使用 ? 进行占位,但是不建议这样使用,所以可以对Class中泛型
进行 "冷处理" ,不管他默认Object,之后在进行转换操作
如果通过Class调用forName方法,需要注意,需要处理编译时异常
ClassNotFoundException 无法找到当前这个类
【推荐指数:60%】
*/
Class cl1 = Class.forName("com.qfedu.reflect.Class.Student");
//2.通过类自身的.class来构建类对象【在线程说一些,类锁】
//【推荐指数:39.9%】
Class cl2 = Student.class;
//3.【推荐指数:0.01%】
//通过Object类中getClass方法来创建类对象
//getClass方法是一个成员方法,所以创建一个对象,这个对象调用getClass方法
Student stu1 = new Student();
Class cl3 = stu1.getClass();
/*
以上3个方法,强烈推荐第一种,即forName方法
2和3这两种方式对类的依赖太大了,只有类存在才能创建Class对象
*/
}
}
PS:【类对象】和【类的对象】在使用上效果是一样,通过可以对类进行操作,只不过反射创建的对象权限更大一些
问题1:在使用上面三种方式获取Class对象时,是否所有类型都有字节的类对象?
是,除了Java中所提供类都具备当前[.class]方法,自定义类也具备这个方法,JavaSE中九大内置Class实例【这些实例是JVM预先就加载好的】:{byte、short、int 、long、float、double、char、boolean、void}
除了void之外,八种基本数据类型对应是其包装类,在包装类中有一个【TYPE】常量,用于返回该包装类对应基本数据类型字节文件
package com.qfedu.reflect.Class;
public class CreateClassInstanceDemo {
public static void main(String[] args) throws ClassNotFoundException {
//包装类中有一个常量【TYPE】,这个常量返回的式基本数据类型的字节码文件
System.out.println(Integer.TYPE == int.class); //true
//这样比较是false
//Integer是独立的class文件,而int是JVN内置的独立class文件,所以他们一定不相等
System.out.println(Integer.class == int.class); //false
}
}
问题2:数组是如何获取Class对象?数组是引用类型,数组创建出来的对象就是【相当与是数组的一个实例】
package com.qfedu.reflect.Class;
public class CreateClassInstanceDemo {
public static void main(String[] args) throws ClassNotFoundException {
int[] arr = new int[10];
Class aClass = int[].class; //获取数据的Class对象
//因为Java中所有类都是直接或间接继承于Object,数组也不例外,所以
Calss bClass = arr.getClass(); //通过数据对象.getClass()方法
}
}
反射的操作
通过各种方式能获取到字节码文件对象即【类对象】,可以做什么操作?
通常情况下,框架中的类是不能直接修改原码内容,若需要对类属性或方法进行修改或调用此时就可以使用反射这种形式完成
反射获取的字节码文件对象,通常可以操作以下三种
1.属性【Field】 2.方法【Method】 3.构造方法【Constructor】
PS:在反射面前【访问权限修饰符】没有任何作用
演示
package com.qfedu.reflect.FieldAndMethodAndConstructor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
//通过获取字节码文件对象,对Person类进行操作
try {
//1.先获取到字节码文件对象,这种方式是不能自动推断出 字节码文件的数据类型即不能给Class进行泛型赋值
Class aClass = Class.forName("com.qfedu.reflect.FieldAndMethodAndConstructor.Person");
//2.这种方式是可以直接推断数据类型,即推断出Class泛型
Class<Person> personClass = Person.class;
//常用操作1:通过获取的字节码文件对象即【类对象】来构建Person类的对象--》【Person p = new Person()】
//必要前提:必须获取当前创类的字节文件对象
//提供一个方法:获取类中构造方法 public Constructor getConstructor(Class... parameterTypes)
/*
说明:这个方法参数是一个 “可变参数”,因为是一个可变参数所以这个参数中传递的数据可以什么都没有,也可以有很多
若什么参数都不传递,相当于获取的是【无参构造方法】
若传递出了对应参数【指的是类中构造方法的形参列表】,相当与获取的式【有参构造方法】
特别注意:
getConstructor方法的参数类型是Class ,所以传递参数 必须是一个字节码文件对象
构造方法中数据的数据类型是什么,那么这个字节码文件对象就传递什么
这个方法的返回值是一个Constructor对象,通过和这个对象调用newInstance即可以创建出对应类的 ————> 类的对象
*/
Constructor constructor = aClass.getConstructor();
/*
newInstance(Object ... initargs) 这个方法可变参数是对 当前类中进行赋值操作,需要【具体值】
若是什么都不传递,触发的就无参构造方法
若传递具体数据,触发的就是有参构造方法
*/
Object o = constructor.newInstance();
System.out.println("证明是Person对象?"+(o instanceof Person));
//有参构造方法
Constructor constructor1 = aClass.getConstructor(String.class, int.class);
Object o1 = constructor1.newInstance("张三", 19);
//将o1转变为Person对象
if(o1 instanceof Person){
Person p = (Person)o1;
System.out.println(p);
}
//字节码文件对象存在一个方法 newInstance 这个方法可以直接创建出对应类 --》 类的对象
//这个方法触发的无参构造方法,依据的原则【类会默认提供一个无参构造方法】
Object o2 = aClass.newInstance();
System.out.println(o2);
//反射破一切,在反射面前没有任何权限修饰符可言
//暴力反射,无视任何权限修饰符 只要反方带有这个单词Declared方法即使暴力反射
//Test can not access a member of class com.qfedu.reflect.FieldAndMethodAndConstructor.Person with modifiers "private"
//现在的反射是无法获取到私有属性或私有方法,那么如何获取
// Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class);
// Object o3 = declaredConstructor.newInstance("李四");
// System.out.println(o3);
System.out.println("-------------------属性获取-----------------");
//1.获取类中属性进行操作 参数是要操作属性名字
//返回值是一个Field对象,这个Field对象是不能直接进行打印,也无法获取其内部存储的值
Field age = aClass.getField("age");
//需要调用当前Field中一个方法 get,这个get方法的参数是通过Constructor对象创建出来
//类的对象
System.out.println("存储在当前Person对象中age属性的值是:"+ age.get(o1));
//私有权限暴力反射单词Declared
Field name = aClass.getDeclaredField("name");
//开启权限,获取单个类型对象【方法,属性和构造方法】,哪个对象就开启权限
name.setAccessible(true);//只要设置为true就相当于开启了访问权限【无视修饰符】
System.out.println("存储在当前Person对象中name属性的值是:"+ name.get(o1));
//需要对某属性进行修改值 通过field对象调用set方法
//参数 第一个 是 当前创建出来类的对象 第二个参数是具体修改的值
age.set(o1,10000);
System.out.println("存储在当前Person对象中age属性的值是:"+ age.get(o1));
System.out.println("-------------------方法------------------");
/*
getMethod(String name, Class<?>... parameterTypes)
通过这个方法获取到类中方法,需要两个参数
第一个参数必须传递,因为需要方法的名字
第二个参数是可以不传递,因为是一个可变参数
可变参数传递将决定哪个方法
返回值是一个Method的对象,这个对象是不能直接操作方法
所以需要用到Method对象中一个方法invoke
*/
Method method = aClass.getMethod("show");
/*
invoke(Object obj, Object... args)
第一个参数必须传递时一个创建好的类的对象【通过Constructor创建的】
第二个参数是可变参数,决定是否当前方法是否有参数值,这个位置要传递具体的值
invoke方法带有返回值,这个返回值对应的就是类中的原始方法返回值
*/
method.invoke(o1);
Method method2 = aClass.getMethod("show", String.class);
method2.invoke(o1,"王五");
//同理依旧支持暴力反射,但是需要开权限
// aClass.getDeclaredMethod();
//method2.setAccessible(true);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
总结:
1.如果要使用反射,需要使用获取字节码文件对象的三种方式之一即可
2.获取构造方法getConstructor 返回值是一个Constructor对象,在使用newInstance就可以创建获取到字节码文件对应类对象
3.获取属性getField,返回的是一个Field对象,在使用get获取存储在对应对象中属性值,set可以对对应对象中属性值进行修改
4.获取方法getMethod,返回值是一个Method对象 ,在使用invoke方法可以执行获取到方法,若接返回值证明方法是有返回指定,否可以不接收,接收与不接收都会执行运行
5.反射破一切,在于可以对私有实行进行暴力反射,只需要记住带有Declared单词的都可以暴力反射,但是反射过后获取的对象,需要开通权限setAccessible,这个权限的使用是通过暴力反射方法获取对象来调用的,并给需要参数为true
反射的其他API:
getModifiers() 获取权限修饰符 getName() 返回类的全限定名字 getPackage() 返回包的名字
getSuperClass() 获取当前父类 isArray 判断是否是数组 isEnum 判断是否是枚举
现阶段获取的都是成员变量和成员方法,如果获取静态方法和静态变量和方法参数是数组?
package com.qfedu.reflect.FieldAndMethodAndConstructor;
import java.lang.reflect.Method;
import java.util.Arrays;
public class Employee {
public static void doWork1(int... arr){
System.out.println("doWork1方法被调用:"+ Arrays.toString(arr));
}
public static void doWork1(String... arr){
System.out.println("doWork1方法被调用:"+ Arrays.toString(arr));
}
}
class MethodInvokeDemo{
public static void main(String[] args) throws Exception {
Class employeeClass = Employee.class;
//1.都需要获取Method对象
Method doWork1 = employeeClass.getMethod("doWork1", int[].class);
//如何触发静方法,并且进行采纳数传递
//因为是静态方法,所以是不需要Constructor构建的对象,所以直接传null
//这个操作方式同样适用于静态静态
doWork1.invoke(null,new int[]{1,2,3,4,5,6,7,8});
//触发的是引用类型
Method doWork11 = employeeClass.getMethod("doWork1", String[].class);
//此时如果是引用类型,它会认为是Object作为参数存在,此时传入String就是出现问题
//如果是引用类型数组,底层就会认为需要一个Object类型数组,这个数组中存储要传递的引用类型数组对象
//doWork11.invoke(null,new String[]{"A","B","C"});
doWork11.invoke(null,new Object[]{new String[]{"A","B","C"}});
//为了保证传递参数参数不问题建议都使用 new Object[]{参数数组}的方式进行传递
}
}
扩展:反射加载资源文件
如果需要通过反射来加载资源文件,需要在工程中创建一个资源文件夹【Source Folder】
这个资源文件夹会自动将内容文件编译到classPath路径下即【当前存储字节码文件的路径下】
package com.qfedu.reflect.FieldAndMethodAndConstructor;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;
public class ReadFileDemo {
public static void main(String[] args) throws IOException {
//读取资源文件夹下文件
Properties p = new Properties();
//1.不需要创建流对象,直接使用类的加载器
//1.1获取类的加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//字节读取文件获取输入流
//这个文件必须在资源文件夹下
InputStream resourceAsStream = contextClassLoader.getResourceAsStream("db.properties");
p.load(resourceAsStream);
System.out.println(p);
//只能在SE中使用,不能在EE中使用
InputStream systemResourceAsStream = ClassLoader.getSystemResourceAsStream("db.properties");
//不用系统类,自定义类也可以获取流对象
InputStream resourceAsStream1 = ReadFileDemo.class.getResourceAsStream("db.properties");
}
}
设计模式
什么是设计模式?
一套被反复使用,多数人知晓,经过分类,代码设计总结出来的经验。简单理解:特定问题固定的解决方法。
好处:使用设计模式的代码可以最大程度的重用,让代码更容易被人理解,保证代码可靠性,重用性。
PS:是软件开发前辈们总结出来解决某种问题有效方式
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
后期学习框架就是用设计模式堆砌而成【SSM】替换【SSH】
工厂设计模式【简单工厂】
工厂设计模式就是为了提供 【统一的对象创建】
得到需求 --》 需求生产对应物品 —》 送出 《—》 参数 --》 创建 —》返回值
需求: 女娲造人,
造人时候需要有一个思想 : 身高,体重,性别,年龄 等等
在这个思想的基础上 更加细化的分出要创造的东西,通过不同的属性,创建出不同对象, 所有的都要有一个统一的出口【都是人】
package com.qfedu.DesignMode.FactoryDesign;
//思想--》 相当于是父类,为了防止父类被创建【直接创建】
public abstract class Person {
//基础属性
private String name;
private int age;
private double height;
private double weight;
private String gender;
public Person(){}
public Person(String name, int age, double height, double weight, String gender) {
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
//具体行为有子类实现,提供抽象方法
public abstract void eat();
public abstract void sleep();
public abstract void say();
}
//对思想的细化
public class Man extends Person{
public Man() {
}
public Man(String name, int age, double height, double weight, String gender) {
super(name, age, height, weight, gender);
}
@Override
public void eat() {
System.out.println("吃肉");
}
@Override
public void sleep() {
System.out.println("睡觉");
}
@Override
public void say() {
System.out.println("说话:7000句");
}
}
//对思想的细化
public class Woman extends Person{
public Woman() {
}
public Woman(String name, int age, double height, double weight, String gender) {
super(name, age, height, weight, gender);
}
@Override
public void eat() {
System.out.println("吃素");
}
@Override
public void sleep() {
System.out.println("睡觉");
}
@Override
public void say() {
System.out.println("说话:20000句");
}
}
public class CreatePersonException extends RuntimeException {
public CreatePersonException() {
}
public CreatePersonException(String message) {
super(message);
}
public CreatePersonException(String message, Throwable cause) {
super(message, cause);
}
public CreatePersonException(Throwable cause) {
super(cause);
}
public CreatePersonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
//工厂的核心,做当前对象的创建
public class NvwaFactory {
//提供一个方法,这个方法可以统一的创建出人类, 需要一个参数,通过这个参数可以创建不同人
public static Person createPerson(String gender){
switch (gender){
case "男人":
return new Man();
case "女人":
return new Woman();
default:
throw new CreatePersonException("不好意这个人创建不了....");
}
}
}
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
//通过工厂模式可以获取当对象Person
Person person = NvwaFactory.createPerson("男人");
}
}
单例设计模式
无论如何创建对象只有一个【地址是相同】
例如:多个线程并发访问临界资源,保证资源安全,不会出现重复数据,多个对象创建出现多个成员变量,每一个对象都维护自己的一个变量,就可以将当前类作为单例为,无论如何创建对象你得到结果只有一个对象,只会维护一份成员变量
饿汉模式
1.私有构造方法
2.定义私有一个全局静态变量,这个静态变量的类型是当前类的类型并进行初始化
3.提供一个公有静态方法,可以获取对象
package com.qfedu.DesignMode.SingleDesign;
//饿汉模式
public class HungrySingle {
//1.私有化构造方法
private HungrySingle(){}
//2.提供一个私有化的静态变量,对当前类进行创建【创建好一个对象】
private static HungrySingle instance = new HungrySingle();
//3.提有静态方法,可以获取当前类的对象
public static HungrySingle getInstance(){
return instance;
}
}
class Demo1{
public static void main(String[] args) {
HungrySingle hs1 = HungrySingle.getInstance();
HungrySingle hs2 = HungrySingle.getInstance();
System.out.println(hs1 == hs2);//true
}
}
懒汉模式
1.私有化构造方法
2.提供一个私有静态的变量,是当前类的类型【不初始化】
3.提供一个公有静态方法,这个方法的返回值是当前类类型,在方法体的内部进行判断
如果是第一创建 那么就new对象,否在直接返回创建好的对象
package com.qfedu.DesignMode.SingleDesign;
//懒汉
public class LazySingle {
// 1.私有化构造方法
private LazySingle(){}
// 2.提供一个私有静态的变量,是当前类的类型【不初始化】
private static LazySingle instance;
// 3.提供一个公有静态方法,这个方法的返回值是当前类类型,在方法体的内部进行判断
public static LazySingle getInstance() {
// 如果是第一创建 那么就new对象
if(instance == null){
instance = new LazySingle();
}
//否在直接返回创建好的对象
return instance;
}
}
class Demo2{
public static void main(String[] args) {
LazySingle ls1 = LazySingle.getInstance();
LazySingle ls2 = LazySingle.getInstance();
System.out.println(ls1 == ls2);
}
}
PS:就是因为多了一句判断,所以会出现一个致命的问题,就是在多线程并发访问的前提下,饿汉单例是线程安全的但是懒汉单例是不安全的【可以创建出不是同一个对象】
package com.qfedu.DesignMode.SingleDesign;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
//懒汉
public class LazySingle {
// 1.私有化构造方法
private LazySingle(){}
// 2.提供一个私有静态的变量,是当前类的类型【不初始化】
private static LazySingle instance;
// 3.提供一个公有静态方法,这个方法的返回值是当前类类型,在方法体的内部进行判断
public static LazySingle getInstance() {
// 如果是第一创建 那么就new对象
if(instance == null){
instance = new LazySingle();
}
//否在直接返回创建好的对象
return instance;
}
}
class Demo2{
private Set<LazySingle> sets = new HashSet<>();
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i<20;i++){
sets.add(LazySingle.getInstance());
}
System.out.println("打印集合对象:");
for(LazySingle ls : sets){
System.out.println(ls);
}
}
}
public static void main(String[] args) {
new Demo2().new MyThread().start();
new Demo2().new MyThread().start();
}
}
双重检查加锁:
可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?
所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
//懒汉
public class LazySingle {
// 1.私有化构造方法
private LazySingle(){}
// 2.提供一个私有静态的变量,是当前类的类型【不初始化】
private static LazySingle instance;
// 3.提供一个公有静态方法,这个方法的返回值是当前类类型,在方法体的内部进行判断
public static LazySingle getInstance() {
if (instance == null) {
synchronized ("") {
// 如果是第一创建 那么就new对象
if (instance == null) {
instance = new LazySingle();
}
}
}
//否在直接返回创建好的对象
return instance;
}
}
总结:多线访问建议使用饿汉,就算新能也是最好
PS:有没有简单的单例即不用私有也不用提供公有–》 枚举
public enum Singletion{
INSTANCE;
}
注解
什么注解?
注解是程序中特殊的标记,程序可以读取注解,一般用来替代配置文件使用的
ps:注解和注释的区别? 注释是个程序猿看到,它不会参与程序的运行,注解是个程序看,它会参与运行
在Java中注解典型应用
1.可以通过反射获取到类中注解,决定类该如何去运行【框架】
2替代配置文件 在JavaEE编程中书写web程序时,需要配置一个文件【web.xml】
PS: 在JavaEE7开始提供web编程的新规范,通过注解可以简化配置程序,不在需要书写web.xml文件
如何定义注解
需要使用到一个关键字@interface,帮组我们创建注解【注解只能包含属性】
PS:注解还可以在不改变原有逻辑的前提下, 在源文件中嵌入补充一些信息,注解可以修饰**【类,属性、方法】**,有无注解都不会影响程序的正常执行
定义注解
//创建注解
public @interface MyAnnotation {
//注解中只能有【“属性”】,但是需要注意这个“属性” 类似于 方法
//定义方式 语法: 数据类型 属性名();
String name();
int age();
}
既然这个注解定义“属性”类似于方法,所以注解还支持默认值 --》在定义属性的同时指定属性默认值
//创建注解
public @interface MyAnnotation2 {
//注解中只能有【“属性”】,但是需要注意这个“属性” 类似于 方法
//定义方式 语法: 数据类型 属性名();
//添加默认值 语法: 数据类型 属性名() default 值;
String name() default "张三";
int age() default 18;
}
实现
package com.qfedu.DesignMode.Annotation;
public class Person {
//使用注解时,如果注解中有属性并且属性没有进行赋值,就需要在使用注解的位置对属性进行赋值
@MyAnnotation(name = "李四",age = 18)
public void show(){
}
//使用注解时,一种就是这个注解没有任何属性,所以不用赋值
// 另外一种注解中的属性已经被赋值了【使用默认值】所以 外界使用注解时不用赋值
// 如果默认值无法满足需求,可以对属性重新赋值
@MyAnnotation2
public void show2(){
}
//JavaEE如果只有一个属性 是不写属性名直接赋值
@MyAnnotation3(name = "王五")
public void show3(){
}
}
注解中可以使用对声明属性的数据类型和注解的本质
在注解中可以使用的数据类型 : "基本数据类型、String类型、Class类型 、枚举类型、注解类型 、一位数组"
//创建注解
public @interface MyAnnotation4 {
int age();
String name();
Class class1();
Gender gender();
MyAnnotation an();
int[] arr();
}
注解的底层实现
package com.qfedu.DesignMode.Annotation;
import java.lang.annotation.Annotation;
// Referenced classes of package com.qfedu.DesignMode.Annotation:
// Gender, MyAnnotation
public interface MyAnnotation4
extends Annotation
{
public abstract int age();
public abstract String name();
public abstract Class class1();
public abstract Gender gender();
public abstract MyAnnotation an();
public abstract int[] arr();
}
"ps:注解的本质就是一个接口,定义属性就是接口中抽象方法,相当于定这个接口继承 Annotation"
通过反射获取注解和元注解
package com.qfedu.DesignMode.Annotation;
import java.lang.reflect.Method;
public class Person2 {
@MyAnnotation5(name= "张三",age=20,gender="男")
public void show(String name,int age,String gender){
System.out.println(name+"===="+age+"==="+gender);
}
}
class Test{
public static void main(String[] args) throws Exception {
//1.字节码文件对象
Class aClass = Class.forName("com.qfedu.DesignMode.Annotation.Person2");
//2.获取方法
Method show = aClass.getMethod("show", String.class, int.class, String.class);
//3.获取方法上注解
MyAnnotation5 annotation = show.getAnnotation(MyAnnotation5.class);
//4.就可以获取存储在注解中值
System.out.println(annotation.name());//null.name;
System.out.println(annotation.age());
System.out.println(annotation.gender());
}
}
问题:通过反射获取方法上注解,获取之后调用了注解种的属性但是出现了空指针异常?
当前编写注解,在字节码文件进入到JVM之前就已经消亡了,所以此时获取注解就是一个null
元注解
用描述注解的注解,它可以决定注解的一些基础信息【注解使用在什么位置和注解的声明周期】
@Retention 可以声明在自定义注解的上方,表示注解可以保存在哪一个代码时期,保存的时间【注解声明周期】
ps:一般自定义注解都会使用RUNTIME(使用反射获取注解)
有三种表示形式被封装在"RetentionPolicy"枚举中
"CLASS(默认值):注解可以存储在源文件和字节码文件中,但是一旦JVM加载,会在JVM中消失"
"RUNTIME:注解 可以存在于源文件和字节码文件和JVM中"
"SOURCE:注解只能存储在于源文件中,一旦编译,在字节码中就消失"
@Target表示注解可以贴在哪些位置,内部使用ElementType枚举定义位置 【注解可以使用在什么位置】
"TYPE:只能修饰类,接口和枚举"
"FIELD:只能修饰属性,包括常量(枚举常量)"
"METHOD:只能修饰方法 "
"PARAMETER:只能修饰参数"
"CONSTRUCTOR:只能修饰构造方法 "
"LOCAL_VARIABLE:只能修饰局部变量"
"ANNOTATION_TYPE 只能修饰注解 "
PACKAGE:修饰包
ps:在创建包的时候可以创建出一个package-info.java文件,这个文件的主要作用就是给包使用注解
因为包需要放到整段代码的第一张,那么无法在包上添加注解,所以写到文件中就是给包添加注解了
@Document 表示是一个文档注解,会保存在文档中
@Inherited 表示当前注解可以被继承
PS:"在没有表明注解使用位置的前提下,注解可以使用任何位置"
修改代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//创建注解
//表明声明周期和位置的必须在注解上方
@Retention(value = RetentionPolicy.RUNTIME)//表示的是声明周期
@Target(value = {ElementType.METHOD}) //表明注解存在的位置
public @interface MyAnnotation5 {
String name();
int age();
String gender();
}
package com.qfedu.DesignMode.Annotation;
import java.lang.reflect.Method;
public class Person2 {
@MyAnnotation5(name= "张三",age=20,gender="男")
public void show(String name,int age,String gender){
System.out.println(name+"===="+age+"==="+gender);
}
}
class Test{
public static void main(String[] args) throws Exception {
//1.字节码文件对象
Class aClass = Class.forName("com.qfedu.DesignMode.Annotation.Person2");
//2.获取方法
Method show = aClass.getMethod("show", String.class, int.class, String.class);
//3.获取方法上注解
MyAnnotation5 annotation = show.getAnnotation(MyAnnotation5.class);
//4.就可以获取存储在注解中值,传递到方中
Object o = aClass.newInstance();
show.invoke(o,annotation.name(),annotation.age(),annotation.gender());
}
}
总结:
【使用注解,必须有三方参与才真正的意义 】
1.获得注解标签本身
2.被贴的程序(类,接口.属性,方法)
3.是由第三方程序使用反射的等手段来注解功能(Java可以获取注解)
【JDK是自带注解】
@SuppressWarnings(“rawtypes”) 压制警告
@Override 限定重写
@Deprecated 方法过时
JDK7提供一个新的注解
@SafeVarargs 抑制堆污染
JDK8提供一个新的注解
@FunctionalInterface 函数式接口