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.
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.
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
).
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.
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.
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
.
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
:
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);
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.
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.
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
.
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.).
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:
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.
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.
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.
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.
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 methodrun
, which should contain the code to be executed in parallel. -
You can add
implements Runnable
to the declaration of a class and implement the methodrun
, 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 theThread
object -
You can add
implements Callable
to the declaration of your class and implement the methodcall
, which plays a similar role to the methodrun
.
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 + "!");
}
}
}
-
We start the program with creating a new instance of the
Thread
class using its constructor that takes an object that implementsRunnable
interface. -
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 ofThread
expects an instance ofRunnable
then our lambda expression implements this interface. Thefor
loop automatically becomes an implementation of the methodrun
. This is what we want to run in a separate thread -
The lambda expression ends here.
-
To start the execution of the thread, we need to call the method
start
, which is defined in the classThread
and will internally invoke the methodrun
, which is implemented as lambda. The methodstart
doesn’t wait for the completion of the methodrun
so the next line in ourmain
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.
In this exercise I’d like you to try create your own collection that uses ArrayList
, generics, private variables and public getters and setters.
-
Create a new IDEA project named Chapter6.
-
Create a package named contacts.
-
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 classObject
. -
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.
-
In the package contacts write a program
MyContacts
that will declare and instantiate anArrayList
calledfriends
it should allow only the objects of typeContactDetail
. -
Create two or more instances of
ContactDetail
object and populate them with your friends' data using setters. -
Add all instances of
ContactDetail
to thefriends
collection by using the methodadd
. -
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 classContactDetails
has an overriden methodtoString
, you can simply print the objects as shown below - the methodtoString
will be invoked automatically:for (ContactDetail friend: friends){ System.out.println(friend); }
-
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