栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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的东西继续更新)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/358127.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号