Final 关键字

final 关键字通过在定义前加上 final 来阻止子类覆盖方法或常量。如果类本身被定义为 final,那么它就不能被扩展。

示例 #1 Final 方法示例

<?php
class BaseClass {
public function
test() {
echo
"BaseClass::test() called\n";
}

final public function
moreTesting() {
echo
"BaseClass::moreTesting() called\n";
}
}

class
ChildClass extends BaseClass {
public function
moreTesting() {
echo
"ChildClass::moreTesting() called\n";
}
}
// 导致致命错误:无法覆盖 final 方法 BaseClass::moreTesting()
?>

示例 #2 Final 类示例

<?php
final class BaseClass {
public function
test() {
echo
"BaseClass::test() called\n";
}

// 由于类已经是 final,final 关键字是多余的
final public function moreTesting() {
echo
"BaseClass::moreTesting() called\n";
}
}

class
ChildClass extends BaseClass {
}
// 导致致命错误:类 ChildClass 不能继承 final 类 (BaseClass)
?>

示例 #3 从 PHP 8.1.0 开始的 Final 常量示例

<?php
class Foo
{
final public const
X = "foo";
}

class
Bar extends Foo
{
public const
X = "bar";
}

// 致命错误:Bar::X 无法覆盖 final 常量 Foo::X
?>

注意 属性不能声明为 final:只有类、方法和常量(从 PHP 8.1.0 开始)可以声明为 final。 从 PHP 8.0.0 开始,除了构造函数之外,私有方法不能声明为 final。

添加笔记

用户贡献的笔记 15 笔记

jriddy at gmail dot com
15 年前
对于 Java 开发人员的说明:PHP 中的类常量不使用 'final' 关键字。我们使用 'const' 关键字。

https://php.net/manual/en/language.oop5.constants.php
Rumour
1 年前
从 PHP 8.1.0 开始,类常量可以被“final 化”。为了部分反驳很久以前写下的最受欢迎的用户贡献,他们当时绝对是正确的。
penartur at yandex dot ru
17 年前
请注意,即使在父类中定义为私有,您也不能覆盖 final 方法。
因此,以下示例
<?php
class parentClass {
final private function
someMethod() { }
}
class
childClass extends parentClass {
private function
someMethod() { }
}
?>
将导致错误“致命错误:无法在 ***.php 第 7 行覆盖 final 方法 parentClass::someMethod()”

这种行为看起来有点出乎意料,因为在子类中我们无法知道父类中存在哪些私有方法,反之亦然。

因此,请记住,如果您定义了一个私有 final 方法,您不能在子类中放置具有相同名称的方法。
someone dot else at elsewhere dot net
10 年前
@thomas at somewhere dot com

'final' 关键字非常有用。继承也很有用,但可能会被滥用,在大型应用程序中会成为问题。如果您遇到想要扩展的 finalized 类或方法,请改为编写装饰器。

<?php
final class Foo
{
public
method doFoo()
{
// 做一些有用的事情并返回结果
}
}

final class
FooDecorator
{
private
$foo;

public function
__construct(Foo $foo)
{
$this->foo = $foo;
}

public function
doFoo()
{
$result = $this->foo->doFoo();
// ... 自定义结果 ...
return $result;
}
}
?>
mattsch at gmail dot com
10 年前
您可以使用 final 方法来替换类常量。这样做的原因是,您不能独立地对另一个类中使用的类常量进行单元测试,因为您不能模拟常量。final 方法允许您具有与常量相同的功能,同时保持代码松散耦合。

紧耦合示例(不建议使用常量)

<?php
interface FooInterface
{
}

class
Foo implements FooInterface
{
const
BAR = 1;

public function
__construct()
{
}
}

interface
BazInterface
{
public function
getFooBar();
}

// 此类不能独立进行单元测试,因为必须加载实际类 Foo 才能获取 Foo::BAR 的值
class Baz implements BazInterface
{
private
$foo;

public function
__construct(FooInterface $foo)
{
$this->foo = $foo;
}

public function
getFooBar()
{
return
Foo::BAR;
}

}

$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>

松散耦合示例(消除了常量使用)

<?php
接口 FooInterface
{
public function
bar();
}

Foo 实现 FooInterface
{
public function
__construct()
{
}

final public function
bar()
{
return
1;
}
}

接口
BazInterface
{
public function
getFooBar();
}

// 此类可以在隔离环境中进行单元测试,因为通过模拟 FooInterface 并调用最终的 bar 方法,无需加载类 Foo。
Baz 实现 BazInterface
{
private
$foo;

public function
__construct(FooInterface $foo)
{
$this->foo = $foo;
}

public function
getFooBar()
{
return
$this->foo->bar();
}

}

$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
cottton at i-stats dot net
10 年前
IMO,值得知道。
<?php
BaseClass
{
protected static
$var = '我属于 BaseClass';

public static function
test()
{
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且这是我的变量: `'.self::$var.'`<br>';
}
public static function
changeVar($val)
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
final public static function
dontCopyMe($val)
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
}

ChildClass 扩展 BaseClass
{
protected static
$var = '我属于 ChildClass';

public static function
test()
{
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且这是我的变量: `'.self::$var.'`<br>'.
'并且这是我的父类变量: `'.parent::$var.'`';
}
public static function
changeVar($val)
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>'.
'但父类 $var 仍然是: `'.parent::$var.'`';
}
public static function
dontCopyMe($val) // 致命错误:无法在 ... 中重写最终方法 BaseClass::dontCopyMe()。
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
}

BaseClass::test(); // 我是 `BaseClass::test()` 并且这是我的变量: `我属于 BaseClass`
ChildClass::test(); // 我是 `ChildClass::test()` 并且这是我的变量: `我属于 ChildClass`
// 并且这是我的父类变量: `我属于 BaseClass`
ChildClass::changeVar('一些新东西'); // 我是 `ChildClass::changeVar()` 并且我刚刚将我的 $var 更改为: `一些新东西`
// 但父类 $var 仍然是: `我属于 BaseClass`
BaseClass::changeVar('一些不同的东西'); // 我是 `BaseClass::changeVar()` 并且我刚刚将我的 $var 更改为: `一些不同的东西`
BaseClass::dontCopyMe('一段文本'); // 我是 `BaseClass::dontCopyMe()` 并且我刚刚将我的 $var 更改为: `一段文本`
ChildClass::dontCopyMe('一段文本'); // 致命错误:无法在 ... 中重写最终方法 BaseClass::dontCopyMe()。
?>
slorenzo at clug dot org dot ve
16 年前
<?php
parentClass {
public function
someMethod() { }
}
childClass 扩展 parentClass {
public final function
someMethod() { } // 重写父函数
}

$class = new childClass;
$class->someMethod(); // 调用子类中的重写函数
?>
santoshjoshi2003 at yahoo dot co dot in
15 年前
final 关键字的使用与 Java 中的类似。
在 Java 中,final 有三种用途。
1) 防止类继承。
2) 防止方法重写或在子类中重新定义方法。
方法在子类中。
3) 以及声明常量。
但第三点似乎在 PHP 中缺失。
我猜想,因为我是一名 Java 开发人员,目前正在提升 PHP 技能。
Anonymous
13 年前
FINAL 的行为没有你想象的那么严重。一个简单的例子:
<?php
A {
final private function
method(){}
}

B 扩展 A {
private function
method(){}
}
?>

通常你可能会期望以下情况之一发生
- final 和 private 关键字不能一起使用,会报错。
- 不会报错,因为 private 可见性说明,方法/变量/等等只能在同一个类中可见。

但 PHP 的行为有点奇怪:“无法重写最终方法 A::method()”。

因此,可以禁止子类中的方法名!不知道这是否是好的行为,但也许对你来说很有用。
Anonymous
5 天前
当需要特殊的类结构时,使用 final 关键字来最终化魔术方法可能会有帮助。

<?php

抽象类 A {
final public function
__construnct(){ echo "A"; }
}

B 扩展 A {
public function
__construct(){ echo "B"; }
}

$b = new B(); // 输出:PHP 致命错误:无法重写最终方法 a\A::__construct()

?>
Baldurien
13 年前
"注意:对于 Java 开发人员,PHP 中不使用 'final' 关键字来表示类常量。我们使用 'const' 关键字。"。

https://php.net/manual/en/language.oop5.constants.php

这大体上是正确的,尽管 PHP 中的常量(无论是在类级别定义还是否定义)都只能是标量(int、string 等),而在 Java 中它们可能是纯对象(例如:java.awat.Color.BLACK)。唯一可能的解决方案是拥有这种常量,如下所示:

<?php
Bar {...}
Foo {
public static
$FOOBAR;

static function
__init() {
static
$init = false;
if (
$init) throw new Exception('常量已经初始化');
self::$FOOBAR = new Bar();
$init = true;
}
}
Foo::__init();
?>
也就是说,除非 PHP 自动调用 __init() 方法,否则可能没有用。

但是,在某些情况下可以采用以下替代方法:

<?php
function __autoload($className) {
... require
包含该类定义的文件 ...
if (
interface_exists($className, false)) return;
if (
class_exists($className, false)) {
$rc = new ReflectionClass($className);
if (!
$rc->hasMethod('__init')) return;
$m = $rc->getMethod('__init');
if (!(
$m->isStatic() && $m->isPrivate())) {
throw new
Exception($className . '__init() 方法必须是私有静态方法!');
}
$m->invoke(null);
return;
}
throw new
Exception('类或接口未找到 ' . $className);
}
?>

这种方式只有在每个文件只定义一个类时才有效,因为我们确保 __autoload() 将被调用来加载包含该类的文件。

例如

test2.php
<?php
class B {
public static
$X;
private static function
__init() {
echo
'B', "\n";
self::$X = array(1, 2);
}
}
class
A {
public static
$Y;
private static function
__init() {
echo
'A', "\n";
self::$Y = array(3, 4);
}
}
?>
test.php
<?php
function __autoload($n) {
if (
$n == 'A' || $n == 'B') require 'test2.php';
... 执行
__init() 的技巧 ...
}
var_dump(B::$X); // 显示 B,然后显示 array(2) (1, 2)
var_dump(A::$Y); // 显示 NULL。
?>
t at bestcodepractise dot com
10 年前
@someone
@thomas
无法装饰一个已完成的类。提到的装饰器是不完整的,存在根本缺陷。请看
<?php
// 复制粘贴你的 FooBar 和 Foo 定义

$f = new Foo;
$fd = new FooDecorator($f);

var_dump($fd instanceof $f); //FALSE
var_dump(is_a($fd, 'Foo')); //FALSE
fooFoo($fd); // 这里出现 E_RECOVERABLE_ERROR !!!;

?>
你创建的只是一个恰好拥有相同方法的对象(鸭子类型)。但是如果在客户端代码中有人根据传递的装饰器的类型做出决策,他们将做出错误的决策,或者更准确地说,不是你(装饰器的作者)希望他们做出的决策。
FYI,这是基于 GoF 的正确实现。
<?php
class Foo
{
public
method doFoo()
{
// 做一些有用的事情并返回结果
}
}

// 装饰器必须通过 *扩展* 被装饰类来继承接口(方法、类型信息等)。
class FooDecorator extends Foo
{
private
$foo;

public function
__construct(Foo $foo)
{
$this->foo = $foo;
}

public function
doFoo()
{
$result = $this->foo->doFoo();
// ... 自定义结果 ...
return $result;
}
}

function
fooFoo(Foo $f) {}
$f = new Foo;
$fd = new FooDecorator($f);

var_dump($fd instanceof $f); //true
fooFoo($fd); // 这里不会出现 E_RECOVERABLE_ERROR;

?>

我还没有遇到过任何对最终类/方法的合法使用,我个人认为“final”没有太多用处,只是从 Java 复制粘贴到 PHP 的。该关键字使代码难以测试。如果你必须从一个最终类创建一个测试替身,因为你需要创建一个派生类型来屏蔽你不关心的方法。如果其中一个被最终化,你就已经失败了。
xavier dot inghels at gmail dot com
9 年前
Final 并不是真正关于覆盖或重载方法。
PHP 不支持(至少现在还没有?)方法覆盖。

但是,final 关键字会阻止你重新定义已经声明过的方法。
suisuiruyi at gmail dot com
8 年前
注意:属性不能声明为 final,只有类和方法可以声明为 final。
你可以使用 trait
<?php
trait PropertiesTrait {
public
$same = true;
public
$different = false;
}

class
PropertiesExample {
use
PropertiesTrait;
public
$same = true; // 严格标准
public $different = true; // 致命错误
}
?>
John smith
7 年前
正确方式
final protected function example() {
}

错误方式

protected final function example() {
}

来源:实践
To Top