ITI0011RUS:Objects

Allikas: Kursused
Mine navigeerimisribale Mine otsikasti

Вернуться на страницу предмета

Объект-ориентированное программироваие (OOП)

Объектно-ориентированное программирование (англ. object oriented programming, aka OOP) это разновидность программирования, при котором программа рассматривается как набор объектов. Java по большей части объектно-ориентировнный язык программирование (элементарные типыданных, такие как int, double и т.д. не являются объектами).

ООП это техника, которая позволяет сделать процесс программирования более удобным. Польза от этого подхода ощущается в особенности в больших проектах. ООП не делает ваши программы более быстрыми.

Основные цели ООП:

  • Инкапсуляция информации (encapsulation). Другие объекты могут пользоваться не всеми методами и переменными данного объекта, а только теми, которыми им разрешено пользоваться.
  • Модульность (modularity). Разделение кода на более или менее независимые модули - классы. Каждый класс выполняет одну конкретную задачу и только ему (классу) известными способами - другие модули не знают как какой-то конкретный модуль выполняет свои задачи - все, что им следует знать - это какой будет результат работы модуля при определенных входных данных, логика работы самого модуля - "черный ящик".
  • Полиморфизм (polymorphism). Один и тот же метод в разных объектах может вести себя по-разному.
  • Наследование (inheritance). Классы могут образовывать иерархию, в которой производные (под)классы наследуют определенные методы и переменные родительского класса, помимо этого, дополняют его своими собственными переменными и методами.
  • Повторное использование кода. Готовые модули, написанные для одной программы, могут использоваться повторно для других целей в других проектах.

Основная концепция ООП

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

Для описания состояния объекта используют следующие термины: атрибут (attribute), свойство (property), поле (объекта) ((instance) field), переменная (объекта) ((instance) variable).


Идеология ООП

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

ООП позволяет (в случае более крупных программ):

  • лучше структурировать код программы
  • поддерживать код в понятной для понимания форме
  • дополнения/изменения в код делать легче

и т.д.

Класс и объект

Класс описывает абстрактные свойства объектов. Класс похож на шаблон, который описывает свойства чего-либо. Например, класс Собака описывал бы свойства, которые присущи всем собакам - такие как, например, порода, цвет, навыки (лаяние, сидение, бег). Объект - конкретная собака с набором значений параметров, присущим конкретно этой собаке. Таким образом, класс - это описание свойств объектов - то, какими свойствами будут обладать все объекты данного класса. Значения соответствующих свойств задаются объектами класса - у нескольких разных объектов одного и того же класса набор свойств один и тот же, а вот значения этих свойств различны.

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

В ООП часто вместо термина объект используется термин экземпляр (instance) класса.

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

  • статическое поле (public static int dogCount;) это переменная, принадлежащая классу (не объекту!). Если класс описывает всех собак, то и статическая переменная описывает также свойство всех собак. Если где-то (в каком-то месте кода) значение этого поля меняется - оно меняется во всей программе.
  • не статическое поле (String name;), также известное как переменная класса или переменная экземпляра, описывает свойства конкретного объекта (собаки), в данном случае, имя конкретной собаки. Имя каждой собаки может отличаться. Если изменить имя в одном объекте, в других объектах значение этого поля не поменяется.

Класс также описывает действия, или поведение объектов данного класса. Эти действия называются методами (фактически это просто функции, но в терминологии ООП они называются методами). Методы позволяют объектам выполнять те или иные действия. Например, у объектов класса "Собака" может быть метод лаять, который заставляет лаять конкретную собаку, в чьем объекте метод был вызван.

  • статический метод (public static int getDogLimit()), также называемый функцией или методом класса, это функция, которая принадлежит классу и не зависит от конкретного объекта. Например количество собак, которые могут находиться в конуре не зависит от конкретных собак. Это функция, единая в рамках всей программы.
  • не статический метод (public void bark()), также называемый функцией или методом объекта, привязан к конкретному объекту. Этот метод или функция вызывается отдельно для каждого объекта (можно вызывать этот метод и для всех объектов, вызвав этот метод у каждого объекта в отдельности).

Рассмотрим пример кода: <source lang="java">

/**

* Describes a student which
* has some test results.
*
*/

public class Student { /** * The name of the student. */ public String name; /** * Unique ID over all the students. */ public int ID; /** * The results of three tests. */ public double test1, test2, test3;

/** * Calculates the average test result. * @return Average test result */ public double getAverage() { return (test1 + test2 + test3) / 3; }

/** * Static variable which holds the next unique id. * The value of this variable is usually student count. * This is the same for all the students. */ private static int nextUniqueID = 0;

/** * Gets a unique ID for the student. * The ID number itself is increased. * @return Unique id for the student. */ public static int getUniqueID() { nextUniqueID++; return nextUniqueID; } }

</source>

В примере выше в классе студент присутствуют как статические, так и не статические методы. Статический метод getUniqueID() не зависит от какого-либо конкретного студента. Он зависит лишь от того, какие значения уже были назначены (уникальность значения предполагает, что ни у какого другого студента такого номера больше нет). Самый простой способ сгенерировать уникальный номер заключается в том, что каждому новому студенту генерируется номер, который на единицу больше чем номер, сгенерированный последнему студенту. Если в университете 100 студентов, то нет необходимости обращаться к каждому из них в отедельности, чтобы получить уникальный номер. Поэтому этот метод и является статическим, поскольку он един для всей программы (например, един для всех студентов в университете).

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

Создание объектов

Класс (а точнее говоря, его имя) задает новый тип данных. Все объекты, которые вам посчастливилось использовать (String, ArrayList и т.д.) на самом деле являются классами, и подобно классу Student из примера выше, эти классы где-то описаны.

Таким образом, имя класса используется как тип данных. Например, мы можем создать переменную, у которой тип данных Student: <source lang="java"> Student std; </source>

В Javas объявление переменной (как показано в примере выше) еще не создает объект - мы только создаем переменную для хранения объектов данного типа данных. В общем:

  • В Java ни одна переменная не хранит объект в себе.
  • Переменная хранит лишь ссылку на объект (сам объект находится где-то в другой области памяти процесса).

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

Создание объекта осуществляется ключевым словом new: <source lang="java"> std = new Student(); </source>

Код выше создает новый объект класса Student в памяти, и записывает ссылку на этот объект в переменную std. Проинициализированная таким образом переменная std используется в качестве объекта. Чтобы обратиться к полям или методам данного объекта, можно использовать созданную переменную, например std.name .

Null ссылка

Бывают случаи, когда требуется показать, что переменная, подобная std, тип данных которой - класс, не ссылается ни на один существующий объект. В этом случае переменная std хранит так называемую null-ссылку или пустую ссылку (null reference). В Java null-ссылка обозначается как null. Можно присвоить переменной значение null-ссылки следующим образом: <source lang="java"> std = null; </source> и проверка, содержит ли переменная null-ссылку выглядит следующим образом: <source lang="java"> if (std == null) { </source>

Если значением переменной является null, то фактически у нас нет никакого объекта к которому можно было бы обратиться. Если например std = null;, то обращение к полю std.name приведет к исключению null pointer exception (NullPointerException).

Экземпляр класса

Конкретный объект называют экземпляром своего класса (instance). Ниже приведен пример того, как создавать различные экземпляры описанного ранее класса Student.

<source lang="java"> // Declare four variables of type Student Student std, std1, std2, std3; // Create a new object belonging to the class Student, // store a ref to that object in the var std. std = new Student(); std1 = new Student(); // Create a second Student object std2 = std1; // Copy the reference value in std1 into the variable std2. std3 = null; // Store a null reference in the variable std3. std.name = "John Smith"; // Set values of some // instance vars, getUniqueIdisa static method, // therefore is accessed from class Student // (not object instance like std) std.ID = Student.getUniqueID(); std1.name = "Mary Jones"; std1.ID = Student.getUniqueID(); // (Other instance variables have default initial values of zero.) </source>

На рисунке ниже видно, как выглядят объекты в памяти, в случае данного примера:

Java-juhend-Objects-in-heap.png

Если кака-либо переменная ссылается на объект - то это обозначено на картинке стрелкой. Видно, что std, std1 и std2 ссылаются на объект типа Student, причем std1 и std2 ссылаются на один и тот же объект. Если несколько переменных ссылаются на один объект, и через одну из переменных в объекте что-то меняют (например, имя), то при чтении этого поля через другие переменные, полуим новое (обновленное значение). Тоесть сама переменная - это лишь ссылка на область памяти (как бы адрес объекта в памяти). Если несколько переменных ссылаются на один и тот же объект в памяти, то при изменении чего-то в объекте все переменные "видят" это изменение.

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

Если переменной не примитивного типа (тип данных переменной является объектом) присваивается значение другой не примитивной переменной (в примере выше std2 = std1), то std2 начнет указывать на тот же объект, что и std1 - тоесть std2 присваивается та же ссылка (адрес в памяти), что и у переменной std1. В результате такого присваивания обе переменные будут ссылаться на ту же самую область памяти. std1.name и std2.name будут ссылаться на одно и то же имя. Если, например, используя std1 изменить имя, то при прочтении его используя std1 имя сменится. std1.name и std2.name - две разные возможности сослаться на одну и ту же область памяти.

Конструктор

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

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

Пример того, когда при создании объекта следует передать явно какие-либо параметры его инициализации. У каждого студента есть имя. Если использовать только конструктор по умолчанию (который вообще не инициализирует это поле), придется вызывать метод student.setName("..."), чтобы задать имя студенту. Но то же самое можно сделать и сразу при инициализации объекта, если создать вариант конструктора, который принимает имя в качестве параметра и инициализирует поле name переданным значением.

Student.java: <source lang="java"> public class Student { private String name;

public Student(String name) { this.name = name; } } </source>

Теперь можно создать объект этого класса следующим образом: <source lang="java"> Student s = new Student("Reinuvader"); </source>

Причем следующий код вернет ошибку: <source lang="java"> Student s = new Student(); </source>

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

<source lang="java"> public static class Student { private String name;

public Student() { }

public Student(String name) { this.name = name; } } </source>

Эта техника называется перегрузкой конструктора. Перегрузка означает, что в классе есть несколько методов с одинаковым именем, но разным набором аргументов (разным количеством аргументов или разными типами данных аргуменов). Если в коде объект создается как new Student();, то запускается конструктор без параметров. Если объект создается как new Student("Koolipoiss");, то запускается тот конструктор, который принимает имя строку в качестве аргумента.

При объявлении конструктора обратите внимание на некоторые особенности:

  • нет возвращаемого значения
  • имя метода идентично имени класса

Сравнение объектов

При сравнении двух переменных, которые ссылаются на какие-либо объекты if (std1 == std2) проверяется, ссылаются ли обе переменные на одну и ту же область памяти. Сравниваются только адреса в памяти, а данные, хранящиеся в этой области памяти, не анализируются.

String является объектом в Java. Пусть у нас будут две строки str1 и str2. При их сравнении if (str1 == str2) результатом будет значение true только если обе переменные ссылаются на один и тот же объект (на один и тот же адрес в памяти). Часто в программах этого недостаточно - нам нужно сравнить содержимое объектов, находящихся по разным адресам. Пусть у нас есть две строки с одинаковым содержимым: <source lang="java"> String str1 = "hello"; String str2 = "hello"; </source>

В примерах выше мы видели, что при создании объектов используется инструкция new, но в примере со строками она не была использована. Поскольку тип данных "слово" - очень распространенный тип данных, то в Java сделали т.н. упрощенный вариант создания подобных объектов, в котором инструкцию new использовать не нужно. В случае создания объектов таким образом, при создании объекта не создается новый объект каждый раз заново. Если строка с таким же содержимым уже находится в памяти (например, по адресу 123), то при создании еще одной строки (str2) с точно таким же содержимым, переменная str2 инициализируется значением, по которому в памяти находится строка hello - адресом 123. Если сравнить эти две переменные if (str1 == str2), то с большой вероятностью в результате получим значение true.

Строку можно создать и "классическим" способом: String str3 = new String("hello");. В этом случае при создании объекта каждый раз в памяти будет выделяться новая область памяти. Если str3 сравнить с str2 или str1, в результате получим false, поскольку str3 ссылается на совсем другую область памяти, чем str1 и str2 (которые ссылаются на одну и ту же область памяти).

Пример кода: <source lang="java"> public class StringExample { public static void main(String[] args) { String str1 = "tere"; String str2 = "tere"; // str1 and str2 point to the same memory location System.out.println(str1 == str2); // and the contents are equal System.out.println(str1.equals(str2));

// enforce new object creation String str3 = new String("tere"); // now str3 is stored in a separate location in memory System.out.println(str1 == str3); // but the contents are still equal System.out.println(str1.equals(str3)); } } </source>

Наследование

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

В Java, класс, который наследует, называется под-классом (subclass), а класс, от которого наследуют, называется сверх-классом (superclass). В объявлении класса можно указать, что создаваемый класс является подклассом какого-то другого класса. Например, если нужно описать класс B и этот класс должен наследовать часть структуры и поведения класса A, то класс B объявляется следующим образом:

<source lang="java"> class B extends A {

   // additions to, and modifications of,
   // stuff inherited from class A

} </source>

Несколько классов могут наследовать один и тот же класс. Например, классы B, C и D все наследуют класс А. Можно сказать, что классы B, C и D частично схожи в структуре и поведении (но только частично).

Java-juhend-Subclass-superclass.png

В левой части рисунка показан "упрощенный" случай, когда один класс наследует другой класс. В правой части рисунка показана более развернутая версия этого наследования.

Пример наследования: транспортные средства

Создадим классы для описания транспортных средств. Пусть будет общий на все транспортные средства класс, который так и назовем "транспортное средство" (Vehicle). Помимо этого класса, создадим также классы машина (car), грузовик (truck), и мотоцикл (motorcycle) для описания различных типов транспортных средств. Все эти три класса наследуют класс Vehicle. В графическом представлении это выглядело бы следующим образом:

Java-juhend-Vehicle-hierarchy.png

Класс транспортного средства Vehicle мог бы содержать такие свойства, которые есть у всех транспортных средств, вне зависимости от конкретного типа транспортного средства: например, регистрационный номер, владелец, и какой-нибудь метод, например, transferOwnership(). Все поля и методы, описанные в классе Vehicle, будут присутствовать в классах, которые его наследуют - в Car, Truck, и Motorcycle.

В дополнение к этим свойствам, каждый из трех классов транспортных средств может иметь свои поля и методы, характерные для объектов только этого класса. Например:

  • Класс Car может содержать поле numberOfDoors.
  • Класс Truck может содержать поле numberOfAxels.
  • Класс Motorcycle может содержать поле hasSidecar.

В коде все это могло бы выглядеть следующим образом:

<source lang="java"> class Vehicle {

  int registrationNumber;
  Person owner;  // (Assuming that a Person class has been defined!)
  void transferOwnership(Person newOwner) {
      . . .
  }
  . . .

}

class Car extends Vehicle {

  int numberOfDoors;
  . . .

}

class Truck extends Vehicle {

  int numberOfAxles;
  . . .

}

class Motorcycle extends Vehicle {

  boolean hasSidecar;
  . . .

} </source>

В этом примере показаны 4 различных класса. Технически это означает, что весь код разделен на 4 файла - каждый класс в своем отдельном файле.

Создать объект какого-либо из транспортных средств можно следующим образом: <source lang="java"> Car myCar = new Car(); // from the Car itself System.out.println(myCar.numberOfDoors); // and from superclass Vehicle System.out.println(myCar.registrationNumber); System.out.println(myCar.owner); myCar.transferOwnership(); </source>

Из примера выше видно, то в классе Car мы можем обращаться как к полям, которые описаны в классе Car, так и к полям, которые описаны в классе Vehicle.