Writing Bullet-Proof Code in Java

Learn 35 techniques to write high quality Java code.
3.2 (14 ratings) Instead of using a simple lifetime average, Udemy calculates a
course's star rating by considering a number of different factors
such as the number of ratings, the age of ratings, and the
likelihood of fraudulent ratings.
618 students enrolled
$20
Take This Course
  • Lectures 41
  • Contents Video: 5.5 hours
  • Skill Level Intermediate Level
  • Languages English
  • Includes Lifetime access
    30 day money back guarantee!
    Available on iOS and Android
    Certificate of Completion
Wishlisted Wishlist

How taking a course works

Discover

Find online courses made by experts from around the world.

Learn

Take your courses with you and learn anywhere, anytime.

Master

Learn and practice real-world skills and achieve your goals.

About This Course

Published 3/2015 English

Course Description

Learn best practices that are simple and important to help you write good quality programs with the popular programming language - Java.


Why should you do this training?

This course addresses a burning problem faced by the software industry.

“.. Global cost of debugging software has risen to $312 billion annually.... (More than double the cost of Eurozone bail out).

... on an average, software developers spend 50% of their programming time finding and fixing bugs".

-- Cambridge University Study 2013

This course covers best practices to reduce defect introduction rates in software.


Who should learn?

  • Java programmers
  • Job aspirants
  • Engineering and IT students


Prerequisites

Knowledge of the syntax of Java, keywords, control structures, basics of object orientation, basics of exception and error handling.


How will I benefit from attending this training?

Java programmers are in high demand globally. The need for Java developers will continue to grow. So your investment into learning Java will boost your career.

Even if you know Java this course will hone your programming skills.

If you are a good programmer, it will help you become a better programmer leading to faster career progress.

Develop 'bug-hunting' skills to become an excellent programmer.

It will translate to getting a higher-paying job.

If you are aspiring to undergo Sun/Oracle certificate assessments then this course is made for you!


Content and overview

You will learn 35 specific and proven programming best practices for creating bullet-proof i.e. robust, stable and reliable software code.

Lectures consists of videos, downloadable source code for executing the code in your favourite compiler. By attempting end of module quizzes you can gauge your learning.

You will get a Certificate after completing assessments.

What are the requirements?

  • This training assumes that you are acquainted with programming in Java.
  • Ideally you should be an engineer or technology graduate with familiarity in programming

What am I going to get from this course?

  • Hone programming skills
  • Write robust, reliable and stable Java code
  • Apply for Oracle Certification Programs like OCJP, OCPJP etc.
  • Apply for programming jobs
  • Have fun in bug hunting

What is the target audience?

  • Engineering and IT students
  • Aspirants to jobs in top IT product and services companies
  • Developers wanting to up skill themselves in Java programing
  • This course is only for Java and not of relevance to other programming languages

What you get with this course?

Not for you? No problem.
30 day money back guarantee.

Forever yours.
Lifetime access.

Learn on the go.
Desktop, iOS and Android.

Get rewarded.
Certificate of completion.

Curriculum

Section 1: About the course
01:17

IT industry want to ensure that the code written is always of high quality. Here are the important reasons why you should take up this course and how this training is oriented toward writing high quality code.

00:33

This course is divided into easy to learn modules. Each module has a set of techniques which will help us write bullet proof code.

02:06

This course Writing Bullet-Proof Code in Java helps you become ready for job in top IT companies. It helps you hone your programming skills. Bug hunting is fun which you will love to do when you know the best practices to find defects in the code.


00:43

You can go through each technique at your own pace. Every technique has an objective as well as examples to help you and ends with a summary.

Download the source code zip file provided with each topic to execute in your favourite java compiler.

After every module you also have a quiz to test your understanding of the module.

Section 2: Module 1: Java Fundamentals
11:15

Technique: Use explicit curly braces for grouping statements for blocks of code

Curly brackets {, } can play a crucial role in Java programming.

Indentation is just for Readability:

  • Using indentation (i.e. using white-spaces and aligning the code) is for improving the readability of the code.
  • The compiler ignores whitespace between statements, keywords. Specifically, the lexical analyzer (also known as “lexer”) ignores unnecessary whitespace.
  • The compiler matches the conditional statement only to the first statement (since there is no block to match).

If you depend on indentation for treating a set of statements as a block, you may get a bug.

Sequence of statements may look like a block when you use indentation for statements.

However, the compiler will treat a set of statements as a block only if you use enforced blocks by using braces “{“ and “}”.

Follow this best practice:

“Always use Explicit Curly Braces for body of Conditional Statements if-else, for loop, while loop, etc.”

09:21

Technique : Beware of and avoid the dangling else problem

Use explicit curly braces for code blocks.

When there is no explicit curly braces matching the else statement to an if statement, then the compiler matches it to the innermost if statement, this is known as the dangling else problem.

Why is dangling else a problem? The behavior of dangling else is not intuitive to the readers of the code - when reading the code it may appear as if it is associated with the preceding if statement, but the compiler will match it to the innermost if statement. This confusion often results in subtle defects in the code.

Note that dangling else problem occurs only when explicit curly braces are not used in the if statements. Hence, to avoid this problem, it is a good practice to use explicit curly braces for the code blocks.

09:42

Technique: Beware of (and avoid) typing errors that can result in subtle defects.

Typing errors are not always caught by the compiler and this can cause subtle bugs to arise in the program.

A few examples of typing mistakes that can create subtle defects are:

  • Introducing a semi-colon after a for loop statement
  • Misspelling main as "mian"
  • Calling an object's tostring method instead of the correct toString method

The reasons why typing mistakes should be avoided are:

  • Most typing errors (typos) are caught by the compiler; the typos not caught by the compiler result in runtime defects.
  • Many typos even make their way to production code.

We can detect any typing mistakes in our code by the following ways:

  • Use manual code reviews
  • We can also consider using one of the two techniques to avoid typos:

1)Check compiler warnings

2) Use static analysis tools such as PMD, FindBugs, Checkstyle

Remember that typing mistakes can result in hard to find run time errors and hence, to avoid this problem, make sure to avoid typos by either manually reviewing your code or using a program specially designed for this purpose.

08:24

Technique : Choose the Correct Datatype to Suit the Need.

It is important to choose the correct data type depending on the context and need.

Things to keep in mind while choosing a datatype are:

  • Java is a strongly typed language.
  • Every variable and expression has a type and each type is strictly defined.
  • The Java compiler checks all expressions and parameters to ensure that the types are compatible.
  • Any type mismatches are errors that must be corrected before the compiler will finish compiling the class because it can lead to a wrong or inaccurate results.
  • The datatypes 'float' and 'double' do not represent decimal values accurately.
  • 'BigDecimal' datatype can represent decimal values accurately and should be used when performing financial calculations.
Choosing the wrong datatype can lead to an inaccurate or wrong result which is not suitable and hence, to avoid this problem, the best practice is to choose the suitable data type depending on your need.
12:15

Technique: Use Enumerations Instead of Using a Set of Constants or Strings

Using enumerations are suitable for representing a set of related numbers or strings and are also more type-safe.

Consider two programs where we represent the card suits using integer values and another where we use string values. Both these programs don't work. The reason is:

  • The constants do not express the relationship between the elements of the club suits set.
  • Club suits consist only of the four possible values (one of “spades”, “hearts”, “diamonds”, or “clubs”), but the program does not capture it.
  • In specific, in the program, the constants seem to be different from each other and it is not clear if they are exactly a part of a set of related values.
  • Using constants to represent the values results in programs that are not type safe.
  • Programs can have “type errors” that are not detected by the compiler, i.e., errors that arise because of incorrect use of data type values, mixing different types of data, etc.
  • “Type safety” is the term that refers to writing code in such a way to avoid such errors in usage of types.

So what is the recommended solution? Using Enumerations!
  • You can create an enumeration using enum keyword in Java.
  • An enum enables for a variable to be a set of predefined constants.
  • When the compiler compiles an enumeration, it internally creates a class that extends java.lang.Enum and provides methods and fields defined in the enumeration in the generated class.
  • Only valid enumeration values can be assigned to a variable.
  • Any mistakes in assigning invalid values (i.e., outside the range of values that the enum can hold) will result in a compiler error.


10:13

Technique: Avoid Hard-coded Logic

To hard-code means to fix (data or parameters) in a program in such a way that they cannot be altered without modifying the program.

Hard-coded numbers and strings are sometimes referred to as "magic numbers" and "magic strings". Why?

  • When you read a program, often you'll find some strange constants or string literals used in the program and their meaning may not be clear immediately. When we don't recognize how the program works making use of these constants, it appears as if it works by "magic".
  • Magic numbers and strings cause difficulties when porting the program to other platforms or when maintaining the programs.


Scenarios where we hard-code are:

  • We hard-code the value of PI using the expression 22.0/7.0
  • We hard-code the path of a file
The alternatives to hard-coding are :
  • Use the PI constant defined in Math package
  • Instead of hard-coding the path use a mnemonic constant instead.
The recommended practice would be to :
  • Use named constants (mnemonics) provided in the JDK or by defining them in your program
  • Use named constants (mnemonics) instead of hard-coding String constants in the program.

07:46

Technique: Avoid Duplicated Code Segments

Copying code segments can cause subtle bugs in programs and defect fixes or enhancements need to be replicated in all the duplicated code segments.

The different types of code clones are:

  1. TYPE I : Exactly identical except for variations in whitespace, layout, and comments
  2. TYPE II : Syntactically identical except for variation in symbol names, whitespace, layout, and comments
  3. TYPE III : Identical except some statements changed, added, or removed
  4. TYPE IV : When the fragments are semantically identical but implemented by syntactic variants
Copying and pasting code appears to improve the productivity but in reality it involves a lot of maintenance effort.
The best practice would be to avoid duplicated code segments.
08:12

Technique : Avoid Globally Accessible Variables

By providing public static members, it is possible to simulate the use of global variables in Java.

Java does not support global variables as in languages like C and C++. However, Java enforces that all the code such as programming logic, variables, etc. to be provided only within classes. Does it mean it is not possible to provide global variables in Java?

  • Syntactically, you cannot provide a global variable as in C or C++. However, it is still possible to provide globally visible static variables that have similar semantics as global variables by providing public static members.
  • Global variables can be freely read or modified by any code in the program; for this reason, it is hard to understand programs that use global variables.
  • Using global variables is a bad programming practice and must be strictly avoided!
9 questions

Please attempt a quiz for the Module 1 - Java Fundamentals.

Section 3: Module 2: Operators and Control Flow
07:34

Technique : Use Explicit parenthesis to enforce precedence to avoid ambiguity

Different operators have different precedence levels and hence using explicit parenthesis will enforce precedence.

  • Java has well-defined rules for specifying the order in which the operators in an expression are evaluated when the expression has several operators.
  • For example, multiplication and division have a higher precedence than addition and subtraction.
  • Precedence rules can be overridden by explicit parentheses.
  • When two operators share an operand the operator with the higher precedence goes first.
  • When an expression has two operators with the same precedence, the expression is evaluated according to its associativity.


In java when we use multiple operators in an expression, we can get a result different from what we expect. Hence, to avoid this problem, the best practice would be to always use explicit parenthesis to enforce precedence and avoid ambiguity.

07:16
Technique: Simplify condition checks whenever possible
Simplifying Boolean expressions can result in better code.

We can use laws to help simplify Boolean expressions. They are:

  1. Identity law
  2. Unit and zero property
  3. Absorption law
  4. Distributive law
  5. Idempotent law
  6. Domination law

Unnecessarily complex condition checks can affect make the program hard to understand as well as cause defects and this can compromise the reliability of the code.

Hence, the recommended practice would be to simplify the condition checks whenever possible.

13:53

Technique : Ensure termination of loops

To avoid infinite loops and the defects that can arise out of it, it is important to ensure terminations of loops that are meant for termination.

When you execute a program, instead of responding to you or doing meaningful work, if the program “hangs”, it is likely that the program has entered an infinite loop.

Logical mistakes or typing mistakes can cause non-terminating loops:

  • When a loop does not terminate, the program will hog the processor and will not do any meaningful work or make progress. The only solution is to forcefully terminate the program.
  • Logical mistakes are a common cause of infinite loops. You can use manual reviews or static analysis tools to detect non-terminating loops in programs.
  • For example, if the condition within the if loop is true then it will execute the statements within the body of the if loop. Since there is no increment statement the program gets into an infinite loop
  • An example of a typing mistake could be when we have two for loops and the inner for loop increments variable i instead of the correct variable j.
  • Loops with wrong termination conditions may result in infinite loops.

We can ensure termination of loop in the following ways:
  • By giving special attention to the termination condition to make sure that it is correct.
  • We should ensure that the loop logically progresses towards termination of the loop
  • And we should review the code to check for any typing mistakes that can result in infinite loop.
09:28

Technique : Ensure that no dead code is left in production code

Reasons why dead code shouldn't be present in production code are:

  • Dead code means the code that is unreachable since the code can never be executed.
  • Dead-code may appear harmless since the code is anyway not going to get executed. However, dead-code is dangerous since unreachable code may get accidentally enabled during maintenance
  • Dead code usually indicates a potential logical or typing mistake in the program.
  • Dead code cannot be reached by the tests since by definition “dead code” is “unreachable”, so the code cannot be tested.
  • Dead-code also bloats the code size and makes understanding the code more difficult

Consider using the following techniques to avoid dead-code:

  1. Look out for the compiler warnings that warn you about the dead code. You can also enable optional warnings from the compiler using –Xlint option and get warnings about unreachable code.
  2. During manual code reviews, examine the code to ensure that no code is “unreachable”. Manual reviews are effective in detecting dead code.
  3. Use static code analyzers such as FindBugs, PMD, and IntelliJ IDEA. Most static analyzers warn you about unreachable code.

In short, it is easy to detect dead-code. Hence, detect dead-code and remove it and ensure that no dead-code is left in production code.

09:52

Technique: Avoid using bitwise operators instead of logical operators

Logical operators have short circuit evaluation semantics, hence use logical operators

The difference between the logical( &&, ||) and binary operators(&, |) lies in “short-circuit evaluation”.

What is short-circuit evaluation?

  • Short circuit evaluation is when the rest of the logical expression will not be evaluated if the truth value is determined.
  • Bitwise AND(&) and OR(|) operators do not have short circuit evaluation semantics and hence they evaluate the entire expression.
  • When Java finds the value on the left side of a logical AND(&&) operator to be false, then Java gives up and declares the entire expression to be false. That's called short circuit expression evaluation.
  • The same kind of thing happens with the logical OR( || ) operator (another short circuit operator) when the value on the operator's left side is true.

Bitwise operators are preferred over logical operators for the following reasons:

  • Performance reasons
  • Evaluation is faster
But using bitwise operators can result in subtle bugs and hence the best practice would be to use logical operators instead.
07:45

Technique: Beware of “fall-through” nature of switch attachments

Provide explicit break statements to avoid fall-through nature of switch statements.

When there are no “break” statements between case statements, the control flows to the following case statements within the switch loop

Sometimes, the “fall-through” nature of the switch statements is useful.In those cases, it is better to high-light the fact that the missing break is intentional!

Keep in mind that a continue statement cannot be used inside a switch statement.

10:23
Technique: Do not assign same variable more than once in an expression

There are two types of increment operators:

  • Pre-Increment operator : Evaluate to the incremented value of i. In other words, “pre”-increment the value of i.The effect of a pre-increment operation is immediately reflected in the expression.
  • Post-Increment operator : It means save the original value of i and increment the value of i but evaluate to the saved value of i In other words, “post”-increment the value of i. The effect of a post-increment operation is reflected only at the end of the expression.

An example of reassiging variables in a statement is:
i = i++

The expression i++ evaluates to current value of i, but i gets reassigned in this statement. Hence the post-increment effect of i is lost!

Important points to keep in mind are :

  • The pre-increment evaluates to the current value of the variable; the post-increment the variable, but evaluates to the saved or original value of the variable.
  • When the value of three statements is put into a single statement the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the right-hand operand is not evaluated and no assignment occurs.
  • It is not possible to apply pre-increment and post-increment both together for a variable.
The best practice would be to not assign the same variable more than once in an expression.
09:50

Technique: Avoid idempotent expressions

What are idempotent expressions?

  • Idempotent expressions refer to redundant expressions such as “a = a” where “a” is a variable. As in the example “a = a”, the assignment is completely useless and can be removed from the program.
Why would idempotent expressions occur in programs?
  • One reason is typing mistake. For example, instead of typing “a = b”, the programmer could have typed as “a = a”.
  • Another reason for idempotent expressions is misunderstanding the semantics of the operators. For instance, an inexperienced programmer may think that (a & a) may be a meaningful expression when it is not.
  • In general, idempotent expressions are indicative of logical mistakes in code, and hence are likely defects in code. Ensure that production code does not have idempotent expressions.

Not all idempotent expressions are mistakes – sometimes they do something meaningful in programs.

However, in general, idempotent expressions are indicative of logical or typing errors in the code. Hence, follow this best practice to write quality code: “avoid idempotent expressions”.

06:05

Technique: Use break and continue judiciously to alter control flow in loops

When break statements are used in a loop, it is complex to understand the logic when compared to the simple loops without break statements

For instance, with unnecessary loops, the code is likely to be lengthier and complex.

Complex code is hard to understand and hence it affects the maintainability of the code. Complex code is a also a breeding ground for defects, as well as a place for defects to hide, and hence unnecessary 'breaks' and 'continues' may negatively impact the reliability of the program.

Hence, check if it possible to rewrite loops that use 'break' or 'continue' by simplifying the program logic to not use them!

05:37

Technique: Always provide default case for switch statements

A missing default case in a switch statement can cause bugs in the program.

Why is it important to provide a default case in switch?

  • Lack of 'default' statement can result in subtle problems when no 'case' statement matches for the given variable in the 'switch' condition.
  • You can write tests to detect this problem, but it is difficult to simulate situations in tests that reach for a 'case' statement that does not have a match within the 'switch' statement.
  • Hence, it is easier to ensure that you provide a default 'case' statement in every 'switch' statement.
  • It is not necessary that the default 'case' statement should be the last statement in the switch case body.
  • In many cases, there will not be a default 'case' that is feasible to be reached in a 'switch' statement; providing such a default 'case' statement would result in unreachable or dead code.
  • In this case where the default is not feasible, throw an exception or assert for that assumption that the 'case' indicates impossible value.

In general, it is always better to provide a default 'case' statement.

10 questions

Please answer the following questions

Section 4: Module 3: Using Types
12:40

Technique : Beware of and avoid Overflows in Integers

The primitive types have a range of values they can hold:

  • The byte type can hold values only from -128 to +127.
  • The short type can hold values from -32,768 to +32,767
  • The int type can hold values from -2,147,483,648 to +2,147,483,647
  • The long type can hold values from -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807
What will happen if we try to assign a value, say +130 to a byte variable? Since +130 is outside the positive range of values the byte type can hold, it will “overflow” and become a negative value. In a similar way, what will happen if we try to assign a value, say -130 to a byte variable? Since -130 is outside the negative range of values the byte type can hold, it will “underflow” and will become a positive value.

There are many ways to overcome this problem:

  • One way is to use a data type of larger range. For example, if an integer overflows, you can use a larger data type such as long.
  • Another way is to program in such a way that the expression result is within the range of the type.

To summarize, when expressions result in values that the primitive type values cannot hold, it results in an “overflow” or an “underflow”. A program with overflow problem will not crash, but the results will be wrong. Hence, overflow affects correctness of the program and can result in subtle defects in code.

11:40

Technique: Beware of the Differences between Pass-by-Value and Pass-by-Reference

Java supports two kinds of argument passing mechanisms:

  • Pass-by-value :Primitive types such as integer, short, long, float, etc., are always passed by value as arguments to other methods. When the parameter value is modified inside the called method, there is NO impact on the variable in the calling method. Any changes to the primitive type inside the called method will be effective only inside that method.
  • Pass-by-reference: Reference types are passed-by-reference. For example, String is a reference type. Just like pass-by-value, when the reference value itself is modified inside the called method, there is NO impact on the variable in the calling method. However, if you make any changes to the variable through the reference, the change will be reflected in the calling method.


To summarize, the semantics of pass-by-value and pass-by-reference are subtly different:

  • In pass-by-value or pass-by-reference, if we change the passed variable itself, the change will not be reflected in the calling method
  • In pass-by-reference, we make any changes through the passed variable, it will be reflected in the calling method

It is easy to get confused between the two argument passing mechanisms because syntactically they look same in the programs. So, look out for the data type used to differentiate between them.

  • If a primitive type such as integer or long is used, then the program uses pass-by-value.
  • If a reference type such as Object or String is passed as an argument, then the code uses pass-by-reference.
Not recognizing this semantic difference between pass-by-value and pass-by-reference can result in subtle defects in code.
09:57

Technique: Do not hide Variables in Different Scopes

  • In Java, it is possible to use variables with the same names in a program.
  • Using same variable names can cause confusion.
  • A variable in the innermost scope hides the variable with the same name in the outer scope(s).
  • Consider the scenario in which a method in a class has a local variable with the same name as the data member in the class. In this case, the compiler will resolve any reference to the variable name in the method to the local variable and not to the data member. Why? Because the local variable name hides the data member in the outer scope.

There are two ways to solve the problem

  1. Use explicit 'this' qualifier for the variable in the Left Hand Side (LHS) of the assignment operator
  2. Use a different variable name for the data members or the parameters
The key point to remember is that a variable in a statement gets resolved to the variable that is “visible” to that statement. Such “variable hiding” is confusing and can result in subtle bugs in code.
You could either use explicit qualifiers for resolving ambiguity in the variable names or rename the local variables to solve the hiding problem.
08:01

Technique: Avoid direct Equality Comparisons in Floating Point Numbers


What is the result of 10.0/3.0? It becomes 3.3333…. with the value “3” that goes on forever right. Now how is it possible to represent the result of the expression 10.0/3.0 accurately in a few bytes in computer memory? It is not possible to store the result accurately. Not just the recurring decimal fractions, even some binary fractions cannot be represented accurately in the floating-point format supported by our digital computers.

In other words, floating point numbers suffer from the problem that many fractional values cannot be accurately represented in the floating-point format in computers. Because of that, direct comparisons using == or != operators may not work if the arguments differ slightly in their values.

However, note that BigDecimal type accurately represents values. Unlike float and double type, it does not result in loss of value when performing arithmetic operations.

08:35

Technique: Explicitly Initialize Array of Objects before use

  • When an array of string buffer is created, memory is allocated to hold its references only. By default, references variables in an array are initialized with a null value. So, when the null references are used, the program throws a NullPointerException. So, unless the array members are explicitly initialized to a non-null value, we should not dereference them. If we dereference such uninitialized array members, we will get a NullPointerException.
  • When an array of primitive types is created, memory is allocated to hold the value of primitive type.For example: Arrays of int, byte, short, long, char etc.Every element in the array is initialized to its default value.
  • When you initialize an array variable by allocating an array, it initializes only that variable and not the contents within that allocated array


We can solve this by explicitly initializing an array of objects before using it.

A NullPointerException can occur when references in an array are accessed before they are initialized with objects.To avoid this problem, follow this simple technique when creating array of objects to write reliable and correct code: "always explicitly initialize array of objects before it is used".


10 questions

Please answer the following questions

Section 5: Module 4: Writing Type-Safe Code
11:53

Technique: Avoid Mixing Different Types in Expressions

  • You can get subtle bugs in programs when expressions involve variables of different types.
  • For instance, if integral expressions are part of a larger floating-point expression, the evaluation of the integral expression may give unintuitive results.
  • Note that problems due to mixing up types in an expression is a “silent” problem – you will get wrong runtime results but will not get exceptions thrown. So, it is often difficult to detect why the program does not crash but the results are wrong.
An example of mixing different types in expressions would be:
float area = (22/7) * r * r
Here, (22/7) is an integral expression and results in the value 3 leading to a wrong output.
To overcome this problem, use same types in the expression or using explicit casts can help solve the problem.
So we can change the expression to as follows:
float area= (22.0/7.0) * r * r
or
float area= ((float)22/7) * r * r

To summarize: It is a bad idea to combine different types in the same expression and depend on implicit casts to work correctly and hence we should use the same types in the expression or use explicit casts.

08:55

Technique: Avoid Explicit Downcasts that can Result in Loss of Data

There are two kinds of casts that you can perform in programs -

  1. Upcasts : It is a conversion from a derived reference type to a base reference type. Upcast also happens when you convert a smaller primitive type to a larger primitive type.
  2. Downcasts : It is opposite of upcast. Downcast is a conversion from a base reference type to a derived reference type. Downcast also happens when you convert a larger primitive type to a smaller primitive type.

Now here is something important to understand and remember about upcasts and downcasts.

  • Upcasts always succeed.
  • Downcasts can fail.
  • Downcast of primitive types can result in loss of data.
    • If the value is within the range of the target type for the downcast, then the cast is safe and no data loss occurs.
    • If the value is outside the range of the target type for a downcast, then the cast is unsafe and it will result in data loss.
  • Downcast of reference types can fail by throwing a runtime exception.


Hence for writing reliable and correct programs, adhere to this best practice: avoid downcasts that result in loss of data.

06:37

Technique: Avoid Implicit Downcasts that can Result in Loss of Data


Downcasts can be of two types:

  1. Implicit downcast: An implicit downcast occurs when there is no cast operator used, but a conversion from a base type to derived type occurs or a conversion from larger primitive type to smaller primitive type occurs. An implicit downcast is harder to identify in the programs.
  2. Explicit downcast: An explicit downcast occurs when you use a cast operator in the program and convert from a base type to derived type occurs or a conversion from larger primitive type to smaller primitive type. An explicit downcast is easy to observe in the program because a cast operator is used.


Implicit downcasts between primitive types can result in losing data.

  • If the value is within the range of the target type for the downcast, then the cast is safe and no data loss occurs.
  • If the value is outside the range of the target type for a downcast, then the cast is unsafe and it will result in data loss.
To avoid the problem with implicit downcasts and get into correctness and reliability problems, adhere to this simple best practice: “Avoid performing implicit downcasts that can result in loss of data”.
Module 4 Quiz
5 questions
Section 6: Module 5: Writing Defensive Code
11:28

Technique : Ensure that array accesses are always within bounds


  • The correct range of an array in Java is from 0 to (length - 1) and not from 1 to length.
  • For instance, if the length of the array is 10, the correct range is from 0 to 9. Specifically, the valid index values are NOT from 1 to 10 – the correct indexes are 0 to 9.
  • If by mistake, we try to access an element in the array that is outside the valid range, an Array-Out-Of-Bounds Exception would occur.
  • Accessing out-of-bounds in an array is a common mistake and such a programming mistake can crash the program.

How about providing a catch handler for handling this Array Out-Of-Bounds Exception?

  • This is a wrong way to deal with this problem. When this exception occurs in a program, it indicates a programming error.
  • Hence, the correct solution is to fix the program by providing the valid array index instead of providing a catch handler.

Note that there are two kinds of out-of-bounds-exceptions:

  • One occurs when we attempt to access a character in a string by providing an invalid index value.
  • Another one could occur when we attempt to access an element in an array by providing an invalid index value. .

The bad practice of handling an out of bounds access is to use exception handlers.Instead we should ensure that correct index is provided to avoid out of bounds access.

12:29

Technique: Provide defensive check to avoid divide by zero

A divide-by-zero is in specific an arithmetic-exception

  • If you divide an integral value like byte, short, integer or long by zero, it will result in a divide-by-zero exception.
  • If you divide a floating-point value (float or double type) by zero, you will get the result as Infinity.In specific, you will not get an exception
  • Floating-point numbers can take on three exceptional values:
  • Infinity
  • Minus Infinity
  • NaN(Not-a-number)
    • These values are produced as a result of exceptional or otherwise unresolvable floating-point operations, such as division-by-zero.
  • The mod operator(%)can also throw a divide by zero exception.
How can we overcome this problem?
  • Handling divide by zero using exception handlers is a bad practice.
  • Using condition checks such as ensuring the denominator is not zero, to avoid divide by zero is a good practice.

Divide-by-zero problem can happen in division (or mod) operators if divisor is zero. Adhere to this best practice: “always use defensive checks to avoid divide-by-zero problem”. Checking the value to ensure that we are not dividing-by-zero is known as a "defensive check”.
11:55

Technique : Provide defensive check to avoid null pointer dereference

  • Any operation that can dereference a null value can result in throwing NullPointerException.
  • When uncaught this exception can crash the program and hence it is important to avoid raising this exception.
  • There are many scenarios that result in a null pointer exception when using a reference variable with null value. Here are a few scenarios:
    • When you try calling a method using a variable and the variable happens to be null
    • When you try to access a data member using a variable and that variable happens to be null
    • When you use synchronized keyword on a variable and that variable happens to be null
    • When you try throwing an exception using a variable and that variable happens to be null
    • When you access an element in an array by providing an index and that array variable happens to be null

What is a NullPointerException?

  • NullPointerException in Java is nothing but an error or precisely an exception which occurs if we tried to perform any operation on object which is null.
  • In Java, reference variable points to object created in heap but when you create a reference variable of type object by default it points to "null" and when you try to call any method on null or try to access any variable on null you will get this null pointer exception.


How do we handle this exception?

  • It is a bad practice to provide try-catch locks to handle NullPointerException, since this exception indicates a programming error.
  • The best solution is to perform a defensive check before dereferencing any reference type variable and check for null before accessing fields or call any methods.
08:58

Technique : Provide defensive checks instead of handling programming errors

There are various reasons why exceptions can occur:

  • For instance, a logical error in the program can result in an exception.
  • Another example is a programming error which can result in throwing an exception.

The way we deal with these different kinds of exceptions is different. When you make programming mistakes, it can result in runtime exceptions. When left unhandled, such exceptions will crash the application.


What should we do to overcome this problem?

  • It is a bad solution to handle these exceptions
  • The recommended solution would be to use defensive checks

08:22

Technique : Provide defensive exception handlers for operations that may fail


In Java, many methods can throw runtime exceptions. When unhandled, these exceptions can crash the program. It is very important to handle these exceptions correctly, so that the programs will not crash abruptly.

Consider the following solutions to use to handle these exceptions:

  1. One possibility is to provide defensive checks so that these run time exceptions won't occur.
    • What if defensive checks are not feasible? Then if the exception is not a recoverable one, then provide a “graceful exit”.
  2. Another alternative is to provide defensive exception handlers.


  • It may be difficult to understand how to handle exceptions – should I provide a defensive check, or defensive handler, or allow the program to terminate with “graceful exit”.
  • You need to analyze the context to determine which approach to follow. However, note that under no circumstances, the application should crash badly - i.e., without graceful exit – by throwing an exception.
  • Before making API calls in third party libraries or when invoking methods provided in the Java library, read the java documentation to check what exceptions can get thrown when the method fails.
  • Once you find the exceptions that a method may throw, provide defensive checks to handle the exception.
  • Note that the compiler will complain for missing exception handlers only for the checked exceptions.
  • For runtime exceptions, the compiler will not issue any errors or warnings for missing catch hanlers.
  • The best practice is to provide defensive exception handlers for operations that may fail.


10:41

Technique: Provide defensive null check before unboxing

  • Boxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes. For example, converting an int to an Integer, a double to a Double, and so on. In other words,Boxing occurs when a primitive type variable is assigned to the corresponding reference type variable.
  • Unboxing is the inverse of boxing.Unboxing occurs when a reference type variable is assigned to a corresponding primitive type variable
There is an important semantic difference between boxing and unboxing operations:
  • A boxing operation will always succeed and never fail.
  • However, an unboxing operation may fail due to various reasons. For instance, if the reference type variable is null, unboxing operation will fail by throwing a NullPointerException.
There are two possible solutions for handling unboxing:
  1. In solution one we will provide a defensive null check before performing a unboxing operation.
  2. Solution 2 would be use of Integer type
Sometimes it is possible to avoid performing unboxing operation by using the reference type variable itself.
The best practice would be to provide defensive null checks before performing unboxing operation.
12:24

Technique: Perform defensive checks before performing downcasts


There are two kinds of casts that you can perform in programs -

1.Upcasts : It is a conversion from a derived reference type to a base reference type. Upcast also happens when you convert a smaller primitive type to a larger primitive type.

2.Downcasts : It is opposite of upcast. Downcast is a conversion from a base reference type to a derived reference type. Downcast also happens when you convert a larger primitive type to a smaller primitive type.

Now here is something important to understand and remember about upcasts and downcasts.

  • Upcasts always succeed.
  • Downcasts can fail.
  • Downcast of primitive types can result in loss of data.
  • Downcast of reference types it can fail by throwing a runtime exception.

An object can be of dynamic type or static type:

  • Dynamic type of an object refers to the type of the object pointed by the reference
    • In a cast, if the dynamic type of the object cannot be converted to the type given in the cast, the downcast fails
  • Static type of an object refers to the type of the reference type itself

How do we overcome the downcast problem?

  • It is a bad practice to provide catch handlers for such exceptions
    • If you must perform a downcast, guard it with an instance of check
    • If the cast type was wrong, provide the suitable cast type
  • The recommended solution is to use a defensive check.

You can get subtle bugs in programs when performing downcasts. Unlike upcasts (which will always succeed), downcasts can fail and throw a ClassCastException. For this reason, you need to provide defensive checks to avoid any potential problem when performing a downcast.
09:14

Technique : Make defensive copies of objects when needed

What do we mean by defensive copying?

  • Defensive copying is making a copy of mutable object and providing only the copy to other classes during interaction.
  • Defensive copy is concerned with protecting mutable objects. Only the copy of the mutable objects should be provided to other classes so that any changes to the object do not change the state of the original object.

Mutable objects can be modified using references with the native classes or other classes that interact with it. To protect immutability always make a defensive copy of the mutable objects when interacting with other classes. This is an important technique to follow to create reliable code.

To summarize, immutability of the target objects can be compromised when a reference to the mutable objects stored in the target object is provided to the client program. In this context, clients can accidentally or intentionally corrupt an object if defensive copy is not made.

10 questions

Please answer the following questions.

Section 7: Module 6: Compiler Warnings
08:52

Technique: Check the compiler warnings to avoid subtle defects

Why is it important to check the compiler warnings?

  • Warnings provide valuable information on potential bugs in the code and would help to resolve it in a timely manner.
  • Numerous potential bugs goes undetected into production code.
  • To ensure quality of the software is good, it is important to find out as many problems as possible earlier during the development lifecycle instead of waiting for testing team or the customers to report the problems
Note that all warnings are not enabled by default when you use the java compiler. So how can you enable the warnings?
  • For the standard java-c compiler shipped as part of the JDK, use -Xlint option. You can also use enable only specific warnings use the " -Xlint:name" format.
    • .The java-c compiler's -Xlint option warns you of potential mistakes in the code.
      • For example, javac –Xlint DivideByZero.java
    • You can use the optional name part in “ -Xlint:name” to selectively enable a warning.
      • For example, javac –Xlint:divzero DivideByZero.java

Check the compiler warnings using the -Xlint option to avoid subtle defects in code.

2 questions

Please answer the following questions

Section 8: Final Assessment
30 questions

Please answer the following questions.

40 questions

Section 9: Course Summary
00:44

You have come to the end of the course.

With this training, you have learnt 35 specific techniques.

Now that you have learnt these techniques you can use them to write high quality software which will help you save cost, time and efforts.

00:57

You have met the objectives of this training program.

1.You have learnt techniques which will give you an edge in the job market.

2. As a result of the techniques that you've learnt, you'll find that now you are in a much better position to apply for certification programs provided by Oracle like OCJP, OCPJP etc.

3. The most important benefit that you will have taken away from this training is bug hunting. You must have had a great time trying to find bugs in the program.

Students Who Viewed This Course Also Viewed

  • Loading
  • Loading
  • Loading

Instructor Biography

Latitude Edutech, Top eLearning Content Development Company

Latitude Edutech provides solutions for content creation and aggregation in eLearning.

We create content of high quality along with Subject Matter Experts to add to the knowledge levels of learners. We develop both packaged and custom learning programs tailored to our customers' needs. Our programs are aligned to add value to business and to individuals.

Latitude Edutech was founded to deliver scalable solutions that assist our customers to address a wide audience with learning solutions. Members of the Latitude team include management graduates, instructional designers, graphic artists, engineers, trainers and subject matter experts, working towards this goal. Our organizational structure has been designed to support future growth and innovation while levering operational, managerial and marketing efficiency.

Ready to start learning?
Take This Course