0

Иерархия классов в java

Есть какая-нибудь картинка где изображена иерархия встроенных классов в java? От класса Object и все классы и интерфейсы, которые от него наследуются. А то когда читаешь книгу, пишут такой то класс наследуется от такого то и так далее. Потом эту иерархию представить себе не можешь. И каша в голове.

1 ответ 1

Всех классов много, в одной картинке не уместить:

Иерархия класса Reader.

Тут можно увидеть AWT:

Тут у нас потоки:

Ошибки:

Немного io:

Вообще, как мне кажется, нет смысла придумывать картинку, именно такую, какую ты хочешь. Ведь все классы наследуются от Object. Это все равно, что уместить все знания о Java в одном img.

Страницы

2 июл. 2015 г.

Наследование. Часть 1 – введение.

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

Чтобы еще раз лучше отложилось в голове:

  • Суперкласс – это родительский класс
  • Подкласс – это класс наследник родительского класса

Объявление класса-наследника

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

В данном примере объявляется что класс Derived является наследником класса Example.

Теперь рассмотрим простой пример. Создадим родительский класс Robot и унаследуем от него класс робота-уборщика – RobotCleaner.

Как видите в классе наследнике нет ни каких методов и полей.

В классе с методом main() мы создаем по экземпляру классов Robot и RobotCleaner, устанавливаем имя для объекта rc и затем выводим информацию о наших роботах. И хотя в классе наследнике нет ни каких методов и полей, мы все же можем обращаться к ним, поскольку они унаследованы.

Вывод у программы следующий:

Доступ к членам и наследование

В нашем примере поле name объявлено с модификатором private, а методы с модификатором protected. Именно по этому мы могли использовать методы, вот если попробуем получить доступ к унаследованным полям на прямую, в обход методов, то компилятор выдаст нам ошибку:

Хотя подкласс включает в себя все члены своего суперкласса, он не может получать доступ к тем членам суперкласса, которые объявлены как private .

Чтобы исправить эту ситуацию можно объявить поле name в родительском классе Robot как protected и тогда мы сможем к нему обращаться из классов наследников.

Давайте сделаем это…

После этих изменений ошибка исчезнет и наш класс RobotCleaner откомпилируется.

Надеюсь вы заметили что в класс RobotCleaner мы добавили метод printName(), то есть мы расширили (extends) функционал в классе наследнике.

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

Иерархия классов

У каждого определяемого вами класса есть родительский класс. Если вы не указали родительский класс в операторе extends, то его родительским классом будет класс java.lang.Object . Класс Object уникален по двум причинам:

  • Это единственный класс в Java, у которого нет родителя.
  • Все классы Java наследуют методы класса Object.

Так как у каждого класса есть родитель, то классы в Java образуют иерархию классов, которая может быть представлена в виде дерева с классом Object в качестве корня.

Тут стоит отметить, что в Java не поддерживается множественное наследование . То есть у класса потомка может быть только один родительский класс.

Конструкторы подклассов

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

super(список_аргументов);

Список_аргументов определяет любые аргументы, требуемые конструктору в суперклассе, он может быть пустым для вызова конструктора суперкласса по умолчанию. Оператор super всегда должен быть первым выполняемым внутри конструктора подкласса .

Чтобы все стало понятнее, попрактикуемся. Я добавил в класс Robot конструктор по умолчанию (на примере слева). Теперь вывод у программы следующий:

Мы видим что конструктор по умолчанию был вызван два раза. Одни раз при создании объекта rb, второй – при создании объекта rc.

Здесь, пока, мы не использовали ключевое слово super, так как я хотел показать цепочку вызовов конструкторов. Именно на это и хочу обратить внимание, что Java сама подставила вызов конструктора суперкласса.

Java гарантирует, что конструктор класса будет вызываться при каждом создании экземпляра класса. Конструктор супер класса будет вызываться всякий раз при создании экземпляра подкласса. Чтобы гарантировать второе утверждение, Java обеспечивает порядок, согласно которому каждый конструктор будет вызывать родительский конструктор . Поэтому, если первый оператор в конструкторе не вызывает другой конструктор с помощью this() или super(), то Java неявно вставляет вызов super(), то есть вызывает родительский конструктор без аргументов . Если у родительского класса нет конструктора без аргументов, но определены другие конструкторы, то подобный неявный вызов приводит к ошибке компиляции.

Читайте также:  Запуск windows после замены материнской платы

Как видно из трех отрывков кода наших классов, если мы уберем конструктор по умолчанию в классе Robot и добавим другой, с каким-либо параметром, а в классах RobotCleaner и RobotShow появятся ошибки компиляции. Обратите внимание на то, что в классе RobotCleaner нет вообще ни каких конструкторов. Java подставила туда вызов конструктора по умолчанию суперкласса, но поскольку он не определен в суперклассе Robot, то получилась ошибка компиляции.

Все это означает, что вызовы конструкторов объединяются в цепочку; при каждом создании объекта вызывается последовательность конструкторов: конструктор подкласса, конструктор родительского класса и далее вверх по иерархии классов до конструктора класса Object. Так как конструктор родительского класса всегда вызывается первым оператором конструктора подкласса, то операторы конструктора класса Object всегда выполняются первыми. Затем выполняются операторы конструктора подкласса и далее вниз по иерархии классов вплоть до конструктора класса, объект которого создается. Здесь есть важное следствие: когда вызван конструктор, он может положиться на то, что поля родительского класса уже проинициализированы.

Если конструктор не вызывает конструктор родительского класса, то Java делает это неявно . А если класс объявлен без конструктора ? как в нашем случае в классе RobotCleaner? В этом случае Java неявно добавляет конструктор по умолчанию . Конструктор по умолчанию не делает ничего, кроме вызова родительского конструктора по умолчанию. В нашем примере это привело к ошибке компиляции, так как у родительского класса Robot не был определен конструктор по умолчанию.

Если класс не объявляет ни одного конструктора, то для него по умолчанию создается конструктор без аргументов. Классы, объявленные с указанием модификатора public, получают конструкторы с модификатором public. Все остальные классы получают конструктор по умолчанию, который объявляется без каких бы то ни было модификаторов доступа .

Теперь приведем наших роботов в более-менее рабочий вид. Класс Robot я оставил как есть, то есть без конструктора по умолчанию, но с конструктором принимающим строку. А вот класс RobotCleaner я изменил добавив конструктор по умолчанию, который вызывает конструктор этого же класса с параметром принимающим строку и вызывающим конструктор суперкласса, который так же принимает строку. Кажется немного замысловато, но вообще все достаточно просто. Так же пришлось изменить строку создающую объект rb в классе RobotShow. Теперь она имеет вид:

Robot rb = new Robot ( "NoNaMe" ) ;

Так пришлось сделать, поскольку в классе Robot у нас нет конструктора по умолчанию.

Теперь у нас все работает. Пример вывода программы представлен слева. Рабочий пример можно посмотреть в коммите Примеры наследования. Вызовы super и this.

Затенение полей родительского класса

В нашем классе RobotCleaner мы можем определить свое поле с именем name. В таком случае говорят что поле подкласса затеняет (shadows) или скрывает поле родительского класса. Как же мы тогда можем сослаться на поле name родительского класса Robot? Для этого существует специальный синтаксис, использующий ключевое слово super:

super.член_класса

Где член_класса может быть методом либо переменной экземпляра.

Чтобы лучше понять, попрактикуемся. Я изменил класс RobotCleaner как на примере слева. Другие классы я не менял. Теперь вывод у программы следующий:

Унаследованный метод setName() установил значение унаследованного от супер класса Robot поля name в объекте rc , а поле name класса RobotCleaner осталось не тронутым.

Теперь изменим классы RobotShow, Robot и RobotCleaner, так чтобы конструктор класса RobotCleaner устанавливал значение поля name для класса RobotCleaner и оставим другой метод этого класса без изменений. В классе Robot расскоментируем конструктор по умолчанию. А в классе RobotShow создадим объект rc при помощи конструктора по умолчанию.

Таким образом мы сможем изменить значение поля name в классе RobotCleaner.

Вывод у программы сейчас следующий:

Как видно из вывода поле name класса RobotCleaner получило значение "Cleaner".

Первую из последних двух строчек выводит первая строка в методе printName() класса RobotCleaner, воторя – соответственно выводит вторую.

Другой способ сослаться на затененное поле – привести this (или любой экземпляр
класса) к соответствующему родительскому классу и обратиться к полю. Вспомните как мы приводили примитивные типы малой разрядности к примитивным типам бОльшей разрядности.

System . out . println ((( Robot ) this ) . name ) ;

Вывод у программы не изменится. Мы просто поменяли метод обращения к полю name суперкласса.

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

Для примера возьмем три класса: А, В и С. Класс В является потомком класса А, а класс С потоком класса В. В каждом классе есть поле x. А так же есть методы выводящие значение поля x для каждого класса. И еще в классе А есть метод printX(), который выводит значение поля х для класса А.

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

Класс В использует свой метод printB() для вывода своего поля х, которое затеняет поле х, класса А.

Так же в этом методе выводится значение поля х из класса А.

Как видно из кода класса С, мы можем обратиться к полю х класса А, который не является прямым родителем класса С через приведение типов (строка 10).

Читайте также:  Бытовой паропылесос керхер sv 7 отзывы

Вы не можете ссылаться на затененное поле x в родителе родителя с помощью вызова super.super.x. Это неправильный синтаксис.

Благодаря приведению классов можно ссылаться на поля вышестоящих родителей, если они открыты для доступа. Пример этого приведен в строках 25-27 класса АВС.

До настоящего времени мы обсуждали поля экземпляров. Поля класса (static) также могут быть затенены. Но в этом нет особого смысла.

Вывод у данной программы следующий:

Первые две строки выводятся командами в строках 12 и 13 класса АВС.

Вторые три строки выводятся командами в строках 15 и 16.

Третьи четыре строки выводятся командами в строках 18 и 19.

Затем вывод делают строки 21 – 23, ну это мы уже проходили. Это простой доступ к полям экземпляров.

Ну и на последок, в строках 25 – 27 мы видим доступ к полям родителей через приведение классов. Синтаксис может показаться немного запутанным, но на самом деле он логичный и простой.

На что следует обратить особенное внимание в этом примере так это на метод printX() в классе А, и на его вызовы на экземплярах классов В и С. Не смотря на затенение поля х в этих классах, метод printX() все время выводит поле х класса А. Это происходит потому, что методы родительского класса А могут работать только с полями своего же класса, так как ни чего не знают о полях в классах потомках.

Подобно полям, могут "затенятся" и методы, но это уже называется перегрузкой (override) методов, что является основой полиморфизма о котором мы скоро поговорим.

Чтобы еще чуть лучше усвоить как работает сокрытие полей, в класс АВС можно добавить еще три строчки:

b . printA () ;
c . printA () ;
c . printB () ;

Которые будут выводить следующее:

Класс А
Класс А
Класс B
Из В Класс А

То есть метод каждого класса выводит только сове собственное поле x.

Информацию о затенении полей я привел для полноты картины и понимания, но вообще использование их – это не очень хорошая практика.

Переменная суперкласса может ссылаться на объект подкласса

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

A ab ;
ab = new B () ;
ab . printA () ;
//ab.printB(); // ОШИБКА!
B bc = new C () ;
bc . printA () ;
bc . printB () ;
// bc.printc(); // ОШИБКА!

Данный код сгенерирует следующий вывод:

Класс А
Класс А
Класс B
Из В Класс А

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

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

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

Чем обусловлена структура Java?

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

Как происходит загрузка классов?

Для того, чтобы найти класс по имени когда вы его вызываете, в Java существует стандартный загрузчик классов. Он оперирует понятием classpath. Список classpath — это набор путей, где следует искать файлы классов. Каждый classpath может указывать как на директорию, так и на так называемый jar-файл (зазипованная директория со скомпилированными .class’ами и разрешением .jar, типа «йА java-archive!»). По умолчанию в classpath входят файлы стандартных библиотек и директория, из которой вы вызвали саму Java. Именно таким образом был найден класс HelloWorld — java нашла файл HelloWorld.class и запустила в нём метод main. Собственно, так и работают большинство программ, даже самых сложных. Всё начинается с одного main’а…

Пакеты классов

Пакет (package) представляют собой набор классов, объединённых по смыслу. Пакеты обычно вкладываются друг в друга, образуя собо иерархию, дающую понять, что зачем. На файловой системе такая иерархия выглядит в виде вложенных друг в друга директорий с исходниками. Так, исходники пакета a лежат в папке a, исходники пакета a.b — в папке a/b и так далее. Типичный путь к пакету выглядит примерно так:
org.apache.commons.collectons . Видите, сразу ясно зачем он нужен. Чтобы использовать какой-то класс в коде другого класса, вы должны импортировать его, написав до объявления класса строку
import путь.к.классу.ИмяКласса;
Кроме того, если вы используете классы одного пакета часто, вы можете импортировать весь пакет:
import путь.к.классу.*;
Это относится ко всем пакетам, кроме java.lang — он импортирован по умолчанию, именно из него были в прошлом примере взяты классы System и String. На самом деле они лежат в некоем jar’е, в каталоге java/lang.

Читайте также:  Домашняя бухгалтерия лучшие бесплатные программы

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

Организация кода

Если вы пишете свои первые маленькие примерчики и вам лень создавать иерархию классов — пусть, это ваше право. Но помните, в серьёзном проекте вы всегда должны будете разложить свои классы по пакетам. Обычно корневые пакеты создаются такими, чтобы ясно давать понять кто автор кода, и к чему он относится.
Например:
ru.vasiapupkin.photomaker выбирается корневым пакетом
ru.vasiapupkin.photomaker.core сюда мы пишем классы отвечающие за логику
ru.vasiapupkin.photomaker.visual сюда, допустим, все наши окошки приложения

и так далее.
Чтобы создать класс
ru.vasiapupkin.photomaker.Starter
вы должны:
создать файл Starter.java в папке ru/vasiapupkin/photomaker/
прописать в нём первой строчкой (точнее говоря, до импортов)
package ru.vasiapupkin.photomaker;

Коллижн

«А что будет, если у нас будет два класса с одним именем?», — спросите вы. «Смотрите», — отвечу я.
Допустим вы решили что вы умнее джавы и создали свой класс строки — String. Но вот проблема, у нас же уже есть такой!
Значит, вам придётся положить свой класс в пакет, скажем ru.vp.stuff и обращаться к нему так: ru.vp.stuff.String.
Именно поэтому не рекомендуется класть классы прямо в корень classpath — таким образом вы роете себе дорогу к несовместимости, ведь Java требует, чтобы каждый класс определялся однозначно. Именно поэтому нельзя написать так:
import ru.vp.SuperClass;
import ru.mashka.SuperClass;
За это вас накажет компилятор, потому что он не будет знать, какой из них использовать.
Мораль: правильно выбирайте и имя класс и имя пакета.

Погоняем?

Давайте улучшим первое приложение. Эх, классика интернета… Создадим апплет.

Эх, может быть апплетами.

import java.applet.Applet ;
import java.awt.Graphics ;

public class HelloWorld extends Applet<

public void paint( Graphics g) <
g.drawString( "Hello World" ,15,15);
>

>

* This source code was highlighted with Source Code Highlighter .

Так, что у нас тут? Импортировано 2 класса, один из них — стандартный пустой апплет, который мы будем расширять. Второй — Graphics. Graphics — это понятие из библиотеки AWT. Кстати, небольшой экскурс. AWT (Abstract Window Toolkit) входил ещё в первую Java и был предназначен для многих задач, связанных в основном с отображением.
Так вот, объект типа Graphics позволяет нам рисовать на себе всякую муру типа строк, линий, кружочков и прочего. В данном примере мы написали строчку с отступом.
Метод paint здесь написан не от балды — он перекрывает аналогичный метод класса Applet, и когда java будет перерисовывать этот конкретный апплет, она вызовет этот метод.

Посмотреть на наш апплет достаточно просто — пишем небольшой HTML:

body >
applet code ="HelloWorld.class" codebase ="file:///home/devgru/" width ="150" height ="30" > applet >
body >

* This source code was highlighted with Source Code Highlighter .

… а может приложением.

Давайте попробуем сделать HelloWorld в standalone-приложении.

import java.awt.* ;
import javax.swing.* ;
public class HelloWorld extends JFrame<

public static void main( String [] args) <
new HelloWorld();
>

<
add( new JLabel( "Hello world" ));
setSize(200,200);
setVisible( true );
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
>
>

* This source code was highlighted with Source Code Highlighter .

Здесь мы полностью импортируем основные классы пакетов Swing и AWT. Swing — более поздняя чем AWT библиотека, сейчас именно она обеспечивает отображение основной части графического интерфейса для Java-приложений.
Итак, в main мы просто создаём экземпляр класса HelloWorld.
Сейчас наш класс наследуется от класса JFrame. Это класс из Swing, он представляет собой окно, которое отображается пока не будет закрыто.
Блок <. >— это «общий конструктор». Он добавляется к любому конструктору нашего класса. Так как у нас нет ни одного — он добавляется к пустому конструктору без параметров, который создаётся на лету, если у класса нет ни одного.
Мы добавляем на окно новый объект типа JLabel (т.е. надпись), затем устанавливаем окну размеры и отображаем его. Последняя строчка нужна, чтобы выполнение приложения закончилось, когда будет закрыто окно. Таким образом, вы можете быть уверены что после закрытия окна у вас в памяти не останется висеть ваше приложение.
Запускать его нужно точно так же как и прошлое: пишем, компилируем, запускаем.

А может и сервлетами? Наверное, потом.

В этих двух статьях я постарался дать вам начальное представление о возможностях Java в общем. За рамками сегодняшней статьи (наверное, будут в завтрашней) остались сервлеты и прочие радости серверной части типа JSP-страниц, а также МИДлеты — приложения для мобилок. Я бы мог рассмотреть и то и то, но хотел бы знать, чего больше хотят читатели. Кроме того, возможно, нужно рассказать о самых основах языка. Примерно на том же уровне подробности, что и начало этой статьи. Напишите в комментариях, какую статью вы хотели бы видеть в следующий раз:
— классы и интерфейсы: ООП в джаве;
— буквы-цифры-строчки: работа с базовыми типами;
— создание оконных приложений с помощью Swing;
— от мала до велика: сервлеты, мидлеты и 2 слова о портлетах.

Когда отпишетесь — станет ясно, куда копать дальше. Всем спасибо за внимание.

Ссылка для тех, кому лень ждать завтра: основы языка (eng.).

admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *