This article aims to familiarize you with Swift's ʻEquatable and
Comparable`. Since it's a big deal, I'll compare it with Java.
Both Swift and Java are strong static cleanups. 1
is not"1"
. Swift has reference types and value types, and Java also has reference types and primitive types. But Swift doesn't say "I've compared String
with ==
> <". Let's see what is the same and what is different.
Items to check
is.
Swift
Use the ===
operator. By comparing ʻAnyObject, it can be used without implementing ʻEquatable
etc.
public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return Bool(Builtin.cmp_eq_RawPointer(
Builtin.bridgeToRawPointer(Builtin.castToUnknownObject(l)),
Builtin.bridgeToRawPointer(Builtin.castToUnknownObject(r))
))
case (nil, nil):
return true
default:
return false
}
}
===
is in Equatable.swift.
Only the type that implements ʻEquatable` can perform equality judgment. (error: binary operator '==' cannot be applied)
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
When implementing ʻEquatable, it should be implemented so as to maintain substitutability. Two instances that are equal to each other are compatible in code that uses that value. Therefore,
== should consider all visible aspects of the type. ʻEquatable
types should not expose any worthless aspects other than class identify, and everything that is exposed should be documented.
If ʻEquatable`, the following properties must be satisfied.
b == a
if ʻa == b`)and
b == c, ʻa == c
)In addition, ʻa! = Bmust be
! (A == b)`. This is satisfied on the standard library side.
extension Equatable {
@_transparent
public static func != (lhs: Self, rhs: Self) -> Bool {
return !(lhs == rhs)
}
}
ʻOptional itself is not ʻEquatable
, but there are some==
in addition to those of ʻEquatable. Which
== is used depends on whether T is ʻEquatable
.
When comparing nil with ʻOptional such that T is ʻEquatable
, the following==
is used.
@_inlineable
public func == <T: Equatable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
When comparing nil with ʻOptional where T is not ʻEquatable
@_transparent
public func == <T>(lhs: T?, rhs: _OptionalNilComparisonType) -> Bool {
switch lhs {
case .some(_):
return false
case .none:
return true
}
}
To use. _OptionalNilComparisonType
allows T that is not ʻEquatable` to be compared with nil, and is not used in the true / false judgment process itself.
It is possible to compare T and nil, but it is treated as a warning. (warning: comparing non-optional value of type to nil always returns false)
By implementing Hashable
, it can be the key of Dictionary
.
Hashable
inherits from ʻEquatable`.
public protocol Hashable : Equatable {
var hashValue: Int { get }
}
For any two objects a, b, if ʻa == b, then ʻa.hashValue == b.hashValue
. It is not necessary to have ʻa == b when ʻa.hashValue == b.hashValue
.
Limited to comparisons of the same type by Self
.
Ordering to types is done by implementing Comparable
. ʻArray has several sorting methods, but
muting func sort () /
func sorted ()-> [Element] can only be done when ʻElement
is Comparable
.
public protocol Comparable : Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
}
Four methods are defined, but if you implement ==
and <
of ʻEquatable`, the remaining three will be covered.
extension Comparable {
public static func > (lhs: Self, rhs: Self) -> Bool {
return rhs < lhs
}
public static func <= (lhs: Self, rhs: Self) -> Bool {
return !(rhs < lhs)
}
public static func >= (lhs: Self, rhs: Self) -> Bool {
return !(lhs < rhs)
}
}
This ensures that only one of ʻa == b / ʻa <b
/ b <a
is true.
Also, the total order in the narrow sense always holds.
! (B <a)
if ʻa <b`)and
b <c, ʻa <c
)The implementation type may contain values that are treated as exceptions and are not subject to comparison, but such values need not be treated in a narrow total order.
FloatingPoint.nan
returns false no matter how you compare it.
1.21 > Double.nan // false
1.21 < Double.nan // false
1.21 = Double.nan // false
Double.nan == Double.nan // false
Double.nan.isNaN // true
Positioning is the definition of the default comparison method, and free sorting is possible by using ʻareInIncreasingOrder`, which will be introduced later.
Yes. Comparable
inherits from ʻEquatable`. Only when there is an equal sign can we talk about the inequality sign.
There is no comparison with nil.
By the way, it is possible to implement Comparable
in ʻOptional, but it is not possible to implement protocol for constrained ones such as ʻOptional where Wrapped: Equatable
.
(error: extension with constraints cannot have an inheritance clause)
Limited to comparisons of the same type by Self
.
By preparing (Element, Element) throws-> Bool
, you can sort according to the situation.
mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
However, the weak order in the narrow sense must be established.
and ʻareInIncreasingOrder (b, c)
, ʻareInIncreasingOrder (a, c)`)No limit. It depends on the implementation.
No limit. If implemented, ʻArray <Optional
Limited to comparisons of the same type by (Element, Element)
.
XCTAssertEqual
requires ʻEquatable`.
func XCTAssertEqual<T>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
_ message: @autoclosure () -> String = default,
file: StaticString = #file,
line: UInt = #line
) where T : Equatable
There is a separate T?
Version for type safety reasons.
Java
Use ==
.
Use the ʻequals method. The ʻequals
method itself originally originated from class ʻObject`. However, the actual situation is the same judgment.
public boolean equals(Object obj) {
return (this == obj);
}
The inheritance side overrides this and determines it. An example of String
is shown below.
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
ʻEquals` method
x.equals (x) == true
) y.equals (x) == true
if x.equals (y) == true
) x.equals (y) == true
and y.equals (z) == true
if x.equals (z) == true
)There is.
It is supposed to compare non-null objects. If the other party is null, it will be false.
When overriding the ʻequalsmethod, you also need to override the
public int hashCode (), which is also derived from ʻObject
, and take care of it together. This is because hashCode
has the following rules regarding ʻequals`.
hashCode
always returns the same integer if the information used in the comparison by the ʻequals` method has not changed.If two objects are methodically equal,
hashCode` returns the same integer for each.If the two objects are different in method,
hashCode` should return different integers.String
overrides hashCode
as follows:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
You can use ʻequals because all classes inherit from ʻObject
.
If Comparable <? Super T>
, as a sort method for List <T>
public static <T extends Comparable<? super T>> void sort(List<T> list)
Can be used.
public int compareTo (To)
The following must be guaranteed at the time of implementation.
sgn (x.compareTo (y)) == -sgn (y.compareTo (x))
(x.compareTo (y)> 0 && y.compareTo (z)> 0)
then x.compareTo (z)> 0
x.compareTo (y) == 0
means sgn (x.compareTo (z)) == sgn (y.compareTo (z))
When the results of ʻe1.compareTo (e2) == 0 and ʻe1.equals (e2)
match for all e1 and e2 of class C, it is said to be consistent with ʻequals. Consistency with ʻequals
is not required, but it is recommended to be consistent as it does not guarantee the behavior of sort sets and sort maps. An example of inconsistency is BigDecimal
.
If it is inconsistent, it is recommended to state so.
Null is not an instance of any object, so an exception should be thrown.
Comparable <T>
is
public interface Comparable<T> {
public int compareTo(T o);
}
As you can see, it's possible to implement a comparison with another type.
By preparing Comparator <T>
public static <T> void sort(List<T> list, Comparator<? super T> c)
Can be sorted by.
ʻInt compare (To1, To2) `The following must be guaranteed at the time of implementation.
sgn (compare (x, y)) == -sgn (compare (y, x))
((compare (x, y)> 0) && (compare (y, z)> 0))
then compare (x, z)> 0
compare (x, y) == 0
means sgn (compare (x, z)) == sgn (compare (y, z))
Comparator <T>
is consistent with ʻequals when the results of ʻe1.compareTo (e2) == 0
and ʻe1.equals (e2)` match for all e1 and e2 in set S There is.
Consistency is also recommended as the behavior of sort sets and sort maps is no longer guaranteed. If it is inconsistent, it is better to specify it.
Comparison with null is allowed depending on the option.
For Comparator <T>
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)
There are methods like.
It is intended for comparison between the same types.
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
//Abbreviation
}
For reference types, there is an assert method that takes ʻObject`.
static public void assertEquals(String message, Object expected, Object actual)
Processing is divided depending on whether ʻObject expected is null or not. If it is null, the result depends on whether actual is null. If it is not null, make a judgment with ʻexpected.equals (actual)
.
On the other hand, methods are prepared for each primitive type. For example, float
static public void assertEquals(String message, float expected, float actual, float delta)
There is a Float
inside
public static int compare(float f1, float f2) {
if (f1 < f2)
return -1; // Neither val is NaN, thisVal is smaller
if (f1 > f2)
return 1; // Neither val is NaN, thisVal is larger
// Cannot use floatToRawIntBits because of possibility of NaNs.
int thisBits = Float.floatToIntBits(f1);
int anotherBits = Float.floatToIntBits(f2);
return (thisBits == anotherBits ? 0 : // Values are equal
(thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1)); // (0.0, -0.0) or (NaN, !NaN)
}
Is calling.
Swift | Java | |
---|---|---|
Same judgment | === |
== |
Equal value judgment | == |
equals |
Properties imposed on equality judgment | Reflective/Symmetry/Transitive | Reflective/Symmetry/Transitive/Consistency |
Comparison with null in equality judgment | Optional<T> May be true if type |
Always false |
Hash value consistency when equal | Hashable Required |
Mandatory |
Comparison with other types in equality judgment | Impossible | Yes |
Ordering to classes | Comparable (Total order in the narrow sense) |
Comparable<T> (Total order in the narrow sense) |
Consistency of ordering to classes and equality judgment | Always there | Recommendation |
Comparison with null in ordering to classes | Impossible | Exception throw recommended |
Comparison with other types in ordering to classes | Impossible | Yes |
Ordering to collection | areInIncreasingOrder (Weak ordering in a narrow sense) |
Comparator<T> (Total order in the narrow sense) |
Consistency of ordering and equality determination to collections | No mention | Recommendation |
Comparison with null in ordering to collection | Optional<T> Yes |
Possible depending on the option |
Comparison with other types in ordering to collection | Impossible | Impossible |
Judgment by assert method | NeedEquatable |
equals use |
The assert method is different between Swift and Objective-C.
When I line it up again,
func XCTAssertEqual<T>(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, _ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) where T : Equatable
func XCTAssertEqual<T>(_ expression1: @autoclosure () throws -> T?, _ expression2: @autoclosure () throws -> T?, _ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) where T : Equatable
XCTAssertEqual(expression1, expression2, ...)
XCTAssertEqualObjects(expression1, expression2, ...)
static public void assertEquals(String message, float expected, float actual, float delta)
static public void assertEquals(String message, Object expected, Object actual)
ObjC is closer to Java in this regard.
About strict total ordering in the narrow sense and strict weak ordering in the narrow sense. It's a term somewhere.
What is strict and total weak is
Swift's Comparable
is
"Non-reflexive"
"Do not allow incomparability" (FloatingPoint.nan
etc. are not applicable)
Therefore, it is strict total ordering.
on the other hand
ʻAreInIncreasingOrder` "Non-reflexive" "Tolerate incomparability" "Impossible to transition" Therefore, it is strict weak ordering.
__maybe. __
By the way, Java's Comparable <T>
and Comparator <T>
both call themselves total ordering in the class description and mention non-reflective in the method description. In other words, it can be read that strict total ordering is assumed.
For incomparability, see the next item.
Let's try
The story is appropriate.
Takashi-kun got a lot of balls from his brother.
struct Ball {
let diameter: Int
let color: Color
enum Color {
case blue
case red
case yellow
}
}
class Ball {
int diameter;
Color color;
public Ball(int diameter, Color color) {
this.diameter = diameter;
this.color = color;
}
enum Color {
BLUE, RED, YERROW
}
}
The size and color of the balls vary. Is there the same ball? I think that.
extension Ball: Equatable {}
///Satisfy reflectivity, symmetry and transitivity
///All visible things have been woven
/// (a != b)When!(a == b)Results match
func ==(lhs: Ball, rhs: Ball) -> Bool {
return lhs.diameter == rhs.diameter && lhs.color == rhs.color
}
/**
*Satisfy reflectivity, symmetry, transitivity and consistency
*hashCode has also been overridden
*Returns false if null
*/
@Override
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof Ball) {
Ball anotherBall = (Ball)anObject;
return this.diameter == anotherBall.diameter && this.color == anotherBall.color;
}
return false;
}
/**
*Returns the same integer if diameter and color are unchanged
* `equals`Returns the same integer if they are equal
* `equals`Returns different integers if they are different
*/
@Override
public int hashCode() {
return Objects.hash(this.diameter, this.color);
}
Even so, I don't know what kind of balls there are because there are too many. In order to find the ball to play catch next time, I decided to arrange them in order of size.
let balls: [Ball] = [/*Lots!*/]
balls.sort {
return $0.diameter < $1.diameter
}
List<Ball> list = Arrays.asList(/*Lots!*/);
//I'm not going to allow nulls
list.sort((Ball o1, Ball o2) ->
o1.diameter - o2.diameter
);
Takashi-kun tolerated incomparability. I didn't decide how to arrange the colors, so I couldn't decide how to arrange the balls of the same size.
Considering (10, blue) and (10, red), (10, blue) <
(10, red) is false and (10, blue) >
(10, red) is also false. Since both <
and >
are false, (10, blue) ==
(10, red) holds. In other words, balls of the same size and color __difference __ are __same __. This is a story that does not occur if incomparability is not tolerated.
The arrangement that focuses only on this size
Is strict weak ordering. Swift's ʻareInIncreasingOrderrequest is strict weak ordering, so it's okay. However, Java's
Comparator does not fit because it calls itself total ordering. Not only that, but focusing only on size means that the results of ʻe1.compareTo (e2) == 0
and ʻe1.equals (e2)do not match for balls of the same size. To do. That is, there is inconsistency between the
Comparator and ʻequals
methods.
I was happy to line up the balls, but my mom got angry and I had to clean up. I decided to match the size and color because it was a big deal.
extension Ball: Comparable {}
///Satisfy non-reflective, asymmetric and transitive
func < (lhs: Ball, rhs: Ball) -> Bool {
if lhs.diameter == rhs.diameter {
return lhs.color.hashValue < rhs.color.hashValue
} else {
return lhs.diameter < rhs.diameter
}
}
/**
*Satisfy non-reflective, asymmetric and transitive
* `equals`Consistent with
* `o`Throws a nullpo when is null
*/
@Override
public int compareTo(Ball o) {
if (this.diameter == o.diameter) {
return this.color.hashCode() - o.color.hashCode();
} else{
return this.diameter - o.diameter;
}
}
Takashi-kun came to line up by looking at the colors. This establishes consistency with ʻequals. Of course, it is also consistent with ʻEquatable
.
As a result of properly deciding the order of colors, the requirement of strict total ordering is also satisfied. This will satisfy the request from either Comparable``
Comparable
Java jdk8/jdk8/jdk: 687fd7c7986d /src/share/classes/ java.lang (Java Platform SE 8) java.util (Java Platform SE 8) Assert (JUnit API)
Apple Swift Standard Library | Apple Developer Documentation apple/swift: The Swift Programming Language [swift-evolution] [Draft][Proposal] Formalized Ordering
order 2004-05-29 - Cry’s Diary algorithm --cpprefjp C ++ Japanese Reference
Recommended Posts