Effective Java : General programming

General Programming

Objective: This section is devoted to the nuts and bolts of Java.

Key topics:

  1. Scope of Local Variables
  2. For-Each Loops and Traditional For Loops
  3. Use of Libraries
  4. Float, Double, and Exact Calculations
  5. Primitive Types and Boxed Primitives
  6. Use of Strings and Other Types
  7. String Builder and String Concatenation
  8. Interface and Class References
  9. Optimization
  10. Naming Conventions

Estimated time: 15-30 minutes.

Scope of Local Variables

Can you spot a bug in one of the following two code segments?

 Local variable with minimal scope

for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
Element e = i.next();
… // Do something with e and i
}

for (Iterator<Element> i2 = c2.iterator(); i2.hasNext(); ) {
Element e2 = i2.next();
… // Do something with e2 and i2
}

 Local variable with method scope

Iterator<Element> i = c.iterator();
while (i.hasNext()) {
Element e = i.next();
… // Do something with e and i
}

Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) {
Element e2 = i2.next();
… // Do something with e2 and i2
}

The second implementation above uses  i  instead of  i2  in the second loop, causing a bug at runtime. This bug can be caught at compile time if you take the following advice: Use local variable with minimal scope, so as to increase the readability and maintainability of your code, as well as to reduce the likelihood of silly, but sometimes disastrous, bugs. Here are several ways to do it:

  • Declare the scope of a local variable where it is first used.
  • Initialize every local variable.
  • Prefer  for  loops to  while  loops.
  • Keep methods small and focused on a single task.

For-Each Loops and Traditional For Loops

Can you spot a bug in one of the following two code segments?

 Traditional for loop

enum Suit { Club, Diamond, Heart, Spade }
enum Rank { Ace, Deuce, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King }

List<Card> deck = new ArrayList<>();
for (Iterator<Suit> s = suits.iterator(); s.hasNext(); )
for (Iterator<Rank> r = ranks.iterator(); r.hasNext(); )
deck.add(new Card(s.next(), r.next()));

 For-each loop

for (Suit s : suits.values())
for (Rank r : ranks.values())
deck.add(new Card(s, r));

 For  loops are usually better than  while  loops, but they are not perfect. Use  for-each  loop, or enhanced  for  statement, so as to increase the readability and maintainability of your code further, and thus reduce the likelihood of errors such as the one in the first implementation above where  s.next()  is wrongly executed in the second loop.

Use of Libraries

Which of the following options would you prefer to generate random integers?

 Write a random generator method

static Random myRandom = new Random();

static int generateRandom(int upperBound) {
return Math.abs(myRandom.nextInt()) % upperBound;
}

int randomInt = generateRandom(1000);

 Use a library

int randomInt = ThreadLocalRandom.current().nextInt();

Obviously, use of library would be desirable. In general, use of libraries would give you the following advantages:

  • The knowledge of experts who implemented it and the experience of others who used it before you.
  • New features that are added to the libraries in every major release.
  • Better performance that is implemented by experts.

Float, Double, and Exact Calculations

Can you spot a bug in one the following code segments?

 Use of floating point for monetary calculation

int getPossibleBoughtItems() {
double dollarFunds = 1.0;
int numItems = 0;
for (double price = 0.10; dollarFunds >= price; price += 0.10, dollarFunds -= price) {
numItems++;
}
return numItems;
}

 Use of int for monetary calculation

int getPossibleBoughtItems() {
int centFunds = 100;
int numItems = 0;
for (int price = 10; centFunds >= price; price += 10, centFunds -= price) {
numItems++;
}
return numItems;
}

You should use  BigDecimal  int , or  long  for exact calculations. You should avoid using  float  and  double  for exact calculations because they are carefully designed for accurate approximations in scientific and engineering calculations. Note that the first method above will return 3, which is wrong, with funds left of $0.3999999999999999.

Primitive Types and Boxed Primitives

Which of the following two methods runs faster?

 Use of boxed primitives

Long getSumOfPositiveIntegers() {
Long sum = 0L;
for (long i = Integer.MAX_VALUE; i > 0; i–) {
sum += i;
}
return sum;
}

 Use of primitives

long getSumOfPositiveIntegers() {
long sum = 0;
for (long i = Integer.MAX_VALUE; i > 0; i–) {
sum += i;
}
return sum;
}

Whenever possible, you should use primitives, instead of boxed primitives, because:

  • Unnecessary use of boxed primitives may result in a hideously slow program because of repeatedly boxed and unboxed operations such as in the one above.
  • When unboxing is run, it can throw a  NullPointerException .
  • When boxes primitives are used, applying  ==  operator is almost always wrong and can lead to deadly bugs that are difficult to discover.

Use of Strings and Other Types

Which of the following two implementations would be correct?

 Use of a parameterized class

public final class ThreadLocal<T> {
public ThreadLocal() {};
public void set (T value) {…};
public T get() {…};
}

 Use of string as capacity

public final class ThreadLocal {
private ThreadLocal() {};
public static void set (String key, Object value) {…};
public static Object get(String key) {…};
}

Whenever possible, you should avoid using  String , because it is poor substitutes for other value types, or aggregate types, or capacity types. It is cumbersome, slower, error-prone, and inflexible than other types. Note that in the second implementation above, the string keys represent a shared namespace for thread-local variables, leading to failure and security problems (multiple clients can fail if they use the same key, and a malicious client can gain illicit access to another client’s data).

String Builder and String Concatenation

Which of the following two implementations would be better?

 Use of string builder

public String firstNamesToString(List<Person> members) {
StringBuilder sb = new StringBuilder();
for (Person p : members) {
sb.append(“[“);
sb.append(p.firstName);
sb.append(“]”);
}
return sb.toString();
}

 Use of string concatenation

public String firstNamesToString(List<Person> members) {
String s = “”;
for (Person p : members) {
s += “[” + p.firstName + “]”;
}
return s;
}

In general, you should avoid using string concatenation because of its poor performance, unless if you want to concatenate only a few strings or if performance is irrelevant.

Interface and Class Reference

Which of the following two method signatures would be better?

 Use of interface reference

public List<Person> getPeopleByFirstName(List<Person> members, String firstName) {}

 Use of class reference

public ArrayList<Person> getPeopleByFirstName(LinkedList<Person> members, String firstName) {}

It would be desirable, more flexible, and more backward-compatible to

  • use interface types to refer to parameters, return values, variables, and fields if appropriate interface types exist,
  • use least specific class types to refer to parameters, return values, variables, and fields if appropriate interface types do not exist.

Optimization

Which of the following two implementations would be faster?

 Use of a regular pattern

public class RomanNumber {
static boolean isRomanNumber(String s) {
return s.matches(“^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$”);
}
}

 Use of a precompiled pattern

public class RomanNumber {
private static Pattern ROMAN = Pattern.compile(“^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$”);

static boolean isRomanNumber(String s) {
return ROMAN.matcher(s).matches();
}
}

The use of a precompiled pattern can improve the performance by several times faster, because it creates an immutable  Pattern , precompiles it, and reuses the same instance for every invocation of  isRomanNumber  method. Note that optimization is very hard to achieve; premature optimization could be the root of all evil; so, don’t do it unless you have very clear evidence (e.g., after you compare run time of the above two implementations). Here are some suggestions:

  • Strive to write good programs rather than fast ones, because a good architecture would allow to optimize it easily in the long run.
  • Strive to avoid design decisions that limit performance, because once those design decisions are made they are hard to be changed and optimized later (e.g., APIs that are exposed to clients).
  • Measure the performance carefully before and after each attempted optimization (e.g., use profiling).

Naming Conventions

Which of the following two implementations would be more readable?

 Naming conventions don’t really matter

package com.amazon.Session3.Repository;

import com.amazon.Session3.Model.customer;
import java.util.List;
import java.util.Optional;

public interface customerRepository {
public List<customer> Findallcustomers();
public Optional<customer> FindCustomerbyid(Integer CUSTOMERID);
}

 It is useful to adhere to generally accepted naming conventions

package com.amazon.session3.repository;

import com.amazon.session3.model.Customer;

import java.util.List;
import java.util.Optional;

public interface CustomerRepository {

public List<Customer> findAllCustomers();

public Optional<Customer> findCustomerById(Integer id);

}

You should rarely violate typographical naming conventions and never without a good reason. There are only a handful of them, as follows:

  • Use lower case for packages or modules, for example  com.amazon.common.collection .
  • Use title case for classes and interfaces, for example  HashMap .
  • Use title case, except for the first letter for methods for example,  getFirstName() .
  • Use title case, except for the first letter, for fields and local variables, for example  i, isChecked, numOfItems .
  • Use upper case for constant fields, for example MAX_VALUE.
  • Use one capital letter with an optional digit for type parameters, for example  T, E, K, V, T1, T2 .

Grammatical naming conventions are more flexible, but it would be desirable that you and your team make them consistently, for example, always start a method name with a verb.

Leave a Reply

%d bloggers like this: