В чем смысл сеттеров и геттеров в java?

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

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

Спасибо заранее! И вот здесь код: Mileage.java:

package gasMileage;

import java.util.Scanner; //program uses class Scanner

public class Mileage 
{
    public int restart;
    public double miles, gallons, totalMiles, totalGallons, milesPerGallon;
    public Mileage(int newRestart, double newMiles, double newGallons, 
                   double newTotalMiles, double newTotalGallons, double newMilesPerGallon)
    {
        setRestart(newRestart);
        setMiles(newMiles);
        setGallons(newGallons);
        setTotalMiles(newTotalMiles);
        setTotalGallons(newTotalGallons);
        setMilesPerGallon(newMilesPerGallon);
    }
    public void setRestart(int newRestart)
    {
        restart = newRestart;
    }
    public int getRestart()
    {
        return restart;
    }
    public void setMiles(double newMiles)
    {
        miles = newMiles;
    }
    public double getMiles()
    {
        return miles;
    }
    public void setGallons(double newGallons)
    {
        gallons = newGallons;
    }
    public double getGallons()
    {
        return gallons;
    }
    public void setTotalMiles(double newTotalMiles)
    {
        totalMiles = newTotalMiles;
    }
    public double getTotalMiles()
    {
        return totalMiles;
    }
    public void setTotalGallons(double newTotalGallons)
    {
        totalGallons = newTotalGallons;
    }
    public double getTotalGallons()
    {
        return totalGallons;
    }
    public void setMilesPerGallon(double newMilesPerGallon)
    {
        milesPerGallon = newMilesPerGallon;
    }
    public double getMilesPerGallon()
    {
        return milesPerGallon;
    }
    public void calculateMileage()
    {
        Scanner input = new Scanner(System.in);
        while(restart == 1)
        {
            System.out.print("Please input number of miles you drove: ");
            miles = input.nextDouble();
            totalMiles = totalMiles + miles;
            System.out.print("Please input number of gallons you used: ");
            gallons = input.nextDouble();
            totalGallons = totalGallons + gallons;
            milesPerGallon = miles / gallons;
            System.out.printf("Your mileage is %.2f MPG.\n", milesPerGallon);
            System.out.print("Would you like to try again? 1 for yes, 2 for no: ");
            restart = input.nextInt();
        }
        milesPerGallon = totalMiles / totalGallons;
        System.out.printf("Your total mileage for these trips is: %.2f.\nYour total gas consumed on these trips was: %.2f.\n", totalMiles, totalGallons);
        System.out.printf("Your total mileage for these trips is: %.2f MPG", milesPerGallon);
    }
}

Mileagetest.java:

package gasMileage;

public class Mileagetest 
{
    public static void main(String[] args) 
    {
        Mileage myMileage = new Mileage(1,0,0,0,0,0);
        myMileage.calculateMileage();
    }
}

И теперь для одного без сеттеров и геттеров:

Testmileage.java:

package gasMileage;

import java.util.Scanner;

public class Testmileage 
{
    int restart = 1;
    double miles = 0, milesTotal = 0, gas = 0, gasTotal = 0, mpg = 0;
    Scanner input = new Scanner(System.in);
    public void testCalculate()
    {
        while(restart == 1)
        {
            System.out.print("Please input miles: ");
            miles = input.nextDouble();
            milesTotal = milesTotal + miles;
            System.out.print("Please input gas: ");
            gas = input.nextDouble();
            gasTotal = gasTotal + gas;
            mpg = miles/gas;
            System.out.printf("MPG: %.2f", mpg);
            System.out.print("\nContinue? 1 = yes, 2 = no: ");
            restart = input.nextInt();
        }
            mpg = milesTotal / gasTotal;
            System.out.printf("Total Miles: %.2f\nTotal Gallons: %.2f\nTotal MPG: %.2f\n", milesTotal, gasTotal, mpg);
    }
}

Testmileagetest.java:

package gasMileage;

public class Testmileagetest 
{

    /**
     * @param args
     */
    public static void main(String[] args) 
    {
        Testmileage test = new Testmileage();
        test.testCalculate();
    }

}

Еще раз спасибо!

Ответы

Ответ 1

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

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

Ответ 2

Инкапсуляция

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

Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );

И это:

Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;

Оба фрагмента кода раскрывают идею о том, что Человек тесно связан с Волосами. Эта жесткая связь затем проявляется во всей кодовой базе, что приводит к хрупкому программному обеспечению. То есть, становится трудно изменить, как хранятся волосы Лица.

Вместо

Person bob = new Person();
bob.setHairColour( Colour.RED );

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

Разница между двумя сценариями такова:

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

Другой способ избежать этой проблемы - вернуть копию цвета волос Боба (как новый экземпляр), который больше не связан с Бобом. Я считаю, что это неэлегантное решение, потому что это означает, что существует поведение, которое желает другой класс, используя волосы Лица, которые больше не связаны с самим Человеком. Это уменьшает возможность повторного использования кода, что приводит к дублированию кода.

Скрытие типов данных

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

public class Person {
  private long hColour = 1024;

  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour << 16 & 255 );
  }
}

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

public class Person {
  private long hColour = 1024;

  public long getHairColour() {
    return hColour;
  }

  /** Cannot exist in Java: compile error. */
  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour<< 16 & 255 );
  }
}

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

Сообщить, не спрашивать

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

Пример файла

Рассмотрим следующий код, слегка измененный из ответа ColinD:

public class File {
   private String type = "";

   public String getType() {
      return this.type;
   }

   public void setType( String type ) {
      if( type = null ) {
        type = "";
      }

      this.type = type;
   }

   public boolean isValidType( String type ) {
      return getType().equalsIgnoreCase( type );
   }
}

Метод getType() в этом случае избыточен и неизбежно (на практике) приведет к дублированному коду, например:

public void arbitraryMethod( File file ) {
  if( file.getType() == "JPEG" ) {
    // Code.
  }
}

public void anotherArbitraryMethod( File file ) {
  if( file.getType() == "WP" ) {
    // Code.
  }
}

Вопросы:

  • Тип данных. Атрибут type не может легко перейти от строки к целому (или другому классу).
  • Подразумеваемый протокол. Время отнимать тип от конкретных (PNG, JPEG, TIFF, EPS) до общего (IMAGE, DOCUMENT, SPREADSHEET).
  • Представляет ошибки. Изменение подразумеваемого протокола не приведет к ошибке компилятора, что может привести к ошибкам.

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

public void arbitraryMethod( File file ) {
  if( file.isValidType( "JPEG" ) ) {
    // Code.
  }
}

Это подразумевает изменение метода доступа get к private:

public class File {
   public final static String TYPE_IMAGE = "IMAGE";

   private String type = "";

   private String getType() {
      return this.type;
   }

   public void setType( String type ) {
      if( type == null ) {
        type = "";
      }
      else if(
        type.equalsIgnoreCase( "JPEG" ) ||
        type.equalsIgnoreCase( "JPG" ) ||
        type.equalsIgnoreCase( "PNG" ) ) {
        type = File.TYPE_IMAGE;
      }

      this.type = type;
   }

   public boolean isValidType( String type ) {
      // Coerce the given type to a generic type.
      //
      File f = new File( this );
      f.setType( type );

      // Check if the generic type is valid.
      //
      return isValidGenericType( f.getType() );
   }
}

Никакой другой код в системе не сломается, если класс File переводит подразумеваемый протокол из определенных типов (например, JPEG) в общие типы (например, IMAGE). Весь код в системе должен использовать метод isValidType, который не дает тип вызывающему объекту, но сообщает классу File проверить тип.

Ответ 3

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

Возьмем, например, файл (игнорируя существование класса File в Java). Этот класс File имеет поле для хранения типа файла (.pdf,.exe,.txt и т.д.)... мы будем игнорировать все остальное.

Первоначально вы решили сохранить его как String без геттеров и сеттеров:

public class File {
   // ...
   public String type;
   // ...
}

Вот некоторые проблемы, связанные с использованием геттеров и сеттеров.

Нет контроля над установкой поля:

Любые клиенты вашего класса могут делать с ними то, что они хотят:

public void doSomething(File file) {
   // ...
   file.type = "this definitely isn't a normal file type";
   // ...
}

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

Неспособность легко изменить внутреннее представление:

Позже вы решите, что хотите сохранить тип файла как экземпляр интерфейса с именем FileType, что позволяет связать некоторое поведение с разными типами файлов. Однако многие клиенты вашего класса уже извлекают и устанавливают типы файлов как String s. Таким образом, у вас будет проблема... вы бы сломали много кода (даже код в других проектах, которые вы не можете исправить самостоятельно, если это библиотека), если вы просто изменили поле с String на a FileType.

Как Getters и Setters решают эту проблему

Теперь представьте, что вместо этого вы создали поле типа private и создали

public String getType() {
   return this.type;
}

public void setType(String type) {
   this.type = type;
}

Управление настройкой свойства:

Теперь, когда вы хотите реализовать требование, чтобы только определенные строки были допустимыми типами файлов и предотвращали другие строки, вы могли просто написать:

public void setType(String type) {
   if(!isValidType(type)) {
       throw new IllegalArgumentException("Invalid file type: " + type);
   }
   this.type = type;
}

private boolean isValidType(String type) {
   // logic here
}

Возможность легко изменить внутреннее представление:

Изменение представления String типа относительно просто. Представьте, что у вас есть enum ValidFileType, который реализует FileType и содержит допустимые типы файлов.

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

public class File {
   // ...
   private FileType type;
   // ...
   public String getType() {
      return type.toString();
   }

   public void setType(String type) {
      FileType newType = ValidFileType.valueOf(type);

      if(newType == null) {
         throw new IllegalArgumentException("Invalid file type: " + type);
      }

      this.type = newType;
   }
}

Так как клиенты класса вызывали getType() и setType(), ничто не меняется с их точки зрения. Изменены только внутренние элементы класса, а не интерфейс, который используют другие классы.

Ответ 4

Идея заключается в том, что если ваши классы клиентов называют функции get/set, вы можете изменить то, что они делают позже, и вызывающие абоненты изолированы. Если у вас есть общедоступная переменная, и я обращаюсь к ней напрямую, вам не удастся добавить поведение позже, когда оно будет доступно или установлено.

Даже в вашем простом примере вы можете воспользоваться им больше.

Вместо использования:

milesPerGallon = miles / gallons;

в calculateMileage()

Вы можете изменить setMiles() и setGallons() для обновления мильPerGallon при их вызове. Затем удалите setMilesPerGallon(), чтобы указать, что это свойство только для чтения.

Ответ 5

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

Посмотрите статью Википедии по этому вопросу.

Ответ 6

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

Mileage m = new Mileage();
m.miles = 5.0;
m.gallons = 10.0;
...

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

Ответ 7

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

class ExpensiveObject {
    private int foo;

    public ExpensiveObject() {
       // Does something that takes a long time.
    }

    public int getFoo() { return foo; }
    public void setFoo(int i) { foo = i; }
}

class ExpensiveObjectProxy extends ExpensiveObject {
    private ExpensiveObject realObject;

    public ExpensiveObjectProxy() { ; }

    protected void Load() {
       if ( realObject == null ) realObject = new ExpensiveObject();
    }

    public int getFoo() { Load(); return realObject.getFoo(); }
    public void setFoo(int i) { Load(); realObject.setFoo(i); }
}

class Main {
    public static void main( string[] args ) {
         // This takes no time, since ExpensiveOjbect is not constructed yet.
         ExpensiveObject myObj = new ExpensiveObjectProxy();

         // ExpensiveObject is actually constructed here, when you first use it.
         int i = myObj.getFoo();
    }
}

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

Ответ 8

В общих сеттерах и геттерах был плохим взломом ранних разработчиков GUI (борланд), чтобы обойти тот факт, что все переменные должны быть частными (действительно, это абсолютно необходимо)

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

Getters и Setters поощряют доступ к данным класса извне класса. Любой код, который обращается к члену класса, должен, вероятно, существовать внутри этого класса (как указано в вашем проекте), и поэтому ему не нужны сеттеры или геттеры. Они ДОЛЖНЫ быть ненужными.

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

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

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

Это позволит работать с некорректным кодом, отличным от OO, позволит вашему классу модифицировать наборы и получать, когда это необходимо, и, при необходимости, использовать геттеры (которые иногда очень полезны).

Ответ 10

Ответ одним словом - интерфейсы.

Интерфейсы допускают методы, а не поля, поэтому установленное соглашение должно иметь методы getX и setX для этой цели.

(И интерфейсы - это способ отделить функциональность от реализации на Java)

Ответ 11

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

Характеристики полезного, разумного использования геттеров и сеттеров:

  • Класс, который используется многими другими классами (скрытие деталей реализации облегчает работу с клиентами)
  • Getters и seters только для полей, для которых они действительно нужны - как можно меньше, большинство полей должны быть частными и использоваться только в пределах их класса
  • Очень немногие сеттеры в целом: изменчивые поля значительно усложняют отслеживание состояния программы, чем поля только для чтения.
  • Getters и seters, которые на самом деле делают что-то помимо доступа к fied, например. которые генерируют исключения для недопустимых значений или обновляют временную метку "последнего изменения" или геттер, который вычисляет значение "на лету", а не полагается на базовое поле

Ответ 12

Продвиньте вперед несколько месяцев. Возможно, ваш учитель попросит вас реализовать удаленную версию класса Milage. Может быть, как веб-сервис, может быть, что-то еще.

Без геттеров/сеттеров вам придется менять каждый код везде, где есть Milage, с геттером/сеттерами вы в значительной степени (в идеальном мире по крайней мере) просто нужно изменить создание типа Милосердия.

Ответ 13

Getters and Setters позволяют создавать полезные ярлыки для доступа и обработки данных внутри объекта. Как правило, это можно рассматривать как альтернативу наличию двух функций с объектом, который используется для получения и установки значения, например:

{
    getValue: function(){
        return this._value;
    },
    setValue: function(val){
        this._value = val;
    }
}

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

function Field(val){
    var value = val;

    this.getValue = function(){
        return value;
    };

    this.setValue = function(val){
        value = val;
    };
}

Добавление методов Setter и Getter Чтобы сделать доступным состояние управляемого bean, вам нужно добавить методы setter и getter для этого состояния. Метод createSalutation вызывает метод bean sgreet, и метод getSalutation извлекает результат. После добавления методов setter и getter, bean завершен. Окончательный код выглядит следующим образом: приветствие пакета;

import javax.inject.Inject;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class Printer {

    @Inject @Informal Greeting greeting;

    private String name;
    private String salutation;

    public void createSalutation() {
        this.salutation = greeting.greet(name);
    }

    public String getSalutation() {
        return salutation;
    }
    public String setName(String name) {
       this.name = name;
    }

    public String getName() {
       return name;
    }
}

Ответ 14

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

  • Я реализовал класс, в котором я использую переменную days.
  • В моем классе никто не может установить значение дней больше 365.
  • Кто-то хочет наследовать от моего класса. (повторное использование кода).
  • Теперь, когда он вводит значение дней более 365, тогда вся функциональность моего класса не будет выполнена.
  • Следовательно, я должен был объявить переменную days как частное поле данных.
  • Теперь, если бы я объявлял поле данных дней как private, тогда никто не мог установить значение дней больше, чем 365, поскольку я бы реализовал функции setter с указанными ограничениями на ввод.