Quiz Yourself

Try more intermediate and advanced test questions.

May 15, 2019

Download a PDF of this article
More quiz questions available here

If you’re a regular reader of this quiz, you know that these questions simulate the level of difficulty of two different certification tests. Those marked “intermediate” correspond to questions from the Oracle Certified Associate exam, which contains questions for a preliminary level of certification. Questions marked “advanced” come from the 1Z0-809 Programmer II exam, which is the certification test for developers who have been certified at a basic level of Java 8 programming knowledge and now are looking to demonstrate more-advanced expertise.

Question 1 (intermediate).

The objective is to apply the static keyword to methods and fields. Given the following classes:


public class Test {
    int result = -1;    
}

public class CodeTest extends Test {
    public static void main(String[] args) {
        // line n1
    }
}

What two lines added independently at line n1 will make the code compile successfully? Choose two.

  1. result = 0;
  2. this.result = 0;
  3. super.result = 0;
  4. new Test().result = 0;
  5. new CodeTest().result = 0;

Question 2 (intermediate).

The objective is to use abstract classes and interfaces. Given the following:


interface Text {
    default String getContent() { return "Blank"; }
    void setContent(String txt);
    void spellCheck() throws Exception;
}

abstract class Prose {
    public abstract void setAuthor(String name);
    public void spellCheck() {
        System.out.print("Do generic prose spellcheck");
    }
}

Class Novel extends Prose implements Text {
  // line n1
}

Which two fragments added simultaneously at line n1 allow the following code to compile and run? Choose two.


Novel n = new Novel();
n.spellCheck();
  1. public void spellCheck() throws Exception { }
  2. String getContent() { return "Novel"; }
  3. public String getContent() { return "Novel"; }
  4. public void setAuthor(String a) { }
  5. public void setContent(String txt) { }

Question 3 (intermediate).

The objective is to declare and initialize variables (including casting of primitive data types). Given the following primitive variable declarations:


char c = '1';
int i = 2;
long l = 3L;
float f = 4.0F;
double d = 5.0D;

Which of the following compile? Choose two.

  1. f = d;
  2. l = f;
  3. i = c;
  4. f = l;
  5. i = f;

Question 4 (advanced).

The objective is to submit queries and read results from the database, including creating statements, returning result sets, iterating through the results, and then properly closing result sets, statements, and connections. Given the following WAREHOUSE table:


ID   TITLE        QUANTITY
——————————————————————————
0    Cell Phone   10
1    Computer     20
2    TV           30

And the following code fragment:


Connection conn = … // properly initialized connection
Statement stmt = conn.createStatement
(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet results = stmt.executeQuery("SELECT * FROM WAREHOUSE");
while (results.next()) {
    int id = results.getInt(1);
    if (id == 1) {
         results.updateInt(3, 15); // line n1 
         results.next();
         break;
    }
}
            
results.previous(); // line n2
int qty = results.getInt(3);
System.out.print(qty);

Assume the database connection is properly initialized, any JDBC features used are supported by the driver and database, and any modes not explicitly shown are default.

What is the result? Choose one.

  1. Runtime exception at line n1
  2. Runtime exception at line n2
  3. 15
  4. 20

Question 5 (advanced).

The objective is to use java.util.concurrent collections and classes, including CyclicBarrier and CopyOnWriteArrayList. Given the following CBTest class:


import static java.lang.System.out;
public class CBTest {

    private List<Integer> results = 
      Collections.synchronizedList(new ArrayList<>());

    class Calculator extends Thread {
        CyclicBarrier cb;        
        int param;
        Calculator(CyclicBarrier cb, int param) {
            this.cb = cb;
            this.param = param;
        }

        public void run() {
            try {
                results.add(param * param);
                cb.await();
            } catch (Exception e) {
            }
        }
    }

    void doCalculation() {
        // add your code here
    }

    public static void main(String[] args) {
        new CBTest().doCalculation();
    }
}

Which code fragment when added to the doCalculation method independently will make the code reliably print 13 to the console? Choose one.

  1. 
    CyclicBarrier cb = new CyclicBarrier(2, () -> {   
        out.print(results.stream().mapToInt(v ->  v.intValue()).sum());
        });
    new Calculator(cb, 2).start();
    new Calculator(cb, 3).start();
    
    
  2. 
    CyclicBarrier cb = new CyclicBarrier(2);
    out.print(results.stream().mapToInt(v -> v.intValue()).sum());
    new Calculator(cb, 2).start();
    new Calculator(cb, 3).start();
    
    
  3. 
    CyclicBarrier cb = new CyclicBarrier(3);
    new Calculator(cb, 2).start();
    new Calculator(cb, 3).start();
    cb.await();
    out.print(results.stream().mapToInt(v -> v.intValue()).sum());
    
  4. 
    CyclicBarrier cb = new CyclicBarrier(2);
    new Calculator(cb, 2).start();
    new Calculator(cb, 3).start();
    out.print(results.stream().mapToInt(v -> v.intValue()).sum());
    
    

Answer 1.

The correct options are D and E. This question investigates a small part of the rules by which names are resolved, the distinction between static and nonstatic variables, and access to variables from a static context. Here, we present a simplified description of some of the rules laid out in Java Language Specification sections 6 and 15. Our discussion will be nowhere near as complete as those sections, in either scope or detail, but we aim to present a perspective that has sufficient scope to answer this question and others like it. The approach presented is sound as far as it goes, but it does have limitations. Undoubtedly some readers will have knowledge way beyond the intended audience of this question, and they will likely be able to see exceptions to this description.

Every variable in Java exists in a context. The same is true of methods, although this discussion will mostly focus on variables. That context can be a method, an object (an instance of a particular class), or a class.

Variables declared inside methods have a method context and are often called either a method-local variable or an automatic variable. This question isn’t really concerned with variables in method contexts.

Variables declared inside classes—but outside of methods—are in either a class or an instance context. If such variables carry the static modifier, they exist in a class context. If such variables do not have the static modifier, they exist in an object context. With the object context, a different variable that has the same name exists in every object of the same type. With the class context, just one variable exists and it’s considered to be part of the class.

Given this, it’s perhaps not a surprise that to address any variable, the context in which it exists must also be identified. This identification can always be done explicitly, but it is often done implicitly.

If you have a class such as:


public class Car {
    public static int MAX_SPEED = 105;
    public int speed = 10;
}

And two objects created from this class:


Car c1 = new Car();
Car c2 = new Car();

Then the variable speed is an instance variable, and it must be referred to in the context of an instance of the class Car. Therefore, provided the variables c1 and c2 are in scope, you can write code in terms of c1.speed or c2.speed, but you cannot refer to Car.speed. This is because both c1 and c2 refer to contexts that contain a variable called speed, whereas the Car context does not. It’s also worth noting that the contexts referred to by c1 and c2 each contain independent variables that simply share the same basic name. This, of course, allows you to model multiple cars that each has its own speed.

By contrast, the variable MAX_SPEED exists in the context of the class Car. Therefore, the proper way to refer to it is Car.MAX_SPEED. Perhaps unfortunately, because the compiler knows that c1 and c2 are of the type Car, it’s also possible to refer to the exact same variable—that is, Car.MAX_SPEED—as either c1.MAX_SPEED or c2.MAX_SPEED. The syntax of those latter two is widely discouraged, but it is likely to show up on an exam precisely because it’s potentially confusing and a competent programmer must not be confused by it. Notice that c1.MAX_SPEED and c2.MAX_SPEED look like different variables, but they’re not; they’re just aliases for Car.MAX_SPEED, and that ambiguity is a bad thing when it comes to creating understandable code.

In a few paragraphs, we’ll discuss the context called this. At the risk of getting ahead of ourselves, know that if this exists, it too can be used as a prefix for a static variable. This is the same as using an instance prefix, and it’s at least as ugly. If this paragraph confused you, ignore it for now; keep going and revisit this paragraph after you’ve read the discussion on this that comes later.

Up to this point, explicit contexts have been discussed. Implicit contexts and the meaning of this and super, which bridge explicit and implicit contexts, also need to be considered.

Let’s start with implicit contexts. An implicit context means there’s no prefix in front of a variable name. In this case, the compiler must work out where to find the variable from one of three possible contexts.

First, the compiler checks if there’s a method local variable in scope with that name. If one is found, that’s what’s accessed. Local variables always win.

If there’s no local variable, the compiler looks for class or instance variables, starting at the most specific class definition and working up through the class hierarchy. Only one or the other can possibly exist in one class declaration; otherwise, the code won’t compile.

If this search finds a static variable, that variable is used. Implicitly, the compiler has determined that the context is that of the enclosing class.

However, if the search finds an instance variable, the context must be the value known as this. However, this exists only in an instance (nonstatic) method. Instance methods must be invoked with a context, and that context becomes the value of this inside the method. Consider the following code fragment:


public class Car {
    private int speed;
    public void setSpeed(int s) {
        speed = s;
    }
}

And the following code:


Car c1 = new Car();
c1.setSpeed(55);

Inside the code of setSpeed, when the call to c1.setSpeed(55) is executed, the object referred to by the variable this is the same object that was referred to by c1 at the moment of the call. That is, the context of the method call is c1, and that same context has been embedded inside this in that particular method invocation.

A static method cannot access an instance variable using this prefix, either implicitly or explicitly.

Now, the use of an implicit context—the implicit use of this—makes sense only if there is a value for this. And because that value comes from the instance prefix used for the method invocation, it’s perhaps not a surprise that there is no value called this in a static method. As a result, a static method cannot access an instance variable using the this prefix, either implicitly or explicitly.

As a side note, you could be forgiven for thinking that the ugly syntax mentioned above—using an object prefix to invoke a static method—might create a this context in the static method, but it does not. This is another reason the syntax is considered ugly and to be avoided. When a static method is invoked in this way, the compiler simply takes the type of the variable prefix and discards the value. Of course, the syntax used to call the method is not known at the time the method is written, nor could you guarantee that all invocations would use this ugly form. Therefore, the call format cannot sensibly affect what is or is not permitted inside the method. The bottom line is that a static method does not have the use of this either implicitly or explicitly.

Another note is that it’s common to hear people say “static methods cannot access instance variables.” That’s not accurate; they absolutely can do so, provided they have an explicit object context and that context is not this. (The normal rules of access control apply too; so, if the variable in question is in an instance of another class, it can’t be private and it might need to be public.)

Moving on, there’s a variation on the explicit context this, which is super. The super keyword means “the object referred to by this, but viewed with the parent type.” Logically then, if this doesn’t exist, neither does super. And if this exists (in other words, if you’re inside an instance method), this and super are equal in value, but different in type. The type of super is the type of the immediate superclass of the type of this.

Now that you’ve reviewed the background, you should be in a good position to evaluate the options and decide on the right answers.

The method that surrounds line n1 is a static method. Because of that, you know that no this context is available—and neither is a super context. This fact is key in answering this question.

The code of option A makes an unqualified reference to a variable called result. The compiler will look for a method local variable of that name but fail to find one. Because of that failure, the compiler proceeds to look for a static variable in the context of the class CodeTest but again fails. Because the method is static, no this context is available, so the search in the class CodeTest has failed. The search then repeats up the parent class hierarchy (in the class Test and then in Object), but, of course, it fails again. Even though Test has a field called result, there’s still no this context from which to access an instance. Therefore, the code fails to compile and option A is incorrect.

As a side note, paying close attention to the way that search was just described reveals that it’s possible to have a static variable and an instance variable with the same name in the same object, but only if they are declared at different points in the hierarchy. Of course, it is probably evident that this is very bad practice, because it will cause confusion that will make maintenance harder. Just because syntax is legal doesn’t make it good.

In option B, the code tries to make explicit use of the this context. But the enclosing method is still static, so no such context exists, the code cannot compile, and option B is also incorrect.

Option C trades the this explicit context for the super explicit context, but it was already established that the code is in a static method, so neither this nor super contexts exist, and the code also fails to compile. Therefore, option C is also incorrect.

In option D, the code creates an object of the type Test. This is successful, because the class Test is accessible and the class has an accessible zero-argument constructor (that’s the compiler-generated default constructor, in this case). This newly created object satisfies the need for an explicit object context for the variable result, and the code compiles. Therefore, option D is correct.

Option E does almost the same thing as option D, but it creates an instance of CodeTest rather than the base class Test. What matters, however, is that the CodeTest object forms a context that does contain a variable called result. Consequently, the code compiles, and option E is also correct.

As a final note, let’s consider the accessibility of the variable result in this question. It’s not specified explicitly and, therefore, it is the default—which means accessible from code within the same package. The code shown doesn’t indicate a package, so how can you know if the access succeeds? There are three considerations that might be applied here.

First, you could consider that you see all the code and, therefore, both classes are in the default package. In this case, the access would succeed.

Second, see the exam guidelines and expand the “Review Exam Topics” section, which explicitly states the following:

Missing package and import statements: If sample code does not include package or import statements, and the question does not explicitly refer to these missing statements, then assume that all sample code is in the same package, or import statements exist to support them.

Third, there’s no possible way for the first three options (A, B, and C) to compile, but if you assume that the two classes are in the same package, they could provide an answer that’s believable. Given a question that requires two selections, along with two believable answers and three impossible ones, you have no meaningful choice but to select the two believable ones.

Answer 2.

The correct options are D and E. The class Novel does not carry the modifier abstract. It is therefore a concrete class, and this requires that all the abstract methods it inherits, whether directly or indirectly, must have concrete implementations or the code cannot compile.

All the methods defined in an interface (in Java 8) are public, and they are abstract, default, or static. Any method that is not explicitly marked as either default or static is abstract. This means that the interface declares two abstract methods, which are setContent and spellCheck. These, therefore, must be implemented before the Novel class satisfies the requirements for being concrete. The interface additionally provides a default method called getContent. Although this is not considered a concrete method, it is not abstract either, and it’s acceptable for a class that claims to be a concrete implementation of this interface to have no mention of this method.

At this point, you know that Novel must still provide or obtain concrete implementations for setContent and spellCheck (and that these implementations must provide the correct argument lists). Let’s move on and consider what the abstract class brings to Novel.

The abstract class Prose declares one abstract method, setAuthor. The Novel class must obtain or provide a concrete implementation for this. At this point, you’re looking for concrete implementations of three methods to satisfy the needs of Novel. However, Prose also provides a concrete implementation of spellCheck, and the argument list exactly matches the same-named abstract method in the interface. This method satisfactorily resolves the requirement that the Novel class obtain or provide an implementation of the abstract spellCheck method declared in the interface. That leaves you with a need for two concrete methods to be implemented in the Novel class. These are setAuthor and setContent. These are the two answers in options D and E, which are the correct answers.

A related question is whether a method that does not throw exceptions can satisfy a requirement for a method that does.

Now, let’s explore why the incorrect answers are incorrect. Let’s start by considering why option A is incorrect. The interface declares an abstract method spellCheck that throws Exception in its signature. Is the spellCheck defined in the Prose class sufficient for this? It turns out that, yes, it is. It’s completely OK for an implementation of an interface method to be inherited from elsewhere in the class’s hierarchy, so the method can come from a parent class or from a default method in another interface.

Another related question is whether a method that does not throw exceptions can satisfy a requirement for a method that does. The answer to this, too, is yes. Just because a method declares an exception does not mean it will throw the exception; it means only that it might. Something that never actually throws the exception is OK. The converse, however, is not OK. A concrete method that declares checked exceptions that are not declared (either exactly or using a superclass) by an abstract method cannot correctly provide the implementation of that abstract method.

Option B will not compile. The method that it attempts to override is declared (and given a default implementation) in the interface, and even though no access modifier is provided, it is implicitly public. An overriding method may not reduce the accessibility of the method it overrides. Given that the method declared in option B has default access, which is less accessible than public access, compilation fails. Therefore, option B is incorrect.

A further observation on option B, from an exam-taking perspective, is that it’s not necessary to make the code compile correctly. So even if it compiled, selecting it would “use up” one of the two options that you can select, and because both options D and E are necessary, if you chose option B, you’d end up missing one of the options that are required.

Finally, option C presents code that would compile but is not necessary. The method getContent is already provided by the default implementation in the interface. And as with the discussion on option B, if you selected option C, that would prevent you from selecting option D or option E; therefore, you should not select option C and it is incorrect.

As a side note, in Java 9, it’s permissible to provide a private concrete method in an interface. However, for abstract methods, the rules have not changed; they are always public. Keep in mind that for the time being, the certification exams are still written for Java 8.

Answer 3.

The correct options are C and D. Generally, Java permits assignments of primitive types based on whether they’ll work reliably. Therefore, if the entire range of values that can be represented by the type of a given expression can also be represented by the type of a given destination type, the assignment from the expression to the destination type is permitted. The converse also applies: If an expression can represent values that are outside the range of the type of a destination, such an assignment is rejected.

Let’s look at the ranges of the primitive types:

Ranges of the primitive types

Some things should be pretty clear:

  • Boolean cannot be assigned to or from a numeric expression.
  • For the main integer numeric types, assignment is possible from smaller to larger; that is, from byte to short to int to long.
  • For the floating-point types, assignment from float to double is possible.
  • Assignment from any of the integer types to either of the floating point types looks good also.
  • float and double do some magic to be able to store a vastly bigger range in the same amount of storage.
  • Perhaps a little surprising is the fact that assignment between short and char cannot be performed in either direction. This is because a char can represent values greater than the maximum value of a short (65535 > 32767) and a short can represent negative values, which are not representable by a char.

These rules are more than sufficient to answer this question, although before we dive into the answers, we’ll mention that the formal specification of these rules (and more) is described Java Language Specification sections 5.1.2 and 5.1.3.

Let’s look at the options. Option A tries to assign a double value to a float. From our table, it’s clear that the double can represent a much greater range than the float, so the assignment is not permitted, compilation fails, and you can conclude that option A is incorrect.

Of course, you know that the actual value stored in the double is small enough to be represented properly by the float and, in general, if you are confident that the value about to be assigned does not overflow the capacity of the destination, you can use a cast to persuade the compiler to let you perform the assignment. In this case, the resulting code would look like this:


f = (float)d;

However, no cast is included in the code shown, so this does not alter the fact that option A fails to compile.

Assignments that might result in a completely wrong value—based on the ranges representable by the types—will be rejected by the compiler, but you can force the compiler’s hand by using a cast.

Option B tries to assign a float to a long, but it’s clear from the table that the range of values representable by a float vastly exceeds that of a long; therefore, the assignment is refused, compilation fails, and option B is incorrect.

Note that the objection in option B is the loss of gross value, not the potential for loss of precision. You just saw that the float can represent a much greater range than the long, and because of that, the assignment is rejected. But imagine that you have a float that contains 3.14, and you cast and assign that to a long. The result would be 3, and the fractional part would be lost entirely. This, and some situations like it, might be called “loss of precision.” Although it’s not evident or even possible here (because the float-to-long assignment—and, actually, assignment of any Java floating-point primitive type to any Java integer primitive type—is always rejected due to loss of range), you’ll see later that Java permits assignments that might simply lose some precision.

Option C assigns a char value to an int. Every value that can be represented by a char can be represented perfectly by an int. Therefore, the result is reliably accurate, the compiler permits it, and option C is correct.

Option D assigns a long to a float. The range of a float is much greater than the range of a long, and the compiler allows this. Therefore, option D is also correct. It might seem odd that a long has 8 bytes of effective storage and a float has only 4 bytes. That might seem like option D should be rejected. But again, the issue here relates to loss of precision, not loss of gross value. This will be investigated further in a moment.

Option E attempts to assign a float to an int, but as the table shows, the range of a float is vastly greater than that of an int and, therefore, the attempt is rejected by the compiler and option E is incorrect.

At this point, you’ve seen that assignments that might result in a completely wrong value—based on the ranges representable by the types—will be rejected by the compiler, but you can force the compiler’s hand by using a cast. A cast operation is safe if you’re sure the value will fit.

You’ve also seen that an assignment that risks losing only precision, not gross value, is permitted. The rules described in Java Language Specification sections 5.1.2 and 5.1.3 are concerned with what’s permitted and what might happen, not with understanding the consequences. At this point, you’ve not seen how an assignment could cause the issue raised here. So now let’s look closer at it and as a side thought consider how a 4-byte float value can store a larger range than an 8-byte long value.

It’s easy to understand losing the fractional part when you assign (through a cast) a float to an int, but what does it mean to “lose precision” when you assign, for example, a long to a float? The answer lies in how the float manages to have a wider range than the long, despite using half the storage.

Floating-point numbers in Java (in fact, in most computer languages) don’t actually count “units” in the way that integer values do. Instead, they count in a variable chunk size. Although it’s possible a value is counted in ones, it might be counted in sixteenths or five-hundred-and-twelfths. This behavior is how these types of numbers handle fractions. But similarly, the number might be counted in chunks of two hundred and fifty-six. This is how these numbers can have much greater ranges than might be expected.

In essence, the floating-point number’s storage is split into two parts. One part stores the number of “chunks” (this is called the significand) and the other part indicates the size of each chunk (this part is called the exponent). Wait—isn’t an exponent the thing after the E in a number such as 3E+12? Yes, in effect, it is. When you write 3E+12, you’re saying “three chunks” where each chunk is worth one followed by twelve zeros. You could equally say 314159E-5, which would be the same as 3.14159 without having a “fraction” and with having only the information that each unit (of which there are 314,159) has a value of a one-hundred-thousandth of 1.

With that background on the floating-point number representation, let’s get back to the idea of loss of precision. It turns out that for a float value in Java, the significand (the “chunk count”) uses 24 bits, which means that the largest value it can represent is 224, which is 16,777,216. Notice this is much less than the largest value of an int and very much less than the upper limit for a long. If you try to use a float value to store a number bigger than this, the chunk size must be increased. Initially the count is in twos, and then after 16,777,216 twos, the count is by fours.

There are a couple of immediately demonstrable consequences to this. First, if you try to assign to a float the literals 16,777,216; 16,777,217; 16,777,218; and 16,777,219, you see that it takes on the values 16,777,216 (accurate); 16,777,216 (rounded down by one); 16,777,218 (correct); and 16,777,220 (rounded up by one). You can try this easily by casting the literals to float, and then casting them back to ints, like this:


System.out.println((int)((float)(16777216)));
System.out.println((int)((float)(16777217)));
System.out.println((int)((float)(16777218)));
System.out.println((int)((float)(16777219)));

Another effect that is perhaps even more significant is what happens in this loop. Try to guess how many times this prints incrementing. Then copy the following code and find out if you were right.


float count = 33554432;
while (count < 33554435) {
    System.out.println("incrementing");
    count = count + 2;
}
 

Of course, double numbers have essentially the same behavior, although the boundary numbers are different and, in fact, a double can accurately represent the full range of values of an int (it has a 53-bit significand, which has much greater range than a 32-bit int).

The point here is that loss of precision means just that: When you assign an int to a float or a long to either a float or a double, you might end up with an approximation of your original number. And this approximation might have practical consequences for your code.

If you’re interested in more detail, Java (at least in strictfp mode) uses IEEE 754 floating-point representations and a Wikipedia page provides more details.

This discussion is now complete, right? Well, no. Look back at that table of data type ranges. The second column is entitled “effective storage.” Why is that significant? Why not simply use the title “storage”? It’s significant because the storage space used for a variable isn’t specified by the language or the virtual machine specification.

For example, on modern 64-bit processors, it’s possible that the machine cannot efficiently address single bytes or perhaps even 4-byte words. In this situation, a particular implementation is free to allocate more storage than is strictly needed for the data.

What the specifications do mandate is that the integral data types must behave as if they are “two’s complement binary numbers” with the specified amount of storage. For floating-point, the specification mandates that the behavior must be exactly compliant with the IEEE 754 specification only if the class or method carries the modifier strictfp. The bottom line is that although the previous table describes what the numerical behavior will be, you cannot assume that allocating 1,000 int variables will reliably allocate 4,000 bytes of memory. This discrepancy can be even more startling with Boolean values. It’s possible that an array of 64 Booleans might be packed into a single 8-byte word, but it’s also possible (though perhaps not very likely) that each individual value might take 8 bytes. The reality is likely somewhere in between; but crucially, neither the language specification nor the virtual machine specification mandate this behavior.

Answer 4.

The correct option is D. This question investigates several aspects of the behavior and the use of result sets. One aspect is the operations that are permitted based on the configuration of the statement that produces the result set. Another is the sequence of operations necessary to update a table through a ResultSet.

The createStatement(...) method exists in three overloaded forms. The form used in this question accepts two int parameters that define the “result set type” and the “concurrency,” respectively.

The result set type parameter is selected from three possible values:

  • ResultSet.TYPE_FORWARD_ONLY: This specifies that the result set may be traversed only in a forward direction and traversed only once. (This is the default that’s implied if the zero-argument overload createStatement() is used.)
  • ResultSet.TYPE_SCROLL_INSENSITIVE: This specifies that the result set permits arbitrary traversal, backward and forward, and absolute positioning. With this mode, if some other process performs updates on the underlying table, they will not be visible through this result set object.
  • ResultSet.TYPE_SCROLL_SENSITIVE: This specifies the same traversal freedom of the previous mode, but if data in the underlying database is updated during navigation, those changes will be visible through this result set object.

It is true that not all JDBC drivers support all types of result sets, but the question tells you to assume that everything used is supported. Therefore, there’s no reason to expect the mode configuration to cause a problem. And, as noted, the ResultSet.TYPE_SCROLL_SENSITIVE type permits forward and reverse navigation through the result set, so line n2 can be expected to complete without raising an exception. Because of this, you can see that option B is incorrect. (If the mode had been configured as ResultSet.TYPE_FORWARD_ONLY, option B would have been correct.)

The second parameter passed into the createStatement(...) method defines the concurrency type of the future result set. It may accept two values:

  • ResultSet.CONCUR_READ_ONLY: This is the default mode (when you create a statement without customization), and it allows only reading of the data from the result set.
  • ResultSet.CONCUR_UPDATABLE: This allows updating of the database through the result set.

As with the previous parameter, in practice, a driver or database might not support the updatable concurrency mode, but the question specifies that you can assume all modes are supported. Therefore, you can expect the update call at line n1 to complete without an exception. Because of this, you can see that option A is incorrect. (Again, note that if the mode had been configured as ResultSet.CONCUR_READ_ONLY, line n1 would throw a java.sql.SQLException exception, and option A would have been correct.)

Having determined that the code runs successfully and, therefore, generates some output from the print statement, let’s consider what will be printed. It seems clear that the programmer’s intention was that the output should be 15; however, it won’t be that because the code fails to use the ResultSet object properly.

Often, when a row in a result set is updated, multiple individual fields are changed, and with the result set, this must be done using multiple calls to updateXxx methods. If each of these calls committed a change to the persistent storage, it would be expensive in terms of multiple writes and it would unnecessarily create transactionally inconsistent partial changes. Because of this, the updateXxx methods modify only the row data in memory—even though the connection is in auto-commit mode—and the accumulated changes must be written to persistent storage under the explicit control of the programmer. The process of updating a row through a result set is completed by using a call to the updateRow() method on that result set, like this:


results.updateRow();

If this call were included right after line n1, the change to the table would be persisted and the result would be 15 (and, consequently, option C would have been correct).

However, in the absence of the call to updateRow(), the changes to the result set are not persisted, and option C is incorrect. As a result, the output is 20 and option D is correct.

One final note is that in this question it does not matter whether you use ResultSet.TYPE_SCROLL_INSENSITIVE or ResultSet.TYPE_SCROLL_SENSITIVE because the changes are not being performed by another thread or process. With the question as written, no changes are made anyway, and even with the addition of the call to updateRow(), the changes are visible to this result set, because they’re made through it.

Answer 5.

The correct option is A. This question investigates some of the core features of the java.util.concurrent.CyclicBarrier class, including its purpose and use.

The CyclicBarrier class is a feature of the java.util.concurrent package and it provides timing synchronization among threads, while also ensuring that data written by those threads prior to the synchronization is visible among those threads (this is the so-called “happens-before” relationship). These problems might otherwise have been addressed using the synchronized, wait, and notify mechanisms, but they are generally considered low-level and harder to use correctly.

If multiple threads are cooperating on a task, two problems must commonly be addressed. (Note that other issues might exist, too). One problem is that the data written by one thread must be read correctly by another thread when the data is needed. Another problem is that the other thread must have an efficient means of knowing when the necessary data has been prepared and is ready to be read. The major feature of the CyclicBarrier is to provide timing synchronization using a barrier point.

The operation of the CyclicBarrier might be likened to a group of colleagues at a conference preparing to go to a presentation together. They get up in the morning and go about their routines individually, getting ready for the presentation and their day. When they’re ready, they go to the lobby of the hotel they’re staying in and wait for the others. When all of the colleagues are in the lobby, they all leave at once.

Similarly, a CyclicBarrier is constructed with a count of “parties” as an argument. In the analogy, this represents the number of colleagues who plan to go to the presentation. In the real system, this is the number of threads that need to synchronize their activities. When a thread is ready, it calls the await() method on the CyclicBarrier (in the analogy, this is arriving in the lobby). At this point, one of two behaviors occurs. Suppose the CyclicBarrier was constructed with a “parties” count of 3. The first and second threads that call await() will be blocked. Being blocked means their execution is suspended, using no CPU time, until some other occurrence causes the blocking to end. When the third thread calls await(), the blocking of the two threads that called await() before is ended, and all three threads are permitted to continue execution. (This is the second behavior mentioned earlier.)

A second effect of the CyclicBarrier is that after the block is ended, data written by any of the threads prior to calling await() will be visible (unless it is perhaps subsequently altered, which can confuse the issue) by all the threads that called await() on this blocking cycle of this CyclicBarrier. This is the happens-before relationship and it addresses the visibility problem.

After the threads are released, the CyclicBarrier can be reused for another synchronizing operation—that’s why the class has cyclic in the name. Note that some other synchronization tools in the java.util.concurrent API cannot be reused in this way.

The CyclicBarrier provides two constructors. Both require the number of parties (threads) they are to control, but the second also introduces a new behavior called the barrier action:


public CyclicBarrier(int parties, Runnable barrierAction)

The barrier action defines an action that is executed when the barrier is tripped; that is, when the last thread enters the barrier. This barrier action will be able to see the data writes by the awaiting threads, and any writes by the barrier action will be visible to the threads after they resume. The API documentation states, “Memory consistency effects: Actions in a thread prior to calling await() happen-before actions that are part of the barrier action, which in turn happen-before actions following a successful return from the corresponding await() in other threads.”

Now that you know what the CyclicBarrier does in general, let’s review the code. Each of the options creates a CyclicBarrier and passes it to a thread (created from the Calculator class). Each thread—or each calculator, if you prefer—performs a calculation and then adds the result of that calculation to a thread-safe List that’s shared between the two threads. Note that the ArrayList itself isn’t thread-safe, but the Collections.synchronizedList method creates a thread-safe wrapper around it. After adding the result to the List, the calculator thread calls the await() method on the CyclicBarrier. Subsequently, the intention is to pick up the data items that have been added to the List and print their sum.

For this to work correctly, the summing operation must see all the data written by the calculators and not occur until after the calculated values have been written. The code should achieve this using the CyclicBarrier.

In option B, the attempt to calculate the sum and print the result precedes the construction and start of the two calculator threads. As a result, option B is incorrect.

Option B might occasionally print the right answer; the situation is what’s called a race condition. Although unlikely, it’s not impossible that the JVM might schedule its threads in a way that the calculations are completed before the summing and printing starts. It’s also possible that the data written in this situation might become visible to the thread that performs the summing and printing. However, it’s unlikely at best and certainly not reliable. The output is most likely to be 0 (because the list is empty), but the values 4, 9, and 13 are all possible.

Option D is a variation of option B with swapped lines. Although this looks like the calculations might be executed before the summing and printing operations, the same uncertainty exists. So, although this option is more likely than option B to print 13 on any given run, for the same reasons, all the values are possible. Therefore, option D is incorrect.

Option C is almost correct, but not quite. The CyclicBarrier is created with a “parties” count of three. Three calls to await are made, so the main thread would not proceed until the two calculations are complete. The timing would be correct and the visibility issue would be correctly addressed such that the last line of the option—the line that computes and prints the sum—would work reliably if it were not for one remaining problem with the implementation shown.

Blocking behaviors in the Java APIs are generally interruptible, and if they are interrupted, they break out of their blocked state and throw an InterruptedException, which is a checked exception. Because neither the code of option C nor the body of the doCalculation method into which the code is inserted include code to address this exception, the option fails to compile.

In fact, the await() method throws another checked exception, BrokenBarrierException, and this is also unhandled. However, while you might not know about the BrokenBarrierException, you should definitely know about the InterruptedException because it’s a fundamental and pervasive feature of Java’s thread management model. Because these checked exceptions are unhandled and the code does not compile, option C is incorrect.

In option A, the CyclicBarrier is created with two “parties” (which will be the two calculator threads) and also with a barrier action. The barrier action is the lambda expression that aggregates results from working parties. The two Calculator threads invoke await() after they have written the result of their calculations to the list. This behavior ensures that both writes have occurred before, and the data is visible to, the barrier action. Then, the barrier action is invoked to perform the summing and printing. As a result, it’s guaranteed that both 4 and 9 are in the list before the summing and printing and that they are visible to that operation. Consequently, the output must be 13, and you know that option A is correct.

Also in This Issue

Getting Started with Kubernetes
GraalVM: Native Images in Containers
Containerizing Apps with jlink
New switch Expressions in Java 12
Java Card 3.1 Unveiled
Improving the Reading Experience

Simon Roberts

Simon Roberts joined Sun Microsystems in time to teach Sun’s first Java classes in the UK. He created the Sun Certified Java Programmer and Sun Certified Java Developer exams. He wrote several Java certification guides and is currently a freelance educator who publishes recorded and live video training through Pearson InformIT (available direct and through the O’Reilly Safari Books Online service). He remains involved with Oracle’s Java certification projects.

Mikalai Zaikin

Mikalai Zaikin is a lead Java developer at IBA IT Park in Minsk, Belarus. During his career, he has helped Oracle with development of Java certification exams, and he has been a technical reviewer of several Java certification books, including three editions of the famous Sun Certified Programmer for Java study guides by Kathy Sierra and Bert Bates.

Share this Page