Continuing my TDD from exercism.Write a program that given a phrase can count the occurrences of each word in that phrase. For example for the input olly olly in come freeolly: 2in: 1come: 1free: 1import java.util.Map;import java.util.HashMap;/** * Counts the word in a given string. Ignores any punctuation. * Example: *    one two, two# ??three === {one: 1, two: 2, three: 1} */public class WordCount {  private final Map<String, Integer> wordFrequencyMap;  WordCount() {    wordFrequencyMap = new HashMap<>();  }  public Map<String, Integer> phrase(String phrase) {    for (String word : phrase.trim().split(\\s+)) {      word = withoutPunctuation(word.toLowerCase());      if (word == null || word.isEmpty()) {        continue;      }      if (!wordFrequencyMap.containsKey(word)) {        wordFrequencyMap.put(word, 1);      } else {        wordFrequencyMap.put(word, wordFrequencyMap.get(word) + 1);      }    }    return wordFrequencyMap;  }  private String withoutPunctuation(String word) {    String wordWithoutPunctuation = ;    for (char ch : word.toCharArray()) {      if (isLetter(ch) || isDigit(ch)) {        wordWithoutPunctuation += ch;      }    }    return wordWithoutPunctuation;  }  private boolean isLetter(char ch) {    return ch >= 'a' && ch <= 'z';  }  private boolean isDigit(char ch) {    return ch >= '0' && ch <= '9';  }}Test suite:import org.junit.Test;import java.lang.Integer;import java.lang.String;import java.util.HashMap;import java.util.Map;import static org.junit.Assert.*;public class WordCountTest {    private final WordCount wordCount = new WordCount();    @Test    public void countOneWord() {        Map<String, Integer> actualWordCount = new HashMap<String, Integer>();        final Map<String, Integer> expectedWordCount = new HashMap<String, Integer>();        expectedWordCount.put(word, 1);        actualWordCount = wordCount.phrase(word);        assertEquals(            expectedWordCount, actualWordCount        );    }    @Test    public void countOneOfEach() {        Map<String, Integer> actualWordCount = new HashMap<String, Integer>();        final Map<String, Integer> expectedWordCount = new HashMap<String, Integer>();        expectedWordCount.put(one, 1);        expectedWordCount.put(of, 1);        expectedWordCount.put(each, 1);        actualWordCount = wordCount.phrase(one of each);        assertEquals(            expectedWordCount, actualWordCount        );    }    @Test    public void countMultipleOccurences() {        Map<String, Integer> actualWordCount = new HashMap<String, Integer>();        final Map<String, Integer> expectedWordCount = new HashMap<String, Integer>();        expectedWordCount.put(one, 1);        expectedWordCount.put(fish, 4);        expectedWordCount.put(two, 1);        expectedWordCount.put(red, 1);        expectedWordCount.put(blue, 1);        actualWordCount = wordCount.phrase(one fish two fish red fish blue fish);        assertEquals(            expectedWordCount, actualWordCount        );    }    @Test    public void ignorePunctuation() {        Map<String, Integer> actualWordCount = new HashMap<String, Integer>();        final Map<String, Integer> expectedWordCount = new HashMap<String, Integer>();        expectedWordCount.put(car, 1);        expectedWordCount.put(carpet, 1);        expectedWordCount.put(as, 1);        expectedWordCount.put(java, 1);        expectedWordCount.put(javascript, 1);        actualWordCount = wordCount.phrase(car : carpet as java : javascript!!&@$%^&);        assertEquals(            expectedWordCount, actualWordCount        );    }    @Test    public void includeNumbers() {        Map<String, Integer> actualWordCount = new HashMap<String, Integer>();        final Map<String, Integer> expectedWordCount = new HashMap<String, Integer>();        expectedWordCount.put(testing, 2);        expectedWordCount.put(1, 1);        expectedWordCount.put(2, 1);        actualWordCount = wordCount.phrase(testing, 1, 2 testing);        assertEquals(            expectedWordCount, actualWordCount        );    }    @Test    public void normalizeCase() {        Map<String, Integer> actualWordCount = new HashMap<String, Integer>();        final Map<String, Integer> expectedWordCount = new HashMap<String, Integer>();        expectedWordCount.put(go, 3);        actualWordCount = wordCount.phrase(go Go GO);        assertEquals(            expectedWordCount, actualWordCount        );    }}Learning from my previous experiences I agree the code breaks encapsulation as an exception (client requirement). Other thing is that I didn't want to use Regex hence wanted to test my own knowledge.Regarding performance I haven't thought much it should be O(n).
Count words in a string
java;performance;object oriented;programming challenge;unit testing
Building Strings with +=In your withoutPunctuation method, you are building the String to return by appending each character with +=.private String withoutPunctuation(String word) {  String wordWithoutPunctuation = ;  for (char ch : word.toCharArray()) {    if (isLetter(ch) || isDigit(ch)) {      wordWithoutPunctuation += ch;    }  }  return wordWithoutPunctuation;}It would be better to use a StringBuilder which is a class dedicated for that. The problem is that each character will be turned into a String and concatenated to the previous String, resulting in a new String again and again. All those String creation takes time. Using a StringBuilder solves that problem:private String withoutPunctuation(String word) {    StringBuilder wordWithoutPunctuation = new StringBuilder();    for (char ch : word.toCharArray()) {        if (isLetter(ch) || isDigit(ch)) {            wordWithoutPunctuation.append(ch);        }    }    return wordWithoutPunctuation.toString();}Unnecessary checksThe String returned by the withoutPunctuation method can never be null (and that's a good thing). So there is no need to check for it later:word = withoutPunctuation(word.toLowerCase());if (word == null || word.isEmpty()) {  continue;}You can just have:word = withoutPunctuation(word.toLowerCase());if (word.isEmpty()) {    continue;}Potential bugsYou are currently lowercasing Strings withword.toLowerCase()This can lead to weird bugs. In fact, if someone with a Turkish locale were to run your code on the sentence I LIKE JAVA, they would have as result the map {java=1, lke=1}. Notice the I that are missing. This is because changing the case of a String depends on the locale and by default, this is the locale of the JVM, which also defaults to your system locale. The capital I in Turkish is lowercased to  (dot-less i), which then doesn't pass your filter because it isn't a character between 'a' and 'z'.To counteract this, it is best to use the ROOT locale when changing the case, so you should have:word.toLowerCase(Locale.ROOT)Cache the PatternCompiling a regulax expression Pattern, which is done internally by split, takes time. It would be best to compile it only once and reuse the Pattern. Consider making it a constant:private static final Pattern PATTERN = Pattern.compile(\\s+);Then you can havefor (String word : PATTERN.split(phrase.trim())) {    // ...}Other commentsDo you really need wordFrequencyMap as an instance field? It is only used inside the phrase method so it would be better to declare it inside.If you're on Java 8, consider using the Stream API for the phrase method. You would have:public Map<String, Long> phrase(String phrase) {    return PATTERN.splitAsStream(phrase.trim())                  .map(s -> withoutPunctuation(s.toLowerCase(Locale.ROOT)))                  .filter(s -> !s.isEmpty())                  .collect(Collectors.groupingBy(s -> s, Collectors.counting()));}In the same way, the withoutPunctuation method can be writtenprivate String withoutPunctuation(String word) {    return word.chars()               .filter(ch -> isLetter((char) ch) || isDigit((char) ch))               .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)               .toString();}