|Inheritance Tutorial (UNIX)||Exception Handling Tutorial (UNIX)|
This tutorial is about using the collection classes. Collections are very useful in object-oriented programming. They are used to store groups of objects. You usually store objects of the same type in any particular collection, although you can also store objects of different types in a collection.
Object COBOL also enables you to store COBOL intrinsic data, which is not represented by objects, in collections. This is done by a mechanism which enables you to treat an item of intrinsic data, for example a PIC X(20), as though it were an object. You can't mix intrinsic data and objects inside a single collection, or mix different types of intrinsic data.
Finally, this tutorial covers dictionaries, which are another type of collection.
This tutorial consists of the following sessions:
Time to complete: 25 minutes.
The different types of collection in the Class Library can all be categorized by the following properties:
You can access any particular element in an indexed collection by giving its position. This is similar to using a conventional array or table. In non-indexed collections, elements are not stored in a defined order.
Automatically growable collections get bigger when you exceed the capacity for which you created the collection. A manually growable collection only gets bigger when you send it the "grow" message.
Some collections disallow duplicate elements; trying to add an element which has the same value as one already in the collection will cause an exception.
The different collection classes available are listed below, with their properties.
|Bag||Non-indexed, automatically growable, duplicates allowed|
|Array||Indexed, manually growable, duplicates allowed|
|CharacterArray||Indexed, manually growable, duplicates allowed|
|OrderedCollection||Indexed by insertion order, automatically growable, duplicates allowed|
|SortedCollection||Indexed by sort order, automatically growable, duplicates allowed|
|ValueSet||Non-indexed, automatically growable, duplicate values disallowed|
|IdentitySet||Non-indexed, automatically growable, duplicate object handles disallowed|
|Dictionary||Indexed by key, automatically growable, duplicate key values disallowed|
|IdentityDictionary||Indexed by key, automatically growable, duplicate key
object handles disallowed
ValueSet and IdentitySet only differ in the way they determine duplicate elements. ValueSets compare the values of elements and disallow duplicate values; IdentitySets compare object references, and disallow storing the same object more than once.
Dictionary and IdentityDictionary are special types of collection which are dealt with in a later section of this tutorial. They determine duplicate keys in the same way that ValueSet and IdentitySet determine duplicate elements.
Next you will look at a simple COBOL program, coll0.cbl which illustrates some of the differences and similarities between the main types of collection. The program is not an Object COBOL class program, but procedural COBOL code which uses the Class Library collection objects.
To animate coll0.cbl
cob -a coll0.cbl
This is a set of strings the program stores in different types of collection.
You need to specify an initial size for all types of collection. In the case of an Array, you can't exceed the initial capacity of the collection unless you send it the "grow" message. Other types of collection will grow in size if you add more elements than initially specified.
However, growing collections can be an expensive operation at run-time, so you should still always try to pick an initial size which will reduce the number of times a collection needs to grow.
move 20 to i).
i holds the length for the instances of
CharacterArray which will be created as elements for the different
types of collection.
invoke CharacterArray "withLengthValue"...).
A CharacterArray is an object for storing strings. The "withLengthValue" message creates a new instance of CharacterArray and initializes it with some data.
invoke aBag "add"...) until the
With all the different collection types, except Array, you use the "add" message to add new elements. With the Array, which does not grow automatically, you use "atPut" which stores the element at the specified index position.
Although OrderedCollection is indexed, you can't use "atPut" until you have added elements to the collection. For example, once you have used "add" to add the first five elements, you can use "atPut" and an index between one and five to replace any of those elements. You can't do an "atPut" with an index greater than five until you have added more elements.
You can't ever use "atPut" to put an element in a SortedCollection. A SortedCollection uses the value of each element to determine its position in the collection.
This completes execution of the perform loop without you needing to step through each statement in turn.
display " ").
This retrieves the object reference to the fourth element in the Array instance. Sending the "display" message to the CharacterArray displays it on the screen. Its contents should be the word "banana". Push the F2=View key to see the console contents, and any other key to return to the Animator display.
invoke aBag "includes"...).
Because a Bag is not indexed, you can't retrieve elements from it directly. Instead, you can query it to find out whether it has one or more occurrences of an object with a given value.
The "includes" message returns 1 if a Bag contains an object with a matching value and 0 if it doesn't. You can also examine the contents of unindexed collections (Bags and ValueSets) by using the iterator methods. These are covered later in this tutorial.
display " ").
These test the result of the returned value and tell you whether or not the bag contained an object with a matching value.
invoke aBag "add"...).
This adds the string to the Bag again. A Bag stores objects with duplicate values by recording the number of occurrences of each object with a different value.
invoke aBag "occurrencesOf"...).
The "occurrencesOf" message returns the number of elements a collection has which match the specified object in value.
display " ").
This displays the information that this Bag has two occurrences of the specified string.
invoke anArray "occurrencesOf"...) and A015 (
display " ").
This code demonstrates how the Array also responds to the "occurrencesOf" message. You can use "occurrencesOf" and "includes" on indexed collections as well as instances of Bag and ValueSet. This tells you whether or not the element exists, but not its position.
invoke aValueSet "add"...).
Instances of ValueSet do not maintain duplicate elements. The ValueSet already contains an element with value "banana", so it will not be added a second time.
invoke aValueSet "occurrencesOf"...).
This message always returns 1 or 0 for ValueSet instances.
display " ").
This displays the result of the "occurrencesOf" message.
The statements below tag A016 (
contents") display every string in the OrderedCollection
and SortedCollection in indexed order. The elements in the
OrderedCollection appear in the order in which they were added. The
elements in the SortedCollection appear sorted into ascending
alphabetical order. Ascending order is the default for a
At the end of this section, you may have some questions to ask:
Program coll0.cbl stores instances of CharacterArray in the collections it creates. An instance of a CharacterArray is a simple object, with an obvious single value (the string you store in it). But if you stored Account objects, like the ones used in the tutorial Inheritance in a collection, how would you determine the value? Would it be the name, the balance or the account number?
The answer is that the collection objects provide a framework within which objects stored as elements must work. When a collection needs to know whether two objects are equal, it sends one object the "equal" message, passing it the other as a parameter. It is then up to the object to interrogate the other element and decide whether they are equal or not.
The default sort method for the SortedCollection works in a similar way. The SortedCollection sends one element the "lessThanOrEqual" method and a second element as a parameter. The receiving element can then compare itself to the second element and return a result.
If you are writing your own objects to store in collections, you may need to implement these methods yourself, unless you are subclassing from a class like CharacterArray, which implements them for you. There is also a default "equal" method in Base which compares the object handles of two objects. This implementation of "equal" will only find two elements equal if they are actually the same object.
For full information on the methods you might need to implement to use the Collection classes, see the chapter Collection Frameworks.
In the previous session, you looked at a program which stored objects in different types of collection. There may be occasions when you want to store intrinsic COBOL data, like numbers, in collections. You can do this by using the intrinsic classes of the Class Library.
Object COBOL provides a mechanism which enables you to send a message to an intrinsic data item, as though it were an object.
To send a message to an intrinsic data item
The class library includes classes for three different types of intrinsic data (PIC X, PIC X COMP-X, PIC X COMP-5). These classes are templates which handle data of fixed length. When you clone the class, you specify the actual size of data you want to handle.
The example below sends the "hash" message to a numeric data item:
00001 working-storage section. 00002 01 aValue pic x(2) comp-5. 00003 01 cloneX2Class object reference. 00004 01 aLength pic x(4) comp-5. ... 00005 procedure division. ... 00006 move 2 to aLength 00007 invoke COBOLCOMP5 "newClass" using aLength 00008 returning cloneX2Class ... 00009 invoke aValue as cloneX2Class "hash" 00010 returning aHashValue ...
This is what the code above is doing:
|Lines 1-4||Declaring data.|
|Lines 6-8||Cloning the COBOLCOMP5 to create a new class for comp-5 data items two bytes long.|
|Line 9||Sending a message to the data in data item
invoke...as statement uses the data item as an
instance of the cloned class. You can think of an intrinsic object as a
static object which has memory allocated to it at compile time. Static
objects do not have object handles, unlike the dynamic objects created by
the OO RTS at run-time. Intrinsic data objects are the only examples of
static objects in Object COBOL.
We will now look at a short sample program, coll1.cbl, which uses the intrinsic classes to store a set of integers in an array.
To animate coll1.cbl
cob -a coll1.cbl
move 4 to i).
The "newClass" message creates a clone of the CobolComp5 class, initialized in this case for data four bytes in length. The object returned is a new class object, and not an instance of CobolComp5.
move 10 to i).
This creates an instance of Array with space for ten elements. This time the message to create the Array instance is "ofValues" (in the example in the previous section it was "ofReferences").
When a collection is created with the "ofValues" message, it stores intrinsic data instead of object handles. The cloned class, PicX4Comp5, is used as a template so that the Array knows how much space to allocate for each element.
You can't mix objects and intrinsics inside a single collection. Once you create a collection you can only store the type of data for which it is initialized. If you want to mix many different kinds of data in a collection, you should create the collection using "ofReferences", and use different types of objects to represent the different types of data. There is no restriction on mixing different kinds of object inside a collection of references.
move 10 to element).
perform varying from 1 ...).
This executes the entire perform loop to initialize the array.
This retrieves the fourth element from the array (which has a value
of 7) and displays it. Because a data item is returned, we can show it
display verb; when we got back objects in the
previous example we had to send them the "display" message
to show them.
This completes this part of the tutorial on using intrinsic data. For more information, see the chapter Intrinsic Frameworks.
Dictionaries are a special sort of indexed collection, which store key-data pairs (known as associations). In a dictionary, the key is used as the index when you store or retrieve the data. Dictionaries do not allow you to store duplicate keys.
Like the other collection types, you can store either objects or intrinsic data in a dictionary. In a dictionary though, either the key or the data part can be an intrinsic or an object. This gives you four possible combinations for intrinsic or object storage:
When you create a dictionary you have to give it a template so that it knows how the key and data portions are to be stored. The template is either the Association class, or a clone of the Association class.
The Association class is another clonable class, like the classes for intrinsic data classes, used for creating templates for data storage. An Association template actually consists of two templates; one for the key and one for the data. To create any type of dictionary you need to create an Association template.
To create an Association template
If both the key and data entries in the dictionary are to be objects you can use the Association class itself as a template; you don't need to create a clone.
Having created a template for your dictionary object, there are two methods you can use to create a dictionary itself; "ofValues" or "ofAssociations". A dictionary "ofValues" stores each element as a key data pair. A dictionary "ofAssociations" stores each element as an instance of the Association template you used to create the dictionary.
A dictionary "ofAssociations" in effect stores three items for every entry in the dictionary:
An object which contains the key and data
Which could be an object or an intrinsic value
Which could be an object or an intrinsic value
A dictionary "ofValues" stores the key and data directly without wrapping them inside an association object. When would you use a dictionary "ofAssociations" and when would you use a dictionary "ofValues"?
A dictionary "ofValues" is more efficient at run-time in terms of speed and memory if you simply want to store data items against key items. However, if your application uses associations elsewhere to manage key/data pairs, a dictionary "ofAssociations" is a better choice as the actual dictionary only stores the object handles to the associations, and you don't have to extract the key and value from the association before putting it in the dictionary.
The sample code below creates an association template and then uses it to create a dictionary "ofValues":
00001 working-storage section. 00002 01 aKeyTemplate object reference. 00003 01 aDataTemplate object reference. 00004 01 anAssocTemplate object reference. 00005 01 aDictionary object reference. 00006 01 aLength pic x(4) comp-5. ... 00007 procedure division. ... 00008 move 3 to aLength 00009 invoke CobolCompX "newClass" using aLength 00010 returning aKeyTemplate 00011 move 20 to aLength 00012 invoke CobolPicX "newClass" using aLength 00013 returning aDataTemplate 00014 invoke Association "newClass using aKeyTemplate 00015 aDataTemplate 00016 returning anAssocTemplate 00017 invoke Dictionary "ofValues" using anAssocTemplate 00018 returning aDictionary ...
|Lines 1-6||Declares storage for the templates.|
|Lines 8-9||Creates a template for a PIC X(3) COMP-X numeric key.|
|Lines 11-12||Creates a template for a PIC X(20) data portion.|
|Line 14||Creates an association template.|
|Line 17||Creates a dictionary of values.
In the next part of this section we will animate through some code which uses a dictionary to store account objects. The account objects are the ones which were introduced in the tutorial Inheritance.
Each account object is stored in the dictionary against the customer name. We are going to use a simple program, coll2.cbl to demonstrate the use of the dictionary.
To animate coll2.cbl
cob -a coll2.cbl
Animator starts with the statement below tag A040 highlighted ready for execution.
move length of...).
The dictionary we want to create uses the customer name as the key, and account objects as values. This code clones the intrinsic class CobolPicX to create a class for representing strings the same length as a customer name.
set wsNull to null).
This code clones the Association class, to create a template for the dictionary. The key portion represents strings the same length as wsCustomer, the data portion is set to null and represents an object handle.
invoke Dictionary "ofValues"...).
This creates the new dictionary. It is initialized to store 10 elements, but grows automatically if more elements than this are added.
move spaces to wsCustomer).
This creates a check account. Using Perform Step saves animating through all the "openAccount" code.
invoke wsDictionary "atPut"...).
The "atPut" message stores the account object at the key in wsCustomer.
move "Mike" to wsCustomer).
This creates two more accounts, and stores them in the dictionary.
move spaces to wsCustomer).
The "at" message retrieves Bob's account from the dictionary.
invoke wsAccount "printStatement").
The "printStatement" message displays the account details on the console. Press F2=view to switch the display from Animator to see what is on the console. Press any key to return to the Animator view.
Don't shut down Animator; the next session carries on directly from this one.
This completes this section; in the next section you will use the already running application to look at the iterator methods for collections.
The collection classes all provide iterator methods which enable you to examine all the elements of a collection. There are four iterator methods, and they are supported by all types of collection:
Passes every element as a parameter to a method which you specify.
Passes every element as a parameter to a method which you specify, and creates a subcollection of the elements for which your method returns the value 1.
Passes every element as a parameter to a method which you specify, and creates a subcollection of the elements for which your method returns the value 0.
Passes every element as a parameter to a method which you specify, and creates a new collection of the elements which your method returns.
This section shows how coll2.cbl uses the iterator methods to carry out operations on all accounts. The instructions below assume that you are continuing directly from the end of the previous section, and have not stopped animating coll2.cbl.
To see the use of iterators
invoke EntryCallback "new"...).
Iterator methods pass elements in the collection to a piece of code. An EntryCallback is an object which contains an entry-point name; in effect it is a piece of code wrapped up inside an object. The class library also includes a Callback class, which contains an object handle and a message name; in effect wrapping a method up inside an object.
invoke wsDictionary "do"...).
The callback is passed to the "do" method inside dictionary. The "do" method uses the callback to call the entry-point "printAll" for each object in the dictionary. Execution switches to the the statement below tag A160.
invoke lnkAccount "printStatement").
Execution jumps straight back to the top of entry-point, below tag A160. The "do" method in the dictionary has passed its next account object to the entry-point. The entry-point is called once for every single account in the dictionary. All collections respond to the "do" message.
The iterator prints statements for the remaining accounts in the dictionary. You can see how the combination of collection iterator methods, and polymorphic methods is a powerful programming technique.
This completes the tutorial.
This tutorial covered the following:
Copyright © 1999 MERANT International Limited. All rights reserved.
This document and the proprietary marks and names used herein are protected by international law.
|Inheritance Tutorial (UNIX)||Exception Handling Tutorial (UNIX)|