JavaRush/Courses/Kotlin SELF/Comparing arrays: why == doesn’t work and when you need c...

Comparing arrays: why == doesn’t work and when you need contentDeepEquals

Open

1. Why arrays are compared differently

When you first run into arrays, you want to think of them as “a list, just with a fixed size.” And that’s partly true. But arrays have a personality: they’re a bit closer to the “metal,” they’re often used as a compact container, and because of that in Kotlin (and on the JVM in general) they aren’t compared the same way as List.

Let’s imagine we’re evolving our console expense-tracking app (the one where we store expenses as “category–amount” pairs, sort them, take top-N, and build reports). Sometimes it’s convenient to keep, for example, amounts by day of the week in an IntArray—exactly 7 numbers: Mon…Sun. And then a practical question comes up: “Do two weekly snapshots match?”, “Did the data change after recalculation?”, “Did I break something in the logic?”.

And this is where the surprise hits: you write “okay, I’ll just compare with ==” — and you get a result that feels like it’s mocking you.

Why == doesn’t compare arrays element-by-element

Briefly and to the point: == for arrays doesn’t do what you expect. In Kotlin, == means structural equality and calls equals() (with safe handling of null). But for arrays on the JVM, historically equals() behaves like “compare containers,” not “compare contents.” And Kotlin explicitly warns you: for arrays, don’t use ==/!= if you want to compare elements.

Intuitively, it’s like comparing two identical pizza boxes, but instead of asking “is the topping the same?” you ask “is this the exact same box from my fridge?”. In most cases the answer will be “no,” even if the inside is equally delicious.

Mini example: the == trap

fun main() {
    val a = arrayOf(1, 2, 3)
    val b = arrayOf(1, 2, 3)

    println(a == b)   // false (expected true? welcome)
    println(a === b)  // false (these are definitely different arrays)
}

Why is that? Because a and b are two different arrays. == in this case does not do an element-by-element comparison, and essentially does not say “equal by content.”

At the same time, for many other types == really does compare “by meaning,” and Kotlin clearly separates structural (==) and referential (===) equality. Arrays are just a special zone.

Comparison map: ==, ===, contentEquals, contentDeepEquals

So you don’t have to keep everything in your head as a mess, it’s handy to have a small table—a road sign that says “careful, arrays.”

What you write What it means Works for arrays? When to use
a == b
structural equality (equals) for arrays: not for comparing elements almost always for “regular” types and collections (List, Map)
a === b
referential equality (the exact same object) yes, but it’s not about elements rare checks like “is this the same container?”
a.contentEquals(b)
element-by-element comparison of a flat array yes when the array is 1D: IntArray, Array<String>, …
a.contentDeepEquals(b)
“deep” comparison for nested arrays yes when arrays contain other arrays

The main takeaway of this lecture: if you want to compare arrays by their data, use contentEquals/contentDeepEquals.

2. contentEquals() for flat arrays

When an array is flat (meaning its elements are not arrays), you almost always want contentEquals().

Example 1: Array<Int>

fun main() {
    val a = arrayOf(1, 2, 3)
    val b = arrayOf(1, 2, 3)

    println(a.contentEquals(b)) // true
}

Now this looks like a human conversation: “yes, the elements are the same and in the same order.”

Example 2: IntArray

For our “expense tracking by day” tasks, it’s more logical to use IntArray (it’s a primitive array on the JVM, so it’s more efficient). Kotlin provides types like IntArray, DoubleArray, etc.

fun main() {
    val week1 = intArrayOf(100, 0, 250, 0, 90, 500, 10)
    val week2 = intArrayOf(100, 0, 250, 0, 90, 500, 10)

    println(week1.contentEquals(week2)) // true
}

Important nuance: order matters

Just like with lists, content comparison takes order into account. That’s logical: an array is a sequence. If you swap days around, that’s already a different value.

fun main() {
    val a = intArrayOf(1, 2, 3)
    val b = intArrayOf(3, 2, 1)

    println(a.contentEquals(b)) // false
}

What this looks like in our app

Suppose you wrote a function that builds weekly expense totals by day (we’re not building class-based architecture yet—we’re just working with simple types and collections). And you want to check: “Did the recalculation produce the same result as the previous algorithm version?”

fun main() {
    val oldCalc = intArrayOf(120, 0, 50, 80, 0, 0, 10)
    val newCalc = intArrayOf(120, 0, 50, 80, 0, 0, 10)

    val same = oldCalc.contentEquals(newCalc)
    println("Did the weekly totals match? $same") // Did the weekly totals match? true
}

This is a simple but very real “sanity test”: if you’re refactoring code and afraid of breaking logic, comparing arrays by content gives you a quick signal.

3. contentDeepEquals() for nested arrays

As soon as an array contains other arrays, a regular contentEquals() is no longer enough. It will compare the top level, but nested elements will be compared as separate container objects. And the same problem resurfaces: “different containers, even though the inside is identical.”

That’s exactly why Kotlin provides contentDeepEquals().

Example: a “2D table” of expenses

Let’s imagine we wanted to store expenses as a “week × categories” table: 7 rows (days), and in each row, say, 3 numbers (food, transport, other). This can be represented as a nested array.

fun main() {
    val a = arrayOf(
        intArrayOf(100, 20, 0),
        intArrayOf(0, 0, 10)
    )
    val b = arrayOf(
        intArrayOf(100, 20, 0),
        intArrayOf(0, 0, 10)
    )

    println(a.contentEquals(b))     // false
    println(a.contentDeepEquals(b)) // true
}

Why does contentEquals return false? Because it sees the elements of the top-level array, and those elements are IntArray—i.e., separate arrays. They differ by reference. So contentEquals at the top says: “the first element is not equal to the first element,” and that’s it.

But contentDeepEquals “goes inside” and compares the contents of nested arrays recursively, so we get true.

A small diagram: why you need deep

flowchart TD
    A[Top-level Array] --> B1[Element 0: IntArray]
    A --> B2[Element 1: IntArray]

    C[Another top-level Array] --> D1[Element 0: IntArray]
    C --> D2[Element 1: IntArray]

    B1 -. contentEquals compares as an object .- D1
    B2 -. contentEquals compares as an object .- D2

    B1 --> E1[100, 20, 0]
    D1 --> F1[100, 20, 0]

The idea: contentEquals at the top level does not “unwrap” nested arrays by default, so you need contentDeepEquals, which does it explicitly.

4. Snapshots and aliasing: the “before/after” trap

Even if you use contentEquals, you can run into another nasty issue: you compare an array “before” and “after,” but in reality you don’t have two arrays—you have the same array under two different names.

It’s the same story we discussed for MutableList: assigning a reference value creates an alias (a second name). That’s also relevant for arrays.

Example: two names, one array

fun main() {
    val week = intArrayOf(10, 20, 30)
    val alias = week

    alias[0] = 999

    println(week.joinToString())  // 999, 20, 30
    println(week === alias)       // true
}

Comparing “before/after” is pointless here, because you no longer have a “before.”

Solution: make a copy if you need a snapshot

If you want to preserve the old state of an array, make a copy (for example, copyOf()—you’ve already seen it in the arrays and fixed-size topic).

fun main() {
    val week = intArrayOf(10, 20, 30)
    val snapshot = week.copyOf()

    week[0] = 999

    println(snapshot.joinToString())          // 10, 20, 30
    println(snapshot.contentEquals(week))     // false
}

And now this is a real “before/after”: one array kept the past state, the other changed.

In the context of our app, this is genuinely useful: for example, you recalculate weekly totals and want to compare whether the result changed. A “snapshot” helps you honestly see what happened.

5. Mini utilities for the project

In the learning project, we try to write code so that main() doesn’t turn into a spaghetti mess. So it’s useful to wrap array comparisons into small functions. This also makes the code more readable: instead of “why is contentDeepEquals happening here?” you’ll have “compare week matrices.”

Function for a flat array

fun sameWeeklyTotals(a: IntArray, b: IntArray): Boolean {
    return a.contentEquals(b)
}

fun main() {
    val x = intArrayOf(1, 2, 3)
    val y = intArrayOf(1, 2, 3)

    println(sameWeeklyTotals(x, y)) // true
}

A super-simple function, but it wins in “words”: you immediately read the intent.

Function for a “table”

fun sameWeekMatrix(a: Array<IntArray>, b: Array<IntArray>): Boolean {
    return a.contentDeepEquals(b)
}

fun main() {
    val a = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
    val b = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))

    println(sameWeekMatrix(a, b)) // true
}

Notice how nicely it reads: sameWeekMatrix immediately hints that the structure is nested and the comparison will be “deep.”

6. Diagnostics: how to print arrays

When you compare arrays, the next thought is often: “Okay, they’re not equal. But where exactly do they differ?” And this is where printing arrays becomes part of debugging.

For flat arrays you can use joinToString(), and for nested ones—“deep” representations like contentDeepToString().

Flat array

fun main() {
    val a = intArrayOf(1, 2, 3)
    val b = intArrayOf(1, 999, 3)

    println("a = ${a.joinToString()}") // a = 1, 2, 3
    println("b = ${b.joinToString()}") // b = 1, 999, 3
}

Nested array

fun main() {
    val a = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
    val b = arrayOf(intArrayOf(1, 2), intArrayOf(3, 999))

    println(a.contentDeepToString()) // [[1, 2], [3, 4]]
    println(b.contentDeepToString()) // [[1, 2], [3, 999]]
}

These prints are especially useful when you build reports/tables and then compare results after sorting or recalculation.

7. Common mistakes when comparing arrays

Mistake #1: using == “out of habit” and trusting the result.
Lists (List) are often truly compared “by content” via ==, so your hand automatically writes the same for arrays. But arrays are the exception: Kotlin explicitly warns not to use ==/!= to compare array elements. If you want to compare elements, your default choice is contentEquals().

Mistake #2: using contentEquals for nested arrays and being surprised by false.
When an array contains arrays, contentEquals compares top-level elements as separate objects. As a result, two 2D arrays that are identical “by data” look different. For such structures you need contentDeepEquals(), which goes inside and compares nested contents.

Mistake #3: comparing “before/after” without preserving “before.”
Sometimes you think you have two arrays, but in reality you have one and the same structure under two names. Then any “after” change instantly changes “before” as well, and the comparison becomes absurd theater. If you need a snapshot, make a copy (for example, copyOf()) and only then compare.

Mistake #4: confusing “is it the same container?” with “is the data the same?”
=== can be useful, but it’s not a replacement for contentEquals. === answers “is it the same object?” (for example, you accidentally passed an array somewhere and want to see whether it’s that exact instance). But “the data matches” is contentEquals/contentDeepEquals.

Mistake #5: checking only that it’s false, but not being able to quickly see the difference.
When comparison fails, the next step is printing. For flat arrays, joinToString() works well; for nested ones, use contentDeepToString(). This speeds up debugging drastically, because you stop guessing “where exactly did it not match?” and start seeing specific numbers/strings.

1
Task
Kotlin SELF,  23 level,  4 lecture
Locked
Three array checks
Three array checks
1
Task
Kotlin SELF,  23 level,  4 lecture
Locked
Weekly summary
Weekly summary
1
Task
Kotlin SELF,  23 level,  4 lecture
Locked
Two tables: comparison
Two tables: comparison
Комментарии
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны sign in
This page has no comments yet