EDIT: Я відповів на це запитання, бо багато людей, які навчаються програмуванню, задають це питання, і більшість відповідей дуже технічно грамотні, але їх не так просто зрозуміти, якщо ви новачок. Всі ми були новачками, тому я подумав, що я спробую свої сили у більш дружній відповіді для новичок.
Дві основні з них - поліморфізм та валідація. Навіть якщо це просто дурна структура даних.
Скажімо, у нас такий простий клас:
public class Bottle {
public int amountOfWaterMl;
public int capacityMl;
}
Дуже простий клас, який вміщує, скільки в ньому рідини, і яка її ємність (у мілілітрах).
Що відбувається, коли я роблю:
Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;
Ну, ти не очікував, що це спрацює, правда? Ви хочете, щоб там була якась перевірка обгрунтованості. І ще гірше, що робити, якщо я ніколи не вказав максимальну ємність? О, дорогі, у нас проблема.
Але є й інша проблема. Що робити, якщо пляшки були лише одного типу контейнера? Що робити, якщо у нас було декілька контейнерів, все з ємностями та кількістю рідини? Якби ми могли просто зробити інтерфейс, ми могли б дозволити решті нашої програми прийняти цей інтерфейс, і пляшки, джерікани та всілякі речі просто працювали б між собою. Хіба це не було б краще? Оскільки інтерфейси вимагають методів, це теж непогана річ.
Ми закінчили б щось на кшталт:
public interface LiquidContainer {
public int getAmountMl();
public void setAmountMl(int amountMl);
public int getCapacityMl();
}
Чудово! А тепер ми просто змінюємо Пляшку на це:
public class Bottle extends LiquidContainer {
private int capacityMl;
private int amountFilledMl;
public Bottle(int capacityMl, int amountFilledMl) {
this.capacityMl = capacityMl;
this.amountFilledMl = amountFilledMl;
checkNotOverFlow();
}
public int getAmountMl() {
return amountFilledMl;
}
public void setAmountMl(int amountMl) {
this.amountFilled = amountMl;
checkNotOverFlow();
}
public int getCapacityMl() {
return capacityMl;
}
private void checkNotOverFlow() {
if(amountOfWaterMl > capacityMl) {
throw new BottleOverflowException();
}
}
Я залишу визначення BottleOverflowException як вправу для читача.
Тепер зауважте, наскільки це надійніше. Зараз ми можемо мати справу з будь-яким типом контейнера в нашому коді, приймаючи LiquidContainer замість пляшки. І як ці пляшки поводяться з подібними матеріалами, все може відрізнятися. Ви можете мати пляшки, які записують їх стан на диск, коли він змінюється, або пляшки, які зберігають у базах даних SQL, або GNU знає, що ще.
І все це може мати різні способи поводження з різними дурнями. Пляшка просто перевіряє, і якщо вона переповнена, вона кидає RuntimeException. Але це може бути неправильним. (Існує корисна дискусія про обробку помилок, але я спеціально тримаю це дуже просто. Люди в коментарях, ймовірно, вкажуть на недоліки цього спрощеного підходу.;))
І так, схоже, ми переходимо від дуже простої ідеї до швидкого отримання набагато кращих відповідей.
Зауважте також, що ви не можете змінити ємність пляшки. Тепер він встановлений в камені. Ви можете зробити це за допомогою int, оголосивши його остаточним. Але якби це був список, ви могли б випорожнити його, додати до нього нові речі тощо. Ви не можете обмежити доступ до торкання нутрощів.
Є ще третє, що не всі звернулися до уваги: геттери та сетери використовують виклики методів. Це означає, що вони виглядають як звичайні методи скрізь. Замість того, щоб мати дивний специфічний синтаксис для DTO та інших матеріалів, ви маєте всюди те саме.