文章目录
Class 类
Class 类代表类的类。每个 Class 类的实例,都代表了一个类。
每个类在 jvm 中都对应一个 Class 对象。jvm 将要使用的 Class 对象加入到类加载器中。
- java 程序运行前,并不会将所有的 Class 对象放入 jvm 中;
- 每个 .class 类在 jvm 中只会对应一个 Class 实例对象;
- Class 对象只能由 jvm 创建和加载;
- Class 对象功能:运行时提供和获取某个对象的类型信息。
获得 Class 对象的三种方法
Object 类的.getClass()
与 类名.class
与 Class.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 可以用来控制是否可以访问。