Java开发之道
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

陷阱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 平方米房屋的正确总金额