探究Java和C++中的关键字:final vs. const

从字面含义理解Java的final和C/C++的constant都意味着常量、不可改变,但今天我们来细究一下这两个“不可变”关键字的详细用法。

不可变的类

类的改变来自继承,子类有以下方法来改变原有父类的行为:

  • Overwrite父类中的方法/函数
    • Java: 非final、非static method(非static成员method默认就是可被子类overwrite的)
    • C++: virtual function (C++中的virtual不能用在inline, static和constructor函数上)。
  • Hide父类中的可见成员(public, protected, default)。
  • 子类增加新的成员变量或方法来扩展父类。

有时候创建一个不可改变的类(immutable class)从设计上是有用的,比如定义不可改变的String类。实现不可改变的类,不同语言有不同做法。

Java

Java中直接用final修饰class,结果就是不可能定义新的子类了,这也意味着final类永远不可能被子类改变(不存在子类),没有必要去给成员函数(包括static和非static)加上final去修饰了。

C++

C++中要实现final class的效果,需要利用语言技巧来自己实现
http://blog.csdn.net/renwotao2009/article/details/6596710
http://www.cnblogs.com/moonz-wu/archive/2008/05/07/1186065.html

不可变的method/function

方法/函数在子类中被改变就是在上文“不可变类”中提到的Overwrite或者Hide两种情况。

Java

一句话:Java中防止方法被子类overwrite或者hiding就用final修饰Class的method:

  • 防止non-static method被Overwrite: 用final修饰非static method.
  • 防止static method被Hide: 用final修饰static method.

在Java中为了防止构造函数调用的函数被子类重写引起不确定的问题,一般从构造函数之调用final成员方法。

// Base.java
public class Base
{
    public       void m1() {}
    public final void m2() {}

    public static       void m3() {}
    public static final void m4() {}
}

// Derived.java
public class Derived extends Base
{
    public void m1() {}  // OK, overriding Base#m1()
    //public void m2() {}  // forbidden
    public void m1(int i) {}  // OK, overloading with Derived::m1(), 
    public void m2(int i) {m2();}  // OK, new extended method in Drived class, it is ok to call Base::m2();

    public static void m3() {}  // OK, hiding Base#m3()
    //public static void m4() {}  // forbidden
}

C++

在C++中,如果类的成员函数不被声明成virtual就无法被子类overwritten,但要注意子类同名成员函数会hiding父类的同名函数。C++的hiding不同于Java,在Java中子类和父类同名函数如果参数不同不算hiding. 而在C++中不管参数是否相同,子类如果定义了非virtual的同名函数都会hiding父类的同名函数(下面例子中的Derived::m1(int)和static Derived::m3(int)均hiding了父类的同名函数)。

为了避免意外hiding父类函数,保证overwrite时父类和子类成员函数的签名一致性,C++11引入了overide关键字,来让编译器帮助检查错误

C++的const不同于Java的final:

  • C++的const并无禁止子类overwrite或hiding父类成员函数的功能;const non-static成员函数只是防止函数修改对象状态;
  • C++的const用于函数时,只能用于Class non-static member function(i.e. 不能用于non-member函数和类的static函数 ),所以Class中不存在static xxx() const函数。
// 用g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3在Linux上测试
// hiding.h
#ifndef HIDING_H_
#define HIDING_H_

class Base {
 public:
  void m1() {
  }

  void m2() const;

  static void m3() {
  }

  /* Forbidden
   error: static member function static void Base::m4() cannot he cv-qualifi
  static void m4() const {
  }
  */

  // virtual must be non-static
  virtual void m5() {
  }

  virtual void m6() const;
};


class Derived : public Base {
 public:
  void m1(int i) {
  }

  void m2();

  static void m3(int i) {
  }

  virtual void m6() const;
};


// hiding.cpp
#include "hiding.h"
#include <iostream>
using namespace std;

/* Forbidden
 error: non-member function void mInline() cannot have cv-qualifi
inline void mInline() const {
}
*/

void Base::m2() const
{
  cout << "I am Base non-virtual m2." << endl;
}

void Base::m6() const
{
  cout << "I am Base virtual m6." << endl;
}


void Derived::m2()
{
  cout << "I am Derived non-virtual m2." << endl;
}

void Derived::m6() const
{
  cout << "I am Derived virtual m6." << endl;
}


int main(void)
{
  Derived subObj;
  // subObj.m1();  // Failed, Derived::m1(int) hiding non-static Base::m1()
  subObj.m2();

  // Derived::m3();  // Failed, Derived::m3(int) hiding static Base::m3()

  subObj.m5();
  subObj.m6();

  Base& baseRef = subObj;
  baseRef.m1();
  baseRef.m2();
  baseRef.m6();  // Will execute Derived::m6

  return 0;

#endif

========================
Output:
I am Derived non-virtual m2.
I am Derived virtual m6.
I am Base non-virtual m2.
I am Derived virtual m6.

final, const,“常量”

Java中final声明的变量只能被初始化一次,不同于“常量”,final变量的值在编译期不一定是确定的,比如一个类的final成员在构造函数初始化,但是这个类的对象可能到运行时才会被创建。作为惯例(convention),final变量一般用大写字母和下划线组合来命名。

final如果修饰的是一个reference, 只是表明该refrence不能被再次赋值,但是ref的对象本身是可以被改变的。例如一个final int[] array, array里的值还是能够被修改的。

下面的声明也是合法的,每次定义的final在进入作用域后相当于被重新声明了。

for (final SomeObject obj : someList) {
   // do something with obj
}

一个method中的final变量可以被inner-class直接访问。

Java选择了纯粹,不可变的“常量”是真的不能修改(使用反射仍有可能修改final变量),而C++却又多了一个后门,通过类型转换,不可变的“常量”还有机会逃脱成为一个变量。关于这个问题,以后有机会再细究。

Reference

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值