Java核心卷一之继承和反射详解

目录

继 承

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 )
超类中的这个方法

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 ) 的指令

注: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 类型

 

下面声明和构造一个保存 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 方法

5.7.3 利用反射分析类的能力

下面简要地介绍一下反射机制最重要的内容
检查类的结构
java . lang . reflect 包中有三个类 Field Method Constructor 分别用于描述类的域
法和构造器 这三个类都有一个叫做 getName 的方法 用来返回项目的名称 Held 类有一
getType 方法 用来返回描述域所属类型的 Class 对象 Method Constructor 类有能够
报告参数类型的方法 Method 类还有一个可以报告返回类型的方法 < 个类还有一个叫
getModifiers 的方法 它将返回一个整型数值 用不同的位开关描述 public static 这样
的修饰符使用状况 另外 还可以利用 java . lang . refleCt 包中的 Modifiei 类的静态方法分析
getModifiers 返回的整型数值 例如 可以使用 Modifier 类中的 isPublic isPrivate isFinal
判断方法或构造器是否是 public private final 我们需要做的全部工作就是调用 Modifier
类的相应方法 并对返回的整型数值进行分析 另外 还可以利用 Modifier . toString 方法将
修饰符打印出来
Class 类中的 getFields getMethods getConstructors 方 法 将 分 别 返 回 类 提 供 的
public 方法和构造器数组 其中包括超类的公有成员 Class 类的 getDeclareFields
getDeclareMethods getDeclaredConstructors 方法将分别返回类中声明的全部域 方法和构
造器 其中包括私有和受保护成员 但不包括超类的成员

(等读到jvm的东西继续更新)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值