Skip to content

对象比较

概述

PHP 提供了两种对象比较方式:==(松散比较)和 ===(严格比较)。== 比较两个对象的属性值是否相等,而 === 比较两个对象是否是同一个实例(引用相同)。理解这两种比较方式的区别对于正确处理对象间的比较逻辑至关重要。

基础概念

比较运算符

运算符含义比较内容
==松散比较属性值相等(类型相同、属性值相同)
===严格比较同一个实例(引用相同)
!==严格不等不是同一个实例
!=松散不等属性值不同

<=> (Spaceship) 运算符

PHP 7.0+ 引入的 spaceship 运算符可用于排序场景中的对象比较。

语法与代码

=== 严格比较(同一实例)

php
<?php

declare(strict_types=1);

class User
{
    public function __construct(
        public readonly string $name,
        public readonly int $age
    ) {}
}

$user1 = new User('Alice', 30);
$user2 = new User('Alice', 30);
$user3 = $user1;

var_dump($user1 === $user2);  // false - 不同实例
var_dump($user1 === $user3);  // true  - 同一实例
var_dump($user2 === $user3);  // false - 不同实例

== 松散比较(属性值相等)

php
<?php

declare(strict_types=1);

class Point
{
    public function __construct(
        public readonly float $x,
        public readonly float $y
    ) {}
}

$p1 = new Point(1.0, 2.0);
$p2 = new Point(1.0, 2.0);
$p3 = new Point(1.5, 2.0);

var_dump($p1 == $p2);   // true  - 相同类、相同属性值
var_dump($p1 == $p3);   // false - x 值不同
var_dump($p1 === $p2);  // false - 不同实例

不同类的比较

php
<?php

declare(strict_types=1);

class Dog
{
    public function __construct(public string $name) {}
}

class Cat
{
    public function __construct(public string $name) {}
}

$dog = new Dog('Buddy');
$cat = new Cat('Buddy');

var_dump($dog == $cat);    // false - 不同类
var_dump($dog === $cat);   // false - 不同实例、不同类

继承关系中的比较

php
<?php

declare(strict_types=1);

class ParentClass
{
    public function __construct(public string $value) {}
}

class ChildClass extends ParentClass {}

$parent = new ParentClass('test');
$child = new ChildClass('test');

var_dump($parent == $child);   // false - 不同类(虽然属性值相同)
var_dump($parent === $child);  // false

<=> Spaceship 运算符与排序

php
<?php

declare(strict_types=1);

class Score
{
    public function __construct(
        public readonly string $player,
        public readonly int $points
    ) {}

    public function compareTo(Score $other): int
    {
        return $this->points <=> $other->points;
    }
}

$scores = [
    new Score('Alice', 85),
    new Score('Bob', 92),
    new Score('Charlie', 78),
];

// 使用 spaceship 排序
usort($scores, fn(Score $a, Score $b) => $a->compareTo($b));

foreach ($scores as $score) {
    echo "{$score->player}: {$score->points}" . PHP_EOL;
}
// Charlie: 78
// Alice: 85
// Bob: 92

实现 Comparable 接口

php
<?php

declare(strict_types=1);

interface Comparable
{
    public function compareTo(self $other): int;
}

class Version implements Comparable
{
    public function __construct(
        public readonly int $major,
        public readonly int $minor,
        public readonly int $patch
    ) {}

    public function compareTo(Version $other): int
    {
        return [$other->major, $other->minor, $other->patch]
            <=> [$this->major, $this->minor, $this->patch];
    }

    public function equals(Version $other): bool
    {
        return $this->compareTo($other) === 0;
    }

    public function greaterThan(Version $other): bool
    {
        return $this->compareTo($other) > 0;
    }
}

$v1 = new Version(2, 0, 0);
$v2 = new Version(1, 5, 10);
$v3 = new Version(2, 0, 0);

var_dump($v1->equals($v2));          // false
var_dump($v1->equals($v3));          // true
var_dump($v1->greaterThan($v2));      // true

详细说明

== 比较的详细规则

  1. 两个对象必须是同一个类的实例(不考虑继承)
  2. 比较所有 public、protected 和 private 属性
  3. 属性值必须相等(使用 == 比较)
  4. 属性数量和名称必须完全一致
php
<?php

declare(strict_types=1);

class A
{
    public string $x = 'hello';
}

$a1 = new A();
$a2 = new A();

// 手动修改属性
$a2->x = 'world';

var_dump($a1 == $a2);   // false
var_dump($a1 === $a2);  // false

数组中的对象比较

php
<?php

declare(strict_types=1);

class Item
{
    public function __construct(public readonly int $id) {}
}

$items = [
    new Item(1),
    new Item(2),
    new Item(3),
    new Item(2),  // 重复
];

// in_array 使用 ==
$has = in_array(new Item(2), $items);  // true(不同的实例,但属性值相同)

// === 需要同一实例
$target = $items[1];
var_dump(in_array($target, $items, true));  // true(同一实例)

对象作为数组键

php
<?php

declare(strict_types=1);

// 对象不能直接作为数组键
// $key = new stdClass();
// $arr[$key] = 'value';  // Fatal error: Illegal offset type

// 解决方案:使用对象的唯一 ID 或 spl_object_id()
class CacheKey
{
    private int $id;

    public function __construct(int $id)
    {
        $this->id = $id;
    }
}

$obj = new CacheKey(42);
$cache = [];
$cache[spl_object_id($obj)] = 'cached_value';

实战示例

场景一:值对象比较

php
<?php

declare(strict_types=1);

class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency
    ) {}

    public function equals(Money $other): bool
    {
        return $this->amount === $other->amount
            && $this->currency === $other->currency;
    }

    public function greaterThan(Money $other): bool
    {
        if ($this->currency !== $other->currency) {
            throw new \RuntimeException('Cannot compare different currencies');
        }
        return $this->amount > $other->amount;
    }
}

$price1 = new Money(10000, 'CNY');
$price2 = new Money(10000, 'CNY');
$price3 = new Money(20000, 'CNY');

var_dump($price1->equals($price2));       // true
var_dump($price1->greaterThan($price3));  // false

场景二:对象排序

php
<?php

declare(strict_types=1);

class Task
{
    public function __construct(
        public readonly string $name,
        public readonly int $priority,
        public readonly string $dueDate
    ) {}
}

$tasks = [
    new Task('Write docs', 2, '2024-02-01'),
    new Task('Fix bug', 1, '2024-01-15'),
    new Task('Add feature', 3, '2024-03-01'),
];

// 按优先级排序
usort($tasks, fn(Task $a, Task $b) => $a->priority <=> $b->priority);

// 按日期排序
usort($tasks, fn(Task $a, Task $b) => $a->dueDate <=> $b->dueDate);

注意事项

注意事项

  • == 比较要求两个对象是同一类的实例(继承关系也不行)
  • === 比较的是引用,两个独立创建但属性相同的对象返回 false
  • 对象不能直接作为数组键
  • === 的性能优于 ==(无需比较属性值)

小贴士

  • 对于值对象,自定义 equals() 方法更语义化
  • 对于需要排序的场景,使用 spaceship 运算符
  • 使用 spl_object_id() 获取对象的唯一 ID

最佳实践

1. 为值对象实现 equals 方法

php
<?php

declare(strict_types=1);

class Email
{
    public function __construct(private readonly string $address) {}

    public function equals(Email $other): bool
    {
        return strtolower($this->address) === strtolower($other->address);
    }
}

2. 使用 === 进行实例检查

php
<?php

declare(strict_types=1);

// 推荐使用 === 检查是否是同一实例
if ($cachedUser === $currentUser) {
    // 同一实例,无需更新
}

参考链接