java基础(泛型,枚举,反射)

本文详细介绍了Java中的泛型、枚举和反射三大核心概念。泛型提供了类型参数化的特性,提高了代码的安全性和复用性;枚举用于表示固定数量的值,具备类的功能,如定义属性和方法;反射则允许程序在运行时动态获取类信息并调用其方法。通过学习,可以深入理解Java的这些高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的时候进行传参来确定

注意,如果将来Point在使用的时候,没有给泛型参数T传值,那么T默认就表示为Object类型

注意,T是泛型参数的名字,也就是相当于形参,名字随便起,但是一般用一个有意义的大写字母

Point<T>类的使用:

public static void main(String[] args) {
    //p1对象中x和y的属性类型都是Integer
    Point<Integer> p1 = new Point<Integer>();
    p1.x = 1;
    p1.y = 2;
    
    //p2对象中x和y的属性类型都是String
    Point<String> p2 = new Point<String>();
    p2.x = "1";
    p2.y = "2";
    
    //p3对象中x和y的属性类型都是Object
    Point p3 = new Point();
    p3.x = new Object();
    p3.y = new Object();
    
}

可以看出,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");
    //编译报错,add(E e) 已经变为 add(String e)
    //int类型的数据1,是添加不到集合中去的
    //c.add(1);

    for(String str : c){
        System.out.println(str);
    }
}

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

Collection<String> c = new ArrayList<String>();

可以简写成:

Collection<String> 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<>();
    
    //根据泛型类型的指定,put方法中的key只能是Integer类型,value只能是String类型
    map.put(1,"hello1");
    map.put(2,"hello2");
    map.put(3,"hello3");
    map.put(4,"hello4");
	
    //根据上面列出的源码可知,当前指定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的子类型,但是ArrayList<Integer>ArrayList<Object>之间没有子父类型的关系,它们就是俩个不同的类型

所以,

Object o = new Integer(1);编译通过

ArrayList<Object> list = new ArrayList<Integer>();编译报错

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

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 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("hello5");

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

}

2.6 泛型边界

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

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

泛型的上限:(ps:不可添加元素,但可以删除遍历已有元素)

  • 例如: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等

泛型的下限:(ps:可以添加元素,也可以删除,遍历已有元素)

  • 例如: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>();


}

泛型中extendssuper对比:

  • 使用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

例如,

//编译报错
//因为在编译后,泛型信息会被擦除
//那么一个类会就存在了俩个一样的方法
//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 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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4nVUlGZ-1597894256191)(corejava-day12.assets/image-20200805231242306.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完成类加载的时候,就会自动调用无参构造器创建对象MALEFEMALE

因为MALEFEMALE在枚举类型是使用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("女生测试");
        }
    };

    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 枚举使用总结

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

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

思考,除了性别在项目可以定义为枚举类型之外,你还能想到哪些类型可以定义为枚举?

4 反射

4.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文件),我们也能指定这个类中都声明了哪些属性

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

4.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

4.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();
  • Student stu = new Student(); Class c3 = stu.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方法获取

4.4 获取类的信息

例如,以Student为例进行操作说明

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)throws Exception {
    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.briup.demo.Student
Student
com.briup.demo
public
java.lang.Object
[interface com.briup.demo.Action, interface com.briup.demo.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.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.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)throws Exception {
    Student stu = new Student();
    Class c = stu.getClass();
	
     //获取类中声明的所有方法
    Method[] methods = c.getDeclaredMethods();
    
    for(Method m : methods){
        //获取方法的修饰符
        System.out.println(Modifier.toString(m.getModifiers()));
        //获取方法的返回类型
        System.out.println(m.getReturnType().getSimpleName());
        //获取方法的名字
        System.out.println(m.getName());

        System.out.println("方法参数个数:"+m.getParameterCount());
        //获取方法的参数列表
        Class[] paramArr = m.getParameterTypes();
        //输出方法的参数列表
        System.out.println("\t"+Arrays.toString(paramArr));

        //获取方法所抛出的异常类型
        Class[] exceptionArr = m.getExceptionTypes();
        System.out.println("方法抛出异常个数:"+exceptionArr.length);
        //输出方法所抛出的异常列表
        System.out.println("\t"+Arrays.toString(exceptionArr));
        System.out.println("-------------------------------");
    }

}

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

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

例如,获取类中声明的构造器:

获取当前类中的public构造器

  • public Constructor<?>[] getConstructors()
  • public Constructor<T> getConstructor(Class<?>... parameterTypes)

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

  • public Constructor<?>[] getDeclaredConstructors()
  • public Constructor<T> 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表示类中的方法

4.5 反射访问属性

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

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

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

    //获取类中名字叫age的属性
    Field f2 = c.getDeclaredField("age");
    //用反射的方式,给指定对象的age属性赋值
    //相当于之前的stu.age = 20;
    f2.set(stu,20);
    //用反射的方式,获取这个属性的值
    //相当于之前的stu.age
    System.out.println(f2.get(stu));

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

    //获取类中名字叫num的属性
    Field f3 = c.getDeclaredField("num");
    //用反射的方式,给静态属性num赋值,不需要对象
    //相当于之前的Student.num = 99;
    f3.set(null,99);
    //用反射的方式,获取这个静态属性的值,不需要对象
    //相当于之前的Student.num
    System.out.println(f3.get(null));


}

4.6 反射调用方法

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

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

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

    //输出执行结果
    System.out.println(result);

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

    //获取类中的sayHello方法,需要一个String类型的参数,这是自己定义的方法
    Method m2 = c.getMethod("sayHello", String.class);

    //反射的方式,调用stu对象中的这个方法,参数是"tom",并接收执行结果
    //相当于之前的:Object result = stu.sayHello("tom");
    result = m2.invoke(stu,"tom");

    //输出执行结果
    System.out.println(result);


}`

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

  • obj,表示要调用哪个对象的方法,如果是静态方法,可以传null
  • args,可变参数,表示要调用的方法的参数列表

m.invoke(obj,null)中的m,就是通过Class对象获取到的类中的某一个指定的方法

4.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 Object test(String className,String methodName) throw Exception{
    //如何实现?
    //得到Class对象
    Class.forName(className);
    //通过Class对象调用无参构造器去创建对象
    Object obj = c.newInstance();
    //通过对象得到方法
    Method method = c.getDeclareMethods(methodName);
    //设置方法可调用
    methos.setAccessible(true);
    
    //调用该方法并传参
    Object jj = method.invoke(obj);
    //返回值
    return jj;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值