Не секрет, что в Java существует соглашение о свойствах: для доступа к приватным полям класса нужно сделать соответствующие get/set методы. Многие начинающие java программисты часто недоумевают, какой в этом смысл, ведь почти всегда эти методы не делают ничего иного, кроме доступа к этим полям. А поскольку эти методы являются public и доступны из любого кода, даже за пределами пакета, так почему бы просто не сделать поля публичными и напрямую назначать и читать их значения?

Рассмотрим данный случай на конкретном примере:

public class City {
    public String name;
}
City city = new City();
city.name = "Moscow";
System.out.println(city.name);
public class Country {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
Country country = new Country();
country.setName("Russia");
System.out.println(country.getName());

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

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

public class Country {
    private String name;
    private City capital;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        if(name != null)
            this.name = name;
        else
            this.name = "";
    }
    public City getCapital() {
        if(capital != null)
            return capital.clone();
        else
            return null;
    }
    public void setCapital(City capital) {
        this.capital = capital;
    }
}

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

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