É isso mesmo. Evite concatenar strings, use sempre StringBuilder.
Antes de alguém sair jogando pedras no Java, experimente rodar o mesmo programa em C# (.NET). Você vai ver que o comportamento é muito semelhante (usar System.Text.StringBuilder.Append é MUITO mais rápido que += . )
Isso é porque o seu programa que usa “+=” é convertido, pelo compilador, para algo como:
String s = "";
for (int i = 0; i < 1000000; ++i) {
s = (new StringBuilder (s).append ("0")).toString();
}
Ou seja, a cada “+=”, é criado um NOVO objeto StringBuilder (com uma cópia física dos caracteres da string original), e um NOVO objeto String (com uma cópia física dos caracteres da StringBuilder na hora em que foi chamado o método toString(), sem contar os 2 NOVOS objetos char[] que contém os tais caracteres e que são atributos dessas classes StringBuilder e String. Total de objetos criados e reciclados : 4, sendo que 2 desses objetos (char[]) são muito grandes no seu exemplo. Isso força muito o Garbage Collector, e é por isso que leva vários minutos para concatenar 1 milhão de strings grandes (mais de 500.000 caracteres, ou seja, 1.000.000 bytes).
Em contrapartida, seu primeiro programa só cria objetos novos (char[]) muito de vez em quando, quando o buffer interno do StringBuilder estoura e ele é obrigado a realocar o tal buffer.