陷阱16 损失惨重——不小心隐藏父类的final字段
一个Java应用程序是由许多类组成的,而某些类又有一定的继承关系,为了使数据具有一定的通用性,对于一些使用频率较高的数据,需要在父类中将其定义为常量,即定义一些静态的final字段,这时如果子类中定义的某个常量与父类中的重名,则子类就会将父类中的重名常量隐藏,导致父类中的重名常量的值不能被正确使用。
说明
如果父类中的常量存储的是一些重要的数据,如金额、价格或面积等信息,而子类将父类中的重名常量隐藏了,就可能会造成巨大的经济损失,为此,在子类中定义常量时一定要特别小心,不要定义与父类中具有相同名称的常量。
示例:
假设房屋每平方米的单价固定是4 800元,可以将房屋单价定义为常量,如果将该常量定义在父类中,这样在其子类中就可以直接使用该常量了。
定义父类House:
public class House { ❶public static final int UNIT_PRICE = 4800; // 定义表示房屋单价的常量 }
定义子类TotalMoney,该类是House类的子类:
public class HouseTotalMoney extends House{ ❷public static final int UNIT_PRICE = 2000; // 定义常量,该常量与父类中的重名 public static int getTotalMoney (int squareMeter){// 定义获得房屋总价的方法 return ❸HouseTotalMoney. UNIT_PRICE * squareMeter; // 计算并返回房屋总价 } public static void main (String[] args) { // 计算面积是70平方米的房屋的总金额并输出 int totalMoney = ❹HouseTotalMoney. getTotalMoney (70); System. out. println ("70平方米的房屋的总金额是:\n" + totalMoney + "元。"); } }
运行本示例,程序将在控制台输出如图3.11所示的信息,显示70平方米房屋的总金额是140 000元。
图3.11 输出70 平方米房屋的总金额
说明
本示例之所以输出如图3.11所示的信息,是由于在子类TotalMoney中定义的常量隐藏了父类House 中定义的常量,即在标记❷处定义的UNIT_PRICE 隐藏了标记❶处定义的UNIT_PRICE,所以当程序在标记❹处调用getTotalMoney (int squareMeter)方法并为其传递实参70后,将执行该方法的方法体代码,而此时标记❸处的HouseTotalMoney. UNIT_PRICE恰好是子类TotalMoney中(即在标记❷处)定义的UNIT_PRICE,其值是2 000,而不是父类中定义的UNIT_PRICE的值4 800,所以程序输出的房屋总价是140 000,而不是所期待的正确结果336 000。
从该实例的输出结果不难看出,由于子类中定义的常量UNIT_PRICE隐藏了父类中定义的常量UNIT_PRICE,从而造成了196 000元的损失。
编程准则:在类中应避免使用公共常量
在程序中应尽量不要定义具有相同名称的常量,而应该将这些常量定义为私有的,然后通过定义获得常量值的final修饰的Getter方法来获得常量的值,这时,如果子类重写父类中final修饰的Getter方法,程序就会发生编译错误,就需要修改代码,所以可以极大地减少或消除由于常量使用不当而给企业造成的损失。
对上面的示例进行更改,将父类和子类中的常量都定义为私有的,并用final 修饰的Getter方法来获得常量的值,更改后的代码如下。
定义父类House:
public class House { ❶private static final int UNIT_PRICE = 4800; // 定义表示房屋单价的常量 ❷public static final int getUnitPrice () { // 定义获得房屋单价的Getter方法 return UNIT_PRICE; } }
说明
这里对上个示例中的父类House 进行了更改,将常量定义为私有的,即在标记❶处使用private对常量进行修饰,并在标记❷处添加了一个final修饰的方法,该方法用于获得常量UNIT_PRICE的值。
定义子类TotalMoney(该子类中存在错误):
public class HouseTotalMoney extends House{ private static final int UNIT_PRICE = 2000; // 定义常量,该常量与父类中的重名 public static final int ❸getUnitPrice () { return UNIT_PRICE; } public static int getTotalMoney (int squareMeter){// 定义获得房屋总价的方法 ❹return HouseTotalMoney.UNIT_PRICE * squareMeter; // 计算并返回房屋总价 } public static void main (String[] args) { // 计算面积是70平方米的房屋的总金额并输出 int totalMoney = HouseTotalMoney. getTotalMoney (70); System. out. println ("70平方米的房屋的总金额是:\n" + totalMoney + "元。"); } }
说明
由于在父类House中用final对获得常量值的方法进行了修饰,所以在子类TotalMoney中重写父类中final修饰的方法时会发生编译错误,因此必须要对该方法进行更改,即将标记❸处标识的方法改名,如将标记❸处的方法名getUnitPrice改为getPrice,这样就可以避免由于子类隐藏父类的方法而造成损失,最后还要将标记❹处的黑体字的代码 "UNIT_PRICE" 改为从父类继承的方法getUnitPrice (),这样,程序就不会出错了。
对子类TotalMoney进行修改,修改后的代码用黑体字标识,将子类TotalMoney修改正确后的代码如下:
public class HouseTotalMoney extends House{ private static final int UNIT_PRICE = 2000; // 定义常量,该常量与父类中的重名 public static final int getPrice () { return UNIT_PRICE; } public static int getTotalMoney (int squareMeter){// 定义获得房屋总价的方法 return HouseTotalMoney.getUnitPrice () * squareMeter; // 计算并返回房屋总价 } public static void main (String[] args) { // 计算面积是70平方米的房屋的总金额并输出 int totalMoney = HouseTotalMoney. getTotalMoney (70); System. out. println ("70平方米的房屋的总金额是:\n" + totalMoney + "元。"); } }
这样修改后,程序就可以正常执行了,并能输出70平方米房屋的真正总金额336 000元,如图3.12所示。
图3.12 输出70 平方米房屋的正确总金额