JAVA-201
Java Coding Practices
1
The String Pool & String Immutability
Since String objects are the most often used object, Java has a special place
in memory for Strings, called the "String Pool".
Strings are not garbage collected right away. They linger in the String pool,
so that if the same String is needed elsewhere, no need to instantiate a new
String.
2
The String Pool & String Immutability
static String stringX() {
return "Test";
}
static String stringY() {
return "Test";
}
public static void main(String[] args) {
String x = stringX();
String y = stringY();
[Link](x == y); // true
}
3
The String Pool & String Immutability
This helps with performance if certain strings are used over and
over in the system.
No cost of repeated String instantiation.
However, things get complicated if we
try to "modify" a String.
4
The String Pool & String Immutability
String objects are immutable. You can never modify a String object, you only
create a new one.
String greeting = "happy";
greeting = greeting + " ";
greeting = greeting + "birth ";
greeting = greeting + "day";
7 objects are instantiated:
1. "happy" 5. "happy birth "
2. " " 6. "day"
3. "happy " 7. "happy birth day"
4. "birth "
5
The String Pool & String Immutability
Each intermediate object gets left in the String pool. Performance is slow
due to multiple instantiations.
If these types of operations happen often, may lead to large memory
consumption.
String greeting = "happy";
greeting = greeting + " ";
greeting = greeting + "birth ";
greeting = greeting + "day";
6
The String Pool & String Immutability
The string pool is also the reason why you should never use the
String(String) constructor.
See code demo...
7
Consider StringBuilder to Modify Strings
A StringBuilder object has a mutable char buffer. You may append, insert &
remove characters without creating intermediate String objects (except for
any String literals you pass into the StringBuilder).
new StringBuilder("Please").append('B').append(true)
.append(2).append("me.") // ??
To turn the char buffer into a String, call StringBuilder toString() method.
String s = new StringBuilder("Please").append('B').append(true)
.append(2).append("me.").toString();
If you're doing a lot of string manipulation, the performance of
StringBuilder versus using just Strings can be significant. See code demo...
8
Consider StringBuilder to Modify Strings
On the other hand, concatenating with the + operator is more readable.
"'amount' should less than 'balance'. amount: " + amount
+ " balance: " + balance
If it's just one line, compiler replaces this with a StringBuilder.
To turn the char buffer into a String, call StringBuilder's toString() method.
new StringBuilder("'amount' should be less than 'balance'. amount: ")
.append(amount).append(" balance: ").append(balance)
If a concatenation operation is infrequent (e.g. error messages), then the +
operator is safe to use.
If a string-manipulation might leave a lot of objects in the StringPool, or
performance is a high priority, consider StringBuilder.
9
Validate Parameters of Non-Private Methods
Throw IllegalArgumentException if parameter is invalid. Include the
parameter in the error message so that your teammates can debug with just
the error message.
void method(int x) {
if (x < 0) {
throw new IllegalArgumentException("x must be positive but was: " + x);
}
...
}
10
Consider Validating Fields Before Using
but better to never allow object to be in invalid state
Throw IllegalStateException if field is invalid. Include the field in the error
message so that your teammates can debug with just the error message.
private int x;
...
void method() {
if (x < 0) {
throw new IllegalStateException("x must be positive but was: " + x);
}
...
}
11
Consider Validating Fields Before Using
but better to never allow object to be in invalid state
Use instanceof to check for correct type.
Object o = method();
...
if (o instanceof Number) {
add( (Number) o );
}
12
Do Not Use float or double for Exact Numbers
Computers are unable to store fractions and decimals precisely, since these
values need to be stored in binary.
[Link](1 - .42); // 0.5800000000000001
[Link](1 - .9); // 0.09999999999999998
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10; funds >= price; price += 0.10) {
funds -= price;
itemsBought++;
}
[Link](itemsBought + " items bought."); // 3 items bought.
[Link]("Change: Php" + funds); // Change: Php0.3999999999999999
13
Do Not Use float or double for Exact Numbers
float & double are therefore not suitable for financial computations.
If the computation will have few intermediate values, consider using
BigDecimal instead.
final BigDecimal TEN_CENTS = new BigDecimal("0.10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS;
[Link](price) >= 0;
price = [Link](TEN_CENTS)) {
itemsBought++;
funds = [Link](price);
}
[Link](itemsBought + " items bought."); // 4 items bought.
[Link]("Change: Php" + funds); // Change: Php0.00
14
Do Not Use float or double for Exact Numbers
Remember to pass in a String into the constructor of BigDecimal, not a
double or float. See what happens if we forget the quotation marks.
final BigDecimal TEN_CENTS = new BigDecimal("0.10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS;
[Link](price) >= 0;
price = [Link](TEN_CENTS)) {
itemsBought++;
funds = [Link](price);
}
[Link](itemsBought + " items bought."); // 4 items bought.
[Link]("Change: Php" + funds); // Change: Php0.00
15
Do Not Use float or double for Exact Numbers
If we pass in String, the BigDecimal constructor will look for the decimal
point and count the number of decimal places after. It will then retain that
number of decimal places in its field scale. If we pass in a float or double,
we will need to include a MathContext object to set the scale.
new BigDecimal(1 - .42, new MathContext(2)); // ???
16
Do Not Use float or double for Exact Numbers
There are two problems with BigDecimal. The first is that operations are
less readable because you cannot use arithmetic nor comparison operators.
You need to use the methods of BigDecimal.
The second, more serious problem is when computations require a lot of
intermediate values, which are common in the Finance and Insurance
domains, where Time Value of Money is usually involved.
Since BigDecimal is immutable, a new object must be instantiated for each
intermediate value. This can be excruciatingly slow, as well as running into
the danger of running low on memory.
17
Do Not Use float or double for Exact Numbers
The solution is to use int or long for the computations instead. Just multiply
the decimal values by a multiple of 10 large enough to make all the values
whole numbers, then divide the result by that same number.
18
Do Not Use float or double for Exact Numbers
Below is the Items Bought routine using int, where we've multiplied all the
values by 100:
int funds = 100 ;
int itemsBought = 0;
for (int price = 10; funds >= price; price += 10) {
funds -= price;
itemsBought++;
}
[Link](itemsBought + " items bought.");
int intChange = funds % 100;
String strChange = intChange < 10 ? "0" + intChange : "" + intChange;
[Link]("Change: Php" + funds / 100 + '.' + strChange);
This approach can be error prone, so be careful. Use this only if you're not
getting the performance you need from BigDecimal. 19
Consider enums Instead of Constants
Using constants can be error prone. You always need to validate that the
value being passed is valid.
class Suit {
public static final int CLUBS = 1;
public static final int SPADES = 2;
public static final int HEARTS = 3;
public static final int DIAMONDS = 4;
}
void method(int suit) {
if (suit < 1 || suit > 4) {
throw new IllegalArgumentException("Invalid suit value: " + suit);
}
...
}
What happens if we add a suit? What happens if we remove a suit? All the
client code needs to be updated!
20
Consider enums Instead of Constants
enum Suit { Client code:
CLUBS, SPADES, HEARTS, DIAMONDS
} method([Link]);
void method(Suit suit) { import static ....Suit.*;
[Link](suit); ...
... method(CLUBS);
} ...
21
Consider enums Instead of Constants
enums can also define their own instance variables and methods, so you
can remove the if-else blocks from the clients and instead push specific
behaviors or values to the enums.
See code example...
22
Another advantage of enums is of course the ability to add behavior to the
enums!
enum Suit {
CLUBS("Club"), SPADES("Spade"), HEARTS("Heart"), DIAMONDS("Diamond");
String name;
Suit(String name){
[Link] = name;
}
void printValue(){
return name;
}
}
23
Java Collection Framework
In Java, it lets you put objects together so you can go through them or
contain them in one variable.
● Collection: groups of objects together
● Map: Key-Value pairs
24
Knowing the right List
● ArrayList: fast for add(), get(), and set() elements. The basic
implementation of a List.
● LinkedList: fast for inserting (in between), remove().
● Vector: synchronized arraylist, locks object for one thread at a time. It is
not recommended to use this, as this is an old object back when JAVA
was not multi-user.
25
Knowing the right Set
● HashSet: a Set with no added special handling. The basic
implementation of a Set.
● TreeSet: a Set which orders the elements in their natural order.
● LinkedHashSet: a Set which maintains the order of the objects added to
it. Slower than HashSet since it uses both a LinkedList and a HashSet
internally. Not recommended.
26
Knowing the right Queue
Queue collects elements which you can offer() and then poll().
Examples:
● LinkedList: Also a queue, in which the first in is given first in first out
order. Basic implementation of a Queue.
● PriorityQueue: An implementation of a queue which rearranges offered
elements to their natural order once polled. They are still unordered
when you print it out.
27
Knowing the right Queue
● BlockingQueue: An interface of a queue with which you can set a limit
to the number of elements added. Note that no Exception is thrown
when using offer(), but prevents it from being added anyway. Exception
only appears when using add().
● Deque: An interface that does what a Queue can do, but also offers a
LIFO order, by using push() and pop() instead, respectively.
28
Knowing the right Map
Maps allow you to store key-value pairs, used when maintaining behavior
which requires keys and returns a certain value.
● HashMap - The basic implementation of the Map.
● TreeMap - a Map which orders elements in their natural order.
● LinkedHashMap - A map which maintains the order of the elements
added when printing them out. Slower in that it has both a LinkedList
and a HashMap internally.
29
Iterator vs For-Loops in a Collection
In order to use collections effectively, we must be able to traverse it's
elements effectively to perform functions.
There are many ways to do this. But the most common ones are:
● For Loop
● For Each Loop
● Iterators
● Streams (Java 8+)
30
Iterator vs For-Loops in a Collection
● For Loop - given a collection, it's more efficient to use a For Each
● For Each Loop - recommended for traversing through collections
● Iterator - use this if you plan to add/delete elements in a collection
during traversal
● Streams - it's just a shortcut, but essentially a for each loop. Utilizes
Java Functions.
31
Create Defensive Copies
class Student {
private Set<Section> sections;
Student(Set<Section> sections) {
[Link] = sections;
}
Set<Section> getSections() {
return sections;
}
Dangerous since other objects get a reference to the internal Set.
32
Create Defensive Copies
class Student {
private Set<Section> sections = new HashSet<>();
Student(Set<Section> sections) {
[Link](sections);
}
Set<Section> getSections() {
return new HashSet<>(sections);
}
}
Use copy-constructor to create copies. Now internal Set is only accessed by
the object itself.
33
Create Defensive Copies
Even if the parameter is used just locally, make a defensive copy, since
another thread might modify the parameter mid-operation.
void method(List<String> names) {
names = new ArrayList<String>(names);
...
}
34
Favor Immutable Fields (Continues next slide)
Make fields immutable whenever possible - thread safe & less error-prone.
class Transaction {
private int id;
private Date transactionDate;
int getId() {
return id;
}
void setId(int id) {
[Link] = id;
}
...
35
Favor Immutable Fields
Make fields immutable whenever possible - thread safe & less error-prone.
...
Date getTransactionDate() {
return transactionDate;
}
void setTransactionDate(Date transactionDate) {
[Link] = transactionDate;
}
}
36
Favor Immutable Fields
Start by making fields final & removing unneeded setters.
class Transaction {
private final int id;
private final Date transactionDate;
Transaction(int id, Date transactionDate) {
[Link] = id;
[Link] = transactionDate
}
int getId() {
return id;
}
Date getTransactionDate() {
// this is still a problem since [Link] is mutable
return transactionDate;
}
} 37
Favor Immutable Fields
Don't return a reference to a mutable object. Instead, return a copy.
class Transaction {
private final int id;
private final Date transactionDate;
Transaction(int id, Date transactionDate) {
[Link] = id;
[Link] = new Date([Link]());
}
int getId() {
return id;
}
Date getTransactionDate() {
return new Date([Link]());
}
}
38
Favor Immutable Fields
Or use types that can be made immutable.
class Transaction {
private final int id;
private final long longTransactionDate;
Transaction(int id, Date transactionDate) {
[Link] = id;
[Link] = [Link]();
}
int getId() {
return id;
}
Date getTransactionDate() {
return new Date(longTransactionDate);
}
}
39
Favor Immutable Fields
Considerations
Note that some technologies do not support final fields. The JPA standard
says fields cannot be final, although Hibernate works fine with final fields.
If you are unable to make your fields final, at least remove mutators
wherever possible.
40
For Domain Classes, Always Override toString()
Override toString() so that you can directly embed objects in Strings to
make meaningful messages.
This is especially useful for helpful error messages for debugging.
41
class Address {
private final int no;
private final String street;
private final String city;
private final String region;
...
@Override
public String toString() {
return no + ' ' + street + ", " + city + ", " + region;
}
...
Address addr = new Address(58, "Sta. Teresita", "Pasig", "NCR");
[Link](addr); // 58 Sta. Teresita, Pasig, NCR
42
class Cart {
private final int cartId;
private final Map<Product, Integer> productAndQuantity = new HashMap<>();
...
@Override
public String toString() {
return "Cart #" + cartId;
}
...
Cart cart1 = new Cart(1);
[Link](HAT, 2);
[Link](BAG, 3);
[Link](cart1); // Cart #1
For entities, normally just the ID is enough. Using too many fields can lead
to strings that are too long, or worse, infinite loops.
43
For Domain Classes, Always Override equals() & hashCode()
Do you prefer this…
[Link]().toString().equals([Link]().toString())
&& [Link]().equals([Link]())
…or this…
[Link](money2);
44
For Domain Classes, Always Override equals() & hashCode()
But wait! This is the contract of the equals() method, according to its
Javadoc:
● It is reflexive: for any non-null reference value x, [Link](x) should
return true.
● It is symmetric: for any non-null reference values x and y, [Link](y)
should return true if and only if [Link](x) returns true.
● It is transitive: for any non-null reference values x, y, and z, if
[Link](y) returns true and [Link](z) returns true, then [Link](z)
should return true.
45
For Domain Classes, Always Override equals() & hashCode()
● It is consistent: for any non-null reference values x and y, multiple
invocations of [Link](y) consistently return true or consistently
return false, provided no information used in equals comparisons on
the objects is modified.
● For any non-null reference value x, [Link](null) should return false.
46
For Domain Classes, Always Override equals() & hashCode()
Also, the hashCode() method tied to equals() since part of its contract is that
if two objects are equal, then their hashCode should be the same.
Another part of the hashCode() contract is that an instance should always
return the same hashCode.
hashCode() is not meant to be used by application programmers, rather it is
used internally by JDK classes, such as Collections. In Collections,
hashCode() determines an object's position in many Collections, and allows
it to be retrievable from the Collection. If your hashCode() method is not
in-sync with your equals() method, your objects may not be retrievable
from Sets and Maps.
47
For Domain Classes, Always Override equals() & hashCode()
Fortunately, IDEs make it easy to implement equals() & hashCode().
In Eclipse, just open the class you want to add equals() & hashCode(), then
right-click anywhere in the text editor to show the context-menu.
On the context-menu, click Source → Generate hashCode() and equals()....
Choose the fields that you want to be the basis of your equals() &
hashCode().
Continued on next page...
48
For Domain Classes, Always Override equals() & hashCode()
To conform to the contracts, pick the field or fields in a class that represent
an instances identity or value. Also, these fields should be immutable.
● For entities, just choose the ID.
● For value objects, you may use all fields, but make them all final.
Click the OK button and the methods will be generated.
See code example...
49
Avoid Passing Null
● Nulls can cause unexpected problems.
● NullPointerExceptions (NPEs) tend
to be hard to debug.
● NPEs can happen anywhere, so it's
hard to validate for every possible case.
● Programmers should strive to
avoid having variables that are set
to null, or passing null values.
50
Avoid Passing Null
Avoid empty declarations wherever possible. Unless a variable is final,
assign an initial value upon declaration:
private BigDecimal amt;
private BigDecimal amt = [Link];
private String name;
private String name = "";
51
Avoid Passing Null
Avoid empty declarations wherever possible. Unless a variable is final,
assign an initial value upon declaration:
private int[] arr;
private int[] arr = {};
private List list;
private List list = [Link]();
52
Avoid Passing Null
If your method's return type returns String,
String findName() {
...
if (...) { // name not found
return null;
return "";
}
}
53
Avoid Passing Null
If your method's return type is an array or collection, return an empty array
or collection instead of null.
String[] findNames() {
...
if (...) { // names not found
return null;
return new String[0];
}
}
Collection<String> findNames() {
...
if (...) { // names not found
return null;
return [Link]();
}
}
54
Avoid Passing Null
Use Optional
Optional<Student> findByStudentNumber(int studentNumber) {
Student student = ... // search for a student in DB, might return null
return [Link](student);
}
55
Avoid Passing Null
Use Optional
class Student {
private final Integer studentNumber;
private Optional<String> major;
...
Student(Integer studentNumber, String major, ...) {
[Link] = studentNumber;
[Link] = [Link](major);
...
56
Avoid Passing Null
before Java 8: Null Object Pattern.
public class Student {
public final static Student NONE = new Student(0);
private final int studentNo;
public Student(int studentNo) {...}
}
Student findStudent() {
...
if (...) { // student not found
return null;
return [Link];
}
}
57
Never Have Empty Catch Blocks
try {
[Link]();
} catch (FileNotFoundException e) {
// No more errors!!! Wheeee!!!
}
58
Don't Handle Exceptions at the Wrong Layer
try {
[Link]();
} catch (FileNotFoundException e) {
[Link]();
}
try {
[Link]();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
59
Catch Specific Exceptions
Wrong: Right:
try { try {
[Link]();
[Link](); } catch (FileNotFoundException e) {
} catch (Exception e) { ...
... } catch (MalformedUrlException e) {
} ...
} catch (DocumentException | DOMException e) {
...
}
60
Close Resources in finally Block, or Use try-with-resources
Before Java 7:
Connection conn = null;
try {
conn = [Link]();
PreparedStatement pstmt = [Link](sql);
...
} catch (SQLException e) {
...
} finally {
try {
[Link]();
} catch (SQLException e) {
...
}
}
61
Close Resources in finally Block, or Use try-with-resources
try-with-resources (Java 7):
try (Connection conn = [Link]()) {
PreparedStatement pstmt = [Link](sql);
...
} catch (SQLException e) {
...
}
62
Embed Debugging Info in Error Message
if ([Link](newSec)) {
throw new EnlistmentConflictException("Current Section: " + current
+ ", New Section: " + newSec);
}
63
Use Exception Translation
So that method signatures don’t change.
Initially, data stored in files:
List getData() throws FileNotFoundException {...}
Client code:
void bizOperation() {
try {
List data = getData();
...
} catch (FileNotFoundException e) {
...
}
}
64
Use Exception Translation
So that method signatures don’t change.
Initially, data stored in files:
List getData() throws FileNotFoundException {...}
Client code:
void bizOperation() throws FileNotFoundException {
List data = getData();
}
65
Use Exception Translation
So that method signatures don’t change.
Then data was moved to a database, accessed via JDBC:
List getData() throws SQLException {...}
Client code no longer compiles:
void bizOperation() {
try {
List data = getData();
...
} catch (FileNotFoundException e) {
...
void bizOperation() throws FileNotFoundException {
List data = getData();
} 66
Use Exception Translation
So that method signatures don’t change.
What happens if the data-access behavior changes again?
The problem is we broke encapsulation by revealing the implementation.
We also broke the principle of Separation of Concerns, since we allowed the
persistence concern to leak elsewhere. This is called a "leaky abstraction".
We therefore need to wrap or nest the exception produced into one that
won't break the method signature.
67
Use Exception Translation
So that method signatures don’t change.
The simplest way would be to wrap the exception in a RuntimeException or
a subtype of RuntimeException:
List getData() { // no need to declare method in signature
try {
...
} catch (FileNotFoundExcepton e) {
throw new RuntimeException("...message...", e);
}
}
68
Use Exception Translation
So that method signatures don’t change.
You can also create a custom Runtime exception to be more descriptive,
without describing implementation details.
class DataAccessException extends RuntimeException {...}
List getData() { // no need to declare method in signature
try {
...
} catch (FileNotFoundExcepton e) {
throw new DataAccessException("...message...", e);
}
}
69
Use Exception Translation
So that method signatures don’t change.
Even if the the implementation changes, the method signature doesn't
change, so client code is insulated.
List getData() { // no change in method signature
try {
...
} catch (FileNotFoundException e) {
} catch (SQLException e) {
throw new DataAccessException("...message...", e);
}
}
void bizOperation() {
List data = getData();
...
}
70
Use Exception Translation
So that method signatures don’t change.
However, some organizations want to use checked exceptions. In which
case, you really need to create custom exceptions.
class DataAccessException extends Exception {...}
List getData() throws DataAccessException {
try {
...
} catch (FileNotFoundExcepton e) {
throw new DataAccessException("...message...", e);
}
}
71
Use Exception Translation
So that method signatures don’t change.
Client layer will need to have to its own custom exceptions, and do its own
exception translation:
class BizException extends Exception {...}
void bizOperation() throws BizException {
try {
List data = getData();
...
} catch (DataAccessException e) {
throw new BizException("...message...", e);
}
}
72
Again, even if the the implementation changes, the method signature
doesn't change, so client code is insulated.
List getData() throws DataAccessException { // no change in method signature
try {
...
} catch (FileNotFoundException e) {
} catch (SQLException e) {
throw new DataAccessException("...message...", e);
}
}
void bizOperation() throws BizException {
try {
List data = getData();
...
} catch (DataAccessException e) {
throw new BizException("...message...", e);
}
}
73
Avoid Synchronized Methods
class Student {
private final Integer studentNumber;
private final String firstname;
private final String lastname;
private final Collection<Section> sections = new HashSet<>();
void enlist(Section newSection) {
if ([Link]() >= 8) {
throw new MaxSectionsException("cannot enlist more than 8 sections");
}
[Link]( // check for schedule conflicts
currSection -> [Link](newSection));
[Link](newSection)
}
}
74
Avoid Synchronized Methods
class Student {
private final Integer studentNumber;
private final String firstname;
private final String lastname;
private final Collection<Section> sections = new HashSet<>();
void synchronized enlist(Section newSection) {
if ([Link]() >= 8) {
throw new MaxSectionsException("cannot enlist more than 8 sections");
}
[Link]( // check for schedule conflicts
currSection -> [Link](newSection));
[Link](newSection)
}
}
75
Minimize the Scope of Synchronized Block
class Student {
private final Integer studentNumber;
private final String firstname;
private final String lastname;
private final Collection<Section> sections = new HashSet<>();
void enlist(Section newSection) {
synchronized (sections) {
if ([Link]() >= 8) {
throw new MaxSectionsException("cannot enlist more than 8 sections");
}
[Link]( // check for schedule conflicts
currSection -> [Link](newSection));
[Link](newSection)
}
}
}
76
Minimize the Scope of Synchronized Block
class Student {
private final Integer studentNumber;
private final String firstname;
private final String lastname;
private final Collection<Section> sections = new HashSet<>();
void enlist(Section newSection) {
[Link]( // check for schedule conflicts
currSection -> [Link](newSection));
synchronized (sections) {
if ([Link]() >= 8) {
throw new MaxSectionsException("cannot enlist more than 8 sections");
}
[Link](newSection)
}
}
}
77
Do Not Synchronize DB Calls
Do not do the job of the DB in your application!!!
private static final Object lock = new Object();
public void reserveSeats(Collection<Seat> seats, Customer customer) {
String sql = "UPDATE seats SET reserved_for = ? WHERE seat_id = ?";
try (Connection conn = [Link]();
PreparedStatement stmt = [Link](sql)) {
synchronized(lock) {
for (Seat set : seats) {
[Link](1, [Link]());
[Link](2, [Link]());
[Link]();
}
[Link]();
}
} catch (SQLException e) {
throw new RuntimeException("problem while reserving seats "
+ seats + " for customer " + customer, e);
}
}
78
Do Not Synchronize DB Calls
Do not do the job of the DB in your application!!!
● In high-concurrency environments, response-time will suffer because
threads would be have to line up to execute the synchronized block.
Note that I/O calls will be slow compared to in-memory calls.
● Locking will be bypassed if there is more than one application instance
connecting to the DB.
● Transactional operations will require rollback in case of errors. Very
difficult to implement on the application level.
DBs will be much more efficient & less error-prone in implementing in
record-locking & transactional operations.
Java persistence, transactions, & locking are discussed in more detail in
Java Enterprise Fundamentals & Best Practices.
79
This is by no means an exhaustive set of Java practices. If anything, it's a
drop in the bucket. Continue reading on coding practices, for Java in
general and for specific technologies, problem domains and
implementation domains.
Some recommended readings:
Java Practices website - [Link]
80
Exercise
Download and setup the Eclipse project: CodingPracticesExercise
1. Download the project and unpack it into your workspace directory.
2. Press Shift-Alt-N to open the "New" menu.
3. Select "Java Project".
4. In the "Project name:" field, type "CodingPracticesExercise".
5. Press the "Finish" button.
Study the code and look for ways to improve it. There will be clues in the
"TODO" comments.
81