Сегодня я решил чуть подробнее написать о классах. Это тема явно животрепещущая. Вопросы на эту тему не раз встречались в комментах ко многим статьям в этом блоге. Вопросы на эту тему я часто слышу от своих студентов. И очень хорошо вижу, что многие из них не до конца понимают эту тему. Итак, попробуем разобраться как можно более досконально в этом не очень сложном для понимания вопросе.
И начнем, пожалуй, издалека. Что вообще такое объектно-ориентированное программирование и для чего оно нужно? Чтобы ответить на этот вопрос, давайте посмотрим вокруг. Все, что находится рядом с нами — это объекты. Например на моем столе лежит ноутбук, на котором я набираю эту статью, телефон, на котором я тестирую свои программы, тарелка на которой лежали оладьи и т.д. Каждая из этих вещей обладает какими-то свойствами, каждая из чего-то состоит, каждая в той или иной степени сложна и обладает какими-то возможностями. Таким образом делаем вывод:
Наш мир состоит из объектов. Мы привыкли обращаться с объектами.
Было бы не плохо перенести привычные нам концепции в программирование, правда? Собственно именно именно для этого и появилось ООП. Глубоко вдаваться в теоретические основы не буду. Что такое полиморфизм, наследование, инкапсуляция, абстракция вы можете подробно узнать из wikipedia :-).
Я постараюсь объяснить эти понятия на пальцах (на примерах из жизни).
Наследование — предполагает создание новых классов на основе уже существующих. Например вы разработали свою электробритву. Начали ее производство, а потом решили добавить к ней триммер. Вы не трогаете основную реализацию вашего устройства «электробритва», вы просто добавляете к ней триммер, внося в нее минимальные изменения. При этом основной функционал бритвы не изменяется. таким образом вы унаследовали от класса «бритва» весь имеющийся функционал в класс «бритва с триммером», расширили его и стали создавать новые объекты :).
Теперь пример с кодом:
class Shaver{ int mRazors; Shaver(int razorCount){ this.mRazors=razorCount; } void shave(int stubble){ while(stubble>=mRazors){ stubble-=this.mRazors; } } }
Попробуем его разобрать как можно подробнее. Итак, у нас есть забавный класс бритва (Shaver). У класса, как водится, есть одноименный конструктор, переменная mRazors в которой хранится количество лезвий и метод shave(). Давайте попробуем разобраться что будет происходить, когда мы создаем экземпляр этого класса например вот так:
Shaver myShaver = new Shaver(3);
Как я уже писал здесь, описание класса — есть ни что иное как шаблон, по которому будут создаваться объекты. Итак, по описанию создастся экземпляр класса Shaver (т.е. будут инициализированы все переменные, описанные в классе), а затем вызовется конструктор, который у нас принимает в качестве параметра целочисленное значение — количество лезвий. У нас лезвий три штуки. В конструкторе присутствует такая запись:
this.mRazors=razorCount;
То есть новосозданной бритве мы передали три лезвия. А что такое слово this? Это указатель на текущий объект. Т.е. в данном случае на конкретный экземпляр класса Shaver, для которого вызывается конструктор. По сути это аналог такой вот записи:
mRazors=razorCount;
Для чего же тогда нужно это this в принципе? А представим себе такой вот код:
Shaver(int mRazors){ this.mRazors=mRazors; }
Здесь у нас в качестве параметра в конструктор передается переменная с таким же именем, как и поле класса. И как теперь компилятору понять куда что присваивать? А используя указатель на текущий объект this достаточно просто определить что именно и куда мы хотим присвоить.
Ну и собственно сам процесс бритья описан в методе shave(). Передаем в него в качестве параметра щетину и начинаем брить до тех пор, пока щетина больше чем ноль. Кстати, можем чего лишнего отхватить! 🙂
Но возвращаемся к наследованию. Давайте таки попробуем наследовать наш класс. Для этого создаем вот такой вот:
class ShaverWithTrimmer extends Shaver{ ShaverWithTrimmer(int razorCount) { super(razorCount); this.mRazors++; } }
Это наш класс — наследник бритва с триммером и говорит нам об этом ключевое слово extends. Ура! Что есть в этом классе? Пока что только другой конструктор. А что значит непонятное super()? Это просто вызов конструктора из родительского класса. Т.е. когда мы создадим объект «бритва с триммером» например вот так:
ShaverWithTrimmer myTrimShaver = new ShaverWithTrimmer(3)
произойдет примерно следующее, создастся экземпляр класса ShaverWithTrimmer который унаследует все поля и методы от своего класса родителя. Затем вызовется конструктор класса ShaverWithTrimmer в котором вызовется конструктор родительского класса, а затем после выполнения конструктора родительского класса количество лезвий в нашей бритве увеличится на 1 (это как раз то самое лезвие триммера 🙂 ) (Ух какое предложение, сам офигеваю 🙂 )
Теперь давайте поменяем процесс бритья. Сделаем отдельный метод trim() — подравнять. И переопределим метод бритья. Выглядеть наш класс теперь будет вот так:
class ShaverWithTrimmer extends Shaver { ShaverWithTrimmer(int razorCount) { super(razorCount); this.mRazors++; } void trim() { // Тут как-то ровняем } @Override void shave(int stubble) { super.shave(stubble); this.trim(); } }
Давайте теперь разберемся. @Override — указывает компилятору что мы хотим перегрузить переопределить метод класса родителя. Что такое super.shave(subble)? Это мы вызвали метод класса родителя (фактически Shaver.shave()). А затем this.trim() — подровняли :-). Вот оно наследование в действии.
Полиморфизм — предполагает что по-разному реализованными объектами можно управлять используя один и тот же интерфейс независимо от того как реализованы эти объекты. Возьмем в качестве примера все многообразие мобильных телефонов. Вы можете единообразно совершать звонки с любого телефона: набрать на клавиатуре номер (клавиатура, кстати, может быть реализована в виде обычных кнопок или кнопок на тачскрине), нажать кнопку вызова и телефон постарается позвонить на набранный вами номер. И абсолютно не важно кто производитель телефона, какие у него кнопки и т.д. ведь все телефоны обладают сходим интерфейсом, а значит мы легко можем управлять любым из них зная основные принципы работы с телефонами.
Ну и вернемся к нашему примеру с бритвами в котром возможен такой вот код:
Shaver [] shavers = new Shaver[2]; shavers[0] = new Shaver(3); shavers[1] = new ShaverWithTrimmer(3); for (Shaver s: shavers){ s.shave(0); }
Что тут происходит? Прежде всего создается массив бритв из двух элементов. В первый элемент мы записываем новую бритву без триммера, во второй новую бритву с триммером. Оп-па! Вроде же бритва с триммером и бритва без триммера это разные вещи? А мы так легко их обе запихали в массив элементы которого — просто бритва без триммера. А дело в том, что бритва с триммером наследуется у класса бритва. А значит такое присвоение возможно. Правда напрямую использовать мы можем только те методы и поля, которые есть у класса бритва (но это логично, ведь у класса бритва нет метода trim() вообще!). Дальше в цикле для всех элементов массива мы вызываем метод shave(). (т.е. управляем различными объектам с разной реализацией с помощью одного интерфейса) А самое интересное то, что для для простой бритвы вызовется метод shave из класса Shaver, а для бритвы с триммером метод Shave из класса ShaverWithTrimmer! И это очень удобно!
Т.е. объекты реализованы по разному, а управляются с использованием одного и того же метода, хотя в каждом случае происходят разные вещи (в первом случае ничего не ровняется а во втором еще и подравниваем).
Инкапсуляция — предполагает что вы можете не знать, как именно работает тот или иной объект для того чтобы пользоваться им. Например вам вовсе не обязательно знать детали реализации архитектуры процессора в вашем телефоне или планшете, для того чтоб играть на нем в Angry Birds. Т.е. инкапсуляция — это когда вы скрываете от пользователя то как именно реализованы ваши классы, выставляя напоказ лишь интерфейс для взаимодействия с классом. Ну здесь я думаю примеров из кода даже не надо 🙂
Фууух…
Думаю на сегодня хватит. Если что-то не понятно, если что-то плохо написано, или где-то ошибка пишите в комментах! Буду рад дискуссии!
Спасибо, всё описано очень доходчиво)
Я старался 🙂
Спасибо Большое! В одно время даже начал думать, что новых заметок уже не будет 😉
Будут, еще как будут 🙂
Спасибо автору за статьи:) написано, как говорится, «Русским по белому» 🙂
Хорошая статья.
Прочел два раза.. Тяжко.. =))
Смысл и возможности классов уяснил. Трудно усваивается описание и синтаксис.
Чесно, с ходу не смог бы описать какой-нибудь класс. =)
И опять же- хорошо бы комменты к коду.
Это как вы так решили взяться за разработку для Android, без базовых знаний ООП? Мне реально страшно смотреть на ваш код, если вы даже класс описать не можете.
Ну может просто синтаксис непонятен. конкретно в java
Все мы с чего-то начинали )
Вот человек как раз и начал с Java, а потом возьмется за Android. Надо верить в людей )
Для новичка — все прекрасно и доходчиво…
Хочу больше базовой информации для работы с графикой и SQL очень надо реализовать приложение (красивое для обработки БД).
P.S. БД находится на удаленном сервере, доступ через интернет…
Заранее спасибо за помощь…
Я сам пока что с удаленными б.д. не работал 🙁 но планирую к лету заняться подробнее.
Очень хорошая статья…… умение объяснять доходчиво это талант, я так не умею 🙂
Спасибо! Рад что Вам понравилось!
Очень примечательная статейка. Просто «ням» для новичка в ООП. Спасибо автору!
Отличная статья, спасибо Вам!
И Вам спасибо, что читаете! Надеюсь было интересно и ползено 🙂
Здравствуйте!
не понял вот эту строку:
«for (Shaver s: shavers){
s.shave(0);
}»
объясните плиз синтаксически…
Это аналог цикла foreach 🙂
Можно поподробнее я тоже не понял, я так понял он перебирает все классы Shaver (в нашем случае 2) и обоим в конструктор передает 0. Верно?
Если быть точным, данный код перебирает все объекты в массиве shavers, и для каждого из них вызывает конструктор. При этом для объектов массива, которые являются экземплярами класса Shaver вызовется конструктор из этого класса, а для объектов являющихся экземплярами ShaverWithTrimmer конструктор из этого класса.
Здравствуйте, Dаvimd! Возможно, мой вопрос не входит в рамки данной темы, но очень нужна помощь. Собственно, сам вопрос. Какое отличие между виртуальными и статическими методами? Хотя бы общие принципы. Заранее спасибо!
Статические методы принадлежат не объекту, а самому классу, виртуальные методы для Java вообще не применимы. Вообще виртуальные методы — это те, которые определены таким образом чтоб их конкретная реализация зависела от потомка класса. Т.е. виртуальный метод должен быть реализован и переопределен где-то в классе-потомке. Блин фигово как-то объясняю 🙁
Спасибо!
По моему @Override — это переопределение, а не перегрузка.
Было бы здорово увидеть подобные объяснения на тему «интерфейсов». Чем отличается extens от implements. Что такое private , protected.
Так же очень полезно узнать подробности о таких ключевых словах, как final, static…
Упс простите. Public, private, protected уже рассмотрены.
А вот @Override и вправду надо поправить. Ведь перегрузка и переопределение — это две большие разницы.
Познавательно, благодарю. Вот что-то по функциям Java для новичков ничего на сайте нету.
«Ну и собственно сам процесс бриться описан в методе shave().»
Не «бриться», а «бритья».
Да, действительно, спасибо 🙂
Благодарю за статью. Всё просто и понятно написано. Продолжайте своё дело.