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
- Read line.
trim. Skip if empty or comment.splitwith correct regex and limit.- Check
length. - Parse primitives with try/catch or small helpers that wrap exceptions.
- Build immutable value objects with validating constructors.
7. Pitfalls
- Off-by-one on
splitwhen consecutive delimiters produce empty strings—decide whether empties are legal. - Using
==forStringcontent equality—always.equalsorObjects.equals. - Letting
parseIntrun 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.What does `"a,b,c".split(",")` roughly produce?
2.`Integer.parseInt("42")` returns…
3.Why prefer `Objects.equals(a, b)` for possibly-null references?
4.Why is a validating constructor better than letting callers pass any raw string to a parsed type?
5.If `parseInt` receives `"3.14"`, what happens?