Java Getter и Setters для лучших практик простых свойств

Я знаю, что это распространенный вопрос, и я прочитал несколько ответов, но все они очень субъективны, и я пытаюсь получить ответ на наиболее распространенную практику (т. е. что использовать большую часть времени).

Я работаю на C#.net и переписываю приложение на Java (версия 21). В C# публичное свойство с методами получения и установки функционально совпадает с частным полем с открытыми методами получения и установки (пример C# ниже). Get и set неявно используются при ссылке на свойство. Таким образом, на практике в 99% случаев я использую общедоступное свойство с методами получения и установки, поскольку это дает мне дополнительное преимущество инкапсуляции и не добавляет многословия в код.

Теперь с Java я пытаюсь решить, когда использовать геттеры и сеттеры. Если бы я писал код так же, как на C#, большинство свойств моего Java-объекта были бы частными полями с общедоступными методами получения и установки. Это делает код очень многословным (пример 1 ниже), и я не уверен, что это необходимо/обычно для простых объектов, таких как ответы json (сопоставленные Джексоном) или сущностей.

Итак, с учетом всего сказанного, на практике это наиболее стандартный способ обработки простых свойств, которые просто получают и устанавливают поле в Java (без манипуляций с данными). Например, если бы я увидел корпоративное приложение, какой был бы наиболее стандартный способ обработки примеров 1 и примера 2 ниже. Основываясь на моих исследованиях, я склоняюсь к примеру 1 (сохраняет инкапсуляцию), но я не хочу использовать геттеры и сеттеры повсюду, если на самом деле более распространенной практикой является просто использование общедоступных полей с простыми свойствами. Дайте мне знать, что вы думаете.

С# получить/установить:

namespace SpeedRunAppImport.Model.Entity
{
    public class GameEntity
    {
        public string Name { get; set; }
        
        // Same thing as above
        private string _Name;
        public string Name
        { 
            get
            {
                return _Name;
            }
            set
            {
                _Name = value;
            } 
        }
    }
}

Сущность игры:

@Entity
@Table(name = "tbl_game")
public class Game {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)    
    private Integer id;
    private String name;
    private String code;
    private String abbreviation;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCode() {
         return code;
    }

    public void setCode(String code) {
         this.code = code;
    }

    public String getAbbreviation() {
         return abbreviation;
    }

    public void setAbbreviation(String abbreviation) {
         this.abbreviation = abbreviation;
    }
}

GameResponse (ответ JSON, сопоставленный Джексоном):

public class GameResponse
{
    private String id;
    private String name;
    private String abbreviation;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
         return name;
    }

    public void setName(String name) {
         this.name = name;
    }

    public String getAbbreviation() {
         return abbreviation;
    }

    public void setAbbreviation(String abbreviation) {
         this.abbreviation = abbreviation;
    }
}

Пример 1 (с использованием геттеров/сеттеров):

public void SaveGames(List<GameResponse> games)
{
    _logger.info("Started SaveGames: {@count}", games.size());
    
    var existingGamesVWs = _gameRepo.GetGamesByCode(games.stream().map(x -> x.getId()).toList());            

    var gameEntities = games.stream().map(i -> { var game = new Game();
                              var existingGame = existingGamesVWs.stream().filter(x -> x.code == i.getId()).findFirst().orElse(null);
                              game.setId(existingGame != null ? existingGame.getId() : null);
                              game.setName(i.getName());
                              game.setCode(i.getId());
                              game.setAbbr(i.getAbbreviation());

                              return game;
                            }).toArray(Game[]::new);


    _logger.info(Integer.toString(existingGamesVWs.size()));
}

Пример 2 с использованием полей (предположим, общедоступные поля в объектах выше):

public void SaveGames(List<GameResponse> games)
{
    _logger.info("Started SaveGames: {@count}", games.size());
    
    var existingGamesVWs = _gameRepo.GetGamesByCode(games.stream().map(x -> x.id).toList());             

    var gameEntities = games.stream().map(i -> { var game = new Game();
                              var existingGame = existingGamesVWs.stream().filter(x -> x.code == i.id).findFirst().orElse(null);
                              game.id = existingGame != null ? existingGame.id : null;
                              game.name = i.name;
                              game.code = i.id;
                              game.abbr = i.abbreviation;

                              return game;
                            }).toArray(Game[]::new);
}

Я не совсем понимаю, зачем в качестве примера требуется столько кода. В конце концов это мнение. Геттеры и сеттеры являются нормой, если вы не используете записи.

Dave Newton 09.03.2024 20:28

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

mgmedick 09.03.2024 20:31

… вы спрашиваете о геттерах/сеттерах и публичных членах, когда манипуляции с данными не требуются 🤷‍♂️ YMMV, но кажется, что для демонстрации того, что вы спрашиваете, требуется очень мало (если вообще что-то). Я бы также сказал, что это уже до смерти обсуждалось в мире.

Dave Newton 09.03.2024 20:36

Да, я читал статьи и там действительно много предположений. Используйте записи, не используйте записи из-за неизменности. Используйте lombok, подождите, не используйте lombok, потому что когда-то он был привязан к определенным IDE (я использую код Visual Studio) и может вызвать дополнительную путаницу. Если это простые свойства неизменяемых объектов, просто используйте поля, подождите, не используйте поля, они теряют инкапсуляцию. Мне нужен был простой и краткий ответ «это то, что делает большинство людей», о многих методах написания кода можно спорить до смерти, я ищу, какой стандартный шаблон будет в 2024 году для этой проблемы.

mgmedick 09.03.2024 20:41

Как вы обнаружили, это зависит от мнения и открыто для обсуждения. Делайте то, что подходит для вашей конкретной ситуации и экосистемы.

Dave Newton 09.03.2024 20:47

Правильно, вопрос в том, что чаще всего используют. Это не мнение, а скорее то, что является наиболее распространенным подходом на практике. Если для всего используются геттеры и сеттеры, то хорошо, я просто хотел знать, что наиболее распространено, поскольку у меня нет доступа к корпоративному Java-коду, чтобы увидеть.

mgmedick 09.03.2024 20:52

YMMV, но существует масса Java-кода с открытым исходным кодом. Я полагаю, вы могли бы использовать простой анализатор, чтобы количественно определить, какой шаблон является «наиболее распространенным», если это так важно.

Dave Newton 09.03.2024 21:05

Да, просматривая некоторые примеры на github, я вижу частое использование геттеров/сеттеров, поэтому склоняюсь в этом направлении. Спасибо за отзыв.

mgmedick 09.03.2024 21:36

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

Anonymous 09.03.2024 22:27

Что ж, когда вопрос сформулирован «что является наиболее стандартным / распространенным», можно подумать, что люди дадут вам такой ответ. И, как вы видите ниже, ответ был дан. Вопрос совершенно актуальный, я не ищу интернет-дебатов, я ищу реальный ответ, спасибо.

mgmedick 09.03.2024 23:01

«Это делает код очень многословным…» — для этого и нужен javadoc. Фундаментальная концепция объектно-ориентированной разработки заключается в том, что когда вам нужно получить краткое описание возможностей класса, вы должны смотреть на документацию (контракт), а не на внутреннюю реализацию. Создаваемая C# документация неуклюжа и неуклюжа (по крайней мере, так было в последний раз), но javadoc оптимизирован и организован.

VGR 10.03.2024 18:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
11
438
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Самый распространенный подход или соглашение в Java — использовать геттеры и сеттеры, чтобы соблюдать принцип инкапсуляции и упростить макетирование/тестирование классов, как и в любом другом языке. Некоторые языки долгое время делали это проще и менее многословным, хорошими примерами этого являются C# и Kotlin.

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

Несколько способов добиться этого:

1. Явное объявление геттеров и сеттеров (стиль JavaBeans)
public class Example {
   private final String value;

   public Example(String value) {
       this.value = value;
   }

   public String getValue() {
       return this.value;
   }

   public void setValue(String value) {
       this.value = value;
   }
}
2. Используйте обработку аннотаций Lombok, которая генерирует шаблон во время компиляции и может использоваться на уровне класса или уровне свойства.
@Getter
@Setter 
// or @Data, which adds more functionality (e.g. equals, hashCode, toString)
@AllArgsConstructor
public class Example {
   private final String value;
}
3. Используйте Records в Java 16+, лучший эквивалент класса данных на других языках.
record Example(String value) {}

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

Понятно, спасибо за четкий и краткий ответ. Я просмотрел записи, но из-за их неизменяемости не думаю, что их можно использовать большую часть времени. Как вы сказали для ответов JSON (вероятно, будете использовать) или, может быть, для сущностей БД, я мог бы их использовать. Но часто я извлекаю объект из БД, обновляю его свойство и отправляю обратно в БД для сохранения, поэтому с неизменностью может возникнуть проблема. Звучит как вариант 1 или 2, Ломбок сначала отключил меня, потому что это плагин IDE, и я надеялся, что мой код останется независимым. Похоже, что вариант 1 лучше всего, еще раз спасибо.

mgmedick 09.03.2024 21:34

Пожалуйста. Это верный момент в отношении записей, хотя даже в этом случае общепринятой практикой сохранения объектов неизменяемыми и потокобезопасными является просто создание еще одной записи на основе оригинала с необходимыми изменениями, отдающими предпочтение безопасности над производительностью. Опять же, классы данных Котлина упрощают это, например example.copy(value = "other"). Java не предлагает ничего подобного (во всяком случае, изначально).

rph 09.03.2024 21:42

Да, хороший момент, я вижу, что это довольно распространенная практика с сущностями. Просто это был небольшой дизайнерский шок от C#, где вы используете свойство, являющееся общедоступным, и особо о нем не думаете. Вероятно, одно из самых больших отличий дизайна, которое нужно ко всему привыкнуть, составляет почти 1:1 (даже с потоком вместо linq и т. д.). Так что пока мне просто придется привыкнуть к геттерам/сеттерам :). Еще раз спасибо.

mgmedick 09.03.2024 21:53

Другие вопросы по теме