Planning Motivation Control

Philosophy java 4th edition pdf. Wikibooks Java Philosophy. Reuse implementation

PROGRAMMER'S LIBRARY

Bruce Eckel

4th edition

(^ PPTER

Moscow - Saint Petersburg - Nizhny Novgorod - Voronezh Rostov-on-Don - Yekaterinburg - Samara - Novosibirsk Kiev - Kharkov - Minsk

BBK 32.973 2-018.1

Eckel B.

E38 Java philosophy... Programmer's library. 4th ed. - SPb .: 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 certain characteristics - it is necessary to understand the tasks of this language as particular tasks of programming in general. The r3ia book is 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 specific problems.

This book, which has survived more than one reprint in the original, thanks 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 whatsoever without the written permission of the copyright holders.

The information contained in this book has been obtained from sources considered reliable by the publisher. However, due to 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 the use of the book.

ISBN 978-0131872486 © Prentice Hall PTR, 2006

ISBN 978-5-388-00003-3 © Russian translation by OOO Peter Press, 2009

© Edition in Russian, designed by OOO Peter Press, 2009

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

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

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

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

Development of abstraction ........ ................... 18

The object has an interface .......................... 20

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

Latent 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 their lifetime ............ 36

Exception Handling: 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

The static keyword ............................ 60

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

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

Program design style ........................ 70

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

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

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

Java Operators ............................... 72

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

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

Summary ................................... 100

Chapter 4. Control structures ................ 101

Foreach syntax ............................. 105

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

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

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

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

Chapter 5. Initialization and Completion .............. 116

Constructor guarantees initialization ................. 116

Method overloading ............................ 118

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

Initialization of class members ....................... 137

Initialization by constructor ....................... 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

Access to classes .............................. 164

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

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

Song syntax ........................... 170

Inheritance syntax .......................... 172

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

Combination of composition and inheritance ................... 178

Composition versus inheritance ................. 184

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

Upstream Type Conversion ..................... 186

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

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

Chapter 8. Polymorphism ...................... 198

Upstream transformation again. ... ... > ................ 199

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

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

Covariance of Return Types ................... 216

Development with inheritance ........................ 217

Summary ................................... 220

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

Abstract classes and methods ....................... 221

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

Separation of interface from implementation ................... 227

Extending the interface through inheritance ............... 233

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

Nested interfaces .......................... 239

Interfaces and factories ........................... 242

Summary ................................... 244

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

Creating inner classes ....................... 245

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

Constructions.this and.new .......................... 248

Inner Classes and Upstream Conversion ............. 249

Unnamed inner classes ...................... 253

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

Inheriting from Inner Classes .................... 272

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

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

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

Chapter 11. Collections of objects ................... 277

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

Basic concepts ............................ 280

Adding groups of elements ........................ 281

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

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

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

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

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

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

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

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

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

The idiom "method-adapter" .......................... 306

Summary ................................... 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

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

Completion with finally ....................... 330

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

Limitations when using exceptions ............... 336

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

Identifying Exceptions ........................ 343

Alternative solutions ......................... 344

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

Chapter 13. Information on types .................. 352

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

Registration of factories ............................ 372

Reflection: Dynamic Class Information .............. 376

Dynamic intermediaries ......................... 380

Objects with an undefined state .................. 384

Interfaces and Type Information ... 390

Summary ................................... 394

Chapter 14. Parameterization ..................... 397

Simple parameterisation .......................... 398

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

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

Building complex models ....................... 419

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

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

Summary ................................... 452

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

Features of arrays ........................... 454

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

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

Multidimensional Arrays ........................... 460

Arrays and parameterization ......................... 463

Creating test data ......................... 465

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

Arrays Supporting Toolkit .................. 474

Summary ................................... 482

Chapter 16. 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


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

Foreword 13

Java SE5 and SE6 14

Acknowledgments 14

Chapter 1. Introduction to Objects 17

Development of abstraction 18

Object has interface 20

Object provides services 22

Hidden realization 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 created explicitly 49

Objects never have to be deleted 53

Creating New Data Types 54

Methods, Arguments, and Return Values ​​56

Writing a Java Program 58

Static 60 keyword

Our First Java Program 61

Comments and Inline Documentation 64

Style of programming 70

Chapter 3. Operators 71

Simple Print Commands 71

Java Operators 72

Literals 82

Java is missing sizeof () 92

Summary 100

Chapter 4. Control Structures 101

Foreach syntax 105

break and continue 108

Bad Team goto 109

Summary 115

Chapter 5. Initialization and Completion 116

Constructor guarantees initialization 116

Method Overloading 118

Cleanup: Finalization and Garbage Collection 130

Initialization of Class Members 137

Constructor Initialization 140

Initializing Arrays 146

Summary 151

Chapter 6. Access Control 152

Package as a Library Unit 153

Java Access Specifiers 159

Interface and Implementation 163

Access to 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

Upstream Type Conversion 186

Final 188 keyword

Summary 197

Chapter 8. Polymorphism 198

Upstream transformation again. ... ... > 199

Features 201

Constructors and Polymorphism 208

Return Type Covariance 216

Developing with Inheritance 217

Summary 220

Chapter 9. Interfaces 221

Abstract Classes and Methods 221

Interfaces 224

Decoupling Interface from Implementation 227

Extending an Interface Through Inheritance 233

Interfaces as a means of adaptation 236

Nested interfaces 239

Interfaces and Factories 242

Summary 244

Chapter 10. Inner Classes 245

Creating Inner Classes 245

Communication with external class 246

Constructions.this and.new 248

Inner Classes and Upstream Conversion 249

Unnamed Inner Classes 253

Inner classes: why? 261

Inheriting From Inner Classes 272

Is it possible to override the inner class? 272

Local Inner Classes 274

Summary 276

Chapter 11. Collections of Objects 277

Parameterized and Typed Containers 277

Basic Concepts 280

Adding Groups of Elements 281

Iterators 288

Set 294

Queue 298

PriorityQueue 299

Collection and Iterator 301

Method-adapter idiom 306

Summary 309

Chapter 12. Error Handling and Exceptions 310

Major exceptions 310

Catching Exceptions 312

Creating Your Own Exceptions 314

Exception Specifications 319

Catching Arbitrary Exceptions 320

Java Standard Exceptions 328

Completion with finally 330

Using finally with return 334

Limitations on Using Exceptions 336

Constructors 339

Identifying Exceptions 343

Alternative solutions 344

Summary 351

Chapter 13. Type Information 352

The Need for Dynamic Type Inference (RTTI) 352

Registration of factories 372

Reflection: Dynamic Class Information 376

Dynamic intermediaries 380

Undefined Objects 384

Interfaces and Type Information 390

CV 394

Chapter 14. Parameterization 397

Easy parameterization 398

Parameterized 404 interfaces

Parameterized Methods 407

Building Complex Models 419

Restrictions 437

Metacharacters 440

CV 452

Chapter 15. Arrays 454

Features of 454 Arrays

Array as Object 456

Returning an array 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. Java 483 I / O System

File 484 class

Input and output 489

Adding Attributes and Interfaces 491

The Reader and Writer Classes 494

RandomAccessFile: by itself 497

Typical Usage 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

CV 555

Chapter 17. Parallel Execution 557

Thread Class 559

Artists 561

Resource Sharing 578

Interaction between threads 598

Interlock 602

New library components 607

CountDownLatch 607

CyclicBarrier 609

PriorityBlockingQueue 614

Semaphores 619

Modeling 624

CV 629

Index 631

Introduction to objects

We dissect nature, transform it into concepts and ascribe meaning to them the way 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 with the organization and classification of data established by this agreement.

Benjamin Lee Worf (1897-1941)

We owe the computer revolution to the machine. Therefore, our programming languages ​​are trying to be closer to this machine.

But at the same time, computers are not so much mechanisms as means of amplifying thoughts (“bicycles for the mind,” as Steve Jobe likes to say), and another means of self-expression. As a result, programming tools tend to 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 making the computer a vehicle for self-expression.

This chapter will introduce you to the basics of OOP, including an overview of the basic methods of program development. It, and the book in general, implies that you have experience in programming in a procedural language, not necessarily C. If you think that before reading this book you do not have enough knowledge in programming and syntax of C, use the Thinking in C multimedia seminar. which can be downloaded from the site

INTRODUCTION TO OBJECTS

We owe the computer revolution to the machine. Therefore, our programming languages ​​are trying to be closer to this machine.

But at the same time, computers are not so much mechanisms as means of amplification of thought ("bicycles for the mind", as he liked to say Steve Jobs), and another means of self-expression. As a result, programming tools tend to 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 making the computer a vehicle for self-expression.

This chapter will introduce you to the basics of OOP, including an overview of the basic methods of program development. It, and the book in general, implies that you have experience programming 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 don't get the big idea until they see exactly how it works; such people often get bogged down in general words, having no examples in front of them. If you belong to the latter and are eager to get down to the basics of the language, you can skip right to the next chapter - skipping this will not be an obstacle to writing programs or learning the language. However, you should return to this chapter a little later to broaden your horizons and understand why objects are so important and where they are in software design.

Development of abstraction

All programming languages ​​are built on abstraction. Perhaps the difficulty of the tasks being 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 operates. Many so-called "command" languages ​​that followed it (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, not the problem at hand. The programmer must establish a connection between the machine model (in the "solution space", which represents the place where the solution is implemented - for example, a computer) and the model of the problem that needs to be solved (in the "problem space", which is the place where the problem exists - for example, applied area). Establishing communication requires an effort that is divorced from the programming language itself; the result is programs that are difficult to write and difficult to maintain. Not only that, it also created a whole branch of "programming methodologies".

An alternative to modeling a machine is to model the problem being solved. Early languages ​​like LISP and APL, chose a special approach to modeling the surrounding world (“All tasks 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 restrictions, and special languages ​​in which programming was carried out by manipulating graphic constructions (the scope of the latter turned out to be too narrow). Each of these approaches is good in a certain area of ​​the tasks being solved, but once you get out of this area, it becomes difficult to use them.

The object approach goes a step further by providing the programmer with the means to represent a task in its space. This approach is quite general and does not impose restrictions on the type of problem to be solved. 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 it. It is a more flexible and powerful abstraction that surpasses everything that existed before in its capabilities. Some language designers believe that object-oriented programming alone is not sufficient for solving all programming problems, and advocate the combination of different programming paradigms in one language. Such languages ​​are called multiparadigm?(multiparadigm). See Timothy Budd's book Multiparadigm Programming in Leda (Addison-Wesley, 1995).... Thus, OOP allows you to describe a 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; he has a state and operations that he allows to carry out. This analogy goes well with outside world, which is "the reality given to us in objects" that have characteristics and behavior.

Alan Kay summed up and deduced 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 by asking it to perform operations on itself. Theoretically, absolutely any component of the problem being solved (dog, building, 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 a message to it." More clearly, you can imagine a message as a call to a method belonging to a specific object.
  • Each object has its own "memory" made up of other objects. In other words, you create a new object by embedding existing objects in it. Thus, it is possible to design an arbitrarily complex program, hiding the overall complexity behind the simplicity of individual objects.
  • Every object has a type. In other terms, each object is an instance of a class, where “class” is the equivalent of “type”. The most important difference classes from each other is precisely 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 will see shortly, this is a very important circumstance. Since an object of the "circle" type is also an object of the "figure" type, it is true that the "circle" is knowingly capable of receiving messages for the "figure". This means that you can write code for shapes and be sure that it will work for anything that falls under the concept of a shape. Interchangeability is one of the most powerful OOP concepts.

Booch offered an even more succinct description of the object:

Object has state, behavior and personality.

The bottom line is that an object can have internal data (which is the state of the object), methods (which determine 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, as objects can actually exist on other machines and in different address spaces, and can also be stored on disk. In these cases, the identity of the object must be determined by something other than the memory address..

The object has an interface

Aristotle was probably the first to carefully study the concept of type; he spoke of "the class of fish and the class of birds." The concept that all objects, while being 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 to 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 - many “objects”. Objects that are identical in everything except the internal state during program execution are grouped into "object classes". This is where the key word came from class ... The creation of abstract data types is a fundamental concept in all object-oriented programming. Abstract data types work 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 sending messages or requesting; 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, invoices, transfers, etc. can be represented by unique entities inside computer program... This is the essence of an object, and each object belongs to a specific class that defines its characteristics and behavior.

So while we actually create new datatypes 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 differentiate between the two by pointing out that a type defines an interface and a class is a concrete implementation of an interface..

Since a class defines a set of objects with identical characteristics (data items) and behavior (functionality), a 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 a task, instead of using a pre-existing type that represents the storage unit of the machine. You extend the programming language by adding new data types to suit your needs. The programming system is supportive of new classes and pays the same attention to them as to built-in types.

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

After defining a new class, you can create any number of objects of this class, and then manipulate them as if they were elements of the problem being solved. 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 get the object to do the things you want? There must be a mechanism for transmitting a request to an object to perform some action - completing a transaction, drawing on the screen, etc. Each object is able to perform only a certain range of requests. The requests you can send to an object are determined by its interface, with the object's interface being determined by its type. The simplest example would be a light bulb:

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

The interface determines what requests you are allowed to make to a specific object. However, there must be some code to execute the queries somewhere. This code, along with the hidden data, constitutes the implementation. From the point of view of procedural programming, this is not so difficult. The type contains a method for every possible request, and when a specific request is received, the desired method is called. The process is usually combined into one whole: both "sending a message" (sending a request) to an object, and processing it by an object (executing code).

In this example, there is a type (class) named Light (lamp), specific object of type Light With name It , and the class supports various requests to the object Light : turn off the light, turn on, brighten or dim. You are creating an object Light , defining a "link" to it ( It ) and calling the operator new to create a new instance of this type. To send a message to an object, you must specify the name of the object and associate it with the required request with a dot symbol. From the point of view of the user of a predefined class, this is quite enough to operate on its objects.

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

Often in 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, then you can skip the bottom part too.

The facility provides services

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

First, ask yourself: "If I could magically remove objects from the hat, which ones would be able to solve my problem right now?" Suppose you are developing accounting software... You 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 all kinds of printers. Perhaps some of these objects already exist, while 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 continue like this, you will sooner or later say, "This object is simple enough that you can sit down and write it down," or "Surely such an object already exists." This is a smart way to distribute the solution of a problem to individual objects.

Representing an entity as a service provider has the added benefit of helping to improve connectivity ( cohesiveness) object. Good connectivity - essential quality a software product: it means that various aspects of a software component (such as an object, although this may also refer to a method or a library of objects) “fit together” well with each other. One of the typical mistakes made when designing an object is its oversaturation with a large number of properties and capabilities. For example, when developing a module that prints receipts, you might want it to "know" everything about formatting and printing.

If you think about it, chances are you will come to the conclusion that there is too much for one object, and move on to three or more objects. One object will be a catalog of all possible check forms and can be asked about how the check should be printed. Another object or a set of objects will be responsible for a generalized printing interface that "knows" everything about different 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 represents an associated set of services offered to it. In a well-planned object-oriented project, each object does a good job of doing one specific task without trying to do more. As shown, this not only allows you to determine which objects are worth purchasing (an object with a print interface), but also allows you to end up with an object that can then be used elsewhere (a catalog of receipts).

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 an object - then he will be able to adequately assess the object by the level of the provided service, and this will greatly simplify the integration of the latter into another project.

Hidden implementation

It is useful to break down programmers into class creators(those who create new data types) and client programmersI am grateful for this term to my friend Scott Meyers.(class consumers using data types in their applications). The goal of the latter is to collect as many classes as possible in order to be engaged in rapid development of programs. The goal of a class creator is to build a class that only exposes 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 ability to arbitrarily change them without fear of harm to someone. The "hidden" part is usually the most "fragile" part of the object, which can easily be spoiled by an unwary or ignorant client programmer, so hiding the implementation reduces the number of errors in programs.

In any relationship, it is important to have any boundaries that are not crossed by any of the participants. By creating a library, you are establishing a relationship with the client programmer. He is a programmer just like you, but he will use your library to create an application (or maybe libraries are more high level). By giving anyone access to all members of a class, the client programmer can do whatever he pleases with the class, and there is no way you can force him to "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 classroom is open to everyone.

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

The second reason for the 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 whip up a certain class to speed up program development, and then rewrite it to make it faster. If you have properly partitioned and secured the interface and implementation, this should be a snap.

Java uses three explicit keywords to describe the access level: public, private and protected ... Their purpose and use are very simple. These access specifiers determine who is allowed to use the following definitions. Word public means that subsequent definitions are available to everyone. On the contrary, the word private means that the following clauses are available only to the creator of the type, inside its methods. Term private - "fortress wall" between you and the client programmer. If anyone tries to use private -members, it will be stopped by a compilation error. The 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).

IN Java there is also "default" access, which is used in the absence of any of the listed specifiers. It is also sometimes referred to as in-package access ( package access), because classes can use friendly members of other classes from their package, but outside of it, the same friendly members acquire the status private .

Reuse implementation

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 believe; Designing reusable objects requires experience and insight. But once you have a good design, it just begs to be incorporated into 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 instantiating its object, but you can also place an object of that class inside a new class. We call this object injection (creating a member object). The new class can contain any number of objects of other types, in any combination that is necessary to achieve the required functionality. Since we are composing a new class from existing classes, this method is called composition(if composition is done dynamically it is usually named aggregation). Composition is often referred to as an "has" relationship ( has-a), as, for example, in the sentence “The car has an engine”.

(In UML diagrams, composition is denoted by a filled diamond, indicating, for example, that there is only one car. I usually use more than general form relations: only lines, no rhombus, which means association (connection). 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 ), making them inaccessible to client programmers using the class. This allows you to make changes to these member objects without modifying pre-existing client code. You can also modify these members at runtime to dynamically control the behavior of your program. The inheritance described below does not have this flexibility because the compiler places certain restrictions on classes that are created using inheritance.

Inheritance plays an important role in object-oriented programming, so it is often the focus 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, since it is simpler and more flexible. If you adopt the recommended approach, your programming constructs will become much clearer. And as you accumulate practical experience figuring out where inheritance should be applied is easy.

Inheritance

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

But you must admit that it would be a shame to create a 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 add 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, the inherited class, subclass or child class).

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

The type defines not only the properties of a group of objects; it is also related to other types. The two types can have common features and behavior, but differ in the number of characteristics, as well as the ability to handle more messages (or handle them differently). To express this generality 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 objects in your system. Other types are spawned from the base type, expressing other implementations of this entity.

For example, a waste recycling machine sorts waste. The basic type will be "trash", and each particle of trash has a weight, value, etc., and can be crushed, melted or decomposed. Based on this, more specific types of garbage are inherited, which have additional characteristics (the bottle has a color) or behavior traits (an aluminum can can be crumpled, a steel can is attracted by a magnet). In addition, some of the behavior may vary (the cost of the 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 basic type here is "shape", and each shape has a size, color, location, etc. Each shape can be drawn, erased, moved, painted over, etc. Further, specific types of shapes are produced (inherited): a circle, a square, triangle, etc., each of which has its own additional characteristics and traits of behavior. For example, mirroring operation is supported for some shapes. Individual behaviors can vary, 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 connecting the description of the solution to the description of the problem. When working with objects, the hierarchy of types becomes the primary model, so that you go from describing a system in the real world directly to describing a system in program code. In fact, one of the difficulties with object-oriented planning is that it is very easy to walk from the beginning of the problem to the end of the solution. A mind trained to make complex decisions often gets stuck with simple approaches.

By inheriting from an existing type, you create 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, it 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 between class types by the set of messages that we can send to them, this means that the derived class is a special case of the base class... In the previous example, "a circle is a shape." The type equivalence achieved through inheritance is one of the fundamental conditions for understanding the meaning of object-oriented programming.

Since both base and derived classes have the same base interface, there must be an implementation for that interface. In other words, there must be code somewhere that is executed when an object receives a specific message. If you just inherited a class and took no further action, the methods from the interface of the base class will migrate to 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 in this case 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 did not do everything that was required for this task, so you added a few methods. However, such a simple and primitive approach to inheritance sometimes turns out to be the perfect solution to the problem. However, it should be carefully considered that the base class may also need these added methods. The process of identifying patterns and redesigning an architecture is a daily routine in object-oriented programming.

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

To override a method, you just need to create a new definition of that method in the 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 you override methods during inheritance? only base class (and not add new methods that do not 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 objects of the base class with objects of derived classes. We can talk about a complete replacement, and this is often called substitution principle... In a sense, this way of inheritance is ideal. This way of linking base and derived classes is often referred to as linkage. "Is so and so" because you can say "a circle is a figure." To determine how appropriate inheritance is, it is enough to check whether a "is" relationship exists 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. 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 relationship is described by the expression "similar to" (that's my term); the new type contains the interface of the old type, but also includes new methods, and it cannot be said that these types are exactly the same. Let's take an air conditioner as an example.

Suppose your home is equipped with everything necessary equipment to control the cooling process. Imagine now that the air conditioner breaks down and you replace it with a heater that can both heat and cool. A heater is "like" an air conditioner, but it can do more. Since your home's control system is only able to control cooling, it is limited in communication with the cooling part of the new building. The interface of the new object has been extended, and the existing system does not recognize anything other than the original interface.

Of course, when you look at this hierarchy, it becomes clear that the base class "cooling system" is not flexible enough; it should be renamed "temperature control system" so that it includes heating as well - and then the substitution principle will work. However, this diagram provides an example of what might actually happen.

After familiarizing yourself with the principle of substitution, you might get the impression that this approach (complete substitution) is the only way to develop. Generally speaking, if your type hierarchies work like this, that's really good. But in some situations it is absolutely necessary to add new methods to the interface of the derived class. On careful analysis, both cases seem quite obvious.

Interchangeable Objects and Polymorphism

When using type hierarchies, you often have to treat an object of a particular type as a base type. This allows you to write type-independent code. So, in the shape example, the methods simply manipulate the shapes, regardless of whether they are circles, rectangles, triangles, or some not even defined shape yet. All shapes can be drawn, erased, and moved, and the methods simply send messages to the shape object; they don't care how the subject handles the message.

Such code is independent of the addition of new types, and adding new types is the most common way of extending 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 dramatically improves the architecture of the program while reducing the cost of maintaining software.

However, when you try to refer to objects of derived types as basic types (circle as a shape, bicycle as a vehicle, cormorant as a bird, etc.), one problem arises. If a method is going to tell a generic shape 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 execute. This is the whole point - when a message is sent, the programmer does not want to know what code is being executed; the drawing method can be applied equally well to a circle, a rectangle, or a triangle, and the object will execute the correct code depending on its characteristic type.

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

In the following example, the object BirdController (bird control) can only work with generic objects Bird (bird) without knowing their exact type. From point of view BirdController this is convenient, since you do not have to write special code for checking the type of the used object 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 (the 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. In early linking, the compiler generates a function call with the specified name, and the linker binds the 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 be triggered.

To solve this problem, object-oriented programming languages ​​use the concept late binding... When you send a message to an object, the called code is unknown 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 code will be executed.

For 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 described in great detail in Chapter 7 on polymorphism). Thus, each object can behave differently, depending on the content of that special piece of code. When you send a message, the object actually decides what to do with it.

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

Consider the example with shapes. A family of classes (based on the same interface) was shown in the diagram earlier in this chapter. To demonstrate polymorphism, we'll write a piece of code that ignores characteristics types and only works with the base class. This code is decoupled from type specificity, 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 shape will work as well as the code for the existing types. Thus, the program becomes extensible.

Let's say you wrote in Java the following method (you will learn how to do this shortly):

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

The method works with a generic figure ( Shape ), that is, it does not depend on the specific type of object that is being drawn or erased. Now we 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 object.

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

doSomething (circle);

This is where the following happens: the method expecting an object Shape , the "circle" object ( 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 a method can send Shape is also accepted and Circle ... This action is completely safe and just as logical.

We refer to this process of treating a derived type as a base type. upstream conversion... Word transformation means that the object is treated as belonging to a different type, and ascending this is because in inheritance diagrams, base classes tend to be 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 up-conversion, because that is how you get rid of the need to know the exact type of object you are 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 every 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, so do it, and take care of the details yourself."

In the method code doSomething () the interesting thing is that everything turns out right by itself. When calling draw () for the object Circle other code is executed, not the one that is executed when called draw () for objects Square or Line , and when draw () applies to unknown figure Shape , the correct behavior is ensured by using a real type Shape ... This is eminently interesting because, as noted earlier, when the compiler generates the code doSomething () , he doesn't know exactly what types he is working with. Accordingly, one would expect to call versions of the methods draw () and erase () from base class Shape rather than their variants from specific classes Circle , Square or Line ... And yet everything works correctly thanks to polymorphism. The compiler and runtime take care of all the details; all you need to know is that this will happen ... and more importantly, how to create programs using this approach. When you send a message to an object, the object will choose the correct behavior using an upward transform.

Single root hierarchy

Soon after appearing, C ++ the question from OOP began to be actively discussed - should all classes be inherited from a single base class? IN 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-root hierarchy have some common interface, so by and large they can all be considered as one fundamental type. IN C ++ another option was chosen - there is no common ancestor in this language. In terms of compatibility with legacy code, this model is better in keeping 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 have to create your own class hierarchy to get the same benefits that are built into other OOP languages. And in any new class library you may come across some incompatible interface. Incorporating these new interfaces into your program architecture takes extra effort (and possibly multiple inheritance). Is the extra "flexibility" worth it? C ++ similar costs? If you need it (for example, with large investments in code development C), then you will not be a loser. If development starts from scratch, the approach Java looks more productive.

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

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 runtime, an object whose type cannot be determined will never appear in the system. This is especially important when performing system operations, such as exception handling, and for greater programming flexibility.

Containers

It is often not known in advance how many objects will be required 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? Unknown, since this information will only become available while the program is running.

Many problems in object-oriented programming are solved by a simple action: you create another type of object. A new object type that addresses this particular problem contains references to other objects. Of course, arrays, which are supported in most languages, can also play this role. However, a new object commonly called container(or a collection, but in Java this term is used in a different sense) will expand as needed to accommodate whatever you put in it. Therefore, you will not need to know ahead of time how many objects the container's 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 pre-built containers. IN C ++ it is part of the standard library C ++, sometimes called the standard template library (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 that it is enough 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 (ensuring that the values ​​are unique for each type). Container libraries can also contain queues, trees, stacks, etc.

From a design standpoint, all you really need is a container that can solve your problem. If one type of container meets all the 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 different interfaces and interoperability. The behavior and interface of a stack is different from the behavior and interface of a queue, which behaves differently than a set or a list. One of these containers can provide a more efficient solution to your task compared to the others. Second, different containers perform the same operations in different ways. Best example- This ArrayList and LinkedList ... Both are simple sequences that can have identical interfaces and behaviors. However, some operations differ significantly in terms of 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 unprofitable to work with random access - the further down the list is an element, the longer the delay is caused by its search. On the other hand, if you need to insert an element in the middle of the list, LinkedList will do it faster than ArrayList ... These and other operations have different efficiency, depending on the internal structure of the container. At the planning stage of the program, you can select 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 to the code.

Parameterized types (generics)

Before the release Java SE5 only data could be stored in containers Object - the only universal type Java... Single root hierarchy means that any object can be viewed as Object , so the container with elements Object suitable for storing any objects Primitive types cannot be stored in containers, but thanks to the mechanism automatic packing(autoboxing) Java SE5 this limitation is immaterial. Later in the book, this topic will be discussed in more detail..

When working with such a container, you simply place references to objects 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, the conversion to Object , that is, the loss of the "individuality" of the object. When fetching it back, you get a link to Object rather than a reference to the type that was placed in the container. How do you convert it to a specific type of object placed in the container?

The problem is solved by the same type conversion, but this time you do not use an up-conversion (up the inheritance hierarchy to the base type). You are now using a way to convert down the inheritance hierarchy (to a child type). This method is called top-down conversion... In the case of an upward conversion, it is known that the circle is a figure, therefore the conversion is certainly safe, but when converting back (to a child type), it is impossible to tell in advance whether the instance represents Object an object Circle or Shape so a downcasting is only safe if you know the exact type of the object.

However, the danger is not that great - a downward conversion to the wrong type will result in a runtime error called exception(see below). But when retrieving object references from a container, you must somehow remember the actual type of their objects in order to perform the correct down conversion.

Downcast and runtime type checking takes extra time and effort on the part of 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 parameterizations... Parameterized types are classes that the compiler can automatically adapt to work with specific types. For example, the compiler can set up a parameterized container to store and retrieve only shapes ( Shape ).

One of the biggest changes Java SE5 is the support for parameterized types ( generics). Parameterized types are easily recognized by the angle brackets that enclose the parameter type names; for example container ArrayList for storing 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 see shortly, generic types are found in many of the sample programs in this book.

Creation, use of objects and their lifetime

One of the most important aspects of working with objects is organizing 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 in order for the resources it occupies to become available to others. In simple situations, the task does not seem difficult: you create an object, use it as long as you need it, and then destroy it. However, in practice, more complex situations are often encountered.

Let's say, for example, that you are developing a system for controlling the movement of an aircraft. (The same model is also suitable for controlling the movement of containers in a warehouse, or for a video rental system, or in a kennel for stray animals.) At first, everything seems simple: a container for aircraft is created, then a new aircraft is built, which is placed in a container of a certain air traffic control zone. ... 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 as much attention as the main control function. Maybe these are the flight plans of all small planes leaving the airport. This creates a second container for small aircraft; every time a new aircraft object is created in the system, it is also included in the second container if the aircraft is small. Further, a certain background process works with objects in this container at the moments of minimum busyness.

Now the problem gets more complicated: how do you know when to delete objects? Even if you have finished working 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 finishing work with them (for example, in C ++), it becomes quite complex.

Where is the object's data stored and how is its lifetime determined? IN C ++ efficiency is paramount, so the programmer is given a choice. For maximum execution speed, the storage 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. Thus, the speed at which objects are created and destroyed is a major factor, and this can be invaluable in some situations. However, this has to sacrifice flexibility, since the number of objects, their lifetime and types must be accurately known at the stage of program development. When solving problems of a wider profile - the development of computer-aided design systems
(CAD), inventory control, or air traffic control - this approach can be overly limited.

The second way is to dynamically create objects in a memory area called the "heap" ( heap). In this case, the number of objects, their exact types and lifetime remain unknown until the program is launched. 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 you need it. Since the heap is managed dynamically, allocating memory from the heap during program execution takes much longer than allocating memory on the stack. (To allocate memory on the stack, just one machine instruction is sufficient, moving the stack pointer down, and the deallocation is done by moving this pointer up. The time it takes to allocate memory on the heap depends on the storage structure.)

The dynamic approach assumes that objects are large and complex, thus additional expenses time for allocating and freeing memory will not have a noticeable effect on the process of their creation. Then, additional flexibility is very important for solving basic programming problems.

IN Java only the second approach is used The primitive types discussed below are a special case.... The keyword is used every 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 the object has been in use and can automatically destroy it. However, when creating an object on the heap, the compiler has no idea about the object's lifespan. In languages ​​like C ++, the destruction of an object must be explicitly documented in the program; if this is not done, a memory leak occurs (a common problem in programs C ++). IN Java there is a mechanism called garbage collection; it automatically detects when an object is no longer in use and destroys it. The garbage collector is very convenient 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 (which has brought more than one language project to its knees) hasn't crept into your program. C ++).

IN Java the garbage collector is designed so that it can deal with the problem of freeing memory on its own (this does not apply to other aspects of object life termination). The garbage collector knows when an object is no longer in use and uses this knowledge to automatically free memory. Due to this fact (along with the fact that all objects inherit from a single base class Object and only created on the heap) programming on Java much easier than programming in C ++... The developer has to make fewer decisions and overcome fewer obstacles.

Exception Handling: Dealing with Errors

From the earliest days of programming languages, error handling has been one of the most tricky issues. Developing a good error handling mechanism is very difficult, so many languages ​​simply ignore this problem, leaving it to the developers of software libraries. The latter provide half-hearted solutions that work in many situations, but which can often be easily circumvented (usually just by not paying attention to them). The main problem with many exception handling mechanisms is that they rely on the programmer's conscientious observance of rules that the language does not enforce. If a programmer is inattentive - and this often happens in a rush to work - he can easily forget about these mechanisms.

The exception handling engine builds error handling right into the programming language or even operating system... An exception is an object that is generated at the location of an error, which can then be "caught" by an appropriate exception handler designed for a specific error type. Exception handling is like defining a parallel path of program execution that takes effect when something goes wrong. And since it defines a separate path of execution, error handling code does not mix with regular code. This makes it easier to write programs because you don't have to constantly check for possible errors. In addition, the exception does not resemble the numeric error code returned by the method, or the flag set in the event of a problem situation - the latter can be ignored. An exception cannot be ignored, it is guaranteed to be handled somewhere. Finally, exceptions provide an opportunity to restore normal work programs after an incorrect operation. Instead of just terminating the program, you can correct the situation and continue executing it; thus, the reliability of the program is increased.

The exception handling mechanism in Java stands out from other languages ​​because it was built into the language from the very beginning, and the developer is obliged to use it. This is the only acceptable way to report errors. If you don't write code to handle exceptions appropriately, you will receive a compile-time error. This sequential approach sometimes makes error handling a lot easier.

It's worth noting that exception handling is not a feature of an object-oriented language, although in those languages ​​an exception is usually represented by an object. This mechanism existed even before the emergence of object-oriented languages.

Parallel programming

One of the fundamental concepts of programming is the idea of ​​performing multiple operations 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 know 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 needed to process the operations of time-critical tasks, but there is a whole class of tasks where you just need to split the task into several separately executed parts so that the program responds faster to external influences. These separately executed parts of the program are called streams, and the whole principle was named parallelism(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.

Typically, threads only define how the time of a single processor is allocated. However, if the operating system supports multiple processors, each thread can be assigned to a separate processor; this is how true concurrency is achieved. One of the convenient properties of parallelism, at the language level, is that the programmer does not need to know if there is one processor in the system or several. The program is logically divided into threads, and if the machine has more than one processor, it runs faster without any special settings.

All of this gives the impression that streams are very easy to use. But here's the catch: shared resources. Problems arise if multiple threads try to access the same resource at the same time. For example, two processes cannot send information to a printer at the same time. To prevent conflict, shared resources (such as a printer) must 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 is built into the language Java, and with the exit Java SE5 it adds significant library support.

Java and the Internet

If 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 problems, the answer is not immediately obvious. Although the language Java useful when building standalone applications, its most important application has been and remains programming for the network World wide web.

What is the Web?

At first glance, the Web looks quite mysterious due to the abundance of newfangled terms like "surfing", "presence" and "home pages". To understand what it is, it is helpful to imagine the big picture - but first you need to understand the interaction of client / server systems, which are one of the most difficult problems of 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, the key role is assigned to a centralized repository of information, which usually allows data to be changed in such a way that these changes are propagated to information users. All together: a repository of information, software that disseminates information, and the computer (s) that store the software and data is called a server. The software on the user's machine that communicates with the server, receives information, processes it and then displays it appropriately is called a client.

So the basic concept of client / server computing is not that hard. Problems arise because one server is trying 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 across tables. Additionally, the system often allows the client to add new information to the server. This means that new customer information must be protected from loss during storage in the database, as well as from the possibility of overwriting it with data from another customer. (This is called transaction processing.) When you change the client software, you must not only compile and test it, but also install it on the client machines, which can be much more complex and expensive than you might imagine. It is especially difficult to organize support for many different operating systems and computer architectures. Finally, there is a critical performance factor to consider: the server can receive hundreds of requests at the same time, and the slightest delay can have serious consequences. To reduce latency, programmers try to distribute computations, often even performing them 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 ​​spreading information between people has so many levels of complexity in its implementation that, on the whole, its solution seems unattainable. And yet it is vital: about half of all programming tasks are based on it. It is involved in solving a variety of problems, from servicing orders and credit card transactions to disseminating all kinds of data - scientific, government, stock quotes ... the list goes on and on. In the past, for each new problem, you had to create a separate solution. 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 client / server computing problem needs a broader approach.

The web is like a giant server

In fact, the web is one huge client / server system. However, this is not all: all servers and clients coexist on a single network at the same time. However, this fact should not interest you, since usually you connect and interact with only one server (even if you have to search for it all over the world).

At first, a simple one-way exchange of information was used. You made a request to the server, it sent you a file, which your viewer (that is, the client) processed for you. But soon, simply fetching static pages from the server was no longer enough. Users wanted to use all the capabilities of the client / server system, send information from client to server in order, for example, to view the server database, add new information to the server or place orders (which required special security measures). We are constantly seeing these changes in the development of the web.

Web viewers (browsers) have taken a big step forward: they have introduced the concept of information that is displayed the same way on all types of computers. 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 a few minutes just 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 safety - the user was prevented from running programs containing viruses or errors.)

Various approaches have been taken to solve these problems. For starters, the graphics display standards have been improved so that browsers can display animations and videos. The rest of the tasks required the possibility of launching 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 assigned to the server. The server generated static pages for the client's browser, which simply processed and displayed them. Standard Html Supports the simplest means of data entry: text boxes, radio buttons, checkboxes, lists, and drop-down lists, along with buttons that can do only two things: flushing the form data and submitting it to the server. The information sent is processed by the interface CGI (Common Gateway Interface) supported by all web servers. The request text indicates CGI how exactly to deal with the data. Most often, upon 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 cgi-bin.) Such programs can be written in almost all languages. Commonly used Perl, since it is focused on text processing, and is also an interpreted language, accordingly, it can be used on any server, regardless of the type of processor or operating system. However, the language Python(my favorite language - go to www.Python.org) gradually wins his "territory" due to its power and simplicity.

Many powerful web servers today operate entirely on the basis of CGI; in principle, this technology can solve almost any problem. However, web servers built on CGI-programs are hard to maintain and have responsiveness issues. Response time CGI-program depends on the amount of information sent, as well as on the server and network load. (Because of everything mentioned, running CGI-programs may take a long time). Early web designers did not anticipate how quickly a system would deplete when used in a variety of applications. For example, it is almost impossible to display graphs in real time in it, since whenever the situation changes, it is necessary to build a new GIF file and transfer it to the client. Without a doubt, you have your own bitter experience - for example, from simply submitting form data. You press a button to send information; server starts CGI-the program that detects the error generates Html- a page informing you about this, and then sends this page in your direction; you have to retype the data and try again. It's not only slow, it's downright inelegant.

The problem is solved by client side programming. 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 for the next page to be served. Client-side programming entrusts the browser with all the work it can do, which translates into faster web browsing and improved interactivity for the user.

However, a discussion of client programming is not much different from a discussion of 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 presents a dizzying number 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 add-on modules ( plug-ins). This approach allows the programmer to add new functionality to the browser by downloading a small program that is embedded in the browser. In fact, from this moment on, the browser acquires new functionality. (An extension module only loads once.) Plugins have allowed browsers to be equipped with a number of fast and powerful innovations, but writing such a module is not an easy task, and you probably won't want to create extensions every time you create a new site. The value of add-ons for client programming is that they allow an experienced programmer to add new features to the browser without asking permission from the browser creator. Thus, add-on modules provide a back door for integrating new programming languages ​​on the client side (although not all languages ​​are implemented in such modules).

Scripting languages

The development of plugins has led to the emergence of 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 you view it. Scripting languages ​​are usually fairly easy to learn; in essence, the script code is the text included in the Html-page, so it loads very quickly as part of a single request to the server while fetching the page. The pay off is that anyone can view (and steal) your code. However, it is unlikely that you will write anything worthy of imitation and sophisticated in scripting languages, so the problem of copying code is not such a big deal.

A scripting language that is supported by almost any browser without installing additional modules is JavaScript(which has very little to do with Java; the name was used to snatch 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 a while for its support to appear in all browsers (in addition, the company Microsoft actively promoted her own language VBScript vaguely resembling JavaScript). In general, 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 code debugging JavaScript then occupation is in best case difficult. Only recently have developers managed to create a really complex system written in JavaScript(company Google, service GMail), and it took the utmost in enthusiasm and experience.

This shows that the scripting languages ​​used in browsers were designed to solve a range of specific tasks, mainly to create a richer and more interactive graphical user interface ( GUI). However, the scripting language can be used to solve 80% of client programming problems. Your task may just fall into this 80%. Since scripting languages ​​allow you to quickly and easily write code, you should consider this language first before moving on to more complex technological solutions like Java.

Java

If scripting languages ​​take over 80% of client programming tasks, then who can handle the remaining 20%? For them, the most popular solution today is Java... It is not only a powerful programming language designed with security, platform compatibility and internationalization in mind, but also an ever-evolving tool, complemented by new features and libraries that elegantly fit into traditionally complex programming problems: multitasking, database access, network programming, and distributed computing. Client programming on Java comes down to developing applets as well as using a package Java Web Start.

An applet is a mini-program that can only be executed inside the browser. Applets are automatically loaded as part of a web page (in the same way as, for example, graphics are loaded). When the 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 earlier. The user receives the latest version of the client program, without any problems and difficulties associated with reinstallation. According to ideology Java, the programmer creates only one program that automatically runs on all computers where there are browsers with a built-in interpreter Java... (This is true for almost all computers.) Since Java is a full-fledged programming language, as much of the work as possible should be done on the client side before (or after) accessing 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's computer can quickly draw a graph without waiting for the server to do it and send back the file with the image. This arrangement not only provides instant gains in speed and responsiveness, but also reduces the load on the main network transport and servers, preventing overall Internet slowdowns.

Alternatives

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

Indeed, there are a number of very interesting applets on the web. Yet the massive transition to applets never materialized. Probably the main problem was that downloading the 10MB package to install the environment Java Runtime Environment (JRE) too frightened of an ordinary user. The fact that the company Microsoft did not include JRE in delivery Internet Explorer, finally decided the fate of applets. Anyway, applets Java have not received widespread use.

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

.NET and C #

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

At the moment, the main question is whether Microsoft will completely allow porting .NET to other platforms. IN Microsoft claim that there is no problem with this, and the project Mono () provides partial implementation .NET for Linux... However, since this implementation is incomplete, so far Microsoft will not decide to throw out any part of it, bet on .NET it's too early for cross-platform technology.

Internet and intranet

The web provides a solution of the most general nature for client / server problems, so it makes sense to use the same technology for solving problems in special cases; this is especially true for the classic client / server interaction within the company. The traditional client / server approach has problems with different types of client computers, adding to the difficulty of installing new software 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, the network is called an intranet. Intranets are much more secure than the Internet because you can physically control access to your company's servers. In terms of learning, it is much easier for a person who understands the concept of a browser to understand the different pages and applets, so the time it takes to learn new systems is reduced.

The security issue brings us to one of the directions that automatically arises in client programming. If your program runs on the Internet, then you don't know what platform it will run on. You have to be extra careful to avoid spreading incorrect code. This requires cross-platform and secure solutions like Java or scripting language.

Other restrictions apply on intranets. Quite often, all machines in the network run on the platform Intel / Windows... On an intranet, you are responsible for the quality of your code and you can troubleshoot bugs as you find them. In addition, you may already have a collection of solutions that have been tested on more traditional client / server systems, while new software will have to be manually installed on the client machine with each update. Time spent on updates is the most compelling reason for browser technology, where updates are invisible and automatic (the same allows you to do Java Web Start). If you are involved in the maintenance of an intranet, it is prudent to use the path that will allow you to draw on existing developments without rewriting programs in new languages.

When faced with a volume of client programming tasks that can baffle any designer, it is best to assess 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 possible solution are always relevant. This proactive attitude will give you the opportunity to prepare for the inevitable challenges of software development.

Server side programming

Our discussion has bypassed the topic of server programming, which is considered by many to be its greatest strength. Java... What happens when you send a request to the server? More often than not, the request boils down to a simple request "send me this file." The browser then processes the file appropriately: like Html-page like picture like Java-applet like script, etc.

A more complex request to the server is usually associated with a database call. In the most common case, a query is made for a complex search in the database, the results of which are then converted by the server into Html-page and sends it 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 the load on the server.) Or maybe you need to register in the database when joining a group, or place an order, which will require changes in the database. Such requests must be handled by some code on the server; in general, this is called server programming. Traditionally, server programming has been done on Perl, Python, C ++ or another language that allows you to create programs CGI, but more interesting options appear. These include those based on Java web servers that allow you to do server programming on Java using so called servlets. Servlets and their creations JSPs are two main reasons for web content development companies to move to Java, mainly due to the fact that they solve the problems of incompatibility between different browsers.

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: they are suitability for solving programming problems, and resistance to errors, and a large standard library, and numerous third-party developments - both existing and constantly appearing.

Summary

You know what a procedural program looks like: data definitions and function calls. To figure out the purpose of such a program, it is necessary to make an 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 means - by themselves they are more focused on the computer, and not on the problem being solved.

Since OOP adds many new concepts to those already found 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 is usually much easier to understand than its procedural counterpart. All you see are object definitions that represent the concepts of solution space (not computer implementation concepts), and messages sent to those objects that represent actions in that space. One of the advantages of OOP is precisely 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 are easily solved by existing class libraries.

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

Exchanges by the powers that be:

Directly on the qiu side: http: //site/javabooks/Thinking_in_Java_4th_edition.html BB codes: Thinking_in_Java_4th_edition
html-permissions: