equal?,eql?,===和==之间有什么区别?


552

我试图了解这四种方法之间的区别。我知道默认情况下会==调用equal?当两个操作数引用完全相同的对象时返回true 的方法。

===默认情况下也调用==哪个调用equal?...好的,因此,如果这三个方法都未被覆盖,那么我猜想 =====并且equal?做的完全一样吗?

现在来了eql?。这是做什么的(默认情况下)?它是否调用操作数的哈希/ ID?

为什么Ruby有这么多相等标志?他们应该在语义上有所不同吗?


我刚开始IRB并且有以下结果这违背你的......这些3的全部为真:"a" == "a""a" === "a""a".eql? "a"。但这是错误的:("a".equal? "a"我的是红宝石1.9.2-p180)
PeterWong 2011年

7
@Peter:那是因为字符串会覆盖所有相等运算符。通过努力a = Object.new; b = Object.new,那么所有=====.equal?.eql?将返回trueaVS a和虚假的aVS b
Nemo157

Answers:


785

我将在这里大量引用对象文档,因为我认为它有一些很好的解释。我鼓励您阅读它,以及这些方法的文档,因为它们在其他类(例如String)中被覆盖。

旁注:如果您想在不同的对象上自己尝试一下,请使用以下方法:

class Object
  def all_equals(o)
    ops = [:==, :===, :eql?, :equal?]
    Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
  end
end

"a".all_equals "a" # => {"=="=>true, "==="=>true, "eql?"=>true, "equal?"=>false}

== -通用“平等”

在“对象”级别,==仅当objother是相同的对象时才返回true 。通常,此方法在子孙类中被重写以提供特定于类的含义。

这是最常见的比较,也是您(作为类的作者)决定两个对象是否“相等”的最基本的位置。

=== —大小写平等

对于Object类,实际上与call相同#==,但通常被后代覆盖以在case语句中提供有意义的语义。

这是非常有用的。具有有趣===实现的事物示例:

  • 范围
  • 正则表达式
  • Proc(在Ruby 1.9中)

因此,您可以执行以下操作:

case some_object
when /a regex/
  # The regex matches
when 2..4
  # some_object is in the range 2..4
when lambda {|x| some_crazy_custom_predicate }
  # the lambda returned true
end

在这里查看我的答案,以获取有关case+ Regex如何使代码更简洁的完整示例。当然,通过提供自己的===实现,您可以获得自定义case语义。

eql?- Hash平等

eql?如果obj并且other引用相同的哈希键,则该方法返回true 。用来Hash测试成员是否相等。对于类的对象Objecteql?是的同义词==子类通常通过继承eql?其覆盖的==方法来延续这一传统,但是也有例外。Numeric类型,例如,跨==,但不跨eql?,执行类型转换,因此:

1 == 1.0     #=> true
1.eql? 1.0   #=> false

因此,您可以随意覆盖此值以供自己使用,也可以覆盖==和使用,alias :eql? :==以便这两种方法的行为相同。

equal? —身份比较

与方法不同==,该equal?方法永远不能被子类覆盖:它用于确定对象标识(即a.equal?(b)iff与a是同一对象b)。

这实际上是指针比较。


32
从您的回答中可以看出,严格程度是:相等吗?<eql?<== <===。通常,您使用==。对于某些宽松的目的,可以使用===。对于严格的情况,请使用eql ?,对于完整的身份,请使用equal?。
sawa 2012年

21
严格性的概念并没有在文档中得到强制执行,甚至没有提出,只是这种情况Numeric以比严格的方式进行处理==。这完全取决于课程的作者。===case语句外很少使用。
jtbandes 2012年

4
==在大小上也相等。即,如果您包含Comparable,则会根据<=>返回0进行定义。这就是为什么1 == 1.0返回true。
apeiros 2012年

5
我通常认为@sawa ===是“匹配”的意思(大致)。如“使正则表达式匹配字符串”或“使范围匹配(包括)数字”一样。
开尔文

7
有趣的事实:官方文档现在链接到此答案(请参见ruby-doc.org/core-2.1.5/…)。
Mark Amery 2014年

46

我喜欢jtbandes答案,但是由于它很长,因此我将添加自己的紧凑型答案:

=====eql?equal?
是4个比较器,即 在Ruby中比较2个对象的4种方法。
因为在Ruby中,所有比较器(和大多数运算符)实际上都是方法调用,所以您可以自己更改,覆盖和定义这些比较方法的语义。但是,重要的是要理解,当Ruby的内部语言构造使用哪个比较器时:

==(值比较)
Ruby在所有地方都使用:==来比较2个对象的,例如。散列值:

{a: 'z'}  ==  {a: 'Z'}    # => false
{a: 1}    ==  {a: 1.0}    # => true

===(大小写比较)
在构造时,Ruby使用:===。以下代码段在逻辑上是相同的:

case foo
  when bar;  p 'do something'
end

if bar === foo
  p 'do something'
end

eql?(哈希键比较)
Ruby使用:eql?(与方法hash结合使用)以比较Hash键。在大多数课程中:eql?与:==相同。
有关:eql的知识?仅在要创建自己的特殊类时才重要:

class Equ
  attr_accessor :val
  alias_method  :initialize, :val=
  def hash()           self.val % 2             end
  def eql?(other)      self.hash == other.hash  end
end

h = {Equ.new(3) => 3,  Equ.new(8) => 8,  Equ.new(15) => 15}    #3 entries, but 2 are :eql?
h.size            # => 2
h[Equ.new(27)]    # => 15

注意:常用的Ruby-Class Set也依赖于Hash-key-comparison。

equal?(对象身份比较)
Ruby使用:equal?检查两个对象是否相同。(属于BasicObject类的)此方法不应被覆盖。

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false

30
这是一个很好的答案,但几乎与jtbandes的一样长。:)
odigity

2
@odigity,大约70%。我可以想到将这30%花在的很多事情。
卡里·斯沃夫兰

我认为这个例子eql?很容易引起误解。eql?是一个相等比较,与哈希的计算方式一致,即a.eql?(b)保证a.hash == b.hash。它不是简单地比较哈希码。
安德烈·塔兰佐夫

时相比真的等同于bar === foofoo === bar?我希望后者是正确的,并且很重要,因为编译器将调用左侧:===`'
Alexis Wilke

据我所知,它是bar === foo:Ruby在左侧使用case值,在右侧使用case变量。这可能与避免NPE(空指针异常)有关。
安德烈亚斯·雷奥·肯尼普

33

等号运算符:==和!=

==运算符,也称为相等或双精度相等,如果两个对象相等,则返回true,否则返回false。

"koan" == "koan" # Output: => true

!=运算符(也称为不等式)与==相反。如果两个对象不相等,则返回true;如果相等,则返回false。

"koan" != "discursive thought" # Output: => true

请注意,具有相同元素但顺序不同的两个数组不相等,同一字母的大写和小写版本也不相等,依此类推。

比较不同类型的数字(例如整数和浮点数)时,如果它们的数值相同,则==将返回true。

2 == 2.0 # Output: => true

等于?

与==运算符测试两个操作数是否相等不同,equal方法检查两个操作数是否引用相同的对象。这是Ruby中最严格的相等形式。

例如:a =“ zen” b =“ zen”

a.object_id  # Output: => 20139460
b.object_id  # Output :=> 19972120

a.equal? b  # Output: => false

在上面的示例中,我们有两个具有相同值的字符串。但是,它们是两个不同的对象,具有不同的对象ID。因此,等于?方法将返回false。

让我们再试一次,仅这次b是对a的引用。注意,两个变量的对象ID相同,因为它们指向同一对象。

a = "zen"
b = a

a.object_id  # Output: => 18637360
b.object_id  # Output: => 18637360

a.equal? b  # Output: => true

eql?

在Hash类中,eql?用于测试密钥是否相等的方法。需要一些背景来解释这一点。在计算的一般上下文中,哈希函数采用任意大小的字符串(或文件),并生成称为哈希码的固定大小的字符串或整数,通常仅称为哈希。一些常用的哈希码类型是MD5,SHA-1和CRC。它们用于加密算法,数据库索引,文件完整性检查等。某些编程语言(例如Ruby)提供一种称为哈希表的集合类型。哈希表是类似于字典的集合,它们成对存储数据,由唯一键及其对应的值组成。在后台,这些密钥存储为哈希码。哈希表通常称为哈希表。请注意,hash一词如何引用哈希码或哈希表。

Ruby提供了一种称为hash的内置方法,用于生成哈希码。在下面的示例中,它接受一个字符串并返回一个哈希码。注意具有相同值的字符串如何始终具有相同的哈希码,即使它们是不同的对象(具有不同的对象ID)也是如此。

"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547

hash方法在Object类中包含的Kernel模块中实现,该模块是所有Ruby对象的默认根目录。一些类(例如Symbol和Integer)使用默认实现,而其他类(例如String和Hash)提供其自己的实现。

Symbol.instance_method(:hash).owner  # Output: => Kernel
Integer.instance_method(:hash).owner # Output: => Kernel

String.instance_method(:hash).owner  # Output: => String
Hash.instance_method(:hash).owner  # Output: => Hash

在Ruby中,当我们将某些内容存储在哈希(集合)中时,将作为键提供的对象(例如字符串或符号)转换为哈希码并存储为哈希码。稍后,当从哈希(集合)中检索元素时,我们提供了一个对象作为键,该对象被转换为哈希码并与现有键进行比较。如果存在匹配项,则返回相应项目的值。使用eql进行比较。引擎盖下的方法。

"zen".eql? "zen"    # Output: => true
# is the same as
"zen".hash == "zen".hash # Output: => true

在大多数情况下,eql?方法的行为类似于==方法。但是,也有一些例外。例如,eql?比较整数和浮点数时,不执行隐式类型转换。

2 == 2.0    # Output: => true
2.eql? 2.0    # Output: => false
2.hash == 2.0.hash  # Output: => false

大小写相等运算符:===

Ruby的许多内置类,例如String,Range和Regexp,提供了自己===运算符的实现,也称为大小写相等,三等号或三等值。因为在每个类中实现的方式不同,所以它的行为取决于调用的对象的类型。通常,如果右侧的对象“属于”左侧的对象或“属于”左侧的对象,则返回true。例如,它可以用来测试一个对象是否是一个类(或其子类之一)的实例。

String === "zen"  # Output: => true
Range === (1..2)   # Output: => true
Array === [1,2,3]   # Output: => true
Integer === 2   # Output: => true

使用其他可能最适合该工作的方法可以实现相同的结果。通常最好编写尽可能易于理解的代码,而又不牺牲效率和简洁性。

2.is_a? Integer   # Output: => true
2.kind_of? Integer  # Output: => true
2.instance_of? Integer # Output: => false

注意最后一个示例返回false,因为诸如2之类的整数是Fixnum类的实例,而Fixnum类是Integer类的子类。===,is_a?和instance_of?如果对象是给定类或任何子类的实例,则方法返回true。instance_of方法更严格,仅当对象是该确切类的实例而不是子类的实例时才返回true。

is_a?和kind_of?方法在内核模块中实现,该模块由Object类混合。两者都是同一方法的别名。让我们验证一下:

Kernel.instance_method(:kind_of?)== Kernel.instance_method(:is_a?)#输出:=> true

范围实现===

在范围对象上调用===运算符时,如果右侧的值落在左侧的范围内,则它返回true。

(1..4) === 3  # Output: => true
(1..4) === 2.345 # Output: => true
(1..4) === 6  # Output: => false

("a".."d") === "c" # Output: => true
("a".."d") === "e" # Output: => false

请记住,===运算符会调用左侧对象的===方法。因此(1..4)=== 3等于(1..4)。===3。换句话说,左侧操作数的类将定义===方法的哪种实现是调用,因此操作数位置不可互换。

正则表达式的===实现

如果右侧的字符串与左侧的正则表达式匹配,则返回true。/ zen / ===“今天练习zazen”#输出:=> true#与“今天练习zazen”相同== / / zen /

在case / when语句中===运算符的隐式用法

此操作符还用于case / when语句的内部。这是其最常见的用法。

minutes = 15

case minutes
  when 10..20
    puts "match"
  else
    puts "no match"
end

# Output: match

在上面的示例中,如果Ruby隐式使用了double equal运算符(==),则范围10..20将不被视为等于诸如15的整数。之所以匹配,是因为Triple equal运算符(===)为在所有case / when语句中隐式使用。上面示例中的代码等效于:

if (10..20) === minutes
  puts "match"
else
  puts "no match"
end

模式匹配运算符:=〜和!〜

=〜(等号)和!〜(等号)运算符用于将字符串和符号与正则表达式模式进行匹配。

String和Symbol类中==方法的实现期望使用正则表达式(Regexp类的实例)作为参数。

"practice zazen" =~ /zen/   # Output: => 11
"practice zazen" =~ /discursive thought/ # Output: => nil

:zazen =~ /zen/    # Output: => 2
:zazen =~ /discursive thought/  # Output: => nil

Regexp类中的实现期望使用字符串或符号作为参数。

/zen/ =~ "practice zazen"  # Output: => 11
/zen/ =~ "discursive thought" # Output: => nil

在所有实现中,当字符串或符号与Regexp模式匹配时,它将返回一个整数,该整数是匹配的位置(索引)。如果不匹配,则返回nil。请记住,在Ruby中,任何整数值都是“ truthy”,而nil是“ falsy”,因此=〜运算符可用于if语句和三元运算符。

puts "yes" if "zazen" =~ /zen/ # Output: => yes
"zazen" =~ /zen/?"yes":"no" # Output: => yes

模式匹配运算符对于编写较短的if语句也很有用。例:

if meditation_type == "zazen" || meditation_type == "shikantaza" || meditation_type == "kinhin"
  true
end
Can be rewritten as:
if meditation_type =~ /^(zazen|shikantaza|kinhin)$/
  true
end

!〜运算符与=〜相反,当没有匹配项时返回true,在有匹配项时返回false。

有关更多信息,请参见此博客文章


6
我发现这是一个比当前接受的答案更好的答案,因为它提供了很好的示例,并且对于不同种类的相等性意味着什么以及它们为什么存在/在何处使用则没有太多的歧义。
qqwy

1
非常详细的答案,但是在我的irb(红宝石v 2.2.1)上:zen === "zen"返回false
Mike R

@MikeR谢谢您让我知道。我已经纠正了答案。
BrunoFacca

我认为您的意思是type_of?“注意最后一个示例返回false,因为诸如2之类的整数是Fixnum类的实例,而Fixnum类是Integer类的子类。===,is_a?和instance_of?(TYPE_OF?)”
user1883793 16-10-26

1
我喜欢这个答案。谢谢
Abdullah Fadhel


8

===#-大小写相等

==#-泛型相等

两者的工作原理相似,但“ ===”甚至可以做案例陈述

"test" == "test"  #=> true
"test" === "test" #=> true

这里的区别

String === "test"   #=> true
String == "test"  #=> false

3
它们并没有类似地工作,即使a==b那时确实如此a===b。但是a===b功能更强大。 ===它不是对称的,它的a===b含义与之完全不同b===a,更不用说了a==b
mwfearnley

8

我想扩展一下===操作员。

=== 不是相等运算符!

不。

让我们真正理解这一点。

===在Javascript和PHP中,您可能熟悉作为相等运算符,但是这在Ruby中并不是相等运算符,并且具有根本不同的语义。

那怎么===办?

=== 是模式匹配运算符!

  • === 匹配正则表达式
  • === 检查范围成员
  • === 检查是否是类的实例
  • === 调用lambda表达式
  • === 有时会检查是否相等,但大多数情况下不会

那么这种疯狂有什么意义呢?

  • Enumerable#grep===内部使用
  • case when语句在===内部使用
  • 有趣的事实,内部rescue使用===

这就是为什么您可以在case when语句中使用正则表达式,类和范围甚至lambda表达式的原因。

一些例子

case value
when /regexp/
  # value matches this regexp
when 4..10
  # value is in range
when MyClass
  # value is an instance of class
when ->(value) { ... }
  # lambda expression returns true
when a, b, c, d
  # value matches one of a through d with `===`
when *array
  # value matches an element in array with `===`
when x
  # values is equal to x unless x is one of the above
end

所有这些示例都适用pattern === valuegrep方法。

arr = ['the', 'quick', 'brown', 'fox', 1, 1, 2, 3, 5, 8, 13]
arr.grep(/[qx]/)                                                                                                                            
# => ["quick", "fox"]
arr.grep(4..10)
# => [5, 8]
arr.grep(String)
# => ["the", "quick", "brown", "fox"]
arr.grep(1)
# => [1, 1]

-8

我为上述所有内容编写了一个简单的测试。

def eq(a, b)
  puts "#{[a, '==',  b]} : #{a == b}"
  puts "#{[a, '===', b]} : #{a === b}"
  puts "#{[a, '.eql?', b]} : #{a.eql?(b)}"
  puts "#{[a, '.equal?', b]} : #{a.equal?(b)}"
end

eq("all", "all")
eq(:all, :all)
eq(Object.new, Object.new)
eq(3, 3)
eq(1, 1.0)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.