第5章 继承

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/NCUscienceZ/article/details/83216146

1.然而, 超类中的有些方法对子类 Manager 并不一定适用 。 具体来说 , Manager 类中的getSalary 方法应该返回薪水和奖金的总和。 为此 , 需要提供一个新的方法来覆盖 ( override )超类中的这个方法 :

public double getSalaryO
{
return salary + bonus ; / / won ' t work
}

然而, 这个方法并不能运行 。 这是因为 Manager 类的 getSalary 方法不能够直接地访问超类的私有域。 也就是说 , 尽管每个Manager 对象都拥有一个名为 salary 的域 , 但在 Manager 类的getSalary 方法中并不能够直接地访问 salary 域 。 只有 Employee 类的方法才能够访问私有部分。 如果Manager 类的方法一定要访问私有域 , 就必须借助于公有的接口 , Employee 类中的公有方法 getSalary 正是这样一个接口 。
现在, 再试一下 。 将对 salary域的访问替换成调用 getSalary 方法。

public double getSalaryO
{
double baseSalary = getSalaryO ; / / still won ' t work
return baseSalary + bonus ;
}

上面这段代码仍然不能运行。 问题出现在调用getSalary 的语句上, 这是因为 Manager 类
也有一个 getSalary 方法 ( 就是正在实现的这个方法 ), 所以这条语句将会导致无限次地调用自己, 直到整个程序崩溃为止 。这里需要指出 : 我们希望调用超类 Employee 中的 getSalary 方法 , 而不是当前类的这个方法。 为此 ,可以使用特定的关键字 super 解决这个问题 :

super . getSalaryO

上述语句调用的是 Employee 类中的 getSalary 方法 。 下面是 Manager 类中 getSalary 方法的正确书写格式 :

public double getSalaryO
{
double baseSalary = super . getSalaryO *
return baseSalary + bonus ;
}

2、 注释 : 有些人认为 super 与 this 引用是类似的概念 , 实际上 , 这样比较并不太恰当 。 这是因为 super 不是一个对象的引用 , 不能将 super 赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

3、正像前面所看到的那样 , 在子类中可以增加域 、 增加方法或覆盖超类的方法, 然而绝对不能删除继承的任何域和方法。

4、 使用super 调用构造器的语句必须是子类构造器的第一条语句 。

5、在 Java 程序设计语言中, 对象变量是多态的 。 一个Employee 变量既可以引用一个
Employee 类对象 , 也可以引用一个 Employee 类的任何一个子类的对象。

Manager boss = new Manager ( . . . ) ;
Employee [ ] staff = new Employee [ 3 ] ;
staff [ 0 ] = boss ;

在这个例子中, 变量 staff [ 0 ] 与 boss 引用同一个对象。 但编译器将staff [ 0 ] 看成 Employee 对象 。这意味着 , 可以这样调用
boss . setBonus ( 5000 ) ; // OK
但不能这样调用
staff [ 0 ] . setBonus ( 5000 ) ; / / Error
这是因为 staff [ 0 ] 声明的类型是 Employee , 而 seffionus 不是 Employee 类的方法。

6、然而, 不能将一个超类的引用赋给子类变量 。这条要重点理解,子类is a超类,但是超类is not a子类。

7、 应该养成这样一个良好的程序设计习惯 : 在进行类型转换之前, 先查看一下是否能够成功地转换 。 这个过程简单地使用instanceof 操作符就可以实现 。 例如 :

if ( staff [ 1 ] instanceof Manager )
{
boss = ( Manager ) staff [ 1 ] :
}

综上所述 :
• 只能在继承层次内进行类型转换 。
• 在将超类转换成子类之前 , 应该使用instanceof 进行检查。

8、为了提高程序的清晰度 , 包含一个或多个抽象方法的类本身必须被声明为抽象的。

9、类即使不含抽象方法, 也可以将类声明为抽象类 。

10、可以定义一个抽象类的对象变量 , 但是它只能引用非抽象子类的对象 。

11、在 Java 中, 只有基本类型 ( primitive types )不是对象。所有的数组类型, 不管是对象数组还是基本类型的数组都扩展了 Object 类 。

12、警告 : 只有 i 小于或等于数组列表的大小时 , 才能够调用 list . set ( i , x ) 。 例如 , 下面这段代码是错误的 :

ArrayList < Employee > list = new ArrayListo ( 100 ) ; // capacity 100, size0
list . set ( 0 , x ) ; // no element 0 yet

使用 add 方法为数组添加新元素 , 而不要使用 set 方法 , 它只能替换数组中已经存在
的元素内容。

13、C + + 注释 : 事实上, Java 中的受保护部分对所有子类及同一个包中的所有其他类都可见 。这与 c + + 中的保护机制稍有不同 , Java 中的 protected 概念要比 C + + 中的安全性差 。下面归纳一下 Java 用于控制可见性的 4 个访问修饰符 :
1 ) 仅对本类可见 private。
2 ) 对所有类可见 public :
3 ) 对本包和所有子类可见 protected。
4 ) 对本包可见 — 默认 ( 很遗憾 ) ,不需要修饰符。

关于第三点的理解重中之重:
然而, 在有些时候 , 人们希望超类中的某些方法允许被子类访问 ,或允许子类的方法访问超类的某个域。 为此 , 需要将这些方法或域声明为 protected 。 例如 , 如果将超类Employee中的 hireDay 声明为 proteced , 而不是私有的 , Manager 中的方法就可以直接地访问它 。
不过, Manager 类中的方法只能够访问Manager 对象中的 hireDay 域 , 而不能访问其他Employee 对象中的这个域 。 这种限制有助于避免滥用受保护机制 , 使得子类只能获得访问受保护。

14、将方法或类声明为 final 主要目的是 : 确保它们不会在子类中改变语义。 例如 , Calendar类中的 getTime 和 setTime 方法都声明为 final。 这表明 Calendar 类的设计者负责实现 Date 类与日历状态之间的转换 , 而不允许子类处理这些问题。 同样地 , String 类也是 final 类 , 这意味着不允许任何人定义 String 的子类。 换言之 , 如果有一个 String的引用, 它引用的一定是一个 String 对象, 而不可能是其他类的对象 。

15、对于 final 域来说 , 构造对象之后就不允许改变它们的值了 。 不过, 如果将一个类声明为final , 只有其中的方法自动地成为 final ,而不包括域。

16、Object 类中的 equals 方法必须重点掌握:
利用下面这个示例演示equals 方法的实现机制 :

public class Employee
	public boolean equals ( Object otherObject ){
		/ / a quick test to see if the objects are identical
		if ( this = = otherObject ) return true ;
		/ / must return false if the explicit parameter is null
		if ( otherObject = = null ) return false ;
		// if the classes don ' t match , they can ' t be equal
		if ( getClassO ! = otherObject . getClass ( ))
			return false ;
		// now we know otherObject is a non - null Employee
		Employee other = ( Employee ) otherObject ;
		// test whether the fields have identical values
			return name . equals ( other . name ) & & salary = other , sal ary
	& & hi reDay . equals ( other , hi reDay ) :
	}
}

这段例程有考虑不周全之处
为了防备 name 或 hireDay 可能为 null 的情况 , 需要使用 Objects . equals 方法。如
果两个参数都为 null , Objects . equals ( a, b )调用将返回 true ; 如果其中一个参数为 null ,则返回 false ; 否则 , 如果两个参数都不为 null , 则调用 a . equals ( b )。利用这个方法 ,Employee . equals 方法的最后一条语句要改写为 :

return Objects . equals ( name , other . name )
	& & salary = = other . sal ary
	& & Object . equals ( hireDay , other . hireDay ) ;

此段判等程序最重要的要注意一点,判断两个对象是否同属于一个类:

if ( getClassO ! = otherObject . getClass ( ))
	return false ;

16、*java对象不同类对象判等重中之重:
在比较一个类是否和另一个类属于同一个类实例的时候,我们通常可以采用instanceof和getClass两种方法通过两者是否相等来判断,但是两者在判断上面是有很大的差别:
instanceof:判断某对象是否属于某类或者某类的派生类。继承超类的子类对象也认为类型相同。
getClass:就是判断某对象是否属于同一类,仅对比当前所属类,不考虑继承关系。

17、关于java equals判等的四大性质要求:
自反性, 对称性, 传递性, 一致性本书5.2.2有极其详细的讲解。之后有时间重点深入研究。

18、编写完美equals方法的规则:
1 ) 显式参数命名为 otherObject , 稍后需要将它转换成另一个叫做 other 的变量 。
2 ) 检测 this 与 otherObject 是否引用同一个对象 :
if ( this = otherObject ) return true ;
这条语句只是一个优化。 实际上 , 这是一种经常采用的形式 。 因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多 。
3 ) 检测 otherObject 是否为 null , 如 果 为 null , 返 回 false。 这项检测是很必要的 。
if ( otherObject = null ) return false ;
4 ) 比较 this 与 otherObject 是否属于同一个类。 如果 equals 的语义在每个子类中有所改
变, 就使用 getClass 检测 :
if ( getClass ( ) ! = otherObject . getCIassO ) return false ;
如果所有的子类都拥有统一的语义, 就使用instanceof 检测 :
if ( ! ( otherObject instanceof ClassName ) ) return false ;
5 ) 将 otherObject 转换为相应的类类型变量 :
ClassName other = ( ClassName ) otherObject
6 ) 现在开始对所有需要比较的域进行比较了 。 使用 = 比较基本类型域 , 使用 equals 比
较对象域。 如果所有的域都匹配 , 就返回true ; 否 则 返 回 false。
return fieldl = = other . field
& & Objects . equals ( fie 1 d 2 , other . field 2 )
如果在子类中重新定义 equals , 就要在其中包含调用 super . equals ( other ) 。

19、关于equals必错点:
警告 : 下面是实现 equals 方法的一种常见的错误 。 可以找到其中的问题吗 ?
public class Employee
{
public boolean equals ( Employee other )
{
return other ! = null
& & getClassO == other . getClass 0
&& Objects . equals ( name , other . name )
&& salary == other.salary
& & Objects.equals ( hireDay , other . hireDay ) ;
这个方法声明的显式参数类型是 Employee。 其结果并没有覆盖Object 类的 equals 方
法,而是定义了一个完全无关的方法。

为了避免发生类型错误 , 可以使用 @ Override 对覆盖超类的方法进行标记 :
© Override public boolean equals ( Object other )
如果出现了错误 , 并且正在定义一个新方法 , 编译器就会给出错误报告
。 例如 ,假设将下面的声明添加到 Employee 类中 :
Override public boolean equals ( Employee other )
就会看到一个错误报告,这是因为这个方法并没有覆盖超类 Object 中的任何方法。

20、关于Object.equals相关API:
java . util . Arrays 1.2:
•static boolean equals ( type [ ] a , type [ ] b ) 5.0如果两个数组长度相同 , 并且在对应的位置上数据元素也均相同 , 将返回 true。 数组的兀素类型可以是 Object 、 int 、 long
、 short 、 char 、 byte 、boolean 、 float 或 double。
java . util . Objects 7:
• static boolean equals ( Object a , Object b )
如果 a 和 b 都为 null , 返回 true ; 如果只有其中之一为 null , 则返回 false ; 否则返回
a . equals ( b )

21、String 类使用下列算法计算散列码 :
int hash = 0 ;
for ( int i = 0 ; i < length 0 ; i + + )
hash = 31 * hash + charAt ( i ) ;

22、由于 hashCode 方法定义在 Object 类中, 因此每个对象都有一个默认的散列码 , 其值为对象的存储地址。

23、来看下面这个例子 。
String s = " Ok " ;
StringBuilder sb = new StringBuilder ( s ) ;
System . out . println ( s . hashCode ( ) + " " + sb . hashCode ( ) ) ;
String t = new String ( " Ok " ) ;
StringBuilder tb = new StringBuilder ⑴ ;
System . out . println ( t . hashCode ( ) + " ’’ + tb . hashCode ( ) ) ;
请注意 , 字符串 s 与 t 拥有相同的散列码, 这是因为字符串的散列码是由内容导出
的。 而字符串缓冲 sb 与tb 却有着不同的散列码,这是因为在 StringBuffer 类中没有定义
hashCode 方法, 它的散列码是由Object 类的默认 hashCode 方法导出的对象存储地址。

24、需要组合多个散列值时 , 可以调用 ObjeCtS . hash 并提供多个参数 。 这个方法会对各个参数调用 Objects . hashCode, 并组合这些散列值 。 这样 Employee . hashCode 方
法可以简单地写为 :
public int hashCodeO
{
return Objects , hash ( name , salary , hireDay ) ;
}
Equals 与 hashCode 的定义必须一致 :
如果 x . equals ( y ) 返回 true , 那么 x . hashCode ( ) 就必须与 y . hashCode ( ) 具有相同的值。
例如 ,
如果用定义的 Employee . equals 比较雇员的 ID, 那么 hashCode 方法就需要散列 ID
, 而不是雇员的姓名或存储地址 。

25、Object.hashCode 相关API:
java . util . Object 1.0
• int hashCode ( )
返回对象的散列码。 散列码可以是任意的整数 , 包括正数或负数 。 两个相等的对象要
求返回相等的散列码。
java . util . Objects 7
• static int hash ( Object . … objects )
返回一个散列码, 由提供的所有对象的散列码组合而得到 。
• static int hashCode ( Object a )
如果 a 为 null 返回 0, 否则返回a . hashCode ( ) 。
java . lang . ( lnteger | Long | Short | Byte | Double | Float | Character | Boolean ) 1.0
• staticint hashCode ( ( int 11 ong | short | byte | double | f 1 oat | char | boolean ) value ) 8
返回给定值的散列码。
java . utii . Arrays 1.2
• static int hashCode ( type [ ] a ) 5.0
计算数组 a 的散列码。 组成这个数组的元素类型可以是 object , int , long ,short , char ,
byte , boolean , float 或 double。

26、关于toString之后再研究。

27、
java . lang . Object 1.0
•Class getClass ( )
返回包含对象信息的类对象 。
•boolean equals ( Object otherObject )//默认的Object类中的equals方法
比较两个对象是否相等 , 如果两个对象指向同一块存储区域 , 方法返回 true ; 否 则 方
法返回 false。 在自定义的类中 ,应该覆盖这个方法。
•String toString ( )
返冋描述该对象值的字符串。 在自定义的类中 , 应该覆盖这个方法 。
java . lang . Class 1.0
•String getName ( )
返回这个类的名字。
•Class getSuperclass ( )
以 Class 对象的形式返回这个类的超类信息。

28、java高级版动态数组ArrayList创建相关API:
java . util . ArrayList < E > 1.2
• ArrayList < E > ()
构造一个空数组列表 。
• ArrayList < E > ( i nt initialCapacity )
用指定容量构造一个空数组列表 。参数 : initalCapacity 数组列表的最初容量
• boolean add ( E obj )
在数组列表的尾端添加一个元素 。 永远返回 true。参数 : obj 添加的元素
• int size ( )
返回存储在数组列表中的当前元素数量。( 这个值将小于或等于数组列表的容量。)
• void ensureCapacity ( int capacity )
确保数组列表在不重新分配存储空间的情况下就能够保存给定数量的元素。
参数 : capacity 需要的存储容量
• void trimToSize ( )
将数组列表的存储容量削减到当前尺寸。多余的空间将由java虚拟机自动垃圾回收。

29、警告 : 只有 i 小于或等于数组列表的实际已用大小时(不是分配大小) , 才能够调用 list . set ( i , x ) 。 例如 , 下面这段代码是错误的 :
ArrayList < Employee > list = new ArrayListo ( 100 ) ; // capacity 100, size 0
list . set ( 0 , x ) ; // no element 0 yet
使用 add 方法为数组添加新元素 , 而不要使用 set 方法 , 它只能替换数组中已经存在
的元素内容。

30、java高级版动态数组ArrayList使用相关API:
java . util . ArrayList < T > 1.2
•void set ( int index E obj )
设置数组列表指定位置的元素值 , 这个操作将覆盖这个位置的原有内容。
参数 : index 位置 ( 必须介于 0 ~ size ( ) - 1 之间 ) obj 新的值
•E get ( int index )
获得指定位置的元素值。
参数 : index 获得的元素位置 ( 必须介于 0 ~ size ( ) - 1 之间 )
•void add ( int index , E obj )
向后移动元素, 以便插入元素 。
参数 : index 插入位置 ( 必须介于 0 〜 size ( ) - 1 之间 )obj 新元素
•E removed int index )
删除一个元素 , 并将后面的元素向前移动。 被删除的元素由返回值返回 。
参数 : index 被删除的元素位置 ( 必须介于 0 〜 size ( ) - 1 之间 )

31、关于类型化数组和原始数组列表的兼容性问题以后遇到再深入研究。

32、假设想定义一个整型数组列表。 而尖括号中的类型参数不允许是基本类型 , 也就是说 ,不允许写成 ArrayList < int >。 这里就用到了 Integer 对象包装器类 。 我们可以声明一个Integer对象的数组列表。
ArrayList < Integer > list = new ArrayList <> ( ) ;

33、关于java 对象包装器与自动装箱自动拆箱重中之重:
(1)、对象包装器类是不可变的 , 即一旦构造了包装器 , 就不允许更改包装在其中的值。对这句话的理解重中之重,一开始没太理解好,结合下面的例子重点分析。
(2)、在算术表达式中也能够自动地装箱和拆箱。 例如 , 可以将自增操作符应用于一个包装器引用 :
Integer n = 3 ;
n + + ;
编译器将自动地插人一条对象拆箱的指令 , 然后进行自增计算 , 最后再将结果装箱。
表面上看似改变了integer n包装器内部的数值,其实忽略了n本身不是一个包装器对象,而是一个包装器对象的引用,因此一切疑惑都解开了。在执行后自加运算的时候,编译器先是自动拆箱,将包装器内部的数值3提取到int变量中,然后执行自加运算,之后,再次新建一个integer包装器对象,并且将数组4自动装箱,然后将包装器引用n指向新建的包装器对象,从而实现表面上的n++操作。
(3)、非常重要的一些数值包装器类的常用API:
java . lang . Integer 1.0
• int intValue ()
以 int 的形式返回 Integer 对象的值 ( 在 Number 类中覆盖了 intValue 方法 )。
• static String toString ( int i )
以一个新 String 对象的形式返回给定数值 i 的十进制表示。
• static String toString ( int i , int radix )
返回数值 i 的基于给定 radix 参数进制的表示。
• static int parselnt ( String s )
• static int parseInt ( String s , int radix )
返回字符串 s 表示的整型数值, 给定字符串表示的是十进制的整数 ( 第一种方法 ) ,或者是 radix 参数进制的整数 ( 第二种方法 )。
• static Integer valueOf ( String s )
• Static Integer value Of ( String s , int radix )
返回用 s 表示的整型数值进行初始化后的一个新 Integer 对象, 给定字符串表示的是十
进制的整数 ( 第一种方法 ), 或者是 radix 参数进制的整数 ( 第二种方法 ) 。

java . text . NumberFormat 1.1
•Number parse ( String s )
返回数字值, 假设给定的 String 表示了一个数值 。

34、参数可变的方法,枚举类,反射,以后深入研究。

猜你喜欢

转载自blog.csdn.net/NCUscienceZ/article/details/83216146