== vs .equals(), and the String constant pool
== on objects compares reference identity. .equals() compares logical equality (as defined by
the class). String literals are special: the JVM interns them in a shared constant pool, so two identical
literals ARE the same reference — but new String(...) deliberately creates a distinct heap object even
if the contents match. .intern() looks a string up in (or adds it to) the pool.
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println("s1 == s2 (both literals, pooled): " + (s1 == s2));
System.out.println("s1 == s3 (s3 is `new String(...)`): " + (s1 == s3));
System.out.println("s1.equals(s3): " + s1.equals(s3));
System.out.println("s1 == s3.intern(): " + (s1 == s3.intern()));