Вопрос

Является ли это полностью противоречащим Java-способу создания структурноподобных объектов?

class SomeData1 {
    public int x;
    public int y;
}

Я вижу класс с аксессорами и мутаторами, больше похожий на Java.

class SomeData2 {
    int getX();
    void setX(int x);

    int getY();
    void setY(int y);

    private int x;
    private int y;
}

Класс из первого примера удобен с точки зрения обозначения.

// a function in a class
public int f(SomeData1 d) {
    return (3 * d.x) / d.y;
}

Это не так удобно.

// a function in a class
public int f(SomeData2 d) {
    return (3 * d.getX()) / d.getY();
}
Это было полезно?

Решение

Это часто обсуждаемая тема.Недостатком создания общедоступных полей в objects является то, что у вас нет контроля над значениями, которые для них установлены.В групповых проектах, где много программистов используют один и тот же код, важно избегать побочных эффектов.Кроме того, иногда лучше вернуть копию объекта field или как-то преобразовать его и т.д.Вы можете имитировать такие методы в своих тестах.Если вы создадите новый класс, вы можете увидеть не все возможные действия.Это похоже на защитное программирование - когда-нибудь геттеры и сеттеры могут оказаться полезными, и их создание / использование не будет стоить больших денег.Так что иногда они бывают полезны.

На практике большинство полей имеют простые методы получения и установки.Возможное решение выглядело бы примерно так:

public property String foo;   
a->Foo = b->Foo;

Обновить:Крайне маловероятно, что поддержка свойств будет добавлена в Java 7 или, возможно, когда-либо еще.Другие языки JVM, такие как Groovy, Scala и т.д., теперь поддерживают эту функцию.- Алекс Миллер

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

Похоже, что многие Java-пользователи не знакомы с Руководством по кодированию Sun Java. которые говорят, что вполне уместно использовать общедоступную переменную экземпляра, когда класс по сути, это «структура», если Java поддерживает «структуру»; (когда нет поведения).

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

Соглашения по Java Code от 1999 года и до сих пор не изменились.

10.1 Предоставление доступа к переменным экземпляра и класса

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

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

http://www.oracle.com/ technetwork / Java / JavaSE / документация / codeconventions-137265.html # 177

http://en.wikipedia.org/wiki/Plain_old_data_structure

http://docs.oracle.com/ JavaSE / 1.3 / Docs / руководство / коллекции / designfaq.html # 28

Используйте здравый смысл действительно. Если у вас есть что-то вроде:

public class ScreenCoord2D{
    public int x;
    public int y;
}

Тогда нет смысла заключать их в методы получения и установки. Вы никогда не будете хранить координаты x, y в целых пикселях другим способом. Геттеры и сеттеры только замедляют вас.

С другой стороны, с помощью:

public class BankAccount{
    public int balance;
}

Возможно, вы захотите изменить способ расчета баланса в какой-то момент в будущем. Это должно действительно использовать геттеры и сеттеры.

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

Для решения проблем изменчивости вы можете объявить x и y как окончательные. Например:

class Data {
  public final int x;
  public final int y;
  public Data( int x, int y){
    this.x = x;
    this.y = y;
  }
}

При вызове кода, который попытается записать в эти поля, будет получена ошибка времени компиляции " поля x объявлено как final; нельзя назначить ".

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

public class DataTest {
    public DataTest() {
        Data data1 = new Data(1, 5);
        Data data2 = new Data(2, 4);
        System.out.println(f(data1));
        System.out.println(f(data2));
    }

    public int f(Data d) {
        return (3 * d.x) / d.y;
    }

    public static void main(String[] args) {
        DataTest dataTest = new DataTest();
    }
}

Не используйте public поля

Не используйте public поля, когда вы действительно хотите обернуть внутреннее поведение класса.Взять java.io.BufferedReader например.В нем есть следующее поле:

private boolean skipLF = false; // If the next character is a line feed, skip it

skipLF считывается и записывается во всех методах чтения.Что, если внешний класс, запущенный в отдельном потоке, злонамеренно изменил состояние skipLF в середине чтения? BufferedReader это определенно пойдет наперекосяк.

Используйте public поля

Возьми это Point класс, например:

class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() {
        return this.x;
    }

    public double getY() {
        return this.y;
    }

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }
}

Это сделало бы вычисление расстояния между двумя точками очень болезненным при написании.

Point a = new Point(5.0, 4.0);
Point b = new Point(4.0, 9.0);
double distance = Math.sqrt(Math.pow(b.getX() - a.getX(), 2) + Math.pow(b.getY() - a.getY(), 2));

Класс не имеет никакого поведения, кроме простых методов получения и установки.Допустимо использовать открытые поля, когда класс представляет собой просто структуру данных и не имеет, и никогда не будет иметь поведения (тонкие добытчики и сеттеры - это нет рассмотренное здесь поведение).Это может быть написано лучше таким образом:

class Point {
    public double x;
    public double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

Point a = new Point(5.0, 4.0);
Point b = new Point(4.0, 9.0);
double distance = Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));

Чистый!

Но помни:У вашего класса должно отсутствовать не только поведение, но и НЕТ причина для того, чтобы вести себя так и в будущем.


(Это именно то, что этот ответ описывает.Процитировать "Условные обозначения кода для языка программирования Java:10.Практика программирования":

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

Таким образом, официальная документация также признает эту практику.)


Кроме того, если вы абсолютно уверены, что участники из вышеперечисленных Point класс должен быть неизменяемым, тогда вы могли бы добавить final ключевое слово для его принудительного применения:

public final double x;
public final double y;

Кстати, структура, которую вы даете в качестве примера, уже существует в библиотеке базовых классов Java как java.awt.Point . У него есть x и y в качестве открытых полей, проверьте его для себя .

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

Re: аку, изб, Джон Топли ...

Остерегайтесь проблем с изменчивостью ...

Может показаться целесообразным не указывать методы получения / установки. Это на самом деле может быть хорошо в некоторых случаях. Реальная проблема с предложенным шаблоном, показанным здесь, является изменчивостью.

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

Плохо, когда этот объект является важной частью внутреннего состояния другого, вы только что раскрыли внутреннюю реализацию. Чтобы предотвратить это, вместо этого должна быть возвращена копия объекта. Это работает, но может вызвать огромное давление GC из-за тонны созданных одноразовых копий.

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

См. " Эффективная Java " Джошуа Блох - Предмет № 13: Неизменность в пользу.

PS: также имейте в виду, что все JVM в эти дни будут оптимизировать getMethod, если это возможно, в результате всего лишь одна инструкция чтения поля.

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

Как отмечали другие выше, есть 2 проблемы, с которыми вы сталкиваетесь, и на самом деле они не поддаются исправлению:

  • Практически любой автоматизированный инструмент в мире Java полагается на соглашение о получении / установке.То же самое, как отмечали другие, для тегов jsp, конфигурации spring, инструментов eclipse и т.д.и т.д...Борьба с тем, что ожидают увидеть ваши инструменты, - это рецепт долгих сеансов блуждания по Google в попытках найти нестандартный способ инициализации spring beans.На самом деле это не стоит таких хлопот.
  • Как только у вас будет ваше элегантно закодированное приложение с сотнями общедоступных переменных, вы, вероятно, обнаружите по крайней мере одну ситуацию, когда их недостаточно - когда вам абсолютно необходима неизменяемость, или вам нужно запустить какое-то событие при установке переменной, или вы хотите создать исключение при изменении переменной, потому что оно устанавливает состояние объекта во что-то неприятное.Затем вы оказываетесь перед незавидным выбором между загромождением вашего кода каким-либо специальным методом везде, где на переменную ссылаются напрямую, и наличием какой-либо специальной формы доступа для 3 из 1000 переменных в вашем приложении.

И это в лучшем случае сценарий работы полностью в автономном частном проекте.Как только вы экспортируете все это в общедоступную библиотеку, эти проблемы станут еще серьезнее.

Java очень многословна, и это заманчиво.Не делай этого.

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

Вы также не можете воспользоваться поддержкой классов, совместимых с именами JavaBean, что может повредить, если вы решите, скажем, использовать класс на странице JavaServer, написанной с использованием языка выражений.

Статья JavaWorld Почему методы получения и установки являются Злая статья также может быть интересна для размышления о том, когда не следует реализовывать методы доступа и мутатора.

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

В этом типе кода нет ничего плохого, если автор знает , что они являются структурами (или шаттлами данных), а не объектами. Многие Java-разработчики не могут различить правильно сформированный объект (не просто подкласс java.lang.Object, но объект true в определенном домене) и ананас. Поэтому они в конечном итоге пишут структуры, когда им нужны объекты и наоборот.

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

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

Кстати, по утверждению e-bartek, весьма маловероятно, IMO, что поддержка свойств будет добавлена в Java 7.

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

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

Очень-очень старый вопрос, но позвольте мне внести еще один небольшой вклад.Java 8 представила лямбда-выражения и ссылки на методы.Лямбда-выражения могут быть простыми ссылками на методы и не объявлять "истинное" тело.Но вы не можете "преобразовать" поле в ссылку на метод.Таким образом

stream.mapToInt(SomeData1::x)

это не законно, но

stream.mapToInt(SomeData2::getX)

есть.

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

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

Здесь я создаю программу для ввода имени и возраста 5 разных людей и выполняю сортировку по возрасту. Я использовал класс, который действует как структура (например, язык программирования C), и основной класс для выполнения всей операции. Ниже я предоставляю код ...

import java.io.*;

class NameList {
    String name;
    int age;
}

class StructNameAge {
    public static void main(String [] args) throws IOException {

        NameList nl[]=new NameList[5]; // Create new radix of the structure NameList into 'nl' object
        NameList temp=new NameList(); // Create a temporary object of the structure

        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

        /* Enter data into each radix of 'nl' object */

        for(int i=0; i<5; i++) {
            nl[i]=new NameList(); // Assign the structure into each radix

            System.out.print("Name: ");
            nl[i].name=br.readLine();

            System.out.print("Age: ");
            nl[i].age=Integer.parseInt(br.readLine());

            System.out.println();
        }

        /* Perform the sort (Selection Sort Method) */

        for(int i=0; i<4; i++) {
            for(int j=i+1; j<5; j++) {
                if(nl[i].age>nl[j].age) {
                    temp=nl[i];
                    nl[i]=nl[j];
                    nl[j]=temp;
                }
            }
        }

        /* Print each radix stored in 'nl' object */

        for(int i=0; i<5; i++)
            System.out.println(nl[i].name+" ("+nl[i].age+")");
    }
}

Приведенный выше код не содержит ошибок и протестирован ... Просто скопируйте и вставьте его в свою среду IDE и ... Вы знаете и что ??? :)

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

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

Как и в большинстве случаев, есть общее правило, а затем есть конкретные обстоятельства. Если вы делаете закрытое захваченное приложение, чтобы знать, как будет использоваться данный объект, вы можете воспользоваться большей свободой, чтобы улучшить видимость и / или эффективность. Если вы разрабатываете класс, который будет использоваться публично другими, не зависящими от вас, тогда склоняйтесь к модели получателя / установщика. Как и во всем, просто используйте здравый смысл. Часто нормально провести начальный раунд с публикой, а затем поменять их на getter / setters.

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

Таким образом, вы начинаете с неперехваченных полей с правильным квалификатором доступа. Когда требования к вашей программе растут, вы присоединяете логику для проверки, делаете копию возвращаемого объекта и т. Д.

Философия getter / setter накладывает затраты на большое количество простых случаев, когда они не нужны.

Является ли аспектный стиль чище или нет, это несколько качественно. Мне было бы легко видеть только переменные в классе и просматривать логику отдельно. На самом деле, смыслом для Apect-ориентированного программирования является то, что многие проблемы являются сквозными, и разделение их на части в самом теле класса не является идеальным (регистрация в качестве примера - если вы хотите регистрировать все, получает от Java, то хочет, чтобы вы написать целую кучу геттеров и синхронизировать их, но AspectJ позволяет вам использовать одну строку).

Проблема IDE - красная сельдь. Это не столько печатание, сколько чтение и визуальное загрязнение, которое возникает в результате get / sets.

Аннотации кажутся с первого взгляда похожими на аспектно-ориентированное программирование, однако они требуют от вас исчерпывающего перечисления pointcut, добавляя аннотации, в отличие от краткой спецификации подстановки в виде подстановочных знаков в AspectJ.

Надеюсь, осведомленность об AspectJ не позволяет людям преждевременно освоиться на динамических языках.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top