Review¶
We have learned a lot this week about object-oriented programming! Some important points:
- How to define our own classes.
- How to manage state (fields) and define behaviors (methods) for those classes.
- How to create instances of the classes we defined.
- How to create abstractions that separate the client from knowing how our class works internally and how to enforce that the client doesn’t get access to our internal representation (private fields).
These are all concepts that can be pretty tricky to wrap your head around the first time you see them since they require a pretty different way to think about the programs than what you are used to.
Class Constants¶
As discussed in class, programs are meant to be read by humans, not by computers. This means it is very important we focus on the readability of our code to some other reader; this reader could be a co-worker or partner but could also be you looking back at code you wrote when trying to solve a critical bug. This means we want our code to convey the semantic meaning of what it is trying to accomplish as clearly as possible.
Take our version of ArrayIntList
for example:
public class ArrayIntList {
private int[] elementData;
private int size;
public ArrayIntList() {
this(10);
}
public ArrayIntList(int capacity) {
...
}
}
If someone else were to read our code, they might ask “Why is the number 10 there? Is there any reason it’s not 9?”. In this program, the number 10 is what we call a magic value or a value in a program that has no inherent semantic meaning. Now we, as the author of this code, know we wanted that number 10 to be the default capacity of the internal array. However, by just hard-coding the number 10 we are not conveying that semantic meaning of “default capacity”.
One solution is to just write a comment indicating the meaning of the value 10. That’s not a bad idea but it doesn’t exactly solve the problem if we had the number 10 appearing more often in our code and we wanted to have the flexibility of changing it if necessary without having to change every instance it appears in the class.
A much better solution is to store this magic value in a class constant with a descriptive name that we use in our code instead of that magic value. For example, our ArrayIntList
would look like this if we added a constant.
public class ArrayIntList {
public static final int DEFAULT_CAPACITY = 10;
private int[] elementData;
private int size;
public ArrayIntList() {
this(DEFAULT_CAPACITY);
}
public ArrayIntList(int capacity) {
...
}
}
Now it is much clearer that we are constructing something of the default capacity!
A few things to mention about class constants:
- They should almost always be declared
public static final
. There may be some cases where you want those to be different, but every constant in CSE 143 should start with all of these modifiers. - The naming convention uses the
UPPER_CASE
format (affectionately known asYELLING_CASE
). - Everywhere we would use the number 10 in this example (that represented the default size) we will now use the constant
DEFAULT_CAPACITY
.
Notice, this program is much more readable even though we only use that constant once! Class constants are even more powerful when you use one in multiple places. If you were to want to change the default capacity, you would only have to change it once at the constant declaration since no other places have the value hard-coded!
Double Vision¶
What if we wanted to write methods for ArrayIntList
that took other ArrayIntList
instances as parameters? This sounds kind of weird at first but can be really helpful!
For example, the ArrayList
class has a method addAll
that takes another ArrayList
and adds all the values in that list to this one. We want to emulate this behavior for ArrayIntList
.
We would want to write the client code so it would look something like the following
public class Client {
public static void main(String[] args) {
ArrayIntList list1 = new ArrayIntList();
list1.add(1);
list1.add(2);
list1.add(3);
ArrayIntList list2 = new ArrayIntList();
list2.add(4);
list2.add(5);
list1.addAll(list2);
System.out.println(list1); // Should print [1, 2, 3, 4, 5]
}
}
We would start by writing a method with the correct header
public class ArrayIntList {
private int[] elementData;
private int size;
// constructors and other methods
// post: adds all the values from other to the end of this list.
// the values will appear at the end in the same order they appear in other.
public void addAll(ArrayIntList other) {
// TODO implement this method
}
}
In the addAll
method, we would have two variables that refer to different instances of the ArrayIntList
class; this
would refer to the same list as list1
while other
would refer to the same list as list2
.
Now we could loop over all the values in other
and add them to the contents of our list. A simple way to do this would be
public void addAll(ArrayIntList other) {
for (int i = 0; i < other.size(); i++) {
this.add(other.get(i));
}
}
What this is doing is using the standard traversal over the other
ArrayIntList
, getting the value at each index, and then calling the add
method (that appends at the end) on this
ArrayIntList
. You are allowed to prepend method calls on yourself with this.
to make it clear which ArrayIntList
you are calling the method on; this is not required because Java will go in and insert the this.
itself (just like it did for accessing fields), but I added it for clarity.
Check your understanding¶
Try solving this problem on Practice-It!
Some lazy pun about “private”¶
The addAll
code above is actually a working implementation and is totally fine. One other thing I want to mention is an extra thing you can do when you take another instance of your type as a parameter: you are allowed to access its private fields!
Here is the same method implemented by accessing the fields of other
directly.
public void addAll(ArrayIntList other) {
for (int i = 0; i < other.size; i++) {
this.add(other.elementData[i]);
}
}
This sounds weird at first since I first said that private
means no one outside can access your fields, but Java has a slight technicality to that statement. To Java, private
doesn’t mean private to things outside this instance, it means private to things outside of this class. While that looks like a tiny wording difference, what it implies is that any instance of ArrayIntList
has the ability to access fields of any other ArrayIntList
it has a reference to because they are of the same class.
This is definitely different to our human notion of the word “private”! We would think of any private state you have as only accessible only to you, but Java would allow anyone of type Human
to access your private fields!
It’s rare that you are required to access private fields of other instances of your type, but it’s good to know that it is a possibility. As we mentioned in lecture, there is always a trade-off between directly accessing fields and calling methods (fields are generally faster, but accessing methods instead leads to better abstractions and can make your code more flexible) so it’s really up to you to figure out what you think is the most readable.