Same judgment / equal value judgment / comparison / order in Swift and Java

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

Same judgment

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.

Equal value judgment

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.

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)
  }
}

Treatment of nil

ʻ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)

Handling of hash values

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.

Comparison with other types

Limited to comparisons of the same type by Self.

Ordering to type

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.

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.

Consistency with equality judgment

Yes. Comparable inherits from ʻEquatable`. Only when there is an equal sign can we talk about the inequality sign.

Treatment of nil

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)

Comparison with other types

Limited to comparisons of the same type by Self.

Ordering to ʻArray`

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.

Consistency with equality judgment

No limit. It depends on the implementation.

Treatment of nil

No limit. If implemented, ʻArray <Optional > `can be sorted.

Comparison with other types

Limited to comparisons of the same type by (Element, Element).

Judgment by assert method

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

Same judgment

Use ==.

Equal value judgment

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

There is.

Handling of null

It is supposed to compare non-null objects. If the other party is null, it will be false.

Handling of hash values

When overriding the ʻequalsmethod, you also need to override thepublic int hashCode (), which is also derived from ʻObject, and take care of it together. This is because hashCode has the following rules regarding ʻequals`.

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;
}

Comparison with other types

You can use ʻequals because all classes inherit from ʻObject.

Ordering to classes

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.

Consistency with equality judgment

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.

Handling of null

Null is not an instance of any object, so an exception should be thrown.

Comparison with other types

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.

Ordering to collection

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.

Consistency with equality judgment

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.

Handling of null

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.

Comparison with other types

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
}

Judgment by assert method

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.

The difference between Swift and Java at a glance

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 HashableRequired 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 equalsuse

bonus

Objective-C assert method

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.

Order rules

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'sComparator 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 theComparator 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 `.

References

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

Same judgment / equal value judgment / comparison / order in Swift and Java
[Java / Swift] Comparison of Java Interface and Swift Protocol
Equal value judgment of Integer and int
Java and Swift comparison (1) Source control / Scope / Variables
Java and Swift comparison (3) Class implementation / Class inheritance / Class design
StringUtils.isNotBlank is convenient for empty judgment and null judgment in java
Sort List in descending order in Java and generate a new List non-destructively
[Java] Get the dates of the past Monday and Sunday in order
Java Two Lists are ignored in order, null is ignored, and duplicates are ignored.
StringBuffer and StringBuilder Class in Java
Java pass by value and pass by reference
[Java] Judgment of identity and equivalence
Understanding equals and hashCode in Java
Hello world in Java and Gradle
Java and Swift comparison (2) Basic type / arithmetic expression / control syntax / function definition
This and that of the implementation of date judgment within the period in Java
Comparison of thread implementation methods in Java and lambda expression description method
How to create your own annotation in Java and get the value
Member description order in Java coding convention
Regarding String type equivalence comparison in Java
Difference between final and Immutable in Java
Reverse Key from Value in Java Map
[Java] for Each and sorted in Lambda
A Java engineer compared Swift, Kotlin, and Java.
[Java] Collection and StringBuilder operation method comparison
All same hash code string in Java
Arrylist and linked list difference in java
Program PDF headers and footers in Java
Learn Flyweight patterns and ConcurrentHashMap in Java
Java Direction in C ++ Design and Evolution
Java to C and C to Java in Android Studio
Reading and writing gzip files in Java
Difference between int and Integer in Java
Discrimination of Enums in Java 7 and above
Hex and UIColor mutual conversion in Swift