Java的final和C++的const有什么区别?

162

Java for C++ programmers tutorial中说(此处突出显示是我加的):

关键字final在Java中大致等同于C++中的const。

这里的“大致”是什么意思?它们难道不是完全相同的吗?

如果有任何区别,那会是什么?

11个回答

207

在C++中,将成员函数标记为const表示它可以在const实例上调用。Java没有相应的功能。例如:

class Foo {
public:
   void bar();
   void foo() const;
};

void test(const Foo& i) {
   i.foo(); //fine
   i.bar(); //error
}

在Java中,值只能被分配一次,例如:

public class Foo {
   void bar() {
     final int a;
     a = 10;
   }
}

Java中合法,但C++中不合法,而:

public class Foo {
   void bar() {
     final int a;
     a = 10;
     a = 11; // Not legal, even in Java: a has already been assigned a value.
   }
}

在Java和C++中,成员变量可以分别为final/const。这些变量需要在类的实例完成构造之前赋值。

在Java中,它们必须在构造函数完成之前设置,可以通过以下两种方式实现:

public class Foo {
   private final int a;
   private final int b = 11;
   public Foo() {
      a = 10;
   }
}

在C++中,您需要使用初始化列表为const成员赋值:

class Foo {
   const int a;
public:
   Foo() : a(10) {
      // Assignment here with = would not be legal
   }
};

在Java中,final可以用于标记不可重写的内容。在C++(C++11之前)中不支持此功能。例如:

public class Bar {
   public final void foo() {
   }
}

public class Error extends Bar {
   // Error in java, can't override
   public void foo() {
   }
}

但在C++中:

class Bar {
public:
   virtual void foo() const {
   }
};

class Error: public Bar {
public:
   // Fine in C++
   virtual void foo() const {
   }
};

这是可以的,因为标记成员函数为const的语义是不同的。(您还可以通过仅在一个成员函数上使用const来进行重载。(注意,C++11允许将成员函数标记为final,请参见C++11更新部分)

C++11更新:

C++11实际上允许您将类和成员函数都标记为final,其语义与Java中的相同功能相同,例如在Java中:

public class Bar {
   public final void foo() {
   }
}

public class Error extends Bar {
   // Error in java, can't override
   public void foo() {
   }
}

现在可以用C++11精确地编写如下内容:
class Bar {
public:
  virtual void foo() final;
};

class Error : public Bar {
public:
  virtual void foo() final;
};

我不得不使用 G++ 4.7 的预发布版本编译这个示例。需要注意的是,在这种情况下,它并没有替换 const,而是增加了它,提供了最接近的 C++ 关键字所没有的类似 Java 的行为。因此,如果你想让一个成员函数同时具有 finalconst 特性,你可以这样做:

class Bar {
public:
  virtual void foo() const final;
};

这里需要按照 constfinal 的顺序。

之前没有直接等价于 const 成员函数的选项,虽然使函数非 virtual 是一种潜在的选择,但编译时不会出错。

同样适用于 Java:

public final class Bar {
}

public class Error extends Bar {
}

C++11中的变化:

class Bar final {
};

class Error : public Bar {
};

以前在C++中,private构造函数可能是实现此功能的最接近方式。

有趣的是,为了与C++11之前的代码保持向后兼容性,final并不像通常的关键字那样。 (看看简单合法的C++98示例struct final;,就可以知道将其作为关键字会破坏代码)


3
你应该将那些方法声明为虚函数;否则,你实际上并没有做到同样的事情。 - BlueRaja - Danny Pflughoeft
1
在你的最后一个例子中,你所做的是合法的,但值得一提的是 final int a; a = 10; a = 11; 不是合法的(这就是 final 作为变量修饰符的目的)。此外,类的 final 成员只能在声明时或构造函数中设置一次。 - corsiKa
2
请注意,C++0x添加了“final”成员函数修饰符,以实现此特定目的。VC++ 2005、2008和2010已经实现了这一点,使用上下文关键字“sealed”而不是“final”。 - ildjarn
@ildjarn - 很有趣的知识,又是我不知道的相对较小的C++0x变化!我可能会在文本中添加一个小注释,指出这将随着C++0x的改变而改变。 - Flexo
1
显然,人们仍然在使用“s/const/final/g”在代码库中,并产生了finalructor的结果! - Flexo
在覆盖派生类成员函数中,virtual关键字的存在或缺失完全是可选的,没有任何影响。 - Alex Bitek

37

const对象只能调用const方法,通常被视为不可变。

const Person* person = myself;
person = otherPerson; //Valid... unless we declared it const Person* const!
person->setAge(20); //Invalid, assuming setAge isn't a const method (it shouldn't be)

final对象不能被设置为新对象,但它并非不可变 - 没有任何阻止某人调用任何set方法。

final Person person = myself;
person = otherPerson; //Invalid
person.setAge(20); //Valid!

Java没有固有的声明对象不可变的方式;您需要自己设计类为不可变。

当变量是原始类型时,final/const起到相同的作用。

const int a = 10; //C++
final int a = 10; //Java
a = 11; //Invalid in both languages

3
这也是一个很棒的答案(就像这里的许多其他答案一样)。不幸的是,我只能接受一个答案。 :) - WinWin
1
完美的答案! - ADJ

31

在Java中,final关键字可以用于四个方面:

  • 对类或方法使用以封闭它(不允许子类/重写)
  • 对成员变量使用以声明它只能被设置一次(我想这就是您所说的内容)
  • 对方法中声明的变量使用,以确保它只能被设置一次
  • 对方法参数使用,以声明其不能在方法内进行修改

一个重要的事情是: Java final成员变量必须被设置一次!例如,在构造函数、字段声明或初始化器中。(但您不能在方法中设置final成员变量)。

另一个将成员变量声明为final的后果与内存模型有关,如果您在线程环境中工作,则很重要。


你所说的“内存模型”是什么意思?我不太明白。 - Tony
1
@Tony:Java语言规范,第17.4章。内存模型 - http://docs.oracle.com/javase/specs/jls/se8/html/index.html -- 谷歌的第一个搜索结果。 - Ralph

14

Java中的final关键字相当于C++中对于基本数据类型的const。

对于Java的引用类型,final关键字相当于一个const指针...

//java
final int finalInt = 5;
final MyObject finalReference = new MyObject();

//C++
const int constInt = 5;
MyObject * const constPointer = new MyObject();

"final关键字相当于一个const指针" 说得好。 - ADJ

9

Java中的final仅适用于原始类型和引用,而不是对象实例本身,其中const关键字适用于任何内容。

const list<int> melist;final List<Integer> melist;进行比较,前者使修改列表变得不可能,而后者仅阻止您将新列表分配给melist


8
你已经有了一些很好的答案,但是有一个值得补充的点:在C++中,常量(const)通常用于防止程序的其他部分更改对象的状态。正如已经指出的那样,在Java中,final不能做到这一点(除了基本类型),它只是防止引用被更改为不同的对象。但是,如果你使用集合(Collection),你可以通过使用静态方法来防止对你的对象进行更改。
 Collection.unmodifiableCollection( myCollection ) 

这会返回一个Collection的引用,它可以读取元素,但如果尝试进行修改则会抛出异常,有点像C++中的const

3
除了具有某些微妙的多线程特性外,声明为final的变量不需要在声明时进行初始化!
即在Java中这是有效的:
// declare the variable
final int foo;

{
    // do something...

    // and then initialize the variable
    foo = ...;
}

这段文字用C++的const写法将不被视为有效。

2
根据 wikipedia
  • 在C++中,const字段不仅受到重新分配的保护,而且额外限制只能调用const方法,并且只能作为其他方法的const参数传递。
  • 非静态内部类可以自由访问封闭类的任何字段,无论是否为final。

1
“reassigned”这个词在当前页面的版本中没有出现,也没有任何类似于您第二点的内容,这要么是不正确的,要么是无关紧要的,具体取决于您所说的“访问”是什么意思。“非静态内部”是一种含糊不清的说法。维基百科不是C++或Java的规范参考。 - user207421

2
我猜它加上“大致”是因为在讨论指针时,C++中的const含义变得复杂,例如常量指针与指向常量对象的指针。由于Java中没有“显式”指针,所以final就没有这些问题。

1

让我通过一个switch/case语句的例子来解释一下我的理解。

每个case语句中的值必须是编译时常量,并且与switch值具有相同的数据类型。

声明以下内容(可以在您的方法中作为本地实例,也可以在您的类中作为静态变量(然后添加static),或者是一个实例变量)。

final String color1 = "Red";

并且

static final String color2 = "Green";

switch (myColor) { // myColor is of data type String
    case color1:
    //do something here with Red
    break;
    case color2:
    //do something with Green
    break;
}

如果color1是一个类/实例变量而不是局部变量,则此代码将无法编译。 如果color1被定义为静态常量(那么它就变成了静态常量变量),则可以编译。
当它无法编译时,您将收到以下错误信息。
error: constant string expression required

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接