Strings and Math Methods
Dot notation, immutability, and the two workhorse classes you'll call in almost every program
Your programs spend most of their time moving text around — reading it from the user, comparing it, reformatting it, and printing it back. They also do arithmetic with numbers that don’t behave quite like the ones in your algebra class: floating-point has roundoff, and you need a library of useful math operations. Java hands you two tools for these jobs — the String class (for text) and the Math class (for numbers). This lesson is about how to use them.
In a nutshell
A String is an object — think of it as a small container that holds some data (the text itself) and knows how to do things with that data (the methods). You interact with an object through dot notation: s.length() reads as “ask the String s for its length.” Strings are immutable: every method that looks like it changes a String actually returns a brand-new one. If you do not catch the return value, the change is silently discarded.
Math is a class of static methods. “Static” here means the methods do not belong to any particular object — they belong to the class itself. So you call them directly on the class name: Math.sqrt(25). No new Math(), ever. Think of Math as a library of free-standing utilities. Each method returns a value; it does not print.
When you are done with this lesson you will be able to call the core String methods (length, charAt, substring, toUpperCase, toLowerCase, equals, equalsIgnoreCase, contains), explain why s.toUpperCase() on its own does nothing, call the common Math methods (abs, sqrt, pow, min, max, round), and compare two doubles safely using Math.abs(a - b) < EPSILON.
Lab 5 (Chat With a Bot) uses every method in that list.
Quick reference
String methods
All examples assume String s = "Building Java Programs";.
| Call | Returns | Example | Result |
|---|---|---|---|
s.length() |
int |
s.length() |
22 |
s.charAt(int i) |
char |
s.charAt(0) |
'B' |
s.substring(int from) |
new String |
s.substring(9) |
"Java Programs" |
s.substring(int from, int to) |
new String |
s.substring(9, 13) |
"Java" |
s.toUpperCase() |
new String |
s.toUpperCase() |
"BUILDING JAVA PROGRAMS" |
s.toLowerCase() |
new String |
s.toLowerCase() |
"building java programs" |
s.equals(Object o) |
boolean |
s.equals("Building Java Programs") |
true |
s.equalsIgnoreCase(String o) |
boolean |
s.equalsIgnoreCase("BUILDING JAVA PROGRAMS") |
true |
s.contains(String sub) |
boolean |
s.contains("Java") |
true |
s.indexOf(String sub) |
int (-1 if absent) |
s.indexOf("Java") |
9 |
s.startsWith(String p) |
boolean |
s.startsWith("Build") |
true |
s.endsWith(String p) |
boolean |
s.endsWith("s") |
true |
s.trim() |
new String |
" hi ".trim() |
"hi" |
Rules of thumb:
- Indexes start at
0. The last valid index islength() - 1. substring(from, to)is half-open:fromis included,tois excluded. Result length isto - from.equalsis case-sensitive;equalsIgnoreCaseis not.- Every “modifying” method returns a new String. Assign it back if you want the change to stick.
Math methods
| Call | Returns | Example | Result |
|---|---|---|---|
Math.abs(x) |
same type as x |
Math.abs(-7) |
7 |
Math.sqrt(double) |
double |
Math.sqrt(16) |
4.0 |
Math.pow(double b, double e) |
double |
Math.pow(2, 10) |
1024.0 |
Math.min(a, b) |
same type as args | Math.min(3, 9) |
3 |
Math.max(a, b) |
same type as args | Math.max(3, 9) |
9 |
Math.round(double) |
long |
Math.round(3.7) |
4L |
Math.floor(double) |
double |
Math.floor(3.7) |
3.0 |
Math.ceil(double) |
double |
Math.ceil(3.2) |
4.0 |
Math.random() |
double in [0, 1) |
Math.random() |
e.g. 0.42917... |
Math.PI (constant) |
double |
Math.PI |
3.141592653589793 |
The double-equality pattern:
final double EPSILON = 1e-9;
if (Math.abs(a - b) < EPSILON) { /* treat as equal */ }
Never compare doubles with ==. Floating-point arithmetic is inexact. Pick epsilon by scale — 0.01 for money, 1e-9 for general small numerics.
Deep dive
1. Strings are objects, and that changes everything
A primitive like int holds one number. You work with it using operators — +, *, %, ==. That’s the whole interface.
A String is different. It is an object: a package containing your text AND the methods that know how to work with it. To do anything to a String, you call one of its methods using a dot:
String name = "Alice";
int howLong = name.length();
Read name.length() as “ask name for its length.” The dot says “apply this method to this value.” Every String carries the same set of methods — you don’t define them, you just call them.
Here is the rule that trips up almost everyone: Strings are immutable. Once a String exists it cannot be modified. Every method that looks like it changes the String — toUpperCase, trim, replace — actually builds a brand new String and returns it. The original is untouched.
Predict the output of this code, then read on:
String s = "hello";
s.concat(" world");
System.out.println(s);
If you predicted "hello world", you have plenty of company, and you’re wrong. The output is "hello". concat did build "hello world", but nobody caught it in a variable, so it was thrown away. The name s still points at the original "hello".
To make the change stick, assign the result back:
s = s.concat(" world");
System.out.println(s); // hello world
Here’s the mental model that explains this behavior. The variable s does not contain the text "hello" directly — it contains an arrow (the technical term is a reference) that points at a String stored somewhere else in memory. When concat runs, Java builds a brand-new String "hello world" at a new location. Because nobody caught that new String in a variable, no arrow points at it, and Java discards it. The arrow in s still points at the unchanged "hello". s = s.concat(" world") works because the assignment re-aims s’s arrow at the new String.
The same rule applies to toUpperCase, toLowerCase, substring, trim, and every other “modifying” method. No assignment, no change.
s?
String s = "hello";
s.concat(" world");concat built a new String "hello world" and returned it — but nobody caught the return value, so it was discarded. s still points at the original "hello". To update s, you would need s = s.concat(" world");.
2. The core String methods you will actually use
length() — how many characters. Spaces and punctuation count.
"hi there".length(); // 8
"".length(); // 0
charAt(int) — the character at a position. Indexes start at 0. The last valid index is length() - 1.
String word = "Java";
word.charAt(0); // 'J'
word.charAt(word.length() - 1); // 'a'
word.charAt(4); // StringIndexOutOfBoundsException
substring(int) and substring(int, int) — a slice. The two-argument version is half-open: from is included, to is not. Count to - from to get the length of the result.
String s = "Building Java Programs";
s.substring(9, 13); // "Java"
s.substring(9); // "Java Programs" (from 9 to the end)
A warmup exercise worth trying: given String book = "Building Java Programs";, write the expression that extracts just "Java". (Answer: book.substring(9, 13).)
toUpperCase() and toLowerCase() — case conversion. These return new Strings. Remember: assign the result.
String raw = "Hello";
String up = raw.toUpperCase(); // "HELLO"
System.out.println(raw); // still "Hello"
equals(Object) and equalsIgnoreCase(String) — content comparison. Use these. Not ==. == on Strings compares whether two variables point at the same object in memory — useful once in a thousand programs and wrong the other 999 times. For text comparison, always use .equals(). If you want to ignore case, use .equalsIgnoreCase().
"hello".equals("hello"); // true
"hello".equals("Hello"); // false — case matters
"hello".equalsIgnoreCase("Hello"); // true
One warning that burns a lot of students. If you experiment in jshell with two literal strings, you may see "hi" == "hi" return true and conclude that == works on Strings. It does not. The JVM reuses identical literal Strings as a memory optimization (called string interning), so two literals often share the same reference by coincidence. The moment a String comes from sc.nextLine(), from concat, or from anywhere that builds a new String at runtime, that coincidence ends and == returns false. Do not trust the demo.
contains(String) — is this substring present?
"user@example.com".contains("@"); // true
"hello".contains("xyz"); // false
3. The Math class and the double-equality problem
Math is a class of static utility methods. “Static” means the methods belong to the class itself, not to any object. You do not create a Math object. You call methods on the class name:
double root = Math.sqrt(25); // 5.0
int bigger = Math.max(3, 9); // 9
The methods return values; they do not print anything. Math.sqrt(16) by itself does nothing visible — it produces 4.0, which is thrown away unless you capture it. Compare:
Math.sqrt(16); // does nothing you can see
System.out.println(Math.sqrt(16)); // prints 4.0
double r = Math.sqrt(16); // stores 4.0 in r
Method returns a value, caller decides what to do with it. That pattern is the shape of real software. Make mental space for it now — it comes back in Week 5 when you start writing your own methods.
A return-type gotcha. Most Math methods return the same type you gave them; sqrt and pow always return double. But Math.round(double) returns long, which surprises almost everyone:
long rounded = Math.round(3.7); // 4L
int asInt = (int) Math.round(3.7); // cast if you need int
And the double-equality problem. Lesson 2-e showed that 0.1 + 0.2 does not equal 0.3 in Java — not a bug, but a consequence of how binary floating-point represents decimals. The sum comes out to 0.30000000000000004. Comparing with == returns false, and you will be confused.
The fix is the epsilon pattern:
final double EPSILON = 1e-9;
if (Math.abs(a - b) < EPSILON) {
// treat as equal
}
Choose epsilon by scale. For general-purpose comparisons of numbers near 1, 1e-9 works. This idiom is common enough in professional code that you should commit it to muscle memory.
(Side note for the curious: don’t use double for money. Roundoff accumulates, and a penny-per-transaction error becomes real dollars over millions of transactions. Real financial code uses BigDecimal or stores values as integer cents. That is a topic for a later class — in CS1, using doubles is fine because you are not actually running anyone’s bank.)
Before you leave
Between Strings and Math, you now have the two workhorse classes that appear in nearly every Java program. The labs ahead assume you can call methods on them fluently — if any of this still feels slippery, open this page’s quick reference on one side of the screen and work through a few expressions in jshell or a scratch file before moving on.
Next: intro to conditionals. You have been using methods that return boolean; now you will use those booleans to make the program take different paths depending on what it finds.