| Part 1 : True and False | |
| a. | False. All three statements are incorrect. The first tries to set the size of the array while declaring it; this is allowed in other programming languages, but not in Java. On the left side of the equals sign, you must simply state the data type, the variable name, and empty square brackets (one set for each dimension which the array will have). On the right side, you must either use initializers (curly braces which list the elements for the array), or you must use the keyword new and the data type, with a value which sets at least the initial dimension of the array. The correct statement would look like this: int arr [] = new int[3][3];The second statement fails to list the data type when the keyword new is used (compare the example above). The third statement tries to combine use of the keyword new with use of initializers - this also is not allowed. You may use one or the other, but not both in the same declaration. |
| b. | True. Any variable or method declared private in a class may not be accessed outside of that class, regardless of whether the other class is a subclass, a separate class in the same directory or package, or both. Private means private. |
| c. | False. Be careful of this type of mistake - it's very subtle. Suppose we create a Dog class, and then create the Poodle class to extend Dog. We can then say that a Poodle "is-a" Dog, but we don't know that any Dog will necessarily be of type Poodle. Thus, we can make a Dog reference and assign it to point to a Poodle object - a superclass type of variable/reference pointing to a subclass object. However, we CANNOT make a Poodle reference and simply point it to any Dog object (a subclass type of variable/reference pointing to a superclass object) - the Dog might be of a different type (Lab, or Newfoundland) which doesn't fit a Poodle object. To set the Poodle reference to point to a Dog object, we first must make sure that the Dog IS really an instance of Poodle; then we can type-cast it and the assignment will work. |
| d. | False.
Variables 'x' and 'z' DO point to the same object; but variable 'y'
points to a separate object which happens to have its instance variables
set to the same values as the first object. Keep in mind that these
references hold a memory address, which indicates where in memory the
actual object is located. Saying 'Object z = x;' copies
the reference held in variable 'x' into 'z'; however, at no time is
'y' copied to either of these variables, so the statement (x
== y) will
test false. |
| e. | False. This is known as overloading methods, when a class has multiple methods which differ only in their arguments. Interestingly, changing the access type ( public, private, etc.) or return type is NOT enough to make the distinction. You may use the same number and types of arguments as long as there are multiple data types and their sequence is different (i.e., the following is legal):
public void method1(int value, String text)...
public void method1(String text, int value)...
However,
listing the same number of arguments and types in the same sequence is
not allowed.
|
| f. | False. Characters in a String are not accessed using square brackets ('[ ]') - these are used only for arrays. They must be obtained using the charAt() method, with the appropriate arguments included in parentheses. The correct statement would be: System.out.println( s.charAt(s.length() - 1) ); |
| g. | True. Prior to Java 1.5, the loop would crash on the first time through with an IllegalArgumentException. Vectors can only store objects, and Math.random() returns a primitive type double. However, as of Java 1.5 automatic wrapping and unwrapping of primitive types (autoboxing) is performed, so this code will now work. |
| h. | False. In fact, you can declare a class abstract simply by including the word abstract in the class declaration and have NO abstract methods inside it, instead including other non-abstract instance methods. However, if your class does have even one abstract method, or inherits an abstract method which it does not define, then the class must be declared abstract. |
| Part 2 : Short Answer | |
| a. | Describe
two distinct uses of the keyword this in Java. The keyword 'this' can be used to call a different constructor in the SAME class, by passing the appropriate required arguments inside parentheses. It can also be used to access variables and methods inside the same object, by listing the keyword, followed by a dot and the item you wish to access. |
| b. | Explain the difference between overloading and overriding a method. The difference can be explained by where the versions of a method are located. When you overload a method, you will have multiple versions of it in the same class, and they must be differentiated by the types, number, and sequence of arguments (see Part 1, (e) above). When you override a method, you will have one version in a superclass, and a modified version in a subclass. In this case, objects of the subclass type can use its more specific version when the method is called, regardless of the type listed for the reference variable pointing to the object. |
| Part 3 : Programming Problem | |
| a. |
In this
section we're working with BaseballPlayer as a superclass, and
Pitcher and Hitter as subclasses. You are given the BaseballPlayer class,
and asked to write the subclasses. Remember that everything that is in
BaseballPlayer WILL BE INHERITED by the subclasses. Therefore, you won't
again define name or yearOfBirth. We can assume there is a 0-param constructor
in the BaseballPlayer class, since there are no other constructors written;
we aren't explicitly calling it in the subclasses. Therefore, your subclasses
need only to add the specific new variables for each player type, write
the constructor to set everything, and add the requested method.
NOTE that the subclass methods are NOT listed as abstract in the superclass,
so we will need to deal with this if we use superclass reference variables
(and we will do so below)!!
public abstract class BaseballPlayer
{
protected String name;
protected int yearOfBirth;
public String getName() { return name; }
}
class Hitter extends BaseballPlayer
{
private int atBats, hits;
public Hitter (String n, int ab, int h)
{
name = n;
atBats = ab;
hits = h;
}
public double battingAvg ()
{
if (atBats > 0) return (double) hits / atBats;
else return 0.0;
}
}
class Pitcher extends BaseballPlayer
{
private int gamesWon, gamesLost;
public Pitcher (String n, int gw, int gl)
{
name = n;
gamesWon= gw;
gamesLost = gl;
}
public double winningPercentage()
{
if (gamesWon+gamesLost > 0) return (double) gamesWon / (gamesWon+gamesLost) ;
else return 0.0;
}
}
|
| b. |
This section asks you
to create an ArrayList to store objects of both subclass types, so we
should create the ArrayList to be of the superclass type. We are specifying
the type using Java generics, placing that type in angle brackets
in the declaration AND in the call to the constructor. Once we have the
list created, we can then add the specified objects to it.
ArrayList < BaseballPlayer > redSox = new ArrayList < BaseballPlayer >();
redSox.add (new Pitcher("Jon Lester", 18, 9));
redSox.add (new Hitter("Dustin Pedroia", 100, 40));
redSox.add (new Pitcher("Josh Beckett", 15, 12));
|
| c. | I've
included the entire main program below, with the declaration of the
ArrayList and the loop to print. However, as I noted above, we have
the slight problem that our ArrayList is of type BaseballPlayer, and
the BaseballPlayer class doesn't know about the winningPercentage()
method in Pitcher, or about the battingAvg() method in Hitter. In order
to call the method, we need to associate the object with
a variable of the correct subclass type. At the same time, if we try
to assign (cast) a subclass type to the wrong subclass reference variable
type, we will have a ClassCastException thrown, so we must test before
we make this kind of assignment. We can test an object's type by using the operator instanceof and asking if the object matches the datatype. Every object knows internally by which constructor (from which class) it was created, regardless of the reference variable type that it is assigned to. We thus test by using a simple if condition: if(<object> instanceof <type>)... NOTE that the type is NOT a String and is not in quotes (and neither one is really in angle brackets)! Once we have determined the correct subtype, we can cast the object (correctly!) to that type and assign it to a subclass-type variable, then call the method. You'll notice that this is what is being done in the for loop below.
class P3
{
public static void main (String [] args)
{
ArrayList < BaseballPlayer > redSox = new ArrayList < BaseballPlayer >();
redSox.add (new Pitcher("Jon Lester", 18, 9));
redSox.add (new Hitter("Dustin Pedroia", 100, 40));
redSox.add (new Pitcher("Josh Beckett", 15, 12));
for (BaseballPlayer player : redSox)
{
if (player instanceof Pitcher)
{
Pitcher p = (Pitcher) player;
System.out.println(p.getName() + " has winning percentage of "
+ p.winningPercentage());
}
else
{
Hitter h = (Hitter) player;
System.out.println(h.getName() + " has a batting average of " +
h.battingAvg());
}
}
}
}
|
| Part 4 : Debugging a Recursive Method | |
| When
we write recursive methods, we need to pay attention to two aspects.
We must be able to identify a "base case", where we know the answer
without further calculation and can simply return it. We then additionally
need to identify a way to take more complex situations of the problem
and simplify them until we can eventually get to the base case. When we have a recursive method that won't end, we should look for either of two problems: on one hand, we may not have correctly identified the base case. If not, it won't be correctly reached and/or won't return the base case result. On the other hand, we may not have taken all more complex cases into account, and provided a way that each one can move toward the base case. Let's now look at the code for this method:
// Adds up the digits of the given number
// For example: digitSum(3456) returns 3+4+5+6 = 18
public static int digitSum (int n)
{
if (n > 10)
{ // the base case (small number)
return n;
}
else
{ // the recursive case (large number)
return n % 10 + digitSum (n/10);
}
}
Did you find the
error above? In the else clause, each time we go through the method,
we will split off a digit to add to the running total (created by the
stack of method calls), then recursively call the method with a smaller
version of the problem (n / 10 ). However, in the base case, the 'if'
tests for numbers greater than 10, not less than 10. Therefore, any number
the user enters which is greater than 10 is simply returned "as is" -
the result of calling digitSum( 54321) would return 54321. However, if
a single digit is passed to the method, the else retrieves the remainder
of division by 10 (so far so good), but then calls the method with the
number divided by 10, which will be 0. Zero is not greater than 10, so
the else executes repeatedly, and we have a Stack Overflow.What we really want to have happen is that we split off digits (the else, or recursive case) while the number is greater than 10, and once we are down to a single digit, we can just return that digit and be done. To fix the method, then we need only to reverse the comparison operator in the if so that it reads: if( n < 10 ) and the program will work properly. |
|