Ruby的Object是所有类的父对象,因此它的所有方法在所有对象中都是可用的(如果没有被覆盖的话)。因此它的重要性那是不言而喻的。
Object到底实现哪些功能,下面一一道来:
1. 对象比较
Ruby定义了若干对象比较的方法,有==、===、eql?和equal?,是不是很头大,不过还没完,还有一个=~ 。下面我们对这些比较方式做一个说明。
首先是==,只有两个对象相同时,该方法的返回值为true。但是子类一般都会对这个方法进行过载,来表示语义上的相等。比如字符串对象和数值对象都对==方法进行了过载。与之相对照的是,equal?方法不应该被子类过载,只有在两个对象表示同一个对象的情况下才会返回true值(注意,是不应该,而不是不能。没有人可以阻止你在Ruby的世界里干这样的坏事,不过你愤怒的老板会给你一张黄牌)。
首先看一个简单的例子:
class MyClass
attr_reader :str
def initialize(s)
@str = s
end
end
obj1 = MyClass.new("hello")
obj2 = MyClass.new("hello")
puts obj1 == obj2 #false
puts obj1.equal?(obj2) #false
可以看出,在一般情况下,==和equal?的行为是一样的,不过我们通常用比较有意义的方式来过载==方法:
class MyClass
attr_reader :str
def initialize(s)
@str = s
end
def ==(o)
str == o.str
end
# 你不应该这样做!
# def equal?(o)
# str == o.str
# end
end
obj1 = MyClass.new("hello")
obj2 = MyClass.new("hello")
puts obj1 == obj2 #true
puts obj1.equal?(obj2) #false
可以看出,我们重新定义的==方法使得比较成功了,再仔细看看,我们是使用了String对象的==方法来实现我们自己定义的==方法的,你应该看得出来,String类已经重载了==方法(否则我们的相等操作无法返回true),如果还没有看出来,下面的这段代码可以帮助你:
s1 = "hello"
s2 = "hello"
puts s1 == s2 #true
puts s1.equal?(s2) #false
s3 = s1
puts s1 == s3 #true
puts s1.equal?(s3) #true
应该很清楚了,在定义自己的新类的时候,你通常应该为它定义一个比较有意义的==方法,但是你不应该为它重新定义一个equal?方法。
那么,eql?的作用是什么?如果两个对象的值相等的话,那么eql?方法的返回值为true。eql?方法有一个特殊的作用,它被Hash对象用来测试键值是否相等(注意,还需要有hash方法来配合使用,这个方法在后面介绍)。
class MyClass
attr_reader :str
def initialize(s)
@str = s
end
def eql?(o)
str.eql?(o.str)
end
def hash
str.hash
end
end
obj1 = MyClass.new("hello")
obj2 = MyClass.new("hello")
puts obj1 == obj2 #false
puts obj1.eql?(obj2) #true
hash = {}
hash[obj1] = 1
puts hash[obj2] #1
hash[obj2] = 2
puts hash[obj1] #2
在Object类中,==与eql?的行为是一致的,一般说来,在定义具体类时,也应该保持这样的方式。不过在系统定义的类中有一些特例,比如在Numeric类型的类中,==要在类型和值都相同时才会返回真值,而eql?方法则只要值相等就可以:
1 == 1.0 #=> true
1.eql? 1.0 #=> false
===在Ruby的文档中称作“case equality”,它的主要用途是在case语句中,用来判断case语句中when子句的匹配状况。当然你也可以直接在语句中使用它,比如判断一个对象是否在一个范围内就可以使用===来判断:
puts (1..10) === 2 #=>true
puts (1..10) === 11 #=>false
值得注意的是是Range类重载了===方法,因此Range对象实例必须在左侧,下面的结果你应该不会感到意外:
puts 2 === (1..10) #=>false
puts (1..10) === 11 #=>false
下面我们用一个故意设计的类(其实可以直接用Range的)来演示一下在自己定义的类中过载===方法,我们定义一个ScoreRange的类来对分数进行判断:
class ScoreRange
def initialize(min, max)
@min = min
@max = max
end
def ===(score)
(@min..@max) === score
end
end
PASSED = ScoreRange.new(60, 100)
FAILED = ScoreRange.new(0, 59)
GOOD = ScoreRange.new(80, 100)
score = 68
case score
when GOOD
puts "Good!"
when PASSED
puts "Passed!"
when FAILED
puts "Failed!"
end
最后的输出结果回事“Passed!”,当然,这个程序还存在瑕疵,比如对59.5这样的成绩没有理会,也没有无效分数的判断,它只是用来演示===方法的过载。
最后一个=~方法是用于正则表达式的模式匹配的,Object的子类应该用比较有意义的方式来过载它,在Object类中,该方法总是返回false。它的具体使用方式可以参见正则表达式的部分,这里不做详细剖析。