Классы, объекты, методы

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

Шаблоном или описанием объекта является класс (class), а объект представляет экземпляр класса. Можно провести следующую аналогию. У нас у всех есть некоторое представление о машине - наличие двигателя, шасси, кузова и т.д. Есть некоторый шаблон auto - этот шаблон можно назвать классом. Реально же существующий автомобиль auto_solaris (фактически экземпляр данного класса) является объектом этого класса.

Определение класса

Класс определяется с помощью ключевого слова сlass. Вся функциональность класса представлена его членами - полями (полями называются переменные класса) и методами. Например, класс Book мог бы иметь следующее описание :

class Book
{
    public String name;
    public String author;
    public int    year;
 
    public void Info(){
        System.out.printf("Книга '%s' (автор %s) была издана в %d году \n", name, author, year);
    }  
}

Таким образом, в классе Book определены три переменных и один метод Info, который выводит значения этих переменных.

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

class Book
{
    public String name;
    public String author;
    public int    year;
     
    Book(){
        this.name   = "неизвестно";
        this.author = "неизвестно";
        this.year   = 0;
    }
 
    Book(String name, String author, int year){
         
        this.name   = name;
        this.author = author;
        this.year   = year;
    }
     
    public void Info(){
        System.out.printf("Книга '%s' (автор %s) была издана в %d году \n", name, author, year);
    }

Класс Book имеет два конструктора. Первый конструктор без параметров присваивает "неопределенные" начальные значения полям. Второй конструктор присваивает полям класса значения, которые передаются через его параметры.

Так как имена параметров и имена полей класса в данном случае у нас совпадают - name, author, year, то мы используем ключевое слово this. Это ключевое слово представляет ссылку на текущий объект. Поэтому в выражении this.name = name; первая часть this.name означает, что name - это поле текущего класса, а не название параметра name. Если бы у нас параметры и поля назывались по-разному, то использовать слово this было бы необязательно.

Мы можем определить несколько конструкторов для установки разного количества параметров и затем вызывать один конструктор класса из другого :

public class Book
{
    public String name;
    public String author;
    public int    year;
 
    Book(String name, String author)
    {
        this.name = name;
        this.author = author;
    }
 
    Book(String name, String author, int year)
    {
        // вызов конструктора с двумя параметрами
        this(name, author);
        // определение третьего параметра 
        this.year = year;
    }
}

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

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

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

Book b;         // объявление переменной определенного типа/класса
b = new Book(); // выделение памяти под объект Book

После объявления переменной Book b; эта переменная еще не ссылается ни на какой объект и имеет значение null. Затем создаем непосредственно объект класса Book с помощью одного из конструкторов и ключевого слова new.

Инициализаторы

Кроме конструктора начальную инициализацию полей объекта можно проводить с помощью инициализатора объекта. Так можно заменить конструктор без параметров следующим блоком :

public class Book
{
    public String name;
    public String author;
    public int year;
     
    /* начало блока инициализатора */
    {
        name = "неизвестно";
        author = "неизвестно";
        year = 0;
    }
    /* конец блока инициализатора */
     
    Book(String name, String author, int year){
        this.name = name;
        this.author = author;
        this.year = year;
    }  
}

Методы класса

Метод класса в объектно-ориентированном программировании — это функция или процедура, принадлежащая какому-либо классу или объекту.

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

Различают простые методы и статические методы :

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

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

Кроме имени и тела (кода) у метода есть ряд других характеристик:

  • набор модификаторов;
  • тип возвращаемого значения;
  • набор аргументов (параметров).

Модификаторы метода - public, protected, private

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

  • public : открытый — общий интерфейс для всех пользователей данного класса;
  • protected : защищённый — внутренний интерфейс для всех наследников данного класса;
  • private : закрытый — интерфейс, доступный только изнутри данного класса.

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

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

Класс может включать метод main, который должен иметь уровень доступа public; к нему обращается виртуальная машина Java, не являющаяся частью какого-либо пакета.

Абстрактный класс, abstract class

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

public abstract class Price
{
    public abstract int getPriceCode();
    
	public abstract double summPrice(int days);
    
	public int bonusPrice(int days)
	{
        return 1;
    }
}

Переопределение метода, Override

В реализации ReleasePrice, наследующего свойства класса Price, "реализуем" абстрактные методы и "переопределяем" метод с использованием аннотации @Override :

public class ReleasePrice extends Price
{
    public int getPriceCode()
	{
        return Disk.NEW_RELEASE;
    }

    public double summPrice(int days)
	{
        double summ = days*3;
        return summ;
    }
    /*
     * Метод bonusPrice переопределяем. В родительском метод возвращал 1, а здесь уже идет расчет
     */
    @Override
    public int bonusPrice(int days)
	{
        if (days > 1)
            return 2;
        return 1;
    }
}

Теперь, если в родительском класса Price метод bonusPrice будет удален или переименован, то среда разработки должна выдать соответствующее сообщение. Компилятор также выдасть ошибку.

Перегрузка методов, overload

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

Пример класса Test с тремя перегруженными методами test :

public class Test
{
     void test()
     {
         System.out.println("параметр отсутствует");
     }
     void test(int a)
     {
         System.out.println("a = " + String.valueOf(a));
     }
     void test (int a, int b)
     {
         System.out.println("a + b = " + String.valueOf(a + b));
     }
     void test(double d)
     {
         System.out.println("d = " + String.valueOf(d));
     }
}

Пример использования класса Test:

public class Testing
{
    public static void main(String args[])
    {
        Test of = new Test();
        double result = 30.25;
        of.test();
        of.test(10);
        of.test(20, 10);
        of.test(result);
    }
}

Java рекурсия

Рекурсией называется метод (функция), которая внутри своего тела вызывает сама себя.

Рассмотрим пример рекурсивного метода вычисления факториала. Для того чтобы вычислить n!, достаточно знать и перемножить между собой (n-1)! и n. Создадим метод, реализующий описанный способ.

static int fact (int n)
{
    if (n == 1) {
        return 1;
    } else if (n == 2) {
        return 2;
    } else {
        return fact(n-1) * n;
   }
}

Указанный рекурсивный метод вычисляет факториал натурального числа.

Рассмотрим пример, вычисляющий через рекурсию n-ое число Фибоначчи. Напомним, как выглядят первые элементы этого ряда: 1 1 2 3 5 8 13 …

static int fib (int n)
{
    if (index <= 0) {
        return 0;
    } else if (n == 1)
        return 1; 
    else if (n == 2))
        return 1;

    return fib (n-2) + fib (n-1);
}

Суперкласс Object

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

У класса Object есть несколько важных методов:
МетодОписание
Object clone()Функция создания нового объекта, не отличающий от клонируемого
boolean equals(Object object)Функция определения равенства текущего объекта другому
void finalize() Процедура завершения работы объекта; вызывается перед удалением неиспользуемого объекта
Class<?> getClass()Функция определения класса объекта во время выполнения
int hashCode()Функция получения хэш-кода объекта
void notify() Процедура возобновления выполнения потока, который ожидает вызывающего объекта
void notifyAll()Процедура возобновления выполнения всех потоков, которые ожидают вызывающего объекта
String toString()Функция возвращает строку описания объекта
void wait()Ожидание другого потока выполнения
void wait(long ms)Ожидание другого потока выполнения
void wait(long ms, int nano)Ожидание другого потока выполнения

Методы getClass(), notify(), notifyAll(), wait() являются "финальными" (final) и их нельзя переопределять.

Проверка принадлежности класса instanceof

Для проверки принадлежности класса какому-либо объекту необходимо использовать ключевого слова instanceof. Иногда требуется проверить, к какому классу принадлежит объект. Это можно сделать при помощи ключевого слова instanceof. Это логический оператор, и выражение foo instanceof Foo истинно, если объект foo принадлежит классу Foo или его наследнику, или реализует интерфейс Foo (или, в общем виде, наследует класс, который реализует интерфейс, который наследует Foo).

Пример с рыбками. Допустим имеется родительский класс Fish и у него есть унаследованные подклассы SaltwaterFish и FreshwaterFish. Необходимо протестировать, относится ли заданный объект к классу или подклассу по имени

SaltwaterFish nemo = new SaltwaterFish();
if(nemo instanceof Fish) {
    // рыбка Немо относится к классу Fish
    // это может быть класс Fish (родительский класс) или подкласс типа
    // SaltwaterFish или FreshwaterFish. 

    if(nemo instanceof SaltwaterFish) {
        // Немо  - это морская рыбка!
    }
}

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

void checkforTextView(View v)
{
    if(v instanceof TextView)
    {
        // Код для элемента TextView
    } else {
        // Для других элементов View
    }
}

Импорт класса import

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

import packane_name.Class_Name_To_Import;

За ключевым словом следуют класс, который нужно импортировать. Имя класса должно быть полным, то есть включать свой пакет. Чтобы импортировать все классы из пакета, после имени пакета можно поместить '.*;'

IDE Eclipse упрощает импорт. При написании кода в редакторе Eclipse можно ввести имя класса, а затем нажать Ctrl+Shift+O. Eclipse определяет, какие классы нужно импортировать, и добавляет их автоматически. Если Eclipse находит два класса с одним и тем же именем, он выводит диалоговое окно с запросом, какой именно класс вы хотите добавить.

Статический импорт

Существует ещё статический импорт, применяемый для импорта статических членов класса или интерфейса. Например, есть статические методы Math.pow(), Math.sqrt(). Для вычислений сложных формул с использованием математических методов, код становится перегружен. К примеру, вычислим гипотенузу.

hypot = Math.sqrt(Math.pow(side1, 2) + Math.pow(side2, 2));

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

import static java.lang.Math.sqrt;
import static java.lang.Math.pow;

...
hypot = sqrt(pow(side1, 2) + pow(side2, 2));

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

Второй допустимый вариант, позволяющий сделать видимыми все статические методы класса:

import static java.lang.Math.*;

В этом случае не нужно импортировать отдельные методы. Но данный подход в Android не рекомендуется, так как требует больше памяти.

  Рейтинг@Mail.ru