Java基础-11(方法引用,Junit测试,反射,注解)

58.方法引用

在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑 一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?

  • 冗余的Lambda场景

定义一个打印的函数式接口

/*
    定义一个打印的函数式接口
 */
@FunctionalInterface
public interface Printable {
    //定义字符串的抽象方法
    void print(String s);
}

Printable 接口当中唯一的抽象方法 print 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda 来使用它的代码很简单:

public class Demo01Printable {
    //定义一个方法,参数传递Printable接口,对字符串进行打印
    public static void printString(Printable p) {
        p.print("HelloWorld");
    }

    public static void main(String[] args) {
        //调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda
        printString((s) -> {
            System.out.println(s);
        });

        /*
            分析:
                Lambda表达式的目的,打印参数传递的字符串
                把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
                注意:
                    1.System.out对象是已经存在的
                    2.println方法也是已经存在的
                所以我们可以使用方法引用来优化Lambda表达式
                可以使用System.out方法直接引用(调用)println方法
         */
        printString(System.out::println);
    }
}

其中 PrintString 方法只管调用 Printable 接口的 print 方法,而并不管 print 方法的具体实现逻辑会将字符串 打印到什么地方去。而 main 方法通过Lambda表达式指定了函数式接口 Printable 的具体操作方案为:拿到 String(类型可推导,所以可省略)数据后,在控制台中输出它

而从例子代码中的注释分析中可以得到,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out 对象中的 println(String) 方法

所以,直接利用方法引用的方式,将Lambda表达式的代码再进行优化成 printString(System.out::println);即可

  • 方法引用符

双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方 法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

语义分析

例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于 printString 方法的函数式接口参数,对比下面两种写法,完全等效:

  1. Lambda表达式写法: s -> System.out.println(s);
  2. 方法引用写法: System.out::println

第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。

第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全一 样,而第二种方法引用的写法复用了已有方案,更加简洁。

注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常

推导和省略

如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都 将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。

函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。

  • 通过对象名引用成员方法

这是常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法

/*
    定义一个打印的函数式接口
 */
@FunctionalInterface
public interface Printable {
    //定义字符串的抽象方法
    void print(String s);
}

public class MethodRerObject {
    //定义一个成员方法,传递字符串,把字符串按照大写输出
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}

那么当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的Lambda的时候,已经具有了 MethodRefObject 类的对象实例,则可以通过对象名引用成员方法,代码为:

/*
    通过对象名引用成员方法
    使用前提是对象名是已经存在的,成员方法也是已经存在
    就可以使用对象名来引用成员方法
 */
public class Demo01ObjectMethodReference {
    //定义一个方法,方法的参数传递Printable接口
    public static void printString(Printable p){
        p.print("Hello");
    }

    public static void main(String[] args) {
        //调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda表达式
        printString((s)->{
            //创建MethodRerObject对象
            MethodRerObject obj = new MethodRerObject();
            //调用MethodRerObject对象中的成员方法printUpperCaseString,把字符串按照大写输出
            obj.printUpperCaseString(s);
        });

        /*
            使用方法引用优化Lambda
            对象是已经存在的MethodRerObject
            成员方法也是已经存在的printUpperCaseString
            所以我们可以使用对象名引用成员方法
         */
        //创建MethodRerObject对象
        MethodRerObject obj = new MethodRerObject();
        printString(obj::printUpperCaseString);
    }
}
  • 通过类名称引用静态方法

由于在 java.lang.Math 类中已经存在了静态方法 abs ,所以当我们需要通过Lambda来调用该方法时,有两种写 法。首先是函数式接口

@FunctionalInterface
public interface Calcable {
    //定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
    int calsAbs(int number);
}

两种写法的对比:

/*
    通过类名引用静态成员方法
    类已经存在,静态成员方法也已经存在
    就可以通过类名直接引用静态成员方法
 */
public class Demo01StaticMethodReference {
    //定义一个方法,方法的参数传递要计算绝对值的整数,和函数式接口Calcable
    public static int method(int number,Calcable c){
       return c.calsAbs(number);
    }

    public static void main(String[] args) {
        //调用method方法,传递计算绝对值得整数,和Lambda表达式
        int number = method(-10,(n)->{
            //对参数进行绝对值得计算并返回结果
            return Math.abs(n);
        });
        System.out.println(number);

        /*
            使用方法引用优化Lambda表达式
            Math类是存在的
            abs计算绝对值的静态方法也是已经存在的
            所以我们可以直接通过类名引用静态方法
         */
        int number2 = method(-10,Math::abs);
        System.out.println(number2);
    }
}

在这个例子中,下面两种写法是等效的:

  1. Lambda表达式: n -> Math.abs(n)

  2. 方法引用: Math::abs

  • 通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口:

/*
    定义见面的函数式接口
 */
@FunctionalInterface
public interface Greetable {
    //定义一个见面的方法
    void greet();
}

然后是父类 Human 的内容:

/*
    定义父类
 */
public class Human {
    //定义一个sayHello的方法
    public void sayHello(){
        System.out.println("Hello 我是Human!");
    }
}

后是子类 Man 的内容,其中使用了Lambda的写法:

/*
    定义子类
 */
public class Man extends Human{
    //子类重写父类sayHello的方法
    @Override
    public void sayHello() {
        System.out.println("Hello 我是Man!");
    }

    //定义一个方法参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        //调用method方法,方法的参数Greetable是一个函数式接口,所以可以传递Lambda
        /*method(()->{
            //创建父类Human对象
            Human h = new Human();
            //调用父类的sayHello方法
            h.sayHello();
        });*/

        //因为有子父类关系,所以存在的一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法
       /* method(()->{
            super.sayHello();
        });*/

      /*
           使用super引用类的成员方法
           super是已经存在的
           父类的成员方法sayHello也是已经存在的
           所以我们可以直接使用super引用父类的成员方法
       */
      method(super::sayHello);
    }

    public static void main(String[] args) {
        new Man().show();
    }
}

在这个例子中,下面两种写法是等效的:

  1. Lambda表达式: () -> super.sayHello()
  2. 方法引用: super::sayHello
  • 通过this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方 法引用。首先是简单的函数式接口:

/*
    定义一个富有的函数式接口
 */
@FunctionalInterface
public interface Richable {
    //定义一个想买什么就买什么的方法
    void buy();
}

函数式接口即可以使用Lambda表达式或者引用方法来执行

/*
    使用this引用本类的成员方法
 */
public class Husband {
    //定义一个买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院!");
    }

    //定义一个结婚的方法,参数传递Richable接口
    public void marry(Richable r){
        r.buy();
    }

    //定义一个非常高兴的方法
    public void soHappy(){
        //调用结婚的方法,方法的参数Richable是一个函数式接口,传递Lambda表达式
       /* marry(()->{
            //使用this.成员方法,调用本类买房子的方法
            this.buyHouse();
        });*/

        /*
            使用方法引用优化Lambda表达式
            this是已经存在的
            本类的成员方法buyHouse也是已经存在的
            所以我们可以直接使用this引用本类的成员方法buyHouse
         */
        marry(this::buyHouse);
    }

    public static void main(String[] args) {
        new Husband().soHappy();
    }
}
  • 类的构造器引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。首先是一个简单 的 Person 类:

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然后是用来创建 Person 对象的函数式接口:

/*
    定义一个创建Person对象的函数式接口
 */
@FunctionalInterface
public interface PersonBuilder {
    //定义一个方法,根据传递的姓名,创建Person对象返回
    Person builderPerson(String name);
}

要使用这个函数式接口,可以通过Lambda表达式或方法引用:

/*
    类的构造器(构造方法)引用
 */
public class Demo {
    //定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
    public static void printName(String name,PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        //调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda
        printName("迪丽热巴",(String name)->{
            return new Person(name);
        });

        /*
            使用方法引用优化Lambda表达式
            构造方法new Person(String name) 已知
            创建对象已知 new
            就可以使用Person引用new创建对象
         */
        printName("古力娜扎",Person::new);//使用Person类的带参构造方法,通过传递的姓名创建对象
    }
}
  • 数组的构造器引用

数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时, 需要一个函数式接口:

/*
    定义一个创建数组的函数式接口
 */
@FunctionalInterface
public interface ArrayBuilder {
    //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
    int[] builderArray(int length);
}

在应用该接口的时候,可以通过Lambda表达式或方法引用:

/*
    数组的构造器引用
 */
public class Demo {
    /*
        定义一个方法
        方法的参数传递创建数组的长度和ArrayBuilder接口
        方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
     */
    public static int[] createArray(int length, ArrayBuilder ab){
        return  ab.builderArray(length);
    }

    public static void main(String[] args) {
        //调用createArray方法,传递数组的长度和Lambda表达式
        int[] arr1 = createArray(10,(len)->{
            //根据数组的长度,创建数组并返回
            return new int[len];
        });
        System.out.println(arr1.length);//10

        /*
            使用方法引用优化Lambda表达式
            已知创建的就是int[]数组
            数组的长度也是已知的
            就可以使用方法引用
            int[]引用new,根据参数传递的长度来创建数组
         */
        int[] arr2 =createArray(10,int[]::new);
        System.out.println(Arrays.toString(arr2));
        System.out.println(arr2.length);//10
    }
}
59.Junit测试
  • 测试分类
  1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
  2. 白盒测试:需要写代码的。关注程序具体的执行流程。(推荐学会)

在这里插入图片描述

  • 使用步骤
  1. 定义一个测试类(测试用例)

    建议:

    ​ 命名:测试类名:被测试的类名Test

    ​ 包名:xx.xxx.test

  2. 定义测试方法:可以独立运行

    建议:

    ​ 方法名:test测试的方法名

    ​ 返回值:void(独立运行,不需要)

    ​ 参数列表:空参(独立运行,不需要其他传参帮助运行)

  3. 给方法加 @Test

  4. 导入Junit依赖环境

  5. 判断结果:

    红色:失败(测试可以直接运行,不需要main方法,如果成功,在控制台那边会呈现对应的颜色框,IDEA环境下)

    绿色:成功

    一搬使用断言操作来处理结果

    Assert.assertEquals(期望的结果,运算的结果);

    如果预期结果和运算结果不同,程序会报异常

被测试类

public class Caculator{
    public int add(int a, int b){
        return a+b;
    }
    public int sub(int a, int b){
        return a-b;
    }
}

测试类

public class CaculatorTest{
    @Test
    public void testAdd(){
       	//System.out.println("我被执行了");
        //1.创建计算器对象
        calculator c = new Calculator();
        
        //2.调用add方法
        int result = c.add( a: 1,b: 2);
        
        //System.out.println(result);
        //3.断言﹐我断言这个结果是3
        Assert.assertEquals( 3,result);
	}
    @Test
    public void testSub(){
        //1.创建计算器对象
        Calculator c = new Calculator();
        int result = c.sub( 1, 2);
        Assert.assertEquals( -1,result);
    }
}

补充

  1. 在测试类中,可以创建两个通用方法init()close()方法用于初始化的申请资源和最后的资源释放
  2. init()方法可以用@Before声明,修饰的方法会在测试方法之前被执行
  3. close()方法可以用@After声明,修饰的方法会在测试方法执行之后执行
60.反射

反射是框架设计的灵魂,将类的各个组成部分封装为其他对象,这就是反射机制

框架:半成品软件,可以在框架的基础上进行软件编程开发,简化代码

在这里插入图片描述

  • 好处:

    • 可以在程序运行过程中,操作这些对象。
    • 可以解耦,提高程序的可扩展性。
  • 获取Class对象的方式

  1. Class.forName("全类名")∶将字节码文件加载进内存,返回Class对象

    多用于配置文件,将类名定义在配置文件中。读取文件,加载类

  2. 类名.class:通过类名的属性class获取

    多用于参数的传递

  3. 对象.getclass():getclass()方法在object类中定义着。

    多用于对象的获取字节码的方式

public class Person {
    private String name;
    private int age;
    //默认空参,满参,set和get,toString方法已写
}
public class ReflectDemo1 {
    public static void main(String[] args) throws Exception {
        //1.Class.forName("全类名")
        Class cls1 = Class.forName( "cn.itcast.domian.Person");//包名.类名
        System.out.println(cls1);
        
        //2.类名.class
        Class cls2 = Person.class;
        System.out.println(cls2);
        
        //3.对象.getClass()
        Person p = new Person();
        class cls3 = p.getClass();
        System.out.println(cls3);
        //== 比较三个对象
        System.out.println(cls1 == cls2);//true
        System.out.println(cls1 == cls3);//true
                               
    }
}

结论

同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。

  • Class对象的功能函数
public class Person {
    private String name;
    private int age;
    //默认空参,满参,set和get,toString方法已写
    
    public String a;
    protected String a;
    default String a;
    private String a;
}
  1. 获取成员变量们

    Field[] getFields(): 获取所有public修饰的成员变量

    Field getField(string name):获取指定名称的成员变量

    Field[] getDeclaredFields():获取所有的成员变置,不考虑修饰符

    Field getDeclaredField(string name)3

    public class ReflectDemo2{
    	public static void main(String[] args)throws Exception{
            //0.获取Person的class对象
    		Class personClass = Person.class;
            
            //1.Field[] getFields()获取所有public修饰的成员变量
            Field[] fields = personClass.getFields();
            for (Field field : fields) {
            	System.out.println(field);
            }
            System.out.println("--------------");
            //Field getField(string name):获取指定名称的成员变量
    		Field a = personClass.getField("a");
    		//荻取成员整量a的值
            Person p = new Person();
            Object value = a.get(p);
            System.out.println(value);
    		//设置a的值
            a.set(p,"张三");
            system.out.println(p);
    
            System.out.println("=========");
            //Field[] getDeclaredFields():获取所有的成员变置,不考虑修饰符
            Field[] declaredFields = personClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
            	System.out.println(declaredField);
            }
    
            //Field getDeclaredField(String name):获取指定命名的成员变量,且不考虑权限
            Field d = personClass.getDeclaredField( name: "d" );
            //忽略访问权限修饰符的安全检查
            d.setAccessible(true);//暴力反射,没有这行代码的会报异常,且无法获取值
            object value2 = d.get(p);
            System.out.println(value2);
    
        }
    }
    

    Field:成员变量
    操作:
    1.设置值void set(object obj,object value)

    2.获取值get(object obj)

    3.忽略访问权限修饰符的安全检查setAccesible(true)暴力反射

  2. 获取构造方法们

    constructor<?>[]getconstructors():获取构造方法们

    constructor<T> getconstructor(类<?>... parameterTypes):获取指定参数的构造方法

    constructor<T>getDeclaredconstructor(类<?>... parameterTypes):Declared即代表忽略权限修饰,都是获取构造方法(如果要复制,则需要进行暴力反射,即constructor1.setAccesible(true)

    constructor<?>[]getDeclaredconstructors()

    public class ReflectDemo3{
    	public static void main(String[] args)throws Exception{
            //0.获取Person的class对象
    		Class personClass = Person.class;
            
           //Constructor<T>getConstructor(类<?>... parameterTypes),这个方法的参数和对应的构造方法有关,如果构造方法为Person(String name,int age),则对应的获取构造器写法如下
    		Constructor constructor = personClass.getConstructor(String.classint.class);
    		System.out.println(constructor);
    		//创建对象
            Object person = constructor.newInstance( "张三"23);
            System.out.println(person);
            System.out.println("--------------");
            
            Constructor constructor1 = personClass.getConstructor();
    		System.out.println(constructor);
    		//创建对象
            Object person1 = constructor1.newInstance( );
            System.out.println(person1);
    
            //java9之后就没有再使用了
            Object o = personClass.newInstance();
    		System.out.println(o);
    
    		
        }
    }
    

    Constructor :构造方法

    1.创建对象:T newInstance(object... initargs);如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法

  3. 获取成员方法们

    Method[]getMethods():获取公共方法

    Method getMethod(string name,类<?>... parameterTypes):获取指定名称和指定参数的公共方法

    Method[] getDeclaredMethods():忽略修饰符获取方法

    Method getDeclaredMethod(string name,类<?>... parameterTypes)

    //在Person类中添加一些成员方法
    public class Person {
       //其他的和前面一致
        public void eat(){
    		System.out.println( "eat...");
    	}
        public void eat(String food){
    		System.out.println( "eat..."+food);
    	}
    }
    
    public class ReflectDemo4{
    	public static void main(String[] args)throws Exception{
            //0.获取Person的class对象
    		Class personClass = Person.class;
            
            //获取指定名称的方法
            Method eat_method = personClass.getMethod( "cat");
            Person p = new Person();
    		//执行方法
    		eat_method.invoke(p);
    
            Method eat_method2 = personClass.getMethod("eat",String.class);
            //执行方法
            eat_method2.invoke(p,"饭");
    
            system.out.println("-----------");
            //获取所有public修饰的方法
            Method[]methods = peronClass.getMethods();
            for (Method method : methods) {
            	System.out.println(method);//会将父类中的公共方法也打印处理
                //method.setAccessible(true);//也支持暴力反射
                String name = method .getName();
    			System.out.println(name);
            }
        }
    }		
    

    Method:方法对象

    1.执行方法:Object invoke(Object obj,Object... args)

    2.获取方法名称:String getName:获取方法名

  4. 获取类名

    String getName():获取类名

public class ReflectDemo4{
	public static void main(String[] args)throws Exception{
        //0.获取Person的class对象
		Class personClass = Person.class;
        //获取类名
        String className =personClass.getName();
        system.out.println(className);
    }
}
  • 案例

需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。

实现

​ 1.配置文件

​ 2.反射

步骤

1.将需要创建的对象的全类名和需要执行的方法定义在配置文件中

2.在程序中加载读取配宣文件

3.使用反射技术来加载类文件进内存

4.创建对象

5.执行方法

正常操作以及对象实现:

创建一个Person类和Student类

public class Person {
    private String name;
    private int age;
    //默认空参,满参,set和get,toString方法已写

    public void eat(){
		System.out.println( "eat...");
	}
    public void eat(String food){
		System.out.println( "eat..."+food);
	}
}
public class Student {
	public void sleep(){
		System.out. println("sleep.. ");
	}
}

假设的框架类

/**
*框架类
*/
public class ReflectTest {
	public static void main(String[] args){
        //可以创建任意类的对象,可以执行任意方法
        /*
            前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
        */
       /* Person p_ = new_Person();
        p.eat();
        */
        //这里可以修改代码,不符合框架类是一个半成品软件的定义
        Student stu = new Student();
        stu.sleep();
    }
}

实现框架类的操作

创建一个配置文件,pro.properties,然后在里面进行配置

className=cn.itcast.domain.Person
methodName=eat

然后将上面的正常操作代码进行修改

/**
*框架类
*/
public class ReflectTest throws Exception{
	public static void main(String[] args){
        //1.加载配置文件
        //1.1创建Properties对象
        Properties pro= newProperties();//properties是唯一和IO流有关的集合
        //1.2加载配置文件,转换为一个集合
        //1.2.1获取class目录下的配置文件
        /*
        要找到配置文件,需要用类加载器来获取,而要获取ReflectTest的字节码文件则用类名.class,
        然后再用其成员方法中的getClassLoader()来获取类加载器,并把ReflectTest加载进内存
        因为ClassLoader()可以获取对应目录下的类文件,所以也可以获得我们写的配置文件
        */
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsstream("pro.properties");//获取字节流
        pro.load(is);

        //2.获取配置文件中定义的数据,使用集合操作获取值
        String className = pro.getProperty( "c1assName" );
		String methodName = pro.getProperty( "methodName" );

        //3.加载该类进内存
		Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);

		//6.执行方法
		method.invoke(obj);
        
        //这样就可以不改变该类的代码,通过改动配置文件来实现所要的功能来实现
    }
}
61.注解

用于说明程序的,是给计算机看的
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引又的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元秦进行说明,注释。

  • 分类

    • 编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
    • 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
    • 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【override】
  • JDK中预定义的一些注解

    • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
    • @Deprecated:该注解标注的内容,表示已过时
    • @SuppressWarnings:压制警告
@SuppressWarnings("all")//会使类的所以警告都消失
public class AnnoDemo2 {
    @override
    public String toString() {
        return super.toString();
    }
    
    @Deprecated
    public void show1(){
    	//有缺陷
    }
    
    public void show2(){
    	//替代show1方法
    }
	public void demo(){
        show1();//在IDEA中会出现删除线
    } 
}
  • 自定义注解

格式:

元注解
public @interface注解名称{
    属性列表
}
//注解实例
public @interface MyAnno {
}

将上面的例子注解进行编译在反编译,可得到如下:

public interface MyAnno extends java.lang.annotation.Annotation {}
  • 本质:注解本质上就是一个接口,该接口默认继承Annotation接口

  • 属性:接口中可以定义的成员方法

要求:

1.属性的返回值类型: 基本数据类型、String、枚举、注解、以上类型的数组

2.定义了属性,使用前需要赋值

如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。

如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略。

数组赋值时,值使用包裹。如果数组中只有一个值,则可以不写

public @interface MyAnno {
    public abstract String show();
    //void show();//不可以,会报错
    int age();
    String name default"张三";//默认赋值,不想赋值的时候可以不写
    //int value();
    /*
    String show2();
    Person per();
    MyAnno anno2();
    String[] str2();
    */
}
//枚举类
public enum Person {
	P1,P2;
}
//注解
public @interface MyAnno2{
}
@MyAnno(age = 1/*,name ="李四"*/)
//@MyAnno( 1)//其他数据类型赋值时根据其对应的属性进行赋值即可
public class Worker{

}
  • 元注解

用于描述注解的注解

种类:

  1. @Target:描述注解能够作用的位置

    ElementType取值:

    TYPE :以作用育类上

    METHOD :可以作用于方法上

    FTEILD :可以用于成员变量上

  2. @Retention :描述注解被保留的阶段

    @Retention(RetentionPolicy.RUNTIMNR)∶当前被描述的注解,会保留到class字节码文件中,并被 JVM 读取到

  3. @Documented:描述注解是否被抽取到api文档中

  4. @Inherited:描述注解是否被子类继承

@Target(value={ElementType.TYPE/*,ElementType.METHOD,ElementType.FIELD*/})//TYPE表示该MyArno3注解只能作用于类上
@Retention(RetentionPolicy.RUNTIME)//定义在运行时阶段
@Documented
@Inherited
public @interface MyAnno3{
}

@MyAnno3
public class Worker{
  	// @MyAnno3//会报错,只有TYPE的情况下
	String xxx;
    // @MyAnno3//会报错,只有TYPE的情况下
    public void show();
}
  • 解析注解
/**
*描述需要执行的类名,和方法名
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
public class Demo1 {
    public void show(){
        System.out.println( "demo1.. .show...");
    }
}
public class Demo2 {
    public void show(){
        System.out.println( "demo2.. .show...");
    }
}
//之前的框架类,利用注解进行配置
@Pro(className = "cn.itcast.annotation.Demo1" , methodName = "show")
public class ReflectTest throws Exception{
	public static void main(String[] args){
        //1.解析注解
        //1.1获取该类的字节码文件对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //2.获取上边的注解对象
        //其实就是在内存中生成了一个该注解接口的,类实现对象
        /*生成的类的生成逻辑
        	public class ProImpl implements Pro{
                public String clalsName(){
                    return "cn.itcast.annotation .Demo1";
                }
                public String methodName( ){
                    return "show";
                }
			}
        */
        Pro an = reflectTestClass.getAnnotation(Pro.class);
		//3.调用注解对象中定义的抽象方法,获取返回值
		String className = an.className();
		String methodName = an.methodName( );
        System.out.println(className);
        System.out.println(methodName);
		
        //其他就和配置文件时步骤一致了
        //3.加载该类进内存
		Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);

		//6.执行方法
		method.invoke(obj);
    }
}

在程序使用(解析)注解∶获取注解中定义的属性值

  1. 获取注解定义的位置的对象(class,Method,Field)
  2. 获取指定的注解,getAnnotation(class)
  3. 调用注解中的抽象方法获取配置的属性值
  • 小结
  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用? 编译器给解析程序用
  3. 注解不是程序的一部分,可以理解为注解就是一个标签
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值