2024 年 PHP 日本大会

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";
}
}
// 将导致致命错误:无法重写最终方法 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.4.0 起的 final 属性示例

<?php
class BaseClass {
final protected
string $test;
}

class
ChildClass extends BaseClass {
public
string $test;
}
// 将导致致命错误:无法重写 final 属性 BaseClass::$test
?>

示例 #4 自 PHP 8.1.0 起的 final 常量示例

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

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

// 致命错误:Bar::X 无法重写最终常量 Foo::X
?>

注意 从 PHP 8.0.0 开始,私有方法不能声明为 final,构造函数 除外

注意声明为 private(set) 的属性隐式为 final

添加注释

用户贡献注释 11 条注释

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

https://php.net/manual/en/language.oop5.constants.php
penartur at yandex dot ru
17 年前
请注意,即使在父类中将 final 方法定义为私有,也不能重写它们。
因此,以下示例
<?php
class parentClass {
final private function
someMethod() { }
}
class
childClass extends parentClass {
private function
someMethod() { }
}
?>
将报错 "Fatal error: Cannot override final method parentClass::someMethod() in ***.php on line 7"

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

因此,请记住,如果您定义了一个私有的 final 方法,则不能在子类中放置同名方法。
Rumour
1 年前
自 PHP8.1 起,类常量可以“final 化”。为了部分反驳很久以前写的大多数流行用户贡献,他们当时是完全正确的。
someone dot else at elsewhere dot net
10 年前
@thomas at somewhere dot com

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

<?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;
}
}
?>
slorenzo at clug dot org dot ve
17 年前
<?php
class parentClass {
public function
someMethod() { }
}
class
childClass extends parentClass {
public final function
someMethod() { } // 重写父函数
}

$class = new childClass;
$class->someMethod(); // 调用子类中的重写函数
?>
mattsch at gmail dot com
10 年前
您可以使用 final 方法替换类常量。这样做的原因是,您无法隔离地单元测试另一个类中使用的类常量,因为您无法模拟常量。final 方法允许您拥有与常量相同的功能,同时保持代码的松散耦合。

紧密耦合示例(不应使用常量)

<?php
接口 FooInterface
{
}

Foo 实现 FooInterface
{
常量
BAR = 1;

公有函数
__construct()
{
}
}

接口
BazInterface
{
公有函数
getFooBar();
}

// 此类无法单独进行单元测试,因为必须加载实际类 Foo 才能获取 Foo::BAR 的值
Baz 实现 BazInterface
{
私有
$foo;

公有函数
__construct(FooInterface $foo)
{
$this->foo = $foo;
}

公有函数
getFooBar()
{
返回
Foo::BAR;
}

}

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

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

<?php
接口 FooInterface
{
公有函数
bar();
}

Foo 实现 FooInterface
{
公有函数
__construct()
{
}

最终公有函数
bar()
{
返回
1;
}
}

接口
BazInterface
{
公有函数
getFooBar();
}

// 此类可以单独进行单元测试,因为通过模拟 FooInterface 并调用最终的 bar 方法,不需要加载类 Foo。
Baz 实现 BazInterface
{
私有
$foo;

公有函数
__construct(FooInterface $foo)
{
$this->foo = $foo;
}

公有函数
getFooBar()
{
返回
$this->foo->bar();
}

}

$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
[email protected]
10 年前
我认为值得了解
<?php
BaseClass
{
受保护的静态
$var = '我属于 BaseClass';

公有静态函数
test()
{
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且这是我的变量: `'.self::$var.'`<br>';
}
公有静态函数
changeVar($val)
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
最终公有静态函数
dontCopyMe($val)
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
}

ChildClass 扩展 BaseClass
{
受保护的静态
$var = '我属于 ChildClass';

公有静态函数
test()
{
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且这是我的变量: `'.self::$var.'`<br>'.
'并且这是我的父变量: `'.parent::$var.'`';
}
公有静态函数
changeVar($val)
{
self::$var = $val;
echo
'<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>'.
'但是父 $var 仍然是: `'.parent::$var.'`';
}
公有静态函数
dontCopyMe($val) // 致命错误: 无法覆盖最终方法 BaseClass::dontCopyMe() in ...
{
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() in ...
?>
[email protected]
16年前
final 关键字的使用就像在 Java 中一样。
在 Java 中,final 有三种用途:
1) 防止类继承
2) 防止方法重写或在子类中重新定义方法
方法
3) 并声明常量
但是 PHP 中似乎缺少第三点。
我想,因为我是一名 Java 开发人员,目前正在学习 PHP。
匿名用户
14年前
FINAL 的行为并不像你想象的那么严重。一个小例子
<?php
A {
最终私有函数
method(){}
}

B 扩展 A {
私有函数
method(){}
}
?>

通常情况下,你可能会期望发生以下情况:
- final 和 private 关键字不能一起使用导致错误
- 没有错误,因为 private 可见性表示方法/变量/等仅在同一类中可见

但是 PHP 的情况有点奇怪:“无法覆盖最终方法 A::method()”

因此,可以在子类中拒绝方法名!不知道这是否是一个好的行为,但也许对你的目的有用。
匿名用户
3个月前
当需要特殊的类结构时,最终确定魔法方法可能会有所帮助。

<?php

抽象类 A {
最终公有函数
__construnct(){ echo "A"; }
}

B 扩展 A {
公有函数
__construct(){ echo "B"; }
}

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

?>
Baldurien
14年前
“注意:Java 开发者请注意,PHP 中不使用 'final' 关键字来定义类常量。我们使用 'const' 关键字。”

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

这或多或少是正确的,尽管事实上 PHP 中的常量(无论是否在类级别定义)只能是标量(int,string 等),而在 Java 中它们可以是纯对象(例如:java.awt.Color.BLACK)。要实现这种类型的常量,唯一可能的解决方案是

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

static function
__init() {
static
$init = false;
if (
$init) throw new Exception('Constants were already initialized');
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。
?>
To Top