Что такое полиморфизм приведите примеры из реальной жизни
Привет! Сегодня мы заканчиваем серию лекций о принципах ООП. На этом занятии поговорим о полиморфизме. Полиморфизм — это возможность работать с несколькими типами так, будто это один и тот же тип. При этом поведение объектов будет разным в зависимости от того, к какому типу они принадлежат. Давай рассмотрим это утверждение подробнее. Начнем с первой части: «возможность работать с несколькими типами так, как будто это один и тот же тип». Как разные типы могут при этом быть одним и тем же? Звучит немного странно :/ На самом деле все просто. К примеру, такая ситуация возникает при обычном использовании наследования. Посмотрим, как это работает. Допустим, у нас есть простой родительский класс Cat с единственным методом run() — «бежать»: А теперь создадим три класса, которые наследуются от Cat : Lion , Tiger и Cheetah , обозначающие льва, тигра и гепарда. Итак, у нас есть 3 класса. Давай смоделируем ситуацию, при которой мы сможем работать с ними так, как будто это один и тот же класс. Представим, что кто-то из наших котов заболел, и ему нужна помощь доктора Айболита. Попробуем создать класс Aibolit , который будет способен лечить и львов, и тигров, и гепардов. Казалось бы, проблема решена — класс написан и готов к работе. Но что мы будем делать, если захотим расширить нашу программу? Сейчас у нас всего 3 вида: львы, тигры, и гепарды. Но в мире существует больше 40 видов кошек. Представь, что будет, если мы добавим в программу отдельные классы для манулов, ягуаров, мейн-кунов, домашних кошек и всех остальных. Сама программа, конечно, будет функционировать, но вот в класс Aibolit придется постоянно добавлять новые методы для лечения каждого вида кошек, и в итоге он разрастется до невиданных размеров. Здесь и проявляется свойство полиморфизма — «возможность работать с несколькими типами так, как будто это один и тот же тип». Нам не нужно создавать бесчисленное количество методов, которые будут делать одно и то же — лечить кошку. Достаточно будет одного метода для всех случаев сразу: В метод healCat() мы можем передавать и объекты Lion , и Tiger и Cheetah — они все являются Cat : Вывод в консоль: Вот так наш класс Айболит может работать с разными типами, как будто это один и тот же тип. Теперь давай разберемся со второй частью: «при этом поведение объектов будет разным в зависимости от того, к какому типу они принадлежат». Здесь тоже все просто. В природе все кошки бегают по-разному. Как минимум, у них различается скорость бега. Среди наших трех питомцев гепард — самый быстрый, а тигр и лев бегают медленнее. То есть у них отличается поведение. Полиморфизм не только дает нам возможность использовать разные типы как один. Он при этом еще позволяет не забывать об их отличиях и сохраняет специфическое для каждого из них поведение. Это можно понять на таком примере. Допустим, после успешного выздоровления наши коты решили на радостях немного побегать. Добавим это в наш класс Aibolit : Попробуем выполнить тот же код для лечения трех зверей: И вот как будет выглядеть результат: Здесь мы наглядно видим, что специфическое поведение наших объектов сохранилось, хотя мы передали всех троих зверей в метод, «обобщив» каждого из них до Cat . Благодаря полиморфизму Java прекрасно помнит, что это не просто три каких-то кота, а именно лев, тигр и гепард, которые бегают по-разному. В этом заключается главное преимущество использования полиморфизма — гибкость . Когда нам нужно создать какой-то общий для многих типов функционал — львы, тигры и гепарды превращаются просто в «котов». Все животные разные, но в некоторых ситуациях — кот есть кот, без разницы к какому виду он относится:) Вот тебе видеоподтверждение.
Когда же это «обобщение» не требуется, и нам наоборот нужно, чтобы поведение у видов отличалось, каждый тип ведет себя по-своему. Благодаря полиморфизму, ты создаешь единый интерфейс (набор методов) для широкого набора классов. За счет этого снижается сложность программ. Если бы мы даже расширили программу до 40 видов кошек, у нас все равно сохранился бы максимально простой интерфейс — один метод run() для всех 40 кошек.
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ СДЕЛАТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Я правильно понимаю, что без наследования нет полиморфизма? Или есть какие то другие ситуации?
Самая приятная лекция. . благодаря вставке видео про "кот есть кот"😆😆😆
15 лет спустя понял почему Chester любит Cheetas))
После объявления class Main код посыпался и в иде не работает(
Мне одному кажется, что наглядней было бы написать пример вот так: Cat simba = new Lion(); Cat sherekhan = new Tiger(); Cat chester = new Cheetah();
Что нам даёт полиморфизм в Java?
- Позволяет подменять реализации объектов. На этом основано тестирование.
- Обеспечивает расширяемость программы — становится гораздо легче создавать задел на будущее. Добавление новых типов на основе существующих — наиболее частый способ расширения функциональности программ, написанных в ООП стиле.
- Позволяет объединять объекты с общим типом или поведением в одну коллекцию или массив и управлять ими единообразно (как в наших примерах, заставляя всех танцевать – метод dance или плыть – метод swim ).
- Гибкость при создании новых типов: вы можете выбирать реализацию метода из родителя или переопределить его в потомке.
Напутствие в дорогу
Принцип полиморфизма — это очень важная и обширная тема. Она охватывает едва ли не половину ООП Java и добрую часть основ языка. Отделаться определением этого принципа на интервью не получиться. Незнание или непонимание его, скорее всего, поставит точку на собеседовании. Поэтому не поленитесь проверить свои знания перед испытанием и освежить их в случае необходимости.
Полиморфизм
На сайте Oracle в их официальном Tutorial есть отдельный раздел: "Polymorphism". Воспользуемся Java Online Compiler'ом чтобы увидеть, как работает полиморфизм в Java. Например, у нас есть некоторый абстрактный класс Number, представляющий число в Java. Что он позволяет? У него есть некоторые базовые методы, которые будут у всех наследников. Тот кто наследуется от Number буквально говорит - "Я число, со мной можно работать как с числом". Например, для любого наследника можно при помощи метода intValue() получить его Integer значение. Если посмотреть java api для Number, то видно, что метод abstract, то есть данный метод каждый наследник Number должен реализовать сам. Но что нам это даёт? Посмотрим на пример: Как видно из примера, благодаря полиморфизму, мы можем написать метод, который на вход будет принимать аргументы любого типа, который будет наследником Number (Number мы не можем получить, т.к. это абстрактный класс). Как было в примере с плеером, в данном случае мы говорим, что хотим работать с чем-то, как с Number. Мы знаем, что любой, кто является Number, обязан уметь предоставить своё integer значение. И нам этого достаточно. Мы не хотим вдаваться в подробности реализации конкретного объекта и хотим работать с этим объектом через общие для всех наследников Number методы. Список методов, которые нам будут доступны, будет определён по типу во время компиляции (как это мы видели ранее в байткоде). В данном случае у нас тип будет Number. Как видно из примера, мы передаём различные числа разного типа, то есть на вход метод summ будет получать и Integer, и Long, и Double. Но всех их объединяет то, что они наследники от абстрактного Number, а следовательно переопределили у себя поведение в методе intValue, т.к. каждый конкретный тип знает, как этот тип нужно приводить к Integer. Такой полиморфизм реализован через так называемое переопределение, по английски Overriding.
Переопределение (Overriding) или динамический полиморфизм. Итак, начнём с того, что сохраним файл HelloWorld.java со следующим содержанием: Выполним javac HelloWorld.java и javap -c HelloWorld :
С переопределением так же связано такое понятие, как "ковариантность" (Covariance). Рассмотрим пример: Несмотря на внешнюю заумность смысл сводится к тому, что при переопределении мы можем вернуть не только тот тип, который был указан в предке, но и более конкретный тип. Например, предок возвращал Number, а мы можем вернуть Integer - наследника от Number. Тоже касается и исключений, объявленных в throws у метода. Наследники могут переопределить метод и уточнить бросаемое исключение. Но не могут расширить. То есть если родитель бросает IOException, то мы можем бросать более точное EOFException, но не можем бросать Exception. Аналогично, нельзя сужать область видимости и нельзя накладывать дополнительные ограничения. Например, нельзя добавлять static.
Полиморфизм в Java на собеседовании
Вопросы, посвященные ООП — неотъемлемая часть технического интервью на позицию Java-разработчика в ИТ-компанию. В этой статье поговорим об одном из принципов ООП – полиморфизме. Мы остановимся на аспектах, о которых часто спрашивают на собеседованиях, а также приведём небольшие примеры для наглядности.
Сокрытие (Hiding)
Есть ещё такое понятие, как "сокрытие". Пример: Это довольно очевидная вещь, если подумать. Статические члены класса относятся к классу, т.е. к типу переменной. Поэтому, логично, что если child имеет тип Parent, то и метод будет вызван у Parent, а не у child. Если мы посмотрим байткод, как мы уже делали ранее, то увидим, что вызов статического метода осуществляется при помощи invokestatic. Это объясняет JVM, что надо смотреть на тип, а не по таблице методов, как это делал invokevirtual или invokeinterface.
Что такое полиморфизм?
Полиморфизм – это способность программы идентично использовать объекты с одинаковым интерфейсом без информации о конкретном типе этого объекта. Если вы ответите на вопрос, что такое полиморфизм, таким образом, вас, скорее всего, попросят объяснить, что вы имели ввиду. Лишний раз, не напрашиваясь на кучу дополнительных вопросов, разложите интервьюеру все по полочкам.
Начать можно с того, что подход ООП подразумевает построение Java-программы на основе взаимодействии объектов, которые базируются на классах. Классы – это заранее написанные чертежи (шаблоны), по которым будут созданы объекты в программе. Причем класс всегда имеет определенный тип, который при хорошем стиле программирования своим названием «подсказывает» о своем предназначении. Далее можно отметить, что поскольку Java относится к строго типизированным языкам, в программном коде всегда нужно указать тип объекта при объявлении переменных. К этому добавьте, что строгая типизация повышает безопасность кода, и надежность программы и позволяет еще на стадии компиляции предотвратить ошибки несовместимости типов (например, попытку разделить строку на число). Естественно, компилятор должен «знать» объявляемый тип – это может быть класс из JDK или созданный нами собственноручно. Обратите внимание интервьюера, что при работе с программным кодом мы можем использовать не только объекты типа, который мы назначили при объявлении, но и его наследников. Это важный момент: мы можем работать со многими типами, как с одним (при условии, что эти типы являются производными от базового типа). Также это значит, что, объявив переменную типа суперкласса, мы можем присвоить ей значение одного из наследников. Интервьюеру понравится, если вы приведёте пример. Выберите какой-нибудь объект, который может быть общим (базовым) для группы объектов и унаследуйте от него парочку классов. Базовый класс: В наследниках переопределите метод базового класса: Пример полиморфизма в Java и использования объектов в программе: На коде метода main покажите, что в строках: мы объявили переменную типа суперкласса, а присвоили ей значение одного из наследников. Скорее всего, вас спросят, почему компилятор не будет «ругаться» на несоответствие типов, объявленных слева и справа от знака присваивания, ведь в Java строгая типизация. Поясните, что тут работает восходящее преобразование типов — ссылка на объект интерпретируется, как ссылка на базовый класс. Причем компилятор, встретив в коде такую конструкцию, делает это автоматически и неявно. На основе кода примера можно показать, что тип класса, объявленный слева от знака присваивания Dancer , имеет несколько форм (типов), объявленных справа BreakDankDancer , ElectricBoogieDancer . Каждая из форм может иметь собственное уникальное поведение для общей функциональности, определенной в суперклассе — метод dance . То есть метод, объявленный в суперклассе, может быть по-разному реализован в наследниках. В данном случае мы имеем дело с переопределением метода, а это именно то, что создает многообразие форм (поведений). Увидеть это можно, запустив код метода main на выполнение: Вывод программы Я Антон, мне 18 лет. Я танцую как все. Я Алексей, мне 19 лет. Я танцую брейк-данс! Я Игорь, мне 20 лет. Я танцую электрик буги! Если не использовать переопределение в наследниках, то мы не получим различного поведения. Например, если для наших классов BreakDankDancer и ElectricBoogieDancer закомментировать метод dance , то вывод программы будет таким: Я Антон, мне 18 лет. Я танцую как все. Я Алексей, мне 19 лет. Я танцую как все. Я Игорь, мне 20 лет. Я танцую как все. а это значит, что создавать новые классы BreakDankDancer и ElectricBoogieDancer просто нет смысла. А в чём же, собственно, проявляется принцип полиморфизма? Где спрятано использование объекта в программе без знания о его конкретном типе? В нашем примере — это вызов метода d.dance() на объекте d типа Dancer . Под полиморфизмом Java подразумевается то, что программе необязательно знать какого именно типа будет объект BreakDankDancer или ElectricBoogieDancer . Главное, что он — потомок класса Dancer . И если рассуждать о потомках, следует заметить, что наследование в Java — это не только extends , но и implements . Тут самое время вспомнить, что в Java не поддерживается множественное наследование — каждый тип может иметь одного родителя (суперкласс) и неограниченное количество наследников (подклассов). Поэтому для добавления нескольких функциональностей в классы используются интерфейсы. Интерфейсы уменьшают связанность объектов с родителем по сравнению с наследованием и используются очень широко. В Java интерфейс является ссылочным типом, поэтому в программе может быть объявлен тип переменой типа интерфейса. Здесь самое время привести пример. Создадим интерфейс: Для наглядности возьмем разные и не связанные между собой объекты и реализуем в них интерфейс: Метод main : Результат выполнения полиморфного метода, определенного в интерфейсе, позволяет нам увидеть различия в поведении типов, реализующих этот интерфейс. Они заключаются в разных результатах выполнения метода swim . Изучив наш пример, интервьюер может спросить, почему при выполнении кода из main для наших объектов вызываются методы, определенные в этих классах? Каким образом происходит выбор нужной реализации метода при выполнении программы? Чтобы ответить на эти вопросы необходимо рассказать о позднем (динамическом) связывании. Под связыванием понимают установление связи между вызовом метода и его конкретной реализацией, в классах. По сути, определяется код, какого из трех методов, определенных в классах, будет выполнен. В Java по умолчанию используется позднее связывание (на стадии выполнения программы, а не во время компиляции, как в случае с ранним связыванием). Это значит, что при компиляции кода компилятор еще не знает, код из какого класса — Human , Fish или Uboat он будет исполнять в методе swim . Это определится только при выполнении программы благодаря механизму динамической диспетчеризации — проверки типа объекта во время выполнения программы и выбора нужной реализации метода для этого типа. Если вас спросят, как это реализовано, можете ответить, что при загрузке и инициализации объектов JVM строит таблицы в памяти, и в них связывает переменные с их значениями, а объекты — с их методами. Причем если объект наследуется или имплементирует интерфейс, в первую очередь проверяется наличие переопределенных методов в его классе. Если таковые есть, они привязываются к этому типу, если нет – ищется метод, определенный в классе на ступень выше (в родителе) и так вплоть до корня при многоуровневой иерархии. Рассуждая о полиморфизме в ООП и его реализации в программном коде, отметим, что хорошей практикой является использование абстрактных описаний для определения базовых классов с помощью абстрактных классов, а также интерфейсов. Эта практика основана на использовании абстракции — выделении общего поведения и свойств и заключении их в рамки абстрактного класса, или выделении только общего поведения – в таком случае мы создаем интерфейс. Построение и проектирование иерархии объектов на основе интерфейсов и наследовании классов является обязательным условием для выполнения принципа полиморфизма ООП. Касаясь вопроса полиморфизма и нововведений в Java, можно упомянуть, что при создании абстрактных классов и интерфейсов, начиная с Java 8, есть возможность написания дефолтной реализации абстрактных методов в базовых классах с помощью ключевого слова default . Например: Иногда могут задать вопрос о требованиях к объявлению методов в базовых классах, чтобы не нарушался принцип полиморфизма. Тут все просто: эти методы не должны быть static, private и final. Рrivate делает метод доступным только в классе, и вы не сможете его переопределить в наследнике. Static делает метод достоянием класса, а не объекта, поэтому всегда будет вызываться метод суперкласса. Final же сделает метод неизменяемым и скрытым от наследников.
Полиморфизм и его друзья
Полиморфизм — один из основных принципов объектно-ориентированного программирования. Он позволяет использовать всю мощь строгой типизации Java и писать удобный и поддерживаемый код. Про него сказано многое, но надеюсь из этого обзора каждый сможет вынести что-то новое для себя.
Для чего нужен полиморфизм?
И не понимаю суть примеров которые приводят:
Создают новую функцию в которую помещается вызов определенного метода.
А ведь я могу и без написания доп функции обращаться к ее методу и исходя из примера так даже кода будет меньше.
Полиморфизм бывает разный:
Полиморфизм подтипов - этот тип как раз таки и подразумевают в контексте ООП. Суть в том, что сущность некоторого класса так же может представляться базовым классом или интерфейсом. Это позволяет переиспользовать код, отдавая в один и тот же метод/функцию сущности с разными классами, но с общим интерфейсом.
Параметрический полиморфизм - используется уже не только в ООП, но и в других парадигмах. Опять таки полиморфная функция принимает аргумент разных типов, при этом сам тип так же передается как параметр функции, а следовательно функция может оперировать составными типами на основе переданного. Или например возвращать результат того же или производного типа, сохраняя тем самым тип для вызывающего кода. Чаще всего представлено дженериками, но могут быть и другие формы (например template в C++). Как правило не имеет смысла в языках с динамической типизацией. А еще часто сопровождается контрактами на получаемый тип (например типами высшего порядка или типажами), что позволяет с одной стороны ограничить возможные типы, а с другой - воспользоваться характеристиками типа обусловленными контрактом.
ad-hoc полиморфизм - способность функции предоставить разную реализацию в зависимости от запрашиваемой сигнатуры. Чаще всего выражено перегрузкой функций/методов. Как правило не реализуем средствами языка с динамической типизацией, хотя может быть реализован в рантайме (например в js функция всегда принимает произвольное количество аргументов и может проанализировать их в рантайме с помощью arguments).
В общем случае полиморфизм нужен для переиспользования кода. Соблюдения практик SOLID и DRY не возможно без полиморфизма. Например в языке Go отсутствие полиморфизма в любом виде приводит к загрязнению кодовой базы и большому количеству копипасты.
И еще, из моего ответа, может сложится мнение, что полиморфизм не применим к языкам с динамической типизацией, например к Python. На самом деле это не так, в ЯП с динамической типизацией полиморфизм наоборот возведен в абсолют.
UPD: Примеры:
правда я не шибко хорошо знаю конкретно Python, поэтому абстрактно напишу псевдокод:
Полиморфизм подтипов, как уже говорил, это про ООП. Допустим у меня задача реализовать класс, который читает бинарные данные, проверяет, что они валидный utf-8 и выдает прочитанное как строку. Откуда он читает их? Да мне все равно, это не относятся к моей задаче. Конкретную реализацию я приму в аргументах, а уж откуда она будет читать, из файла или из сети - мне все равно. Главное чтоб эта реализация умела читать байты.теперь вызывающий код может передать в мой метод инстанс любого класса, реализующего мой интерфейс, и мне теперь не нужно дублировать логику чтения строки для файла и для сети.
Еще один яркий пример тут - функция len() в Python, которая принимает любой тип реализующий одноименный магический метод.
Параметрический полиморфизм, это про дженерики. Напишем 2 функции, обе будут принимать на вход массив, одна из них будет возвращать начальный элемент, а другая конечный. Но массивы бывают для элементов разных типов. Без параметрического полиморфизма, нам бы пришлось писать реализацию для каждого нужного типа, но благодаря ему мы можем это сделать в обобщенном виде:
ad-hoc полиморфизм, это про перегрузку функций, пусть мы хотим функцию print которая умеет печатать числа и строки:
Перегрузка методов (Overloading)
Что мы видим ещё видим в Java Oracle Tutorial? В ранее изученном разделе "Defining Methods" есть что-то про Overloading. Что это такое? По-русски это "перегрузка методов", а такие методы называются "перегруженными". Итак, перегрузка методов. На первый взгляд, всё просто. Откроем онлайн компилятор Java, например tutorialspoint online java compiler. Итак, тут всё кажется просто. Как и сказано в tutorial от Oracle, перегруженные методы (в данном случае это метод say) отличаются по количеству и типу аргументов, переданных в метод. Нельзя объявить одинаковые имя и одинаковое количество одинаковых типов аргументов, т.к. компилятор не сможет их отличить друг от друга. Тут стоит сразу отметить очень важную вещь:
То есть при перегрузке компилятор проверяет корректность. Это важно. Но как же на самом деле компилятор определяет, что нужно вызывать определённый метод? Он использует правило "the Most Specific Method", описанного в спецификации языка Java : "15.12.2.5. Choosing the Most Specific Method". Чтобы продемонстрировать его работу, возьмём пример из Oracle Certified Professional Java Programmer: Пример взять отсюда: https://github.com/stokito/OCPJP/blob/master/src/ru/habrahabr/blogs/java/OCPJP1/question1/Overload.j. Как видно, мы передаём в метод null. Компилятор пытается определить наиболее специфичный тип. Object не подходит, т.к. от него наследуются все. Идём дальше. Есть 2 класса исключений. Посмотрим на java.io.IOException и увидим, что в "Direct Known Subclasses" есть FileNotFoundException. То есть выходит, что FileNotFoundException самый специфичный тип. Поэтому, результатом будет вывод строки "FileNotFoundException". А вот если заменить IOException на EOFException, то получится, что у нас два метода находятся на одном уровне иерархии по дереву типов, то есть для них обоих IOException является родителем. Компилятор не сможет выбрать, какой метод нужно будет вызывать и выдаст ошибку компиляции: reference to method is ambiguous . Ещё один пример: Выведет 1. Тут вопросов нет. Тип int. является vararg https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html и на самом деле является не более чем "синтаксическим сахаром" и на самом деле int. array можно читать как int[] array. Если мы теперь добавим метод: То станет выводится не 1, а 2, т.к. мы передаём 2 числа, и 2 аргумента более точное совпадение, чем один массив. Если мы добавим метод: То мы по прежнему будем видеть 2. Потому что в данном случае примитивы более точное совпадение, чем боксинг в Integer. Однако, если мы выполним method(new Integer(1), new Integer(2)); то будет выведено 3. Конструкторы в Java похожи на методы, а так как по ним тоже можно получить сигнатуру, то для них действуют те же правила "overloading resolution", что и перегруженные методы. Спецификация языка Java нам так и сообщает в "8.8.8. Constructor Overloading". Перегруз методов = Раннее связывание (оно же Static Binding) Часто можно услышать про раннее и позднее связывание, он же Static Binding или Dynamic Binding. Различие в них очень простое. Рано - это компиляция, поздно - это момент выполнения программы. Поэтому, раннее связывание (static binding) - определение того, какой метод у кого будет вызван в момент компиляции. Ну а позднее связывание (dynamic binding) - определение того, какой метод вызывать, непосредственно в момент выполнения программы. Как мы видели раньше (когда меняли IOException на EOFException), если мы перегрузим методы так, что компилятор не сможет понять, где какой вызов выполнять, то мы получим ошибку во время компиляции: reference to method is ambiguous. Слово ambiguous в переводе с английского - двусмысленный или неопределённый, неточный. Получается, что перегрузка - это раннее связывание, т.к. проверка выполняется в момент компиляции. Чтобы подтвердить свои умозаключения откроем Java Language Specification на главе "8.4.9. Overloading" :
Получается, во время компиляции будет использована информация о типах и количестве аргументах (которая доступна на момент компиляции), чтобы определить сигнатуру метода. Если метод относится к методам объекта (т.е. instance method), реальный вызов метода будет определён в runtime, используя dynamic method lookup (то есть динамическое связывание). Чтобы стало понятнее, возьмём пример, который похож на ранее рассмотренный: Сохраним этот код в файл HelloWorld.java и скомпилируем его при помощи javac HelloWorld.java Теперь посмотрим, что там написал компилятор наш в байткоде, выполнив команду: javap -verbose HelloWorld .
А что это за methodref такой? Это ссылка на метод. Грубо говоря, это некоторы ключ, по которому во время выполнения виртуальная Java машина сможет действительно определить, какой метод нужно искать для выполнения. Подробнее можно ознакомиться в супер статье: "How Does JVM Handle Method Overloading And Overriding Internally".
Вступление
Думаю, все мы знаем, что язык программирования Java принадлежит компании Oracle. Поэтому, наш путь начинается с сайта: www.oracle.com. На главной странице есть "Menu". В нём в разделе "Documentation" есть подраздел "Java". Всё, что относится к базовым функциям языка относится к "Java SE documentation", поэтому выбираем этот раздел. Раздел документации откроется для последней версии, но пока что в "Looking for a different release?" выберем вариант: JDK8. На странице мы увидим много различных вариантов. Но нас интересует Learn the Language : "Java Tutorials Learning Paths". На этой странице мы найдём ещё один раздел: "Learning the Java Language". Это - святая из святых, tutorial по основам Java от Oracle. Java — объектно-ориентированный язык программирования (ООП), поэтому изучение языка даже на сайте Oracle начинается с обсуждения основных концепций "Object-Oriented Programming Concepts". Из самого названия понятно, что Java ориентирован на работу с объектами. Из подраздела "What Is an Object?" понятно, что объекты в Java состоят из состояния и поведения. Представьте, что у нас есть счёт в банке. Количество денег на счету - это состояние, а методы работы с этим состоянием - это поведение. Объекты надо как-то описывать (рассказывать, какое у них может быть состояние и поведение) и этим описанием является класс. Когда мы создаём объект какого-то класса, то мы указываем этот класс и это называется "типом объекта". Отсюда и говорится, что Java является строго типизированным языком, о чём сказано в спецификации яызка Java в разделе "Chapter 4. Types, Values, and Variables". Язык Java следует концепциям ООП и поддерживает наследование (Inheritance), используя ключевое слово extends (т.е. расширение типа). Почему расширение? Потому что при наследовании дочерний класс наследует поведение и состояние родительского класса и может их дополнить, т.е. расширить функциональность базового класса. Так же в описании класса может быть указан интерфейс (Interface) при помощи ключевого слова implements. Когда класс реализует интерфейс, это значит, что класс соответствует некоторому контракту - декларации программиста остальному окружению, что класс имеет определённое поведение. Например, у плеера есть различные кнопки. Эти кнопки - интерфейс для управления поведением плеера, а поведение будет изменять внутреннее состояние плеера (например, громкость). При этом состояние и поведение как описание дадут класс. Если класс реализует интерфейс, то объект созданный по этому классу может быть описан типом не только по классу, но и по интерфейсу. Давайте уже посмотрим на пример: Тип — это очень важное описание. Оно рассказывает, как мы собираемся работать с объектом, т.е. какое поведение от объекта ожидаем. Поведение - это методы. Поэтому, давайте разбираться с методами. На сайте Oracle методам отведён свой раздел в Oracle Tutorial : "Defining Methods". Первое, что стоит вынести из статьи: Сигнатура метода — это название метода и типы параметров:
Например, объявляя метод public void method(Object o), сигнатурой будет название method и тип параметра Object. Тип возвращаемого значения НЕ входит в сигнатуру. Это важно! Далее выполним компиляцию нашего исходного кода. Как мы знаем, для этого код надо сохранить в файл с именем класса и с расширением java. Код на языке Java компилируется при помощи компилятора "javac" в некоторый промежуточный формат, который умеет выполнять виртуальная машина Java (JVM). Этот промежуточный формат называется байткодом и содержится в файлах с расширением .class. Выполним команду для компиляции: javac MusicPlayer.java После того, как java код скомпилирован, мы можем его выполнять. Используя утилиту "java" для запуска будет запущен процесс виртуальный машины java для выполнения переданного в class файле байткода. Выполним команду для запуска приложения: java MusicPlayer . Мы увидим на экране текст, указанный во входном параметре метода println. Интересно, что имея байткод в файле с расширением .class мы можем его посмотреть при помощи утилиты "javap". Выполним команду <ocde>javap -c MusicPlayer:
Из байткода мы можем увидеть, что вызов метода через объект, типом которого был указан класс выполняется при помощи invokevirtual , а компилятор вычислил, какую сигнатуру метода надо использовать. Почему invokevirtual ? Потому что идёт вызов(invoke переводится как вызывать) виртуального метода. Что такое виртуальный метод? Это такой метод, тело которого может быть переопределено в момент выполнения программы. Представьте просто, что у вас есть некий список соответствий некоторого ключа (сигнатуры метода) и тела (кода) метода. И это соответствие ключа и тела метода во время выполнения программы может меняться. Поэтому метод виртуальный. По умолчанию в Java методы, которые НЕ static, НЕ final и НЕ private, являются виртуальными. Благодаря этому Java поддерживает такой принцип объектно-ориентированного программирования как полиморфизм. Как Вы уже могли понять, об этом наш сегодняшний обзор.
Читайте также: