Philosophy java latest edition. Bruce Eckel - The Philosophy of Java3. Server Side Programming

  • 12.04.2020

To change the default document, edit the "blank.fb2" file manually.

Preface 13

Java SE5 and SE6 14

Thanks 14

Chapter 1 Introduction to Objects 17

Development of abstraction 18

Object has interface 20

The facility provides services 22

Hidden Implementation 23

Reuse implementation 24

Inheritance 25

Interchangeable Objects and Polymorphism 29

Single Root Hierarchy 33

Containers 33

Parameterized types 35

Creation, use of objects and their lifetime 36

Exception Handling: Dealing with Errors 38

Parallel execution 38

Java and the Internet 39

Chapter 2. Everything is an object 48

All objects must be explicitly created 49

Objects never have to be deleted 53

Creating new data types 54

Methods, Arguments, and Return Values ​​56

Creating a Java 58 Program

Keyword static 60

Our first Java 61 program

Comments and embedded documentation 64

Program design style 70

Chapter 3 Operators 71

Simple print commands 71

Java 72 statements

Literals 82

Java lacks sizeof() 92

Resume 100

Chapter 4 Control Structures 101

Syntax foreach 105

break and continue 108

Bad command goto 109

Resume 115

Chapter 5 Initialization and Termination 116

Constructor guarantees initialization 116

Method Overloading 118

Cleanup: finalization and garbage collection 130

Initializing class members 137

Constructor initialization 140

Array initialization 146

Resume 151

Chapter 6 Access Control 152

Package as library module 153

Java Access Specifiers 159

Interface and Implementation 163

Access to classes 164

Resume 167

Chapter 7 Reusing Classes 169

Composition syntax 170

Inheritance syntax 172

Delegation 176

Combining Composition and Inheritance 178

Composition versus inheritance 184

Upward Type Conversion 186

Keyword final 188

Resume 197

Chapter 8. Polymorphism 198

Again about the upward transformation. . . > 199

Features 201

Constructors and polymorphism 208

Return type covariance 216

Development with inheritance 217

Resume 220

Chapter 9 Interfaces 221

Abstract classes and methods 221

Interfaces 224

Separating the Interface from the Implementation 227

Extending an Interface Through Inheritance 233

Interfaces as a means of adaptation 236

Nested interfaces 239

Interfaces and factories 242

Resume 244

Chapter 10 Inner Classes 245

Creating Inner Classes 245

Communication with an external class 246

The .this and .new constructs 248

Inner classes and upcasting 249

Unnamed internal classes 253

Inner classes: why? 261

Inheritance from Inner Classes 272

Is it possible to override an inner class? 272

Local Inner Classes 274

Resume 276

Chapter 11 Object Collections 277

Parameterized and Typed Containers 277

Core Concepts 280

Adding element groups 281

Iterators 288

Set 294

Queue 298

PriorityQueue 299

Collection and Iterator 301

Idiom "method-adapter" 306

Resume 309

Chapter 12 Handling Errors and Exceptions 310

Major exceptions 310

Catching 312 Exceptions

Creating your own exceptions 314

Exception Specifications 319

Catching Arbitrary Exceptions 320

Java 328 Standard Exceptions

Termination with finally 330

Using finally with return 334

Limitations when using exceptions 336

Constructors 339

Exception identification 343

Alternative solutions 344

Resume 351

Chapter 13 Type Information 352

Need for Dynamic Type Inference (RTTI) 352

Registration of factories 372

Reflection: dynamic class information 376

Dynamic mediators 380

Objects with an indeterminate state 384

Interfaces and Type Information 390

Resume 394

Chapter 14 Parameterization 397

Simple parameterization 398

Parameterized Interfaces 404

Parameterized Methods 407

Building complex models 419

Restrictions 437

Metacharacters 440

Resume 452

Chapter 15 Arrays 454

Features of arrays 454

Array as object 456

Array return 458

Multidimensional arrays 460

Arrays and parameterization 463

Creating test data 465

Creating Arrays Using Generators 470

Arrays 474 Helper Toolkit

Resume 482

Chapter 16 The Java I/O System 483

File 484 class

Input and output 489

Adding Attributes and Interfaces 491

The Reader and Writer Classes 494

RandomAccessFile: by itself 497

Typical use of I/O streams 498

File Readers and Writers 505

Standard I/O 507

New I/O (nio) 510

Data compression 531

Serializing objects 536

Preferences 553

Resume 555

Chapter 17 Parallel Execution 557

Thread class 559

Artists 561

Sharing resources 578

Communication between threads 598

Mutual blocking 602

New library components 607

CountDownLatch 607

CyclicBarrier 609

PriorityBlockingQueue 614

Semaphores 619

Modeling 624

Resume 629

Alphabetical index 631

Introduction to Objects

We dissect nature, transform it into concepts, and ascribe meaning to them, as we do in many ways, because we are all parties to an agreement that is valid in a society bound by speech, and which is fixed in the structure of language ... We cannot communicate at all, except by agreeing to the organization and classification of data established by this agreement.

Benjamin Lee Worf (1897-1941)

We owe the emergence of the computer revolution to the machine. Therefore, our programming languages ​​try to be closer to this machine.

But at the same time, computers are not so much mechanisms as they are means of amplifying thought (“bicycles for the mind,” as Steve Jobs likes to say), and another means of self-expression. As a result, programming tools lean less towards machines and more towards our minds, as well as other forms of expression of human aspirations, such as literature, painting, sculpture, animation, and cinema. Object-oriented programming (OOP) is part of turning the computer into a vehicle for self-expression.

This chapter will introduce you to the basics of OOP, including a look at the main methods of software development. It, and the book in general, assumes that you have programming experience in a procedural language, not necessarily C. If you feel that before reading this book you lack knowledge of programming and C syntax, use the Thinking in C multimedia seminar. which can be downloaded from the website

In March last year, he applied to a branch of a large international company in Samara (yes. I have had a lot of arrogance and ambition since childhood). At that time I knew html, css, java, javascript (basics), pascal, visualbasic6, mysql queries, php, general idea:c++. I didn't know Java at all. They offered me a job as a layout designer, but I turned it down. Only as a programmer! Then they gave me a list:

Bruce Eckel Thinking in Java (Russian translation of the 2nd edition or the original of the 4th - read both)
-Steve McConnell - perfect code.
- Gang of four - Pattern design. (this is almost the ABC of OOP)
-be as clear as possible about the difference between j2se and j2ee.

in December the need arose. got a job in a small Samara web-studio. it was immediately clear that these were scammers, but any work that I could show to future employers was needed. they didn’t pay to pay (even though all the ears were eaten with promises), but the code was brought to the design standards, and most importantly, they taught where to look and what to look for in case of errors, what little things are easy to miss.

In addition to the literature above, I took the intuit course (now I understand that it is ridiculous in its scope, but in principle there are basics there)

At the end of February, I resubmitted my resume and received an invitation for an interview. There were 6 interviews in total and lasted 1.5 months. Two of them were held via video link with Moscow. The whole picture was reminiscent of the movie "Come Tomorrow". But in the end, I got a job offer. The contract was drawn up for part-time employment, because. I didn't have a degree at the time. Last month I received a diploma and the contract was renewed for a full-time one.

Current position of Soft-Engineer. The salary is more than satisfactory. Yesterday, in connection with the transition to a full-time job, they raised it by 30%.

Even in that swindler's office they asked for examples of work. I presented the work done by me freelance. Even works in other languages ​​are always much better than none.

Ps: Diploma blue in PHYSICS. I am completely self-taught, so everything is in your hands. I only have English from school Free (7 hours a week). although the American who came to us during his trip around the world does not know him well. I barely understood half of it because of his accent. but this is not so critical in my department. all documentation in English - you will learn even if you did not know)))))

Special thanks to this forum. I actually studied here- daily taught all the topics that come across)

PROGRAMMER LIBRARY

Bruce Eckel

4th edition

(^PPTER

Moscow - St. Petersburg - Nizhny Novgorod - Voronezh Rostov-on-Don - Yekaterinburg - Samara - Novosibirsk Kyiv - Kharkov - Minsk

BBK 32.973 2-018.1

Eckel B.

E38 Java Philosophy. Programmer's library. 4th ed. - St. Petersburg: Peter, 2009. - 640 e.: ill. - (Series "Programmer's Library").

ISBN 978-5-388-00003-3

Java cannot be understood by looking at it only as a collection of some harakureshki - it is necessary to understand the tasks of this language as particular problems of programming in general. r3ia is a book about programming problems: why they became problems and what approach Java uses to solve them. Therefore, the features of the language discussed in each chapter are inextricably linked to how they are used to solve certain problems.

This book, which has withstood many reprints in the original, due to its deep and truly philosophical presentation of the intricacies of the language, is considered one of the best manuals for Java programmers.

BBK 32.973.2-018.1 UDC 004.3

Publishing rights obtained by agreement with Prentice Hall PTR.

All rights reserved. No part of this book may be reproduced in any form without the written permission of the copyright holders.

The information contained in this book has been obtained from sources believed by the publisher to be reliable. However, in view of possible human or technical errors, the publisher cannot guarantee the absolute accuracy and completeness of the information provided and is not responsible for possible mistakes related to using the book.

ISBN 978-0131872486 © Prentice Hall PTR, 2006

ISBN 978-5-388-00003-3 © Translation into Russian by Peter Press LLC, 2009

© Publication in Russian, design by Piter Press LLC, 2009

Foreword..............................13

Java SE5 and SE6..............................14

Acknowledgments..............................14

Chapter 1 Introduction to Objects..............................17

The development of abstraction .........................18

An object has an interface..............................20

The facility provides services..............................22

Hidden Implementation..............................23

Implementation Reuse...................................24

Inheritance..............................25

Interchangeable Objects and Polymorphism.................................29

Single Root Hierarchy..............................33

Containers..............................33

Parameterized Types..............................35

Creation, use of objects and time of their life.......................36

Handling Exceptions: Dealing with Errors..............................38

Parallel Execution..............................38

Java and the Internet..............................39

Summary...................................47

Chapter 2 Everything is an Object...................................48

All objects must be created explicitly..............................49

Objects never have to be deleted...................................53

Creating New Data Types...................................54

Methods, Arguments, and Return Values..............................56

Creating a Java Program......................................58

Keyword static..............................60

Our first Java program..............................61

Comments and Embedded Documentation...................................64

Programming style ........................................70

Summary...................................70

Chapter 3. Operators..............................71

Simple Print Commands..............................71

Java Statements..............................72

Literals...................................82

Java lacks sizeof() ..........................................92

Resume..............................100

Chapter 4 Control Structures ..................101

Syntax foreach..............................105

return, ...................................107

break and continue......................................108

Bad goto command .........................109

Summary...................................115

Chapter 5. Initialization and Termination.......................116

Constructor Guarantees Initialization..................................116

Method Overloading..............................118

Cleanup: Finalization and Garbage Collection..................................130

Initializing Class Members..............................137

Constructor initialization ...............................140

Initializing Arrays..............................146

Summary..............................151

Chapter 6 Access Control ..........................152

Package as a library module..............................153

Java access specifiers..............................159

Interface and Implementation..............................163

Accessing Classes..............................164

Summary...................................167

Chapter 7 Reusing Classes..............................169

Composition Syntax..............................170

Inheritance Syntax..............................172

Delegation...................................176

Combining Composition and Inheritance............................178

Composition Versus Inheritance..................................184

protected.................................185

Upward Type Conversion......................................186

Final Keyword..............................188

Summary...................................197

Chapter 8 Polymorphism

Again about the upward transformation. . . >................199

Features...................................201

Constructors and Polymorphism..............................208

Return type covariance......................................216

Designing with Inheritance............................217

Resume..............................220

Chapter 9. Interfaces.......................221

Abstract Classes and Methods......................................221

Interfaces..............................224

Separating the Interface from the Implementation..............................227

Extending an Interface Through Inheritance.......................233

Interfaces as a means of adaptation....................................236

Nested Interfaces..............................239

Interfaces and Factories..............................242

Resume..............................244

Chapter 10 Inner Classes..............................245

Creating Inner Classes..............................245

Communication with an external class..............................246

The .this and .new constructs............................248

Inner Classes and Upward Conversion.......................249

Unnamed Inner Classes......................253

Inner classes: why? .............................. 261

Inheritance from Inner Classes..............................272

Is it possible to override an inner class?......................272

Local Inner Classes..............................274

Summary...................................276

Chapter 11 Collections of Objects..............................277

Parameterized and Typed Containers.......................277

Basic Concepts..............................280

Adding Element Groups..............................281

List.................................285

Iterators..............................288

LinkedList..............................291

Stack.................................292

Many.................................................294

Map.................................296

Queue..................................298

PriorityQueue..............................299

Collection and Iterator..............................301

Idiom "method-adapter" .........................306

Resume..............................309

Chapter 12 Handling Errors and Exceptions...................310

Major Exceptions..............................310

Catching Exceptions..............................312

Creating Your Own Exceptions..............................314

Exception Specifications..............................319

Catching Arbitrary Exceptions..............................320

Standard Java Exceptions..............................328

Terminating with finally.......................330

Using finally with return........................334

Restrictions on Using Exceptions...............................336

Constructors..............................339

Identification of Exceptions..............................343

Alternative Solutions..............................344

Summary...................................351

Chapter 13 Type Information....................................352

Need for Dynamic Type Inference (RTTI) .........352

Factory registration ................................372

Reflection: dynamic information about a class............................376

Dynamic Intermediaries..............................380

Objects with an indeterminate state...............................384

Interfaces and Type Information..............................390

Resume..............................394

Chapter 14

Simple parametrisation..............................398

Parameterized interfaces..............................404

Parameterized Methods...................407

Building Complex Models..............................419

Restrictions..............................437

Metacharacters..............................440

Resume..............................452

Chapter 15. Arrays..............................454

Array Features..............................454

Array as an Object..............................456

Returning an Array..............................458

Multidimensional arrays..............................460

Arrays and Parameterization..............................463

Creating Test Data..............................465

Creating Arrays Using Generators......................470

Arrays Helper Toolkit..............................474

Resume..............................482

Chapter 16 The Java I/O System..............................483

File class.................................484

Input and Output..............................489

Adding Attributes and Interfaces..............................491

The Reader and Writer Classes..............................494

RandomAccessFile: by itself.......................497

Typical Use of I/O Streams..............................498

File Readers and Writers......................................505

I'm probably not mistaken in assuming that most learners of Java started doing it with the help of Bruce Eckel's famous book: "Thinking in Java", known in Russian edition as "Philosophy of Java". Unfortunately in in electronic format(in Russian) the 2nd edition of this book is the most widely distributed, based on the Java 1.1 version, which has long lost its relevance. The innovations that appeared in the next versions of Java (and especially in Java SE5) were very significant, which led to a serious revision of the book in its fourth edition (the translation of which was published in Russian). However, in an easy-to-read (and most importantly - for quick search) electronic format, the Russian version of this publication did not exist. Therefore, I decided to fill this gap and produce a full version of this popular book in the "wikibook" format. I believe that this information will be interesting and useful not only for language learners, but also for everyone who works in Java due to the huge number of excellent examples illustrating almost all aspects of programming in this language. Especially when it comes to rarely used Java features.

wikibook "Philosophy of Java" posted at:

"Spring in Action"

Books from the series ".....in Action"(usually in PDF format and usually in English) are deservedly popular in certain circles :) Among them there are also capacious Talmuds, such as "JSTL in Action"(easily readable and with moderate knowledge of English, but suitable for the role of a good reference on the topic), and more modest crafts, such as Struts in Action("Not everything is gold..."). Book "Spring in Action" in this list is still from the category of "heavyweights", and in every sense of the word. Reading it without fluent English is probably not easy. And the point is rather not the complexity of the material presented (it is not complicated), but the fact that it turned out - excessively "English-artistic", or something .... Full of lyrical digressions, winged expressions, puns and other blah blah blah, language authors, quickly turns reading this guide (in the original language) into a tedious process. But on the other hand, it allows you to know that the word "draw"(usually - "draw") can be used in the meaning of "extract from" (lit. - "pull, drag"). As a result (taking into account the general style of presentation adopted in the book) to understand exact meaning phrases like: "...spring draw this data ...", it happens at the same time - and it is not easy, and it is extremely necessary. Therefore, readers of the chapters that I have not translated will have to decide for themselves what the authors wanted in such cases: to express themselves poetically about the creation (recording) of a file, or playfully tell about reading it.

This book has been converted by me from a PDF to a wikibook as an express reference for my personal use. Therefore, the translation is not total, but only in places - for which there was enough enthusiasm. The rest of the chapters were simply given in a form convenient for quick search. It is published, EVERYTHING in the form - "as is", and you should not blame the quality of the Russian text ... I am not a professional translator, and I did not have a literary editor. Perhaps I will disappoint someone with the fact that I did not translate some places and chapters of the book (and I don’t even plan to translate them), but it was necessary to leave a reserve for future generations

wikibook "Spring in action " posted at:

INTRODUCTION TO OBJECTS

We owe the emergence of the computer revolution to the machine. Therefore, our programming languages ​​try to be closer to this machine.

But at the same time, computers are not so much mechanisms, but means of amplifying thought (“bicycles for the mind”, as Steve Jobs liked to say), and another means of self-expression. As a result, programming tools lean less towards machines and more towards our minds, as well as other forms of expression of human aspirations, such as literature, painting, sculpture, animation, and cinema. Object-oriented programming (OOP) is part of turning the computer into a medium of self-expression.

This chapter will introduce you to the basics of OOP, including a look at the basic programming techniques. It, and the book in general, assumes that you have programming experience in a procedural language, not necessarily C.

This chapter contains preparatory and additional materials. Many readers prefer to first imagine the big picture, and only then understand the intricacies of OOP. Therefore, many of the ideas in this chapter serve to give you a solid understanding of OOP. However, many people do not grasp the general idea until they see specifically how things work; such people often get stuck in general terms, without having examples in front of them. If you are one of the latter and are eager to get started with the basics of the language, you can skip ahead to the next chapter - skipping this one will not be an obstacle to writing programs or learning the language. However, you should return to this chapter later to broaden your horizons and understand why objects are so important and where they fit into program design.

The development of abstraction

All programming languages ​​are built on abstraction. Perhaps the difficulty of the tasks to be solved directly depends on the type and quality of abstraction. By "type" I mean: "What exactly are we abstracting?" Assembly language is a small abstraction from the computer on which it runs. Many so-called "command" languages ​​created after him (such as Fortran, BASIC and C) were abstractions of the next level. These languages ​​had a significant advantage over assembly language, but their basic abstraction still makes you think about the structure of the computer rather than the problem being solved. The programmer must establish a relationship between the model of the machine (in "solution space", which represents the place where the solution is implemented - for example, a computer) and the model of the problem to be solved (in "problem space", which is the place where the problem exists - for example, applied area). Establishing a connection requires an effort detached from the actual programming language; the result is programs that are hard to write and hard to maintain. Not only that, it also created a whole industry of "programming methodologies".

An alternative to modeling a machine is modeling the problem being solved. Early languages ​​like LISP and APL, chose a special approach to modeling the world around (“All problems are solved by lists” or “Algorithms solve everything”, respectively). PROLOG treats all problems as chains of solutions. Languages ​​were created for programming based on a system of constraints, and special languages ​​in which programming was carried out by manipulating graphical structures (the scope of the latter turned out to be too narrow). Each of these approaches is good in a certain area of ​​the problems to be solved, but once you leave this area, it becomes difficult to use them.

The object approach goes a step further by providing the programmer with a means to represent a task in its space. This approach is quite general and does not impose restrictions on the type of problem being solved. The elements of the problem space and their representations in the solution space are called "objects". (You will probably need other objects that have no analogues in the problem space.) The idea is that the program can adapt to the specifics of the problem by creating new types of objects so that while reading the code that solves the problem, you simultaneously see the words, describing her. This is a more flexible and powerful abstraction, surpassing everything that existed before in its capabilities. Some language designers believe that object-oriented programming by itself is not sufficient to solve all programming problems, and advocate a combination of different programming paradigms in one language. Such languages ​​are called multi-paradigm(multiparadigms). See Timothy Budd's book Multiparadigm Programming in Leda (Addison-Wesley, 1995).. Thus, OOP allows you to describe the task in the context of the task itself, and not in the context of the computer on which the solution will be executed. However, the connection with the computer is still preserved. Each object is like a small computer; it has a state and operations that it allows. Such an analogy fits well with the external world, which is “reality given to us in objects” that have characteristics and behavior.

Alan Kay summarized and deduced the five main features of the language Smalltalk- the first successful object-oriented language, one of the predecessors Java. These characteristics represent a "pure", academic approach to object-oriented programming:

  • Everything is an object. Think of an object as a refined variable; it stores data, but you can "query" an object, asking it to perform operations on itself. Theoretically, absolutely any component of the problem being solved (a dog, a building, a service, etc.) can be represented as an object.
  • A program is a group of objects that tell each other what to do through messages. To make a request to an object, you "send it a message". More visually, a message can be represented as a call to a method belonging to a particular object.
  • Each object has its own "memory" consisting of other objects. In other words, you create new object by embedding existing objects into it. Thus, it is possible to construct an arbitrarily complex program, hiding the overall complexity behind the simplicity of individual objects.
  • Every object has a type. In other terms, every object is an instance of a class, where "class" is the equivalent of the word "type". The most important difference between classes lies precisely in the answer to the question: “What messages can be sent to an object?”
  • All objects of a certain type can receive the same messages. As we shall soon see, this is a very important circumstance. Since an object of type "circle" is also an object of type "shape", it is true that a "circle" is certainly capable of receiving messages for a "shape". And this means that you can write code for shapes and be sure that it will work for everything that falls under the concept of a shape. Interchangeability is one of the most powerful concepts in OOP.

Booch offered an even more concise description of the object:

An object has state, behavior, and identity.

The bottom line is that an object can have internal data (which is the state of the object), methods (which define behavior), and each object can be uniquely distinguished from any other object - more specifically, each object has a unique address in mind This is true with some limitations, since objects may actually exist on other machines and in different address spaces, and may also be stored on disk. In these cases, the identity of an object must be determined by something other than a memory address..

The object has an interface

It is likely that Aristotle was the first to carefully study the concept of type; he spoke of "a class of fish and a class of birds". The concept that all objects, while unique, are at the same time part of a class of objects with similar characteristics and behavior, was used in the first object-oriented language, Simula-67, with the introduction of the fundamental keyword class , which introduced a new type into the program.

Language Simula, as its name implies, was created to develop and simulate situations similar to the classic "bank teller" problem. You have groups of cashiers, customers, accounts, payments, and currencies - lots of "objects". Objects that are identical in all but internal state while the program is running are grouped into "object classes". From here it came keyword class . Creating abstract data types is a fundamental concept in all object-oriented programming. Abstract data types act in much the same way as built-in types: you can create type variables (called objects or instances in OOP terms) and manipulate them (called messaging or querying; you make a request and the object decides what to do with it. ). Members (elements) of each class have similarities: each account has a balance, each cashier accepts deposits, etc. At the same time, all members differ in their internal state: each account has an individual balance, each cashier has a human name. Therefore, all cashiers, customers, accounts, transfers, etc. can be represented by unique entities inside computer program. This is the essence of an object, and every object belongs to a particular class that defines its characteristics and behavior.

So while we do create new data types in object languages, virtually all of these languages ​​use the "class" keyword. When you see the word "type", think "class", and vice versa Some people distinguish between the two by pointing out that a type defines an interface, while a class is a concrete implementation of an interface..

Since a class defines a set of objects with identical characteristics (data members) and behavior (functionality), the class is actually a data type, because, for example, a floating point number also has a number of characteristics and behaviors. The difference is that the programmer defines a class to represent some aspect of the task, instead of using an already existing type that represents a unit of data storage in the machine. You extend the programming language by adding new data types to suit your needs. The programming system favors new classes and gives them exactly the same attention as built-in types.

The object-oriented approach is not limited to building models. Whether you agree or not that any program is a model of the system you develop, the use of OOP technology easily reduces a large set of problems to a simple solution.

Once a new class has been defined, you can create any number of objects of that class, and then manipulate them as though they were part of the problem at hand. In fact, one of the main difficulties in OOP is establishing a one-to-one correspondence between objects in the problem space and objects in the solution space.

But how do you make an object do what you want it to do? There must be a mechanism for sending a request to an object to perform some action - completing a transaction, drawing on the screen, etc. Each object can only perform a certain range of requests. The queries you can send to an object are determined by its interface, and the object's interface is determined by its type. The simplest example would be an electric light bulb:

Light lt = new Light() ;
lt.on();

An interface defines what requests you can make to a particular object. However, the code that executes the queries must also exist somewhere. This code, along with the hidden data, makes up the implementation. From a procedural programming point of view, what is happening is not so difficult. The type contains a method for each possible request, and when a specific request is received, the appropriate method is called. The process is usually combined into one: "sending a message" (transmitting a request) to an object, and processing it by an object (executing code).

In this example, there is a type (class) named light (lamp), concrete object of type light With name It , and the class supports various object queries light : Turn off the bulb, turn it on, make it brighter, or dim it. You create an object light , defining a "reference" to it ( It ) and calling the operator new to create a new instance of that type. To send a message to an object, you must specify the name of the object and associate it with the desired request with a dot. From the point of view of the user of a predefined class, this is quite enough to operate on its objects.

The chart shown above follows the format UML (Unified Modeling Language). Each class is represented by a rectangle, all the data fields that are described are placed in the middle part of it, and the methods (functions of the object to which you send messages) are listed at the bottom of the rectangle.

Often on charts UML only the class name and public methods are shown, and the middle part is missing. If you are only interested in the class name, you can skip the bottom part as well.

The facility provides services

When you're trying to design or understand the structure of a program, it's often helpful to think of objects as "service providers." Your program provides services to the user, and it does so through services provided by other objects. Your goal is to produce (or better still, find in the class libraries) the set of objects that will be optimal for solving your problem.

To begin, ask yourself, “If I could magically remove objects from a hat, which ones would be able to solve my problem right now?” Suppose you are developing an accounting program. One can imagine a set of objects that provide standard windows for entering accounting information, another set of objects that perform accounting calculations, an object that prints checks and invoices on various printers. Perhaps some of these objects already exist, and for other objects, it is worth figuring out what they might look like. What services could those facilities provide, and what facilities would they need to do their job? If you keep going like this, sooner or later you will say, “This object is simple enough that we can sit down and write it down,” or “Surely such an object already exists.” This is a reasonable way to distribute the solution of the problem to separate objects.

Representing an object as a service provider has added benefit: it helps improve connectivity ( cohesiveness) object. Good connectivity - essential quality software product: it means that various aspects of a software component (such as an object, although it can also refer to a method or an object library) fit together well. One of common mistakes allowed when designing an object is to oversaturate it with a large number of properties and capabilities. For example, when developing a module that prints checks, you might want it to "know" all about formatting and printing.

If you think about it, you will most likely come to the conclusion that this is too much for one object, and you will go to three or more objects. One object will be a catalog of all possible forms of checks and can be queried on how the check should be printed. Another object or set of objects will be responsible for a generalized printing interface that "knows" everything about various types of printers (but does not "understand" anything in accounting - it is better to buy such an object than to develop it yourself). Finally, the third object will simply use the services of the described objects in order to complete the task. Thus, each object is a connected set of services it offers. In a well-planned object-oriented project, each object does a good job of doing one specific task without trying to do more than it needs to. As shown, this not only allows you to determine which objects to purchase (object with a print interface), but also allows you to end up with an object that can then be used somewhere else (receipt catalog).

Representing objects as service providers greatly simplifies the task. It is useful not only during development, but also when someone tries to understand your code or reuse the object - then he will be able to adequately evaluate the object for the level of service provided, and this will greatly simplify the integration of the latter into another project.

Hidden implementation

It is useful to divide programmers into class creators(those who create new data types) and client programmersI am grateful for this term to my friend Scott Meyers.(consumers of classes that use data types in their applications). The goal of the second is to collect as many classes as possible in order to engage in rapid development of programs. The goal of a class creator is to build a class that exposes only what the client programmer needs and hides everything else. Why? The client programmer will not be able to access the hidden parts, which means that the creator of the classes reserves the opportunity to change them arbitrarily without fear that it will hurt someone. The "hidden" part is usually the most "fragile" part of the object, which can be easily corrupted by a careless or ignorant client programmer, so hiding the implementation reduces the number of errors in programs.

In any relationship, it is important to have some boundaries that are not crossed by any of the participants. By creating a library, you establish a relationship with the client programmer. He is a programmer just like you, but will use your library to create an application (and maybe a higher level library). If you give access to all members of the class to anyone, the client programmer can do whatever he wants with the class, and there is no way you can make it "play by the rules." Even if you later need to restrict access to certain members of your class, this cannot be done without an access control mechanism. The entire structure of the class is open to all comers.

Thus, the first reason for restricting access is the need to protect "fragile" details from the client programmer - parts of the internal "kitchen" that are not part of the interface with which users solve their problems. In fact, this is also useful for users - they will immediately see what is important to them and what they can ignore.

The second reason for the appearance of access restriction is the desire to allow the library developer to change the internal mechanisms of the class without worrying about how this will affect the client programmer. For example, you can implement a certain class in a hurry to speed up the development of a program, and then rewrite it to improve speed. If you have properly separated and protected the interface and implementation, this should not be difficult at all.

Java uses three explicit keywords characterizing the access level: public, private and protected . Their purpose and use is very simple. These access specifiers determine who has the right to use the definitions that follow them. Word public means that the following definitions are available to everyone. On the contrary, the word private means that the clauses following it are available only to the creator of the type, inside its methods. Term private - "fortress wall" between you and the programmer-client. If someone tries to use private -members, it will be stopped by a compilation error. specifier protected acts similar to private , with one exception - derived classes have access to members marked protected but do not have access to private -members (we'll cover inheritance shortly).

AT Java there is also a "default" access used in the absence of any of the listed specifiers. It is also sometimes called within-package access ( package access) because classes can use the friend members of other classes in their package, but outside the package, the same friend members acquire the status private .

Implementation Reuse

The generated and tested class should (ideally) be a useful block of code. However, it turns out that achieving this goal is much more difficult than many people think; development of reusable objects requires experience and understanding of the essence of the matter. But once you get it right good construction, it will simply be asking for implementation in other programs. Code reuse is one of the most impressive benefits of object-oriented languages.

The easiest way to reuse a class is by directly creating its object, but you can also place an object of that class inside a new class. We call this object injection (member object creation). The new class can contain any number of objects of other types, in any combination that is necessary to achieve the desired functionality. Since we are compiling new class from already existing classes, this method is called composition(when composition is performed dynamically, it is usually named aggregation). Composition is often referred to as a "has" relationship ( has-a), as, for example, in the sentence "A car has an engine."

(In UML diagrams, composition is indicated by a filled diamond, showing, for example, that there is only one car. I usually use a more general form of relationship: only lines, no diamond, which means association (link). This is usually sufficient for most charts where the difference between composition and aggregation is not significant to you.)

Composition is a very flexible tool. The member objects of your new class are usually declared private ( private ), which makes them inaccessible to client programmers using the class. This allows changes to be made to these member objects without modifying existing client code. You can also change these members at runtime to dynamically control the behavior of your program. The inheritance described below does not have this flexibility because the compiler imposes certain restrictions on classes created using inheritance.

Inheritance plays an important role in object-oriented programming, so it is often given a lot of attention, and a beginner might think that inheritance should be applied everywhere. And this is fraught with the creation of clumsy and unnecessarily complex solutions. Instead, when creating new classes, you should first evaluate the possibility of composition, as it is simpler and more flexible. If you adopt the recommended approach, your programming constructs will become much clearer. And as you gain practical experience, it will not be difficult to understand where inheritance should be used.

Inheritance

By itself, the idea of ​​an object is extremely convenient. The object allows you to combine data and functionality at a conceptual level, that is, you can represent the desired concept from the task space instead of concretizing it using a dialect of the machine. These concepts form the fundamental units of a programming language, described with the class keyword.

But you must admit, it would be a shame to create some kind of class, and then do all the work again for a similar class. It is much more rational to take a ready-made class, “clone” it, and then make additions and updates to the resulting clone. This is exactly what you get as a result of inheritance, with one exception - if the original class (also called the base * class, superclass or parent class) changes, then all changes are reflected in its "clone" (called the derived class, inherited class, subclass or child class).

(The arrow (empty triangle) in the UML diagram points from the derived class to the base class. As you will soon see, there can be more than one derived class.)

A type defines more than just the properties of a group of objects; it is also associated with other types. The two types may share similarities and behaviors, but differ in the number of characteristics, as well as the ability to process more messages (or process them differently). To express this commonality of types, inheritance uses the concept of base and derived types. The base type contains all the characteristics and actions common to all types derived from it. You create a base type to represent the basis of your understanding of some of the objects in your system. Other types are derived from the base type, expressing other implementations of this entity.

For example, a recycling machine sorts waste. The base type will be "garbage", and each piece of garbage has a weight, cost, etc., and can be crushed, melted, or decomposed. Based on this, more specific types of garbage are inherited, having additional characteristics (the bottle has a color) or behavioral traits ( aluminum can can be crushed, the steel can is attracted by a magnet). In addition, some behaviors may vary (the cost of paper depends on its type and condition). Inheritance allows you to create a type hierarchy that describes the problem being solved in the context of its types.

The second example is a classic example with geometric shapes. The base type here is “shape”, and each shape has a size, color, location, etc. Each shape can be drawn, erased, moved, painted, etc. Next, specific types of shapes are produced (inherited): circle, square, triangle, etc., each of which has its own additional characteristics and behaviors. For example, some shapes support a mirroring operation. Individual features of behavior may differ, as in the case of calculating the area of ​​a figure. The type hierarchy embodies both similar and different properties of shapes.

Reducing the solution to the concepts used in the example is extremely convenient, because you do not need many intermediate models that connect the description of the solution with the description of the problem. When working with objects, the type hierarchy becomes the primary model, so you go from describing the system in the real world directly to describing the system in code. In fact, one of the difficulties in object-oriented planning is that it's very easy for you to go from the beginning of a problem to the end of a solution. A mind trained for complex solutions often gets stuck when using simple approaches.

By inheriting from an existing type, you are creating a new type. This new type not only contains all the members of the existing type (although the members marked as private , hidden and inaccessible), but, more importantly, repeats the interface of the base class. This means that all messages that you could send to the base class, you can also send to the derived class. And since we distinguish class types by the set of messages that we can send them, this means that the derived class is a special case of the base class. In the previous example, "a circle is a figure." The equivalence of types achieved by inheritance is one of the fundamental conditions for understanding the meaning of object-oriented programming.

Since both the base and derived classes have the same main interface, there must be an implementation for that interface. In other words, somewhere there must be code that is executed when an object receives a particular message. If you simply inherited a class and did nothing else, the methods from the interface of the base class will pass into the derived class unchanged. This means that objects of a derived class are not only of the same type, but also have the same behavior, and at the same time, inheritance itself loses its meaning.

There are two ways to change the new class from the base class. The first is fairly obvious: completely new methods are added to the derived class. They are no longer part of the base class interface. Apparently the base class didn't do everything the task required, so you added a few methods. However, such a simple and primitive approach to inheritance sometimes turns out to be the ideal solution to the problem. However, one thing to consider carefully is that the base class may also need these added methods. The process of identifying patterns and revising the architecture is a daily routine in object-oriented programming.

While inheritance sometimes suggests that an interface will be augmented with new methods (especially in Java, where inheritance is denoted by the keyword extends , that is, "expand"), this is not necessary at all. The second, more important way to modify a class is to change behavior already existing methods base class. This is called overriding (or overriding) a method.

To override a method, you simply create a new definition of that method in a derived class. You're kind of saying "I'm using the same interface method, but I want it to do different things for my new type."

When using inheritance, the obvious question arises: should inheritance override methods only base class (and not add new methods that don't exist in the base class)? This would mean that the derived class would be exactly the same type as the base class, since they have the same interface. As a result, you can freely replace base class objects with derived class objects. You can talk about a complete replacement, and this is often called substitution principle. In a sense, this way of inheritance is ideal. This kind of relationship between a base class and a derived class is often referred to as a relationship. "is something", since one can say "a circle is a figure". To determine how appropriate inheritance will be, it is enough to check whether there is a relationship "is" between classes and how justified it is.

In other cases, the interface of the derived class is supplemented with new elements, which leads to its extension. The new type can still be used in place of the base type, but now this replacement is not ideal because the new methods are not available from the base type. Such a connection is described by the expression "similar to" (this is my term); the new type contains the interface of the old type, but also includes the new methods, and these types cannot be said to be exactly the same. Let's take an air conditioner as an example.

Suppose your house is equipped with everything necessary equipment to control the cooling process. Imagine now that the air conditioner has broken down and you have replaced it with a heater capable of both heating and cooling. A heater is “like” an air conditioner, but it can do more. Since your home's control system can only control cooling, it is limited in communicating with the cooling part of the new facility. The interface of the new object has been extended, and the existing system does not recognize anything other than the original interface.

Of course, looking at this hierarchy, it becomes clear that the "cooling system" base class is not flexible enough; it should be renamed "temperature control system" so that it also includes heating - and after that the principle of substitution will work. However, this diagram provides an example of what can happen in reality.

After getting acquainted with the principle of substitution, one might get the impression that this approach (full replacement) is the only way to develop. Generally speaking, if your type hierarchies work this way, it's really good. But in some situations it is absolutely necessary to add new methods to the derived class interface. On closer examination, both cases seem fairly obvious.

Interchangeable Objects and Polymorphism

When using type hierarchies, it is often necessary to treat an object of a certain type as a base type. This allows you to write code that does not depend on concrete types. Thus, in the shapes example, the methods simply manipulate shapes, regardless of whether they are circles, rectangles, triangles, or some not yet defined shapes. All shapes can be drawn, erased, and moved, and the methods simply send messages to the shape object; they don't care how the object handles the message.

Such code does not depend on adding new types, and adding new types is the most common way to extend object-oriented programs to handle new situations. For example, you can create a new shape subclass (pentagon) without changing methods that only work with generic shapes. The ability to easily extend a program by introducing new derived types is very important because it greatly improves the architecture of the program while at the same time reducing the cost of maintaining the software.

However, when trying to refer to objects of derived types as base types (circles as a figure, a bicycle as a vehicle, a cormorant as a bird, etc.), one problem arises. If a method is going to tell a generic figure to draw itself, or a vehicle to follow a certain course, or a bird to fly, the compiler cannot know exactly which part of the code will be executed. That's the whole point - when a message is sent, the programmer doesn't want to know what code is being executed; the drawing method can be applied to a circle, a rectangle, and a triangle with equal success, and the object will execute the correct code, depending on its characteristic type.

If you don't need to know which piece of code is being executed, then when you add a new subtype, its implementation code can be different without requiring changes to the method from which it was called. If the compiler doesn't know exactly what code to execute, what does it do?

AT next example an object BirdController (bird control) can only work with generic objects bird (bird), not knowing their exact type. From point of view BirdController this is convenient because you don't have to write special code for checking the type of the object used for it bird , to handle some special behavior. How does it happen that when you call the move () method, without specifying the exact type bird , the correct action is performed - the object goose (goose) runs, flies or swims, and the object Penguin (penguin) running or swimming?

The answer is explained main feature object-oriented programming: the compiler cannot call such functions in the traditional way. When calling functions created by a non-OOP compiler, use early binding- many do not know this term simply because they do not imagine another option. With early binding, the compiler generates a function call with given name, and the linker binds this call to the absolute address of the code to be executed. In OOP, the program is not able to determine the address of the code until runtime, so when sending a message to an object, a different mechanism must work.

To solve this problem, object-oriented programming languages ​​use the concept late binding. When you send a message to an object, the code being called is not known until run time. The compiler only makes sure that the method exists, checks the types for its parameters and return value, but has no idea what kind of code will be executed.

To implement late binding, Java uses special code snippets instead of an absolute call. This code calculates the address of the method body based on the information stored in the object (the process is covered in great detail in Chapter 7 on polymorphism). Thus, each object can behave differently, depending on the content of this special piece of code. When you send a message, the object actually decides what to do with it.

Some languages ​​require you to explicitly specify that a method should use a flexible late binding mechanism (in C++ there is a keyword for this virtual). In these languages, methods are not dynamically linked by default. AT Java late binding is done by default, and you don't need to remember to add any keywords to provide polymorphism.

Recall the example with shapes. A family of classes (based on the same interface) was shown in a diagram earlier in this chapter. To demonstrate polymorphism, we'll write a code snippet that ignores type specifics and works only on the base class. This code is separated from type specifics, so it is easier to write and understand. And if a new type (for example, a hexagon) is added through inheritance, then the code you write for the new type of figure will work just as well as the code for existing types. Thus, the program becomes extensible.

Let's say you wrote Java the following method (you'll learn how to do it shortly):

void doSomething(Shape shape) (
shape.erase(); // erase
//...
shape.draw(); // draw
}

The method works with a generalized figure ( shape ), that is, it does not depend on the particular type of object being drawn or erased. We now use the method call doSomething() in another part of the program:

Circle circle = new Circle() ; // circle
Triangle triangle = new Triangle() ; // triangle
Line line = new Line(); // line
doSomething(circle) ;
doSomething(triangle) ;
doSomething(line);

Method calls doSomething() automatically work correctly, regardless of the actual type of the object.

This is actually quite an important fact. Consider the line:

doSomething(circle) ;

This is where the following happens: to a method that expects an object shape , the “circle” object is passed ( Circle ). Since the circle ( Circle ) is simultaneously a figure ( shape ), then the method doSomething() and treats her like a figure. In other words, any message that the method can send shape , is also accepted Circle . This action is completely safe and just as logical.

We refer to this process of dealing with a derived type as if it were a base type. upcasting. Word transformation means that the object is treated as belonging to a different type, and ascending it's because in inheritance diagrams, base classes are usually at the top and derived classes are fanned out at the bottom. This means that the conversion to the base type is an upward movement along the diagram, and therefore it is "upward".

An object-oriented program almost always contains an upcast, because that's how you get rid of the need to know the exact type of the object you're working with. Look at the body of the method doSomething() :

shape.erase();
// ...
shape.draw();

Note that it does not say "if you are an object Circle , do it, and if you are an object Square do this and that." Such code with separate actions for each possible type shape will be confusing and will have to be changed each time a new subtype is added shape . And so, you just say: “You are a figure, and I know that you are able to draw and erase yourself, well, do it, and take care of the details yourself.”

In method code doSomething() What's interesting is that everything works out just right. When called draw() for object Circle other code is executed, and not the one that works when called draw() for objects Square or line , and when draw() applied to an unknown figure shape , the correct behavior is provided by using the real type shape . It's in the highest degree interesting, because, as noted a little earlier, when the compiler generates code doSomething() , it doesn't know exactly what types it's working with. Accordingly, one would expect the method versions to be called draw() and erase() from base class shape , not their variants from concrete classes Circle , Square or line . And yet everything works correctly thanks to polymorphism. The compiler and runtime system take care of all the details; all you need to know is that it will happen... and more importantly, how to build programs using this approach. When you send a message to an object, the object will choose the correct behavior using an upcast.

Single root hierarchy

Shortly after the appearance C++ the question from OOP began to be actively discussed - should all classes necessarily be inherited from a single base class? AT Java(as in almost all other OOP languages ​​except C++) This question was answered in the affirmative. The entire type hierarchy is based on a single base class Object . It turned out that the single-root hierarchy has many advantages.

All objects in a single-rooted hierarchy share some common interface, so that by and large they can all be considered as one underlying type. AT C++ another option was chosen - a common ancestor does not exist in this language. In terms of compatibility with old code, this model is more in line with tradition. C, and you might think that it is less limited. But as soon as the need for full-fledged object-oriented programming arises, you will have to create your own class hierarchy in order to get the same benefits that are built into other OOP languages. And in any new class library, you may encounter some incompatible interface. Incorporating these new interfaces into your program's architecture will require extra effort (and possibly multiple inheritance). Is the extra "flexibility" worth it? C++ similar costs? If you need it (eg. big investments to code development C), then you won't be a loser. If development starts from scratch, the approach Java looks more productive.

All objects in a single root hierarchy are guaranteed to share some common functionality. You know that certain basic operations can be performed on any object in the system. All objects are easily created on the dynamic heap, and passing arguments is greatly simplified.

The single root hierarchy makes it much easier to implement garbage collection - one of the most important improvements Java compared with C++. Since type information is guaranteed to be present in any of the objects at run time, there will never be an object in the system whose type cannot be determined. This is especially important when performing system operations such as handling exceptions and for greater programming flexibility.

Containers

Often it is not known in advance how many objects will be needed to solve a certain problem and how long they will exist. It is also unclear how to store such objects. How much memory should be allocated to store these objects? It is unknown, as this information will only become available while the program is running.

Many problems in object-oriented programming are solved simple action: You are creating another type of object. The new object type that solves this particular problem contains references to other objects. Of course, arrays, which are supported in most languages, can also play this role. However, the new object, usually called container(or a collection, but in Java the term is used in a different sense) will of necessity expand to accommodate whatever you put in it. Therefore, you will not need to know ahead of time how many objects the container capacity is designed for. Just create a container and it will take care of the details.

Fortunately, a good OOP language comes with a set of ready-made containers. AT C++ it's part of the standard library C++, sometimes called a library standard templates (Standard Template Library, STL). Smalltalk comes with a very wide range of containers. Java also contains containers in its standard library. For some libraries, it is considered sufficient to have one single container for all needs, but in others (for example, in Java) there are various containers for all occasions: several different types of lists List (for storing sequences of elements), maps Map (also known as associative arrays, allow you to associate objects with other objects), as well as sets set (providing unique values ​​for each type). Container libraries can also contain queues, trees, stacks, and so on.

From a design standpoint, all you really need is a container that can solve your problem. If one type of container meets all needs, there is no reason to use other types. There are two reasons why you have to choose from the available containers. First, containers provide a variety of interfaces and interactions. The behavior and interface of a stack is different from that of a queue, which behaves differently than a set or a list. One of these containers is able to provide more than effective solution your task in comparison with the rest. Secondly, different containers perform the same operations in different ways. best example- this is ArrayList and LinkedList . Both are simple sequences that can have identical interfaces and behaviors. But some operations differ significantly in execution time. Let's say the sampling time of an arbitrary element in ArrayList always remains the same regardless of which element is selected. However, in LinkedList it is disadvantageous to work with random access - the further down the list an element is, the greater the delay causes its search. On the other hand, if you need to insert an element in the middle of the list, LinkedList do it faster than ArrayList . These and other operations are different efficiency depending on internal structure container. At the planning stage of the program, you can choose a list LinkedList , and then, during the optimization process, switch to ArrayList . Due to the abstract nature of the interface List such a transition will require minimal changes in the code.

Parameterized types (generics)

Before release Java SE5 containers could only store data Object - the only universal type Java. Single root hierarchy means that any object can be considered as Object , so the container with the elements Object suitable for storage of any objects Primitive types cannot be stored in containers, but thanks to the mechanism automatic packaging(autoboxing) Java SE5 this limitation is not essential. This topic will be discussed in more detail later in the book..

When working with such a container, you simply put object references in it, and later retrieve them. But if the container is only capable of storing Object , then when a reference to an object of another type is placed in it, a conversion to Object , that is, the loss of the "individuality" of the object. When fetching it back you get a reference to Object , not a reference to the type that was placed in the container. How to convert it to a specific type of object placed in a container?

The problem is solved by the same type conversion, but this time you don't use an upcast (up the inheritance hierarchy to the base type). You now use the method of converting down the inheritance hierarchy (to a child type). This method called downward conversion. In the case of an upcast, it is known that the circle is a figure, so the conversion is known to be safe, but in the case of a reverse cast (to a child type), it is impossible to tell in advance whether an instance represents Object an object Circle or shape , so downcasting is only safe if you know exactly the type of the object.

However, the danger is not so great - downcasting to the wrong type will result in a run-time error called exception(see below). But when retrieving object references from a container, you need to somehow remember the actual type of their objects in order to perform the correct downcast.

Downcasting and runtime type checking require extra time and extra effort from the programmer. Or maybe you can somehow create a container that knows the type of stored objects, and thus eliminates the need for type conversion and potential errors? The solution is called the mechanism type parameterization. Parameterized types are classes that the compiler can automatically adapt to work with certain types. For example, the compiler might configure a parameterized container to store and retrieve only shapes ( shape ).

One of the most important changes Java SE5 is support for parameterized types ( generics). Parameterized types are easily recognizable by the angle brackets that enclose the parameter type names; e.g. container ArrayList , designed to store objects shape , is created like this:

ArrayList< Shape >shapes = new ArrayList< Shape > () ;

Many standard library components have also been modified to use generic types. As you will soon see, generic types appear in many of the sample programs in this book.

Creation, use of objects and their lifetime

One of critical aspects work with objects - the organization of their creation and destruction. For the existence of each object, some resources are required, primarily memory. When an object is no longer needed, it must be destroyed so that the resources it occupies can be made available to others. In simple situations, the task doesn't seem difficult: you create an object, use it for as long as it's needed, and then destroy it. However, more complex situations often occur in practice.

Let's say, for example, that you're developing a system for managing air traffic. (The same model also applies to tare control in a warehouse, or a video rental system, or a stray animal kennel.) At first, everything seems simple: an aircraft container is created, then a new aircraft is built, which is placed in a container of a certain air traffic control area . As for the release of resources, the corresponding object is simply destroyed when the aircraft leaves the tracking zone.

But perhaps there is another aircraft registration system, and this data does not require such close attention as main function management. Maybe it's the flight plan records of all the small planes leaving the airport. This is how the second container for small aircraft appears; every time a new aircraft object is created in the system, it is also included in the second container if the aircraft is small. Next, some background process works with the objects in this container at the moments of minimal busyness.

Now the task becomes more complicated: how do you know when to delete objects? Even if you are done with the object, it is possible that another system is still interacting with it. The same question arises in a number of other situations, and in software systems where it is necessary to explicitly delete objects after work with them is completed (for example, in C++), it becomes quite complex.

Where is object data stored and how is its lifetime determined? AT C++ Efficiency comes first, so the programmer is given a choice. For achievement top speed execution location and lifetime can be determined at the time of writing the program. In this case, objects are pushed onto the stack (such variables are called automatic) or to the static storage area. So the main factor is the speed of creating and destroying objects, and this can be invaluable in some situations. However, this comes at the cost of flexibility, since the number of objects, their lifetimes, and types must be known exactly at the design stage of the program. When solving problems of a broader profile - the development of computer-aided design systems
(CAD), inventory control or air traffic control - this approach may be too limited.

The second way is to dynamically create objects in an area of ​​memory called the "heap" ( heap). In this case, the number of objects, their exact types, and lifetime remain unknown until the program starts. All this is determined "on the fly" while the program is running. If you need a new object, you simply create it on the heap when needed. Since the heap is managed dynamically, it takes much longer to allocate memory from the heap during program execution than when allocating memory on the stack. (It takes just one machine instruction to allocate memory on the stack, moving the stack pointer down, and freeing it is done by moving this pointer up. The time required to allocate memory on the heap depends on the structure of the store.)

The dynamic approach assumes that objects are large and complex, so the extra time it takes to allocate and deallocate memory will not have a significant impact on the process of creating them. Then, additional flexibility is very important for solving basic programming problems.

AT Java only the second approach is used Primitive types, which will be discussed next, are a special case.. The keyword is used each time an object is created. new to build a dynamic instance.

However, there is another factor, namely the lifetime of the object. In languages ​​that support creating objects on the stack, the compiler determines how long an object is in use and can automatically destroy it. However, when an object is created on the heap, the compiler has no idea about the lifetime of the object. In languages ​​like C++, the destruction of the object must be explicitly framed in the program; if this is not done, a memory leak occurs (a common problem in programs C++). AT Java there is a mechanism called garbage collection; it automatically detects when an object is no longer used and destroys it. The garbage collector is very handy because it saves the programmer a lot of hassle. More importantly, the garbage collector gives you much more confidence that the insidious memory leak problem has not crept into your program (which brought more than one project in the C++).

AT Java the garbage collector is designed so that it can deal with the problem of freeing memory itself (this does not affect other aspects of the end of an object's life). The garbage collector "knows" when an object is no longer being used and uses its knowledge to automatically deallocate the memory. Due to this fact (along with the fact that all objects inherit from a single base class Object and are created only on the heap) Java much easier than programming C++. The developer has to take fewer decisions and overcome fewer obstacles.

Exception Handling: Dealing with Errors

Since the early days of programming languages, error handling has been one of the most tricky topics. It is very difficult to develop a good error handling mechanism, so many languages ​​simply ignore this problem, leaving it to the developers of software libraries. The latter provide half-way solutions that work in many situations, but can often be simply bypassed (usually by simply not paying attention to them). The main problem with many exception-handling mechanisms is that they rely on the programmer's conscientious adherence to rules that are not enforced by the language. If the programmer is careless - and this often happens when the work is in a hurry - he can easily forget about these mechanisms.

The exception handling mechanism builds error handling right into the programming language, or even into operating system. An exception is an object that is thrown at the site of an error, which can then be "caught" by an appropriate exception handler designed for certain types of errors. Exception handling seems to define a parallel program execution path that takes effect when something doesn't go according to plan. And because it defines a separate execution path, the error handling code doesn't get mixed up with normal code. This simplifies writing programs because you don't have to constantly check for possible errors. In addition, the exception is not like a numeric error code returned by a method, or a flag set in case of a problem situation, the latter can be ignored. The exception cannot be ignored, it is guaranteed to be handled somewhere. Finally, exceptions make it possible to restore the normal operation of the program after an incorrect operation. Instead of just terminating the program, you can correct the situation and continue running it; thereby increasing the reliability of the program.

Exception handling mechanism in Java stands out from the rest of the languages ​​because it was built into the language from the very beginning and it is the responsibility of the developer to use it. This is the only acceptable way to report errors. If you don't write code for proper exception handling, you'll get an error at compile time. Such a consistent approach sometimes greatly simplifies error handling.

It is worth noting that exception handling is not a feature of an object-oriented language, although in these languages ​​an exception is usually represented by an object. Such a mechanism existed even before the advent of object-oriented languages.

Parallel programming

One of the fundamental concepts of programming is the idea of ​​doing multiple things at the same time. Many tasks require the program to interrupt its current work, solve some other task, and then return to the main process. The problem was solved in different ways.
At first, programmers who knew the machine architecture wrote interrupt handling procedures, that is, the suspension of the main process was performed at the hardware level. This solution worked well, but it was complex and non-mobile, which made it much more difficult to port such programs to new types of computers.

Sometimes interrupts are really necessary to process the operations of time-critical tasks, but there is a whole class of tasks where you just need to break the task into several separately executed parts so that the program reacts faster to external influences. These separately executed parts of the program are called streams, and the whole principle is called concurrency(concurrency), or parallel computing. A common example of concurrency is the user interface. In a threaded program, the user can press a button and get a quick response without waiting for the program to complete the current operation.

Usually, threads just define how one processor's time is allocated. But if the operating system supports multiple processors, each thread can be assigned to a separate processor; this is how true parallelism is achieved. One of the nice features of parallelism, at the language level, is that the programmer doesn't need to know if there is one or more processors in the system. The program is logically divided into threads, and if the machine has more than one processor, it runs faster without any special settings.

All this gives the impression that streams are very easy to use. But there's a catch: shared resources. If multiple threads try to access the same resource at the same time, problems arise. For example, two processes cannot simultaneously send information to a printer. To prevent conflict, shared resources (such as a printer) should be locked while in use. The thread locks the resource, completes its operation, and then releases the lock so that someone else can access the resource.

Concurrency support built into the language Java, and with the output Java SE5 it added significant support at the library level.

Java and Internet

If a Java is another programming language, the question arises: why is it so important and why is it presented as a revolutionary step in software development? From the point of view of traditional programming tasks, the answer is not immediately obvious. Although the language Java useful when building stand-alone applications, its most important application was and remains programming for the network world wide web.

What is the Web?

At first glance, the Web looks rather mysterious due to the abundance of newfangled terms like "surfing", "presence" and "home pages". To understand what this is, it is useful to understand the big picture - but first you need to understand the interaction of client / server systems, which are one of the most challenging tasks computer computing.

Client/Server Computing

The basic idea behind client/server systems is that you have a centralized repository of information - usually in the form of a database - and that information is delivered at the request of some group of people or computers. In a client/server system, a centralized repository of information plays a key role, which typically allows data to be modified in such a way that these changes are propagated to the users of the information. All together: a repository of information, software, which distributes information, and the computer (s) on which the software and data are stored is called the server. The software on the user's machine that communicates with the server, receives the information, processes it, and then displays it appropriately is called the client.

So the basic concept of client/server computing isn't all that complicated. Problems arise because one server tries to serve many clients at the same time. Typically, a database management system is involved in the solution, and the developer tries to "optimize" the data structure by distributing it into tables. Additionally, the system often allows the client to add new information to the server. And this means that the new information of the client must be protected from loss during storage in the database, as well as from the possibility of overwriting it with data from another client. (This is called transaction processing.) When you change client software, you must not only compile and test it, but also install it on client machines, which can be much more difficult and expensive than you might think. It is especially difficult to organize support for many different operating systems and computer architectures. Finally, it is necessary to take into account the most important factor performance: the server can receive hundreds of requests at the same time, and the slightest delay threatens with serious consequences. To reduce latency, programmers try to distribute calculations, often even on the client machine, and sometimes transferring them to additional server machines, using the so-called middleware (middleware). (Proxy programs also make it easier to maintain programs.)

The simple idea of ​​disseminating information between people has so many levels of complexity in its implementation that, in general, its solution seems unattainable. And yet it is vital: about half of all programming tasks are based on it. It is involved in solving problems ranging from order processing and credit card transactions to the dissemination of all kinds of data - scientific, government, stock quotes ... the list is endless. In the past, you had to create a separate solution for each new task. These solutions are not easy to create, even more difficult to use, and the user had to learn a new interface with each new program. The task of client/server computing needs a broader approach.

The web is like a giant server

In fact, the web is one huge client/server system. However, that's not all: single network all servers and clients coexist simultaneously. However, this fact should not interest you, since usually you connect and interact with only one server (even if you have to look for it all over the world).

At first, a simple unidirectional exchange of information was used. You made a request to the server, it sent you a file that your viewer program (that is, the client) processed for you. But soon, simply getting static pages from the server wasn't enough. Users wanted to take full advantage of the client/server system, send information from the client to the server in order to, for example, browse the server's database, add new information to the server, or place orders (which required special security measures). We constantly observe these changes in the process of web development.

Web browsing tools (browsers) were a big step forward: they introduced the concept of information that is displayed in the same way on any type of computer. However, the first browsers were still primitive and quickly ceased to meet the requirements. They turned out to be not particularly interactive and slowed down the work of both servers and the Internet as a whole - for any action that required programming, you had to send information to the server and wait for it to process it. Sometimes you had to wait several minutes only to find out that you missed one letter in the request. Since the browser was only a viewer, it could not perform even the simplest programming tasks. (On the other hand, this guaranteed security - the user was protected from running programs containing viruses or bugs.)

Various approaches have been taken to solve these problems. To begin with, graphics display standards have been improved so that browsers can display animations and videos. The remaining tasks required the ability to run programs on the client's machine, inside the browser. This has been called client side programming.

Client Side Programming

Initially, the server-browser interaction system was designed for interactive content, but support for this interactivity was completely entrusted to the server. The server generated static pages for the client's browser, which simply processed and displayed them. Standard HTML supports the simplest inputs: text fields, radio buttons, checkboxes, lists, and drop-downs, along with buttons that can only do two things: reset the form data and submit it to the server. The sent information is processed by the interface CGI (Common Gateway Interface), supported by all web servers. The request text indicates CGI how to deal with the data. Most often, on request, a program is launched from the cgi-bin directory on the server. (In the line with the address of the page in the browser, after submitting the form data, you can sometimes see the substring in the mess of characters cgi-bin.) Such programs can be written in almost all languages. Commonly used Perl, since it is text-oriented and also an interpreted language, therefore, it can be used on any server, regardless of the type of processor or operating system. However, language Python(my favorite language - go to www.Python.org) gradually wins back "territory" from him due to its power and simplicity.

Many powerful web servers today operate entirely based on CGI; in principle, this technology allows you to solve almost any problem. However, web servers built on CGI programs are difficult to maintain and have responsiveness issues. Response time CGI-program depends on the amount of information sent, as well as on the load of the server and the network. (Because of all the mentioned launch CGI program may take a long time). The early designers of the web didn't foresee how quickly the system's resources would be depleted as it was used in various applications. For example, it is almost impossible to display real-time graphs in it, since with any change in the situation, it is necessary to build a new GIF file and transfer it to the client. No doubt you've had your own bitter experiences - for example, from simply submitting form data. You press a button to send information; server starts CGI- a program that detects an error, generates HTML- a page that tells you about it, and then sends this page in your direction; you have to retype the data and try again. Not only is this slow, it's simply inelegant.

The problem is solved by programming on the client side. As a rule, browsers run on powerful computers capable of solving a wide range of tasks, and with a standard approach based on HTML the computer simply waits to be served the next page. With client-side programming, the browser is given all the work it can do, and for the user, this translates into faster web browsing and better interactivity.

However, discussing client programming is not much different from discussions about programming in general. The conditions are the same, but the platforms are different: the browser resembles a heavily truncated operating system. You have to program anyway, so client-side programming creates a dizzying array of problems and solutions. This section concludes with an overview of some of the problems and approaches inherent in client-side programming.

Expansion modules

One of the most important areas in client programming has become the development of extension modules ( plug-ins). This approach allows the programmer to add new functionality to the browser by downloading a small program that is built into the browser. In fact, from this moment on, the browser acquires new functionality. (An extension module only loads once.) Plugins have provided browsers with a number of fast and powerful innovations, but writing such a module is not an easy task, and it's unlikely that you want to create extensions every time you create a new site. The value of plug-ins for client programming is that they allow an experienced programmer to add new features to the browser without asking permission from its creator. Thus, extension modules provide a "backdoor" for integrating new programming languages ​​on the client side (although not all languages ​​are implemented in such modules).

Scripting languages

The development of plug-ins has given rise to many scripting languages. Using a scripting language, you embed the client program directly into HTML-page, and the module that processes this language is automatically activated when viewing it. Script languages ​​are usually fairly easy to learn; in essence, the script code is the text that is part of the HTML-pages, so it loads very quickly as part of a single request to the server during page fetch. The price for this is that anyone can view (and steal) your code. However, it's unlikely that you'll write anything emulated and sophisticated in scripting languages, so the problem of code copying isn't that bad.

A scripting language that is supported by almost any browser without installing additional modules is JavaScript(having very little in common with Java; the name was used in order to "grab" a piece of success Java on the market). Unfortunately, the original implementations JavaScript in different browsers were quite different from each other and even between different versions of the same browser. Standardization JavaScript in the shape of ECMAScript was useful, but it took time for its support to appear in all browsers (in addition, the company Microsoft actively promoted their own language VBScript, vaguely resembling JavaScript). In the general case, the developer has to limit himself to a minimum of possibilities JavaScript so that the code is guaranteed to work in all browsers. Regarding error handling and debugging code JavaScript, then this is a difficult task at best. Only recently have developers been able to create a truly complex system written in JavaScript(company Google, service Gmail), and it required the highest enthusiasm and experience.

This shows that the scripting languages ​​used in browsers were designed for a range of specific tasks, mainly to create a richer and more interactive graphical user interface ( GUI). However, the scripting language can be used for 80% of client programming tasks. Your task may just be in those 80%. Because scripting languages ​​make it easy and fast to create programming code, you should first consider just such a language before moving on to more complex technological solutions like Java.

Java

If scripting languages ​​take on 80% of the tasks of client programming, then who can handle the other 20%? For them, the most popular solution today is Java. Not only is it a powerful programming language designed with security, platform compatibility, and internationalization in mind, but it is also a constantly evolving tool with new features and libraries that elegantly fit traditionally complex programming tasks: multitasking, database access, network programming, and distributed computing. Client programming Java comes down to the development of applets, as well as the use of the package Java Web Start.

An applet is a mini-program that can only run inside a browser. Applets are automatically loaded as part of a web page (in the same way that, for example, graphics are loaded). When an applet is activated, it executes the program. This is one of the advantages of the applet - it allows you to automatically distribute programs to clients from the server exactly when the user needs these programs, and not before. The user receives the latest version of the client program, without any problems and difficulties associated with reinstallation. According to the ideology Java, the programmer creates only one program that automatically runs on all computers that have browsers with a built-in interpreter Java. (This is true for almost all computers.) Since Java is a complete programming language, as much of the work as possible should be done on the client side before (or after) calling the server. For example, you do not need to send a request over the Internet to find out that there was an error in the received data or some parameters, and the client computer can quickly draw some kind of graph without waiting for the server to do it and send back the image file. This scheme not only provides an immediate speed and responsiveness benefit, but also reduces the load on the main network transport and servers, preventing slowdowns in the overall Internet experience.

Alternatives

To be honest, applets Java did not justify the initial enthusiasm. On first appearance Java everyone was very enthusiastic about applets because they enabled serious client-side programming, increased responsiveness, and reduced bandwidth for Internet applications. Applets were predicted to have a great future.

Indeed, a number of very interesting applets can be found on the Web. And yet the mass transition to applets did not take place. Probably the main problem was that downloading the 10 MB package to install the environment Java Runtime Environment (JRE) too frightening for the average user. The fact that the company Microsoft did not turn on JRE in delivery Internet Explorer, finally decided the fate of applets. Anyway, applets Java have not been widely used.

However, applets and applications Java Web Start are very useful in some situations. If the configuration of end user computers is under control (for example, in organizations), the use of these technologies to distribute and update client applications is quite justified; it saves a lot of time, labor and money (especially with frequent updates).

.NET and C#

For some time the main contender Java-applets were considered components ActiveX from company Microsoft, although they required for their work the presence on the client's machine Windows. Now Microsoft opposed Java full-fledged competitors: it is a platform .NET and programming language FROM#. Platform .NET is roughly the same as a virtual machine Java(JVM) and libraries Java, and the language FROM# has a clear resemblance to the language Java. Without a doubt, this is the best that Microsoft has created in the field of programming languages ​​and environments. Of course, the developers Microsoft had some advantage; they saw that in Java succeeded and what did not, and could build on these facts, but the result turned out to be quite worthy. For the first time since his birth, Java there was a real competitor. Developers from Sun had to take a look at FROM#, find out why programmers might want to switch to this language, and make every effort to seriously improve Java in Java SE5.

AT this moment the main question is whether Microsoft will allow full porting of .NET to other platforms. AT Microsoft claim that there is no problem in this, and the project mono() provides a partial implementation .NET for linux. However, since this implementation is incomplete, for now Microsoft does not decide to throw out any part of it, to bet on .NET as a cross-platform technology is still early.

Internet and intranet

The Web provides the most general solution for client/server tasks, so it makes sense to use the same technology to solve specific problems; this is especially true for classic client / server interaction within the company. The traditional client/server approach has problems with differences in the types of client computers, adding to them the difficulty of installing new programs for clients; both problems are solved by browsers and client-side programming. When web technology is used to form an information network within a company, such a network is called an intranet. Intranets provide much more security than the Internet because you can physically control access to your company's servers. In terms of learning, it is much easier for someone who understands the concept of a browser to understand different pages and applets, so that the time to master new systems is reduced.

The issue of security brings us to one of the directions that automatically arises in client programming. If your program is running on the Internet, then you don't know what platform it will run on. Special care must be taken to avoid spreading incorrect code. Here we need cross-platform and secure solutions, like Java or scripting language.

Intranets have other restrictions. Quite often, all machines on the network run on the platform Intel/Windows. On the intranet, you are responsible for the quality of your code and can fix bugs as they are found. In addition, you may already have a collection of solutions that are proven to work in more traditional client/server systems, while new software must be manually installed on the client machine with each upgrade. The time spent on updates is the strongest argument in favor of browser technologies, where updates are made invisibly and automatically (the same can be done Java Web Start). If you're involved in maintaining an intranet, it's wiser to take the path that allows you to leverage what you already have without having to rewrite programs in new languages.

When faced with the sheer volume of client programming tasks that can stump any designer, it's best to evaluate them in terms of cost/benefit ratio. Consider the limitations of your problem and try to imagine the shortest way to solve it. Since client programming is still programming, development technologies that promise the fastest solution are always relevant. This proactive attitude will give you an opportunity to prepare for the inevitable problems of programming.

Server Side Programming

Our discussion has bypassed the subject of server-side programming, which many consider to be the most strong point Java. What happens when you send a request to the server? More often than not, the request comes down to a simple "send me this file" request. The browser then processes the file appropriately: as HTML-page like picture like Java-applet, like a script, etc.

A more complex request to the server usually involves accessing the database. In the most common case, a complex database search is requested, the results of which are then converted by the server into HTML-page and sends to you. (Of course, if the client is able to perform some action using Java or a scripting language, the data can be processed by him, which will be faster and reduce server load.) Or you may need to register in the database when joining a group, or checkout, which will require changes to the database. Such requests must be processed by some code on the server; in general, this is what is called server-side programming. Traditionally, server programming was done on Perl, Python, C++ or another language that allows you to create programs CGI, but there are more interesting options. These include those based on Java web servers that allow you to do server-side programming on Java using so-called servlets. Servlets and their offspring JSPs, are the two main reasons why web content companies are moving to Java, mainly because they solve cross-browser incompatibility issues.

Despite all the talk about Java as an Internet programming language, Java in fact, it is a full-fledged programming language capable of solving almost all problems solved in other languages. Advantages Java are not limited to good portability: it is suitability for solving programming problems, and error resistance, and a large standard library, and numerous third-party developments - both existing and constantly emerging.

Summary

You know what a procedural program looks like: data definitions and function calls. Finding out the purpose of such a program requires effort by looking at the functions and creating a big picture in your mind. It is because of this that the creation of such programs requires the use of intermediate tools - in themselves they are more oriented to the computer, and not to the task being solved.

Since OOP adds many new concepts to those already available in procedural languages, it is natural to assume that the code Java will be much more complicated than a similar method in a procedural language. But here a pleasant surprise awaits you: a well-written program in Java usually much easier to understand than its procedural counterpart. All you see are object definitions that represent decision space concepts (not computer implementation concepts), and messages sent to those objects that represent actions in that space. One of the advantages of OOP is that a well-designed program can be understood simply by looking at the source code. In addition, you usually have to write much less code, since many tasks can be easily solved already. existing libraries classes.

Object-oriented programming and language Java are not suitable for everyone. It is very important to find out your needs first in order to decide if switching to Java or it is better to opt for another programming system (including the one you are currently using). If you know that for the foreseeable future you will be faced with very specific needs or your work will be subject to restrictions that Java does not cope, it is better to consider other possibilities (in particular, I recommend taking a closer look at the language Python,). Choosing Java, you need to understand what other options are available and why you chose this path.

Exchange of messages:

Send directly to this side: http://website/javabooks/Thinking_in_Java_4th_edition.html Send in BB codes: Thinking_in_Java_4th_edition
html-message: