对java泛型、枚举、注解以及反射的基础学习

泛型、枚举、注解和反射的初步了解

2 泛型

泛型是JDK1.5及以上才可以使用的特性/语法,它的本质是 类型参数化(Parameterized by types).

2.1 概述

在声明一个类、接口、方法的时候,需要涉及到到一个问题:要给属性确定一个类型,或者给方法的返 回值确定一个类型,或者给方法的参数确定一个类型

之前,定义类、接口、方法的时候,上面所描述的类型都是直接写死,不会变化的

public class Point{
    int x;
    int y;
}

Point 类表示一个坐标点,它的的俩个属性 x 坐标和 y 坐标的类型是 int ,这个 int 是在定义 Point 类的时候就已经写好了,并且不会自动改变。

现在,希望 Point 类中的 x 属性和 y 属性的类型变的灵活一点,可以在将来使用的时候,临时通过传 参的方式,来确定属性x和y的具体类型。

修改 Point 类的代码,添加泛型参数T:

public class Point<T>{
    T x;
    T y;
}
  • T是泛型参数,表示一种数据类型,具体是什么类型,需要将来使用Point的时候进行传参来确定;
  • 如果将来Piont在使用的时候,没有给泛型参数传值,那么T默认就表示为Object类型;
  • T是泛型参数的名字,也就是相当于形参,名字随便取,但是一般用一个有意义的大写字母;

Point<T> 类的使用:

public class Point<T> {
    T x;
    T y;

    public static void main(String[] args) {
        //p1对象中x和y的属性都是Integer
        Point<Integer> point=new Point<Integer>();
        point.x=1;
        point.y=2;
        System.out.println(point.x+""+point.y);

        Point<String> p2=new Point<String>()  ;
      p2.x="1";
      p2.y="2";
        System.out.println(p2.x);

        //p3对象中x和y的属性类型都是Object
        Point p3 = new Point();
        p3.x = new Object();
        p3.y = new Object();
        System.out.println(p3.y);

    }
}

可以看出,Point类中的x和y属性的类型,是可以根据我们在使用时所传的参数,进行临时变化的

如果没有传这个泛型参数,那么这个参数T默认就是Object类型;

注意,给泛型参照传的值,只能是引用类型,不能是基本类型:Point<int>编译报错

在类上面添加泛型参数后,在类中的任何可以使用类型的地方,都可以先使用这个泛型参数:

class Point<T>{
    private T x;
    private T y;
    public Point(){}
    public Point(T x, T y) {
        this.x = x;
        this.y = y;
    }
    public T getX() {
    	return x;
    }
    public void setX(T x) {
    	this.x = x;
    }
    public T getY() {
    	return y;
    }
    public void setY(T y) {
    	this.y = y;
    }
}

思考,为什么泛型也叫做 类型参数化(Parameterized by types)

因为这里的类型是不确定的,通过传递泛型参数来确定是什么类型;

2.2 集合的泛型

了解泛型的意思之后,接下来可以再看下之前学习过的集合中的泛型:

public interface Collection<E>{
	boolean add(E e);
}

Collection是一个泛型接口,泛型参数是E,在接口中,add方法的参数类型也使用了E。

那么说明在使用Collection接口的时候,如果没有给泛型参数传值,那么这个E就默认表示为Object,add方法就可以接受任意类型的对象。

public static void main(String[] args) {
//没有给泛型参数传值,那么泛型默认表示为Object类型
	Collection c = new ArrayList();
    c.add("hello1");
    c.add("hello2");
    c.add("hello3");
    c.add(1);
    for(Object obj:c){
        String str = (String) obj;
        System.out.println(str);
    }
}
//运行结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot
be cast to java.lang.String

在这种情况下,集合中如果存储的数据类型不一致,就会在强制转换的时候出现类型转换异常

如果在使用Collection接口的时候,给泛型参数指定了具体类型,那么就会防止出现类型转换异常的情 况,因为这时候集合中添加的数据已经有了一个规定的类型,其他类型是添加不进来的。

public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("hello1");
        c.add("hello2");
        c.add("hello3");
//        c.add(1);
        for(String str:c){
            System.out.println(str);
        }
    }

可以看出,传入泛型参数后,add方法只能接收String类型的参数,其他类型的数据无法添加到集 合中,同时在遍历集合的时候,也不需要我们做类型转换了,直接使用String类型变量接收就可以 了,JVM会自动转换的

Collection c = new ArrayList();

可以简写成:

Collection c = new ArrayList<>();

Map 接口使用泛型:

public interface Map<K,V>{
    V put(K key, V value);
    Set<Map.Entry<K, V>> entrySet();
}
public static void main(String[] args) {
    Map<Integer,String> map=new HashMap<>();
    map.put(1,"a");
    map.put(2,"b");
    map.put(3,"c");

    //根据上面列出的源码可知,当前指定Map的泛型类型为:Map<Integer,String> map
    //entrySet方法返回的类型就应该是Set<Map.Entry<Integer, String>>
    Set<Map.Entry<Integer,String>> entrySet=map.entrySet();

    for (Map.Entry entry:entrySet){
        System.out.println(entry.getKey()+" "+entry.getValue());
    }
}

2.3 泛型的种类

java中的泛型分三种情况使用:

  • 泛型类;
  • 泛型接口;
  • 泛型方法

泛型类:如果泛型参数定义在类上面,那么这个类就是一个泛型类;

在类中,就可以使用这个T来代表某一个类型,这个类型具体是什么将来使用的时候再传参确定;

public class Point<T>{...}
public static void main(String[] args){
	Point<String> p = new Point<>();
}

泛型接口,如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口;

再接口中,就利用使用这个T来代替某一个类型,这个类型具体是什么将来使用的时候再传参确定;

public interface Action<T>{...}
public static void main(String[] args){
    //创建匿名内部类
    Action<String> a = new Action<>(){
    	//...
    };
}

泛型方法:如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法;

public class Test{
    public <T> T test(T t){
    	//..
    }
}
public static void main(String[] args){
    Test t = new Test();
    String str = t.test("hello");
    Integer i = t.test(1);
    Double d = t.test(10.5D);
}

可以看出,调用test方法时候,我们传什么类型的参数,test方法的返回类型就是什么类型的。 这种效果,在不使用泛型的情况下,是不可能实现的。

2.4 泛型的类型

先看俩种错误的情况:

//编译通过
//父类型的引用,指向子类对象
Object o = new Integer(1);
//编译通过
//Object[]类型兼容所有的【引用】类型数组
//arr可以指向任意 引用类型 数组对象
Object[] arr = new Integer[1];
//编译失败
//注意,这个编译报错,类型不兼容
//int[] 是基本类型数组
Object[] arr = new int[1];
//编译失败
//错误信息:ArrayList<Integer>无法转为ArrayList<Object>
//在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关ArrayList<Object> list = new ArrayList<Integer>();

虽然 IntegerObject 的子类型,但是 ArrayListArrayList 之间没有子 父类型的关系,它们就是俩个不同的类型

所以, Object o = new Integer(1);

编译通过 ArrayList list = new ArrayList();

编译报错 也就是说,俩个类型,如果是当做泛型的指定类型的时候,就没有多态的特点了

2.5 通配符

观察下面代码:

public void test1(Collection<Integer> c){
}
public void test2(Collection<String> c){
}
public void test3(Collection<Object> c){
}
  • test1方法只能接受泛型是Integer类型的集合对象
  • test2方法只能接受泛型为String类型的集合对象;
  • test3方法只能接受泛型是Object类型的集合对象;

原因:由于泛型的类型之间没有多态,所以=号俩边的泛型类型必须一致

在这种情况下,就可以使用通配符(?)来表示泛型的父类型:

public void test(Collection<?> c){
}

注意,这时候test方法中的参数类型,使用了泛型,并且使用问号来表示这个泛型的类型,这个问 号就是通配符,可以匹配所有的泛型类型

test方法可以接收 泛型是任意类型的 Collection集合对象

public static void main(String[] args){
    Test t = new Test();
    t.test(new ArrayList<String>());
    t.test(new ArrayList<Integer>());
    t.test(new ArrayList<Object>());
    t.test(new ArrayList<任意类型>());
}

使用通配符(?)所带来的问题:

Collection<?> c;
c = new ArrayList<String>();
//编译报错
//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?)
//那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符
//所以,编译器不允许使用变量c来向集合中添加新数据。
c.add("hello");
//编译通过
//但是有一个值是可以添加到集合中的,null
//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
c.add(null);

虽然使用通配符(?)的集合,不能再往其中添加数据了,但是可以遍历集合取出数据:

public class Tpeifu {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello1");
        list.add("hello2");
        list.add("hello3");
        list.add("hello4");

        Collection<?> c=list;
        c.add(null);
        //编译报错
        //c.add("hello5");

        for (Object o:c){
            System.out.println(o);
        }
    }
}

2.6 泛型边界

在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。

如果在泛型中使用 extendssuper 关键字,就可以对泛型的类型进行限制。即:规定泛型的上限和 下限。

泛型的上限:

  • 例如: List <? extends Number> list;
  • 将来引用list就可以接收泛型是 Number 或者 Number 子类型的List集合对象
public static void main(String[] args) {
    List<? extends Number> list;
    //list可以指向泛型是Number或者Number【子】类型的集合对象
    list = new ArrayList<Number>();
    list = new ArrayList<Integer>();
    list = new ArrayList<Double>();
    //编译报错,因为String不是Number类型,也不是Number的子类型
    //list = new ArrayList<String>();
}

能表示数字的类型都是Number类型的子类型,例如Byte Short Integer Long等

泛型的下限:

  • 例如: List <? super Number> list
  • 将来引用list就可以接收泛型是 Number 或者 Number 父类型的List集合对象
public static void main(String[] args) {
    List<? super Number> list;
    //list可以指向泛型是Number或者Number【父】类型的集合对象
    list = new ArrayList<Number>();
    list = new ArrayList<Serializable>();
    list = new ArrayList<Object>();
    //编译报错,因为String不是Number类型,也不是Number的父类型
    //list = new ArrayList<String>();
    //编译报错,因为Integer不是Number类型,也不是Number的父类型
    //list = new ArrayList<Integer>();
}

泛型中 extends 和 super 对比:

  • 使用extends可以定义泛型的【上限】,这个就表示将来泛型所接收的类型【最大】是什么类型。可 以是这个最大类型或者它的【子类型】。
  • 使用super可以定义泛型的【下限】,这个就表示将来泛型所接收的类型【最小】是什么类型。可 以是这个【最小类型】或者它的【父类型】。

extends 限定泛型的上限的使用场景

  1. 在声明泛型类或者泛型接口的时候【可以】使用

    public class Point<T extends Number>{
    private T x;
    private T y;
    }
    interface Action<T extends Person>{
    public void test(T t);
    }
    
  2. 在声明泛型方法的时候【可以】使用

    public <T extends Action> void test(T t);
    
  3. 在声明变量的时候【可以】使用

    public class Test{
        	public void test(List<? extends Number> list){
        }
    }
    public static void main(String[] args) {
        List<? extends Number> list = new ArrayList<Long>();
        t.test(list);
    }
    
    

super 限定泛型的下限的使用场景

  1. 在声明泛型类或者泛型接口的时候【不能】使用

    //编译报错
    public class Point<T super Number>{
    private T x;
    private T y;
    }
    //编译报错
    interface Action<T super Person>{
    public void test(T t);
    }
    
    
  2. 在声明泛型方法的时候【不能】使用

    //编译报错
    public <T super Action> void test(T t);
    
  3. 在声明变量的时候【可以】使用

    public class Test{
            public void test(List<? super Number> list){
        }
    }
    public static void main(String[] args) {
        List<? super Number> list;
        list = new ArrayList<Number>();
        list = new ArrayList<Object>();
        //假设Student 继承了 Person
        List<? super Student> list;
        list = new ArrayList<Student>();
        list = new ArrayList<Pesson>();
        list = new ArrayList<Object>();
    }
    

2.7 类型擦除

泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份 字节码。

由于泛型是JDK1.5才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java 可以向后兼容之前没有使用泛型的类库和代码,因为在字节码(class)层面是没有泛型概念的。

例如,定义一个泛型类 Generic 是这样的:

class Generic<T> {
    private T obj;
    public Generic(T o) {
    	obj = o;
    }
    public T getObj() {
    	return obj;
    }
}

那么,Java编译后的字节码中 Generic 相当于这样的:(类型擦除,变为原始类型Object)

class Generic {
    private Object obj;
    public Generic(Object o) {
   	 	obj = o;
    }
    public Object getObj() {
    	return obj;
    }
}

例如

public static void main(String[] args) {
    //编译报错
    //ArrayList<Integer>和new ArrayList<Long>在编译期间是不同的类型
    ArrayList<Integer> list = new ArrayList<Long>();
    //但是编译完成后,它们对应的是同一份class文件:ArrayList.class
    ArrayList<Integer> list1 = new ArrayList<Integer>();
    ArrayList<Long> list2 = new ArrayList<Long>();
    System.out.println(list1.getClass() == list2.getClass());//true
}

注意,泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object

eg:

//编译报错
//因为在编译后,泛型信息会被擦除
//那么一个类会就存在了俩个一样的方法
//public void run(List list)
public class Test {
    public void run(List<String> list){
    }
    public void run(List<Integer> list){
    }
}

可以看出,Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类 型安全,减少运行时由于对象类型不匹配引发的异常。

但是在编译成功后,所有泛型信息会被擦除,变为原始类型Object

3 枚举

3.1 概述

枚举,是JDK1.5引入的新特性,可以通过关键字 enum 来定义枚举类。

枚举类是一种特殊的类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个 接口。

但是枚举类不能继承其他类.

public enum Color {
BLACK, WHITE
}

public enum Color ,表示这是一个枚举类型,名字叫Color

BLACK, WHITE 表示这个枚举类型有俩个固定的对象,一个叫BLACK,另一个叫WHITE

简单的使用这个枚举类型:

public enum Season { //隐含继承Enum
    SPRING,SUMMER,AUTUMN,WINTER; //默认被final和static修饰 即变为常量

    public static void main(String[] args) {
        Season s;

        s=Season.SUMMER;
        System.out.println(s);

        s=Season.valueOf("AUTUMN");
        System.out.println(s);//拿到值,因为重写了toString()方法,该方法中直接返回了name
        System.out.println(s.name());//拿到字符串
        System.out.println(s.ordinal());//返回索引地址

        Season[] seasons=Season.values();
        System.out.println(Arrays.toString(seasons));
    }
}

使用 javap 命令对Color.class文件进行反向解析:

public static void main(String[] args) {
//声明枚举类型的引用
Color c;
//引用指向对象
c = Color.BLACK;
//默认调用toString方法,父类中重写了toString方法,返回枚举对象的名字
System.out.println(c);
//引用指向对象
c = Color.WHITE;
//默认调用toString方法,父类中重写了toString方法,返回枚举对象的名字
System.out.println(c);
}
//运行结果:
BLACK
WHITE

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hvsKC7kD-1638149696606)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210618105253452.png)]

从运行结果中,可以看出:

  • 枚举其实也是一种类,同时还是一个final修饰的类;
  • 枚举类型都会默认继承一个父类类型:java.lang.Enum,这还是一个抽象的泛型类
public abstract class Enum<E extends Enum<E>>{}
  • 枚举中所定义的对象,其实就是类里面的public static final 修饰的常量,并且这些常量会在静态代码块中做初始化;

  • 枚举类中还有一个默认的私有构造器,说明我们在外面并不能自己去创建枚举类型的对象;

  • 枚举类型中还有默认添加进来的方法

    values()方法,可以返回这个枚举类型的所有对象,返回的类型是数组;

    valueOf(String str)方法,通过一个字符串可以返回枚举对象,这个字符串就是枚举对象的名字;

  • 枚举类型会从父类中继承过来一些方法(具体看=可以查看其固定的父类类型),例如,

    String name(),返回这个枚举对象的名字

    int ordinal(),返回这个枚举对象的编号,默认从0开始

3.2 意义

java中的类,从语法形式上来说,可以创建出一个类的无数个对象。

例如,学生类Student,我们可以创建出10个、20个等不同数量的学生对象,并且从意义讲确实也没有问 题,因为实际情况中确实会存在很多不同的学生。

但是这其实还存在另一种情况:一个类的对象,从意义上来说,对象个数是固定的。

例如, Gender 这个类,表示人的性别,从语法上来说,可以创建出无数个性别对象,但是从实际意义 上看,我们只需要俩个对象就可以了,一个表示男、一个表示女。

那么,在这个时候,我们就需要去对 Gender 类的对象创建作出限制,不让用户创建很Gender类型的对 象,因为如果创建了很多对象,那么会占用内存空间,同时这么多对象也没什么实际意义,最终还是只 能表示俩种情况,男和女。

解决这个问题的方式是,可以将 Gender 定义为一个枚举类型(enum),我们就可以在枚举类型中,提 前将这个类型的对象个数和对象名字都固定下来,并且之后的使用中不会改变,也不会再创建其他对 象。

例如

public enum Gender{
MALE,FEMALE
}

那么,这时候表示性别的类型 Gender ,就会有且只有俩个对象,MALE和FEMALE

3.3 获取枚举对象

以 Gender 这个枚举类型为例:

public enum Gender{
MALE,FEMALE
}
方法一:
public static void main(String[] args){
    //使用类名直接访问类中定义的俩对象
    //最常用的一种方式
    Gender g = Gender.MALE;
    g = Gender.FEMALE;
    //可以调用从父类型Enum以及Object中继承过来的方法
    System.out.println(g.name());
    System.out.println(g.ordinal());
    System.out.println(g.toString());
}
//运行结果:
MALE
0
MALE
方法二:
public static void main(String[] args){
    //通过字符串参数的改变,可以获取到Gender中的指定的一个对象
    String name = "MALE";
    Gender g = Gender.valueOf(name);
    System.out.println(g.name());
    System.out.println(g.ordinal());
    System.out.println(g.toString());
}
//运行结果:
MALE
0
MALE
方法三:
public static void main(String[] args){
    //通过字符串确定是哪一个枚举类型
    Class c = Class.forName("com.briup.demo.Genger");
    //通过字符串确定是哪一个名字的枚举对象
    String name = "FEMALE";
    //可以通过改变字符串,获取到java中任何一个枚举类型中的任意一个枚举对象
    Enum g = Enum.valueOf(c,name);
    System.out.println(g.name());
    System.out.println(g.ordinal());
    System.out.println(g.toString());
}
//运行结果:
FEMALE
1
FEMALE

3.4 枚举中定义属性和方法

在枚举类型中,除了可以指定对象的个数和名称之外,还可以定义属性和方法,例如

public enum Gender{
    MALE,FEMALE;
    private String name;
    public void setName(String name){
    	this.name = name;
    }
    public String getName(){
    	return this.name;
    }
    public void test(){
    	System.out.println("Gender test...");
    }
    public static void print(String name){
    	System.out.println("hello "+name);
    }
}
    public static void main(String[] args){
        Gender g = Gender.MALE;
        g.test();
        g.setName("我是男生");
        System.out.println(g.getName());
        Gender.print("jack");
}

注意,枚举类型中的第一行代码,要求一定是指定枚举对象的个数和名字,同时最后面加分号 (;)

在这行代码下, 才可以定义枚举类型的属性和方法

3.5 枚举中定义构造器

枚举类型中还可以定义自己的构造器,例如

public enum Gender{
    MALE,FEMALE;
    private String name;
    private Gender(){
    }
    private Gender(String name){
    this.name = name;
    }
    @Override
    public String toString() {
    return "Gender{name="+name+"}";
    }
}

枚举中的构造器,只能使用private修饰,或者不写修饰符,那么默认也是private,同时还可以构造 器重载

枚举类型中构造器的调用:

public enum Gender{
MALE,FEMALE
}

在枚举中,定义对象的时候,就已经默认使用了无参构造器,所以以上代码可以写成如下:

public enum Gender{
    MALE(),FEMALE();
    //构造器,默认private修饰
    Gender(){
    	System.out.println("无参构造器被调用");
    }
}
public static void main(String[] args)throws Exception {
    //该代码,可以对Gender.class进行类加载
    Class.forName("com.briup.demo.Gender");
}
//运行结果:
无参构造器被调用
无参构造器被调用

可以 看出,当前Gender.class完成类加载的时候,就会自动调用无参构造器创建对象 MALE 和 FEMALE 因为 MALE 和 FEMALE 在枚举类型是使用 public static final 修饰的俩个静态常量。(javap 命令)

也可以调用有参构造器:

public enum Gender{
    MALE("男"),FEMALE("女");
    private String name;
    Gender(){
    	System.out.println("无参构造器被调用");
    }
    Gender(String name){
        this.name = name;
        System.out.println("有参构造器被调用");
    }
}
public static void main(String[] args)throws Exception {
    //该代码,可以对Gender.class进行类加载
    Class.forName("com.briup.demo.Gender");
}
//运行结果:
有参构造器被调用
有参构造器被调用

3.6 枚举中定义抽象方法

枚举类型中还可以定义抽象方法,例如

public enum Gender{
    MALE,FEMALE;
    public abstract void test();
}

注意,这时候代码编译会报错。

需要在定义每一个枚举对象的时候,将抽象方法进行实现,因为枚举类型是final修饰的类,不可能有子 类型来实现这个抽象方法,所以就必须是自己的对象去实现这个抽象方法,例如:

public enum  Gender {
    MALE(){
        @Override
        public void test() {
            System.out.println("男生测试");
        }
    },FEMALE(){
        @Override
        public void test() {
            System.out.println("nv生测试");
        }
    };


    public abstract void test();

}

注意,这种写法和匿名内部类的写法很相似, MALE(){…},FEMALE(){…};

3.7 枚举类型实现接口

枚举类型已经有默认的父类型( java.lang.Enum ),我们就不能让它在继承其他父类了,但是我们可 以让枚举类型实现指定的接口:

public enum Gender implements Action{
MALE,FEMALE;
}
interface Action{
void run();
}

注意,这时候代码编译会报错。 需要在枚举类型中,实现接口中的抽象方法,这里有俩种方式:

方式一,在枚举中,统一实现这个接口中的抽象方法:

public enum Gender implements Action{
    MALE,FEMALE;
    @Override
    public void run() {
    System.out.println("枚举中统一实现接口中的抽象方法");
    }
}
interface Action{
void run();
}

方式二,在每个枚举对象中,分别实现这个接口中的抽象方法:

public enum Gender implements Action{
    MALE(){
        @Override
        public void run() {
            System.out.println("男生对象中,单独实现接口中的抽方法");
    	}
	},FEMALE(){
        @Override
        public void run() {
        System.out.println("女生对象中,单独实现接口中的抽象方法");
        }
       };
}
interface Action{
void run();
}	

3.8 枚举使用总结

对于枚举类型的使用,大多数情况下,我们在枚举中列出它的的每一个对象即可,偶尔会添加几个自定 义的属性和方法,并不会写的那么复杂,否则就没什么意义了。

在项目中,只要一个类型的对象个数和名称能固定下来的,就可以考虑使用枚举类型来表示。

知道什么时候使用枚举:只有有限的几个实例;

枚举继承Enum后的特点(继承了一些方法):枚举类是一种特殊的类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个 接口。

不能继承其他类;

4 注解

4.1 概述

注解(Annotation),是jdk5.0引入的技术,用它可以对java中的某一个段程序进行说明或标注,并且这 个注解的信息可以被其他程序使用特定的方式读取到,从而完成相应的操作。

例如, @Override 注解

public class Person {
    @Override
    public String toString() {
    	return super.toString();
    }
}

注意,编译器在编译该类的时候,会读取到toString方法上的注解@Override,从而帮我们检查这个 方法是否是重写父类中的,如果父类中没有这个方法,则编译报错

注解和注释的区别:

  • 注解是给其他程序看的,通过参数的设置,可以在编译后class文件中【保留】注解的信息,其他程 序读取后,可以完成特定的操作
  • 注释是给程序员看的,无论怎么设置,编译后class文件中都是【没有】注释信息,方便程序员快速 了解代码的作用或结构

4.2 格式

定义注解的格式如下:

没有属性的注解:

public @interface 注解名称 {
}

有属性,但没有默认值的注解:

public @interface 注解名称 {
public 属性类型 属性名();
}

有属性,有默认值的注解:

public @interface 注解名称 {
属性类型 属性名() default 默认值 ;
}

注意, public 可以省去不写,默认就是 public

4.3 范围

注解的使用范围有:

  • TYPE,使用在类、接口、注解、枚举等类型上面

    @Test
    public class Hello{
    }
    
  • FIELD,使用在属性上面

    public class Hello{
    @Test
    private String msg;
    }
    
  • METHOD,使用在方法上面

    public class Hello{
    @Test
    public void say(){
    }
    }
    
  • PARAMETER,使用在方法的参数前面

    public class Hello{
    public void say(@Test String name){
    }
    }
    
  • CONSTRUCTOR,使用在构造器上面(了解)

    public class Hello{
    @Test
    public Hello(){
    }
    }
    
  • LOCAL_VARIABLE,使用在局部变量上面(了解

    public class Hello{
    public void say(){
    @Test
    int num = -1;
    }
    }
    
  • ANNOTATION_TYPE,使用在注解类型上面(了解)

  • @Test
    public @interface Hello{
    }
    
  • PACKAGE,使用在包上面(了解)

@Test
package com.briup.test;

注意,包注解只能写在package-info.java文件中

注意,package-info.java文件里面,只能包含package声明,并做出描述,以便将来生成doc文 件,可以从API源码src.zip中,看到每个包下面都可以对应的package-info.java文件对该包做出 描述

注解的使用范围,都定义在了一个枚举类中:

package java.lang.annotation;
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
}

4.4保持

类中使用的注解,根据配置,可以保持到三个不同的阶段:

  • SOURCE,注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
  • CLASS,注解被保留到class文件,但jvm加载class文件时候被遗弃
  • RUNTIME,注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注 解。

如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 CLASS注解;

如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解

注意,因为RUNTIME的生命周期最长,所以其他俩种情况能作用到的阶段,使用RUNTIME也一定能 作用到

注解的三种保留策略,都定义在了一个枚举类中:

package java.lang.annotation;
public enum RetentionPolicy {
    /**
    * Annotations are to be discarded by the compiler.
    */
    SOURCE,
    /**
    * Annotations are to be recorded in the class file by the compiler
    * but need not be retained by the VM at run time. This is the default
    * behavior.
    */
    CLASS,
    /**
    * Annotations are to be recorded in the class file by the compiler and
    * retained by the VM at run time, so they may be read reflectively.
    *
    * @see java.lang.reflect.AnnotatedElement
    */
    RUNTIME
}

注意,Retention是保留、保持的意思,Policy是政策、策略的意思

4.5 元注解

在我们进行自定义注解的时候,一般会使用到元注解,来设置自定义注解的基本特点。所以,元注解也 就是对注解进行基本信息设置的注解。

常用到的元注解有:

  • @Target,用于描述注解的使用范围,例如用在类上面还是方法上面
  • @Retention,用于描述注解的保存策略,是保留到源代码中、Class文件中、还是加载到内存中
  • @Documented,用于描述该注解将会被javadoc生产到API文档中
  • @Inherited,用于表示某个被标注的类型是被继承的,如果一个使用了@Inherited修饰的annotation 类型被用于一个class,则这个annotation将被用于该class的子类。

例如

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER,
TYPE})
public @interface Deprecated {
}

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}

4.6 自定义

例如,

package com.briup.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
package com.briup.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
//角色的名称
String name() default "";
//name属性的别名,使用value可以简化配置
String value() default "";
}

注意, @Role(value=“admin”) 代码,等价于 @Role(“admin”) ,对应其他注解也同样适用

5. 反射

5.1概述

反射是java中提供的一种机制,它允许我们在程序运行的时候,动态获取一个类中的基本信息,并且可 以调用类中的属性、方法、构造器。

例如, Student 类型

public class Student{
    private String name;
    int age;
    public static int num;
    public Student(){}
    public Student(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
    	return name;
    }
    public void setName(String name) {
    	this.name = name;
    }
    public String sayHello(String name){
    	return "hello! "+name;
    }
}

我们除了用眼睛看到这个类中有些哪些属性、方法、构造器之外,在代码运行的时候,也可以通过反射 机制获取到这个类中的属性、方法、构造器,以及调用它们。

例如,查看类中声明的属性:

public static void main(String[] args)throws Exception {
    Class c = Class.forName("com.briup.demo.Student");

    Field[] fields = c.getDeclaredFields();
    for(Field f : fields){
        System.out.println("属性的修饰符:"+Modifier.toString(f.getModifiers()));
        System.out.println("属性的类型:"+f.getType().getName());
        System.out.println("属性的名称:"+f.getName());
        System.out.println("---------------------");
    }
}
//运行结果:
属性的修饰符:private
属性的类型:java.lang.String
属性的名称:name
---------------------
属性的修饰符:
属性的类型:int
属性的名称:age
---------------------
属性的修饰符:public static
属性的类型:int
属性的名称:count
---------------------

可以看出,即使没有源代码(.java文件),我们也能指定这个类中都声明了那些属性;

同样的,我们也可以使用类似的方式,获取到类中的方法信息和构造器信息,甚至是调用他们

5.2 Class类型

java.lang.Class 是API中提供的一个类,它可以表示java中所有的类型,包括基本类型和引用类型。

在之前的学习中,我们也接触过这个类型,Object中的方法getClass方法:

public final native Class getClass();

该方法的**返回类型就是Class,**所以 obj.getClass(); 这句代码的含义就是:返回obj引用在运行时所指 向对象的实际类型。

使用class的对象来表示基本类型:

public static void main(String[] args)throws Exception {
    //这个对象c就代表java中的int类型
    Class c = int.class;
    //判断对象c所代表的类型是否是基本类型
    System.out.println(c.isPrimitive());
    //获取对象c所代表的类型的名字
    System.out.println(c.getName());
}
//运行结果:
true
int

使用Class类的对象来表示类类型:

public static void main(String[] args)throws Exception {
    //这个对象c就代表Student类
    Class c = Student.class;
    System.out.println(c.getName());
}
//运行结果:
com.briup.demo.Student

使用Class类的对象来表示接口类型:

public static void main(String[] args)throws Exception {
    //这个对象c就代表List接口类型
    Class c = List.class;
    System.out.println(c.isInterface());
    System.out.println(c.getName());
}
//运行结果:
true
java.util.List

使用Class类的对象来表示数组类型:

public static void main(String[] args)throws Exception {
//这个对象c代表数组类型    
	Class c;
    c=int[].class;
    c=int[][].class;
    c=Student[].class;
    System.out.println(c.isArray());
    System.out.println(c.getSimpleName());//返回组成该数组具体类型是什么
   System.out.println(c.getComponentType().getSimpleName());
    }
//运行结果:
true
Student[]
Student

5.3 获取Class对象

在java中,每种类型(基本类型和引用类型)加载到内存之后,都会在内存中生成一个Class类型对象, 这个对象就代表这个具体的java类型,并且保存这个类型中的基本信息。

注意,java中的每种类型,都有且只有唯一的一个Class类型对象与之对应!并且在类加载的时候自动生 成!

获取基本类型的Class对象:

只有一种方式: Class c = int.class;

获取接口类型的Class对象:

有两种方式:

  • Class c1 = Action.class;
  • Class c2 = Class.forName("com.briup.demo.Action");

获取数组类型的Class对象:

有俩种方式:

  • Class c1 = int[].class;
  • int[] arr = new int[4]; Class c2 = arr.getClass();

获取类类型的Class对象:

有三种方式:

  • Class c1 = Student.class;
  • Class c2 = Class.forName("com.briup.demo.Student");
  • Student stu = new Student(); Class c3 = stu.getClass();

可以看出,获取一个类型Class对象的途径一般有三种办法

  • 使用类型名称直接获取;
  • 使用Class类中的静态方法forName获取;
  • 适用对象调用getClass方法获取;

5.4 获取类的信息

例如,以Student为例

package com.zyz.chapter8.RefTest;


import java.util.Map;

public class Student implements Action, Mark {
    private String name;
    int age;
    public static int num;
    public Student(){}
    public Student(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String sayHello(String name){
        return "hello! "+name;
    }
    @Override
    public void run(){

    }

    @Override
    public void star() {
        
    }
}
interface Action{
    void run();
}
interface Mark{
    void star();
}
获取类的基本信息:
public static void main(String[] args) {
    Student stu=new Student();
    Class c =stu.getClass();

    //获取类的名字。全限定名
    System.out.println(c.getName());
    //获取类的名字,简单类名
    System.out.println(c.getSimpleName());
    //获取所属包的名字
    System.out.println(c.getPackage().getName());
    //获取该类的修饰符
    System.out.println(Modifier.toString(c.getModifiers()));
    //获取类的父类型的名字
    System.out.println(c.getSuperclass().getName());
    //获取类实现的所有接口
    System.out.println(Arrays.toString(c.getInterfaces()));

    Class c2 = Object.class;
    Class c3 = Action.class;
    Class c4 = Mark.class;
    Class c5 = String.class;
    //判断c2代表的类型是不是c代表类型 的父类型
    System.out.println(c2.isAssignableFrom(c));
    //判断c3代表的类型是不是c代表类型 的父类型
    System.out.println(c3.isAssignableFrom(c));
    //判断c4代表的类型是不是c代表类型 的父类型
    System.out.println(c4.isAssignableFrom(c));
    //判断c5代表的类型是不是c代表类型 的父类型
    System.out.println(c5.isAssignableFrom(c));
}
运行结果
    com.zyz.chapter8.RefTest.Student
Student
com.zyz.chapter8.RefTest
public
java.lang.Object
[interface com.zyz.chapter8.RefTest.Action, interface com.zyz.chapter8.RefTest.Mark]
true
true
true
false
获取类中声明的属性:

获取类中的public修饰的属性,也包含从父类中继承过来的public属性

  • Field[] getFields()
  • Field getField(String name)

获取类中声明的属性(包含私有的),但是不能获取从父类中继承过来的属性

  • Field[] getDeclaredFields()
  • Field getDeclaredField(String name)
public static void main(String[] args) throws Exception {
    Class c=Class.forName("com.zyz.chapter8.RefTest.Student");
    Field[] field=c.getFields();
    for (Field f :field) {
        //获取public修饰的属性相关的信息
        System.out.println(Modifier.toString(f.getModifiers()));//修饰符
        System.out.println(f.getType().getName());//类型
        System.out.println(f.getName());//属性名
        System.out.println("================");
    }

    Field[] fields=c.getDeclaredFields();
    for (Field f1 :fields) {
        System.out.println(Modifier.toString(f1.getModifiers()));
        System.out.println(f1.getType().getName());
        System.out.println(f1.getName());
        System.out.println("=======================");
    }


}
运行结果
public static
int
num
================
private
java.lang.String
name
=======================

int
age
=======================
public static
int
num
=======================

注意, java.lang.reflect.Field 表示类中的属性

获取类中声明的方法:

获取当前类中的public方法,包含从父类中继承的public方法

  • Method[] getMethods()
  • Method getMethod(String name, Class... parameterTypes)

获取当前类中声明的方法(包含私有的),但是不能获取从父类中继承过来的方法

  • Method[] getDeclaredMethods()
  • Method getDeclaredMethod(String name, Class... parameterTypes)
public static void main(String[] args) {
        Student s=new Student();
        Class c=s.getClass();
//        Method[] methods=c.getMethods();
        Method[] methods=c.getDeclaredMethods();
        for (Method m :methods) {
            System.out.println(Modifier.toString(m.getModifiers()));
            System.out.println(m.getReturnType().getName());
            System.out.println(m.getName());
            System.out.println("方法参数个数"+m.getParameterCount());
            Class[] paramArr=m.getParameterTypes();
            System.out.println("\t"+Arrays.toString(paramArr));
            Class<?>[] exceptionTypes = m.getExceptionTypes();
            System.out.println("方法抛出异常的个数"+exceptionTypes.length);
            System.out.println(Arrays.toString(exceptionTypes));
            System.out.println("========================");
        }

    }
运行结果
    public
void
run
方法参数个数0
	[]
方法抛出异常的个数0
[]
========================
public
java.lang.String
getName
方法参数个数0
	[]
方法抛出异常的个数0
[]
========================
public
void
setName
方法参数个数1
	[class java.lang.String]
方法抛出异常的个数0
[]
========================
public
java.lang.String
sayHello
方法参数个数1
	[class java.lang.String]
方法抛出异常的个数0
[]
========================
public
void
star
方法参数个数0
	[]
方法抛出异常的个数0
[]
========================

注意, java.lang.reflect.Method 表示类中的方法

获取类中声明的构造器:

获取当前类中的public构造器

  • public Constructor[] getConstructors()
  • public Constructor getConstructor(Class... parameterTypes)

获取当前类中的所有构造器,包含私有的

  • public Constructor[] getDeclaredConstructors()
  • public Constructor getDeclaredConstructor(Class... parameterTypes)
public static void main(String[] args)throws Exception {
    Student stu = new Student();
    Class c = stu.getClass();
    //获取类中所有的public构造器
    Constructor[] constructors = c.getConstructors();
    for(Constructor constructor:constructors){
    //构造器的修饰符
    System.out.println(Modifier.toString(constructor.getModifiers()));
    //构造器的名字
    System.out.println(constructor.getName());
    //构造器的参数列表
    Class[] paramList = constructor.getParameterTypes();
    System.out.println(java.util.Arrays.toString(paramList));
    //构造器的抛出异常
    Class[] exceptionList = constructor.getExceptionTypes();
    System.out.println(java.util.Arrays.toString(exceptionList));
    System.out.println("-----------------------------");
    }
}
//运行结果:
public
com.briup.demo.Student
[]
[]
-----------------------------
public
com.briup.demo.Student
[class java.lang.String, int]
[]
-----------------------------

注意, java.lang.reflect.Constructor 表示类中的方法

5.5 反射访问属性

public class reflect2 {
    public static void main(String[] args) throws Exception {
        Student student=new Student();
        Class c=student.getClass();

        //获取类中名字交name的属性
        Field f=c.getDeclaredField("name");
        //设置私有属性可以被访问,否则报错
        f.setAccessible(true);
        //用反射的方式,给指定对象的name属性赋值
        //相当于之前的stu.name = "tom";
        f.set(student,"tom");
        //用反射的方式,获取这个属性的值
        //相当于之前的stu.name
        System.out.println(f.get(student));
        System.out.println("-------------------");

        //获取类中名字叫age的属性
        Field f1=c.getDeclaredField("age");
        //设置私有属性可以被访问,否则报错
//        f1.setAccessible(true);前面设置了
        //赋值
        f1.set(student,18);
        System.out.println(f1.get(student));
        System.out.println("---------------");

        //获取类中名字叫num的属性
        Field f2=c.getDeclaredField("num");
        f2.set(student,99);
        System.out.println(f2.get(student));
    }
}

5.6 反射调用方法

public static void main(String[] args) throws Exception {
    Student stu =new Student();
    Class c=stu.getClass();

    //获取类中的toString方法,没有参数,这是从父类继承的方法
    Method m=c.getMethod("toString",null);

    //反射的方式,调用stu对象中的这个方法,没有参数,并接收执行结果
//相当于之前的:Object result = stu.toString();
    Object result =m.invoke(stu,null);
    System.out.println(result);

    System.out.println("---------------");

    Method m1=c.getMethod("sayHello", String.class);
    Object r2=m1.invoke(stu,"tom");
    System.out.println(r2);
}
运行结果
    com.zyz.chapter8.RefTest.Student@74a14482
---------------
hello! tom

注意, public Object invoke(Object obj, Object... args)

  • obj,表示要调用哪个对象的方法,如果是静态方法,可以传null
  • args,可变参数,表示要调用的方法的参数列表
  • m.invoke(obj,null) 中的m,就是通过Class对象获取到的类中的某一个指定的方法

5.7 反射创建对象

使用反射的方式可以创建对象,也就是需要反射调用构造器。

例如,反射调用类中无参构造器创建对象

public static void main(String[] args)throws Exception {
    Class c = Student.class;
    //默认调用类中的无参构造器来创建对象
    //相当于之前的:Object obj = new Student();
    Object obj = c.newInstance();
    System.out.println(obj);
    }

例如,反射调用类中有参构造器创建对象

public static void main(String[] args)throws Exception {
    Class c = Student.class;
    //获取类中的俩参构造器
    Constructor constructor = c.getConstructor(String.class, int.class);
    //调用有参构造器创建对象,并传入对应的参数值
    //相当于之前的:Object obj = new Student("tom",20);
    Object obj = constructor.newInstance("tom",20);
    System.out.println(obj);
}
public static void main(String[] args) throws Exception, InstantiationException {

    Student s=new Student();
    Class c= s.getClass();

    Object o=c.newInstance();
    System.out.println(o);//输出地址值

    Constructor constructor=c.getConstructor(String.class,int.class);
    Object o1=constructor.newInstance("tom",20);
    Student student=(Student)o1;
    System.out.println(student.getName()+student.age);
    System.out.println("----------------");
}
运行结果:
    com.zyz.chapter8.RefTest.Student@1b6d3586
tom20

思考,使用反射的方式访问属性、调用方法、创建对象,和普通方式比较起来,有什么优势?

破坏封装,即使是用private修饰的属性和方法也都可以获得并使用

5.8 反射获取注解

使用反射,可以获取到一个类中的注解信息 例如,

@Test
public class Service{
}
public class Service{
    @Role(name = "admin")
    public void delete(long id){
    //..
    }
    @Role("admin")
    public void find(long id){
    //..
    }
}

例如,获取Service类上面的【所有】注解类型

public class Demo {
    public static void main(String[] args) {
        Class<?> c = Service.class;
        //获取该类上面的所有注解
        Annotation[] annotations = c.getAnnotations();
        for(Annotation an:annotations){
        System.out.println(an.annotationType());
        }
    }
}

例如,获取Service类上面的【指定】注解类型

public class Demo {
    public static void main(String[] args) {
        Class<?> c = Service.class;
        //获取类上指定类型的注解,没有则返回null
        Test t = c.getAnnotation(Test.class);
        //判断类上面是否使用了指定注解
      System.out.println(c.isAnnotationPresent(Test.class));
    }
}

例如,获取方法上的注解,以及注解的属性值

public class Demo {
    public static void main(String[] args) {
    Class<?> c = Service.class;
    //获取类中所有声明的方法
    Method[] methods = c.getDeclaredMethods();
    for(Method m:methods){
        System.out.println(m.getName());
        //判断当前方法上是否使用了Role注解
        if(m.isAnnotationPresent(Role.class)){
        //获取方法上的Role注解
        Role role = m.getAnnotation(Role.class);
        //获取注解中的属性值
        String name = role.name();
        String value = role.value();
        System.out.println("\tname="+name);
        System.out.println("\tvalue="+value);
    }
    System.out.println();
    }
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值