SCJP in a nutshell
I suppose that every Java developer heard about SCJP certificate. Not so many had tried to pass and those who passed can only say that it’s good to have it done and over with. In this article I present a summary of my lecture of guide preparing to pass this examination.
Introduction
SCJP (Sun Certified Java Programmer) examination is one of the most recognizable certified examination offered by SUN. It’s not only a prerequisition to pass other SUN Java examinations but also a proof of Java knowledge on intermediate level. It’s very valuable attribute recognizable all around the world, especially for people who want to develop Java applications.
My experience with learning to pass this exam have started some time ago. As a person, who didn’t finish any course connected to computer science in any way, this exam suppose to be a confirmation of my knowledge and programming skills. My thoughts about successive chapters of guide written by Katie Bates & Bert Sierra I have published on my blog (http://www.chlebik.wordpress.com). However in spite of categorization and transparent entries there was lack of uniform summary of all written hints. Moreover, many mock examinations available on the Web affected my knowledge as well as updated of some hints. By courtesy of JavaExpress editors i have an opportunity to present whole in unified, more complex form than periodical entries on my blog.
This article contains issues, which were problem for me (or raised my doubts) during solving tests from the end of each chapter of the book and mock exams. Hints are sometimes described in details but it’s not a defect at all – it’s always better to know more than less. I have divided all into 10 parts (everyone corresponding to one chapter in a book) and also into a few general advices. Authors of guide have very nice tendency to include in content of chapters funny statements or aphorisms. It’s not only for fun – it’s excellent mnemonic which is worth to assimilate while learning anything. To every part I have added quote (original) of this kind and I hope this make reading enjoyable. Let’s get to work!
General advices:
It’s necessary to give attention to correctness of every piece of code, always! I know it may sound funny but to tell the true some bugs could be avoided through more meticulous listing analysis. To know by experience – working with IDE can lead to slash developer vigilance.
Elementary activities during analysis of received code:
- check correctness of table indexes, especially applied to parameters passed to
main()
method (from command line); - pay attention to import of used classes. This situation surprise me painfully in case of throwing exception (
IOException
probably). It’s necessary to remember (at least this convention is used in a guide) that when program listing start with line number 1 then we can see all the code and we can assume, in all probability, that code is correct. However when we see only code of one method and numbering starts more or less from 5 then it’s necessary to pay attention if there are all needed imports; - check if all instance variables are initiated, even if so then check if there are definite or default values;
- check if name of each variable is correct. It’s necessary to remember that the freak like this ( $___$___$ ) is by all means correct – it will just great come true as variable name;
- casting and the polymorphism. Whether so that the given object can certainly behave in the given way (mainly concerns collections, but somewhere else is also able to surprise)?
- a bit of attention at reading contents of the task, but more specifically understanding what actually we are being asked about. So compile and run it's not the same what "run", nor it's not the same, what "compile". Such catches are often exploited for emphasizing differences between versions of Java (mainly in questions devoted to the compiler).
Chapter 1 – Declarations and access control
"As a Java programmer, you want to be able to use components from the Java API, but it would be great if you could also buy the Java component you want from "Beans 'R Us," that software company down the street."
Loops for are able to be at times deceptive. The potential combination of traps in case of the declaration, or the workmanship is quite sizeable. Let us consider such a case:
for (int __x = 0; __x < 3; __x++);
It is structure as most correct syntactically! The loop is empty, hence the semicolon at the end of the line. Used identifiers are as most legal. As appropriate we can also regard the following instruction:
for( int i = 0, y = 2; i < 10 && y < 11; ++i, y++ ) { System.out.println( " Variable i is: " + i + " and variable y is: " + y ); }
Variables i
and y
were initiated correctly, the second expression in the loop is giving the BOOL
value in the result, whereas after all we are increasing values of both variables. It is worthwhile underlining, that independently of the used operator (++i
or i++
) value of the variable i
will be increasing only after every run of body of the loop.
A structure which enables to send the amount of parameters of the specific type in advance to the unknown method turned up at Java 5. This solution was called var-args and is beautiful straight out with way to become dilapidated not-knowing this structure of the programmer. The example used in the first chapter was explained in more depth in the chapter two however what didn't disturb in placing the question in the first chapter.
static void sifter(A[]... a2) { s += "1"; } static void sifter(B[]... b1) { s += "2"; } static void sifter(B[] b1) { s += "3"; } static void sifter(Object o) { s += "4"; }
Evoking methods, which declarations var-args was mentioned in by definition are being taken at overloading at the all alone ending (unless it is an only parameter). And it is worthwhile for itself sticking this truth to the head. Treating arrays is the second quite essential issue – as everybody knows they are a container of the value of the determined type, however the array alone from itself is also an object! So if using the above example we would evoked the method with the array as the parameter, then to the variable s
was added value "4".
Arrays. It’s not needed about them too lengthily to write, with only that's strange (authors of the guide are keeping an eye on it), there is a possibility of their creating made bizarre. All following instructions are as most correct.
Boolean [] array []; // Creating two dimension array Boolean[] array; // One dimension array Boolean array []; // As above
Chapter 2 – Object oriented programming
"[…] when the JVM invokes the true object's (Horse) version of the method rather than the reference type's (Animal) version—the program would die a horrible death. (Not to mention the emotional distress for the one who was betrayed by the rogue subclass.)"
The polymorphism doesn't concern static methods. Let’s look this code snippet for the example:
public class Tester { public static void main( String[] sd ) { BetaTester t = new BetaTester(); Tester t2 = new BetaTester(); t.doSomething(); t2.doSomething(); } public void doSomething() { System.out.println(1); } } class BetaTester extends Tester { public void doSomething() { System.out.println(2); } }
On the exit of the program we will receive the result: " 2 2 ". Why? Because each time will be invoked overwritten doSomething
method from the BetaTester
class. However now it will be sufficient to change the declaration (in both classes) on static
and in this moment returned values will differ. In case of static methods – the polymorphism isn't in this moment possible.
Polymorphism and overwriting of methods continuation. One should remember about the situation, in which one class extends second is inheriting properties and methods from her (at least it rather typical of the class, truth?). One of methods is overloaded. Whereas the property by default has given other value than she had in the parental class! What results from it? (example from the guide)
public class Tester { public static void main(String[] args) { new Tester().go(); } void go() { Mammal m = new Zebra(); System.out.println(m.name + m.makeNoise()); } } class Mammal { String name = "mammal "; String makeNoise() { return "any noise"; } } class Zebra extends Mammal { String name = "zebra "; String makeNoise() { return "zebra noise"; } }
On the exit of the program we will see "mammal zebra noise". And so how apparently the polymorphism isn't applying overwrite of methods, or theses in other words – in case of overwriting an object type has priority, rather than of reference to it.
During the examination also an acquaintance of these of notions will be useful:
- cohesion – mainly referred to the class. For aims of passing an examination it will be sufficient to know that behind this is an aspiration to creating classes focused closely on one given task;
- coupling – it is an indicator of the relation between classes or modules. Lower coupling, the bigger cohesion, and on the contrary! Smaller relations make more autonomous classes and "not-interfering" in own implementations.
Chapter 3 – Assignments
"Again, repeat after me, 'Java is not C++.'"
Initialization blocks are this issue which he is able quite well to stir it up and after all to lead to loss of points on fundamentally trite questions. Commonly it is obvious we are dividing initiation blocks to static and instance. Static blocks, as the majority of artefacts of the language marked statically – are being run only once – while loading the class by the virtual machine. Instance blocks are being run at forming every new instance of the object. What's the problem? In keeping an eye on two things:
- order of the performance – blocs are being called after calling all constructors (also parents). In case of the appearance of a few of them in one class an order is being taken into consideration (from up to bottom);
trick questions
– mixing up three classes, in which one is inheriting after second whereas oldest from them has static blocks... bla, bla, bla. Such questions are essence of questions about initialization blocks. Let us look at such a code:
class A { { // variables initialization } } class B extends A { static { // doing something } } class C extends B { // main metod here, constructor, also static initialization blocs }
On the above example one can see the thing clearly – the B class is inheriting after A, so in the moment when the compiler comes across the extends keyword, he is loading the class to memory and is running static initializing blocks for her! The problem relies on it so that don't gather speed and don't notice the problem with it, that A class have no static initializing blocks! On so „ obvious truisms ” very much it is easy to be conned.
shadowing is a next variation of problems with the polymorphism. The thing is quite simple, it will be sufficient to remember about a few substantial issues:
- at sending primitive or the reference to the object we are sending the copy – it results in the fact that caused changes on the primitive value concern only a body of the specific method – more precisely copy of passed value. If references to objects are being talked about everyone should remember about the fact that we can change the referred object, whereas we aren't able to change the reference! So in the following code:
public function doSomething( ClassA a ) { a.setValue( 2 ); // It’s allowed a = null; // It’s not allowed }
- final modifier concerns the reference, not referred object!
For the time I hope Var-args last. The method with parameters in the var-args form will only and exclusively be chosen when there will be no other method for single parameters! It is a short example (from the guide):
public class Bertha { static String s = ""; public static void main(String[] args) { int x = 4; Boolean y = true; short[] sa = {1,2,3}; doStuff(x, y); doStuff(x); doStuff(sa, sa); System.out.println(s); } static void doStuff(Object o) { s += "1"; } static void doStuff(Object... o) { s += "2"; } static void doStuff(Integer... i) { s += "3"; } static void doStuff(Long L) { s += "4"; } }
What will we see on the exit? " 212 " – essential thing to remember is a fact, that in case of widening primitives, this mechanism doesn’t work in combination with autoboxing. And so evoking the doStuff()
method with the parameter int cannot use from invoking the closing declaration of the method (for the parameter of the Long type). Hence invoking the first version (with the parameter of the Object
type).
Garbage Collector (GC). It is possible to cut a notch oneself on questions about the possibility of removing the object from the memory by the garbage collector. Everyone should remember about it, that components of class being objects are also taking into account as objects, which have right to own living. Therefore instance of the following class:
public class Chlebik { String nick = „Chlebik”; int age = 25; }
Won't be removed even after "losing" reference to her, if component nick will be still available. It concerns especially static component classes.
Chapter 4 – Operators
"Having said all this about bitwise operators, the key thing to remember is this:
BITWISE OPERATORS ARE NOT ON THE EXAM!"
Operators are deceptive! To read three times, to spit through the left shoulder, to turn through the right shoulder, to read again, principle 4 xR (to remember, to remember, to remember, to remember).
Operators of the incrementing (++
) and of decrementation (--
) are being used "systematically" even if are operating on the same variable in one line:
static int variable = 7; public static void main(String[] args) { System.out.print( variable++ + " " + ++variable ); }
The above code in the result will give " 7 9 ".
It is possible to compare ENUM
values both with using the operator ==
as well as equals methods. They will always return the same.
Operator &&
at a satisfactory result of the first argument is going to second. If however the first argument returns the FALSE
value, then he is finishing action.
Chapter 5 – Flow, exceptions and assertions
"Using assertions that cause side effects can cause some of the most maddening and hard-to-find bugs known to man! When a hot tempered Q.A. analyst is screaming at you that your code doesn't work, trotting out the old 'well it works on MY machine' excuse won't get you very far."
Method main()
can declare throwing exceptions. Why not?
In case of the loop for there are no problems with using declared earlier variable (e.g. of local variable) as a counter, for widened for loop such an operation will cause generating the error. The following code won't work:
Integer i = 1; for( i : anyIntegerValuesArray ) {}
As can be seen the variable pointing at next array elements must be declared in the body of the loop.
Questions about the potential option of the stack overflow are interesting (StackOverflow
). We should remember, that just triggering of the endless loop, which isn't allocating the additional memory, won't cause the stack overflow. So code:
for( int i = 0; i < 10; i++ ) { if( i == 9 ) i = 1; }
Will be running until Google has their servers and one day longer. If however we will do something like that:
public class Tester { public static void main(String[] args) { new Tester().doSomething(); } void doSomething() { doSomething(); } }
It will result in beautiful StackOverflowError
, because every invoking the method force booking the certain part of the memory for action.
Classes overwriting methods from their parent class cannot declare a wider range of generated exceptions than parental classes! If doSomething()
method is declaring that throws IOException, then if the child class wants to overwrite this method cannot declare throwing the exception of type Exception!
If instead of the standard format of the assertion we will make up our mind to second (with the possibility of generating the reliable information to the output), it will be enough, that the second parameter of the assertion will be returning any object (though there is more about toString()
method).
Chapter 6 – Chains, parsing, I/O, formating
"There are over 500 ISO Language codes, including one for Klingon ('tlh'), although unfortunately Java doesn't yet support the Klingon locale. We thought about telling you that you'd have to memorize all these codes for the exam... but we didn't want to cause any heart attacks."
What after all is subject, and what isn't subject to a serialization? The question is quite essential, because sequence of factors come into play, to which it’s good to pay attention. Let us consider behavior of such a code:
class A {} class B extends A implements Serializable { A variable = new A(); static int variable2 = 9; int transient variable3 = 1; }
In case of the serialization and deserialization the case looks as follows. At the stage of compilation of code obvious errors won't be caught! Class A cannot be serialized what doesn't cause the error of the compilation of the B class. In progress of running of program exception will be generated, however the compilation will succeed.
Variable with variable3 identifier after the process of the deserialization won't receive value 1, but receive the default value for primitive of the type int
(that is 0). Staying already at the deserialization it is good also to mention, that if B class is subject to a serialization and her reconstructing doesn't cause starting the constructor, then the constructor of the parent class will be invoked (that is A).
At the end I have left static values – we should remember that they exist independently of the instance, and so their values aren't generally speaking "moved" by the serialization/deserialization process.
This chapter is focusing in great measure on API – somebody who had the chance to work with the Java longer time should not have more considerable problems. Beginners can however at times come across interesting tricks. I came across on two:
- inconsistency in the nomenclature of methods (
mkdir()
, butcreateNewFile()
), setMaximumFractionDigits()
method inNumberFormat
class concerns only itsformat()
method, in case of theparse()
method has no influence on the result.
Chapter 7 – Generic types and collections
"You're an object. Get used to it. You have state, you have behavior, you have a job. (Or at least your chances of getting one will go up after passing the exam.)"
Before I start, I wish to inform that this chapter waited longer entry on my blog. And so the following list is only a quick summary – I am encouraging reading the entire article.
We should remember straight relations between the equality of objects and their hashcode. If two objects are semantically the same (at overwriting the equals method), then their hashcodes must be the same. However it doesn't work into the other side – if two objects have the same hashcodes it doesn't mean at all that they are semantically identical.
Staying in the subject matter hashcodes – how authors make known, for the purposes of the examination it is possible to assume, that when we aren't overwriting hashcode method in the class, then every object is different – will have different hashcode.
In declarations it is often possible to come across this tricks:
List<List<Integer>> table = new List<List<Integer>>();
What rather won't work, because the List
is an interface and it is not possible to create his instances. Such a code won't also work:
List<List<Integer>> table = new ArrayList<ArrayList<Integer>>();
It won't work for this simple reason, that in the case of generics the declaration must become covered with the created object, in spite of the fact that ArrayList
is widening the List
. What's more – the same situation concerns methods. If method supposed to return e.g.: List
<Number> then if after all we are returning ArrayList<Integer>
- it won't also be possible to compile such a code.
TreeSet
is an interesting class. The issue specifically concerns two things, at least resulting from the same assumption. The TreeSet
class has a few of different constructors, of which aim is creating the set of elements which are organized in the form of the tree. What results from it? Well so much, that elements being components of the class must implement Comparable
interface, or "alone from oneself" to have possibilities to compare one object with another (e.g. String class). If to the object of the TreeSet
class (without exploiting generic types) we will add even a few objects of different classes the code will be compiled without problem. However certainly we will get errors while running a program – the compiler won't simply know what objects he is dealing with and he will throw ClassCastException
.
Chapter 8 – Inner Classes
"More important, the code used to represent questions on virtually any topic on the exam can involve inner classes. Unless you deeply understand the rules and syntax for inner classes, you're likely to miss questions you'd otherwise be able to answer. As if the exam weren't already tough enough."
Basically a majority of questions in this chapter concerns the issue of the availability/visibility of individual classes or methods. I will only write, that in the case of method-local inner class she has the unlimited access to the surrounding class. Such a code:
public class Tester { final String chain = "Chlebik"; public void showInnerClass() { class anyClass { void hello() { System.out.println( chain ); } } } }
is all right and will work without the problem.
Remember about statics – both about the fact that non static methods cannot to be invoked from static methods, as well as static variables are holding on hard and are don’t care about the class instantiation.
Visibility of internal classes – code from the test question:
class A { void m() { System.out.println("outer"); } } public class TestInners { public static void main(String[] args) { new TestInners().go(); } void go() { new A().m(); class A { void m() { System.out.println("inner"); } } } class A { void m() { System.out.println("middle"); } } }
The question is – what will be shown on the exit of the program? Reply: middle. Why? Because the class inside the method is declared only after invoking (that is after the fragment new A().m();
, what is invisible. Class A
, which is in the first line of the code is "level higher" than the class giving on the exit "middle" and therefore won't also be used.
Chapter 9 – Threads
"In fact, the most important concept to understand from this entire chapter is this:
When it comes to threads, very little is guaranteed.”
Putting the thread to sleep is a very interesting issue. About the sleep()
method it is necessary to know two things. First – is a static method of the Thread
class. Therefore always puts the current thread to sleep. Besides throws InterruptedException
, and so we must include invoking the method in the try..catch
clause or to pass the service of the exception higher.
join()
method – similarly as above throws InterruptedException
exception. However it isn't a static method. Let us look at code:
public static void main( String[] args ) { Thread t = new Thread( new Runnable() { public void run() { System.out.println( "Start of loop" ); for( double i = 0; i < 1000000000; i++ ) { } System.out.println( "End of loop" ); } } ); System.out.println( "Chlebik 1:" ); t.start(); System.out.println( "Chlebik 2:" ); try { t.join(); } catch( Exception e ) { } System.out.println( "Chlebik 2:" ); }
Currently executed thread (the one from the method main) won't show "Chlebik 2:" until thread represented by object t will be completed.
wait()
method – it is falling behind the above company a bit, because this method is proper for all objects in the Java (comes from the Object
class). It isn't static, whereas for it’s triggering doesn't cause throwing the exception. Two other methods are going hand in hand with her from the Object class – notify()
and notifyAll()
. All are marked as final, and so in their case breakneck games aren't needed as around e.g. with equals()
method.
Going farther – these methods can be triggered only and exclusively in the synchronized
context! Attempts to use apart from this context result in throwing IllegalMonitorStateException
(it’s not a verifiable exception so it isn't necessary to define its catching). These methods are used for managing blockades of the object (therefore they are elements of Object
class). Method wait()
lets for stopping action of the thread which has the blockade of the object, until invoking by this object the method notify()
or notifyAll()
. Not much I undertake more to explain it writing – I think that the code will say more (taken from the guide):
class ThreadA { public static void main(String [] args) { ThreadB b = new ThreadB(); b.start(); synchronized(b) { try { System.out.println("Waiting for b to complete..."); b.wait(); } catch (InterruptedException e) {} System.out.println("Total is: " + b.total); } } } class ThreadB extends Thread { int total; public void run() { synchronized(this) { for(int i=0;i<100;i++) { total += i; } notify(); } } }
When the blockade exists, rather than when not – it is a very essential topic. It is a quotation from Thinking in Java in version 4.
"It is important to understand, that invoking the sleep()
method doesn’t release blockades of the object, the same how invoking yield()
doesn’t do that. On the other hand, invoking wait()
method initiated within the synchronized method force suspending the thread and releasing blockades of the given object"
getId()
method – a bit I was narked at it. All of a sudden for me questions about this method jumped out. Maybe it’s the way in test questions to bring up issues which were missing in the specific chapter. But it not better would simply be to give the list of methods, against which potentially questions can still be asked? I suppose that learning entire API by heart is not an examination purpose? Ok, end of complaining – thing in the getId()
method.
Documentation is saying that method is returning unique identifier of the thread (primitive of the long
type). Let us look at this code:
// Objects a-a3 are of the same type as this one below read a4 = new Thread( new Runnable() { public void run() { for( int i = 0; i < 100000; i++ ) { if( i == 99999 ) System.out.println('.' + Thread.currentThread().getId()); } } }); System.out.println( Thread.currentThread().getId() ); a.start(); System.out.println( a.getId() ); a2.start(); System.out.println( a2.getId() ); a3.start(); System.out.println( a3.getId() ); a4.start(); System.out.println( a4.getId() );
It is an effect of action:
1 8 9 10 11 54 55 56 57
Still over and over again – the order of digits of course is different (apart from first three-four) – it is obvious, indefinitness of invoking threads. This way it looks. Two issues – as can be seen thread of the main()
method always (at least at me) has number 1. Remaining threads have just enough evenly identifiers and of course going up. Questions on the test examination concerned the possible result on the exit of the similar program to above. It is interesting, but still in many moments it is simply a lottery.
Chapter 10 – Development
"When you start to put classes into packages, and then start to use classpaths to find these classes, things can get tricky. The exam creators knew this, and they tried to create an especially devilish set of package/classpath questions with which to confound you."
Static importing – of course it is necessary using static import keywords (in such an order). However the fact that we can import even single constants and methods this way is less obvious.
Assertions – it was about them in the fifth chapter, but I didn't mark the most essential thing there. So we should remember that assertions were implemented already in the 1.4 version! And therefore also invoking the compiler and the virtual machine in this way:
javac -source 1.4 file.java java -ea file
Will cause, that code in which unfulfilled assertions are appearing (returning the false
value) will cause error while program running (that is assertions will simply be acting). Running, of not a compilation! Let us repeat – run-time errors (run), it is something else than errors of the compilation (compile).
What actually the classpath directive is used to? So mainly finding all classes which the compile/started class will be needed. It is a main objective for classpath. We should remember also that in case of the compilation (javac
command) name of a file is seeking by default in the current directory. In case of running the file it isn't the way! And the last thing – setting value for classpath causes overwriting the system variable (if obviously exists).
JAR files – archives are quite simple to understand, what's more, on the examination there are no questions concerning creating and managing JARS. However it is certainly necessary to know, that after creating the JAR file from the specific directory, even after adding it to classpath accessing classes from archive should be done in code by passing full class name (including the JAR file name). It is an example:
test | file.using.class.from.jar.1 here.created.file.jar to.make.jar.directory | subdirectory1 subdirectory2 | file.java
Referring to the file in the JAR archive, which we created in the ‘test’ directory full path should be given. File in the ‘test’ directory, in which we would like to use the class from the archive must refer to this class by JAR_FILE/to.make.jar.directory/subdirectory2/file – in spite of adding the JAR file to classpath.
Nobody has commented it yet.