Вы находитесь на странице: 1из 24

Features of java

BY DINESH THAKUR Category: Java Evolution

1) Compiled and Interpreter: has both Compiled and Interpreter Feature Program
of java is First Compiled and Then it is must to Interpret it .First of all The Program of
java is Compiled then after Compilation it creates Bytes Codes rather than Machine
Language.
Then After Bytes Codes are Converted into the Machine Language is Converted into
the Machine Language with the help of the Interpreter So For Executing the java
Program First of all it is necessary to Compile it then it must be Interpreter
2) Platform Independent: Java Language is Platform Independent means program
of java is Easily transferable because after Compilation of java program bytes code
will be created then we have to just transfer the Code of Byte Code to another
Computer.
This is not necessary for computers having same Operating System in which the
code of the java is Created and Executed After Compilation of the Java Program We
easily Convert the Program of the java top the another Computer for Execution.
3) Object-Oriented: We Know that is purely OOP Language that is all the Code of
the java Language is Written into the classes and Objects So For This feature java is
Most Popular Language because it also Supports Code Reusability, Maintainability
etc.
4) Robust and Secure: The Code of java is Robust andMeans ot first checks the
reliability of the code before Execution When We trying to Convert the Higher data
type into the Lower Then it Checks the Demotion of the Code the It Will Warns a
User to Not to do this So it is called as Robust.
Secure: When We convert the Code from One Machine to Another the First Check
the Code either it is Effected by the Virus or not or it Checks the Safety of the Code if
code contains the Virus then it will never Executed that code on to the Machine.
5) Distributed: Java is Distributed Language Means because the program of java is
compiled onto one machine can be easily transferred to machine and Executes them
on another machine because facility of Bytes Codes So java is Specially designed
For Internet Users which uses the Remote Computers For Executing their Programs
on local machine after transferring the Programs from Remote Computers or either
from the internet.
6) Simple Small and Familiar:- is a simple Language Because it contains many
features of other Languages like c and C++ and Java Removes Complexity because
it doesn’t use pointers, Storage Classes and Go to Statements and java Doesn’t
support Multiple Inheritance
7) Multithreaded and Interactive:- Java uses Multithreaded Techniques For
Execution Means Like in other in Structure Languages Code is Divided into the
Small Parts Like These Code of java is divided into the Smaller parts those are
Executed by java in Sequence and Timing Manner this is Called as Multithreaded In
this Program of java is divided into the Small parts those are Executed by Compiler
of java itself Java is Called as Interactive because Code of java Supports Also CUI
and Also GUI Programs
8) Dynamic and Extensible Code:- Java has Dynamic and Extensible Code Means
With the Help of OOPS java Provides Inheritance and With the Help of Inheritance
we Reuse the Code that is Pre-defined and Also uses all the built in Functions of
java and Classes
9) Distributed:- Java is a distributed language which means that the program can
be design to run on computer networks. Java provides an extensive library of
classes for communicating ,using TCP/IP protocols such as HTTP and FTP. This
makes creating network connections much easier than in C/C++. You can read and
write objects on the remote sites via URL with the same ease that programmers are
used to when read and write data from and to a file. This helps the programmers at
remote locations to work together on the same project.
10) Secure: Java was designed with security in mind. As Java is intended to be
used in networked/distributor environments so it implements several security
mechanisms to protect you against malicious code that might try to invade your file
system.
For example: The absence of pointers in Java makes it impossible for applications to
gain access to memory locations without proper authorization as memory allocation
and referencing model is completely opaque to the programmer and controlled
entirely by the underlying run-time platform .
11) Architectural Neutral: One of the key feature of Java that makes it different
from other programming languages is architectural neutral (or platform independent).
This means that the programs written on one platform can run on any other platform
without having to rewrite or recompile them. In other words, it follows 'Write-once-
run-anywhere' approach.
Java programs are compiled into byte-code format which does not depend on any
machine architecture but can be easily translated into a specific machine by a Java
Virtual Machine (JVM) for that machine. This is a significant advantage when
developing applets or applications that are downloaded from the Internet and are
needed to run on different systems.
12) Portable : The portability actually comes from architecture-neutrality. In C/C++,
source code may run slightly differently on different hardware platforms because of
how these platforms implement arithmetic operations. In Java, it has been
simplified.
Unlike C/C++, in Java the size of the primitive data types are machine independent.
For example, an int in Java is always a 32-bit integer, and float is always a 32-bit
IEEE 754 floating point number. These consistencies make Java programs portable
among different platforms such as Windows, Unix and Mac .
13) Interpreted : Unlike most of the programming languages which are either
complied or interpreted, Java is both complied and interpreted The Java compiler
translates a java source file to bytecodes and the Java interpreter executes the
translated byte codes directly on the system that implements the Java Virtual
Machine. These two steps of compilation and interpretation allow extensive code
checking and improved security .
14) High performance: Java programs are complied to portable intermediate form
know as bytecodes, rather than to native machine level instructions and JVM
executes Java bytecode on. Any machine on which it is installed. This architecture
means that Java programs are faster than program or scripts written in purely
interpreted languages but slower than C and C++ programs that compiled to native
machine languages

2) An object can be considered a "thing" that can perform a set


of related activities. The set of activities that the object performs defines the
object's behavior. For example, the Hand (object) can grip something, or
a Student(object) can give their name or address.

In pure OOP terms an object is an instance of a class.

b) Constant is something that doesn't change. In C and C++ we use the keyword const to make
program elements constant. const keyword can be used in many contexts in a C++ program. It
can be used with:

1. Variables
2. Pointers
3. Function arguments and return types
4. Class Data members
5. Class Member functions
6. Objects

1) Constant Variables
If you make any variable as constant, using const keyword, you cannot change its value. Also,
the constant variables must be initialized while they are declared.
int main
{
const int i = 10;
const int j = i + 10; // works fine
i++; // this leads to Compile time error
}
In the above code we have made i as constant, hence if we try to change its value, we will get
compile time error. Though we can use it for substitution for other variables.
2) Pointers with const keyword
Pointers can be declared using const keyword too. When we use const with pointers, we can do
it in two ways, either we can apply const to what the pointer is pointing to, or we can make the
pointer itself a constant.

Pointer to a const variable


This means that the pointer is pointing to a const variable.
const int* u;
Here, u is a pointer that can point to a const int type variable. We can also write it like,
char const* v;
still it has the same meaning. In this case also, v is a pointer to an char which is of const type.
Pointers to a const variable is very useful, as this can be used to make any string or array
immutable(i.e they cannot be changed).

const Pointer
To make a pointer constant, we have to put the const keyword to the right of the *.
int x = 1;
int* const w = &x;
Here, w is a pointer, which is const, that points to an int. Now we can't change the pointer, which
means it will always point to the variable x but can change the value that it points to, by changing
the value of x.
The constant pointer to a variable is useful where you want a storage that can be changed in
value but not moved in memory. Because the pointer will always point to the same memory
location, because it is defined with const keyword, but the value at that memory location can be
changed.
NOTE: We can also have a const pointer pointing to a const variable.
const int* const x;

3) const Function Arguments and Return types


We can make the return type or arguments of a function as const. Then we cannot change any
of them.
void f(const int i)
{
i++; // error
}

const int g()


{
return 1;
}

2) Multiple Constructor Methods


A special type of method that creates an instance of a method is called a Constructor Method.
When an object has member variables that are objects, we need to define a constructor method to
set up those variables. We'll show how to do that here. If you want a more basic introduction to
constructor methods, you may want to take a look at my prior article.

As an example, we're going to create a simple class of object for use with my simple video game
kernel in a new version I'll be introducing in an article in the near future. The class definition
consists mostly of constructor methods, since the class itself is presently not much more than
a Rectangle with an added field.

import java.awt.*;

// A Simple class for use in the simple video game examples.


// Mark Graybill, Aug. 2010

public class Brick extends Rectangle{


Color brickColor;

public Brick(int newX, int newY, int newWidth, int newHeight){


super(newX, newY, newWidth, newHeight);
brickColor = new Color(0, 128, 255);
}

public Brick(int newX, int newY){


this(newX, newY, 10, 10);
}
public Brick(){
this(0,0,10,10);
}

public void setColor(Color newColor){ brickColor=newColor; }


public Color getColor(){ return brickColor; }

} // End Bric

b) Converted to Java by type into two categories:


1. Covert transformation;
2. Explicit conversion.
Implicitly (implicit) Type Conversion
Implicit ( hidden ) type conversion is only possible when there is no possibility of
data loss in the conversion , ie When you convert type to a smaller range to type with
greater ( for example from int to long). To make implicit conversion is not need to
use any operator , so called hidden.
The conversion is done automatically by the compiler when mapping value of small-
scale variable in a larger scope, or when expression types has a different scope. Then
convert it to type with a greater range .

Implicit Type Conversion - Example


Here is an example of implicit (implicit) type conversion :
int VarInt = 5;
System.out.println(VarInt); // 5
long Varlong = VarInt;
System.out.println(Varlong); // 5
System.out.println(Varlong + VarInt); // 10

Possible Transformations Implied


These are possible implicit conversion of primitive types in Java:
1. Byte to short, int, long, float, or double
2. Short to int, long, float, or double
3. Char to int, long, float, or double
4. Int to long, float, or double
5. Long to float or double
6. Float to double
Conversion of types from small-scale to a larger no loss of data . The numerical value
remains the same after conversion. As with any rule there are few exceptions.
when convert type int to type float ( 32-bit values) , the difference is int that uses all
your bits for a single integer including as part of the float bits used for the
presentation of Float . Hence, it is possible in the conversion of int to float to a loss of
accuracy due to rounding . The same concerns the conversion of 64-bit long to
double.
Explicitly (explicit) Type Conversion
Explicit type conversion is needed when it is probable loss of data. When you convert
a floating-point type to an integer type, there is always loss of data coming from the
float and must use explicit conversion (double to long). To make such conversion is
necessary to explicitly use operator for data conversion (cast operator): (type).
possibly There may be data loss also when converting from type larger range to type
with less (double to float or long to int).

Explicit Type Conversion - Example


The following example illustrates the use of an explicit conversion types and data
loss:
double VarDouble = 5.1d;
System.out.println(VarDouble); // 5.1
long Varlong = (long)VarDouble;
System.out.println(Varlong); // 5
VarDouble = 5e9d; // 5 * 10^9
System.out.println(VarDouble); // 5.0E9
int VarInt = (int) VarDouble;
System.out.println(VarInt); // 2147483647
System.out.println(Integer.MAX_VALUE); // 2147483647

3) The word polymorphism means having many forms. In simple words, we can define
polymorphism as the ability of a message to be displayed in more than one form.
Real life example of polymorphism, a person at a same time can have different characteristic.
Like a man at a same time is a father, a husband, a employee. So a same person posses have
different behavior in different situations. This is called polymorphism.

There are many differences between method overloading and method overriding in java. A list of
differences between method overloading and method overriding are given below:

No. Method Overloading Method Overriding

1) Method overloading is used to increase the readability of the Method overriding is used to
program. specific implementation of the m
already provided by its super cla

2) Method overloading is performed within class. Method overriding occurs in two


have IS-A (inheritance) relations

3) In case of method overloading, parameter must be different. In case of method overriding, pa


be same.

4) Method overloading is the example of compile time polymorphism. Method overriding is the examp
polymorphism.

5) In java, method overloading can't be performed by changing Return type must be same o
return type of the method only. Return type can be same or method overriding.
different in method overloading. But you must have to change the
parameter.

3) b) The final keyword in java is used to restrict the user. The java final keyword can be used in
many context. Final can be:

1. variable

2. method

3. class

The final keyword can be applied with the variables, a final variable that have no value it is called
blank final variable or uninitialized final variable. It can be initialized in the constructor only. The
blank final variable can be static also which will be initialized in the static block only. We will have
detailed learning of these. Let's first learn the basics of final keyword.

1) Java final variable


If you make any variable as final, you cannot change the value of final variable(It will be
constant).

Example of final variable


There is a final variable speedlimit, we are going to change the value of this variable, but It can't
be changed because final variable once assigned a value can never be changed.

1. class Bike9{
2. final int speedlimit=90;//final variable
3. void run(){
4. speedlimit=400;
5. }
6. public static void main(String args[]){
7. Bike9 obj=new Bike9();
8. obj.run();
9. }
10. }//end of class
Test it Now

Output:Compile Time Error

2) Java final method


If you make any method as final, you cannot override it.

Example of final method


1. class Bike{
2. final void run(){System.out.println("running");}
3. }
4.
5. class Honda extends Bike{
6. void run(){System.out.println("running safely with 100kmph");}
7.
8. public static void main(String args[]){
9. Honda honda= new Honda();
10. honda.run();
11. }
12. }
Test it Now

Output:Compile Time Error

3) Java final class


If you make any class as final, you cannot extend it.

Example of final class


1. final class Bike{}
2.
3. class Honda1 extends Bike{
4. void run(){System.out.println("running safely with 100kmph");}
5.
6. public static void main(String args[]){
7. Honda1 honda= new Honda1();
8. honda.run();
9. }

5)a)The exception handling in java is one of the powerful mechanism


to handle the runtime errors so that normal flow of the application can be
maintained.

In this page, we will learn about java exception, its type and the
difference between checked and unchecked exceptions.

What is exception
Dictionary Meaning: Exception is an abnormal condition.

In java, exception is an event that disrupts the normal flow of the


program. It is an object which is thrown at runtime.

What is exception handling


Exception Handling is a mechanism to handle runtime errors such as
ClassNotFound, IO, SQL, Remote etc.

Advantage of Exception Handling


The core advantage of exception handling is to maintain the normal
flow of the application. Exception normally disrupts the normal flow of
the application that is why we use exception handling. Let's take a
scenario:

1. statement 1;
2. statement 2;
3. statement 3;
4. statement 4;
5. statement 5;//exception occurs
6. statement 6;
7. statement 7;
8. statement 8;
9. statement 9;
10.statement 10;

Suppose there is 10 statements in your program and there occurs an


exception at statement 5, rest of the code will not be executed i.e.
statement 6 to 10 will not run. If we perform exception handling, rest of
the statement will be executed. That is why we use exception handling in
java.

Do You Know ?

o What is the difference between checked and unchecked exceptions ?

o What happens behind the code int data=50/0; ?

o Why use multiple catch block ?

o Is there any possibility when finally block is not executed ?

o What is exception propagation ?

o What is the difference between throw and throws keyword ?

o What are the 4 rules for using exception handling with method
overriding ?
Hierarchy of Java Exception classes
Types of Exception
There are mainly two types of exceptions: checked and unchecked where
error is considered as unchecked exception. The sun microsystem says
there are three types of exceptions:

1. Checked Exception

2. Unchecked Exception

3. Error

Difference between checked and


unchecked exceptions
1) Checked Exception
The classes that extend Throwable class except RuntimeException and
Error are known as checked exceptions e.g.IOException, SQLException
etc. Checked exceptions are checked at compile-time.

2) Unchecked Exception
The classes that extend RuntimeException are known as unchecked
exceptions e.g. ArithmeticException, NullPointerException,
ArrayIndexOutOfBoundsException etc. Unchecked exceptions are not
checked at compile-time rather they are checked at runtime.

3) Error
Error is irrecoverable e.g. OutOfMemoryError, VirtualMachineError,
AssertionError etc.

5)b) Advantage 1: Separating Error-Handling Code from “Regular” Code


Exceptions provide the means to separate the details of what to do when
something out of the ordinary happens from the main logic of a program. In
traditional programming, error detection, reporting, and handling often lead to
confusing spaghetti code. For example, consider the following pseudocode
method that reads an entire file into memory:
readFile {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
}
At first glance, this function seems simple enough, but it ignores all these
potential errors.

 What happens if the file can’t be opened?


 What happens if the length of the file can’t be determined?
 What happens if enough memory can’t be allocated?
 What happens if the read fails?
 What happens if the file can’t be closed?

To handle these cases, the readFile function must have more code to do error
detection, reporting, and handling. The function might look like this:
errorCodeType readFile {
initialize errorCode = 0;
open the file;
if (theFileIsOpen) {
determine the length of the file;
if (gotTheFileLength) {
allocate that much memory;
if (gotEnoughMemory) {
read the file into memory;
if (readFailed) {
errorCode = -1;
}
} else {
errorCode = -2;
}
} else {
errorCode = -3;
}
close the file;
if (theFileDidntClose && errorCode == 0) {
errorCode = -4;
} else {
errorCode = errorCode and -4;
}
} else {
errorCode = -5;
}
return errorCode;
}
There’s so much error detection, reporting, and returning here that the original
seven lines of code are lost in the clutter. Worse yet, the logical flow of the code
also has been lost, thus making it difficult to tell whether the code is doing the
right thing: Is the file really being closed if the function fails to allocate enough
memory? It’s even more difficult to ensure that the code continues to do the right
thing after you modify the method three months after writing it. Many
programmers “solve” this problem by simply ignoring it—errors are “reported”
when their programs crash.

Exceptions enable you to write the main flow of your code and to deal with the
exceptional cases elsewhere. If the readFile function used exceptions instead of
traditional error-management techniques, it would look more like this:
readFile {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
} catch (fileOpenFailed) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (memoryAllocationFailed) {
doSomething;
} catch (readFailed) {
doSomething;
} catch (fileCloseFailed) {
doSomething;
}
}
Note that exceptions don’t spare you the effort of doing the work of detecting,
reporting, and handling errors, but they do help you organize the work more
effectively.
Advantage 2: Propagating Errors Up the Call Stack
A second advantage of exceptions is the ability to propagate error reporting up
the call stack of methods. Suppose that the readFile method is the fourth
method in a series of nested method calls made by the main
program: method1 calls method2, which calls method3, which finally calls readFile:
method1 {
call method2;
}
method2 {
call method3;
}
method3 {
call readFile;
}
Suppose also that method1 is the only method interested in the errors that might
occur within readFile. Traditional error-notification techniques
force method2 and method3 to propagate the error codes returned by readFile up
the call stack until the error codes finally reach method1—the only method that is
interested in them:
method1 {
errorCodeType error;
error = call method2;
if (error)
doErrorProcessing;
else
proceed;
}
errorCodeType method2 {
errorCodeType error;
error = call method3;
if (error)
return error;
else
proceed;
}
errorCodeType method3 {
errorCodeType error;
error = call readFile;
if (error)
return error;
else
proceed;
}
Recall that the Java runtime environment searches backward through the call
stack to find any methods that are interested in handling a particular exception. A
method can “duck” any exceptions thrown within it, thereby allowing a method
farther up the call stack to catch it. Hence, only the methods that care about
errors have to worry about detecting errors:
method1 {
try {
call method2;
} catch (exception e) {
doErrorProcessing;
}

}
method2 throws exception {
call method3;
}
method3 throws exception {
call readFile;
}
However, as the pseudocode shows, ducking an exception requires some effort
on the part of the middleman methods. Any checked exceptions that can be
thrown within a method must be specified in the throws clause of the method.
Advantage 3: Grouping and Differentiating Error Types
Because all exceptions thrown within a program are objects, grouping or
categorizing of exceptions is a natural outcome of the class hierarchy. An example
of a group of related exception classes in the Java platform are those defined
in java.io: IOException and its descendants. IOException is the most general and
represents any type of error that can occur when performing I/O. Its descendants
represent more specific errors. For example, FileNotFoundException means that a
file could not be located on disk.

A method can write specific handlers that can handle a very specific exception.
The FileNotFoundException class has no descendants, so the following handler can
handle only one type of exception:
catch (FileNotFoundException e) {
...
}
A method can catch an exception based on its group or general type by specifying
any of the exception’s superclasses in the catch statement. For example, to catch
all I/O exceptions, regardless of their specific type, an exception handler specifies
an IOException argument:
catch (IOException e) {
...
}
This handler will catch all I/O exceptions,
including FileNotFoundException, EOFException, and so on. You can find the
details on what occurred by querying the argument passed to the exception
handler. For example, to print the stack trace:
catch (IOException e) {
e.printStackTrace(); // output goes to Sytem.err
e.printStackTrace(System.out); // send trace to stdout
}
You could even set up an exception handler that handles any Exception with this
handler:
catch (Exception e) { // a (too) general exception handler
...
}
The Exception class is close to the top of the Throwable class hierarchy. Therefore,
this handler will catch many other exceptions in addition to those that the
handler is intended to catch. You may want to handle exceptions this way if all
you want your program to do, for example, is print out an error message for the
user and exit.
However, in most situations, you want exception handlers to be as specific as
possible. The reason is that the first thing a handler must do is determine what type
of exception occurred before it can decide on the best recovery strategy. In effect,
by not catching specific errors, the handler must accommodate any possibility.
Exception handlers that are too general can make code more error prone by
catching and handling exceptions that weren’t anticipated by the programmer and
for which the handler was not intended.

As we’ve shown, you can create groups of exceptions and handle exceptions in a
general fashion, or you can use the specific exception type to differentiate
exceptions and handle exceptions in an exact fashion.

5)

A thread can be in one of the five states. According to sun, there is only 4 states in thread life
cycle in java new, runnable, non-runnable and terminated. There is no running state.

But for better understanding the threads, we are explaining it in the 5 states.

The life cycle of the thread in java is controlled by JVM. The java thread states are as follows:

1. New

2. Runnable

3. Running

4. Non-Runnable (Blocked)

5. Terminated
1) New

The thread is in new state if you create an instance of Thread class but before the invocation of
start() method.

2) Runnable
The thread is in runnable state after invocation of start() method, but the thread scheduler has not
selected it to be the running thread.

3) Running
The thread is in running state if the thread scheduler has selected it.

4) Non-Runnable (Blocked)
This is the state when the thread is still alive, but is currently not eligible to run.

5) Terminated
A thread is in terminated or dead state when its run() method exits.
6)Code for An applet program that concatenates two string entered in
TextField in Java
/* <applet code="concat2Str" height=150 width=350> </applet> */

import java.awt.*;
import java.applet.*;

publicclass concat2Str extends Applet


{
TextField Ts1,Ts2;

publicvoid init(){
Ts1 = new TextField(10);
Ts2 = new TextField(10);
add(Ts1);
add(Ts2);
Ts1.setText("");
Ts2.setText("");
}

publicvoid paint(Graphics g){


String str1,str2;

g.drawString("Enter Two String to Concat Them ",10,50);

str1=Ts1.getText();
str2=Ts2.getText();
g.setColor(Color.red);
g.drawString(str1+" "+str2,10,70);
showStatus("Concatination of 2 String");
}

public boolean action(Event e, Object o){


repaint();
returntrue;
}

6)b)A Java applet sound example


By Alvin Alexander. Last updated: June 3 2016

We all know that multimedia, used properly, can make any web site more
entertaining. In this applet tutorial, we'll present a brief example of a Javaapplet
that plays a sound file when it is downloaded. The compiled applet class file is
very small - only 559 bytes - and can be downloaded quickly into a user's web
browser.

The SoundApplet applet

Fortunately, using sounds in a Java applet is very simple.


The JavaSoundApplet.java source code shown below demonstrates the
minimal requirements needed to play a sound file when a Java applet is
downloaded.

Notice that to make this Java applet easy to hide on a web page, we resized it's
visible dimensions to zero pixels wide, zero pixels tall.

import java.applet.*;

/**

* JavaSoundApplet.java - an example java applet that plays

* the "gong.au" sound file when the applet is loaded.

*/

public class JavaSoundApplet extends Applet

public void init()

super.init();

// set the applet size

resize(0,0);

// load the sound file and then play it

AudioClip gong = getAudioClip(getDocumentBase(), "gong.au");


gong.play();

As you can see from our Java applet example, there are really only two steps
required to play a sound in a Java applet: (1) loading the sound file into
an AudioClip object, and (2) playing the sound using the play()method of
the AudioClip class.

Also notice that there is no path leading to the gong.au file -- the applet expects to
find the gong.au file in the same directory as the class file. If instead the gong.au
file was located in a sub-directory named sounds, the file would have been loaded
with this statement:

AudioClip gong = getAudioClip(getDocumentBase(), "sounds/gong.au");

The SoundApplet.html HTML file

Every Java applet needs an HTML file to access it, so in the next listing we've
provided the source code for a bare-bones HTML file that loads
the SoundApplet applet.

<!-- SoundApplet.html - a simple HTML file that loads the SoundApplet applet.
-->

<HTML>

<HEAD>

<TITLE>SoundApplet Demo</TITLE>
</HEAD>

<BODY>

<APPLET CODE="SoundApplet.class" height="0" width="0"></APPLET>

</BODY>

</HTML>

Notice in this listing that nothing special is required, other than the use of
the <APPLET> tag. This tag simply gives your browser the information it needs to
load the Java SoundApplet class file. Your browser takes care of the rest of the
work.

Вам также может понравиться