Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
java2.doc
Скачиваний:
21
Добавлен:
05.06.2015
Размер:
113.15 Кб
Скачать

Лабораторная работа №2.

Классы и объекты в языке Java. Наследование.

Цель работы: 1) научиться создавать собственные классы в языке Java. 2) освоить на практике основные принципы ООП.

Продолжительность работы: 4 часа.

Теоретические сведения

Как объектно-ориентированный язык программирования, Java располагает средствами построения классов и объектов.Класс – основное понятие в ООП, он определяет шаблон, форму, по которому создаются объекты.Каждыйобъектв Java имееттип. Типом являетсякласс, к которому принадлежит данный объект. В каждом классе есть члены двух видов:поля(переменные, содержащие данные класса и его объектов) иметоды(содержащие исполняемый код класса).

Объекты создаются посредством выражений, в которых используется ключевое слово new. Созданные на основе определения класса объекты – это экземпляры(instances) данного класса. Объекты размещаются в области системной памяти – куче. Доступ к любому объекту осуществляется с помощью ссылки на объект (вместо самого объекта в переменных содержится лишь ссылка на него). Каждый новый объект класса обладает собственной копией его полей, и поля объектов называют переменными экземпляра (instance variables).

Простейшее описание класса в языке Java имеет вид:

class class_name

{

// описание класса

}

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

  • Открытый (Public): к членам класса всегда можно обращаться из любого места, в котором доступен сам класс; такие члены наследуются в подклассах.

  • Закрытый (Private): доступ к членам класса осуществляется только из самого класса.

  • Защищенный (Protected): к данным членам разрешается доступ из подклассов и из функций, входящих в тот же пакет. Такие члены наследуются подклассами.

  • Пакетный: доступ к членам, объявленным без указания атрибута доступа, осуществляется только из того же пакета. Такие члены наследуются подклассами пакета.

В итоге, контроль доступак данным и методам объекта в Java несколько отличается от С++, т.к. помимо трех уровней доступа, имеющихся в С++ имеется четвертый, находящийся где-то между уровнями public и protected. Он не имеет имени и используется по умолчанию, когда явно не указан другой уровень. Поля этого типа доступны внутри только одного программного пакета. В Javaконтроль доступа реален, поскольку он осуществляется не только при компиляции, но и непосредственно перед запуском кодов на выполнение виртуальной машиной.

Пример простейшего работающего класса, представляющего точку на плоскости:

class Point

{

public double x, y;

}

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

Для статического члена создается всего один экземпляр, общий для всего класса, вместо построения его копий в каждом объекте класса. В случае статических переменных (переменных класса), это ровно одна переменная, независимо от того, сколько объектов было создано на основе класса (даже если ни одного).

Класс также может содержать блоки статической инициализации, которые присваивают значения статическим полям или выполняют иную необходимую работу. Статический инициализатор оказывается наиболее полезным в тех случаях, когда простой инициализации в объявлении поля недостаточно. Например, создание статического массива часто должно выполняться одновременно с его инициализацией в операторах программы. Приведем пример инициализации небольшого массива с простыми числами:

class Numbers

{

protected static int[] knownNumbers = new int[4];

static

{

knownNumbers [0] = 2;

for(int i = 1; i < knownNumbers.length; i++)

knownNumbers [i] = nextNumbers();

}

}

В языке Java, как объектно-ориентированном, операции над классом осуществляются с помощью методов класса, в которых часто используются детали реализации класса, скрытые от остальных объектов. В таком процессе сокрытия данных, и их недоступности для всех прочих объектов заключается основной смысл инкапсуляцииданных.

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

Итак, в хорошо спроектированном классе данные обычно скрываются, чтобы они могли изменяться только методами этого класса. Чтобы вызвать метод, необходимо указать имя объекта и имя метода и разделить их точкой. Параметры передаются методу в виде заключенного в скобки списка значений, разделяемых запятыми. Если метод вызывается без параметров, необходимо указать пустые скобки.

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

Иногда объекту, для которого вызывается метод, бывает необходимо знать ссылку на самого себя. Для этого в каждом методе может использоваться this — ссылка на текущий объект. Ссылка this также может применяться для именования членов текущего объекта.

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

Методы могут быть объявлены статическими, так как они работают не с каким-то определенным объектом, но составляют внутри класса группу со сходными функциями.

Статический метод не может напрямую обращаться к нестатическим членам. При вызове статического метода не существует ссылки на конкретный объект, для которого вызывается данный метод. Это ограничение можно обойти, передавая ссылку на конкретный объект в качестве параметра статического метода. Тем не менее, в общем случае статические методы выполняют свои функции на уровне всего класса, а нестатические методы работают с конкретными объектами. Статический метод, модифицирующий поля объектов, — примерно то же самое, что фабрика по производству определенного товара, пытающаяся изменить серийный номер давно проданного экземпляра.

Рассмотрим пример:

public class Point

{

private static String destinaton;

static

{

destinaton = "Плоскость";

}

private double x = 0;

private double y;

// Конструктор по умолчанию (без параметров)

public Point()

{

y = 5;

}

// Переопределенный конструктор (overloading), принимающий 2 параметра x и y

public Point(double x, double y)

{

this.x = x;

this.y = y;

}

// реализуем 2 метода для получения и установки значения переменной X (реализуем инкапсуляцию)

public double GetX()

{

return x;

}

public void SetX(double x)

{

this.x = x;

}

public double GetY()

{

return y;

}

public void SetY(double y)

{

this.y = y;

}

// 2 статических метода для получения и установки значения статической переменной

public static String GetDestination()

{

return destinaton;

}

public static void SetDestination(String _destination)

{

destinaton = _destination;

}

}

public class Example

{

public static void main(String[] args)

{

// создадим новый объект класса, используя конструктор по умолчанию и выведем на экран

// значения переменных

Point p1 = new Point();

System.out.println(p1.GetX());

System.out.println(p1.GetY());

System.out.println(p1.GetDestination());

System.out.println();

// изменим значение переменной x, используя метод SetX и выведем обновленные значения на

// экран

p1.SetX(0.5);

System.out.println(p1.GetX());

System.out.println(p1.GetY());

System.out.println(p1.GetDestination());

System.out.println();

// создадим новую переменную типа Point, используя конструктор с параметрами

Point p2 = new Point(10, 10);

System.out.println(p2.GetX());

System.out.println(p2.GetY());

System.out.println(p2.GetDestination());

System.out.println();

// изменим значение статической переменной

p2.SetDestination("сфера");

// выведем значения статического поля для каждой из переменных (будут одинаковы)

System.out.println(p1.GetDestination());

System.out.println(p2.GetDestination());

}

}

Полиморфизм означает возможность применения одноименных методов с одинаковыми или различными наборами параметров в одном классе или в группе классов, связанных отношением наследования. Понятие полиморфизма, в свою очередь, опирается на два других понятия: совместное использованиеoverloading(перегрузку, доопределение,совместное использование) и переопределениеoverriding.

В языке Java каждый метод обладает определенной сигнатурой, которая представляет собой совокупность имени с количеством и типом параметров. Два метода могут иметь одинаковые имена, если их сигнатуры отличаются по количеству или типам параметров. Это называетсяперегрузкой (overloading), поскольку простое имя метода “перегружается” несколькими значениями. Когда программист вызывает метод, компилятор по количеству и типу параметров ищет тот из существующих методов, сигнатура которого подходит лучше всех остальных. Тип возвращаемого значения не является определяющим фактором при совместном использовании — при вызове метода транслятору нужно определить, какой из одноименных методов вызывать, а тип возвращаемого значения, в общем случае, не позволяет сделать это однозначно. Поэтому нельзя описать в рамках одного класса два метода с одинаковым набором параметров и разными типами возвращаемых значений. Этостатическийполиморфизм методов классов.

В Java (как и в других объектно-ориентрованных языках) выполняется вызов метода данного объекта с учетом того, что объект может быть не того же класса, что и ссылка, указывающая на него. Т.е. выполняется вызов метода того класса, к которому реально относится объект. Это динамическийполиморфизм методов. Он называетсяпоздним связыванием(dynamic binding, late binding, run-time binding). В C++ соответствующий механизм называется механизмом виртуальных функций.

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

Абстракция оказывается полезной, когда некоторое поведение характерно для большинства или всех объектов данного класса, но некоторые аспекты имеют смысл лишь для ограниченного круга объектов, не составляющих суперкласса. В Java такие классы объявляются с ключевым словом abstract, и каждый метод, не реализованный в классе, также объявляется abstract.

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

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

При расширении класса на его основе создается новый класс, наследующий все поля и методы расширяемого класса. Исходный класс, для которого проводилось расширение, называется суперклассом.

Если подкласс не переопределяет (override) поведение суперкласса, то он наследует все свойства суперкласса. Переопределение же позволяет расширенному классу по-новому реализовать один или несколько унаследованных методов.

Если имя класса родителя не указано, считается, что родителем является класс Object.

В то время как this ссылается на члены текущего объекта, ссылка super используется для ссылок на члены суперкласса. При вызове метода super.method() runtime - система просматривает иерархию классов до первого суперкласса, содержащего method(). Во всех остальных ссылках при вызове метода используется тип объекта, а не тип ссылки на объект.

Все действия по инициализации объектов при наследования классов выполняются этап за этапом в порядке наследования классов.

  • При первом обращении к классу выделяется память под статические поля класса и выполняется их инициализация.

  • Выполняется распределение памяти под создаваемый объект.

  • Выполняются все инициализаторы нестатических полей класса.

  • Выполняется вызов конструктора класса.

При расширении класса необходимо выбрать один из конструкторов суперкласса и вызывать его при конструировании объектов нового класса. Это необходимо для правильного создания части объекта, относящейся к суперклассу, помимо установки правильного исходного состояния для всех добавленных полей.

Конструктор суперкласса может вызываться в конструкторе подкласса посредством явного вызова super(). Если вызов конструктора суперкласса не является самым первым выполняемым оператором в конструкторе нового класса, то перед выполнением последнего автоматически вызывается безаргументный конструктор суперкласса. Если же суперкласс не имеет безаргументного конструктора, вы должны явно вызвать конструктор суперкласса с параметрами. Вызов super() непременно должен быть первым оператором нового конструктора.

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

Object student = new Child(); // Object = Child

student = “oxford student”; // Object = String

Объекту student вполне законно присваиваются ссылки на объекты Child и String, несмотря на то, что эти классы не имеют между собой ничего общего, за исключением неявного суперкласса Object.

Рассмотрим пример:

// нельзя создать переменную абстрактного класса

public abstract class Human

{

private String name;

private int age;

private String sex;

public String GetName()

{

return name;

}

public int GetAge()

{

return age;

}

public String GetSex()

{

return sex;

}

public void SetName(String name)

{

this.name = name;

}

public void SetAge(int age)

{

this.age = age;

}

public void SetSex(String sex)

{

this.sex = sex;

}

protected Human(String name, int age, String sex)

{

this.name = name;

this.age = age;

this.sex = sex;

}

// переопределимметодtoString

public String toString()

{

return name + "\t" + age + "\t" + sex;

}

}

// классстудентрасширяетклассHuman

public class Student extends Human

{

private String patronymic;

public String GetPatronymic()

{

return patronymic;

}

public void SettPatronymic(String patronymic)

{

this.patronymic = patronymic;

}

public Student(String name, int age, String sex, String Patronymic)

{

super(name, age, sex); // вызываем конструктор родительского класса

this.patronymic = patronymic;

}

public String toString()

{

return super.toString() + "\t" + patronymic; // вызываем реализацию родительского класса

}

}

public class Example

{

public static void main(String[] args)

{

Student student = new Student("Andrey Klochkoff", 15, "male", "Teodorovich");

System.out.println(student); // распечатаем на экране информацию о студенте

}

}

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

Подобным образом могут объявляться целые классы:

final class NoExtending

{

// ...

}

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

Интерфейс— не что иное, как именованное множество абстрактных полей и методов, которые лишь объявляются, но не определяются.

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

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

В языке Java новый класс может расширять всего один суперкласс — такая модель носит название одиночного наследования. Расширение класса означает, что новый класс наследует от своего суперкласса не только контракт, но и реализацию. В некоторых объектно-ориентированных языках используется множественное наследование, при котором новый класс может иметь два и более суперклассов.

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

Множественное наследование оказывается полезным в тех случаях, когда требуется наделить класс новыми возможностями и при этом сохранить большую часть (или все) старых свойств. Однако при наличии нескольких суперклассов возникают проблемы, связанные с двойственным наследованием.

Например:

public interface IGod

{

void CreateHuman();

void CreateHuman(String sex);

void CreatePair(Human man);

}

Все классы, которые наследуют интерфейс IGod, обязаны реализовать все три метода, описанных в интерфейсе.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]