Struct как объекты в Java

полностью ли это против способа 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();
}

20 ответов


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

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

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

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


похоже, что многие люди Java не знакомы с руководствами по кодированию Sun Java которые говорят, что вполне уместно использовать переменную открытого экземпляра, когда класс по сути, "структура", если Java поддерживает" структуру " (когда нет поведения).

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

соглашения кода Java от 1999 и все равно без изменений.

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

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

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

http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177

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

http://docs.oracle.com/javase/1.3/docs/guide/collections/designfaq.html#28


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

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

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

С другой стороны, с:

public class BankAccount{
    public int balance;
}

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

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


для решения проблем изменчивости вы можете объявить 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 объявлено окончательным; не может быть назначено".

клиентский код может иметь удобство "короткой руки", которое вы описали в своем сообщении

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();
    }
}

Re: aku, izb, John Topley...

следите за проблемами изменчивости...

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

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

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

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

см.: "Эффективная Java " Джошуа блох -- пункт #13: пользу неизменности.

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


не используйте 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 как java.awt.Point. Он имеет x и y в качестве общедоступных полей, проверьте это для себя.

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


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

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

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

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

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


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

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

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

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


нет ничего плохого в этом типе кода, при условии, что автор знает это структуры (или челноки данных) вместо объектов. Многие разработчики Java не могут сказать разницу между хорошо сформированным объектом (а не только подклассом java.ленг.Объекта, а true "объект" в определенном домене) и ананас. Следовательно, они в конечном итоге пишут структуры, когда им нужны объекты и viceversa.


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

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

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


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

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


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

stream.mapToInt(SomeData1::x)

не является законным, но

stream.mapToInt(SomeData2::getX)

есть.


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


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


здесь я создаю программу для ввода имени и возраста 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.


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


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


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

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

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

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

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

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

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