Вам нужен правильный шаблон для определения местоположения , где необходимо внести изменение, - шаблона нулевой ширины, когда вы хотите использовать splitAsStream
.Совпадение местоположений, которые
- начало слова
- глядя на строчные буквы
- не глядя на слово «или»
Объявите это как
static final Pattern WORD_START_BUT_NOT_OR = Pattern.compile("\\b(?=\\p{Ll})(?!or\\b)");
Затем, используя его для обработки токенов, вы можете использовать поток с прямым потоком и map
.Возвращение строки работает через .collect(Collectors.joining())
:
List<String> input = Arrays.asList("Taxi or bus driver", "apples or oranges");
List<String> result = input.stream()
.map(s -> WORD_START_BUT_NOT_OR.splitAsStream(s)
.map(w -> Character.toUpperCase(w.charAt(0))+w.substring(1))
.collect(Collectors.joining()))
.collect(Collectors.toList());
result.forEach(System.out::println);
Taxi or Bus Driver
Apples or Oranges
Обратите внимание, что при разделении всегда будет первый токен, независимо от того, соответствует ли он критериям.Так как слово «или» обычно никогда не появляется в начале фразы, и преобразование прозрачно для строчных букв, это не должно быть проблемой здесь.В противном случае обработка первого элемента специально потоком сделает код слишком сложным.Если это проблема, цикл предпочтительнее.
Решение на основе цикла может выглядеть как
private static final Pattern FIRST_WORD_CHAR_BUT_NOT_OR
= Pattern.compile("\\b(?!or\\b)\\p{Ll}");
(теперь используется шаблон, который соответствует символу, а не смотрит на него)
public static String capitalizeWords(String phrase) {
Matcher m = FIRST_WORD_CHAR_BUT_NOT_OR.matcher(phrase);
if(!m.find()) return phrase;
StringBuffer sb = new StringBuffer();
do m.appendReplacement(sb, m.group().toUpperCase()); while(m.find());
return m.appendTail(sb).toString();
}
, который в качестве бонуса также способен обрабатывать персонажей, которые охватывают несколько char
единиц.Начиная с Java 9, StringBuffer
можно заменить на StringBuilder
для повышения эффективности.Этот метод может использоваться как
List<String> result = input.stream()
.map(s -> capitalizeWords(s))
.collect(Collectors.toList());
Замена лямбда-выражения s -> capitalizeWords(s)
на ссылку на метод в виде ContainingClass::capitalizeWords
также возможна.