Lecture
When constructing the inheritance hierarchy, it often turns out that a method common to all subclasses and therefore defined in a superclass cannot be programmed in this superclass by any useful algorithm, since it is different in each subclass.
For example, the Point
, Circle
, and Rectangle
(rectangle) classes are inherited from the Figure
class. In the class Figure
there is a paint()
method, common to all subclasses - it is needed to paint a shape. But there is nothing in common between drawing a circle, a point, and a rectangle, so it makes no sense to program this method in the Figure
class.
In such cases, after the method header and the list of parameters, a semicolon is inserted instead of curly brackets, and the keyword abstract is specified before the method declaration. This method is called abstract , i.e. devoid of implementation.
A class in which there is at least one abstract method is called an abstract class and the word class must also precede the word abstract.
You cannot create an abstract class object. You can only inherit other classes from this class, override abstract methods in them (filling them with specific contents) and create objects of these classes already. *
Multiple inheritance is when a class inherits from two or more classes.
Let, for example, the Clock
( Phone
) classes have already been developed and there is a need to write a new Cellurar
class (cell phone), which combines the structure and behavior of both these classes (it can be used as a phone, but it also has the entire clock functionality). It is very convenient to simply inherit the Cellular
class from the Clock
and Phone
classes. First, the programmer does not have to rewrite numerous methods. Secondly, all the advantages of polymorphism remain: an object of the Cellular
class can be used in the program both as a clock (as an object of the Clock
class) and as a phone (as an object of the Phone
class).
However, multiple inheritance has the potential to give rise to intractable problems. They begin when ancestor classes have methods with the same signature (i.e., name and number of parameters).
For example, in the class Clock
there is a ring()
method that is called when the alarm timer is triggered. But in the Phone
class, there is also a ring()
method, which is called when someone calls you on the phone and you need to notify the owner. When the Cellular
class inherits from the Clock
and Phone
classes, it receives a ring()
method. But which of his options? *
This situation does not have a flawless solution. Therefore, many programming languages prohibit multiple inheritance.
Java does not support multiple inheritance.
Note, however, that if the ring()
method in at least one of the Clock
and Phone
classes is abstract, then there can be no conflict. The abstract method has no implementation, and therefore that method that is not abstract is “winning”. If the method is abstract in both classes, it will remain the same in their descendant.
Using this feature of abstract classes, the authors of the Java language introduced a special construction into it - interfaces that allow to preserve some of the advantages of multiple inheritance (namely, the advantages of polymorphism), while avoiding the main problem of multiple inheritance.
An interface is a class in which all fields are constants (that is, static fields are static and immutable are final), and all methods are abstract.
When describing an interface, the interface keyword is used instead of the class keyword, followed by the interface name, and then, in curly brackets, a list of constant fields and methods. No modifiers are needed before declaring fields and methods: all fields automatically become public static final, and methods are public abstract. Methods cannot be implemented, i.e. a semicolon is immediately inserted after the closing parenthesis.
We describe, for example, the interface for an object that “knows how” to communicate information about itself in the price list format (that is, to report its name, price, and a brief description).
interface PriceItem {
String getTitle();
int getPrice(int count);
String getDescription();
}
For a change, the getPrice()
method in this example requires one integer parameter (the number of product units).
Such an interface is useful for a program such as an online store, which must form a price list — a list of goods with an indication of their price and description, at the user's request.
A class can implement an interface, i.e. adopt his behavior. This procedure is similar to inheritance, only the keyword implements is used instead of the extends keyword. But if only one class can be specified after extends, then after implements, you can list an arbitrary number of interfaces separated by commas. A class can simultaneously inherit from another class and implement interfaces.
We can take any class that exists in the program and “teach” it to communicate all the necessary information about itself. For example, the class Dog
, with whom we worked in the last lesson. You can change this class by forcing it to implement our interface, or you can leave it intact * and create a new class on its basis. In the new class, it is necessary to redefine the abstract methods of the interface.
class Dog2 extends Dog implements PriceItem {
private int price;
String getTitle() {
return ("Умная собака");
};
int getPrice(int count) {
return price * count;
};
int setPrice(int p) {
price = p;
}
String getDescription() {
return ("Умная собака, которая знает свой возраст и умеет сообщать его с помощью лая");
};
}
The Dog2
class “knows how” the same as the old Dog
class, but in addition it can be used in the online store program to form a price. Notice that the Dog
class did not know anything about the price, so it was necessary to add the setPrice()
method and the price
field so that this price could be changed. And to change the description of the dog is not required, so the method getDescription()
simply displays the same term.
Similarly, you can take many other classes that are not related to dogs — bicycles, t-shirts, computers — and make them suitable for our new program using the PriceItem
interface and the inheritance mechanism.
Interface type variables can refer to an object of any class that implements this interface.
PriceItem pi; // переменная интерфейсного типа
PriceItem pi; // переменная интерфейсного типа
Dog2 dog = new Dog2(); // создается объект класса Dog2, на него ссылается переменная dog
Dog2 dog = new Dog2(); // создается объект класса Dog2, на него ссылается переменная dog
dog.voice(); // можно вызвать метод лая
dog.voice(); // можно вызвать метод лая
System.out.println(dog.getTitle()); // можно вывести название товара
System.out.println(dog.getTitle()); // можно вывести название товара
Dog oldDog = dog; // переменная oldDog ссылается на тот же самый объект
Dog oldDog = dog; // переменная oldDog ссылается на тот же самый объект
oldDog.voice(); // можно работать с объектом нового класса по-старому
oldDog.voice(); // можно работать с объектом нового класса по-старому
pi = dog; // переменная pi рассматривает тот же самый объект как товар для прайса
pi = dog; // переменная pi рассматривает тот же самый объект как товар для прайса
pi.voice(); // НЕ ПОЛУЧИТСЯ. Этого метода нет в интерфейсе PriceItem*
pi.voice(); // НЕ ПОЛУЧИТСЯ. Этого метода нет в интерфейсе PriceItem*
We can put dogs, bicycles and computers in one array of goods
(goods) and in a cycle form a price:
PriceItem[] goods;
...
// создание и заполнение массива элементами,
// поддерживающими интерфейс PriceItem
for (int i = 0; i < googs.length; i++) {
// поддерживающими интерфейс PriceItem
for (int i = 0; i < googs.length; i++) {
System.out.println("Название: " + goods[i].getTitle() + ", цена за единицу товара: " + goods[i].getPrice(1) + ", описание: " + goods[i].getDescription() + ".");
}
Using the interface, the programmer gets the opportunity to specify a description of "what the class does," without worrying about the details, "how it does it." Each class that implements the interface can choose its own implementation methods.
Suppose in our program there is an object that monitors the current time and every minute (when the time changes) “informs” about this to other objects. This is a very logical and convenient way to organize a program in which objects must do something when a certain time (or event) occurs. Much better than teaching each object to keep track of time.
One object can “inform” something to another object by calling its method. Let the sayTime(int hours, int minutes)
method handle time information. In order to call this method on an object, one must be sure that such a method is described in the class of this object. You can define an interface, say TimeListener
, and implement it in all classes that need to keep track of time without interfering with the main hierarchy of these classes. And then we can have a kind of intelligent dog that barks at midnight and a kind of button that can automatically trigger after a specified period of time.
The class that TimeListener
time will have an internal list of objects of type TimeListener
, methods for adding and removing objects to this list (those objects that “want” to follow the time will call this method) and every minute an object of this class (just one such object on the entire program) will call the sayTime(int hours, int minutes)
method sayTime(int hours, int minutes)
for each of the objects in this list.
A package is a group of interrelated classes. The relationship can be of any type: closely interacting classes, classes that perform the same functions or classes of the same developer. *
Each package has a name . The name is a regular Java identifier. The peculiarity is that this name is also the name of the folder in which the class files included in the package are stored. A dot in the name is converted to a file system name delimiter. That is, the package named java.util
will be represented by the util
folder located inside the java
folder.
The folder contains files with the .java
, containing descriptions of the classes included in the package. At the beginning of such a file there should be a package statement, after which the package name is written.
The full class name consists of the identifier specified after the class keyword and the preceding package name in which this class is located. The ClassA
and ClassB
classes described in package1
have the full names package1.ClassA
and package1.ClassB
.
Classes that are inside the same package can use each other's abbreviated names (which we have always done up to now). In one of the ClassA
class ClassA
you can define a ClassA
class variable, create an object of the ClassB
class, and call its method (for example, f()
) with the commands:
ClassB varb = new ClassB();
varb.f();
instead of commands:
package1.ClassB varb = new package1.ClassB();
varb.f();
If classes are in different packages, they must use full names to refer to each other.
This limitation can be circumvented by importing the required class from another package.
To import a class, use the import keyword followed by its full name. For example, you can import the Vector
class from the java.util
package:
import java.util.Vector;
Now you can use the name Vector
instead of java.util.Vector
.
To import a package completely (that is, all classes of a package can be accessed using an abbreviated name), a dot and an asterisk appear after the package name. So the team
import java.util.*;
imports all files from the java.util
package. But using this method is not recommended, since in this case files with the same name can be imported from different packages. *
Eclipse makes life easier for developers. If the program uses a class with an unknown name, an error warning icon appears in the margin of the code editor. Clicking this icon displays solutions to the problem. For example, create a new class. Or import an existing one (this will display a list of all available packages containing a class with that name). If you select the "Import" option, the corresponding import directive will be automatically added to the beginning of the package.
All classes in the same package must have different names, otherwise an error will occur. For classes that are in different packages, such a requirement is obviously impossible (since packages can be developed by completely different people).
Therefore, when importing multiple packages that contain classes with the same name, there may be confusion in these names. However, Java deals with this problem by “giving” the short name to the first imported class. Another class can be accessed using its full name.
If both the package names and the class names match, the problem will not be solved. And since the programmer can use many packages of different developers, this situation is quite likely if the developers do not agree. Therefore, there is a naming convention for packages (especially those that may later be distributed).
It is recommended for the name of the package to use the address of the site of the developer. Almost all serious program developers have website addresses (and, most importantly, website addresses cannot match). It is recommended to write the website address in reverse. That is, if the address is sun.com, then the package name must begin with com.sun. By the way, there are quite a few such packages on your system, supplied by Sun Microsystems, the developer of the Java language.
So, a Java project can consist of several packages. Each package in the file structure of the operating system corresponds to one folder.
A package may contain classes and interfaces. They are stored in files with the .java extension.
Each file with the extension .java
necessarily describes one class or interface, the name of which coincides with the name of this file. It must be declared as open (the public keyword is specified before the class is declared). All other classes and interfaces from this file are private — they cannot be used outside of this package (neither with the long nor with the short name). Therefore, with the Dog
class that we developed in the last lesson, you can work exclusively within its package.
All classes and interfaces that are subsequently supposed to be used in other packages must be declared open (and therefore must be in separate files).
Eclipse creates a new file with the .java
automatically if you run the New command -> Class or New -> Interface.
A file with the extension .java
is a plain text file. It can be opened and edited both with Eclipse and in any other text editor (even in Notepad).
For each class (open or closed), Java creates a file with the extension .class
. This is a binary file that stores commands in the internal Java language. These files are not editable in Eclipse (if you try to open them, Eclipse actually opens the corresponding .java
file). So that they do not interfere, they can be hidden using a filter. To do this, in the Navigator view, click the small triangular button on the right (menu) and select the Filters ... command. In the window that opens, check the box next to the .class
extension to hide the corresponding files from the Navigator panel.
A class scope is a program section in which you can declare variables and create objects of this class.
An object created in the scope of its class can continue to exist outside of it. But variables of the “invisible” class type cannot be declared, therefore, access to such an object is possible only with the help of variable types of “visible” ancestor classes. Consequently, it is possible to call only those object methods that its class inherited from the corresponding ancestor.
A regular class is always visible within its package. If a class is open, it is also visible from other packages (in this case, it must be accessed using the full name or, if the package is previously imported, abbreviated).
Nested classes declared without the public modifier are visible only in the methods of the class containing them. The classes described in the methods are visible only within these methods.
Anonymous classes are visible only within the command by which they are created.
Class members (methods and attributes) declared as public are visible wherever the class itself is visible.
Class members declared as protected are visible in the class itself and its descendants.
Class members that are declared private are visible only within the class.
If none of the public, private, protected modifiers is applied to the class member, it is visible within the current package.
"Visibility" of the field means that it can be used in expressions, passed as an argument to methods, change its value using assignment.
The "visibility" of a method means the ability to call it.
To refer to the fields and methods of the class, the full name is used, consisting of the object name of the corresponding class and the actual name of the attribute or method. In the body of the method of the same class, the name of the object can be omitted (if this is implied, the object for which this method is called).
Variables declared in the method body are visible from the declaration point to the end of the block in which this declaration is located. Block boundaries are given by curly brackets {}
. Therefore, in the following example:
{
int x = 0;
}
{
int x = 2;
}
two different variables x
(the first variable, equal to 0, ceases to exist beyond the boundaries of its block).
Variables that are parameters of a method are visible throughout the entire body of the method.
The variables listed are called local variables . There should be no two local variables with the same name and overlapping scopes. You cannot, for example, declare two variables as follows:
int x = 0;
{
int x = 2;
}
It is impossible, in particular, to declare in the body of a method a variable that matches (by name) with one of the parameters of the method.
Конфликт имен возникает, если в одной области видимости оказываются два объекта с одинаковыми именами, причем такая ситуация является в языке разрешенной (в противном случае это не конфликт имен, а ошибка в программе).
Конфликт имен возникает, когда импортируются два пакета, содержащие классы с одинаковыми именами.
В этом случае Java отдает предпочтение классу, который был импортирован первым. Для обращения ко остальным классам следует использовать их полные имена.
Java "просматривает" имена классов в следующем порядке. Сначала — классы, импортированные поодиночке. Потом — классы, определенные в данном пакете. В последнюю очередь классы из пакетов, импортируемых полностью в порядке следования команд import.
Конфликт имен возникает так же, когда имя одного из параметров метода совпадает с именем одного из атрибутов этого же класса. Такая ситуация возникает довольно часто, поскольку для метода, изменяющего значение этого атрибута, такое называние параметра довольно наглядно. For example:
class Dog {
int age;
...
public void setAge(int age) {
...
};
...
}
Такой заголовок метода setAge(int age)
лучше, чем использовавшийся нами на прошлом занятии setAge(int a)
, поскольку сразу позволяет судить о назначении параметра. Однако возникает вопрос: к чему будет относиться имя age
в теле этого метода — к атрибуту или к параметру.
Ответ: к параметру. Имя параметра «перекрывает» имя атрибута.
Для того, чтобы обратиться к атрибуту, следует использовать полное имя (т.е. указатель на объект this).
Реализация метода должна выглядеть следующим образом:
public void setAge(int age) {
this.age = age; // проверку диапазона параметра в этом примере проигнорируем
};
1. Вязовик Н.А. Программирование на Java. (глава 8)
2. Khabibullin I.Sh. Java 2 Tutorial (Chapter 3)
Comments
To leave a comment
OOP and Practical JAVA
Terms: OOP and Practical JAVA