一、注解
1.1 认识注解&定义注解
各位小伙伴,接下来我们学习注解。注解和反射一样,都是用来做框架的,我们这里学习注解的目的其实是为了以后学习框架或者做框架做铺垫的。
那注解该怎么学呢?和反射的学习套路一样,我们先充分的认识注解,掌握注解的定义和使用格式,然后再学习它的应用场景。
先来认识一下什么是注解?
Java注解是代码中的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息决定怎么执行该程序。
比如:Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被@Test标记的方法能够被Junit框架执行。
再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被@Override注解标记的方法能够被IDEA识别进行语法检查。
- 注解不光可以用在方法上,还可以用在类上、变量上、构造器上等位置。
上面我们说的@Test注解、@Overide注解是别人定义好给我们用的,将来如果需要自己去开发框架,就需要我们自己定义注解。
接着我们学习自定义注解
自定义注解的格式如下图所示
比如:现在我们自定义一个MyTest注解
public @interface MyTest{ String aaa(); boolean bbb() default true; //default true 表示默认值为true,使用时可以不赋值。 String[] ccc(); }
定义好MyTest注解之后,我们可以使用MyTest注解在类上、方法上等位置做标记。注意使用注解时需要加@符号,如下
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"}) public class AnnotationTest1{ @MyTest(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"}) public void test1(){ } }
注意:注解的属性名如何是value的话,并且只有value没有默认值,使用注解时value名称可以省略。比如现在重新定义一个MyTest2注解
public @interface MyTest2{ String value(); //特殊属性 int age() default 10; }
定义好MyTest2注解后,再将@MyTest2标记在类上,此时value属性名可以省略,代码如下
@MyTest2("孙悟空") //等价于 @MyTest2(value="孙悟空") @MyTest1(aaa="牛魔王",ccc={"HTML","Java"}) public class AnnotationTest1{ @MyTest(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"}) public void test1(){ } }
到这里关于定义注解的格式、以及使用注解的格式就学习完了。
注解本质是什么呢?
想要搞清楚注解本质是什么东西,我们可以把注解的字节码进行反编译,使用XJad工具进行反编译。经过对MyTest1注解字节码反编译我们会发现:
1.MyTest1注解本质上是接口,每一个注解接口都继承子Annotation接口 2.MyTest1注解中的属性本质上是抽象方法 3.@MyTest1实际上是作为MyTest接口的实现类对象 4.@MyTest1(aaa="孙悟空",bbb=false,ccc={"Python","前端","Java"})里面的属性值,可以通过调用aaa()、bbb()、ccc()方法获取到。 【别着急,继续往下看,再解析注解时会用到】
3.2 元注解
各位小伙伴,刚才我们已经认识了注解以及注解的基本使用。接下来我们还需要学习几种特殊的注解,叫做元注解。
什么是元注解?
元注解是修饰注解的注解。这句话虽然有一点饶,但是非常准确。我们看一个例子
接下来分别看一下@Target注解和@Retention注解有什么作用,如下图所示
@Target是用来声明注解只能用在那些位置,比如:类上、方法上、成员变量上等 @Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期
- @Target元注解的使用:比如定义一个MyTest3注解,并添加@Target注解用来声明MyTest3的使用位置
@Target(ElementType.TYPE) //声明@MyTest3注解只能用在类上 public @interface MyTest3{ }
接下来,我们把@MyTest3用来类上观察是否有错,再把@MyTest3用在方法上、变量上再观察是否有错
如果我们定义MyTest3注解时,使用@Target注解属性值写成下面样子
//声明@MyTest3注解只能用在类上和方法上 @Target({ElementType.TYPE,ElementType.METHOD}) public @interface MyTest3{ }
此时再观察,@MyTest用在类上、方法上、变量上是否有错
到这里@Target元注解的使用就演示完毕了。
- @Retetion元注解的使用:定义MyTest3注解时,给MyTest3注解添加@Retetion注解来声明MyTest3注解保留的时期
@Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期 @Retetion(RetetionPloicy.SOURCE): 注解保留到源代码时期、字节码中就没有了 @Retetion(RetetionPloicy.CLASS): 注解保留到字节码中、运行时注解就没有了 @Retetion(RetetionPloicy.RUNTIME):注解保留到运行时期 【自己写代码时,比较常用的是保留到运行时期】
//声明@MyTest3注解只能用在类上和方法上 @Target({ElementType.TYPE,ElementType.METHOD}) //控制使用了@MyTest3注解的代码中,@MyTest3保留到运行时期 @Retetion(RetetionPloicy.RUNTIME) public @interface MyTest3{ }
3.3 解析注解
各位小伙伴,通过前面的学习我们能够自己定义注解,也能够把自己定义的注解标记在类上或者方法上等位置,但是总感觉有点别扭,给类、方法、变量等加上注解后,我们也没有干什么呀!!!
接下来,我们就要做点什么。我们可以通过反射技术把类上、方法上、变量上的注解对象获取出来,然后通过调用方法就可以获取注解上的属性值了。我们把获取类上、方法上、变量上等位置注解及注解属性值的过程称为解析注解。
解析注解套路如下
1.如果注解在类上,先获取类的字节码对象,再获取类上的注解 2.如果注解在方法上,先获取方法对象,再获取方法上的注解 3.如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解 总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解
解析来看一个案例,来演示解析注解的代码编写
按照需求要求一步一步完成
① 先定义一个MyTest4注解
//声明@MyTest4注解只能用在类上和方法上 @Target({ElementType.TYPE,ElementType.METHOD}) //控制使用了@MyTest4注解的代码中,@MyTest4保留到运行时期 @Retetion(RetetionPloicy.RUNTIME) public @interface MyTest4{ String value(); double aaa() default 100; String[] bbb(); }
② 定义有一个类Demo
@MyTest4(value="蜘蛛侠",aaa=99.9, bbb={"至尊宝","黑马"}) public class Demo{ @MyTest4(value="孙悟空",aaa=199.9, bbb={"紫霞","牛夫人"}) public void test1(){ } }
③ 写一个测试类AnnotationTest3解析Demo类上的MyTest4注解
public class AnnotationTest3{ @Test public void parseClass(){ //1.先获取Class对象 Class c = Demo.class; //2.解析Demo类上的注解 if(c.isAnnotationPresent(MyTest4.class)){ //获取类上的MyTest4注解 MyTest4 myTest4 = (MyTest4)c.getDeclaredAnnotation(MyTest4.class); //获取MyTests4注解的属性值 System.out.println(myTest4.value()); System.out.println(myTest4.aaa()); System.out.println(myTest4.bbb()); } } @Test public void parseMethods(){ //1.先获取Class对象 Class c = Demo.class; //2.解析Demo类中test1方法上的注解MyTest4注解 Method m = c.getDeclaredMethod("test1"); if(m.isAnnotationPresent(MyTest4.class)){ //获取方法上的MyTest4注解 MyTest4 myTest4 = (MyTest4)m.getDeclaredAnnotation(MyTest4.class); //获取MyTests4注解的属性值 System.out.println(myTest4.value()); System.out.println(myTest4.aaa()); System.out.println(myTest4.bbb()); } } }
3.4 注解的应用场景
各位同学,关于注解的定义、使用、解析注解就已经学习完了。接下来,我们再学习一下注解的应用场景,注解是用来写框架的,比如现在我们要模拟Junit写一个测试框架,要求有@MyTest注解的方法可以被框架执行,没有@MyTest注解的方法不能被框架执行。
第一步:先定义一个MyTest注解
@Target(ElementType.METHOD) @Retetion(RetetionPloicy.RUNTIME) public @interface MyTest{ }
第二步:写一个测试类AnnotationTest4,在类中定义几个被@MyTest注解标记的方法
public class AnnotationTest4{ @MyTest public void test1(){ System.out.println("=====test1===="); } @MyTest public void test2(){ System.out.println("=====test2===="); } public void test3(){ System.out.println("=====test2===="); } public static void main(String[] args){ AnnotationTest4 a = new AnnotationTest4(); //1.先获取Class对象 Class c = AnnotationTest4.class; //2.解析AnnotationTest4类中所有的方法对象 Method[] methods = c.getDeclaredMethods(); for(Method m: methods){ //3.判断方法上是否有MyTest注解,有就执行该方法 if(m.isAnnotationPresent(MyTest.class)){ m.invoke(a); } } } }
恭喜小伙伴们,学习到这里,关于注解的使用就学会了(▽)
二、内部类与匿名内部类
内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。
当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
比如:汽车、的内部有发动机,发动机是包含在汽车内部的一个完整事物,可以把发动机设计成内部类。
public class Car{ //内部类 public class Engine{ } }
内部类有四种形式,分别是成员内部类、静态内部类、局部内部类、匿名内部类。
我们先来学习成员内部类
1.1 成员内部类
成员内部类就是类中的一个普通成员,类似于成员变量、成员方法。
public class Outer { private int age = 99; public static String a="黑马"; // 成员内部类 public class Inner{ private String name; private int age = 88; //在内部类中既可以访问自己类的成员,也可以访问外部类的成员 public void test(){ System.out.println(age); //88 System.out.println(a); //sisa int age = 77; System.out.println(age); //77 System.out.println(this.age); //88 System.out.println(Outer.this.age); //99 } 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; } } }
成员内部类如何创建对象,格式如下
//外部类.内部类 变量名 = new 外部类().new 内部类(); Outer.Inner in = new Outer().new Inner(); //调用内部类的方法 in.test();
总结一下内部类访问成员的特点
- 既可以访问内部类成员、也可以访问外部类成员
- 如果内部类成员和外部类成员同名,可以使用**
类名.this.成员
**区分
1.2 静态内部类
静态内部类,其实就是在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有。
public class Outer { private int age = 99; public static String schoolName="sisa"; // 静态内部类 public static class Inner{ //静态内部类访问外部类的静态变量,是可以的; //静态内部类访问外部类的实例变量,是不行的 public void test(){ System.out.println(schoolName); //99 //System.out.println(age); //报错 } } }
静态内部类创建对象时,需要使用外部类的类名调用。
//格式:外部类.内部类 变量名 = new 外部类.内部类(); Outer.Inner in = new Outer.Inner(); in.test();
1.3 局部内部类
局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。
public class Outer{ public void test(){ //局部内部类 class Inner{ public void show(){ System.out.println("Inner...show"); } } //局部内部类只能在方法中创建对象,并使用 Inner in = new Inner(); in.show(); } }
1.4 匿名内部类
1.4.1 认识匿名内部类,基本使用
各位同学,接下来学习一种再实际开发中用得最多的一种内部类,叫匿名内部类。相比于前面几种内部类,匿名内部类就比较重要的。
我们还是先认识一下什么是匿名内部类?
匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字。
下面就是匿名内部类的格式:
new 父类/接口(参数值){ @Override 重写父类/接口的方法; }
匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象。
比如,先定义一个Animal抽象类,里面定义一个cry()方法,表示所有的动物有叫的行为,但是因为动物还不具体,cry()这个行为并不能具体化,所以写成抽象方法。
public abstract class Animal{ public abstract void cry(); }
接下来,我想要在不定义子类的情况下创建Animal的子类对象,就可以使用匿名内部类
public class Test{ public static void main(String[] args){ //这里后面new 的部分,其实就是一个Animal的子类对象 //这里隐含的有多态的特性: Animal a = Animal子类对象; Animal a = new Animal(){ @Override public void cry(){ System.out.println("猫喵喵喵的叫~~~"); } } a.eat(); //直线上面重写的cry()方法 } }
需要注意的是,匿名内部类在编写代码时没有名字,编译后系统会为自动为匿名内部类生产字节码,字节码的名称会以外部类$1.class
的方法命名
匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。
1.4.2 匿名内部类的应用场景
学习完匿名内部类的基本使用之后,我们再来看一下匿名内部类在实际中的应用场景。其实一般我们会主动的使用匿名内部类。
**只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法。**这样就可以少写一个类。比如,看下面代码
public interface Swimming{ public void swim(); }
public class Test{ public static void main(String[] args){ Swimming s1 = new Swimming(){ public void swim(){ System.out.println("狗刨飞快"); } }; go(s1); Swimming s1 = new Swimming(){ public void swim(){ System.out.println("猴子游泳也还行"); } }; go(s1); } //形参是Swimming接口,实参可以接收任意Swimming接口的实现类对象 public static void go(Swimming s){ System.out.println("开始~~~~~~~~"); s.swim(); System.out.println("结束~~~~~~~~"); } }
最新Java基础系列课程--Day13-高级特性(二)https://developer.aliyun.com/article/1423543