Java · lesson 9 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Strings in Java
Learn Java strings by running them: immutability, == vs equals, the string pool, core methods, formatting, text blocks, and StringBuilder.
By SkillByExample · Updated
String is the most-used class in Java. Two facts explain most string bugs: strings are
immutable, and == compares objects, not text. Every example is a single file run with
the JDK 21 source launcher (java File.java).
Immutability
public class ImmutableDemo {
public static void main(String[] args) {
String s = "hello";
s.toUpperCase(); // result thrown away
System.out.println(s);
s = s.toUpperCase(); // capture the new String
System.out.println(s);
String original = "data";
String alias = original;
original = original + "-changed";
System.out.println(alias + " / " + original);
}
}
$ java ImmutableDemo.java
hello
HELLO
data / data-changed
toUpperCase returns a new string and leaves s alone, so the first call does nothing useful.
Concatenation also builds a new object: alias still points at the old "data", which no code
can change. That is why strings are safe to share and to use as map keys.
== versus equals and the string pool
public class EqualityDemo {
public static void main(String[] args) {
String a = "java";
String b = "java";
String c = new String("java");
String d = "ja" + "va"; // constant, folded at compile time
String part = "ja";
String e = part + "va"; // built at runtime
System.out.println((a == b) + " " + (a == c) + " " + (a == d) + " " + (a == e));
System.out.println(a.equals(c) + " " + a.equals(e));
System.out.println(a == e.intern());
System.out.println(a.equalsIgnoreCase("JAVA"));
}
}
$ java EqualityDemo.java
true false true false
true true
true
true
Identical literals — including "ja" + "va", which the compiler folds into one constant — share a
single pooled object, so == happens to be true. new String and strings built at runtime are
separate objects, so == is false even though the text matches. intern() returns the pooled
copy. Compare content with equals, every time.
Core methods
import java.util.Arrays;
public class MethodsDemo {
public static void main(String[] args) {
String s = " Hello, Java World! ";
System.out.println(s.length() + " " + s.strip().length());
System.out.println("charAt(7)='" + s.charAt(7) + "' indexOf(Java)=" + s.indexOf("Java") + " lastIndexOf(o)=" + s.lastIndexOf("o"));
System.out.println("[" + s.substring(9, 13) + "]");
System.out.println(s.contains("Java") + " " + s.strip().startsWith("Hello") + " " + s.isBlank() + " " + " ".isBlank());
System.out.println("[" + s.replace("Java", "Python") + "]");
System.out.println(s.strip().toLowerCase().replace(" ", "_"));
System.out.println(Arrays.toString("a,b,,c".split(",")));
System.out.println(String.join(" | ", "Alice", "Bob", "Charlie"));
System.out.println("ab".repeat(3) + " " + "x".compareTo("y") + " " + "apple".compareTo("apple"));
}
}
$ java MethodsDemo.java
22 18
charAt(7)=',' indexOf(Java)=9 lastIndexOf(o)=15
[Java]
true true false true
[ Hello, Python World! ]
hello,_java_world!
[a, b, , c]
Alice | Bob | Charlie
ababab -1 0
Indexes count the two leading spaces, so charAt(7) is the comma and "Java" starts at index 9;
substring(9, 13) takes indexes 9–12 (the end is exclusive). isBlank is true for whitespace-only
strings, unlike isEmpty. compareTo returns a negative, zero or positive number — only the sign
is meaningful — and is what sorting uses.
Formatting and text blocks
public class FormatDemo {
public static void main(String[] args) {
String msg = String.format("Name: %s, Age: %d, Score: %.2f", "Alice", 25, 98.456);
System.out.println(msg);
System.out.printf("[%-8s][%5d][%05d][%,d][%x][%b]%n", "Bob", 42, 42, 1234567, 255, true);
System.out.println("%s scored %d".formatted("Cara", 88));
String json = """
{
"name": "Alice",
"active": true
}""";
System.out.println(json);
System.out.println(json.lines().count() + " lines");
}
}
$ java FormatDemo.java
Name: Alice, Age: 25, Score: 98.46
[Bob ][ 42][00042][1,234,567][ff][true]
Cara scored 88
{
"name": "Alice",
"active": true
}
4 lines
%-8s left-aligns in an 8-wide field, %5d right-aligns, %05d pads with zeros, and %,d adds
grouping separators. A text block (""", Java 15+) strips the common indentation of its lines,
so the JSON starts at column 0 even though the source is indented; closing the quotes on the last
line avoids a trailing newline.
StringBuilder
public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
sb.append(", World");
System.out.println(sb);
sb.insert(5, " there");
System.out.println(sb);
sb.delete(5, 11);
System.out.println(sb);
sb.replace(7, 12, "Java");
System.out.println(sb + " length=" + sb.length());
System.out.println(sb.reverse());
System.out.println(sb);
String csv = new StringBuilder().append("Alice").append(',').append(30).append(',').append(true).toString();
System.out.println(csv);
}
}
$ java StringBuilderDemo.java
Hello, World
Hello there, World
Hello, World
Hello, Java length=11
avaJ ,olleH
avaJ ,olleH
Alice,30,true
Unlike String, a StringBuilder changes in place: every mutating method edits the same buffer
and returns the builder itself, which is what makes chaining work. Note that reverse() mutated
sb — the line after it prints the reversed text again, not the original.
Why StringBuilder in loops
public class LoopConcat {
public static void main(String[] args) {
int n = 20_000;
long start = System.nanoTime();
String s = "";
for (int i = 0; i < n; i++) s += i % 10;
long plusMs = (System.nanoTime() - start) / 1_000_000;
start = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) sb.append(i % 10);
String built = sb.toString();
long builderMs = (System.nanoTime() - start) / 1_000_000;
System.out.println("same result: " + s.equals(built) + ", length " + built.length());
System.out.println("+= at least 5x slower: " + (plusMs > 5 * Math.max(builderMs, 1)));
}
}
$ java LoopConcat.java
same result: true, length 20000
+= at least 5x slower: true
Each s += ... copies the whole string so far into a new object, so the loop does roughly n²/2
character copies; the builder appends into one growing buffer. Exact timings depend on the
machine, so the program prints the comparison rather than milliseconds. Outside loops, plain +
is fine — the compiler already optimises a single expression.
Putting it together: a text analyzer
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class TextAnalyzer {
public static void main(String[] args) {
String text = "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.";
String[] words = text.toLowerCase().split("[^a-z]+");
System.out.println("words: " + words.length);
System.out.println("letters: " + text.chars().filter(Character::isLetter).count());
System.out.println("sentences: " + text.split("[.!?]\\s*").length);
System.out.println("vowels: " + text.chars().filter(c -> "aeiouAEIOU".indexOf(c) >= 0).count());
Map<String, Integer> freq = new TreeMap<>();
for (String w : words) freq.merge(w, 1, Integer::sum);
Map.Entry<String, Integer> top = freq.entrySet().stream()
.max(Map.Entry.<String, Integer>comparingByValue()
.thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder())))
.orElseThrow();
System.out.println("top word: " + top.getKey() + " (" + top.getValue() + "x)");
}
}
$ java TextAnalyzer.java
words: 17
letters: 67
sentences: 2
vowels: 22
top word: the (2x)
Splitting on [^a-z]+ treats any run of non-letters as one separator, so "dog. Pack" yields two
clean words. Here no empty strings appear because the text starts with a letter — a leading
separator would produce an empty first element. The tie-breaker makes the top word deterministic:
highest count, then alphabetically first.
Practice
- Write
isPalindrome(String)that ignores case, spaces and punctuation, usingStringBuilder.reverse(), and test one palindrome and one non-palindrome.
Solution
public class Palindrome {
static boolean isPalindrome(String s) {
String clean = s.toLowerCase().replaceAll("[^a-z0-9]", "");
return clean.equals(new StringBuilder(clean).reverse().toString());
}
public static void main(String[] args) {
System.out.println(isPalindrome("A man, a plan, a canal: Panama"));
System.out.println(isPalindrome("Hello, World"));
}
}
$ java Palindrome.java
true
false
- Capitalise the first letter of every word in
"the quick brown fox"while keeping the double space.
Solution
public class TitleCase {
public static void main(String[] args) {
String input = "the quick brown fox";
StringBuilder sb = new StringBuilder(input.length());
boolean startOfWord = true;
for (char c : input.toCharArray()) {
sb.append(startOfWord ? Character.toUpperCase(c) : c);
startOfWord = c == ' ';
}
System.out.println("[" + sb + "]");
}
}
$ java TitleCase.java
[The Quick Brown Fox]
Walking characters keeps the original spacing; split(" ") followed by join would also work but
needs care with the empty token between the two spaces.
- Count the vowels and consonants in a sentence, ignoring digits, spaces and punctuation.
Solution
public class VowelCount {
public static void main(String[] args) {
String s = "Java 21 is here!";
long vowels = s.toLowerCase().chars().filter(c -> "aeiou".indexOf(c) >= 0).count();
long letters = s.chars().filter(Character::isLetter).count();
System.out.println("vowels=" + vowels + " consonants=" + (letters - vowels));
}
}
$ java VowelCount.java
vowels=5 consonants=5
- Show the difference between
trim()andstrip()on a string padded with the Unicode em space.
Solution
public class StripVsTrim {
public static void main(String[] args) {
String s = " padded ";
System.out.println("trim: [" + s.trim() + "] length " + s.trim().length());
System.out.println("strip: [" + s.strip() + "] length " + s.strip().length());
}
}
$ java StripVsTrim.java
trim: [ padded ] length 10
strip: [padded] length 6
trim() only removes characters up to U+0020, so the em spaces survive; strip() understands
Unicode whitespace.