java篇 类的进阶0x0B:Class 类 与 反射

Class 类

Class 类代表类的类。每个 Class 类的实例,都代表了一个类。

每个类在 jvm 中都对应一个 Class 对象。jvm 将要使用的 Class 对象加入到类加载器中。

  • java 程序运行前,并不会将所有的 Class 对象放入 jvm 中;
  • 每个 .class 类在 jvm 中只会对应一个 Class 实例对象;
  • Class 对象只能由 jvm 创建和加载;
  • Class 对象功能:运行时提供和获取某个对象的类型信息。

获得 Class 对象的三种方法

Object 类的.getClass()类名.classClass.forName("类的全限定名")

对象.getClass() 是 Object 类的方法,返回这个对象的类型(注意,这里是指这个实际对象的类型,而非指向这个对象的引用的类型)。即:

// 包 test 中
package test;

public class A{

}
public class B extends A{
    
}
// 调用类
public class TestUse{
    public static void main(String[] args){
        A a = new B();
        System.out.println(a.getClass());		// class B
    }
}

类名.class 返回的和 对象.getClass() 返回的,以及 Class.forName("类的全限定名") 返回的,都是类型(Class 实例对象)。

Class clazz1 = A.class;					// 获得 class A
Class clazz2 = a.getClass();			// 获得 class B
Class clazz3 = Class.forName("test.B");	// 获得 class B

Class 对象的方法

.getName() .getSimpleName()

.getName();			// 返回这个类的全限定名,如:java.lang.Thread
.getSimpleName();	// 返回这个类的简单类名(不含包名),如:Thread

// 需要用 Class 类的实例来调用,而不能用某个类的类名直接调用。
Dog dog = new Dog();
Class a = dog.getClass();
a.getName();
a.getSimpleName();

// Dog.getName() 是非法的,因为这种调用方式的意思是调用 Dog 类里面定义的 getName() 静态方法,但.getName() 不是定义在该类里的静态方法,也不是 Object 类里的静态方法(如果是的话就自动继承到 Dog 中,那么就真可以调用了,但事实上 .getName() 并没有在 Object 类中定义)。

.newInstance()

不能有参数,返回的是一个 Object 类型的对象。

含义:调用该类的无参构造方法,如果不存在无参构造方法,则报错。

A a = new A();
// 等价于
Class clazz = Class.forName("package.A");
A a = (A)clazz.newInstance();	// 若不强制转换,由于返回的是 Object 实例,无法赋值给 A 类,会报错。
new.newInstance()
构造方法可以调用任意构造方法只能调用无参构造方法
类型确定的类型,写死在代码中类型可以是不确定的,可以动态获取某个类的 Class 实例(比如从txt、xml文件中获取类的全限定名),再创建对象

.getField()

通过一个类的 Class 实例,可以获取一个类的成员变量/静态变量(注意是成员变量/静态变量,而非成员变量/静态变量的值)。

注意:

  • 取得的是类的成员变量/静态变量,它是脱离具体对象存在的,即便获取过程中涉及从某些对象中“取出”的过程。甚至如果存在类继承链,从这个父类或子类取出的成员变量/静态变量名甚至是脱离具体类存在的(当然是指当前类继承链中,如果是一个毫不相干的类中即便也有同名同类型属性,也是取不到的)。

  • 这个成员变量得是 public 访问修饰符修饰的,其他访问修饰符会取不到该字段。

Field xxfield = Class类的实例.getField("属性名");

Dog dog = new Dog();
Class a = dog.getClass();
Field abc = a.getFiled("name");		// 取得类中的 name 成员变量/静态变量

.getName();				// 返回变量名
.getType();				// 返回变量的类型
.getDeclaringClass();	// 返回定义该变量的类
.getInt(对象);				// 以 int 类型来返回对象的该属性的值,不能自动转换的话会报错
.getDouble(对象);				// 以 double 类型来返回对象的该属性的值,不能自动转换的话会报错
.getBoolean(对象);			// 以 boolean 类型来返回对象的该属性的值,不能自动转换的话会报错
.get(对象);					// 返回该对象的该属性(不管是类型)
.get(null);					// 对于静态变量,可以直接传 null 来取得静态变量的值;但如果是取成员变量的属性值,传入null是会报错的。
.set(对象,新属性值);				// 将该对象的该属性设定为这个新的属性值
.setAccessible(true);			// 将非 public 的属性改为可访问(对 public 属性无效,设置为 false也仍旧能访问)

// 例子
abc.getName();
abc.getType();
abc.getDeclaringClass();
abc.getInt(dog);
abc.get(dog);
abc.get(null);
abc.set(dog,"Tom");

Field abb = a.getDeclaredField("age");		// age 为 private 属性,可以通过 .getDeclaredField() 取得
abb.setAccessible(true);

.getMethod()

通过一个类的 Class 实例,可以获取一个类的方法。

Method xxmethod = Class类的实例.getMethod("方法名",形参类型依顺序排列);
// 若形参为可以变长参数,如:String... abc,则写为 String[].class

Dog dog = new Dog();
Class a = dog.getClass();
Method abc = a.getMethod("buy",String.class,int.class);		// 取得类中方法签名为 buy(String,int) 的方法。

.getName();				// 返回方法名
.getReturnType();		// 返回方法的返回类型
.getParameterTypes();	// 返回方法形参的 Class 类型数组(哪怕只有一个形参类型)
.getParameterCount();	// 返回方法形参的个数(可变长参数只算一个参数)
.invoke(对象,按形参顺序依此传递实参);	// 对该对象调用该方法
.invoke(null,按形参顺序依此传递实参);	 // 调用静态方法 
.setAccessible(true);			// 将非 public 的方法改为可访问(对 public 方法无效,设置为 false也仍旧能访问)

// 例子
abc.getName();
abc.getReturnType();
abc.invoke(dog,"Tom",3);

Method abb = a.getDeclaredMethod("buy");		// age 为 private 属性,可以通过 .getDeclaredField() 取得
abb.setAccessible(true);

反射 reflection

可以使用反射(reflection)来访问属性。

反射其实是基本所有高级语言都提供的一个功能,它提供了一种相对动态的方式来获取属性和调用方法。

用反射可以访问和设置属性,还能调用方法,甚至比 对象.属性/方法 这种访问更特殊的是,反射可以访问到 private 的属性和方法。

private 的设置其实并不是为了安全性,而是为了更好地封装,让结构更加清晰。

如果说反射和对象.属性/方法 还有什么区别的话,就是访问效率。反射实际上是走的迂回路线,所以效率没有 对象.属性/方法 的方式高。但这个“效率低”只是指迂回地去建立反射的过程,一旦建立了反射(比如说已经获得了 method),你去调用的话(传入对象和实参),其实不存在效率更低的。所以一般大量使用反射的时候,会把“提取”出来的 method 和 field 都保存起来,然后再用这个 method 和 field 来调用相应的对象和传入参数,这样就不会慢了(只有“提取”的那一步比较慢)。

方法

  • 提取方法时,需要这个 Class 实例所对应的类中定义了该方法(拥有该方法签名),才能提取;

  • 提取方法后,只能用这个提取的类,或其子类的对象来调用(因为这个对象中有这个方法签名的方法)

    • 若子类中有对该方法的覆盖,则调用子类对象时,调用的是该方法的覆盖方法(这就比较巧妙了,仍然是遵守”覆盖多态“的);

    • 但若用当前类的对象调用,则无论子类中是否对该方法进行覆盖,都不影响当前类的对象调用的是当前类的方法。

      • 注意:若此处,父类是 private,子类变为 public 方法签名和返回值一致,并不算发生“覆盖”。父类的该方法与子类的该方法被视作两个完全不同的方法。(假如用 setAccessible 更改父类 private 方法为可访问,并不会出现”覆盖多态")

    • 另外,当前类提取的方法覆盖了其父类的方法的话,用父类对象来调用这个当前类提取的覆盖方法是会报错的。

    • 【静态方法】静态方法则例外,无论是父类还是子类中提取的静态方法(仅仅继承,没有覆盖),都可以随意用父类或子类的对象调用,也可以直接用 null 调用。

    • 【静态方法】静态方法如果发生了覆盖,则从无论是父类或子类中提取的静态方法,都可以随意用父类或子类的对象调用,也可以直接用 null 调用。但最终调用哪个静态方法,取决于这个静态方法是从哪个类中提取的,与具体用什么对象来调用无关。

属性

  • 提取属性后,可以用这个提取的类,或其子类的对象来调用。
    • 当前类、子类、当前类的父类中该属性同名(即类似”覆盖“定义了同名属性):
      • 若子类中有对该属性的重新定义(子类定义了一个和父类属性相同的属性),则子类对象仍然可以调用这个属性值,但是返回的实际上是父类中定义的属性值(这就比较坑了)。
      • 但若是当前类提取的属性,当前类的父类有同名属性(当前类和其父类定义了同一个属性),则妄图用当前类的父类的对象来调用这个属性,是会报错的。
    • 若当前类、子类、当前类的父类中有该属性没有同名,就是继承下来的:
      • 子类提取的属性,完全可以被父类对象调用,返回的是父类对象的属性。
package org.test.classtest;


import java.util.Objects;

public class Dog {
    private String name;
    private String nickname = "little Tom";
    public int age = 8;
    public int size = 10;
    double weight;
    protected boolean live = true;
    public static String blood;
    public static int number = 101;

    public Dog(String name) {
        this.name = name;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Dog)) return false;
        Dog dog = (Dog) o;
        return name.equals(dog.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }

    public void sayHi() {
        System.out.println(this.name + " : Hi~");
    }

    public int sayHello(String dogname) {
        System.out.println("Hello~ " + dogname);
        return 1;
    }

    public int sayHello(String dogname, int dogage) {
        System.out.println("Hello~ " + dogname + " is " + dogage + " years old.");
        return 2;
    }

    public void sayThing(String... words) {
        System.out.print("数组(" + words + ")长度: " + words.length + "。\t");
        for (String a : words) {
            System.out.print(a + "\t");
        }
        System.out.println();
    }

    private void cry() {
        System.out.println(this.name + " : wooo~");
    }
    private void cryOverride(){
        System.out.println("这是 Dog 中的 cryOverride...");
    }

    protected void cryProtect(){
        System.out.println("wooo protected...");
    }

    void cryDefault(){
        System.out.println("woo default...");
    }

    public static void sayWow(String dogname) {
        System.out.println("Wow~ " + dogname);
    }

    public static void sayOops() {
        System.out.println("这是Dog的静态方法 sayOops");
    }
}




package org.test.classtest;

public class Doggy extends Dog {

    public double height = 50;
    public int size = 5;

    public Doggy(String name) {
        super(name);
    }
    public int sayHello(String dogname, int dogage, double weight) {
        System.out.println("Hello~ " + dogname + " is " + dogage + " years old and weights "+weight+" kilogram.");
        return 2;
    }
    public void sayHi() {
        System.out.println("Hi~这是 Doggy 的 sayHi");
    }

    public static void sayOops(){
        System.out.println("这是 dog 的静态方法 sayOops");
    }
    public void cryOverride(){
        System.out.println("这是 Doggy 中的 cryOverride...");
    }
}




package org.test.classtest;

public class Cat {
    public int age;
    public int sayHello(String catname, int catage) {
        System.out.println("Hello~ " + catname + " is " + catage + " years old.");
        return 2;
    }
}



// 调用类
package org.test.classtest;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class TestUse {
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {

        Dog dog = new Dog("Tom");

        // 由于 class 是关键字,所以一般会改写为 clazz
        Class clazz = dog.getClass();
        System.out.println(clazz);                  // class org.test.classtest.Dog

        Class clazz2 = Dog.class;
        System.out.println(clazz2);                 // class org.test.classtest.Dog
        System.out.println(clazz.getName());        // org.test.classtest.Dog
        System.out.println(clazz.getSimpleName());  // Dog

        // .getField()
        // Field nameField = clazz.getField("name");	// 因为 name 在 Dog 类中是 private,所以直接这样调用会报错 NoSuchFieldException

        // Field weightField = clazz.getField("weight");	// 因为 weight 在 Dog 类中是缺省的访问修饰符,而非 public,所以直接调用仍然报错 NoSuchFieldException

        // Field liveField = clazz.getField("live");		// live 在 Dog 类中是 protected 而非 public,直接调用报错 NoSuchFieldException

        // 而 age 在 Dog 类中是 public,所以可以访问得到
        Field ageField = clazz.getField("age");
        System.out.println(ageField);       // public int org.test.classtest.Dog.age
        // 取得属性名
        System.out.println(ageField.getName()); // age
        // 取得属性类型
        System.out.println(ageField.getType()); // int
        // 返回定义该属性的类,注意是这个定义的所在类,如果是在父类中定义的,返回的是该父类。
        System.out.println(ageField.getDeclaringClass()); // class org.test.classtest.Dog
        System.out.println(ageField.getDouble(dog));    // 8.0
//        System.out.println(ageField.getBoolean(dog));   // 报错,因为 int 无法转换成 boolean
        System.out.println(ageField.getInt(dog));       // 8
        System.out.println(ageField.get(dog));          // 8
//        System.out.println(ageField.get(null));         // 报错,因为 null 没有这个属性值
//        System.out.println("强制类型转换:"+ageField.get((Dog)null));    // 报错,仍旧无法取到属性(这也是显然的,因为 null 就是没有属性)


        // 只要是 public 修饰的,静态变量也是可以取到的。
        Field bloodField = clazz.getField("blood");
        System.out.println(bloodField);     // public static java.lang.String org.test.classtest.Dog.blood
        System.out.println(bloodField.getType());   // class java.lang.String
        Field numberField = clazz.getField("number");
        // 即便 number 是静态变量,也可以使用 .get类型(对象) 来返回值
        System.out.println(numberField.getInt(dog));    // 101
        System.out.println(numberField.get(dog));       // 101
        System.out.println(dog.number);                 // 101
        System.out.println(Dog.number);                 // 101
        System.out.println(numberField.get(null));      // 101
        // 即对于静态变量,可以直接传 null 就可以取得。当然,传类的对象也是可以取得的。


        Dog doggy = new Doggy("Mark");
        Class clazzdoggy = doggy.getClass();
        Field doggyAge = clazzdoggy.getField("age");
        // 因为 age 是在父类 Dog 中定义的,所以返回的是 Dog
        System.out.println(doggyAge.getDeclaringClass());       // class org.test.classtest.Dog
        Field doggyHeight = clazzdoggy.getField("height");
        System.out.println(doggyHeight.getDeclaringClass());    // class org.test.classtest.Doggy
        System.out.println("小狗高度: " + doggyHeight.get(doggy));    // 小狗高度: 50.0
//        System.out.println("大狗高度:"+doggyHeight.get(dog));       // 报错,因为父类 Dog 中没有这个属性

        // ageField 与 doggyAge 虽然从不同对象中获得,但实际是同一个变量,所以可以混用调用对方类的实例对象
        System.out.println(ageField.getInt(doggy));         // 8
        System.out.println(doggyAge.getInt(dog));           // 8
        System.out.println(doggyAge.get(dog));              // 8
        System.out.println(doggyAge.get(doggy));            // 8
        // 即子类中直接继承父类的属性,那么子类或父类中提取的属性,可以被对方的对象相互混调。

        ageField.set(dog, 12);
        System.out.println("修改年龄后: " + ageField.get(dog));    // 修改年龄后: 12
        System.out.println(ageField.get(doggy));                // 8
        // 即,子类中继承的父类属性,的确是子类中的,而不是父类中的,子类可以调用用父类提取的属性来获取子类的属性。

        doggyAge.set(dog, 11);
        System.out.println("修改年龄后: " + doggyAge.get(dog));    // 修改年龄后: 11
        System.out.println("修改年龄后: " + ageField.get(dog));    // 修改年龄后: 11
        System.out.println(ageField.get(doggy));                // 8
        // 即,的的确确对于继承的属性,无论从子类还是父类中提取得到的属性,地位是一样的,可以被父类对象或子类对象调用。


        Field dogsize = clazz.getField("size");
        Field doggysize = clazzdoggy.getField("size");
        System.out.println("大狗体型:" + dogsize.get(dog)); // 大狗体型:10
        System.out.println(dogsize.get(doggy));            // 10
        // 即,发生子类属性与父类属性重名时,父类提取的属性,以子类对象来调用的话,实际上取到的并非为子类属性值,而是父类属性值
        System.out.println("小狗体型:" + doggysize.get(doggy)); // 小狗体型:5
//        System.out.println(doggysize.get(dog));             // 报错,因为这个属性值不是 Dog 中的属性值(即便同名,但等于没有被定义)
        // 即,当发生子类与父类属性值同名,子类提取的属性只能被子类的对象调用,并不能被父类对象调用。

        Cat cat = new Cat();
//        System.out.println(doggyAge.getInt(cat));           // 报错,因为这个 age 属性是 Dog/Doggy 类中的 age,不是 Cat 中的 age。


        System.out.println(clazz.getFields());              // [Ljava.lang.reflect.Field;@7699a589
        // 返回的是一个 Field 的数组对象

        // 用下方的静态方法 printFields() 查看一下类的所有属性(可以看到,能取到的,都是 public 修饰的)
        printFields(clazz);
        /*
        org.test.classtest.Dog里的 field
        int age
        int size
        class java.lang.String blood            // 可以看到静态变量 blood 也被打印出来了
        int number                              // 还有静态的 number
         */
        printFields(clazzdoggy);
        /*
        org.test.classtest.Doggy里的 field
        double height
        int size
        int age
        int size
        class java.lang.String blood
        int number
         */
        // 可以看到子类的属性包括了所有从父类中继承的属性。

        // .getField() vs.  .getDeclaredField()
        // Field nameField = clazz.getField("name");	// 因为 name 在 Dog 类中是 private,所以直接这样调用会报错 NoSuchFieldException
        Field nameField = clazz.getDeclaredField("name");
//        System.out.println(nameField.get(dog));         // 报错,无法访问 private 修饰的属性
//         Field weightField = clazz.getField("weight");	// 因为 weight 在 Dog 类中是缺省的访问修饰符,而非 public,所以直接调用仍然报错 NoSuchFieldException
        Field weightField = clazz.getDeclaredField("weight");
//        System.out.println(weightField.get(dog));       // 0.0
        // 即可以访问 缺省 访问修饰符的属性,当然前提得是调用类和 Dog 类在同一个包中,尝试过将它们放到不同包,取 declaredField 不报错,但再进一步取值就报错了。
        // 如果时放到不同包,经过测试,可以通过下面的方式获取到属性值
//        weightField.setAccessible(true);
//        System.out.println(weightField.get(dog));       // 0.0
//        System.out.println(weightField.get(doggy));       // 0.0

//         Field liveField = clazz.getField("live");		// live 在 Dog 类中是 protected 而非 public,直接调用报错 NoSuchFieldException
        Field liveField = clazz.getDeclaredField("live");
//        System.out.println(liveField.get(dog));             // true
        // 即可以访问 protected 访问修饰符的属性。但仍然需要得调用类和 Dog 类在同一个包中,尝试过将它们放到不同包,取 declaredField 不报错,但再进一步取值就报错了。
        // 如果放到不同包,经过测试,可以通过下面的方式获取到属性值
//        liveField.setAccessible(true);
//        System.out.println(liveField.get(dog));             // true
//        System.out.println(liveField.get(doggy));           // true

        nameField.setAccessible(true);
        System.out.println(nameField.get(dog));             // Tom
        // 即更改 private 的属性的访问权限为 true 后,就可以获得该属性值了。且这种改变是对可以调用这个属性的所有对象都有效,而不仅仅是提取该属性时的当前类。
        System.out.println(nameField.get(doggy));           // Mark
        // 这里的神奇之处在于,Doggy 继承 Dog,但 name 属性在 Dog 中是 private ,理论上是继承不了的,但显然这里就等于说事情没有这么简单...毕竟的确构造方法里有一个 this.name 的赋值。
        // 下面尝试一下完全没有在子类中赋值的 private 属性,看能不能用子类对象调用。
        Field nickNameField = clazz.getDeclaredField("nickname");
        nickNameField.setAccessible(true);
        System.out.println(nickNameField.get(dog));         // little Tom
        System.out.println(nickNameField.get(doggy));       // little Tom
        // 发现实际上 Doggy 还真就继承了 Dog 的 private 的 nickName 属性。而 name 之所以还能有自己的值,是因为构造方法里 this.name 赋值了 子类对象的 name 属性值。
        nameField.setAccessible(false);
//        System.out.println(nameField.get(dog));             // 报错,无法访问 private 属性。
        weightField.setAccessible(false);
//        System.out.println(weightField.get(dog));       // 0.0
        // 缺省 访问修饰符修饰的属性,类与调用类在同一个包中时,设置成不可访问还是可以访问,但如果是放到不同包中,则会报错。即能重新设置成不能访问

        liveField.setAccessible(false);
//        System.out.println(liveField.get(dog));         // true
        // protected 访问修饰符修饰的属性,类与调用类在同一个包中时,设置成不可访问还是可以访问,但如果是放到不同包中,则会报错。技能重新设置成不可访问。

        // 即,被设置为可访问的非public 属性,可以重新设置为不可访问。

        System.out.println(ageField.get(dog));          // 11
        ageField.setAccessible(false);
        System.out.println(ageField.get(dog));          // 11
        System.out.println(ageField.get(doggy));        // 8
        // 即,并不能对原本 public 的属性降低访问权限为不能访问。设置了不能访问,还是可以访问的。

        Field declareAgeField = clazz.getDeclaredField("age");
        System.out.println(declareAgeField.get(dog));   // 11
        declareAgeField.setAccessible(false);
        System.out.println(declareAgeField.get(dog));   // 11
        System.out.println(declareAgeField.get(doggy)); // 8
        // 即,public 属性的确是不能通过 setAccessible 来设置成不可访问,与是用 getField 还是 getDeclaredField 取得的无关。


        /*
        综上:用 getDeclareField 取得的属性:
        public 属性,setAccessible 设置成 false 也无法限制访问。 (用 getField 取得也是如此)
        private 属性,setAccessible 可以控制是否可以访问
        缺省 或 protected 属性,若类与调用类在同一个包中,setAccessible 无法控制能否访问,全都可以访问。但若在不同包中,setAccessible 可以用来控制是否可以访问。
         */


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

        Method sayHelloMethod = clazz.getMethod("sayHello", String.class);
        System.out.println(sayHelloMethod.getName());                                   // sayHello
        System.out.println(sayHelloMethod.getReturnType());                             // int
        System.out.println("sayHelloMethod: " + sayHelloMethod.getReturnType());        // sayHelloMethod: int
        for (Class cl : sayHelloMethod.getParameterTypes()) {
            System.out.print(cl.getName() + "\t");                                      // java.lang.String
        }
        System.out.println();
        System.out.println(sayHelloMethod.getParameterCount());                         // 1
        sayHelloMethod.invoke(dog, "Tommy");                                       // Hello~ Tommy
        sayHelloMethod.invoke(doggy, "Timmy");                                     // Hello~ Timmy
        // 也就是父类中提取的方法,可以用子类的对象实例来调用(毕竟子类继承了该方法)

        Method sayHiMethod = clazz.getMethod("sayHi");
        Method sayHiSonMethod = clazzdoggy.getMethod("sayHi");
        sayHiMethod.invoke(dog);                                                        // Tom : Hi~
        sayHiMethod.invoke(doggy);                                                      // Hi~这是 Doggy 的 sayHi
        // 即父类提取的方法,如果在子类中是被覆盖的,用子类对象调用时执行的是子类的覆盖方法
//        sayHiSonMethod.invoke(dog);                                                   // 报错:不是该方法声明的所在类的对象
        // 即子类提取的覆盖方法,并不能被父类对象调用。
        sayHiSonMethod.invoke(doggy);                                                   // Hi~这是 Doggy 的 sayHi


        Method sayHelloMethod2 = clazz.getMethod("sayHello", String.class, int.class);
        System.out.println(sayHelloMethod2.getName());                                  // sayHello
        System.out.println(sayHelloMethod2.getReturnType());                            // int
        System.out.println("sayHelloMethod2: " + sayHelloMethod2.getParameterTypes());  // sayHelloMethod2: [Ljava.lang.Class;@7699a589
        for (Class cl : sayHelloMethod2.getParameterTypes()) {
            System.out.print(cl.getName() + "\t");                                  // java.lang.String	int
        }
        System.out.println();
        System.out.println(sayHelloMethod2.getParameterCount());                    // 2
//        sayHelloMethod2.invoke(dog,"Jerry");                                      // 报错,并没有实现重载
//        sayHelloMethod2.invoke(dog,5,"Jerry");                                    // 报错,因为参数类型不对应
        sayHelloMethod2.invoke(dog, "Jerry", 5);                                // Hello~ Jerry is 5 years old.
//        sayHelloMethod2.invoke(cat,"Tom",5);                                      // 报错,虽然 Cat 类中又一模一样的方法,但这个方法是 Dog 类中的方法,无法使用 cat 来调用。
        sayHelloMethod2.invoke(doggy, "Mark", 3);                               // Hello~ Mark is 3 years old.

//        Method sayHelloMethod3 = clazz.getMethod("sayHello",String.class,int.class,double.class);   // 报错,找不到该方法。这是因为 clazz 是 Dog 类,里面没有定义这个方法签名的重载方法(是在子类 Doggy中定义的)
        Method sayHelloMethod3 = clazzdoggy.getMethod("sayHello", String.class, int.class, double.class);
        sayHelloMethod3.invoke(doggy, "Mark", 3, 12);                            // Hello~ Mark is 3 years old and weights 12.0 kilogram.
//        sayHelloMethod3.invoke(dog,"Mark",3,15);                            // 报错。因为 dog 是 Dog 类的实例,不是定义该方法的类(Doggy)的实例。
        // 即 提取/映射这个方法时,需要这个Class实例所对应的类中得有这个方法签名,并且提取这个方法后,只能用这个类(或子类:因为继承了父类的方法)的实例来调用这个方法。


        Method equalsMethod = clazz.getMethod("equals", Object.class);
        System.out.println(equalsMethod.getName());                                 // equals
        System.out.println(equalsMethod.getReturnType());                           // boolean
        System.out.println("equalsMethod: " + equalsMethod.getParameterTypes());    // equalsMethod: [Ljava.lang.Class;@58372a00
        for (Class cl : equalsMethod.getParameterTypes()) {
            System.out.print(cl.getName() + "\t");                              // java.lang.Object
        }
        System.out.println();
        System.out.println(equalsMethod.getParameterCount());                   // 1


        Method sayMethod = clazz.getMethod("sayThing", String[].class);
        System.out.println("sayMethod: " + sayMethod.getParameterCount());      // sayMethod: 1

        Method wowMethod = clazz.getMethod("sayWow", String.class);
        Method wowMethod2 = clazzdoggy.getMethod("sayWow", String.class);
        wowMethod.invoke(dog,"John");                                   // Wow~ John
        wowMethod.invoke(doggy,"Johnny");                               // Wow~ Johnny
        wowMethod.invoke(null,"Alex");                              // Wow~ Alex
        wowMethod2.invoke(doggy,"Johnny");                              // Wow~ Johnny
        wowMethod2.invoke(dog,"John");                                  // Wow~ John
        wowMethod2.invoke(null,"Alex");                             // Wow~ Alex
        // 即,子类从父类继承的静态方法,无论是从子类还是父类中提取该静态方法,都可以用父类或子类的对象来调用,也可以直接用 null 调用。

        Method oopMethod = clazz.getMethod("sayOops");
        Method oopMethod2 = clazzdoggy.getMethod("sayOops");
        oopMethod.invoke(dog);                                                  // 这是Dog的静态方法 sayOops
        oopMethod.invoke(doggy);                                                // 这是Dog的静态方法 sayOops
        oopMethod.invoke(null);                                             // 这是Dog的静态方法 sayOops
        oopMethod2.invoke(doggy);                                               // 这是 dog 的静态方法 sayOops
        oopMethod2.invoke(dog);                                                 // 这是 dog 的静态方法 sayOops
        oopMethod2.invoke(null);                                            // 这是 dog 的静态方法 sayOops
        // 即,子类覆盖父类的静态方法时,无论方法是从父类还是子类提取的,都可以用父类或子类的对象调用,也可以用 null 调用,但是最终调用哪个方法,取决于方法是从哪个类中提取的,与调用对象无关。


        // private 方法
//        Method cryMethod = clazz.getMethod("cry");                      // 报错。因为访问不到 private 方法
        Method cryMethod = clazz.getDeclaredMethod("cry");
//        cryMethod.invoke(dog);                                            // 报错,没有调用 private 方法的权限。
        cryMethod.setAccessible(true);
        cryMethod.invoke(dog);                                          // Tom : wooo~
        cryMethod.invoke(doggy);                                        // Mark : wooo~
        cryMethod.setAccessible(false);
//        cryMethod.invoke(dog);                                          // 报错,没有调用 private 方法的权限。
        // 即,可以通过 setAccessible 对 private 方法进行访问控制。
//        Method cryProtectMethod = clazz.getMethod("cryProtect");      // 报错,找不到该方法。因为属性不是 public
        Method cryProtectMethod = clazz.getDeclaredMethod("cryProtect");
//        cryProtectMethod.invoke(dog);                                   // wooo protected...
//        cryProtectMethod.invoke(doggy);                                 // wooo protected...
        // 即,当方法所在类和调用类在同一个包中时,可以直接调用 protected 修饰的方法。
        // 若不再同一个包中,则需要用以下方式访问:
        cryProtectMethod.setAccessible(true);
//        cryProtectMethod.invoke(dog);                                     // wooo protected...
//        cryProtectMethod.invoke(doggy);                                     // wooo protected...
//        cryProtectMethod.setAccessible(false);
//        cryProtectMethod.invoke(dog);                                     // 报错,不能访问 protected 权限的方法

//        Method cryDefaultMethod = clazz.getMethod("cryDefault");    // 报错,找不到该方法。因为属性不是 public
        Method cryDefaultMethod = clazz.getDeclaredMethod("cryDefault");
//        cryDefaultMethod.invoke(dog);                               // woo default...
//        cryDefaultMethod.invoke(doggy);                             // woo default...
        // 即,当方法所在类和调用类在同一个包中时,可以直接调用 缺省 访问修饰符的方法。
        // 若不再同一个包中,则需要用以下方式访问:
//        cryDefaultMethod.setAccessible(true);
//        cryDefaultMethod.invoke(dog);                               // woo default...
//        cryDefaultMethod.invoke(doggy);                             // woo default...
//        cryDefaultMethod.setAccessible(false);
//        cryDefaultMethod.invoke(dog);                               // 报错,无法调用 缺省 访问修饰符修饰的方法。

        sayHiMethod.invoke(dog);                                    // Tom : Hi~
        sayHiMethod.setAccessible(false);
        sayHiMethod.invoke(dog);                                    // Tom : Hi~
        Method sayHiDeclareMethod = clazz.getDeclaredMethod("sayHi");
        sayHiDeclareMethod.invoke(dog);                             // Tom : Hi~
        sayHiDeclareMethod.setAccessible(false);
        sayHiDeclareMethod.invoke(dog);                             // Tom : Hi~
        // 即,对于原本就是 public 修饰的方法,无论是通过 getMethod 还是 getDeclaredMethod 提取的,都不能通过 setAccessible 让它变成不可访问的。

        Method cryOverMethod = clazz.getDeclaredMethod("cryOverride");
//        cryOverMethod.invoke(dog);                      // 报错,无法访问 private 方法
//        cryOverMethod.invoke(doggy);                    // 报错。即便 Doggy 中覆盖该方法并且设置为 public 但提取这个方法的类对该方法设置的是 private,所以还是无法调用。
        cryOverMethod.setAccessible(true);
        cryOverMethod.invoke(dog);                      // 这是 Dog 中的 cryOverride...
        cryOverMethod.invoke(doggy);                    // 这是 Dog 中的 cryOverride...
        // 即,父类的方法假如是 private,而子类变为 public ,实际上并不会形成”覆盖“,所以是视作两个完全不同的方法。所以即便用父类的这个 private 方法设置为可访问后,用子类对象来调用,并不会出现”多态“的情况,而仅仅是调用父类的这个方法。

        Method cryOverMethod2 = clazzdoggy.getDeclaredMethod("cryOverride");
        cryOverMethod2.invoke(doggy);                   // 这是 Doggy 中的 cryOverride...
//        cryOverMethod2.invoke(dog);                     // 报错。因为提取的这个方法调用的对象不是当前提取类(或其子类)的实例



        /*
        综上:用 getDeclareMethod 取得的方法:
        public 方法,setAccessible 设置成 false 也无法限制访问。 (用 getMethod 取得也是如此)
        private 方法,setAccessible 可以控制是否可以访问
        缺省 或 protected 方法,若类与调用类在同一个包中,setAccessible 无法控制能否访问,全都可以访问。但若在不同包中,setAccessible 可以用来控制是否可以访问。

        另外其实也发现,实际上,子类是把父类的非 public 方法都继承了。
         */

        // 用下方的静态方法 printMethods() 查看一下类的所有方法
        printMethods(clazz);
        /*
        org.test.classtest.Dog 里的 method
        boolean equals(class java.lang.Object,	)
        class java.lang.String toString()
        int hashCode()
        void sayThing(class [Ljava.lang.String;,	)
        void sayWow(class java.lang.String,	)
        void sayHi()
        int sayHello(class java.lang.String,	int,	)
        int sayHello(class java.lang.String,	)
        void sayOops()
        void wait(long,	int,	)
        void wait()
        void wait(long,	)
        class java.lang.Class getClass()
        void notify()
        void notifyAll()
         */
        // 会发现打印出了一些我们没有定义过的方法,也就是这个类本身继承 Object 类的方法也会被打印出来。但注意到这里打印的其实都是 public 修饰的方法。

    }

    public static void printFields(Class clazz) {
        System.out.println(clazz.getName() + "里的 field");
        for (Field field:clazz.getFields()){
            System.out.println(field.getType()+" "+ field.getName());
        }
    }

    public static void printMethods(Class clazz){
        System.out.println(clazz.getName()+" 里的 method");
        for (Method method:clazz.getMethods()){
            String paratype = "";
            for(Class cl : method.getParameterTypes()){
                paratype += cl +"," +"\t";
            }
            System.out.println(method.getReturnType()+" "+method.getName()+"("+paratype+")");
        }
    }
}

修改访问权限

用 getDeclareField 取得的属性:

  • public 属性,setAccessible 设置成 false 也无法限制访问(用 getField 取得也是如此);
  • private 属性,setAccessible 可以控制是否可以访问;
  • 缺省 或 protected 属性,若类与调用类在同一个包中,setAccessible 无法控制能否访问,全都可以访问。但若在不同包中,setAccessible 可以用来控制是否可以访问。

用 getDeclareMethod 取得的方法:

  • public 方法,setAccessible 设置成 false 也无法限制访问(用 getMethod 取得也是如此);
  • private 方法,setAccessible 可以控制是否可以访问;
  • 缺省 或 protected 方法,若类与调用类在同一个包中,setAccessible 无法控制能否访问,全都可以访问。但若在不同包中,setAccessible 可以用来控制是否可以访问。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值