University College of Applied SciencesProgramming 1
العربية
Lecture 15 · 120 min

Strings and Characters

How does a program take apart and understand what a user types?

By the end of this lecture you will be able to

  1. Explain a string as a sequence of positioned characters and connect it to arrays
  2. Use String methods to search, cut, compare and split text
  3. Explain why strings never change, and store the result of each method in a variable
  4. Walk through a string with a for loop to count, reverse and check its characters
  5. Convert text to numbers and deal with NumberFormatException
  6. Build text inside loops using StringBuilder
In this lecture
  1. Most of what users type is text, and a program must take it apart
  2. A string is a sequence of characters numbered from 0, just like an array
  3. The last position is length() - 1, and going past it stops the program
  4. Arabic text works the same way, and length counts characters
  5. Search methods tell you where a piece of text is, and whether it is there
  6. A string never changes: every method returns a new string
  7. Compare strings with methods, not with ==
  8. split cuts a string into an array of strings
  9. A for loop visits every character, as it visits every array item
  10. Building a reversed string leads to checking palindromes
  11. The Character class answers questions about each character
  12. parseInt and parseDouble turn text into a number
  13. StringBuilder builds text in place inside loops
  14. split with parseInt turns a line into data: word count and CSV
  15. Checking each character gives a Caesar cipher and a username validator
  16. Review questions
Part one: string methods

Most of what users type is text, and a program must take it apart

Username

Is the length acceptable? Does it start with a letter? Does it contain forbidden symbols?

Search box

The site finds java even if you type JAVA and leave a space after it.

Marks file

The line Sara,80,95 is one piece of text, and the program must get a name and two numbers out of it.

A string is a sequence of characters numbered from 0, just like an array

name.charAt(0)'S'char
name.charAt(1)'a'char
name.charAt(2)'r'char
name.charAt(3)'a'char
OperationArrayString
Sizea.lengths.length()
Item at a positiona[i]s.charAt(i)
Last itema[a.length - 1]s.charAt(s.length() - 1)
Change an itema[0] = 9;Not possible

Read a string letter by letter

Predict the four lines before running, then put your own name in place of Sara and predict again.

public class Positions {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.length());
        System.out.println(name.charAt(0));
        System.out.println(name.charAt(1));
        System.out.println(name.charAt(name.length() - 1));
    }
}
Output
4
S
a
a

A string is not written like an array

We used square brackets as with an array. Fix the line so it prints the first letter.

public class FirstLetter {
    public static void main(String[] args) {
        String name = "Sara";
        char first = name[0];
        System.out.println(first);
    }
}
Compiler messagearray required, but String found
Show the fix
public class FirstLetter {
    public static void main(String[] args) {
        String name = "Sara";
        char first = name.charAt(0);
        System.out.println(first);
    }
}
Output
S

Why? A string resembles an array only in idea. Brackets [] belong to arrays; from a string we ask for a character with the charAt method.

length on a string is a method

public class NameLength {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.length);
    }
}
Compiler messagecannot find symbol
Show the fix
public class NameLength {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.length());
    }
}
Output
4

Why? a.length on an array has no parentheses; s.length() on a string has them because it is a method. The compiler looks for a variable named length inside String and does not find one.

The last position is length() - 1, and going past it stops the program

This is the same mistake we saw with arrays: Sara has length 4, so its positions run from 0 to 3. Position 4 does not exist.

Read the error message

Run the program. The first line prints, then the program stops at position 4. Fix it by writing name.length() - 1.

public class OutOfRange {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.charAt(3));
        System.out.println(name.charAt(name.length()));
        System.out.println("This line never runs");
    }
}
Output
a
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 4
This is what JDK 8 prints. The last number is the position you asked for.

The string "Gaza" has length 4. Which call stops the program?

Arabic text works the same way, and length counts characters

Java stores each character by its Unicode number, so the same methods work on "سارة" as on "Sara", and the text shows correctly on this site.

A name in Arabic

Put your own name in Arabic in place of سارة, and predict the length before running.

public class ArabicText {
    public static void main(String[] args) {
        String name = "سارة";
        System.out.println(name);
        System.out.println(name.length());
        System.out.println(name.charAt(0));
        System.out.println(name.contains("ار"));
        System.out.println(name + " تتعلم Java");
    }
}
Output
سارة
4
س
true
سارة تتعلم Java

The two words read the same. What does the program print?

public class ArabicLength {
    public static void main(String[] args) {
        System.out.println("محمد".length());
        System.out.println("مُحمد".length());
    }
}
Show the output
Output
4
5

Why? length counts characters, not bytes, so محمد has length four. The damma is a separate character in the text, so it adds one to the length.

Search methods tell you where a piece of text is, and whether it is there

MethodReturnss = "Java"
s.substring(i, j)Text from position i up to just before js.substring(1, 3) → "av"
s.substring(i)Text from position i to the ends.substring(2) → "va"
s.indexOf(x)Position of the first match, or -1s.indexOf("a") → 1
s.contains(x)booleans.contains("av") → true
s.startsWith(x)booleans.startsWith("Ja") → true
s.endsWith(x)booleans.endsWith("a") → true

Examine an email address

Count the positions up to @ on paper, then run the program. Did the answers from the start of the lecture match what these methods do?

public class EmailSearch {
    public static void main(String[] args) {
        String email = "sara.ali@ucas.edu.ps";
        int at = email.indexOf("@");
        System.out.println(at);
        System.out.println(email.substring(0, at));
        System.out.println(email.substring(at + 1));
        System.out.println(email.contains("ucas"));
        System.out.println(email.startsWith("sara"));
        System.out.println(email.endsWith(".com"));
        System.out.println(email.indexOf("#"));
    }
}
Output
8
sara.ali
ucas.edu.ps
true
true
false
-1

What does this print? Write the letter positions on paper first.

public class Cut {
    public static void main(String[] args) {
        String word = "Programming";
        System.out.println(word.substring(3, 7));
    }
}
Show the output
Output
gram

Why? The start position 3 is included and the end position 7 is not. The length is always 7 − 3 = 4 characters.

What does "Java".indexOf("x") return?

User and domain

Read a line holding an email address. If it has no @, print Invalid. Otherwise print what comes before @ after User: , then what comes after it after Domain: on a second line.

import java.util.Scanner;

public class SplitEmail {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String email = input.nextLine();
        // Find the @, then cut the text around it

    }
}
stdin
sara@ucas.edu.ps
Expected output
User: sara
Domain: ucas.edu.ps
Hint 1

Store the result of indexOf("@") in a variable and compare it with -1.

Hint 2

substring(0, at) for the part before it, and substring(at + 1) for the part after it.

Solution
import java.util.Scanner;

public class SplitEmail {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String email = input.nextLine();
        int at = email.indexOf("@");
        if (at == -1) {
            System.out.println("Invalid");
        } else {
            System.out.println("User: " + email.substring(0, at));
            System.out.println("Domain: " + email.substring(at + 1));
        }
    }
}

A string never changes: every method returns a new string

MethodReturnsExample
toUpperCase()A copy in capitals"Ali" → "ALI"
toLowerCase()A copy in small letters"Ali" → "ali"
trim()A copy without spaces at both ends" a b " → "a b"
replace(a, b)A copy with every match replaced"banana" → "bonono"

This is the most famous trap with strings. What do the two lines print?

public class Immutable {
    public static void main(String[] args) {
        String name = "sara";
        name.toUpperCase();
        System.out.println(name);
        name = name.toUpperCase();
        System.out.println(name);
    }
}
Show the output
Output
sara
SARA

Why? The second line returns a new string that nobody keeps, so name stays as it was. The fourth line puts the new string in the box: the variable changes, not the old string.

Clean up user input

We added the brackets [ ] so spaces can be seen. Notice that raw on the last line has not changed despite everything before it. Try swapping the order of the methods on the third line.

public class CleanText {
    public static void main(String[] args) {
        String raw = "  Hello World  ";
        System.out.println("[" + raw.trim() + "]");
        System.out.println("[" + raw.trim().toLowerCase() + "]");
        System.out.println("[" + raw.replace("World", "Java") + "]");
        System.out.println("banana".replace("a", "o"));
        System.out.println("[" + raw + "]");
    }
}
Output
[Hello World]
[hello world]
[  Hello Java  ]
bonono
[  Hello World  ]

What does this code print?

String s = "abc";
s.replace("a", "z");
System.out.println(s);

Compare strings with methods, not with ==

MethodThe questionReturns
a.equals(b)Exactly the same characters?boolean
a.equalsIgnoreCase(b)The same, ignoring capitals?boolean
a.compareTo(b)Which comes first in dictionary order?Negative if a is first, 0 if equal, positive if later

Three ways to compare

compareTo compares the Unicode number of the characters at the first difference, and capitals come before small letters. That is why Zed comes before apple.

public class CompareStrings {
    public static void main(String[] args) {
        System.out.println("java".equals("Java"));
        System.out.println("java".equalsIgnoreCase("Java"));
        System.out.println("apple".compareTo("banana"));
        System.out.println("banana".compareTo("apple"));
        System.out.println("apple".compareTo("apple"));
        System.out.println("Zed".compareTo("apple"));
    }
}
Output
false
true
-1
1
0
-7

Both strings hold the same characters. What do the two lines print?

public class SameText {
    public static void main(String[] args) {
        String first = "hello";
        String part = "hel";
        String second = part + "lo";
        System.out.println(first == second);
        System.out.println(first.equals(second));
    }
}
Show the output
Output
false
true

Why? == asks whether they are the same string in memory; equals asks whether their characters match. The join created a new string elsewhere, so == says no. Text from the user falls into the same trap.

You want names in alphabetical order. What does a.compareTo(b) greater than zero mean?

Bubble sort with strings

This is the algorithm from the last lecture, with only the comparison changed. Press "Debug line by line" and watch the swaps, then try Arrays.sort(names) in place of the two loops.

import java.util.Arrays;

public class SortNames {
    public static void main(String[] args) {
        String[] names = {"Sara", "Ali", "Huda"};
        for (int i = 0; i < names.length - 1; i++) {
            for (int k = 0; k < names.length - 1 - i; k++) {
                if (names[k].compareTo(names[k + 1]) > 0) {
                    String temp = names[k];
                    names[k] = names[k + 1];
                    names[k + 1] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(names));
    }
}
Output
[Ali, Huda, Sara]

split cuts a string into an array of strings

This is where strings meet arrays: give split a separator and it returns a String[], which you walk through with everything from the last two lectures.

From a line to pieces

Press "Debug line by line" and look at the parts array. Change the separator to ; both in the text and in split.

public class SplitBasics {
    public static void main(String[] args) {
        String line = "Sara,19,3.75";
        String[] parts = line.split(",");
        System.out.println(parts.length);
        for (int i = 0; i < parts.length; i++) {
            System.out.println(i + ": " + parts[i]);
        }
    }
}
Output
3
0: Sara
1: 19
2: 3.75

Two commas in a row, and two at the end. What does this print?

public class SplitEmpty {
    public static void main(String[] args) {
        String[] parts = "one,,three,,".split(",");
        System.out.println(parts.length);
        System.out.println("[" + parts[1] + "]");
    }
}
Show the output
Output
3
[]

Why? An empty piece in the middle is kept, so the second piece is the empty string. Empty pieces at the end are removed by split, which leaves three pieces.

Spaces and the dot

The first line gives 7 pieces because the extra spaces make empty pieces. The fix is on the second line: trim, then "\\s+".

public class SplitPatterns {
    public static void main(String[] args) {
        String text = "  one   two three ";
        System.out.println(text.split(" ").length);
        System.out.println(text.trim().split("\\s+").length);
        System.out.println("3.75".split(".").length);
        System.out.println("3.75".split("\\.").length);
    }
}
Output
7
3
0
2
Part two: walking through a string

A for loop visits every character, as it visits every array item

for (int i = 0; i < text.length(); i++) {
    char c = text.charAt(i);
    // use c
}
The pattern you will write often. The condition is <, not <=.

Count the vowels

Press "Debug line by line" and watch c and count. Why did we turn the text into small letters first? Change the text and predict the count.

public class CountVowels {
    public static void main(String[] args) {
        String text = "Programming in Java".toLowerCase();
        int count = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                count++;
            }
        }
        System.out.println("Vowels: " + count);
    }
}
Output
Vowels: 6

What happens if you write the condition i <= text.length()?

Count the vowels in a line

Read a line and print how many vowels a e i o u it has, capital or small. Print only the number.

import java.util.Scanner;

public class VowelCounter {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine();
        // Count the vowels and print the count

    }
}
stdin
Hello World
Expected output
3
Hint 1

Convert all the text with toLowerCase, and store the result in a variable.

Hint 2

Compare a char with ==, because a character is a primitive and not a string.

Solution
import java.util.Scanner;

public class VowelCounter {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine().toLowerCase();
        int count = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                count++;
            }
        }
        System.out.println(count);
    }
}

Building a reversed string leads to checking palindromes

Reverse a string

We walk from the last position down to 0. Press "Debug line by line" and watch reversed grow. Notice that the word رادار reads the same from both ends.

public class ReverseText {
    public static String reverse(String text) {
        String reversed = "";
        for (int i = text.length() - 1; i >= 0; i--) {
            reversed = reversed + text.charAt(i);
        }
        return reversed;
    }

    public static void main(String[] args) {
        System.out.println(reverse("Java"));
        System.out.println(reverse("رادار").equals("رادار"));
    }
}
Output
avaJ
true

Here we walk forwards, but each letter goes before the text. What does it print?

public class Prepend {
    public static void main(String[] args) {
        String word = "abc";
        String out = "";
        for (int i = 0; i < word.length(); i++) {
            out = word.charAt(i) + out;
        }
        System.out.println(out);
    }
}
Show the output
Output
cba

Why? out becomes a, then ba, then cba, because each new letter goes at the front. The order around + matters with strings.

Is the word a palindrome?

Read a word. Print Palindrome if it reads the same from both ends, ignoring capitals; otherwise print Not palindrome. Write a method isPalindrome that returns a boolean.

import java.util.Scanner;

public class PalindromeCheck {
    // Write isPalindrome here

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String word = input.next();
        // Call your method and print the answer

    }
}
stdin
level
Expected output
Palindrome
Hint 1

Reverse the word as in the program above, then compare with equalsIgnoreCase.

Hint 2

Another way: compare position i with position length() - 1 - i up to the middle of the word.

Solution
import java.util.Scanner;

public class PalindromeCheck {
    public static boolean isPalindrome(String word) {
        String reversed = "";
        for (int i = word.length() - 1; i >= 0; i--) {
            reversed = reversed + word.charAt(i);
        }
        return word.equalsIgnoreCase(reversed);
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String word = input.next();
        if (isPalindrome(word)) {
            System.out.println("Palindrome");
        } else {
            System.out.println("Not palindrome");
        }
    }
}

The Character class answers questions about each character

MethodQuestion or actionExample
Character.isDigit(c)Is it a digit?isDigit('7') → true
Character.isLetter(c)Is it a letter? Arabic counts too.isLetter('س') → true
Character.toUpperCase(c)Returns the capital lettertoUpperCase('a') → 'A'

Sort characters into kinds

Press "Debug line by line" and follow the three counters. Replace the text with something containing Arabic letters.

public class CharKinds {
    public static void main(String[] args) {
        String text = "Room 12B!";
        int letters = 0;
        int digits = 0;
        int others = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (Character.isLetter(c)) {
                letters++;
            } else if (Character.isDigit(c)) {
                digits++;
            } else {
                others++;
            }
        }
        System.out.println("Letters: " + letters);
        System.out.println("Digits: " + digits);
        System.out.println("Others: " + others);
    }
}
Output
Letters: 5
Digits: 2
Others: 2

A character is really a number. What does this print? The Unicode number of a is 97.

public class CharMath {
    public static void main(String[] args) {
        char c = 'a';
        System.out.println(c + 1);
        System.out.println((char) (c + 1));
        System.out.println(Character.toUpperCase(c) + "bc");
    }
}
Show the output
Output
98
b
Abc

Why? char + int gives an int, so it prints 98. The cast (char) turns the number back into a letter. The last line has a String on one side, so + joins. We need this idea for the Caesar cipher.

One character or a string?

public class Grade {
    public static void main(String[] args) {
        char grade = "A";
        System.out.println(grade);
    }
}
Compiler messageincompatible types: String cannot be converted to char
Show the fix
public class Grade {
    public static void main(String[] args) {
        char grade = 'A';
        System.out.println(grade);
    }
}
Output
A

Why? Double quotes make a String even with a single letter. A character is written between ' '.

parseInt and parseDouble turn text into a number

The pieces that come out of split are strings, even when they look like numbers. They must be converted before any arithmetic.

From text to a number

public class ParseNumbers {
    public static void main(String[] args) {
        String age = "19";
        System.out.println(age + 1);
        int years = Integer.parseInt(age);
        System.out.println(years + 1);
        double gpa = Double.parseDouble("3.75");
        System.out.println(gpa * 2);
    }
}
Output
191
20
7.5

What do the two lines print?

public class ParseOrJoin {
    public static void main(String[] args) {
        String a = "5";
        String b = "3";
        System.out.println(a + b);
        System.out.println(Integer.parseInt(a) + Integer.parseInt(b));
    }
}
Show the output
Output
53
8

Why? Between two strings, + joins. After converting, both sides are numbers, so it adds.

Text that is not a number

Run it and read the message: For input string: "abc". The program stops at the line with parseInt.

public class ParseCrash {
    public static void main(String[] args) {
        System.out.println("Before");
        int n = Integer.parseInt("abc");
        System.out.println(n);
    }
}
Output
Before

A preview: try and catch catch the exception

If the exception appears inside try, execution moves to catch instead of stopping the program. This is only a preview, enough for this one exception; you study it in depth in Programming 2. Notice that " 7" fails because of the space, so use trim before converting.

public class ParseTry {
    public static void main(String[] args) {
        String[] inputs = {"42", "3.5", "abc", " 7", ""};
        for (String text : inputs) {
            try {
                int n = Integer.parseInt(text);
                System.out.println("OK: " + n);
            } catch (NumberFormatException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
Output
OK: 42
For input string: "3.5"
For input string: "abc"
For input string: " 7"
For input string: ""

What happens when Integer.parseInt("3.5") runs?

StringBuilder builds text in place inside loops

Since a string never changes, every s = s + c in a loop creates a new string and copies everything so far. That does not matter for a short word, but it is slow for long text. A StringBuilder is an editable box of text that grows without copying each time.

MethodWhat it does
new StringBuilder()An empty box; you create it with new, like a Scanner
sb.append(x)Adds text, a character or a number at the end
sb.reverse()Reverses the content in place
sb.length()Number of characters so far
sb.toString()Returns an ordinary String, to use its methods

Build a line in a loop

Press "Debug line by line" and watch line grow. Why is there no comma after the last number?

public class BuilderLine {
    public static void main(String[] args) {
        StringBuilder line = new StringBuilder();
        for (int i = 1; i <= 5; i++) {
            line.append(i);
            if (i < 5) {
                line.append(", ");
            }
        }
        String result = line.toString();
        System.out.println(result);
        System.out.println(result.length());
    }
}
Output
1, 2, 3, 4, 5
13

This program keeps nothing that the methods return. What does it print?

public class BuilderVsString {
    public static void main(String[] args) {
        StringBuilder box = new StringBuilder("abc");
        box.append("d");
        System.out.println(box);
        String text = "abc";
        text.concat("d");
        System.out.println(text);
        box.reverse();
        System.out.println(box);
    }
}
Show the output
Output
abcd
abc
dcba

Why? A StringBuilder changes itself, so there is no need to keep the result. concat is like +: it returns a new string, which is lost here.

Remove the vowels

Read a line and print it without the small and capital vowels a e i o u, keeping everything else. Use StringBuilder.

import java.util.Scanner;

public class RemoveVowels {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine();
        StringBuilder result = new StringBuilder();
        // Append every character that is not a vowel

        System.out.println(result);
    }
}
stdin
Programming
Expected output
Prgrmmng
Hint 1

Turn each character small with Character.toLowerCase to compare it once, but append the original character.

Solution
import java.util.Scanner;

public class RemoveVowels {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine();
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            char low = Character.toLowerCase(c);
            if (low != 'a' && low != 'e' && low != 'i' && low != 'o' && low != 'u') {
                result.append(c);
            }
        }
        System.out.println(result);
    }
}
Part three: complete programs

split with parseInt turns a line into data: word count and CSV

Word count

Read a line and print how many words it has. There may be extra spaces anywhere, and the line may be empty, in which case the answer is 0.

import java.util.Scanner;

public class WordCount {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine();
        // Clean the line, split it, and print the number of pieces

    }
}
stdin
Java is fun
Expected output
3
Hint 1

Use trim, then split("\\s+").

Hint 2

"".split(...) returns an array with one empty piece, so check for the empty line after trim and before splitting.

Solution
import java.util.Scanner;

public class WordCount {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine().trim();
        if (line.length() == 0) {
            System.out.println(0);
        } else {
            System.out.println(line.split("\\s+").length);
        }
    }
}

A CSV line reader

CSV is how Excel sheets look when saved as text: each line is a row and a comma separates the columns. Press "Debug line by line" and look at parts. Add a line for a new student to the array.

public class CsvParser {
    public static void main(String[] args) {
        String[] lines = {"Sara Ali, 19, 3.75", "Omar, 20, 2.5"};
        for (String line : lines) {
            String[] parts = line.split(",");
            String name = parts[0].trim();
            int age = Integer.parseInt(parts[1].trim());
            double gpa = Double.parseDouble(parts[2].trim());
            System.out.printf("%-10s %3d %5.2f%n", name, age + 1, gpa);
        }
    }
}
Output
Sara Ali    20  3.75
Omar        21  2.50

A line of marks

Read a line of the form name,mark1,mark2,mark3; there may be spaces around the commas. Print Name: with the name, then Total: with the total, then Average: with the average to two decimals. If the line does not have exactly four pieces, print only Bad line.

import java.util.Scanner;

public class CsvLine {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine();
        // Split, check the number of pieces, convert, print

    }
}
stdin
Sara,80,90,100
Expected output
Name: Sara
Total: 270
Average: 90.00
Hint 1

Call trim on each piece before parseInt.

Hint 2

Watch out for integer division: total / 3.0.

Solution
import java.util.Scanner;

public class CsvLine {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine();
        String[] parts = line.split(",");
        if (parts.length != 4) {
            System.out.println("Bad line");
        } else {
            int total = 0;
            for (int i = 1; i < parts.length; i++) {
                total += Integer.parseInt(parts[i].trim());
            }
            System.out.println("Name: " + parts[0].trim());
            System.out.println("Total: " + total);
            System.out.printf("Average: %.2f%n", total / 3.0);
        }
    }
}

Checking each character gives a Caesar cipher and a username validator

A Caesar cipher for small letters

c - 'a' gives the letter number from 0 to 25, and % 26 takes us back to the start after z. Press "Debug line by line" and stop at the letter z to see it. To decrypt, we shift by 26 - key.

public class CaesarLab {
    public static String encrypt(String text, int key) {
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c >= 'a' && c <= 'z') {
                out.append((char) ('a' + (c - 'a' + key) % 26));
            } else {
                out.append(c);
            }
        }
        return out.toString();
    }

    public static void main(String[] args) {
        String secret = encrypt("lazy zebra", 3);
        System.out.println(secret);
        System.out.println(encrypt(secret, 26 - 3));
    }
}
Output
odcb cheud
lazy zebra

The full Caesar cipher

Read the key on the first line (0 to 25) and the message on the second. Print the encrypted message: small and capital letters are shifted and keep their case, and every other character stays unchanged.

import java.util.Scanner;

public class Caesar {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int key = input.nextInt();
        // Careful: what does nextInt leave behind?

    }
}
stdin
3
abc xyz
Expected output
def abc
Hint 1

After nextInt, call nextLine once to pass the end of the first line, then again to read the message.

Hint 2

Add a second branch for capitals that starts from 'A'.

Solution
import java.util.Scanner;

public class Caesar {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int key = input.nextInt();
        input.nextLine();
        String text = input.nextLine();
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c >= 'a' && c <= 'z') {
                out.append((char) ('a' + (c - 'a' + key) % 26));
            } else if (c >= 'A' && c <= 'Z') {
                out.append((char) ('A' + (c - 'A' + key) % 26));
            } else {
                out.append(c);
            }
        }
        System.out.println(out);
    }
}

Username validator

Read a line and check it in this order, printing only the first problem: its length is 5 to 12, otherwise Bad length; it starts with a letter, otherwise Bad start; all its characters are letters, digits or _, otherwise Bad character. If every check passes, print Valid. Write a method that returns the message.

import java.util.Scanner;

public class UsernameValidator {
    public static String check(String name) {
        // Return the first problem, or "Valid"
        return "Valid";
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String name = input.nextLine();
        System.out.println(check(name));
    }
}
stdin
sara_99
Expected output
Valid
Hint 1

Check the length first; it protects you from charAt(0) on an empty string.

Hint 2

return stops the method at once, so you need no else after each check.

Solution
import java.util.Scanner;

public class UsernameValidator {
    public static String check(String name) {
        if (name.length() < 5 || name.length() > 12) {
            return "Bad length";
        }
        if (!Character.isLetter(name.charAt(0))) {
            return "Bad start";
        }
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (!Character.isLetter(c) && !Character.isDigit(c) && c != '_') {
                return "Bad character";
            }
        }
        return "Valid";
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String name = input.nextLine();
        System.out.println(check(name));
    }
}

Review questions

Which is the right way to write the length of string s and of array a?

Which condition correctly checks a password pass read from the user?

When do you choose StringBuilder over +?

Several methods in one program. What does this print?

public class ReviewStrings {
    public static void main(String[] args) {
        String line = " Ali,Sara ";
        String[] names = line.trim().split(",");
        System.out.println(names[1].toUpperCase().charAt(0));
        System.out.println(names[0].length() + names[1].length());
        System.out.println(line.length());
    }
}
Show the output
Output
S
7
10

Why? names[1] is Sara, and its first capital letter is S. The lengths 3 and 4 are numbers, so they add to 7. line has not changed despite trim, so its length is still 10.