The Startup
Published in

The Startup

Postal Codes as an Example of Abstract Classes in Java

Photo by Mick Haupt on Unsplash

Animals are frequently used to illustrate abstract classes in Java. Maybe we have abstract classes for animals, chordates, mammals, canines and dogs, drilling down to “concrete” classes for various breeds of dogs.

It’s a pedagogically valuable example, but it’s also such a toy example. What’s an instance of Labradoodle gonna do? Write “Woof, woof!” or “Arf, arf!” on the console?

Maybe postal codes might feel like an example likelier to have practical value, such as making a mailing label program smart enough to distinguish a mailing address in London, Ontario from a mailing address in London, Texas.

By using an example that is at least slightly realistic, it’s easier to see how we can apply this concept in our own projects.

We could just use String for each and every field of the MailingAddress class. But that would be missing the point of object-oriented programming.

Instead, we should create an abstract PostalCode class that can hopefully be subclassed to represent any postal code for any mailing address in the world.

package postal;import java.util.Locale;public abstract class PostalCode {

protected final long postalCodeNumber;

protected final Locale postalGov;

public Locale getCountry() {
return this.postalGov;
}
public PostalCode(long number, Locale loc) {
this.postalCodeNumber = number;
this.postalGov = loc;
}

}

The use of a 64-bit signed integer presents no problem for postal codes that include letters.

For example, the London postal code WC2H 0AW can be interpreted as the integer 70386781640 in base 36; the London, Ontario postal code N5V 0A2 can be interpreted as the integer 1400566826 in base 36.

Of course this should be subclassed for each country or group of countries that we might want to use MailingAddress for, and that’s the point of making PostalCode an abstract class.

Because PostalCode is abstract, we can’t instantiate it as

PostalCode code = new PostalCode(70386781640, Locale.UK);

And in any case, the PostalCode class has no way of knowing whether 70386781640 represents a valid postal code in the specified location, and it shouldn’t have that capability.

The applicable constructor for WC2H 0AW or N5V 0A2 would take care of interpreting the letters in a way that can be passed on to the PostalCode constructor.

Remember: an abstract class can have a constructor which the subclasses can use so that the programmers don’t have to reinvent and rewrite what all these classes have in common.

I don’t know if British post codes and Canadian post codes are on the same system, but this is not an issue we have to figure out when designing the general abstract class.

For mailing addresses in the United States, the postal codes are called ZIP codes. ZIP codes consist of digits only, with a dash to separate the ZIP+4 “extension” when that is given.

Here are some of the requirements specific to the ZIPCode class:

  • Its primary constructor should take a number in the range 00000 to 99999 (the “general” ZIP code) and a number in the range 0000 to 9999 (the ZIP+4).
  • It should have an auxiliary constructor that takes in a number in the range 00000 to 99999 and fills in 0000 for the ZIP+4.
  • Negative numbers for either parameter should cause an exception.
  • Numbers in excess of 99999 for the “general” ZIP code and numbers in excess of 9999 for the ZIP+4 should also cause an exception.
  • The toString() function should return the ZIP code formatted properly (e.g., “02111–1307”). If the ZIP+4 was given as 0000, it should be omitted (e.g., “90210” for 90210–0000).
  • The getCountry() function (inherited from the superclass) should always return Locale.US.

The way I’m envisioning this, the Locale field for a ZIPCode instance should always be Locale.US even if it’s for an U. S. embassy or military installation overseas (and in any case, American embassies are considered to be on American soil for the purposes of international law anyway).

For example, the ZIP code for an address in Camp Foster, Okinawa, might be 96379, even though addresses outside the base might have Japanese postal code 〒904–0116.

So, if you’re going about this through test-driven development (TDD), the first draft of the ZIPCode constructor should pass null or an obviously wrong Locale instance (such as Locale.FRENCH) to the PostalCode constructor.

Since ZIPCode is a “concrete” class, your integrated development environment (IDE) should have no trouble whatsoever creating ZIPCodeTest for you and hooking up the necessary JUnit and Hamcrest or OpenTest JARs.

You should have no problem coming up with the test for getCountry(). The tests for toString() are also fairly easy to come up with, so I won’t be giving those here.

Given all the specific requirements for toString() in ZIPCode, it makes sense to implement the toString() override in ZIPCode rather than PostalCode. Other PostalCode subclasses will likely have different needs for toString(), like making sure the letters are uppercase in the British post codes.

Considerations of equality and hash codes

Sooner or later, we’ll need to override equals() in ZIPCode. Here are the requirements for equals():

  • It should return true for referential equality.
  • It should return false for comparing to null.
  • It should return false if the runtime classes don’t match (e.g., this is a ZIPCode instance and obj is a JapanesePostalCode instance).
  • If the runtime classes match and the postalCodeNumber fields match, it should return true.

Go ahead and write the pertinent tests in ZIPCodeTest.

Clearly these requirements are easily generalized to the PostalCode class, so rather than repeat the same equals() override in each subclass of PostalCode, I think we should place the override in PostalCode rather than any specific subclass.

However, by the principles of TDD, our tests in ZIPCodeTest don’t really require us to write a comparison of the postalGov field in equals(). Since that’s supposed to always be Locale.US for all instances of ZIPCode, there’s no point comparing those if the getClass() comparison returned true.

It might be somewhat of a problem for our Canadian post code class, since Locale.CANADA is technically for English-speaking Canada. On the other hand, however, the “number” for a post code in Quebec should not match the “number” for a post code anywhere else in Canada.

For hashCode(), we should probably use the postalGov field. And while the “contract” for equals() and hashCode() does not seem to require using the same fields for both functions (the IDEs assume this but allow us to change it), it’s probably best to always thoroughly justify deviations from this practice.

While I think the hashCode() override should also be in PostalCode rather than ZIPCode, I also think the tests for hashCode() should be in the test classes corresponding to concrete classes.

It is perhaps too much to expect unique hash codes for all possible instances of PostalCode, but it is perhaps reasonable to expect instances of a specific subclass to be unique.

For the test of ZIPCode hash codes, I recommend you pick one “general” ZIP code, like 90210, have the test class put each ZIP+4 within that “general” ZIP code in a set and the hash codes for those in another set. The two sets should be of the same size.

Nonetheless, I’m thinking there should be a PostalCodeTest class for PostalCode even though that’s an abstract class.

Making a test class for an abstract class in Eclipse

At the most fundamental level, Eclipse is the same as the two other major Java IDEs (NetBeans and IntelliJ IDEA). Eclipse bundles a source editor, test runner, debugger and other utilities into one convenient program.

When it comes to actually using Eclipse, there are enough small but important differences from the other IDEs to trip up unfamiliar users. And also, there might be problems

I admit I haven’t been too keen on keeping my version of Eclipse up-to-date. I’m using 2019–12 (4.14.0). And I’m still using Java 8, mainly because Scala hasn’t yet completely caught up with modules in Java 9. But I do keep Java updated, my system’s on Java 8 Update 261.

I don’t like Eclipse all that much. I find the whole user interface rather confusing, even though it’s not fundamentally different from that of the other two major Java IDEs.

Also, I don’t like how it doesn’t do anything to segregate source and tests, unless you direct it to. And even if you do direct it so, you still have to pay close attention to make sure that it places the test classes in the test folder.

And I really don’t like the way Eclipse reports JUnit test results. Still, everything that you can do in NetBeans or IntelliJ you can surely also do in Eclipse, even if it’s much less intuitive.

Maybe that includes the testing of abstract classes. If you have the PostalCode class skeleton in the project’s source folder, go ahead and add a skeleton for ZIPCode. Something like this:

package postal;

import java.util.Locale;

public class ZIPCode extends PostalCode {

private static final Locale LOCALE = Locale.ENGLISH;

private final String zipStr;

@Override
public String toString() {
return this.zipStr;
}

public ZIPCode(int zip5) {
this(zip5, -1);
}

public ZIPCode(int zip5, int zip4) {
super(zip5 * 10000 + zip4, LOCALE);
zipStr = Long.toString(this.postalCodeNumber);
}

}

That should fail most of the tests we throw at it. Have Eclipse start off a test class for ZIPCode, make sure it places it in the “Test Packages” “content root” (to mix NetBeans and IntelliJ terminology).

Here are the tests I would start out with, in roughly the order that I would write them. Some of you might recognize the ZIP code 02111–1307 from the mailing address of the Free Software Foundation.

package postal;

import java.util.Locale;

import org.junit.Test;
import static org.junit.Assert.*;

public class ZIPCodeTest {

@Test
public void testToString() {
System.out.println("toString");
ZIPCode zip = new ZIPCode(2111, 1307);
String expected = "02111-1307";
String actual = zip.toString();
assertEquals(expected, actual);
}

@Test
public void testGetCountry() {
System.out.println("getCountry");
ZIPCode zip = new ZIPCode(96379);
assertEquals(Locale.US, zip.getCountry());
}
@Test
public void testReferentialEquality() {
ZIPCode zip = new ZIPCode(2111, 1307);
assertEquals(zip, zip);
}

@Test
public void testNotEqualsNull() {
ZIPCode zip = new ZIPCode(2111, 1307);
assertNotEquals(zip, null);
}

}

With these tests so far, the equals() override can comfortably sit in PostalCode rather than ZIPCode. Here’s what I’ve got for equals() in PostalCode right now:

    @Override
public boolean equals(Object obj) {
return (this == obj);
}

I suggest the next test in ZIPCodeTest be for two ZIPCode instances that are equal in their fields, but not having referential equality.

    @Test
public void testEquals() {
System.out.println("equals");
ZIPCode someZIP = new ZIPCode(2111, 1307);
ZIPCode sameZIP = new ZIPCode(2111, 1307);
assertEquals(someZIP, sameZIP);
}

This fails on the first run, as it should, though with the potentially confusing test failure message “expected: postal.ZIPCode<02111-1307> but was: postal.ZIPCode<02111-1307>”.

No need to compare the fields of this and obj at this point, nor even check their runtime classes, to get this test to pass.

    @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
return true;
}

At this point, both NetBeans and IntelliJ would be warning us that this equals() is not checking the type of obj. But Eclipse seems to not care about this omission.

To motivate checking the type of obj, I generally write a test in which an instance of the class under test is improbably compared to an object of a type it would not normally be compared to, like an SQLClientInfoException.

Such a comparison is unlikely to occur in production, but nothing forbids it from ever occurring. Still, it might seem much more useful to test that a ZIPCode instance does not read as equal to an instance of a class for a postal code from another country.

For example, could 〒904–0116 be confused for the ZIP code 00904–0116? Maybe, since the postalCodeNumber field would be the same, although the latter is not currently a ZIP code recognized by the U. S. Postal Service.

More importantly, however, we haven’t yet created concrete classes to represent any other country’s postal code numbers. So, rather than digress into another country’s postal system, let’s create a class in Test Packages to represent a fictional postal system.

I suggest Zap codes. Zap codes are a lot like ZIP codes, but with these key differences:

  • Each Zap code should always be prefixed by “Zap!” and at least one space.
  • There are no leading zeroes for the “general” Zap.
  • There is no dash before the Zap+4.
  • The Zap+4 is always given, even when it’s 0000.
  • And, most importantly of all, Zap codes must not be available for release or deployment. After all, Zap codes are probably copyright infringement (not that I’m worried about being sued by the criminally underfunded U. S. Postal Service).

So create the ZapCode class in Test Packages. Something like this should suffice for our purpose:

package postal;

import java.util.Locale;

class ZapCode extends PostalCode {

@Override
public String toString() {
return "Zap! " + this.postalCodeNumber;
}

ZapCode(int number) {
super(number, Locale.US);
}

}

You probably already know what test I’m going to write next. I’m going to mix it up just slightly by using the ZIP code for the Beverly Hills City Council.

    @Test
public void testNotEqualsDiffClass() {
ZIPCode zip = new ZIPCode(90210, 4817);
ZapCode zap = new ZapCode(902104817);
assertNotEquals(zip, zap);
}

But… where should this test be placed? You could create a ZapCodeTest class if you wanted to. It would be easiest to put this new test in ZIPCodeTest.

But this test is really about PostalCode, about how that class should guarantee that instances of different subclasses are not to be considered equal even if they match in all their fields. Therefore, this test really should go in PostalCodeTest, not the test class for any of PostalCode’s subclasses.

So, create PostalCodeTest, making sure to place it in Test Packages, and put testNotEqualsDiffClass() in it. The most straightforward way to do this is to right-click PostalCode in the Package Explorer and select New > JUnit Test Case… from the pop-up menu. In the dialog box, be sure to select the test folder, not the source folder.

Of course PostalCodeTest is a “concrete,” not abstract, class. Once you have the test in PostalCodeTest, run it and see it fail. Amend equals() in PostalCode thus:

    @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
return this.getClass().equals(obj.getClass());
}

This should pass all the tests so far.

I think the rest of the tests for equals() can go in ZIPCodeTest, even though we’ll write the actual implementation in PostalCode.

However… an argument could be made that all these tests for equals() belong in PostalCodeTest, not ZIPCodeTest, since the pertinent expectations probably apply to all subclasses of PostalCode.

There is no problem making test classes for abstract classes in NetBeans or IntelliJ, as I’ll explain in another article.

--

--

--

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +756K followers.

Recommended from Medium

To be fair to JavaScript…

Building Lexi: Greek Word of the Day

How to get value from response and refer in a request in Postman

Providing a TypeScript AWS Lambda function as npm library

Handy Naming Conventions for Event Handler Functions & Props in React

Things to Consider When You Build Your First React Native App: 1) Expo CLI vs React Native CLI

Using Meteor with Postgresql 😮

Adding typescript to existing create react app

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alonso Del Arte

Alonso Del Arte

is a composer and photographer from Detroit, Michigan. He has been working on a Java program to display certain mathematical diagrams.

More from Medium

Exception handling In Java.

New Features in Java 18

Right Garbage Collector in Java simplified

Java —  The Introduction & History