SEN2001 · Week 10 · Abstract Data Types

Abstract data types (ADT) and encapsulation

The concept of abstraction, the two conditions of an ADT, information hiding, Ada packages, C++ and Java classes, parameterized ADTs (generics) and encapsulation constructs for large programs (namespace, package).

abstractionprocess abstractiondata abstractionabstract data typeinformation hidingencapsulationinterfaceimplementationaccessorconstructordestructorAda packagespecification packagebody packageprivate typelimited privateC++ classaccess modifierfriendparameterized ADTgeneric classtemplatenamespaceJava package

The way to cope with the complexity of a program is abstraction: seeing only the properties of an entity that matter at the moment and hiding the rest. Subprograms are process abstraction (you call sort without knowing the sorting algorithm). This week the second kind: data abstraction, that is the abstract data type (ADT). On exams conceptual questions are asked (the two conditions of an ADT, the benefit of information hiding, language comparisons) and the output of code that uses an ADT.

Golden rules:

  1. ADT = hidden representation + a type and operations defined in a single unit.
  2. The user sees only the interface. Changing the inside does not break user code.
  3. A stack is LIFO, a queue is FIFO. In output questions write the contents at each step.
  4. A parameterized ADT catches type errors at compile time.

Abstract data type

A user-defined type is an ADT if it satisfies two conditions:

  1. Hidden representation: the internal structure of the type’s objects is hidden from the units that use the type. The user accesses the objects only through the operations the type defines.
  2. A single unit: the declaration of the type and the protocols of the operations on it are in a single syntactic unit. Other units can create variables of this type.

In fact float is also like an ADT: you use + and * without knowing how the bits are arranged. User-defined ADTs bring the same comfort to the programmer’s own types.

Benefits of information hiding:

  • Reliability: the user cannot corrupt the internal state of the object (cannot set the stack’s top field to a random value).
  • Changeability: if the representation changes (a linked list instead of an array), the user code doesn’t change, because it depends only on the interface.
  • Fewer name clashes and smaller, manageable units.

ADTs in Java: the class

static class IntStack {
    private int[] items = new int[10];
    private int top = -1;
    public void push(int x) { items[++top] = x; }
    public int pop() { return items[top--]; }
    public int peek() { return items[top]; }
    public boolean isEmpty() { return top == -1; }
}

IntStack s = new IntStack();
s.push(3); s.push(7); s.pop(); s.push(2); s.push(9);
System.out.println(s.pop() + s.peek());
11
operation  stack (bottom → top)
push 3     [3]
push 7     [3, 7]
pop        [3]          (7 returned)
push 2     [3, 2]
push 9     [3, 2, 9]
pop        [3, 2]       (9 returned)
peek                    (2, not removed)  → 9 + 2 = 11

Writing s.top = 5; or a.balance = 5000; from outside gives a compile error:

error: balance has private access in Account

Access levels in Java: private (only the class), package access (if not specified: the same package), protected (package + subclasses), public. Access is at the class level: a Money object can read the private field of another Money object.

Immutable ADT

static final class Money {
    private final int cents;
    Money(int c) { cents = c; }
    Money add(Money o) { return new Money(cents + o.cents); }
    int cents() { return cents; }
}
Money m1 = new Money(150);
Money m2 = m1.add(new Money(75));
System.out.println(m1.cents() + " " + m2.cents());
150 225

Objects that don’t change can be shared; aliasing problems (being changed from one place and surprised from another) disappear. Java’s String is like this too.

Interface separate from representation

List<Integer> nums = new ArrayList<>(List.of(4, 1, 3));
List<Integer> view = Collections.unmodifiableList(nums);
try { view.add(5); } catch (UnsupportedOperationException e) { System.out.println("UnsupportedOperationException"); }
nums.add(5);
System.out.println(view.size());
UnsupportedOperationException
4

ADTs in other languages

LanguageConstructHidingNote
Adapackage: specification + bodythe private part of the specprivate type: :=, = available; limited private: not
C++class (and struct)private, protected, publicclass default private, struct public; friend grants access; constructor/destructor
Javaclassaccess modifiersAll objects on the heap, no destructor (garbage collection)
C#class, structinternal in addition to Java’sproperty: an accessor with get/set that looks like a field
Rubyclassmethods default public, fields always hiddenattr_reader generates an accessor

In Ada the private part of the specification package is there for the compiler to see (it needs to know the size of the object), but the user cannot access the fields there. To hide the representation completely, the type is declared as a pointer and the structure it points to is defined in the body.

In C++ the destructor is called automatically when the object’s lifetime ends and gives back the memory allocated on the heap. They run in the reverse order of creation. In Java a similar order is seen in try-with-resources:

try (Res r1 = new Res("A"); Res r2 = new Res("B")) {
    System.out.print("body ");
}
open A open B body close B close A

Parameterized ADTs

You want to write a stack that does the same job whatever the element type is. A parameterized ADT (generic class) takes a type parameter:

LanguageSyntax
Adageneric type Element is private; package Stack is ...
C++template <class T> class Stack { ... };
Java 5+class Stack<T> { ... } (reference types only; Integer, not int)
C# 2005+class Stack<T>
List<Integer> xs = new ArrayList<>();
xs.add("five");
error: no suitable method found for add(String)

C++ templates generate separate code for each different type (instantiation at compile time). Java generics use a single piece of code and the type information is erased after compilation (type erasure); that is why you cannot write new T().

Encapsulation for large programs

An ADT encapsulates a single type. In a system with thousands of classes, you need to group related types and subprograms, compile them separately and prevent name clashes.

  • C: a header file (.h, interface) + an implementation file (.c). The compiler does not guarantee that the two files are consistent.
  • C++, C#: namespace. Qualified names like std::cout or using.
  • Java: package. A package is both a namespace and a unit of access: members without a modifier are visible inside the package.
  • Ada: packages can be nested and compiled separately.
  • Ruby: module.

Common mistakes

WrongCorrect
ADT = any structThe representation must be hidden
Hiding is only for securityThe real gain: user code doesn’t change when the representation changes
peek() removes the elementIt only reads
Resources are closed in the order they were openedIn reverse order
C++ class is default publicprivate; struct is public
In Java private = specific to the objectSpecific to the class; another object of the same class can access it
No assignment for an Ada private typeThere is; not for limited private
List<int> is valid in JavaList<Integer>; the generic parameter is a reference type

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

State the two conditions that a user-defined type must satisfy to be an abstract data type.

Show answer
the representation is hidden from users; the type and its operations are declared in a single syntactic unit

(1) Information hiding: the representation (internal structure) of the type's objects is hidden from the units that use it, and the user accesses it only through the defined operations. (2) The declaration of the type and the protocols of its operations are gathered in a single syntactic unit (class, package); other units can create variables of this type.

Question 2easy

What is printed in Java?

IntStack s = new IntStack();   // push, pop, peek on an int array
s.push(3); s.push(7); s.pop(); s.push(2); s.push(9);
System.out.println(s.pop() + s.peek());
Show answer
11

The stack is LIFO: [3] → [3, 7] → pop 7 → [3] → [3, 2] → [3, 2, 9]. pop() returns 9, the stack is [3, 2]. peek() is 2 (does not remove). 9 + 2 = 11.

Question 3medium

What is printed in Java?

IntStack t = new IntStack();
for (int i = 1; i <= 4; i++) t.push(i * i);
int sum = 0;
while (!t.isEmpty()) { int v = t.pop(); if (v % 2 == 0) sum += v; }
System.out.println(sum);
Show answer
20

1, 4, 9, 16 go into the stack. They come out in reverse order: 16, 9, 4, 1. The even ones are 16 + 4 = 20. The user code never saw the array inside; it only used push, pop, isEmpty.

Question 4medium

What is printed? Queue2<T> wraps a LinkedList with enqueue (add at end), dequeue (remove from front) and size.

Queue2<String> q = new Queue2<>();
q.enqueue("a"); q.enqueue("b"); q.dequeue(); q.enqueue("c");
System.out.println(q.dequeue() + q.size());
Show answer
b1

The queue is FIFO: [a, b] → dequeue a → [b] → [b, c] → dequeue b, remaining [c]: size 1. The result is "b" + 1 = "b1". Queue2 is a parameterized (generic) ADT: the same code works with any element type.

Question 5easy

Does this Java code compile? Why?

class Account {
    private int balance = 100;
    public int getBalance() { return balance; }
}
// in another class:
Account a = new Account();
a.balance = 5000;
Show answer
No: balance has private access in Account

A private member can be accessed only from inside the class. Changing it directly from outside is prevented: information hiding. The account balance staying valid (for example not going negative) can be guaranteed by the class's own methods. An accessor (getBalance) is used for reading.

Question 6medium

In what order are the messages printed?

// Res(String n) prints "open n", close() prints "close n"
try (Res r1 = new Res("A"); Res r2 = new Res("B")) {
    System.out.print("body ");
}
Show answer
open A open B body close B close A

Resources are opened in the order written, and when the block ends they are closed in reverse order. This is similar to C++ destructors being called in the reverse order of constructors: the one created later may depend on the one created earlier, so it is cleaned up first.

Question 7medium

Does this compile? Which ADT feature does it illustrate?

List<Integer> xs = new ArrayList<>();
xs.add("five");
Show answer
No; parameterized (generic) ADTs are type checked at compile time

List<Integer> is a parameterized ADT instantiated with Integer. Adding a String is caught at compile time: "no suitable method found for add(String)". Raw collections before Java 5 held Object, and the error would appear only at run time, during a cast.

Question 8easy

In C++, what is the default access of members of a class and of a struct?

Show answer
class: private; struct: public

In C++ the only difference between class and struct is the default access. Three access levels: private (only the class and friends), protected (also subclasses), public (everyone). In Java, if nothing is specified, package access (classes in the same package) applies.

Question 9hard

In Ada, what is the difference between a private type and a limited private type declared in a package specification?

Show answer
private: users get assignment and equality; limited private: no built-in assignment or equality

In Ada an ADT is defined with a package: the specification package holds the interface, the body package holds the implementation. With a private type the user can use :=, = and /= but cannot access the internal structure. With limited private these are not available either; only the operations the package provides can be used. For types containing pointers, the package must determine the meaning of copying.

Question 10medium

What is printed in Java?

static final class Money {
    private final int cents;
    Money(int c) { cents = c; }
    Money add(Money o) { return new Money(cents + o.cents); }
    int cents() { return cents; }
}
// in main:
Money m1 = new Money(150);
Money m2 = m1.add(new Money(75));
System.out.println(m1.cents() + " " + m2.cents());
Show answer
150 225

Money is an immutable ADT: the field is final and private, add returns a new object and does not change m1. Note: o.cents is the private field of another Money object, but it is valid because it is accessed from inside the same class. In Java access is at the class level, not the object level.

Question 11hard

What is printed in Java?

List<Integer> nums = new ArrayList<>(List.of(4, 1, 3));
List<Integer> view = Collections.unmodifiableList(nums);
try { view.add(5); }
catch (UnsupportedOperationException e) { System.out.println("UnsupportedOperationException"); }
nums.add(5);
System.out.println(view.size());
Show answer
UnsupportedOperationException then 4

view is an interface that offers only read operations on the same list; modification operations throw an exception. But the data was not copied: when nums changes, view changes too (4 elements). This is exactly the separation of interface from representation.

Question 12easy

Name the encapsulation construct for large programs: (a) in C++ and C#; (b) in Java; (c) in Ada.

Show answer
(a) namespaces (b) packages (c) packages

In large programs, encapsulations larger than an ADT are needed to prevent name clashes and to compile related code together. C++ namespace and using, C# namespace, Java package and import (which also gives package access), Ada packages (which can be nested). In C this job is done with header files (.h) and separately compiled .c files.

Verification: The stack/queue ADT outputs, the try-with-resources closing order, and the immutable class and unmodifiable view examples were run with the JDK (java verify/SEN2001/W10.java). The private access and generic type error examples were attempted to compile with javac and gave the expected compile errors (verify/SEN2001/w10b/W10b.java, W10c.java). The C++ and Ada examples are conceptual; their compilers are not installed.

Review card

abstraction: show only the important features. process (subprogram) + data (ADT) ADT conditions: (1) representation HIDDEN (2) type + operations in ONE unit advantages of information hiding: reliability, easy to change, fewer name clashes interface ≠ implementation accessor: controlled access with get/set. constructor creates, destructor (C++) cleans up destructor/closing order is the REVERSE of creation (try-with-resources: B then A) Ada: specification package + body package. private: := and = available; limited private: not C++: class default private, struct public. friend grants access Java: private, protected, public, (none) = package. access at CLASS level C#: also property (get/set syntax), struct = value type parameterized ADT: Java generics, C++ template, Ada generic package List<Integer> add("five") → COMPILE error larger encapsulation: C++/C# namespace, Java package, Ada package, C .h + .c