Object Comparisons and Arrays Phil Tayco San Jose
Object Comparisons and Arrays Phil Tayco San Jose City College Slide version 1. 3 Updated Sep. 29, 2019
Arrays Revisited • Recall how Java arrays are created int[] numbers; • This is a variable declaration of an integer array called numbers • When we look under the hood, numbers is also just a reference and in this case, the memory for the array is not yet allocated numbers ? • We have to define how many integers we want to reserve for the array to allocate memory
Arrays Revisited numbers = new int[10]; • This reserves 10 integers in memory • In Java, since the type of the array elements is simple, they are initialized with a given value of 0 numbers 0 x 3320 aad 2 0 0 0 … 0 0 1 2 … 9 • Notice how the memory allocation is done using the “new” keyword. It’s the same notation as creating an object • Arrays are data structures that can also be treated like a class
Array Length • A good example of an Array used like a class is the “. length” property in Java int[] numbers = new int[10]; for (c = 0; c < numbers. length; c++) System. out. println(numbers[c]); • . length is a special property for all Arrays that return the capacity of the Array • The property is public. It is considered okay to be public because it is a fixed value once you allocate memory for an Array • In this example, numbers. length has a value of 10
Fun With Arrays • In the next example, let’s start things off by modeling a Time class public class Time { private int hours; private int minutes; private int seconds; } • The corresponding constructor(s), set/get, and to. String methods should more easily be something you can define now • What are some predicate methods we could add?
Fun With Arrays • Back to arrays. Recall its definition: – An ordered set of elements referred to by the same variable name with each element accessed by an index – Each element is the same data type • The second point is of most significance with classes because remember what a class is: it’s a complex data type! • Just like you can create an array of integers, now you can also create an array of classes… Time[] alarms = new Time[10]; • Arrays of objects need to be treated with care but as seen with other examples, even though the complexity increases, the pattern of use is consistent
Arrays Step By Step Time[] alarms; • As with an Array with simple data types, the declaration of alarms here is only creating the reference with no memory allocated alarms ? • When we are ready to allocate memory for the alarms Array, we state the number of Time elements to reserve • What will the array elements actually look like? • What will the array elements be initialized to?
Arrays Step By Step alarms = new Time[10]; • This will reserve 10 Time references in memory • At this point, each Time element has no memory allocated. Only references are created alarms 0 x 2 d 2 a 1100 ? ? ? … ? 0 1 2 … 9 • Important distinction to make is that we’ve created memory for the array, but each element does not have an actual Time object yet • Any element can have a Time object created at any time but note that also means until that point, the element is pointing to null
Arrays Step By Step alarms[0] = new Time(); alarms[1] = new Time(6, 0, 0); • This creates 2 Time objects in the first 2 elements of the alarms array alarms 0 x 2 d 2 a 1100 0 x 17 f 32 a 20 0 x 44 a 059 f 2 ? … ? 0 1 2 … 9 0 0 6 0 0 0 • Lots of observations to make with this configuration
Arrays of Objects • Once an element of alarms is instantiated, treatment of the object is as normal with the proper Array notation alarms[0]. set. Hours(5); • Array elements are instantiated as normal objects such as using the default constructor or any other overloaded one
Arrays of Objects • Array elements are references so you can set one element equal to another (to be used with care!) alarms[2] = alarms[0]; alarms 0 x 2 d 2 a 1100 0 x 17 f 32 a 20 0 x 44 a 059 f 2 0 x 17 f 32 a 20 0 1 0 0 6 0 0 0 2
Arrays of Objects • The most common error to make with Arrays of objects is to attempt to use member functions for elements that have not yet been instantiated for(int c = 0; c < alarms. length; c++) System. out. println(alarms[c]); • The problem here is that alarms. length returns the capacity of the array and not the actual number of elements that have been instantiated • In this example, alarms. length is 10. If only 3 objects have been instantiated, the other 7 will have null values • Accessing a member function of a null object in Java results in a “Null. Pointer. Exception” – a very common run-time error!
Arrays of Objects • How best to deal with this is to maintain a number. Of. Alarms variable for how many Time objects in the alarms have been instantiated Time[] alarms = new Time[10]; alarms[0] = new Time(); alarms[1] = new Time(6, 0); int number. Of. Active. Alarms = 2; for(int c = 0; c < number. Of. Active. Alarms; c++) System. out. println(alarms[c]); • This also means you must maintain number. Of. Alarms appropriately (instantiations and deletions) as well as the array contents (no “gaps” in the array)
Exercise 6 • In your main program from Exercise 5, add the following exactly in this order – Create an array of Color objects called “crayons” with a maximum capacity of 10 colors – Instantiate 4 different color objects in the crayons array, you can choose any color object values you like – Write a loop that prints the 4 Color objects in the array and uses the Color’s to. String function for each object – Next, add 2 more Color objects to the array and then print the 6 Color objects from the array • You can write a function that prints the Color objects in the array. This is not required for this exercise, but it may be good practice to try as it will help with Exercise 7
Composition with Arrays • Recall the definition of designing classes with Composition – you can have a class with a property that is another class • Since Arrays are classes, it is possible to create a property that is an Array public class Clock { private Time current. Time; private int number. Of. Alarms; private Time[] alarms; } • This is a powerful class design incorporating what we’ve learned that sets up the foundation for advanced OO concepts later on
Composition with Arrays • To learn this type of design, maintain the design processes of what we’ve learned so far • Start with the default constructor public Clock() { current. Time = new Time(); number. Of. Alarms = 0; alarms = new Time[10]; } • number. Of. Alarms is simple and current. Time is an object instantiated with the Time default • For the alarms array, all we did here is allocate the size of the alarms. What would a Clock object now look like?
Composition with Arrays Clock my. Clock = new Clock(); my. Clock 0 x 553 f 12 f 0 current. Time number. Of. Alarms alarms ? ? ? … ? 0 1 2 … 9 0 x 1122 abd 0 0 0 x 73221 ac 4 hours 0 minutes 0 seconds 0 Each Time object alarms in the alarms array has is null. How would we manage Time object allocation?
Composition with Arrays • We could instantiate all 10 Time objects in the alarms array in the default constructor public Clock() { current. Time = new Time(); number. Of. Alarms = 0; alarms = new Time[10]; for (int c = 0; c < alarms. length; c++) alarms[c] = new Time(); } • What would a Clock object now look like? • What are pros/cons with this?
Composition with Arrays Clock my. Clock = new Clock(); my. Clock 0 x 553 f 12 f 0 current. Time 0 x 1122 abd 0 number. Of. Alarms alarms 0 0 x 73221 ac 4 0 x 2233… 0 x 241 a… 0 1 hours 0 minutes 0 seconds 0 0 … hours 0 minutes 0 seconds 0
Composition with Arrays • Benefits are that we are guaranteed at least an object in every Time Array element (reduced chance of Null. Pointer. Exception at run-time) • Cost is memory usage as you may have a Clock object needing only 2 alarms • The “number. Of. Alarms” property also does not align with the number of active Time objects in the alarms array (it is correct conceptually, but not physically) • Guideline is the first approach. In the constructors, only allocate the size of the array for the property • Use other methods to instantiate objects for the array as you need them (more on that later)
Composition with Arrays • Following the guideline, other constructors with class properties that are Arrays should then only focus on the capacity of the Array public Clock(Time c) { current. Time = new Time(c); number. Of. Alarms = 0; alarms = new Time[10]; } • You can also write constructors that take Time objects for alarm(s) • Sticking with the guideline though, you would not want to allow this • This forces the only way to create alarms in a Clock object through other methods…
Composition with Arrays • Set methods typically mean to set the value of a given property. However, alarms is an Array • You can provide a method to create the alarms from a Time array as a parameter public void set. Alarms(Time[] a, int n)… • This requires the user of the Clock object to create a Time array and send it in as a parameter and also send in the number of Time objects active in the array (through n) • While this can be done, this is a good example of putting too much on the programmer • Conceptually, the method of setting an alarm is “adding” one, which we can code
Composition with Arrays public void add. Alarm(Time t) { } • Things to keep in mind for this algorithm – Adding an alarm to the clock conceptually means adding an alarm Time object to the alarms Array – number. Of. Alarms needs to be maintained – Time object is coming in, do we want to use the reference or instantiate a copy of the object?
Composition with Arrays • Here, we will create a new Time object and add it to the alarms Array using the copy constructor public void add. Alarm(Time t) { alarms[? ] = new Time(t); } • The Time object we want to add to the alarms array is instantiated through t • The challenge here is that we now need to figure out where in the alarms array where the new Time object will go. What should go here?
Composition with Arrays Here’s a Clock object with no alarms set. Where should the new alarm go in the alarms array? my. Clock 0 x 553 f 12 f 0 current. Time number. Of. Alarms alarms ? ? ? … ? 0 1 2 … 9 0 x 1122 abd 0 0 0 x 73221 ac 4 hours 0 minutes 0 seconds 0
Composition with Arrays • The correct index element needs to be the next available spot in the Array • If we design the Array such that all elements are next to each other starting at 0, then we already have a property that can also be used to identify the next available open spot in the Array public void add. Alarm(Time t) { alarms[number. Of. Alarms] = new Time(t); } • There’s still a problem with this code though. What is it?
Composition with Arrays What’s wrong with this picture? my. Clock 0 x 553 f 12 f 0 current. Time 0 x 1122 abd 0 number. Of. Alarms alarms 0 0 x 73221 ac 4 0 x 2233… null 0 1 hours 8 minutes 0 seconds 0 … hours 0 minutes 0 seconds 0
Composition with Arrays • number. Of. Alarms must also be maintained. When a new alarm is added, we need to also increment number. Of. Alarms public void add. Alarm(Time t) { alarms[number. Of. Alarms++] = new Time(t); } • Notice the only way Time objects are added to the Array is through this function • Since number. Of. Alarms if maintained this way, we do not (and should not) have a “set. Number. Of. Alarms” method • Control over the Array and number. Of. Alarms is strong because it is managed within the class
Composition with Arrays • What will happen if we’re at capacity for the alarms Array? • We can do a capacity check first public void add. Alarm(Time t) { if (number. Of. Alarms < alarms. length) alarms[number. Of. Alarms++] = new Time(t); } • Note that we use alarms. length instead of a fixed value of 10. Why is this good? • If alarms is full, an attempt to add an alarm does nothing. Should we also print an error message to the user?
Composition with Arrays • What’s the problem here? public void add. Alarm(Time t) { if (number. Of. Alarms < 10) alarms[number. Of. Alarms++] = new Time(t); else System. out. println(“Alarms is full”); } • The logic handling is correct, but printing an error message assumes the program using the Clock object is interacting with the user via console • For a true stand-alone class, the methods should not directly interact with the end user is a specific way like console output • Question is how a capacity error is “reported”
Composition with Arrays public boolean add. Alarm(Time t) { if (number. Of. Alarms < 10) { alarms[number. Of. Alarms++] = new Time(t); return true; } return false; } • The return value communicates back with whatever program that’s using it • This model can be used with other methods and supports a tiered OO design approach • Using system. out. println is still useful for debugging purposes
Comparing Objects • If we can use a method to add an alarm, we’ll need another to remove an alarm • Before looking at though, we need to understand how to compare objects • Comparing simple variables is not hard int x, y; x = 10; y = 10; if (x == y) System. out. println(“Equal variables”); • What happens if we did the exact same thing with objects?
Comparing Objects Time t 1, t 2; t 1 = new Time(10, 15, 30); t 2 = new Time(10, 15, 30); if (t 1 == t 2) System. out. println(“Equal”); • However, this will not result in printing Equal • It is important to see why this happens from looking at memory behind the scenes • Recall that when t 1 and t 2 are instantiated, they are individually assigned locations in memory • Even though the property values within the objects are the same, their locations are different
Comparing Objects t 1 t 2 0 x 4412 ff 20 0 x 17 dc 7248 10 10 15 15 30 30 • The “==“ operation can only compare one value that is at the given variable locations • Notice that with objects, the immediate values compared the memory addresses of t 1 and t 2 – this results in comparing these 2 values as not equal • What we want to happen is for the property values of these objects to be compared • Since classes are complex data types, we have to define how that comparison occurs
Comparing Objects Time t 1, t 2; t 1 = new Time(10, 15, 30); t 2 = new Time(10, 15, 30); if (t 1. equals(t 2)) System. out. println(“Equal”); • The “equals” method is defined in the Time class • It is technically a predicate methodand usually takes its own class as an input parameter (i. e. the Time class “equals” method takes a Time object as input) • Now we need to define the “equals” method
Comparing Objects public boolean equals(Time t) { if (hours != t. get. Hours() || (minutes != t. get. Minutes() || (seconds != t. get. Secondss()) return false; return true; } • Here, we check to see if any of the current Time object property values does not match the given t object’s corresponding property • If any mismatch, the method returns false, otherwise it will return true • This implies another method that should be created when designing any class along with constructors, set/get, and to. String
Composition with Arrays • Now that we have an idea how to compare objects, we can use that in remove. Alarm public boolean remove. Alarm(Time t) { } • Things to keep in mind for this algorithm – Needing to find the alarm in the Array from t – Assuming all elements are next to each other while removing an element cannot leave a “hole” in the Array – Maintaining the number. Of. Alarms property – Using the Boolean return type appropriately • Before coding, we can tackle each of these things conceptually
Composition with Arrays Here’s a Clock with 4 alarms set: my. Clock 0 x 553 f 12 f 0 current. Time hours 0 minutes 0 seconds 0 0 x 1122 abd 0 number. Of. Alarms alarms 4 0 x 73221 ac 4 0 x 2233… 0 xa 314… 0259 a… 0 x 5 bc 2… null 0 1 2 3 4 hours 8 9 9 14 minutes 0 0 30 0 seconds 0 0 …
Composition with Arrays • remove. Alarm algorithm: – We must use a loop to go through each element of the alarms array to see if it equals the given t object – If we find a match, we can remove it and return true. Otherwise, if the loop completes without a match, return false • If we remove an alarm, we cannot leave a “hole” in the Array and we must maintain the number. Of. Alarms property – One approach is to replace the alarm object being removed with the last alarm in the array – We then must also decrement number. Of. Alarms by one
Composition with Arrays public boolean remove. Alarm(Time t) { int c = 0; while (c <= number. Of. Alarms) { if (alarms[c]. equals(t)) { alarms[c] = alarms[number. Of. Alarms-1]; alarms[--number. Of. Alarms] = null; return true; } c++; } return false; } How does this change the previous picture?
Composition with Arrays Here’s a Clock with 4 alarms set: my. Clock 0 x 553 f 12 f 0 current. Time 0 minutes 0 seconds 0 0 x 1122 abd 0 number. Of. Alarms alarms hours 3 0 x 73221 ac 4 0 x 2233… 0 x 5 bc 2… 0259 a… null 0 1 2 3 4 hours 8 9 14 minutes 0 30 0 seconds 0 0 0 …
Composition with Arrays • How can this all look from the Clock object usage perspective? public static void main(String[] args) { Time now = new Time(20, 30); Clock phone. Clock = new Clock(now); Time wakeup. Time = new Time(6, 0); phone. Clock. add. Alarm(wakeup. Time); Time time. To. Drive. To. Work = new Time(8, 0); phone. Clock. add. Alarm(time. To. Drive. To. Work); phone. Clock. remove. Alarm(wakeup. Time); } • Note how the code is more readable and better models the real world concepts versus using more cryptic code symbols • Responsibility for defining how to add and remove an alarm is handled where it should be which is in the Clock class
Composition with Arrays • Consider what other methods could be useful for main to “request” of the Clock object – How many alarms are currently in the Clock? – Does an alarm of a specific time exist? • What about other typical methods? – How would you code to. String in the Clock? – What about the copy constructor and equals methods? • This starts paving the way for larger scale applications with multiple class usage – Thinking about what individual classes have and are capable of providing – Thinking about how users of these classes (main or other classes) will use these objects
Exercise 7 • Create a new project and copy your Car and Color classes from exercise 6 into it • Add a Parking. Lot class that has a name and can contain up to 4 Car objects • Create the following methods for the Parking. Lot class following the exact signatures given – – – public public Parking. Lot() boolean add. Car(Car) boolean remove. Car(Car) int get. Number. Of. Cars. Parked() boolean car. Exists(Car) String to. String() • Create a main program that tests creates a Parking. Lot object and tests all the functions appropriately • Use good test data to test all the logic of your functions
Exercise 7 • Extra credit: Add the following methods – public Parking. Lot(Parking. Lot) – public boolean equals(Parking. Lot) – public void clear() • Create a main program that interactively works with the end user allowing them to do the following functions: – – – Add car Remove car Does a certain car exist Get how many available spaces left Show all parked cars • This is a significant exercise and you will be given extra time to work on this • Extra credit is good practice for the final project
- Slides: 45