Тема 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 8 | Java 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, typedef | static final полета и класове |
| Header файлове | .h + .cpp | Само .java — декларация и дефиниция заедно |
inline | Явна ключова дума | Не съществува явно; final метод е подсказка |
| Документация | Ръчна или Doxygen | javadoc генерира 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полета и класове.