Почему этот класс изменчив?

public class Test {
    private final String url;
    public Test(String url) {
        this.url = url;
    }
    public String getUrl() {
        return url;
    }
}

Тестовый класс имеет:

  1. Только одна переменная экземпляра, которая является приватной и конечной.
  2. Нет сеттеров.
  3. Единственный способ инициализировать переменную экземпляра - через конструктор.
  4. И после того, как URL задан, его нельзя изменить даже в getUrl, даже если этот метод переопределен каким-либо подклассом Test.

Но книга, которую я читаю, говорит, что вышеупомянутый тестовый класс изменчив, потому что:

  • Ни один из классов не является окончательным, чтобы его можно было расширить, а подкласс может переопределять методы экземпляра. Но класс Test на самом деле не имеет никаких методов экземпляра, кроме конструктора.

  • И при этом конструктор не является частным.

Не могли бы вы помочь мне понять, почему класс Test изменчив?

Ответ 1

Не гарантируется, что произвольный экземпляр Test будет неизменным, хотя прямые экземпляры Test есть. Но рассмотрим этот подкласс:

public class MutableTest extends Test {
        private int mutable;
        public MutableTest(String url) {
                super(url);
        }

        @Override
        public String getUrl() {
                return super.getUrl() + mutable++;
        }
}

Тогда вы можете написать что-то вроде этого:

Test instance = new MutableTest("http://example.com/");
String firstGet = instance.getUrl();
String secondGet = instance.getUrl();
assertEquals(firstGet, secondGet); // Boom!

Ответ 2

Объект, который получает объект, который, как известно, имеет тип Test будет знать, что объект является неизменным. У объекта, получающего ненулевую ссылку типа Test, однако, не будет никакого определенного языком способа гарантировать, что идентифицируемый объект не будет изменяемым. Некоторые люди считают это серьезным беспокойством. В целом, однако, я не думаю, что это должно быть.

Если класс полезен для наследования, то, как правило, будет легко придумать производный тип, который был бы совершенно неподходящим для любой не надуманной цели. Единственный способ, которым язык мог бы даже попытаться предотвратить это, - это значительно ограничить диапазон полезных вещей, которые могут делать производные типы. Однако, за исключением нескольких специализированных видов классов (обычно связанных с безопасностью), лучше обеспечить, чтобы производные классы могли делать полезные вещи, чем беспокоиться о возможности того, чтобы они делали надуманные и бесполезные вещи.