Java1.0.2でのオブジェクトのコンテナサポート

ハーバート・スペンサーは、「科学は組織化された知識です」と書いています。当然の結果として、アプリケーションは組織化されたオブジェクトである可能性があります。アプレットではなくアプリケーションの開発に不可欠なJavaのいくつかの側面に目を向けてみましょう。

Javaについて聞いたことがある人の大多数は、人気のある報道機関を通じてJavaについて学びました。 Javaは「Webページに埋め込むことができる小さなアプリケーションまたはアプレットをプログラミングする」ためのものであるという声明が多く出てきます。この定義は正しいですが、新しい言語の1つの側面のみを伝えます。全体像を説明しているわけではありません。おそらくJavaは、全体的または部分的に組み合わせて望ましい全体を生成できる、よく理解されている移植可能なコードのシステム(大規模システム)を構築するために設計された言語としてより適切に説明できます。

このコラムでは、Javaでのビルドに使用できるさまざまなツールについて見ていきます。これらのツールを組み合わせてより大きなアプリケーションを作成する方法と、アプリケーションを作成したら、アプリケーションをさらに大きなシステムにさらに集約する方法を示します。Javaでは完全なアプリケーションとの区別がないため、すべて可能です。単純なサブルーチン。

この列と過去の列にソースコードの飼料を提供するために、BASICインタープリターを作成することにしました。「なぜベーシックなの?」もう誰もBASICを使っていないと思って、あなたは尋ねるかもしれません。これは完全に真実ではありません。BASICは、VisualBasicおよびその他のスクリプト言語で使用されています。しかし、もっと重要なことは、多くの人がそれにさらされており、次の概念的な飛躍を遂げることができることです。「アプリケーション」がBASICでプログラムされ、BASICがJavaで記述できる場合、アプリケーションはJavaで記述できます。BASICは単なる別のインタープリター言語です。構築するツールは、任意の言語構文を使用するように変更できるため、コアコンセプトはこれらの記事の焦点です。したがって、アプリケーションとして開始されるものは、他のアプリケーションのコンポーネントになります。おそらくアプレットですらあります。

ジェネリッククラスとコンテナ

ジェネリッククラスの構築は、アプリケーションを作成するときに特に関係があります。クラスを再利用すると、複雑さと市場投入までの時間の両方を大幅に削減できるからです。アプレットでは、ジェネリッククラスの値は、ネットワークを介してロードするという要件によって軽減されます。ネットワークを介してジェネリッククラスをロードすることの悪影響は、SunのJava Workshop(JWS)によって実証されています。 JWSは、いくつかの非常にエレガントな「シャドウ」クラスを使用して、抽象ウィンドウツールキット(AWT)の標準バージョンを拡張します。利点は、アプレットの開発が簡単で、機能が豊富なことです。欠点は、これらのクラスのロードが遅いネットワークリンクで多くの時間を要する可能性があることです。この欠点は最終的には解消されますが、最良のソリューションを実現するには、クラス開発に関するシステムの観点が必要になることがよくあります。

アプリケーション開発をもう少し真剣に検討し始めているので、ジェネリッククラスが有効なソリューションであるとすでに判断していると想定します。

Javaは、多くの汎用言語と同様に、ジェネリッククラスを作成するためのいくつかのツールを提供します。さまざまな要件を使用する必要があります

さまざまなツール。このコラムでは、containerクラスの開発を例として使用します。これは、ユーザーが使用する可能性のあるほぼすべてのツールに対応できるためです。

コンテナ:定義

オブジェクト指向のことをまだよく知らない人にとって、コンテナは他のオブジェクトを編成するクラスです。一般的なコンテナは、バイナリツリー、キュー、リスト、およびスタックです。Javaは、JDK 1.0.2リリースでjava.util.Hashtable、java.util.Stack、およびjava.util.Vectorの3つのコンテナクラスを提供します。

コンテナには、編成の原則とインターフェースの両方があります。たとえば、スタックは「ファーストイン、ラストアウト」(FILO)として編成でき、それらのインターフェイスは、push()pop()の2つのメソッドを持つように定義できます。単純なコンテナは、標準的なメソッドで。を追加および削除するものと考えることができます。さらに、コンテナ全体を列挙し、候補オブジェクトがすでにコンテナ内にあるかどうかを確認し、コンテナによって保持されている要素の数をテストする手段があります。

Javaコンテナクラスは、コンテナ、特にキー付きコンテナ(キーを使用してオブジェクトを検索するコンテナ)に関するいくつかの問題を示しています。 StackやVectorのようなキーのないコンテナは、単にオブジェクトを詰め込んだり引き出したりします。キー付きコンテナーHashtableは、キーオブジェクトを使用してデータオブジェクトを検索します。キーイング関数が機能するには、キーオブジェクトが、オブジェクトごとに一意のハッシュコードを返すメソッドHashCodeをサポートしている必要があります。このキーイング機能は、ObjectクラスはHashCodeメソッドを定義するため、すべてのオブジェクトに継承されますが、必ずしも必要なものとは限りません。たとえば、オブジェクトをハッシュテーブルコンテナに入れ、Stringオブジェクトでインデックスを作成している場合、デフォルトのHashCodeメソッドは、オブジェクト参照値に基づいて一意の整数を返すだけです。文字列の場合、ハッシュコードを文字列値の関数にする必要があるため、StringはHashCodeをオーバーライドし、独自のバージョンを提供します。つまり、開発し、オブジェクトのインスタンスをキーとして使用してハッシュテーブルに格納するオブジェクトについては、HashCodeメソッドをオーバーライドする必要があります。これにより、同じように構築されたオブジェクトが同じコードにハッシュされることが保証されます。

しかし、ソートされたコンテナはどうですか?Objectクラスで提供される唯一のソートインターフェイスはequals()であり、2つのオブジェクトを同じ参照を持ち、同じ値を持たないものと見なすように制約されています。これが、Javaでは次のコードを記述できない理由です。

 if(someStringObject == "this")then {...何かをする...} 

上記のコードはオブジェクト参照を比較し、ここには2つの異なるオブジェクトがあることに注意し、falseを返します。次のようにコードを記述する必要があります。

 if(someStringObject.compareTo( "this")== 0)then {...何かをする...} 

この後者のテストでは、StringのcompareToメソッドにカプセル化された知識を使用して、2つの文字列オブジェクトを比較し、等しいことを示します。

ボックス内のツールを使用する

先に述べたように、ジェネリックプログラムの開発者は、実装の継承(拡張)と動作の継承(実装)という2つの主要なツールを利用できます。

実装継承を使用するには、既存のクラスを拡張(サブクラス)します。拡張により、基本クラスのすべてのサブクラスは、ルートクラスと同じ機能を持ちます。これは、クラスのHashCodeメソッドの基礎ですObject。すべてのオブジェクトはjava.lang.Objectクラスから継承するため、すべてのオブジェクトには、HashCodeそのオブジェクトの一意のハッシュを返すメソッドがあります。ただし、オブジェクトをキーとして使用する場合は、オーバーライドに関する前述の注意事項に注意してくださいHashCode

実装の継承に加えて、動作の継承(実装)があります。これは、オブジェクトが特定のJavaインターフェイスを実装することを指定することによって実現されます。インターフェイスを実装するオブジェクトは、そのインターフェイスタイプのオブジェクト参照にキャストできます。次に、その参照を使用して、そのインターフェイスで指定されたメソッドを呼び出すことができます。通常、インターフェイスは、クラスが異なるタイプの複数のオブジェクトを共通の方法で処理する必要がある場合に使用されます。たとえば、Javaは、スレッドクラスが独自のスレッド内のクラスを操作するために使用するRunnableインターフェイスを定義します。

コンテナの構築

ジェネリックコードを作成する際のトレードオフを示すために、ソートされたコンテナクラスの設計と実装について説明します。

先に述べたように、汎用アプリケーションの開発では、多くの場合、優れたコンテナーが役立ちます。私のサンプルアプリケーションでは、両方ともキー付きのコンテナが必要でした。つまり、単純なキーを使用して含まれているオブジェクトを取得し、キー値に基づいて特定の順序で含まれているオブジェクトを取得できるように並べ替えました。

システムを設計するときは、システムのどの部分が特定のインターフェースを使用するかを覚えておくことが重要です。コンテナの場合、コンテナ自体とコンテナのインデックスを作成するキーの2つの重要なインターフェイスがあります。ユーザープログラムは、コンテナを使用してオブジェクトを格納および整理します。コンテナ自体は、主要なインターフェイスを使用して、コンテナを整理します。コンテナを設計する際には、使いやすく、さまざまなオブジェクトを保存できるように努めています(したがって、その有用性が高まります)。さまざまなコンテナ実装で同じキー構造を使用できるように、キーは柔軟に設計されています。

To solve my behavioral requirements, keying and sorting, I turn to a useful tree data structure called a binary search tree (BST). Binary trees have the useful property of being sorted, so they can be efficiently searched and can be dumped out in sorted order. The actual BST code is an implementation of the algorithms published in the book Introduction to Algorithms, by Thomas Cormen, Charles Leiserson, and Ron Rivest.

java.util.Dictionary

The Java standard classes have taken a first step toward generic keyed containers with the definition of an abstract class named java.util.Dictionary. If you look at the source code that comes with the JDK, you will see that Hashtable is a subclass of Dictionary.

The Dictionary class attempts to define the methods common to all keyed containers. Technically, what is being described could more properly be called a store as there is no required binding between the key and the object it indexes. However, the name is appropriate as nearly everyone understands the basic operation of a dictionary. An alternative name might be KeyedContainer, but that title gets tedious pretty quickly. The point is that the common superclass of a set of generic classes should express the core behavior being factored out by that class. The Dictionary methods are as follows:

size( )

This method returns the number of objects currently being held by the container.
isEmpty( ) This method returns true if the container has no elements.
keys( ) Return the list of keys in the table as an Enumeration.
elements( ) Return the list of contained objects as an Enumeration.
get(Objectk) Get an object, given a particular key k.
put(Objectk,Objecto) Store an object o using key k.
remove(Objectk) Remove an object that is indexed by key k.

By subclassing Dictionary, we use the tool of implementation inheritance to create an object that can be used by a wide variety of clients. These clients need know only how to use a Dictionary, and we can then substitute our new BST or a Hashtable without the client noticing. It is this property of abstracting out the core interface into the superclass that is crucial to reusability, general-purpose function, expressed cleanly.

Basically, Dictionary gives us two groups of behavior, accounting and administration -- accounting in the form of how many objects we've stored and bulk reading of the store, and administration in the form of get, put, and remove.

If you look at the Hashtable class source (it is included with all versions of the JDK in a file named src.zip), you will see that this class extends Dictionary and has two private internal classes, one named HashtableEntry and one named HashtableEnumerator. The implementation is straightforward. When put is called, objects are placed into a HashtableEntry object and stored into a hash table. When get is called, the key passed is hashed and the hashcode is used to locate the desired object in the hash table. These methods keep track of how many objects have been added or removed, and this information is returned in response to a size request. The HashtableEnumerator class is used to return results of the elements method or keys method.

First cut at a generic keyed container

The BinarySearchTree class is an example of a generic container that subclasses Dictionary but uses a different organizing principle. As in the Hashtable class, I've added a couple of classes to support holding the stored objects and keys and for enumerating the table.

The first is BSTNode, which is equivalent to a HashtableEntry. It is defined as shown in the code outline below. You can also look at the source.

class BSTNode { protected BSTNode parent; protected BSTNode left; protected BSTNode right; protected String key; protected Object payload; public BSTNode(String k, Object p) { key = k; payload = p; } protected BSTNode() { super(); } BSTNode successor() { return successor(this); } BSTNode precessor() { return predecessor(this); } BSTNode min() { return min(this); } BSTNode max() { return max(this); } void print(PrintStream p) { print(this, p); } private static BSTNode successor(BSTNode n) { ... } private static BSTNode predecessor(BSTNode n) { ... } private static BSTNode min(BSTNode n) { ... } private static BSTNode max(BSTNode n) { ... } private static void print(BSTNode n, PrintStream p) { ... } } 

2つのことを明確にするために、このコードを見てみましょう。まず、nullで保護されたコンストラクターがあります。これは、このクラスのサブクラスが、このクラスのコンストラクターの1つをオーバーライドするコンストラクターを宣言する必要がないようにするために存在します。第2に、successorpredecessorminmaxprintの各メソッドは非常に短く、メモリスペースを節約するために同じプライベート等価物を呼び出すだけです。