Matheion

MA-JAVA0 · Module 14

String parsing and Objects.equals

Topics: split · parseInt · Validating constructors · Objects.equals

Overview

Session 14 — String parsing and Objects.equals

Once text sits in a String, you often need to split it, trim whitespace, validate token counts, and call Integer.parseInt (or siblings) to rebuild typed data. Mistakes here are the bread and butter of homework bugs—this session walks a disciplined parsing recipe on a toy “inventory ledger” format.

You will also meet Objects.equals so equality checks stay null-safe without scattering defensive if ladders everywhere, and see how validating constructors keep parsed data well-formed.

Time: about 90 minutes.

Concepts

1. String.split and delimiters

String.split(String regex) returns a String[]. The argument is a regular expression, not a raw character—. means “any character,” so split on literal dots requires "\\.". For comma-separated homework lines, a simple comma often suffices: line.split(",").

Always consider how many parts you expect:

String[] parts = line.split("\\|", -1); // limit -1 keeps trailing empties

If a line should have exactly three fields, assert parts.length == 3 before reading parts[2].


2. trim before validation

User-typed files include leading spaces. Call trim() on the whole line or on each token before isEmpty checks and numeric parsing—otherwise " 7 " fails parseInt even though humans see a number.


3. Integer.parseInt and friends

Integer.parseInt(String s) throws NumberFormatException on bad input—unchecked, but still worth catching at a boundary if you want to attach line numbers:

int qty = Integer.parseInt(parts[1].trim());

Prefer Integer.parseInt over new Integer(...) (deprecated constructor story) in modern Java.


4. Validation belongs in the constructor

When a parsed type must enforce invariants, put the checks in a public constructor rather than hiding construction behind a static factory method:

public final class Sku {
    private final String code;

    public Sku(String raw) {
        String c = raw.trim().toUpperCase();
        if (!c.matches("[A-Z0-9]{3,8}")) {
            throw new IllegalArgumentException("bad sku: " + raw);
        }
        this.code = c;
    }

    public String code() {
        return code;
    }
}

Callers write new Sku(line) and the constructor guarantees every instance is well-formed. There is no static method pretending to be a constructor — Java constructs objects with new and nothing else.


5. Objects.equals for null-safe equality

Objects.equals(a, b) returns true if both references are null, false if exactly one is null, otherwise delegates to a.equals(b). Use it in equals implementations for fields that may be null:

return Objects.equals(this.tag, other.tag);

This removes repetitive null guards and reads clearly in hashCode/equals pairs you will generate later.


6. End-to-end parsing recipe

  1. Read line.
  2. trim. Skip if empty or comment.
  3. split with correct regex and limit.
  4. Check length.
  5. Parse primitives with try/catch or small helpers that wrap exceptions.
  6. Build immutable value objects with validating constructors.

7. Pitfalls

  • Off-by-one on split when consecutive delimiters produce empty strings—decide whether empties are legal.
  • Using == for String content equality—always .equals or Objects.equals.
  • Letting parseInt run on strings with thousands separators or decimals without normalising first.

Examples

Example A — pipe-separated ledger line

Assume each non-comment line looks like SKU|QTY|NOTE where QTY is a non-negative int and NOTE may contain spaces (but no |). Parse in main, then construct the object with new:

public class LedgerLine {
    public final String sku;
    public final int qty;
    public final String note;

    public LedgerLine(String sku, int qty, String note) {
        this.sku = sku;
        this.qty = qty;
        this.note = note;
    }
}
public class LedgerDemo {
    public static void main(String[] args) {
        String line = "SKU-12 | 3 | two boxes";
        String[] p = line.trim().split("\\|", 3);
        if (p.length != 3) {
            throw new IllegalArgumentException("expected 3 fields: " + line);
        }
        int q = Integer.parseInt(p[1].trim());
        if (q < 0) {
            throw new IllegalArgumentException("qty must be >= 0");
        }
        LedgerLine entry = new LedgerLine(p[0].trim(), q, p[2].trim());
        System.out.println(entry.sku + " x" + entry.qty + " — " + entry.note);
    }
}

Limit 3 on split preserves pipes inside NOTE if you later relax the format—here it still caps fields at three chunks.


Example B — comparing nullable tags with Objects.equals

import java.util.Objects;

public class Crate {
    private final String id;
    private final String tag; // may be null meaning "untagged"

    public Crate(String id, String tag) {
        this.id = Objects.requireNonNull(id);
        this.tag = tag;
    }

    public boolean sameTagAs(Crate other) {
        return Objects.equals(this.tag, other.tag);
    }
}

sameTagAs returns true when both tags are null—often the behaviour you want for optional fields.

Practice check — string parsing and validating constructors

Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.

  1. 1.What does `"a,b,c".split(",")` roughly produce?

  2. 2.`Integer.parseInt("42")` returns…

  3. 3.Why prefer `Objects.equals(a, b)` for possibly-null references?

  4. 4.Why is a validating constructor better than letting callers pass any raw string to a parsed type?

  5. 5.If `parseInt` receives `"3.14"`, what happens?