Неизменяемые объекты в Java

Я только что закончил изучать неизменные объекты и их преимущества, поэтому я решил создать один из своих. Вот мой класс сотрудников, который расширяет класс человека. Он не изменен, поскольку у меня есть получатели для моих изменяемых объектов даты.

package objects.objects;

import java.util.Date;

public class Person {

    private final String name;
    private final Date dateOfBirth;

    Person(String name,Date dateOfBirth){
        this.name=name;
        this.dateOfBirth=dateOfBirth;

    }
    public String getName() {
        return name;
    }
    public Date getDateOfBirth() {
        return dateOfBirth;
    }
}

Employee Класс:

package objects.objects;

import java.util.Date;

public final class Employee extends Person{

    private final String empolyeeID;
    private final Designation designation;
    private final double salary;
    private final Date dateOfJoining;


    public Employee(String Name,Date dateOfBirth,String employeeID,Designation designation,double salary,Date dateOfJoining)
    {
        super(Name,dateOfBirth);
        this.empolyeeID=employeeID;
        this.designation=designation;
        this.salary=salary;
        this.dateOfJoining=dateOfJoining;

    }


    public String getEmpolyeeID() {
        return empolyeeID;
    }


    public Designation getDesignation() {
        return designation;
    }


    public double getSalary() {
        return salary;
    }


    public Date getDateOfJoining() {
        return dateOfJoining;
    }
}

Обозначение перечисления:

package objects.objects;

public enum Designation {
    ASSOCIATE,SENIOR_ASSOCIATE,MANAGER,SENIOR_MANAGER,DIRECTOR
}

В документации Oracle Java говорится:

  

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

Кроме того, в документе Oracle говорится:

  

Не позволяйте подклассам переопределять методы. Самый простой способ сделать   это объявить класс окончательным. Более сложный подход   состоит в том, чтобы сделать конструктор закрытым и построить экземпляры на заводе   Методы.

Что делать, если я хочу, чтобы мой класс был подклассифицирован? Какой вред есть, если я не делаю свой класс окончательным или не имеет частного конструктора?

Означает ли это, что я не должен использовать getters или, возможно, передать копию (новый объект Date в getter?) или есть какой-то другой подход, который мне не хватает?

Кроме того, можете ли вы указать, кроме даты, являющейся изменчивой ссылкой на объекты? Есть ли другой недостаток в моем коде, который нарушает изменчивую политику или любые другие улучшения как таковые?

35 голосов | спросил Ishan Soni 14 +04002014-10-14T15:55:31+04:00312014bEurope/MoscowTue, 14 Oct 2014 15:55:31 +0400 2014, 15:55:31

8 ответов


32

Все ваши поля являются частными и окончательными, что является первым шагом к неизменному. Это хорошо.

Класс Date не является неизменным, поэтому, например, у вас есть проблема с методом:

public Date getDateOfBirth() {
    return dateOfBirth;
}

, потому что кто-то мог сделать:

employee.getDateOfBirth().setYear(1900);

и вдруг ваш работник в возрасте ... много.

В документации, описанной в oracle, говорится, что если у вас есть изменяемый контент, вы должны вернуть копию этих данных. Это часто называют «защитной копией»:

public Date getDateOfBirth() {
    return new Date(dateOfBirth.getTime());
}

Обновление: , ваш метод должен быть final, чтобы никакие подклассы не могли изменять поведение getDateOfBirth() (то же самое верно для getName()). Если этот метод не является окончательным, то подкласс может переопределить этот метод и сделать его таким образом, чтобы изменить имя name /dateOfBirth.

Обратите внимание, что конструктор для Date в этом случае принимает значение long. Это приводит к предположению, что неизменяемый класс должен хранить long вместо Date. long неизменен в любом случае, если окончательный.

public class Person {

    private final String name;
    private final long dateOfBirth;

    .....

    public Date getDateOfBirth() {
        return new Date(dateOfBirth);
    }

}

Кроме того, неизменность выглядит прекрасно. Хорошо, даже.

Имя пакета, которое вы выбрали, хотя ... отсутствует: objects.objects. Вы не могли назвать свой пакет лучше?

ответил rolfl 14 +04002014-10-14T16:12:30+04:00312014bEurope/MoscowTue, 14 Oct 2014 16:12:30 +0400 2014, 16:12:30
16

В дополнение к защите getDateOfBirth как @rolfl уже объяснено, не менее важно также создавать защитные копии в конструкторах:

Person(String name, Date dateOfBirth) {
    this.name = name;
    this.dateOfBirth = new Date(dateOfBirth.getTime());
}

В противном случае, если вы просто this.dateOfBirth = dateOfBirth;, то кто-то может написать этот код:

Person jack = new Person("Jack", date);
date.setYear(1900);  // ouch!

Так как изменяемые объекты, такие как Date, настолько сложны, гораздо безопаснее хранить long значение dateOfBirth.getTime() вместо самого Date. Таким образом, вам просто не нужно забывать использовать защитные копии, ваше значение long всегда останется неизменным, в отличие от объектов Date.

Если вам нужен самый безопасный возможный неизменяемый класс, то вам обязательно нужно пойти с этой опцией и сохранить значения long, представляющие даты. То же самое относится к dateOfJoining в Employee.

ответил janos 14 +04002014-10-14T17:56:47+04:00312014bEurope/MoscowTue, 14 Oct 2014 17:56:47 +0400 2014, 17:56:47
15

Этот ответ не о неизменности, так как @rolfl покрыл его.

Вы должны поместить пробелы между вашим = в ваших назначениях, легче определить =, чтобы упростить просмотр назначения.

Между вашими геттерами существует много белых пробелов из Employee, должно быть достаточно, иначе код занимает много места для ничего.

У вас есть параметр PascalCased вместо camelCased, это что-то конкретное java, которое делает невозможным назвать его name? В противном случае вы должны называть его .. name!

ответил IEatBagels 14 +04002014-10-14T16:26:38+04:00312014bEurope/MoscowTue, 14 Oct 2014 16:26:38 +0400 2014, 16:26:38
8

Как упоминалось в разных ответах, Date является изменчивой структурой, поэтому вам нужно копировать ее каждый раз, когда вы хотите поделиться ею. Мой совет не используется вообще, и используйте неизменяемую структуру, такую ​​как Joda Data.

Тип зарплаты double, и это неправильно, вы должны использовать BigDecimal за деньги, а не удваиваете, и убедитесь, что вы используете правильный конструктор, который является который принимает String как параметр

BigDecimal salary =  new BigDecimal("502.34");

BigDecimal сам неизменен, поэтому вам не нужно его копировать.

ответил Sleiman Jneidi 14 +04002014-10-14T17:01:14+04:00312014bEurope/MoscowTue, 14 Oct 2014 17:01:14 +0400 2014, 17:01:14
4
  

Что делать, если я хочу, чтобы мой класс был подклассифицирован? Какой вред есть, если я не делаю свой класс окончательным или не имеет частного конструктора?

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

  

Означает ли это, что я не должен использовать геттеры или, может быть, передать копию (новый объект Date в getter?) или есть какой-то другой подход, который мне не хватает?

Неизменность - это не вопрос того, есть ли у вашего класса геттеры; в частности, класс без сеттеров не делает неизменяемого класса. В своем самом чистом определении неизменность является отсутствием изменчивости. Объект неизменен, если и только если нет способа изменить его состояние.

В Java это означает, что документация говорит:

  • Не сообщайте ссылки на изменяемые объекты. Потому что это означает, что другой код может изменить ваше внутреннее состояние.
  • Не позволяйте подклассам переопределять методы. Поскольку подклассы могут изменять поведение и, следовательно, наблюдаемое состояние объекта. Рассмотрим:

    public class ImmutablePoint {
        // mutable class; share no references!
        private final Point p;
    
        public ImmutablePoint(Point p) {
            // copy values, do not store reference
            this.p = new Point(p.x, p.y);
        }
    
        public double getX() { return p.getX(); }
        public double getY() { return y.getY(); }
    }
    
    // no longer immutable
    public class ExtensionPoint extends ImmutablePoint {
        private Point offset = new Point();
    
        public ExtensionPoint(Point origin) {
            super(origin);
        }
    
        public void setOffset(int x, int y) {
            offset.x = x;
            offset.y = y;
        }
    
        public double getX() { return super.getX() + offset.getX(); }
        public double getY() { return super.getY() + offset.getY(); }
    }
    
    public class JitteryPoint extends ImmutablePoint {
        public JitteryPoint(Point origin) {
            super(origin);
        }
    
        // Here there be shenanigans
        public double getX() { return super.getX() + Math.random(); }
        public double getY() { return super.getY() + Math.random(); }
    }
    
ответил JvR 14 +04002014-10-14T23:09:56+04:00312014bEurope/MoscowTue, 14 Oct 2014 23:09:56 +0400 2014, 23:09:56
4

Я хотел бы упомянуть еще один аспект, который не упоминался большинством других, и это ваш конструктор

public Employee(String, Date, String, Designation, double, Date)

Чем больше аргументов вы добавляете к конструктору, тем сложнее его использовать, потому что порядок параметров уже не ясен. Когда у вас есть несколько параметров одного и того же типа, например в этом случае два String s и два Date s, становится очень легко их смешивать, что вызывает ошибку, которая трудно заметить при чтении кода.

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

Employee.Builder builder = new Employee.Builder();
builder.setName(name);
builder.setDateOfBirth(dateOfJoining);
builder.setEmployeeID(id);
builder.setDesignation(designation);
builder.setSalary(salary);
builder.setDateOfJoining(birthdate);

Person p = builder.build();

Строитель собирает все атрибуты, заданные в частных переменных. Когда вы вызываете build(), он вызывает конструктор Employee с установленными ранее атрибутами и возвращает результат. Когда один атрибут еще не установлен, вы можете использовать разумное значение по умолчанию или вызывать исключение, если значение по умолчанию не имеет смысла.

Кстати, вы видите ошибку, указанную в приведенном выше коде? Вы могли бы определить это, если бы я вызвал конструктор напрямую?

Если у вас есть каждый набор Employee.Builder return this, вы также можете использовать очень элегантный синтаксис, известный как « свободный интерфейс ":

 Person p = new Employee.Builder().setName(name)
                                  .setDateOfBirth(birthdate)
                                  .setEmployeeID(id)
                                  .setDesignation(designation)
                                  .setSalary(salary)
                                  .setDateOfJoining(dateOfJoining)
                                  .build();
ответил Philipp 15 +04002014-10-15T12:45:49+04:00312014bEurope/MoscowWed, 15 Oct 2014 12:45:49 +0400 2014, 12:45:49
2

Что-то из nit в свете других очень хороших ответов, но вы последовательно опечатали employee как empolyee.

ответил fluffy 15 +04002014-10-15T10:09:05+04:00312014bEurope/MoscowWed, 15 Oct 2014 10:09:05 +0400 2014, 10:09:05
0

Вопреки тому, что говорят некоторые люди, нет ничего принципиально неправильного в том, что унаследованный неизменный класс при условии, что контракт наследования диктует, что если два или более экземпляра когда-либо считаются эквивалентными, они всегда и навсегда считаются эквивалентными; далее класс должен корректно работать без жалобы или явно измененного поведения, если некоторые или все ссылки на объект заменяются ссылками на объект, который считается «эквивалентным» . Класс не сможет предотвратить определение незаконных производных классов, которые нарушают его договор наследования, если его методы не объявлены final, но поскольку он не сможет предотвратить определение незаконного производные классы, даже если его методы final, поэтому использование методов final не имеет особого значения. Важно то, что контракт наследования указывает, что все производные классы должны быть полностью неизменными не только в их унаследованных полях, но также и в всех наблюдаемых характеристиках, унаследованных и других . Если производный класс не соблюдает этот контракт, код, который использует производный класс, может быть неисправен, но ошибка будет полностью связана с производным классом, который нарушает контракт.

Вероятно, хорошая идея создать неизменяемые типы final, если вы не хотите точно указывать, что потребуется и чего ожидать от любых производных типов. Однако существует множество ситуаций, когда может быть полезно иметь абстрактный базовый класс или интерфейс, который указывает, что все законные производные классы или реализации будут неизменными. Например, можно определить класс или интерфейс Matrix2d с членами для получения измерений или прочитать ячейку в любой (строке, столбце) координате. Хотя наиболее распространенная реализация может использовать 2d-массив в качестве хранилища резервных копий, существует множество различных способов, которыми производные классы могут хранить информацию. Если Matrix2d был final, который использовал массив в качестве хранилища резервных копий, тогда все объекты, которые можно использовать в качестве типа Matrix2d, должны были иметь Элемент backing-store для каждой ячейки, даже если 99% [или, если на то пошло 100%] были пустыми. Создание нечеткого класса позволило бы определить такие производные, как DiagonalMatrix2d, который мог бы использовать гораздо более простой резервный магазин.

ответил supercat 15 +04002014-10-15T02:12:49+04:00312014bEurope/MoscowWed, 15 Oct 2014 02:12:49 +0400 2014, 02:12:49

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132