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
方法的函数式接口参数,对比下面两种写法,完全等效:
- Lambda表达式写法:
s -> System.out.println(s)
; - 方法引用写法:
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);
}
}
在这个例子中,下面两种写法是等效的:
-
Lambda表达式:
n -> Math.abs(n)
-
方法引用:
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();
}
}
在这个例子中,下面两种写法是等效的:
- Lambda表达式:
() -> super.sayHello()
- 方法引用:
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测试
- 测试分类
- 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
- 白盒测试:需要写代码的。关注程序具体的执行流程。(推荐学会)
- 使用步骤
-
定义一个测试类(测试用例)
建议:
命名:测试类名:
被测试的类名Test
包名:
xx.xxx.test
-
定义测试方法:可以独立运行
建议:
方法名:
test测试的方法名
返回值:void(独立运行,不需要)
参数列表:空参(独立运行,不需要其他传参帮助运行)
-
给方法加
@Test
-
导入Junit依赖环境
-
判断结果:
红色:失败(测试可以直接运行,不需要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);
}
}
补充
- 在测试类中,可以创建两个通用方法
init()
和close()
方法用于初始化的申请资源和最后的资源释放 init()
方法可以用@Before
声明,修饰的方法会在测试方法之前被执行close()
方法可以用@After
声明,修饰的方法会在测试方法执行之后执行
60.反射
反射是框架设计的灵魂,将类的各个组成部分封装为其他对象,这就是反射机制
框架:半成品软件,可以在框架的基础上进行软件编程开发,简化代码
-
好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
-
获取Class对象的方式
-
Class.forName("全类名")
∶将字节码文件加载进内存,返回Class对象多用于配置文件,将类名定义在配置文件中。读取文件,加载类
-
类名.class:
通过类名的属性class获取多用于参数的传递
-
对象.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;
}
-
获取成员变量们
Field[] getFields()
: 获取所有public修饰的成员变量Field getField(string name)
:获取指定名称的成员变量Field[] getDeclaredFields()
:获取所有的成员变置,不考虑修饰符Field getDeclaredField(string name)
3public 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)
暴力反射 -
获取构造方法们
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.class,int.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方法 -
获取成员方法们
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:获取方法名
-
获取类名
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{
}
- 元注解
用于描述注解的注解
种类:
-
@Target:描述注解能够作用的位置
ElementType取值:
TYPE :以作用育类上
METHOD :可以作用于方法上
FTEILD :可以用于成员变量上
-
@Retention :描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIMNR)∶当前被描述的注解,会保留到class字节码文件中,并被 JVM 读取到
-
@Documented:描述注解是否被抽取到api文档中
-
@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);
}
}
在程序使用(解析)注解∶获取注解中定义的属性值
- 获取注解定义的位置的对象(class,Method,Field)
- 获取指定的注解,
getAnnotation(class)
- 调用注解中的抽象方法获取配置的属性值
- 小结
- 以后大多数时候,我们会使用注解,而不是自定义注解
- 注解给谁用? 编译器;给解析程序用
- 注解不是程序的一部分,可以理解为注解就是一个标签