s1 == ""
не является надежным, поскольку он проверяет ссылочное равенство, а не объектное равенство (и String не является строго каноническим).
s1.equals("")
лучше, но может страдать от исключений нулевого указателя. Еще лучше:
"".equals(s1)
Нет исключений нулевого указателя.
РЕДАКТИРОВАТЬ: Хорошо, вопрос был задан о каноническая форма . Эта статья определяет это как:
Предположим, у нас есть некоторый набор объектов S,
с отношением эквивалентности.
каноническая форма задается обозначением
некоторые объекты S должны быть в каноническом
форма ", так что каждый объект под
рассмотрение эквивалентно точно
один объект в канонической форме.
Чтобы дать вам практический пример: возьмите множество рациональных чисел (или, как их обычно называют, «дроби»). Рациональное число состоит из числителя и знаменателя (делителя), оба из которых являются целыми числами. Эти рациональные числа эквивалентны:
3/2, 6/4, 24/16
Рациональные числа обычно пишутся так, что gcd (наибольший общий делитель) равен 1. Таким образом, все они будут упрощены до 3/2. 3/2 можно рассматривать как каноническую форму этого набора рациональных чисел.
Так что же означает в программировании термин «каноническая форма»? Это может означать пару вещей. Возьмем для примера этот воображаемый класс:
public class MyInt {
private final int number;
public MyInt(int number) { this.number = number; }
public int hashCode() { return number; }
}
Хеш-код класса MyInt является канонической формой этого класса, потому что для множества всех экземпляров MyInt можно взять любые два элемента m1 и m2, и они будут подчиняться следующему соотношению:
m1.equals(m2) == (m1.hashCode() == m2.hashCode())
Это отношение является сущностью канонической формы. Более распространенный способ - когда вы используете фабричные методы в таких классах, как:
public class MyClass {
private MyClass() { }
public MyClass getInstance(...) { ... }
}
Экземпляры не могут быть созданы напрямую, потому что конструктор является закрытым. Это просто заводской метод. Фабричный метод позволяет вам делать такие вещи:
- Всегда возвращать один и тот же экземпляр (абстрагированный синглтон);
- Просто создайте новый экземпляр с каждым вызовом;
- Возвращать объекты в канонической форме (подробнее об этом через секунду); или
- как хотите.
По существу, фабричный метод абстрагирует создание объекта, и лично я думаю, что было бы интересной языковой особенностью заставить всех конструкторов быть закрытыми для принудительного использования этого шаблона, но я отвлекся.
Что вы можете сделать с помощью этого фабричного метода, так это кешировать ваши экземпляры, которые вы создаете так, что для любых двух экземпляров s1 и s2 они подчиняются следующему тесту:
(s1 == s2) == s1.equals(s2)
Поэтому, когда я говорю, что String не является строго каноническим, это означает, что:
String s1 = "blah";
String s2 = "blah";
System.out.println(s1 == s2); // true
Но, как другие заметили, вы можете изменить это, используя:
String s3 = new String("blah");
и, возможно:
String s4 = String.intern("blah");
Так что вы не можете полностью полагаться на равенство ссылок, поэтому вам вообще не следует полагаться на него.
В качестве пояснения к приведенному выше шаблону я должен указать, что управление созданием объекта с помощью частных конструкторов и фабричных методов не гарантирует равенство ссылок, что означает равенство объектов из-за сериализации. Сериализация обходит обычный механизм создания объекта. Джош Блох (Josh Bloch) освещает эту тему в Effective Java (первоначально в первом издании, когда он говорил о типе enum типа safeafe, который позже стал языковой функцией в Java 5), и вы можете обойти его, перегрузив (приватный) метод readResolve (). Но это сложно. Загрузчики классов тоже будут влиять на проблему.
Во всяком случае, это каноническая форма.