Skip to content

Latest commit

 

History

History
642 lines (456 loc) · 35.5 KB

Chapter_6.adoc

File metadata and controls

642 lines (456 loc) · 35.5 KB

Access Levels, Collections, Generics, and Multi-Threading

I’ll start this chapter with the explanation of how to hide and protect data using so called access level qualifiers. After that we’ll experiment with Java collections - the language elements that can store multiple objects of the same type. Remember arrays and their limitations? Java collections offer alternative ways of storing similar objects. Then you’ll get familiar with the mechanism of generics that allows Java compiler to ensure that you won’t be using the wrong data types with data collections or other objects.

Finally, I’ll give you a very light introduction to parallel computing using multi-threading.

Access Levels

In code samples from previous chapters I’ve been using the keyword public in declaration of member variables and methods. This means that such a variable or method can be accessed by any other code from the project. You can declare a class, a method, or a member variable to be public, private, or protected. If one of these keywords is missing, Java compiler assumes that it’s a package access level - this element can be accessed only from the code located in the same package (directory).

One of the main features of object-oriented languages is encapsulation - the ability to hide and protect data or code. But who do you need to hide or protect the code from? This is not a protection from bad guys who are not allowed to see your code. You hide and protect the code from misuse. For example, another programmer extended your class and is trying to change the value of a variable in your class, which may result in bug.

Let’s consider an example from the real world. Think of a car – most people have no clue how many parts are under the hood, and what actually happens when a driver pushes the brake pedal. If car designers would not hide control of some of the under-the-hood operations, the driver would have to deal with hundreds of buttons, switches and gauges on the car’s dashboard.

fig 6 01
Figure 1. Overwhelmed by the "public user interface"

When you design a class, hide methods and class member variables that should not be visible from outside. Some of the methods of member variables are meant to be used internally and not to be exposed to other classes.

We’ll start learning about hiding and protecting data with the private access level. If a variable or a method are declared private, they can be used only within the class where they were declared. The next code sample represents the class Car and declares some of its members as private. Note that I’ve created this class in the package vehicles.

package vehicles;

public class Car {

 // This private variable is visible only inside this class
  private String brakesCondition;

 // The public method brake calls private methods
 // to decide which brakes to use
  public void brake(int pedalPressure){
    boolean useRegularBrakes;
    useRegularBrakes=
              checkForAntiLockBrakes (pedalPressure);

    if (useRegularBrakes==true){
    useRegularBrakes();
    }else{
    useAntiLockBrakes();
    }
  }

  // This private method can be called inside
  // this class only
  private boolean checkForAntiLockBrakes(int pressure){
    if (pressure > 100){
      return true;
    }else {
      return false;
    }
  }

   // This private method can be called inside this
   // class only
  private void useRegularBrakes(){
   // code that sends a signal to regular brakes
  }

   // This private method can be called inside this
   // class only
  private void useAntiLockBrakes(){
   // code that sends a signal to anti-lock brakes
   //
  }
}

By looking at this code I can say that the class Car exposes only one public method brake, which internally may invoke several other functions that a driver does not need to know about. For example, if the driver pushes the brakes pedal too hard, the car’s computer may apply special anti-lock brakes implemented in private methods.

I took the next screen shot in IntelliJ IDEA while writing a class CarMaster that uses Car. IDEA helps me with suggesting the Car class members that I can use in this context. Only the method brake is visible from the class Car (other methods are from the class Object).

fig 6 02
Figure 2. Can use the brake method only

The private access level is the most restrictive one. If you’re not sure when to use it, just declare all of your call member variables and methods as private, and loosen this restriction if other classes need to use these members.

fig 6 03
Figure 3. Private is the most restrictive access

What do you think will happen if you simply remove the private keyword from one of the methods of the class Car? Will the CarMaster see it? No, it won’t because the absence of the access qualifier gives a package access level to this member. Only classes that are located in the package vehicles can access it, and CarMaster is not one of them.

The class members declared with the protected keyword can be accessed from other members of the same class, from its descendants or classes located in the same package. Even if a descendant class is located in a different package, it’ll be able to access protected methods of its ancestors.

Some software developers are creating libraries or frameworks of classes to be used by other developers. These classes can be extended, and their creators may use the keyword protected trying to allow access to certain member only descendant classes. But in the real world no one can predict what the developer may want to do with their libraries, and the keyword protected may become an obstacle in achieving of their goals. I do not use the keyword protected in my projects. As you gain more experience with Java, see for yourself if the keyword protected brings some value to your programs.

Casting

In Java all classes are directly or indirectly inherited from the class Object, which is the root of the class hierarchy. In Figure 2 you’ve seen a class Car that has number of methods that we’ve never declared - they belong to the class Object, and the class Car extends Object even though we’ve never asked for it. So any of your application classes have an ancestor - Object.

When you declare a variable to represent an instance of your class, you can give it a type either of your class or any of its ancestors. For example, both of the following declarations are correct:

Car myCar = new Car();

Object myOtherCar = new Car();

Java compiler can cast (convert) one data type to another as long they have inheritance relation. In particular, Java compiler can automatically cast the type to the class ancestors. This is called upcasting. But if a variable has the type of a super class, it won’t see any members of the subclass. The next screen shot shows that the variable myOtherCar doesn’t see the method brake declared in the class Car.

fig 6 04
Figure 4. The Object type variable doesn’t see Car’s members

But you can say, "Doesn’t the variable myOtherCar point at the instance of the class Car, which has a public method brake?" That’s right, but since I declared this variable of the type Object the variable myOtherCar assumes that its just a general object. The programmer can downcast the general type to a more specific one, but this has to be done explicitly by placing the specific type of the object in parentheses before the variable of more general type, for example:

Object myOtherCar = new Car();

Car myOtherCarAfterCasting = (Car) myOtherCar;

It’s like you’re saying, "I know that the variable myOtherCar is of type `Object, but it actually points at the Car instance". Now the variable myOtherCarAfterCasting will see the method brake declared in the class Car:

fig 6 05
Figure 5. The Car type variable sees Car’s members

Why do we need all these complications? Can’t we just always declare variables of the specific types? Sometimes we can’t. For example, JDK comes with lots of other classes that were written to work with the Object data types. Data collection classes were written to be able to store instances of any objects.

Creators of data collections had no idea that you might need to store instances of Car or Fish there. But when you use the data collection object in your program, the data type is known. In the next section you’ll see an example of a FishTank program that stores instances of the class Fish in the ArrayList and then casts them back to the type Fish:

theFish = (Fish) fishTank.get(i);

Selected Data Collections

Now let’s see how to work with collections of data. Say you have a hundred songs in your MP3 player. It’s a collection. If you’ll create a class Song you can create a hundred of instances of this class (one for each song) and put it in a one of the special data structures called Java collections.

Java packages java.util and java.util.concurrent include classes that are quite handy when a program needs to store several instances of some objects in memory. There are dozens of collection classes in Java, but I’ll just show you a couple of them. Some of the popular collection classes from the package java.util are ArrayList, HashTable, HashMap, and List.

The package java.util.concurrent has collections useful in programs that require concurrent (simultaneous) access to some data by different parts of a program (by multiple threads). I’ll introduce you briefly to the concept of multi-threading later in this chapter, but the coverage of concurrent collections doesn’t belong to the introductory book like this one.

The Class ArrayList

In Chapter 4 you’ve gotten familiar with Java arrays, which have a limitation - you have to specify the number of array elements during the declaration of an array. But often you don’t know in advance how many elements there are. For example, if you want to write a program that would print all your followers in Twitter, their number may change many times a day. The class ArrayList can give you more flexibility - it can grow or shrink in size as needed. Think of a list of something - a list of songs, a To-Do list, a list of friends names. Pretty much any objects that can be used with the word list can be stored in an ArrayList.

Why use arrays, then? Let’s just always use ArrayList! Unfortunately, nothing comes for free, and you have to pay the price for having a convenience of dynamically sized arrays. The ArrayList works a little slower than a regular array. Besides, you can only store objects there, while arrays allows you to store primitives too. To create and populate an ArrayList you should instantiate it first and then create instances of the objects you are planning to store there. Add each object to the ArrayList by calling its method add. The next little program will populate an ArrayList with String objects and then print each element of this collection.

import java.util.ArrayList;

public class ArrayListDemo {

  public static void main(String[] args) {
    // Create and populate an ArrayList
    ArrayList friends = new ArrayList();
    friends.add("Mary");
    friends.add("Ann");
    friends.add("David");
    friends.add("Roy");

    // How many friends are there?
    int friendsCount = friends.size();

    // Print the content of the ArrayList
    for (int i=0; i<friendsCount; i++){
        System.out.println("Friend #" + i + " is "
            + friends.get(i));
    }
  }
}

This program will print the following:

Friend #0 is Mary
Friend #1 is Ann
Friend #2 is David
Friend #3 is Roy

The method get extracts the element located at the specified position in the ArrayList. Since you can store any objects in this collection, the method get returns each element of the Object type. The program has to cast this object to a proper data type. We did not do it in the previous example only because we stored String objects in the collection friends, and Java knows how to convert an Object to a String automatically.

Let’s see how you can work with some other objects in ArrayList, for example instances of the class Fish shown next.

package pets;

public class Fish {
      private float weight;
      private String color;

    // constructor

    Fish(float weight, String color){
        this.setWeight(weight);
        this.setColor(color);
    }

    // getters and setters

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

Note that the class fields color and weight are private variables. But this class also defines public getters and setters - the methods that read or modify the fields. In this example the setters and getters don’t contain any application logic, but they could. For example, you could encapsulate the logic that checks the credentials of the users of this class so not everyone can modify the weight property. By Java naming conventions the setter name starts with the prefix set followed by the capitalized letter of the corresponding private variable. Accordingly, the getter starts with get.

Note
IntelliJ IDEA can automatically generate setters and getters for the class. Just right-click on the class name and select the options Refactor | Encapsulate Fields.

The code to add (and extract) a particular Fish to the ArrayList collection may look similar to the program FishTank that comes next.

package pets;

import java.util.ArrayList;

public class FishTank {
 public static void main(String[] args) {
     ArrayList fishTank = new ArrayList();

     Fish fish1 = new Fish(2.5f, "Red");
     Fish fish2 = new Fish(5, "Green");

     Fish theFish;

     fishTank.add(fish1);
     fishTank.add(fish2);

     int fishCount = fishTank.size();

     for (int i=0;i<fishCount; i++){

         theFish = (Fish) fishTank.get(i);  // casting
         System.out.println("Got the " +
            theFish.getColor() + " fish that weighs " +
            theFish.getWeight() + " pounds.");
     }
 }
}

First, this program creates a couple of instances of the class Fish passing the values for the fields via constructor. Note the letter f in the weight value of the first fish: 2.5f. In Java all decimal literals have the type double unless you mark it with the suffix f for float.

Each instance is added to the collection fishTank. Then, the program gets the objects from this collection, casts them to the class Fish and prints their values using getters. Here’s the output of the program FishTank:

Got the Red fish that weighs 2.5 pounds.
Got the Green fish that weighs 5.0 pounds.

The ArrayList collection uses Java arrays internally and initially creates an array for 10 elements. But if you keep adding more elements to ArrayList it internally will create another array of the larger size and copy all existing elements there. Because of this additional memory allocations and data copying ArrayList collections works a little slower than arrays, which allocate enough memory in advance.

The Class Hashtable

While the ArrayList collection only allows referencing its elements by index (e.g. fishTank.get(i)), sometimes it would be easier to reference collection elements by names as key/value pairs. I’ll illustrate by storing my friends' contact information in a HashTable collection that allows accessing objects by key names. Let’s declare a simple class ContactDetail, which can store contact details of one person.

public class ContactDetail {
    String fullName;
    String facebookID;
    String phone;
    String email;
}

The program HashTableDemo will create and populate two instances of ContactDetail, will add them to the Hashtable collection by names (the keys), and then will print the phone number of the second contact.

import java.util.Hashtable;

public class HashTableDemo {
    public static void main(String[] args) {

        ContactDetail friend1 = new ContactDetail();
        friend1.fullName = "Jackie Allen";
        friend1.email = "jallen@gmail.com";
        friend1.facebookID = "jallen";
        friend1.phone="212-545-5545";

        ContactDetail friend2 = new ContactDetail();
        friend2.fullName = "Art Jones";
        friend2.email = "ajones@gmail.com";
        friend2.facebookID = "ajones";
        friend2.phone="212-333-2121";

        Hashtable friends = new Hashtable();
        friends.put("Jackie", friend1);
        friends.put("Art", friend2);

        // Cast from Object to ContactDetail
        String artsPhone = ((ContactDetail) friends.get("Art")).phone;

        System.out.println("Art's phone number is " + artsPhone);
    }
}

In this example I used the first name as the key in the method put that adds elements to a Hashtable. Hence one contact can be referred by the key get("Jackie"), and the other one as get("Art"). The method get returns the collection element as the Object type, so I had to cast it to ContactDetail to be able to see the field phone.

fig 6 04 1
Figure 6. Contacts can be programmed with Hashtable

HashTable requires each key to be unique. Say you have another contact named Art and will create a new instance of the ContactDetail object. If you’ll add it to the same collection under the same key - friends.put("Art", friend3); it’ll replace the contact details of the first Art with the data of the second one. This happens because Java Hashtable internally generates a hash key (the number) from your key and uses it as an index to find the element when need be.The same names will generate the same hash keys. So either give the second Art a nickname like ArtFromNYC or use another Java collection like ArrayList to store your contacts. By the way, ArrayList even allows you to store duplicates (objects with the same values) while Hashtable doesn’t.

I’m not going to cover other Java collections here, but if you’d like to do your own research, read about such collections from the java.util package as HashMap, HashSet, and LinkedList. There is also a class Collections that has a bunch of useful static methods to operate on your data collections (e.g. sort, copy, binarySearch et al.).

Introduction to Generics

Java generics is a feature that allows you to create so called parameterized data types. For example, instead of just declaring a collection that can store any data you can pass it a parameter to allow only the objects of certain data types. Instead of declaring and instantiating a general collection to store friends like this:

  ArrayList friends = new ArrayList();

you can do it with a parameter so it can store only String objects like this:

  ArrayList<String> friends = new ArrayList<>();

The parameter(s) goes in the angle brackets right after the data type. Note the so called diamond operator <> on the right. Since you already declared the required data type on the left, there is no need to repeat it on the right - compiler will guess the type. It’s also an example of type inference introduced in the previous lesson about lambda expressions. Now, if by mistake you’ll try to add an object of another type to friends the Java compiler will complain.

I was able to specify a parameter for ArrayList only because it was created with this ability. If you’ll read the online documentation for ArrayList you’ll see that it’s declared as follows:

public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

That <E> means that you’re allowed to specify a parameter type of elements that will be stored in the the ArrayList. In case of the friends collection the Java compiler would see that in this collection <String> should be the <E>. The online documentation for the class Hashtable looks even scarier:

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable

But <K,V> simply means that you can specify two parameters: K is the the key data type, and V for value.

Let’s write a program that illustrates the advantages of using generics. I’ll reuse the example from the previous section called HashTableDemo that stored ContactDetail instances. Actually, I’ll create two new versions of this program. The first one will be called HashTableBrokenDemo, and I’ll show how to break this program so it crashes during the runtime if we don’t use generics. After that, I’ll rewrite it as HashTableGenericsDemo, where I’ll use generics to show how to prevent the runtime errors from happening. Here’s the broken program:

import java.util.Hashtable;

public class HashTableBrokenDemo {
    public static void main(String[] args) {

        ContactDetail friend1 = new ContactDetail();
        friend1.fullName = "Jackie Allen";
        friend1.email = "jallen@gmail.com";
        friend1.facebookID = "jallen";
        friend1.phone="212-545-5545";

        Hashtable friends = new Hashtable();
        friends.put("Jackie", friend1);

        // this is a time bomb
        friends.put("Art", "Art Jones, ajones@gmail.com, ajones, 212-333-2121");

        // Cast from Object to ContactDetail
        String artsPhone = ((ContactDetail) friends.get("Art")).phone;

        System.out.println("Art's phone number is " + artsPhone);

    }
}

The HashTableBrokenDemo adds the first object (for Jackie) of type ContactDetail to the friends collection, but the contact details for Art are added in the form of a String:

"Art Jones, ajones@gmail.com, ajones, 212-333-2121"

Java compiler sees no crime here - the Hashtable can store any descendants of the class Object. But if you’ll run this program, you’ll get an error in the line that tries to cast the collection element to ContactDetail type. This is how my IntelliJ IDEA screen with the error looks like:

fig 6 06
Figure 7. The runtime exception: ClassCastException

The program failed on line 24 with the error ClassCastException. The runtime errors are called exceptions in Java, and I’ll explain error handling in Chapter 10. But my main point is that this program has crashed during the runtime just because I "forgot" that only the objects of type ContactDetail should be stored in the collection friends.

Now I’ll copy the code of HashTableBrokenDemo into the new class called HashTableGenericsDemo. I’ll make a small change there - I will declare the collection friends with parameters:

Hashtable<String, ContactDetail> friends = new Hashtable<>();

Now I’m explicitly stating that my intention is to use the String objects for keys, and ContactDetail objects as values. The program HashTableGenericsDemo is shown next - it won’t even compile!

public class HashTableGenericsDemo {
  public static void main(String[] args) {

      ContactDetail friend1 = new ContactDetail();
      friend1.fullName = "Jackie Allen";
      friend1.email = "jallen@gmail.com";
      friend1.facebookID = "jallen";
      friend1.phone="212-545-5545";

      Hashtable<String, ContactDetail> friends = new Hashtable<>();
      friends.put("Jackie", friend1);

      // compiler will complain about this line
     friends.put("Art", "Art Jones, ajones@gmail.com, ajones, 212-333-2121");

      // No casting from Object to ContactDetail needed
      String jackiesPhone = friends.get("Jackie").phone;

      System.out.println("Jackie's phone number is " + jackiesPhone);
  }
}

The compiler will complain about the line, where I’m trying to call the method put with two String objects as arguments. The Java compiler will generate an error message that it can’t apply two String parameters to a Hashtable that was declared with parameters <String, ContactDetail>.

Another important thing to note is that there is no casting needed when the program gets the information about Jackie’s phone. Now the friends collection knows from the very beginning that it stores not just some Object types, but the ContactDetail instances.

What have we achieved? The program HashTableBrokenDemo allowed us to store anything in the collection but crashed during the runtime, but HashTableGenericsDemo prevented this error from happening. Having a compiler’s error it’s a lot better than getting surprises during the runtime, isn’t it? Besides, with generics we’ve eliminated the need to cast objects.

I’ve been using the parameterized data type Hashtable that was conveniently offered by the creators of Java, but you can define your own classes with parameters too. Creating your own parameterized classes is one of the more advanced topics, and I won’t be covering it in this book. Check out Oracle’s tutorial if interested.

Light Introduction to Multi-Threading

So far, all our programs perform actions in a sequence – one after another. So if a program calls two methods, the second method is invoked only after the first one completes. In other words, such a program has only one thread of execution, where the code works sequentially.

In a real life though, we can do several things in parallel like eat, talk on the phone, watch TV, and do the homework. To do all these actions in parallel we use several "processors": hands, eyes, and mouth.

fig 6 MultiTask

Today, only your grandma’s computer has a single processor (a.k.a. Central Processing Unit or CPU). A CPU performs calculations, sends commands to the monitor, hard disk or a solid state drive, remote computers on the Internet, and so on. Most likely your computer has at least two CPU’s and a GPU (Graphics Processing Unit) for processing graphics if any.

But even a single processor can perform several actions at once if a program uses multiple threads. One Java class can start several threads of execution that will take turns in getting time slices of the processor’s time. For example, the program asks the user a question and waits for the response. Meantime the CPU can be used for performing some other application logic instead of idling as shown in Figure 7.

fig 6 07
Figure 8. Parallel processing

A good example of a program that creates multiple threads is a Web browser. For instance, you can browse the Internet while downloading some files so one program (the browser) runs two threads of execution (browsing and downloading) in parallel.

How to Create a Thread

All Java applications that we’ve created so far (even Hello World) were running in so called main thread. We didn’t need to do any special programming to create a main thread - Java runtime always creates one thread to each program. But if you want your application code to run in parallel thread, you need to write code to request Java runtime to create a separate thread(s) and execute some code there in parallel with the main thread.

There are different ways of creating threads. I won’t be explaining details of each method because it’s a pretty advanced topic and is not a good fit for this book. But I’ll just list some of the ways of creating threads and will illustrate it with one simple example. These are the main methods of writing code to be executed in a separate thread:

  • You can inherit your class from the Java class Thread and override its method run, which should contain the code to be executed in parallel.

  • You can add implements Runnable to the declaration of a class and implement the method run, which, again, should contain the code to be executed in parallel.

  • Instead of inheriting your class from the class Thread you can create the new instance of the Thread object

  • You can add implements Callable to the declaration of your class and implement the method call, which plays a similar role to the method run.

The easiest way of implementing multi-threading is by using lambda expressions that were introduced in Chapter 5. As a refresher, you can use lambdas to implement functional interfaces - those that have only one abstract method. The interface Runnable declares a single abstract method run, which makes it a perfect candidate to be implemented with lambdas.

Let’s learn by example. First, I’ll show you a pretty simple program that executes all code sequentially in one main thread, and then I’ll re-write it to be executed in parallel.

The class SingleThreadedDemo executes two loops sequentially. First, it prints five value of i in power of 4, and then five values of k.

public class SingleThreadedDemo {
  public static void main(String[] args) {

   for (int i=0; i<5;i++){
     System.out.println("The value of i in power of 4 is "
                                             + i*i*i*i*i);
   }

   for (int k=0; k<5;k++){
      System.out.println("*** The value of k is " + k + "!");
   }

  }
}

The first loop simply multiplies the variable i to itself four times. In the second loop I simply concatenate a String, the value of k and an exclamation point. The second loop will start only after the first loop is complete, and no matter how many times you’ll run this program, the output on the console will always look like this:

The value of i in power of 4 is 0
The value of i in power of 4 is 1
The value of i in power of 4 is 32
The value of i in power of 4 is 243
The value of i in power of 4 is 1024
*** The value of k is 0!
*** The value of k is 1!
*** The value of k is 2!
*** The value of k is 3!
*** The value of k is 4!

This was an example of a sequential code execution in a single thread. But the actions that we do in each loop don’t depend on each other and can be executed in parallel, right?

Let’s change this example a little bit. The following class MultiThreadedDemo executes two loops in parallel. It creates a separate thread for the fist loop, and starts it in a separate thread so the main thread, which has the second loop doesn’t wait for the first one to complete.

public class MultiThreadedDemo {
  public static void main(String[] args) {

   Thread myThread = new Thread(          // (1)
        () ->{                            // (2)
          for (int i=0; i<5;i++){
             System.out.println("The value of i in power of 4 is " + i*i*i*i*i);
          }
        }                                 // (3)
      );

   myThread.start();                      // (4)

   for (int k=0; k<5;k++){
    System.out.println("*** The value of k is " + k + "!");
   }
  }
}
  1. We start the program with creating a new instance of the Thread class using its constructor that takes an object that implements Runnable interface.

  2. Writing a lambda expression is the easiest way to implement Runnable. Note that the lambda expression doesn’t specify any parameter. Java compiler is smart enough to guess that since the constructor of Thread expects an instance of Runnable then our lambda expression implements this interface. The for loop automatically becomes an implementation of the method run. This is what we want to run in a separate thread

  3. The lambda expression ends here.

  4. To start the execution of the thread, we need to call the method start, which is defined in the class Thread and will internally invoke the method run, which is implemented as lambda. The method start doesn’t wait for the completion of the method run so the next line in our main method is executed.

If you run the program MultiThreadedDemo several times, it may print the output differently each time depending on how busy is the CPU is on your computer. I ran this program several times and selected the console output that illustrates that the code was not executed sequentially, take a look:

*** The value of k is 0!
*** The value of k is 1!
The value of i in power of 4 is 0
The value of i in power of 4 is 1
*** The value of k is 2!
*** The value of k is 3!
*** The value of k is 4!
The value of i in power of 4 is 32
The value of i in power of 4 is 243
The value of i in power of 4 is 1024

Note that the first two lines print the output from the second loop! This means that after calling the method start on the thread myThread the program didn’t wait for the completion of the first loop, which ran in parallel thread. Then the first loop had something to print. Then the second, etc. The entire console output is a mix of two parallel number-crunching threads.

You may say, is it even a real-world example? Who needs to run such simple loops in parallel? I made this example simple to get across the message that one program can do stuff in parallel.

For a more realistic scenario think of a game with fancy graphics that needs to update the images on the screen and perform some complex algorithms. If such a program would sequentially do the screen updates and algorithms, the game would run slow with noticeable delays in the graphics during times when the program does number crunching. In Chapter 8 I’ll illustrate the effect of the "frozen" screen and how to deal with it using a separate thread for screen updates.

Project: Using ArrayList and Generics

In this exercise I’d like you to try create your own collection that uses ArrayList, generics, private variables and public getters and setters.

  1. Create a new IDEA project named Chapter6.

  2. Create a package named contacts.

  3. In the package contacts create the class ContactDetail that looks like this:

    public class ContactDetail {
        private String fullName;
        private String facebookID;
        private String phone;
        private String email;
    
        public String toString(){
          return "Name: " + fullName +
                 ", Facebook ID:  " + facebookID +
                 ", phone: " + phone +
                 ", email: " + email;
        };
    }

    Note that I’m overriding the method toString that exists in the class Object.

  4. In IntelliJ IDEA right-click on the class name and select the options Refactor | Encapsulate Fields to generate public getters and setters for all fields.

  5. In the package contacts write a program MyContacts that will declare and instantiate an ArrayList called friends it should allow only the objects of type ContactDetail.

  6. Create two or more instances of ContactDetail object and populate them with your friends' data using setters.

  7. Add all instances of ContactDetail to the friends collection by using the method add.

  8. Write a for-each loop that will iterate through the collection friends and print all their contact information by simply printing each object from the collection friends. Since the class ContactDetails has an overriden method toString, you can simply print the objects as shown below - the method toString will be invoked automatically:

    for (ContactDetail friend: friends){
        System.out.println(friend);
    }
  9. The output of the program MyContacts should look similar to this one:

    Name: Jackie Allen, Facebook ID: jallen, phone: 212-545-5545, email: jallen@gmail.com
    Name: Art Jones, Facebook ID: ajones, phone: 212-333-2121, email: ajones@gmail.com