Към съдържанието

Тема 3 — Класове, Интерфейси, Наследяване. Операции и оператори.

Обектно-ориентираното програмиране почива на три стълба: капсулиране, наследяване и полиморфизъм. В Java тези механизми са реализирани с ясна синтаксис и редица съзнателни ограничения спрямо C++: единично наследяване, задължителна йерархия с корен Object, ключова дума interface вместо чисто виртуален клас. Тази глава разглежда и практическите особености на операторите, инициализацията и ключовата дума final.

1. Изброим тип (enum)

enum в Java е пълноправен клас — не просто набор от цели числа като в C. Сравнение:

C++Java
Дефиницияenum Gender { male, female };public enum Gender { male, female, unknown }
СъздаванеGender m_Gender;Gender gender = Gender.unknown;
Използванеif (m_Gender == male)if (gender == Gender.male)

Прилики с клас: има полета и методи; полетата са public static final (константи). Разлики: не може да се наследява; не може да се инстанцира с new.

Типично приложение: дни от седмицата, образователни степени, видове файлове — навсякъде, където е нужен именован затворен набор от константи.

2. Наследяване

2.1 Единична йерархия с корен Object

За разлика от C++ (гора без общ корен), в Java всеки клас е наследник на Object. Ключовите думи:

  • extends — наследяване на базов клас (единично);
  • super — достъп до методи на базовия клас (само едно ниво нагоре);
  • super(...) — извикване на конструктор на базовия клас.

2.2 Синтаксис

package tu_varna;
public class Student extends Person {
public String strFacNumer = "N/A";
public Student(String strFacNumer) {
super(); // подразбиращ се конструктор на Person
this.strFacNumer = strFacNumer;
}
public Student(String name, String gender, String title, String strFN) {
super(name, gender, title); // явен конструктор на Person
strFacNumer = strFN;
}
public void setStrFacNumer(String fn) {
this.strFacNumer = fn;
super.setLastDate(); // достъп до метод на Person
}
}

Типово преобразуване (downcasting):

Student s = (Student) oPerson; // ClassCastException при несъвместимост

Ограничение: при предефиниране нивото на достъп не може да се намалява — ако базовият клас декларира public метод, наследникът не може да го направи private.

2.3 Модификатори за видимост

Всеки член на клас (поле или метод) носи модификатор, определящ кой код може да го достъпва. Модификаторът се поставя пред всяка декларация поотделно — не като блок, за разлика от C++.

МодификаторВидимост
publicНавсякъде
protectedВ класа, в пакета и в наследниците (в произволен пакет)
(без ключова дума)Само в пакета (package-private)
privateСамо вътре в класа
public class Person {
public String name; // достъпен навсякъде
protected int age; // достъпен в наследниците
String department; // достъпен само в пакета (package-private)
private String password; // достъпен само в Person
}
public class Student extends Person {
void demo() {
name = "Ivan"; // OK — public
age = 20; // OK — protected, Student е наследник
department = "CS"; // OK — в същия пакет
password = "secret"; // ГРЕШКА — private
}
}

protected е ключовият модификатор при наследяване: полетата и методите, маркирани с него, са достъпни за всички наследници независимо дали са в същия пакет. При предефиниране нивото на достъп не може да се стеснява — public в базовия клас не може да стане protected или private в наследника.

3. Интерфейси (interface)

interface дефинира контракт — набор от методи, които имплементиращият клас трябва да предостави. За разлика от абстрактния клас, интерфейсът не носи състояние (нестатични полета не са позволени). От Java 8 методите могат да имат имплементация чрез default и static:

Вид методДо Java 8Java 8+
Абстрактен (без тяло)ДаДа
default (с тяло, наследим)НеДа
static (с тяло, на интерфейса)НеДа
private (помощен)НеJava 9+
public interface Face {
void smile(); // абстрактен — задължителен за имплементиращия
default void greet() { // default — имплементацията може да се предефинира
System.out.println("Hello!");
}
static Face silent() { // static — фабричен метод на интерфейса
return () -> {};
}
}
public class Student extends Person implements Face {
public void smile() {
System.out.println("Interface smile");
}
// greet() се наследява от Face — не е нужно да се предефинира
}

implements позволява имплементиране на множество интерфейси едновременно — Java-тото заобикаляне на множественото наследяване. Ако два интерфейса дефинират default метод с еднакъв подпис, класът трябва да го предефинира явно, за да разреши конфликта.

3.1 Интерфейсът Comparable — за подредба

Всеки клас, имплементиращ Comparable, може да се сортира от Collections.sort() и Arrays.sort():

public class Student extends Person implements Comparable {
public int compareTo(Object obj) {
// нарастващ ред по факултетен номер:
return strFacNumer.compareTo(((Student)obj).strFacNumer);
// Обратен ред: return ((Student)obj).strFacNumer.compareTo(strFacNumer);
}
}

compareTo връща: отрицателно (по-малко), 0 (равно), положително (по-голямо).

3.2 Абстрактни класове

Абстрактният клас стои между конкретен клас и интерфейс: може да има полета, конструктори и реализирани методи, но съдържа поне един абстрактен метод — деклариран с abstract, без тяло. Не може да се инстанцира директно; предназначен е да се наследява.

public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
public String getColor() { // конкретен метод — наследява се директно
return color;
}
public abstract double area(); // абстрактен — наследникът ТРЯБВА да го реализира
public abstract double perimeter(); // абстрактен
}

Наследникът е задължен да реализира всички абстрактни методи — иначе и той трябва да се обяви като abstract:

public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
Shape s = new Circle("red", 5.0); // полиморфизъм — Shape референция, Circle обект
System.out.println(s.area()); // извиква Circle.area()
// Shape s2 = new Shape("blue"); // ГРЕШКА — абстрактен клас не се инстанцира

Абстрактен клас срещу интерфейс:

Абстрактен класИнтерфейс
Полета (нестатични)ДаНе
КонструкторДаНе
Конкретни методиДаdefault (Java 8+)
НаследяванеЕдинично (extends)Множествено (implements)
УпотребаОбща база с частична реализацияДефиниране на контракт

4. Пакети и import

package bg.tu_varna; // на първия ред на файла
import java.util.*; // всички публични класове от пакета
import java.io.*;
import static java.lang.Math.*; // директен достъп до PI, sin() и др. (Java 5.0+)

Пакетният път се превежда до структура от директории. Конвенция: имена с малки букви, обърнат домейн — bg.tu_varna.cs. Пакетът java.lang се импортира автоматично.

5. Поразрядни (битови) операции

Java добавя нова операция спрямо C++:

ОператорДействие
~Унарна инверсия на битовете
&Поразрядна И
|Поразрядна ИЛИ
^Поразрядна изключващо ИЛИ
>>Изместване надясно (запазва знака)
<<Изместване наляво
>>>Логическо изместване надясно (нулира знаковия бит)

>>> е нова операция в Java — реализира деление на 2 за беззнакови числа без знакова екстензия.

6. Оператори на езика

Достъп до статични членове: точка . вместо :: на C++: ClassName.staticMethod(). В Java 8+ :: се ползва само за референции към методи (MethodReference).

Оператор instanceof: проверява типова съвместимост по времена на изпълнение, включително наследяване:

String str = "foo";
(str instanceof String) // true
(str instanceof Object) // true — базов тип
(str instanceof Date) // компилационна грешка — несъвместими типове
String strNull = null;
(strNull instanceof String) // false — null не е инстанция на нищо

7. Инициализация на промен­ливите

В Java полетата на клас винаги се инициализират автоматично при създаване на обект — без „мусор” от паметта. Разлика от C++: не е нужно допълнително определение на статично поле извън класа; инициализаторът може да е при самото обявяване.

8. Програмни оператори

8.1 for и for-each

Класическият for е еквивалентен на while. for-each (Java 5.0+) работи за масиви и всеки тип, имплементиращ Iterable:

int[] arrayOfInts = { 1, 2, 3, 4 };
for (int value : arrayOfInts) {
System.out.println(value);
}

8.2 Метки вместо goto

goto не съществува в Java. За излизане от вложени цикли се използват метки — идентификатори пред цикъл, към които break и continue могат да се насочат по име:

outer:
while (condition1) {
inner:
while (condition2) {
break outer; // излиза от двата цикъла
// continue outer; // продължава outer цикъла
}
}

8.3 switch със String (Java 7+)

switch (month.toLowerCase()) {
case "january": monthNumber = 1; break;
case "february": monthNumber = 2; break;
default: monthNumber = 0;
}

Имплементирано чрез String.equals() при изчисляване на израза.

8.4 Сравнение на референтни типове

== проверява дали две референции сочат към един и същ обект в heap. За сравнение по съдържание се ползва equals().

9. Ключова дума final

final прилага различна семантика в зависимост от контекста:

final поле/локална променлива — константа, не може да се промени след инициализация:

final int aFinalVar = 0;
final int blankFinal; // отложена инициализация
blankFinal = 0; // само веднъж; след това — компилационна грешка

Разлика от C++ const: final пред референция прави непроменяема референцията, не обекта — g.spin() е позволено, но g = new Gizmo() не е.

final клас — не може да се наследява (напр. String).

final метод — не може да се предефинира в наследника; компилаторът го свързва статично (подобно на inline в C++).

10. Разлики в организацията на кода спрямо C++

КонцепцияC++Java
Препроцесор#define, typedefstatic final полета и класове
Header файлове.h + .cppСамо .java — декларация и дефиниция заедно
inlineЯвна ключова думаНе съществува явно; final метод е подсказка
ДокументацияРъчна или Doxygenjavadoc генерира HTML от /** ... */ коментари
// C++: #define COLOR_RED 1
// Java:
class Ball {
public static final int COLOR_RED = 1;
// Достъп: Ball.COLOR_RED
}

Резюме

  • Java разграничава extends (клас) от implements (интерфейс); интерфейсите заменят чисто виртуалните базови класове и позволяват полиморфизъм без множествено наследяване.
  • enum е пълноправен клас с полета и методи, но без инстанциране и наследяване.
  • final пред клас, метод или поле прилага различна семантика — от неизменяемост до статично свързване.
  • instanceof проверява типова съвместимост при изпълнение, включително за наследници.
  • >>> (логическо изместване надясно) е операция, уникална за Java.
  • Препроцесорните директиви (#define, typedef) нямат аналог — заменят се със static final полета и класове.