Definition
- chars().mapToObj(c -> (char) c) converts a String into a
Streamby streaming the int code points and casting to char. - groupingBy(identity(), LinkedHashMap::new, counting()) builds a frequency map where the key is the character, the value is its count, and
LinkedHashMappreserves the order characters first appeared in the string. - Filter and findFirst streams the map entries, keeps those with count equal to 1, and returns the first one in insertion order.
- LinkedHashMap is the critical ingredient:
HashMaprandomizes iteration order, so the "first" non-repeating character would be arbitrary.
At National Bank of India, an IFSC code validator checks each character of a code like "SBIN0001234". It counts how many times each character appears, preserving the order they were first seen. Then it scans the count list and picks the first character that appears only once. LinkedHashMap is like a checklist where entries stay in the order they were written.
Understanding the Concepts
What is a non-repeating character?
A non-repeating character is one that appears exactly once in the string. The "first" non-repeating character is the one that appears earliest in the string among all characters with count 1.
How do you build a character frequency map?
package com.bank.util;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FirstNonRepeatingDemo {
public static void main(String[] args) {
String ifscCode = "SBIN0001234";
// Step 1: character frequency map with insertion order
Map<Character, Long> freq = ifscCode.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new,
Collectors.counting()));
System.out.println("Frequency: " + freq);
}
}
Frequency: {S=1, B=1, I=1, N=1, 0=2, 1=2, 2=1, 3=1, 4=1}
LinkedHashMap preserves the order: S first, B second, I third, and so on.
How do you find the first non-repeating character?
// Step 2: first entry with count == 1
Optional<Character> firstNonRepeating = freq.entrySet().stream()
.filter(e -> e.getValue() == 1L)
.map(Map.Entry::getKey)
.findFirst();
System.out.println("First non-repeating: " + firstNonRepeating.orElse('?'));
First non-repeating: S
Why is LinkedHashMap critical?
WRONG: Using HashMap loses insertion order.
// WRONG: HashMap -- iteration order is random
Map<Character, Long> wrongMap = ifscCode.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting())); // HashMap by default!
// Might iterate as {0=2, 1=2, S=1, 4=1, B=1, ...}
// findFirst could return '4' or 'B' instead of 'S'
CORRECT: LinkedHashMap preserves insertion order.
// CORRECT: LinkedHashMap preserves character order from the string
Map<Character, Long> correctMap = ifscCode.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new, // preserves insertion order
Collectors.counting()));
What about a more complex example?
String word = "BANANA";
// B=1, A=3, N=2 (LinkedHashMap order: B, A, N)
// Filter count==1: only B
// First non-repeating: 'B'
String word2 = "STRESS";
// S=3, T=1, R=1, E=1 (LinkedHashMap order: S, T, R, E)
// Filter count==1: T, R, E
// First non-repeating: 'T' (first in string order)
How do you handle case-insensitive matching?
// Case-insensitive: convert to lowercase first
Optional<Character> caseInsensitive = "RaJeSh".chars()
.mapToObj(c -> Character.toLowerCase((char) c))
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new,
Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() == 1L)
.map(Map.Entry::getKey)
.findFirst();
What if there is no non-repeating character?
String allRepeating = "AABBCC";
// A=2, B=2, C=2 -- no entry has count 1
// findFirst returns Optional.empty()
Optional<Character> result = /* same pattern */;
System.out.println(result.orElse('?')); // prints '?'
What about using indexOf for an alternative approach?
// Alternative: O(n^2) but simpler
Optional<Character> alt = ifscCode.chars()
.mapToObj(c -> (char) c)
.filter(c -> ifscCode.indexOf(c) == ifscCode.lastIndexOf(c))
.findFirst();
// Works but O(n^2) because indexOf scans for each character
What should a senior engineer know?
- The
chars()method returns anIntStream, not aStream. ThemapToObj(c -> (char) c)step is necessary to box the int code points intoCharacterobjects. - For Unicode strings with characters outside the Basic Multilingual Plane (emoji, some CJK characters), use
codePoints()instead ofchars()to handle surrogate pairs correctly. - The frequency map approach is O(n) time and O(k) space where k is the number of distinct characters.
- The LinkedHashMap trick is the exact "gotcha" interviewers test for. Mentioning it unprompted shows deep understanding.
Visual Overview
"STRESS"
│
▼
chars().mapToObj(c -> (char) c)
[S, T, R, E, S, S]
│
▼
groupingBy(identity(), LinkedHashMap, counting())
┌────────────────────────────┐
│ S → 3 (first seen pos 0) │
│ T → 1 (first seen pos 1) │
│ R → 1 (first seen pos 2) │
│ E → 1 (first seen pos 3) │
└────────────┬───────────────┘
│
▼
filter(count == 1) → [T, R, E]
│
▼
findFirst() → 'T'
Key Takeaway
Finding the first non-repeating character uses a two-step stream approach. First, build a character frequency map using chars().mapToObj(c -> (char) c).collect(groupingBy(identity(), LinkedHashMap::new, counting())). The LinkedHashMap supplier preserves insertion order so the map iterates in the order characters first appeared in the string. Second, stream the map entries, filter for count equal to 1, and findFirst returns the first non-repeating character in string position order.
At National Bank of India, the IFSC validator processes "STRESS" and produces {S:3, T:1, R:1, E:1} in LinkedHashMap. Filtering count equal to 1 gives {T:1, R:1, E:1}. The findFirst returns 'T' because it appeared earliest in the string. With a regular HashMap, the iteration order is random, and you might get R or E instead of T.
This LinkedHashMap subtlety is exactly what interviewers test for. Mentioning it unprompted demonstrates deep understanding of both the Stream API and Java collections.
Remember one sentence: LinkedHashMap in the groupingBy supplier preserves character insertion order, which is what makes "first" deterministic.
---