从字面含义理解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
- https://en.wikipedia.org/wiki/Final_%28Java%29
- http://docs.oracle.com/javase/tutorial/java/IandI/final.html
- http://stackoverflow.com/questions/3838553/overriding-vs-method-hiding
- http://stackoverflow.com/questions/10982628/non-member-function-cannot-have-cv-qualifier
- http://en.cppreference.com/w/cpp/language/override