Почему переопределение строки с помощью replace и substring считается плохой практикой?
Что проверяют
Помните ли вы, что String неизменяем, и понимаете ли, что replace и substring ничего не переопределяют, а возвращают новую строку. Вопрос ловит сразу на двух вещах: на непонимании неизменяемости и на заученных советах про производительность.
Как отвечать
Начать с главного: сами по себе методы нормальные, плоха попытка переопределить ими строку.
String s = "a,b";
s.replace(",", ";"); // результат потерян, s не изменилась
s = s.replace(",", ";"); // так правильноДальше про цену. Каждый вызов создаёт новый объект, и в цикле по большому тексту это превращается в квадратичную работу и гору мусора. Для сборки строки берут StringBuilder, для частых замен по шаблону компилируют Pattern один раз и переиспользуют, для разбора формата берут split или indexOf вместо лесенки из substring с посчитанными вручную индексами, которая рассыпается на первом же нестандартном входе.
Полезно добавить, что replace работает с литералом, а replaceAll принимает регулярное выражение, и подстановка туда пользовательского ввода даёт отдельный класс сюрпризов. Про substring стоит сказать, что в современной Java он копирует символы, а не разделяет массив с исходной строкой, так что старый совет про утечку памяти при вырезании куска из огромного текста уже неактуален.
Где спотыкаются
Отвечают «нужен StringBuilder» на любой вопрос про строки. Замена подстроки и склейка это разные задачи, и StringBuilder не делает то, что делает replace, хотя свой replace по индексам у него есть и про него как раз не вспоминают.
Пугают производительностью там, где её нет. Одна замена в строке из сотни символов не стоит ничего, оптимизировать надо цикл и объём, а не сам факт создания объекта.
Путают неизменяемость строки с безопасностью ссылки на неё. Саму строку можно читать из любого числа потоков, но поле, куда вы кладёте результат replace, обновляется как обычная ссылка, и без синхронизации или volatile другой поток увидит старое значение.