[java复习] 泛型

概要

这一章的复习主要涵盖 泛型推断协变和逆变泛型擦除泛型的工作原理泛型通配符(有界和无界通配符)泛型容器写法泛型边界自限定泛型潜在类型机制补偿。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:

泛型概念

  1. 泛型实现了参数化类型的概念
  2. 泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,在运行时不存在任何类型相关的信息
  3. 泛型的类型检查出现在 静态类型检查期,编译时会将所有的泛型擦除
  4. 基本类型无法作为泛型的类型参数,但是自动装箱机制使得基本类型能够自动转换为对应的包装类型传入
  5. 数组并不支持泛型,无法创建泛型数组

泛型的使用

类继承中的泛型

泛型类的泛型声明在 类名后面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// test

public class GenericClass<T> {

T value;

public GenericClass(T t) {
this.value = t;
}

static class TwoTuple<A, B> {}

/** 完全将父类的泛型参数延伸到子类的泛型声明中 */
static class ThreeTuple<D, E, F> extends TwoTuple<D, E> {
D d;
E e;
F f;
}

/** 忽略父类的所有泛型参数 */
static class ThreeTuple2<D, E, F> extends TwoTuple {}

/** 继承时指定父类泛型类型,并拓展子类的泛型参数 */
static class FourTuple1<D, F> extends ThreeTuple<Integer, String, String> {}

/** 指定一部分的父类泛型类型,延伸父类剩余的所有的泛型参数到子类的泛型声明中 */
static class FourTuple2<D> extends ThreeTuple<D, String, String> {}

}

类继承中的泛型。我自己理解为: 子类相当于 使用指定泛型类型 的父类型

  • 指定类型。extends 父类 SuperClass<T, E>,将父类声明为 SuperClass<String, Integer>; 直接指定所有泛型 或者 SuperClass<T, Integer> 指定部分泛型。
  • 这种情况的时候,子类中必 须把父类声明中没有的指定的泛型参数同时声明 SubClass<T> extends SuperClass<String, T>,才不会报错

泛型方法

泛型方法可以独立存在不依赖于泛型类,方法的泛型声明在方法 返回值之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// test

public class Method<E> {

/** 'com.example.review.polymorphic.generic.Method.this' cannot be referenced from a static context
静态方法不能使用类声明的泛型参数 */
//! private static void useClassParam(E e) {} // Error

private static <T> void genericMethod(T t) {}

private <T> T getT(T t) {
return t;
}

}

静态方法不能使用类声明的泛型参数,这里的性质可以看出,泛型类似于非静态成员变量,依赖于实例化对象,而且只有的在类初始化的时候指定泛型类型才会生效,依托于对象实例,这个性质在下面类型推断中的 泛型的显示说明 中有同样的体现

泛型推断

调用泛型方法调用会根据 接收类型的泛型类型 自动地进行泛型推断,从而确定泛型方法中应该使用的 正确的泛型类型

适用的场景

  1. 赋值操作
  2. 直接传给形参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// test

public class GenericInfer {

private static <T> List<T> getList() {
return new ArrayList<T>();
}

private static <K, V> Map<K, V> getMap() {
return new HashMap<K, V>();
}

private static void paramList(List<String> list) {}

private static <T> Map<T, String> paramMap(Map<T, String> map) {
return map;
}

/** 测试非静态方法显式类型的说明 */
private void genericInferNon() {
List<String> list = getListNonStat();
paramList(this.<String>getListNonStat());
}


private static void genericInfer() {
List<String> strList = getList();
List<Integer> intList = getList();

paramList(getList()); // 形参照样可以进行推断
paramList(GenericInfer.<String>getList()); // 静态方法显式类型的说明
// paramList(<String>getList()); // Error

Map<Integer, String> map = paramMap(getMap());
map.put(1, "String");
//! map.put("2", "String"); // Error
log("map.size(): " + map.size());

/* Output:

map.size(): 1
*///:~
}

}

显式指定调用方法的泛型

  1. 非静态方法。使用 this 关键字来调用方法,方法前面使用泛型 this.<Generic Type>method()
  2. 静态方法。使用 ClassName.<Generic Type>method() 显式说明泛型类型

泛型原理

泛型动作都发生在边界,对于一个方法就像是进入方法和出方法

  1. set(T t) 会进行静态编译检查
  2. T get() 会给传递出去的值插入转型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// test

public class GenericMechanism {

public static void main(String[] args) {
Holder<Integer> h = new Holder<Integer>();
h.set(Integer.valueOf(10));
Integer i = h.get();
}

}

// Decompile

public static void main(java.lang.String[]);
Code:
0: new #2 // class com/example/review/polymorphic/generic/GenericMechanism$Holder
3: dup
4: aconst_null
5: invokespecial #3 // Method com/example/review/polymorphic/generic/GenericMechanism$Holder."<init>":(Lcom/example/review/polymorphic/generic/GenericMechanism$1;)V
8: astore_1
9: aload_1
10: bipush 10
12: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
15: invokevirtual #5 // Method com/example/review/polymorphic/generic/GenericMechanism$Holder.set:(Ljava/lang/Object;)V
18: aload_1
19: invokevirtual #6 // Method com/example/review/polymorphic/generic/GenericMechanism$Holder.get:()Ljava/lang/Object;

/** 完成传递出的值进行了类型转化 */
22: checkcast #7 // class java/lang/Integer

25: astore_2
26: return

checkcast 是进行类型转化的字节码指令,将 Object 转化为 Integer

泛型擦除

泛型只出现在 静态类型检查期,在运行的时候都会被擦除,所以无法获取到泛型参数的类型信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// test

public class Erase {
/*
测试泛型擦除的影响
1. 不能直接使用泛型参数的类型信息,在编译期泛型会被擦除
2. 由于泛型擦除,运行中的 ArrayList<String> 和 ArrayInteger<Integer> 其实是一样的,
擦除道一个边界,都是 ArrayList<Object>


*/
private static <T> void testGenericErase() {

//! if (T instanceof Object); // Error
//! T t = new T(); // Error
//! T[] ts = new T[10]; // Error

T t = (T)new Object(); // Uncheck warning
T[] ts = (T[])new Object(); // Uncheck warning

Class clazz1 = new ArrayList<Integer>().getClass();
Class clazz2 = new ArrayList<String>().getClass();
log("clazz1 == clazz2: " + (clazz1 == clazz2));

/* Output:

clazz1 == clazz2: true
*///:~
}

}

泛型容器

不能通过泛型直接创建 泛型对象 或者 泛型数组,所以创建一个泛型数组的方法,可以通过两种方法

  1. Object[] arr 保存,获取数组元素的时候进行强制类型转化 return (T)arr[index];
  2. T[] arr = (T[])new Object[len]; 使用泛型数组引用保存,初始化时直接进行类型转化

两种方式都 <font 无法通过类型转化获得泛型的数组,这和数组的本身性质有关

  • 强制类型转化无法改变数组的底层类型,从 Integer[] 转换到了 Number[] 其底层类型依旧是 Integer[] 类型

泛型容器的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// test

public class Container {
static class ListTArr<T> {
private T[] arr;
private int index;

private ListTArr() {
this(10);
}

private ListTArr(int len) {
arr = (T[]) new Object[len]; // Unchecked cast: 'java.lang.Object[]' to 'T[]'
index = 0;
}

private void add(T t) {
arr[index++] = t;
}

private T get(int index) {
return arr[index];
}

private T[] getArr() {
return arr;
}

@Override
public String toString() {
return getString(arr, index);
}
}

static class ListObjArr<T> {
private Object[] arr;

/** ... 省略掉相同内容 */

private ListObjArr(int len) {
arr = new Object[len];
index = 0;
}

private T get(int index) {
return (T)arr[index]; // Unchecked cast: 'java.lang.Object' to 'T'
}

private T[] getArr() {
return (T[])arr;
}

}

/** 两个类的 toString() 方法类似,所以单独提取来一个泛型方法,用来生成要打印的字符串 */
private static <T> String getString(T[] arr, int index) {
StringBuilder ret = new StringBuilder("[");
for (int i = 0; i < index; i++) {
ret.append(arr[i]).append(", ");
}
if (ret.length() > 1) ret.setLength(ret.length() - 2);
ret.append("]");
return ret.toString();
}

private static void genericContainerUseTArr() {
ListTArr<Integer> list = new ListTArr<>(10);
list.add(1);
list.add(2);
log(list.toString());

Integer i = list.get(1);
log("i: " + i);

Integer[] arr = list.getArr();
log(arr.toString());

/* Output:

[1, 2]
i: 2

Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast
to class [Ljava.lang.Integer; ([Ljava.lang.Object; and [Ljava.lang.Integer; are in module
java.base of loader 'bootstrap')
at com.example.review.polymorphic.generic.Container.genericContainerUseTArr(Container.java:81)
at com.example.review.polymorphic.generic.Container.main(Container.java:108)
*///:~
}

private static void genericContainerUseObjArr() {
ListObjArr<Integer> list = new ListObjArr<>(10);
list.add(2);
list.add(4);
log(list.toString());

Integer i = list.get(0);
log("i: " + i);

Integer[] arr = list.getArr(); // Error
log(arr.toString()); // Error

/* Output:

[2, 4]
i: 2

Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast
to class [Ljava.lang.Integer; ([Ljava.lang.Object; and [Ljava.lang.Integer; are in module
java.base of loader 'bootstrap')
at com.example.review.polymorphic.generic.Container.genericContainerUseObjArr(Container.java:105)
at com.example.review.polymorphic.generic.Container.main(Container.java:127)
*///:~
}

/*
测试数组的协变
*/
private static void arrayTypeCast() {
Object[] arr = new Integer[3];
Integer[] arrObj = (Integer[]) arr;
arr[1] = Integer.valueOf(1);
//! arr[2] = new Object(); // Error: ArrayStoreException

arrObj[1] = Integer.valueOf(10);
//! arrObj[2] = new Object(); // Error

log("arr instanceof Object[]: " + (arr instanceof Object[]));
log("arr instanceof Integer[]: " + (arr instanceof Integer[]));

//! Integer[] castArr = (Integer[])new Object[3]; // Error: ClassCastException

/* Output:

arr instanceof Object[]: true
arr instanceof Integer[]: true
*///:~
}

private static void listTypeCast() {
List<Super> list = new ArrayList<>();
// List<Fruit> list2 = new ArrayList<Apple>();
list.add(new Sub());

// GenericBound<Fruit> bound = new GenericBound<Apple>();
}

}
  1. 强制类型转化并 不会影响数组的实际类型,数组的实际类型在创建的时候就已经确定了
  2. 强制类型转化 只能向上转型,或者向其实际类型的类型进行窄化向下转型(例如: 已经转化为了 Object[] 的),此外的转型都会抛出 ClassCastException 类型转化异常

协变和逆变

逆变与协变用来描述类型转换后的继承关系,其定义:

如果 AB 表示类型,f(⋅) 表示类型转换,<= 表示继承关系 (比如: A <= B 表示 A 是由 B 派生出来的子类)

1
2
3
f(⋅) 是逆变的,当 A <= B 时有 f(B) <= f(A) 成立
f(⋅) 是协变的,当 A <= B 时有 f(A) <= f(B) 成立
f(⋅) 是不变的,当 A <= B 时上述两个式子均不成立,即 f(A) 与 f(B) 相互之间没有继承关系

上述的协变和逆变出现的情况,就相当于对两个类继承的类所表示的范围进行操作,这里以一个 Super 父类子类 Sub 来说明

  • 之所以出现定义中的规律,正是因为类型之间的关系,Object 相当于总范围

协变参数类型

方法参数的传入类型可以为参数类型以及它的导出类型(子类)

1
2
3
4
5
6
7
8
9
10
11
// test

private static Super parameterCovariant(Super su) { return new Sub(); }

/*
测试 协变参数类型
*/
private static void parameterCovariantTest() {
parameterCovariant(new Sub());
parameterCovariant(new Super());
}

协变返回类型

导出类(子类) 重写父类方法的 返回类型 可以是父类方法的 返回类型的导出类型(子类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// test

public class Super {

public Super get() {
return this;
}

}

public class Sub extends Super {

@Override
public Sub get() {
return this;
}

}

泛型不支持协变

泛型是不支持协变的,实现协变需要使用 泛型的通配符

1
2
3
4
5
6
7
8
9
10
11
12
// test

/*
测试 数组 和 泛型 是否支持协变
*/
private static void covariant() {
Number[] arr = new Integer[10]; // 数组支持协变
arr[1] = Integer.valueOf(1);

/** 泛型不支持协变 */
//! List<Number> list = new ArrayList<Integer>(); // Error
}
  • 数组支持协变
  • 泛型不支持协变

泛型边界

在泛型声明的位置使用 extends 关键字可以指定泛型参数 T 的上界, 只能有一个类,可以有多个接口作为上界,放在泛型声明的后面,使用 & 连接

泛型边界的意义

由于泛型的擦除,<T> 泛型会被 擦除到他们的第一个边界,没有设置边界的泛型参数在运行时,会被 擦除到 Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// test

public class GenericBound {

interface Food {}
interface Water {
void drink();
}

static class Fruit implements Food {
void getFruit() {}
}

static class Banana {}
static class Apple extends Fruit {
void getBanana() {}
}
static class Pear extends Fruit implements Water {
@Override
public void drink() {

}
}

/** Interface expected here */
//! private static <T extends Food & Fruit> void method() {} // Error

private static <T extends Fruit & Food & Water> void upperBound(T t) {
t.getFruit();
t.drink();
}

//! private static <T super Apple> void lowerBound() {} // Error

private static void boundTest() {
//! upperBound(new Banana()); // Error
upperBound(new Pear());
}

}
  • 泛型边界的声明使用的是 & 与运算符,所以需要同时满足继承类并实现所有的接口

通配符

需要注意的地方就是区分 通配符泛型参数,也是我半天没弄明白的地方

  • 泛型参数。用于声明泛型参数,泛型的边界也在声明中加入
  • 通配符。其实就是相当于确切类型的占位符,放置在使用泛型的地方

? 非限定通配符

<?> 通配符,其实就是 <? extends Object> 的显示扩展,但是在使用的时候又稍微有点不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// test

public class Wildcard {

private static List list1;
private static List<?> list2;
private static List<? extends Object> list3;

private static void g1(List list) {
list1 = list;
list2 = list;
//! list3 = list; // Warning
}

private static void g2(List<?> list) {
list1 = list;
list2 = list;
list3 = list;
}

private static void g3(List<? extends Object> list) {
list1 = list;
list2 = list;
list3 = list;

}

private static void genericCatch() {
Holder h1 = new Holder();
Holder<?> h2 = new Holder();
//! Holder<? extends Object> h3 = new Holder(); // Warning
}

}
  • 只有在 <? extends Object> 接收 原生类型 的时候,才会出现编译警告

使用了 <?> 统配符的引用类型

  • 对于像 void set(T t) 这种泛型操作都会时效,因为 <?> 类似于 <? extends Object> 不能确定持有的泛型是哪一种,因此砍掉了所有的泛型入口

  • 对于 T get() 泛型出口,统一的接收类型就又变成了 Object

限定通配符

限定通配符包括两种

  1. ? extends Type 上界限定通配符
  2. ? super Type 下界限定通配符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// test

public class Wildcard {

/** 注意这是边界,不是通配符!类比边界不能写成 T super MyClass */
//! private static <T super Sub> void superBond() {} // Error!

private static <T extends Sub> void extendsBound() {}


/*
测试限定通配符
*/
private static void wildcard(List<? extends Super> paramList) {
List<? extends Super> upperList = new ArrayList<Sub>(Arrays.asList(new Sub[]{new Sub()}));
//! upperList.add(new Super()); // Error
//! upperList.add(new Sub()); // Error

//! Sub us = upperList.get(0); // Error
Super us = upperList.get(0); //
log("us: " + us);


List<? super Sub> lowerList = new ArrayList<Super>();
//! lowerList.add(new Super()); // Error
lowerList.add(new Sub());

Super ls = upperList.get(0);

log("ls: " + ls);

/* Output:

us: com.example.review.polymorphic.generic.democlass.Sub@2f4d3709
ls: com.example.review.polymorphic.generic.democlass.Sub@2f4d3709
*///:~
}

/*
测试通配符的含义
*/
private static void method(List<? super Sub> list) {
//! list.add(new Super()); // 通配符就是泛型类型的一个替代符号
list.add(new Sub());
}

}

两种限定通配符分析

  1. ? extends Type 作为泛型类型的时候,插入操作都会失效,获取操作正常 接收类型为 UpperBound

    • 插入: 对于 ? extends Type 这一个通配符而言而言,并不能知道确切类型,所以所有关于泛型的插入一刀切

    • 获取: 能够统配的类型肯定为 Type 的导出类型(子类型)

      1
      UpperBound u = list.get(index);
  1. ? super Type 作为泛型类型的时候,插入extends LowerBound 的对象正常,获取的接收类型为实际类型

    • 插入: 编译器可以知道统配的类型是 LowerBound 的基类型(父类型),所以 LowerBound 以及他的导出类型(子类型) 肯定可以插入

    • 获取: 对于? super LowerBound 只知道为 LowerBound 的一个基类型,对于 ? super LowerBound 的接受类型就为 Object (根类型)

      1
      2
      list = new ArrayList<Type>();
      Type t = list.get(index);

区别对比

限定通配符 泛型返回类型的接收类型( T get() ) 泛型参数/插入类型( void set(T t) )
? extends UpperBound UpperBound x
? super LowerBound Object LowerBound 以及其子类型

泛型捕获转化

编译器可以通过原生类型推断出实际的泛型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// test

public class Catch {

private static <T> void f1(Holder<T> holder) {
T t = holder.get();
log("T: " + t);
}

private static void f2(Holder<?> holder) {
f1(holder);
}

private static void genericCatch() {
Holder holder = new Holder<Sub>(new Sub());
f2(holder);

/** Unchecked assignment: 'com.example.review.polymorphic.generic.democlass.Holder'
to 'com.example.review.polymorphic.generic.democlass.Holder<java.lang.Object>' */
//! f1(holder); // Warning

/* Output:

T: Sub{}
*///:~
}

}
  • 如果只将原生类型传入到有拥有泛型参数的方法中,就会抛出未检查的警告,而使用 <?> 来接收原生类型,可以推断出其内部的泛型类型
  • <T> 接收 <?> 不会发出警告

自限定泛型

  1. 将泛型指定为 <A extends SelfBound<A>> 自限定泛型,只有类 A 满足了 class A extends Bound<A> 这种声明方式,才能作为该类的泛型参数

  2. 自限定的作用就是使基类中的类型,随着子类的指定而发生变化,将父类泛型方法的返回类型指定为了 DerivedGS 这个导出类型

    1. 基类为一个简单的泛型类
      class Basic<E>

    2. 子类实现自限定
      class Derived extends Basic<Derived> {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// test

public class SelfBound {
static class Bound<E extends Bound<E>> {}

static class A extends Bound<A> {}
static class B extends Bound<B> {}

static class C extends Bound<A> {}

static class D {}

//! static class E extends SelfBound<D> {} // Error


static class OrdinarySetter {
void set(Super su) {
log("OrdinarySetter # set");
}
}

static class DerivedSetter extends OrdinarySetter {
void set(Sub su) { // 这里并不是 Override,方法的参数不同为不同的方法
log("DerivedSetter # set");
}
}

/*
测试直接继承
*/
private static void ordinaryArgument() {
Super su = new Super();
Sub sub = new Sub();

DerivedSetter derivedSetter = new DerivedSetter();
derivedSetter.set(su);
derivedSetter.set(sub);

/* Output:

OrdinarySetter # set
DerivedSetter # set
*///:~
}


static class GenericSetter<T> {
void set(T t) {
log("GenericSetter # set");
}
}

static class DerivedGS extends GenericSetter<Sub> {
@Override
void set(Sub sub) {
super.set(sub);
log("DerivedGS # set");
}
}


/*
测试自限定继承
*/
private static void genericInheritance() {
Super su = new Super();
Sub sub = new Sub();

DerivedGS derivedGS = new DerivedGS();
//! derivedSetter.set(su); // Error
derivedGS.set(sub);

/* Output:

GenericSetter # set
DerivedGS # set
*///:~
}

}
  • DerivedSetter 子类和 OrdinarySetter 父类的方法的 参数类型 不一致,为两个不同的方法,属于 方法重载
  • DerivedGS 中通过 super.set(sub) 调用 GenericSetter # set 方法成功,说明它重写了父类方法,DerivedGS # set 覆盖了父类的方法,属于 方法重写

潜在类型机制的补偿

对于存在相同名称方法的类,他们之间没有继承关系,可以通过 反射 来完成对其的泛化使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// test

public class PotentialType {
static class Fruit {
void eat() {
log("eat Fruit");
}
}

static class Animal {
void eat() {
log("eat Animal");
}
}

/*
测试使用反射来调用不相关的类的同名方法
*/
private static void eat(Object obj) {
Class<?> clazz = obj.getClass();
try {
Method method = clazz.getDeclaredMethod("eat");
method.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

private static void reflect() {
eat(new Fruit());
eat(new Animal());

/* Output:

eat Fruit
eat Animal
*///:~
}

}
  • 使用 Class # getMethod 只能获取到类中 public 权限的方法,Class # getDeclaredFields 可以获取到私有权限的属性