目录
继 承
5.1 类、超类和子类
5.1.1 定义子类
5.1.2 覆盖方法
5.1.3 子类构造器
5.1.4 继承层次
5 . 1.5 多 态
5.1.6 理解方法调用
5.1.7 阻止继承:final 类和方法
5.1.8 强制类型转换
5 . 1.9 抽象类
5.1.10 受保护访问
5.3 泛型数组列表(详细会出一个专题)
5.4 对象包装器与自动装箱
5.7 反射
5.7.1 Class 类
5.7.3 利用反射分析类的能力
(等读到jvm的东西继续更新)
继 承
利用继承 , 人们可以基于已存在的类构造一个新类 。 继承已存在的类就 是复用 ( 继承 ) 这些类的方法和域 。 在此基础上 , 还可以添加一些新的方法和域 , 以满足新 的需求 。 这是 Java 程序设计中的一项核心技术 。 反射是指在程序运行期间发现更多的类 及其属性的能力 。 这是一个功能强大的特性 , 使用起来也比较复杂 。5.1 类、超类和子类
假设你在某个公司工作
,
这 个公司中经理的待遇与普通雇员的待遇存在着一些差异。
不过
,
他们之间也存在着很多相同 的地方,
例如
,
他们都领取薪水
。
只是普通雇员在完成本职任务之后仅领取薪水
,
而经理在 完成了预期的业绩之后还能得到奖金。
这种情形就需要使用继承
。
这是因为需要为经理定义 一个新类 Manager
,
以便增加一些新功能
。
但可以重用
Employee
类中已经编写的部分代码
, 并将其中的所有域保留下来。
从理论上讲
,
在
Manager
与
Employee
之间存在着明显的
“
is
- a”
(
是
)
关系
,
每个经理都是一名雇员
:“ is
-
a
”
关系是继承的一个明显特征
。
5.1.1 定义子类
下面是由继承
Employee
类来定义
Manager
类的格式
,
关键字
extends
表示继承
关键字
extends
表明正在构造的新类派生于一个已存在的类
。
已存在的类称为超类
(
superclass
)
、
基类
(
base
class
)
或父类
(
parent
class
)
;
新类称为子类
(
subclass
、
)
派生类
(
derived
class
)
或孩子类
(
child
class
)
。
超类和子类是
Java
程序员最常用的两个术语
,
而了解
其他语言的程序员可能更加偏爱使用父类和孩子类
,
这些都是继承时使用的术语
。
在通过扩展超类定义子类的时候
,
仅需要指出子类与超类的不同之处
。因此在设计类的
时候
,
应该将通用的方法放在超类中
,
而将具有特殊用途的方法放在子类中
,
这种将通用的
功能放到超类的做法
,
在面向对象程序设计中十分普遍
5.1.2 覆盖方法
然而
,
超类中的有些方法对子类
Manager
并不一定适用
。
具体来说
,
Manager
类中的
getSalary
方法应该返回薪水和奖金的总和
。
为此
,
需要提供一个新的方法来覆盖
(
override
)
超类中的这个方法
:
5.1.2 覆盖方法 然而 , 超类中的有些方法对子类 Manager 并不一定适用 。 具体来说 , Manager 类中的 getSalary 方法应该返回薪水和奖金的总和 。 为此 , 需要提供一个新的方法来覆盖 ( override ) 超类中的这个方法 :
super.getSalary()上述语句调用的是 Employee 类中的 getSalary 方法。
注释 : 有些人认为 super 与 this 引用是类似的概念 , 实际上 , 这样比较并不太恰当 。 这是 因为 super 不是一个对象的引用 , 不能将 super 赋给另一个对象变量 , 它只是一个指示编 译器调用超类方法的特殊关键字 。
5.1.3 子类构造器
由于 Manager
类的构造器不能访问
Employee
类的私有域
,
所以必须利用
Employee
类
的构造器对这部分私有域进行初始化
,
我们可以通过
super
实现对超类构造器的调用
。
使用
super
调用构造器的语句必须是子类构造器的第一条语句
。
如果子类的构造器没有显式地调用超类的构造器,
则将自动地调用超类默认
(
没有参数
)
的构造器
。
如果超类没有不带参数的构造器
,
并且在子类的构造器中又没有显式地调用超类
的其他构造器’ 则
Java
编译器将报告错误
。
5.1.4 继承层次
继承并不仅限于一个层次
。
例如
,
可以由
Manager
类派生
Executive
类
。由一个公共超类派生出来的所有类的集合被称为继承层次(
inheritance
hierarchy ), 如图 5-1 所示。在继承层次中, 从某个特定的类到其祖先的路径被称为该类的继承链
(
inheritance
chain)
5 . 1.5 多 态
有一个用来判断是否应该设计为继承关系的简单规则,
这就是
“
is
-
a
”
规则,
它表明子类的每个对象也是超类的对象。
“
is
-
a
”
规则的另一种表述法是置换法则
。
它表明程序中出现超类对象的任何地方都可以
用子类对象置换
。
例如
,
可以将一个子类的对象赋给超类变量
。
在
Java
程序设计语言中
,
对象变量是多态的
。
一个
Employee
变量既可以引用一个
Employee
类对象
,
也可以引用一个
Employee
类的任何一个子类的对象
(
例如
,
Manager
、
Executive
、
Secretary
等
)
然而
,
不能将一个超类的引用赋给子类变量
。
原因很清楚
:不是所有的雇员都是经理
。
如果赋值成功
,
m
有可能引用了一个不是经理的
Employee
对象
,
当在后面调用
m
.
setBonus
(
.
.
.
)
时就有可能发生运行时错误
。
5.1.6 理解方法调用
弄清楚如何在对象上应用方法调用非常重要
。
下面假设要调用
x
.
f
(
args
,
)
隐式参数
x
声
明为类
C
的一个对象
。
下面是调用过程的详细描述:
1
)
编译器査看对象的声明类型和方法名
。
2
)
接下来
,
编译器将査看调用方法时提供的参数类型
。
由于允许类型转换
(
int
可以转换成
double
,
Manager
可以转换成
Employee
,
等等
,
)
所以这
个过程可能很复杂
。
如果编译器没有找到与参数类型匹配的方法
,
或者发现经过类型转换后
有多个方法与之匹配
,
就会报告一个错误
。
至此
,
编译器已获得需要调用的方法名字和参数类型
。
3
)
如果是
private
方法
、
static
方法
、
final
方法
(
有关
final
修饰符的含义将在下一节讲
述
)
或者构造器
,
那么编译器将可以准确地知道应该调用哪个方法
,
我们将这种调用方式称
为静态绑定
(
static
binding
)
。
与此对应的是
,
调用的方法依赖于隐式参数的实际类型
,
并且
在运行时实现动态绑定
。
在我们列举的示例中
,
编译器采用动态绑定的方式生成一条调用
f
(
String
)
的指令
。
5 . 1.5 多 态
有一个用来判断是否应该设计为继承关系的简单规则,
这就是
“
is
-
a
”
规则,
它表明子类的每个对象也是超类的对象。
“
is
-
a
”
规则的另一种表述法是置换法则
。
它表明程序中出现超类对象的任何地方都可以
用子类对象置换
。
例如
,
可以将一个子类的对象赋给超类变量
。
在
Java
程序设计语言中
,
对象变量是多态的
。
一个
Employee
变量既可以引用一个
Employee
类对象
,
也可以引用一个
Employee
类的任何一个子类的对象
(
例如
,
Manager
、
Executive
、
Secretary
等
)
然而
,
不能将一个超类的引用赋给子类变量
。
原因很清楚
:不是所有的雇员都是经理
。
如果赋值成功
,
m
有可能引用了一个不是经理的
Employee
对象
,
当在后面调用
m
.
setBonus
(
.
.
.
)
时就有可能发生运行时错误
。
5.1.6 理解方法调用
弄清楚如何在对象上应用方法调用非常重要
。
下面假设要调用
x
.
f
(
args
,
)
隐式参数
x
声
明为类
C
的一个对象
。
下面是调用过程的详细描述:
1
)
编译器査看对象的声明类型和方法名
。
2
)
接下来
,
编译器将査看调用方法时提供的参数类型
。
由于允许类型转换
(
int
可以转换成
double
,
Manager
可以转换成
Employee
,
等等
,
)
所以这
个过程可能很复杂
。
如果编译器没有找到与参数类型匹配的方法
,
或者发现经过类型转换后
有多个方法与之匹配
,
就会报告一个错误
。
至此
,
编译器已获得需要调用的方法名字和参数类型
。
3
)
如果是
private
方法
、
static
方法
、
final
方法
(
有关
final
修饰符的含义将在下一节讲
述
)
或者构造器
,
那么编译器将可以准确地知道应该调用哪个方法
,
我们将这种调用方式称
为静态绑定
(
static
binding
)
。
与此对应的是
,
调用的方法依赖于隐式参数的实际类型
,
并且
在运行时实现动态绑定
。
在我们列举的示例中
,
编译器采用动态绑定的方式生成一条调用
f
(
String
)
的指令
。
注:Java中只有private、static和final修饰的方法以及构造方法是静态绑定。
a、private方法的特点是不能被继承,也就是不存在调用其子类的对象,只能调用对象自身,因此private方法和定义该方法的类绑定在一起。
b、static方法又称类方法,类方法属于类文件。它不依赖对象而存在,在调用的时候就已经知道是哪个类的,所以是类方法是属于静态绑定。
c、final方法:final方法可以被继承,但是不能被重写,所以也就是说final方法是属于静态绑定的,因为调用的方法是一样的。
总结:如果一个方法不可被继承或者继承后不可被覆盖,那么这个方法就采用的静态绑定。
4 ) 当程序运行 , 并且采用动态绑定调用方法时 , 虚拟机一定调用与 x 所引用对象的实 际类型最合适的那个类的方法 。 假设 x 的实际类型是 D , 它是 C 类的子类 。 如果 D 类定义了 方法 f ( String , ) 就直接调用它 ; 否则 , 将在 D 类的超类中寻找 f ( String , ) 以此类推 。动态绑定
编译器在每次调用方法时都要进行搜索,时间开销相当大。因此虚拟机会预先为每个类创建一个方发表(method table),其中列出了所有方法的签名和实际调用的方法。
动态绑定过程:
<1>虚拟机提取对象的实际类型的方法表。
<2>虚拟机搜索方法签名,此时虚拟机已经知道应该调用哪种方法。(PS:方法的签名包括了:1.方法名 2.参数的数量和类型~~~~返回类型不是签名的一部分。)
<3>虚拟机调用方法
动态绑定有一个非常重要的特性 : 无需对现存的代码进行修改 , 就可以对程序进行扩展 。 假设增加一个新类 Executive , 并且变量 e 有可能引用这个类的对象 , 我们不需要对包含调用 e . getSalary ( ) 的代码进行重新编译 。 如果 e 恰好引用一个 Executive 类的对象 , 就会自动地调 用 Executive . getSalaryO 方法5.1.7 阻止继承:final 类和方法
有时候
,
可能希望阻止人们利用某个类定义子类
。
不允许扩展的类被称为 final
类
。
如果
在定义类的时候使用了
final
修饰符就表明这个类是
final
类
。
例如
,
假设希望阻止人们定义
Executive
类的子类
,
就可以在定义这个类的时候
’
使用
final
修饰符声明
。
5.1.8 强制类型转换
进行类型转换的唯一原因是
:
在暂时忽视对象的实际类型之后
,
使用对象的全部功能
。
例如
,
在
managerTest
类中
,
由于某些项是普通雇员
,
所以
staff
数组必须是
Employee
对象
的数组
。
我们需要将数组中引用经理的元素复原成
Manager
类
,
以便能够访问新增加的所有
变量
(
需要注意
,
在前面的示例代码中
,
为了避免类型转换
,
我们做了一些特别的处理
,
即
将
boss
变量存入数组之前
,
先用
Manager
对象对它进行初始化
。
而为了设置经理的奖金
,
必
须使用正确的类型
。
最后
,
如果这个类型转换不可能成功
,
编译器就不会进行这个转换
。
例如
,
下面这个类
型转换
:
String
c
=
(
String
)
staff
[
1
];
将会产生编译错误
,
这是因为
String
不是
Employee
的子类
。
综上所述
:
•
只能在继承层次内进行类型转换
。
•
在将超类转换成子类之前
,
应该使用
instanceof
进行检查
。
5 . 1.9 抽象类
5.1.10 受保护访问
大家都知道,
最好将类中的域标记为
private
,
而方法标记为
public
。
任何声明为
private
的内容对其他类都是不可见的
。
前面已经看到
,
这对于子类来说也完全适用
,
即子类也不能
访问超类的私有域
。
然而,
在有些时候
,
人们希望超类中的某些方法允许被子类访问
,
或允许子类的方法访
问超类的某个域
。
为此
,
需要将这些方法或域声明为
protected
。
例如
,
如果将超类
Employee
中的
hireDay
声明为
proteced
,
而不是私有的
,
Manager
中的方法就可以直接地访问它
。
不过
,
Manager
类中的方法只能够访问
Manager
对象中的
hireDay
域
,
而不能访问其他
Employee
对象中的这个域
。
这种限制有助于避免滥用受保护机制
,
使得子类只能获得访问受
保护域的权利
。
在实际应用中,
要谨慎使用
protected
属性
。
假设需要将设计的类提供给其他程序员使
用
,
而在这个类中设置了一些受保护域
,
由于其他程序员可以由这个类再派生出新类
,
并访
问其中的受保护域
。
在这种情况下
,
如果需要对这个类的实现进行修改
,
就必须通知所有使
用这个类的程序员
。
这违背了
OOP
提倡的数据封装原则
。
下面归纳一下
Java
用于控制可见性的
4
个访问修饰符
:
1
)
仅对本类可见 private。
2
)
对所有类可见 public:
3
)
对本包和所有子类可见 protected。
4
) 对本包可见
默认(
很遗憾
,
)
不需要修饰符。
5.3 泛型数组列表(详细会出一个专题)
在许多程序设计语言中
,
特别是在
C
++
语言中
,
必须在编译时就确定整个数组的大小
。
程序员对此十分反感
,
因为这样做将迫使程序员做出一些不情愿的折中
。
例如
,
在一个部门
中有多少雇员
?
肯定不会超过丨
00
人
。
一旦出现一个拥有
150
名雇员的大型部门呢
?
愿意为
那些仅有
10
名雇员的部门浪费
90
名雇员占据的存储空间吗
?
在
Java
中
,
情况就好多了
。它允许在运行时确定数组的大小。
当然
,
这段代码并没有完全解决运行时动态更改数组的问题
。
一旦确定了数组的大小
,
改
变它就不太容易了
。
在
Java
中
,
解决这个问题最简单的方法是使用
Java
中另外一个被称为
ArrayList
的类
。
它使用起来有点像数组
,
但在添加或删除元素时
,
具有自动调节数组容量的
功能
,
而不需要为此编写任何代码
。
ArrayList
是一个采用类型参数
(
type
parameter
)
的泛型类
(
generic
class
)
。
为了指定数
组列表保存的元素对象类型
,
需要用一对尖括号将类名括起来加在后面
,
例如
,
ArrayList
<
Employee
>
。
在第
8
章中将可以看到如何自定义一个泛型类
,
这里并不需要了解任何技术细
节就可以使用
ArrayList
类型
。
5 . 1.9 抽象类
5.1.10 受保护访问
大家都知道,
最好将类中的域标记为
private
,
而方法标记为
public
。
任何声明为
private
的内容对其他类都是不可见的
。
前面已经看到
,
这对于子类来说也完全适用
,
即子类也不能
访问超类的私有域
。
然而,
在有些时候
,
人们希望超类中的某些方法允许被子类访问
,
或允许子类的方法访
问超类的某个域
。
为此
,
需要将这些方法或域声明为
protected
。
例如
,
如果将超类
Employee
中的
hireDay
声明为
proteced
,
而不是私有的
,
Manager
中的方法就可以直接地访问它
。
不过
,
Manager
类中的方法只能够访问
Manager
对象中的
hireDay
域
,
而不能访问其他
Employee
对象中的这个域
。
这种限制有助于避免滥用受保护机制
,
使得子类只能获得访问受
保护域的权利
。
在实际应用中,
要谨慎使用
protected
属性
。
假设需要将设计的类提供给其他程序员使
用
,
而在这个类中设置了一些受保护域
,
由于其他程序员可以由这个类再派生出新类
,
并访
问其中的受保护域
。
在这种情况下
,
如果需要对这个类的实现进行修改
,
就必须通知所有使
用这个类的程序员
。
这违背了
OOP
提倡的数据封装原则
。
下面归纳一下
Java
用于控制可见性的
4
个访问修饰符
:
1
)
仅对本类可见 private。
2
)
对所有类可见 public:
3
)
对本包和所有子类可见 protected。
4
) 对本包可见
默认(
很遗憾
,
)
不需要修饰符。
5.3 泛型数组列表(详细会出一个专题)
在许多程序设计语言中
,
特别是在
C
++
语言中
,
必须在编译时就确定整个数组的大小
。
程序员对此十分反感
,
因为这样做将迫使程序员做出一些不情愿的折中
。
例如
,
在一个部门
中有多少雇员
?
肯定不会超过丨
00
人
。
一旦出现一个拥有
150
名雇员的大型部门呢
?
愿意为
那些仅有
10
名雇员的部门浪费
90
名雇员占据的存储空间吗
?
在
Java
中
,
情况就好多了
。它允许在运行时确定数组的大小。
当然
,
这段代码并没有完全解决运行时动态更改数组的问题
。
一旦确定了数组的大小
,
改
变它就不太容易了
。
在
Java
中
,
解决这个问题最简单的方法是使用
Java
中另外一个被称为
ArrayList
的类
。
它使用起来有点像数组
,
但在添加或删除元素时
,
具有自动调节数组容量的
功能
,
而不需要为此编写任何代码
。
ArrayList
是一个采用类型参数
(
type
parameter
)
的泛型类
(
generic
class
)
。
为了指定数
组列表保存的元素对象类型
,
需要用一对尖括号将类名括起来加在后面
,
例如
,
ArrayList
<
Employee
>
。
在第
8
章中将可以看到如何自定义一个泛型类
,
这里并不需要了解任何技术细
节就可以使用
ArrayList
类型
。
5.3 泛型数组列表(详细会出一个专题) 在许多程序设计语言中 , 特别是在 C ++ 语言中 , 必须在编译时就确定整个数组的大小 。 程序员对此十分反感 , 因为这样做将迫使程序员做出一些不情愿的折中 。 例如 , 在一个部门 中有多少雇员 ? 肯定不会超过丨 00 人 。 一旦出现一个拥有 150 名雇员的大型部门呢 ? 愿意为 那些仅有 10 名雇员的部门浪费 90 名雇员占据的存储空间吗 ? 在 Java 中 , 情况就好多了 。它允许在运行时确定数组的大小。 当然 , 这段代码并没有完全解决运行时动态更改数组的问题 。 一旦确定了数组的大小 , 改 变它就不太容易了 。 在 Java 中 , 解决这个问题最简单的方法是使用 Java 中另外一个被称为 ArrayList 的类 。 它使用起来有点像数组 , 但在添加或删除元素时 , 具有自动调节数组容量的 功能 , 而不需要为此编写任何代码 。 ArrayList 是一个采用类型参数 ( type parameter ) 的泛型类 ( generic class ) 。 为了指定数 组列表保存的元素对象类型 , 需要用一对尖括号将类名括起来加在后面 , 例如 , ArrayList < Employee > 。 在第 8 章中将可以看到如何自定义一个泛型类 , 这里并不需要了解任何技术细 节就可以使用 ArrayList 类型 。
下面声明和构造一个保存 Employee 对象的数组列表:
这被称为 “ 菱形 ” 语法 , 因为空尖括号 o 就像是一个菱形 。 可以结合 new 操作符使用菱形 语法 。 编译器会检查新值是什么 。 如果赋值给一个变量 , 或传递到某个方法 , 或者从某个方 法返回 , 编译器会检査这个变量 、 参数或方法的泛型类型 , 然后将这个类型放在 o 中 。 在 这个例子中 , new ArrayListo ( ) 将赋至一个类型为 ArrayList < Employee > 的变量 , 所以泛型 类型为 Employee 。
5.4 对象包装器与自动装箱
有时,
需要将
int
这样的基本类型转换为对象
。
所有的基本类型都冇一个与之对应的类
。
例如
,
丨
nteger
类对应基本类型
int
。
通常
,
这些类称为包装器
(
wrapper
)
这些对象包装器类
拥有很明显的名字
:
Integer
、
Long
、
Float
、
Double
、
Short
、
Byte
、
Character
、
Void
和
Boolean
(
前
6 个类派生于公共的超类
Number
)
。
对象包装器类是不可变的
,
即一旦构造了包装器
,
就不
允许更改包装在其中的值
。
同时
,
对象包装器类还是
final
,
因此不能定义它们的子类
。
幸运的是
,
有一个很有用的特性
,
从而更加便于添加
int
类型的元素到
ArrayLisKlntegeP
中
。下面这个调用 list
.
add
(
3
)
;
将自动地变换成 list
.
add
(
Integer
.
value0
f
(
3
))
;
这种变换被称为自动装箱
(
autoboxing
。
相反地
,
当将一个
Integer
对象赋给一个
int
值时
,
将会自动地拆箱
。
也就是说
,
编译器
将下列语句:
int
n
=
list
.
get
(
i
)
;
翻译成
int
n
=
list
.get(
i
).intValue
(
)
最后强调一下
,
装箱和拆箱是编译器认可的
,
而不是虚拟机
。
编译器在生成类的字节码
时
,
插人必要的方法调用
。
虚拟机只是执行这些字节码
。
5.7 反射
反射库
(
reflection
library
)
提供了一个非常丰富且精心设计的工具集
,
以便编写能够动
态操纵
Java
代码的程序
。
这项功能被大量地应用于
JavaBeans
中
,
它是
Java
组件的体系结构
(
有关
JavaBeans
的详细内容在卷
II
中阐述
)
。
使用反射
,
Java
可以支持
Visual
Basic
用户习惯
使用的工具
。
特别是在设计或运行中添加新类时
,
能够快速地应用开发工具动态地查询新添
加类的能力
。
能够分析类能力的程序称为反射
(
reflective
)
。
反射机制的功能极其强大
,
在下面可以看
到
,
反射机制可以用来
:
•
在运行时分析类的能力
。
•
在运行时查看对象
,
例如
,
编写一个
toString
方法供所有类使用
。
•
实现通用的数组操作代码
。
•
利用
Method
对象
,
这个对象很像中的函数指针
5.7.1 Class 类 在程序运行期间 , Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识 。 这个信息跟踪着每个对象所属的类 。 虚拟机利用运行时类型信息选择相应的方法执行 。 然而 , 可以通过专门的 Java 类访问这些信息 。 保存这些信息的类被称为 Class , 这 个 名 字很容易让人混淆 。 Object 类中的 getClass ( ) 方法将会返回一个Class 类型的实例 。
如同用一个 Employee 对象表示一个特定的雇员属性一样 , 一个 Class 对象将表示一个特 定类的属性 。 最常用的 Class 方法是 getName 。 这个方法将返回类的名字 。 例如 , 下面这条 语句 :
还可以调用静态方法 forName 获得类名对应的 Class 对象 。(全限定类名) String dassName = " java . util . Random " ; Class cl = Cl ass . forName ( dassName ) ; 如果类名保存在字符串中 , 并可在运行中改变 , 就可以使用这个方法 。 当然 , 这个方法 只有在 dassName 是类名或接口名时才能够执行 。 否则 , forName 方法将抛出一个 checked exception ( 已检查异常 ) 。 无论何时使用这个方法 , 都应该提供一个异常处理器 ( exception handler ) o 如何提供一个异常处理器 , 请参看下一节 。 获得 Class 类对象的第三种方法非常简单 。 如果 T 是任意的 Java 类型 ( 或 void 关键字 , ) T . class 将代表匹配的类对象 。例如:
请注意 , 一个 Class 对象实际上表示的是一个类型 , 而这个类型未必一定是一种类 。 例如 , int 不是类 , 但 int . class 是一个 Class 类型的对象 。 还有一个很有用的方法 newlnstance ( ) , 可以用来动态地创建一个类的实例例如 , e . getClass 0 . newlnstance ( ) ; 创建了一个与 e 具有相同类类型的实例 。 newlnstance 方法调用默认的构造器 ( 没有参数的构 造器 ) 初始化新创建的对象 。 如果这个类没有默认的构造器 , 就会抛出一个异常 _ 将 forName 与 newlnstance 配合起来使用 , 可以根据存储在字符串中的类名创建一个对象 String s = " java . util . Random " ; Object m = Cl ass . forName ( s ) . newlnstance ( ) ; 如果需要以这种方式向希望按名称创建的类的构造器提供参数 , 就不要使用上面 那条语句 , 而必须使用 Constructor 类中的 newlnstance 方法 。



