CSE 331 Java Q & A This handout will list frequently encountered questions about the Java language. This page will focus on issues that fall between purely syntactic issues and deep conceptual issues. (Please see a Java reference for the former and your TA or the Assignment specification for the latter.) If you have a Java language question not easily answered by a Java reference, please look here! As with the Assignment specification, you may also want to refer to this page occasionally to be warned about potentially confusing issues in advance. Contents: Classes Inheritance of static members Access keywords and how to use them (private, public, etc.) Adding your own fields, methods, or constructors Constructors Implicit call to super(...) when instantiating a subclass. Calling other constructors Methods Calling static methods Callbacks Arrays Creating and using arrays Using toArray() to populate a typed array Strings Strings and null-termination Hash tables Hash tables and hash codes Interfaces Comparable vs Comparator Classes Inheritance of static members Q: How are static methods dispatched? A: You can invoke static methods in two ways. You can invoke methods by ⟨Classname⟩.⟨method-name⟩ or by ⟨object-reference⟩.⟨method-name⟩. You should always call methods using ⟨Classname⟩.⟨method-name⟩ because in Java, static methods are dispatched by compile-time type (whereas non-static methods are dispatched by run-time type). For example, suppose we execute the following code where Foo extends SuperFoo and they both implement a static method named staticMeth():
SuperFoo myFoo = new Foo();
myFoo.staticMethod();
Because static method calls are resolved by compile-time type, in this case, SuperFoo's implementation of staticMethod() will be invoked. This is because the compile-time type of myFoo is SuperFoo. The lesson to be learned here is to always invoke static methods by ⟨Classname⟩.⟨method-name⟩: either calling SuperFoo.staticMethod() or Foo.staticMethod(), depending on the desired behavior. Q: So when I subclass another class, do I inherit its static methods too? A: Inheritance of static members works in funny ways in Java. If you subclass another class, you will inherit the superclass' static members. However, the subclass does not have its own distinct copy of the static variable; the superclass, and all its subclasses share the same copy. This turns out to be a bit of a pain if you specifically want static methods which access individual static variables for each subclass in a hierarchy. You have to overload every single static method and redefine every static variable in every subclass, somewhat negating the point of inheritance. If you don't do this, every time you attempt to update the static it will update for every class in the hierarchy. For example, the following code:
public class Test {
public static int _myInt = 1;
public static int foo() {
return _myInt;
}
}
public class Test2 extends Test {
public static int foo() {
_myInt++;
return _myInt;
}
}
public class TestDriver {
public static void main(String args[]) {
System.out.println(Test.foo());
System.out.println(Test2.foo());
System.out.println(Test.foo());
}
}
produces the following behavior:
> java TestDriver
1
2
2
This is because the superclass and all of its subclasses share the same copy of all static members. Access keywords and how to use them (private, public, etc.) Q: So what's the deal with the access keywords? What do private, public and protected mean and when should or shouldn't I use them? A: The access modifiers determine the visibility of class constructors/methods/fields. A brief description of the different keywords: public - all classes are able to view this constructor/method/field. protected - only subclasses and other classes in the package are able to view this constructor/method/field private - this constructor/method/field is not visible outside of this class. The default access in java is "package visible (default-access)". This access level grants package-only access to the constructor/method/field. You should always be careful to not expose more of your implementation than necessary, and you should always keep in mind the design trade-offs involved in using each level of visibility. If you're still not sure about when each type of visibility is appropriate, please ask your TA. Adding your own fields, methods, or constructors Q: Can I add my own fields, methods, or constructors to a class if it's not in the provided specification? A: You may not change the specification. However, since the specification only includes whatever is visible to other classes, you may freely add private fields, methods, and constructors, but not public, protected, or package-visible ones. Constructors Implicit call to super(...) when instantiating a subclass. Q: I get this error when I try to compile ElementaryRoute (which is a subclass of Route). What does it mean?
cannot resolve symbol
symbol: constructor Route()
location: class hw2.Route
public ElementaryRoute(GeoSegment gs) {
^
A: When you instantiate a subclass in java, you must either call a super(...) constructor (with or without arguments), or Java will implicitly call the zero-argument constructor of your superclass. If no such constructor of the superclass exists, you will get a compile-time error. Keep in mind that if you do invoke super(...), it must be the first statement in your subclass's constructor. Also, if you define a class but declare no constructors, an implicit zero-argument constructor (which does nothing) is created for you. However, if you declare any constructors, there is no implicit zero-argument constructor. Calling other constructors Q: Why doesn't it work when I call another constructor in my class to augment a constructor? i.e., when I'm writing the Foo() constructor, why doesn't the statement Foo(0); work? A: Constructors are not methods. Note that you normally call them via the new keyword. It's often useful to call another constructor to augment your current constructor, so Java provides a special syntax: this(params);. In the above example, you would use the statement this(0);. Note that calling of other constructors is also restricted to the first line of your constructor. Methods Calling static methods Q: I've already done import java.lang.Math. Why is it that I still have to type Math.random();? Why doesn't using just random(); work? A: Java forces you to always type out the class for a static method to avoid namespace issues, where multiple classes may have methods of the same name. (The exception to this rule is when the method resides in the current class or an ancestor class.) The import statement merely makes it so you don't have to type out the full package path of a class. Indeed, in this case, all java.lang.* classes are imported by default, so the statement is extraneous. Callbacks Q: What is a callback? Suppose that my code calls a library routine, foo(). Usually, the library code executes until a return statement, and then control returns to my program. Here is ASCII art of the flow of execution (time flows to the right). Where there is a line, code is executing. Where there is blank space, code is not executing.
my code -------foo(x) ------------->
\ /
library code -------return
The library might call some other library, too:
my code ----foo(x) ---->
\ /
library code ---bar() ----return
\ /
other library ----return
A callback is when the library calls a function in *my* code, before finishing its work. We could draw it like this:
my code ----foo(x) ---->
\ /
library code ----bar() ----return
\ /
my code ----return
But it is more customary to draw it like this:
my code ----foo(x) ----return ---->
\ / \ /
library code ----bar() ----return
Two examples of callbacks are hashCode() and equals(). When I define a class, I define these methods, so they are my code. When I call HashMap.put, the implementation makes a callback — it invokes hashCode() and equals() in my code — before returning. Arrays Creating and using arrays Q: How do I create and use arrays? A: Arrays in Java are objects, but they obey some special rules as well. You can allocate arrays with the new keyword and with specifying the array size in brackets:
Foo f[] = new Foo[20];
The length of an array is stored in a public final (read-only) field:
int size = f.length;
You cannot directly resize an array. In order to grow an array, you must create a new one of the desired size and copy the old elements over. You can copy the elements with System.arraycopy(). An example:
Foo temp[] = f; // 'temp' now points to the old array.
f = new Foo[f.length*2]; // Make f a new array that's twice as large.
System.arraycopy(temp,0,f,0,temp.length); // Copy old values back over.
Please see what the Java Language Specification has to say about arrays, and speak with a TA if you're still confused. Using toArray() to populate a typed array Q: So I have a java.util.List (Vector, ArrayList, LinkedList, etc.) that I know is populated by a bunch of Foo objects. Is there an easy way to get a Foo[] array without stepping through the whole array and casting? A: Yes. The java.util.List interface defines a toArray(Object[]) method which takes as an argument, the array into which the elements of this list are to be stored, if it is big enough; otherwise, a new array of the same run-time type is allocated for this purpose. Because we pass in the array with that was created with the correct run-time type, it is safe to cast the return value directly to the typed array. For example:
//Vector fooVect is populated with Foo objects
Foo[] fooList = new Foo[fooVect.size()];
fooList = (Foo[])fooVect.toArray(fooList);
Strings Strings and null-termination Q: I'm trying to split a string by inserting a '\0' character, and it doesn't seem to work. A: Java handles strings differently from C/C++. Strings are not simply terminated by a null character. You should refer to the specification of the String class when working with strings. Hash tables Hash tables and hash codes Q: What is a hash table, and how do I write a good hash code? A: A hash table is basically a dictionary where, given a key object, you can look up a value object. Hash tables use a hash function to hash the key into a hash code representing an entry in the hash table. Keys which are the same must always hash to the same hash code, allowing you to find the associated entry in the hash table. Because the hash code is a mere offset in memory, you can retrieve data associated with any key in O(1) (constant) time! (This is as opposed to O(lg(n)) time for a binary search on a list sorted by key.) (In Java, the hash code you generate can be any integer, but, based on the capacity of the hash table (which is maintained automatically), Java will take the modulo of your hash code to map it to an entry in the hash table.) However, there is a many-to-one mapping from keys to hash codes, so knowing a hash code does not necessarily give you a single entry. Also, if many keys map to the same hash code, we'd have to search through all the keys associated with that hash code to find our desired entry; this would deny us our constant time search. (In the worst case, all keys might map to the same hash code, resulting in a simple list.) Thus, we want to try to write a hash function which tries to prevent collisions, mapping different keys to different hash codes whenever possible. The basic tenets of hash tables: You must never have two entries in a hash table with the same key, since if you did, it wouldn't know which one to return to you when you provide it with a key. (With Java's hash tables, attempting to insert a new entry with a key that's already in the hash table will delete the old entry.) Any information used in your .equals() method to compare keys must never change while that key is in use in a hash table. Otherwise, you will no longer be able to find your entry. (For instance, if you use a Date object as a key, you must be sure not to modify it, or it will no longer correspond to the hash code under which it's stored.) Your hash function must be designed such that keys which are the same (i.e. keys which are .equals() to each other) return the same hash code. This is so you can always find your entry. Your hash function should be designed such that keys which are not the same return different hash codes as often as possible. The fewer collisions you have, the faster your program will run. About good vs. bad hash functions: Let's say your key is an ordered pair, (x,y). A worst case hash function would always return, say, 1. A better hash function might return x+y. In that case, (1,4) and (2,3) would still hash to the same code. An even better hash function would be x+1123*y, which would reduce the number of probable collisions even more. (We multiply by a prime number so our efforts aren't negated when Java takes a modulo. There's a lot more nuance regarding hash functions that are beyond the scope of this Q & A.) Hash tables are implemented via the HashMap class. The older Hashtable class is still around, but is slower, and you should use HashMap for most purposes. Please see the spec for Object.hashCode(), which explains the restrictions for your implementation of hashCode(). While you're at it, please see the spec for Object.equals(Object), which explains the restrictions for your implementation of equals(). You must be sure to implement equals() correctly or your class will not work properly as a hash table key. This is a very precursory primer to hash tables. If you would like to know more about them, please speak with your TA. Interfaces Comparable vs Comparator Q: What are Comparator and Comparable and when should I use them? A: In Java, we frequently come across situations where two Objects must be compared to each other, such as when needing to implement sorted data structures (just like in getBallsFromSmallest()). The Java standard library has two methods for automatically comparing Objects together in these data structures: Comparable and Comparator (in each case, T is the class that you want to compare). While these both achieve the same thing, they go about it different ways. Comparable is an interface that you can add to any class you have edit access to (or, more accurately, any non-final class if you're ok with inheritance). The Comparable interface adds a single method, compareTo(), which each of your instantiations of T gets. Whenever one of your Ts gets added to a sorted collection, its compareTo() method is called against all other Ts it is compared against. Good times to use Comparable are when your class must always compare against itself the same way, or you don't want to define an entirely new class just to run comparisons. However, it does require editing the original T class, which is not always possible (or, with the case of Ball, recommended). In those cases, a Comparator must be used. The Comparator interface is an interface designed to create a type of class called a function object. Function objects are a trick of using interfaces to basically gain some degree of first order functions (a function that can be passed as an argument). So that's what a Comparator is. How do you implement it? Unlike Comparable, when you use Comparator you make an entirely new class, containing only the method compare(T first, T second), which works just like compareTo works. In the case of getBallsFromSmallest(), you would make a new class called something like BallSorter that implements Comparator, and just use a regular compare() method like you would use compareTo if you were using Comparable. Then, when you construct a sorted data structure, you can simply pass the Comparator in the constructor and the data structure will use that class's compare() method to perform comparisons. Of these two ways of comparing objects, which is the best? If you have edit access to the class and don't want to go through the hassle of creating a one-time-use class that contains only a single method, use Comparable. If you can't or shouldn't edit the class (such as with Ball), or there are multiple meanings of "comparison" that are all valid and applicable at different times, use a Comparator and pass it to your data structure when you construct it.