泛型、枚举、注解和反射的初步了解
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>();
虽然 Integer
是 Object
的子类型,但是 ArrayList
和 ArrayList
之间没有子 父类型的关系,它们就是俩个不同的类型
所以, 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 泛型边界
在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。
如果在泛型中使用 extends
和 super
关键字,就可以对泛型的类型进行限制。即:规定泛型的上限和 下限。
泛型的上限:
- 例如:
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 限定泛型的上限的使用场景
-
在声明泛型类或者泛型接口的时候【可以】使用
public class Point<T extends Number>{ private T x; private T y; } interface Action<T extends Person>{ public void test(T t); }
-
在声明泛型方法的时候【可以】使用
public <T extends Action> void test(T t);
-
在声明变量的时候【可以】使用
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
限定泛型的下限的使用场景
-
在声明泛型类或者泛型接口的时候【不能】使用
//编译报错 public class Point<T super Number>{ private T x; private T y; } //编译报错 interface Action<T super Person>{ public void test(T t); }
-
在声明泛型方法的时候【不能】使用
//编译报错 public <T super Action> void test(T t);
-
在声明变量的时候【可以】使用
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
,表示要调用哪个对象的方法,如果是静态方法,可以传nullargs
,可变参数,表示要调用的方法的参数列表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();
}
}
}