Java:Language - Enums

From Juneday education
Jump to: navigation, search

Introduction

This chapter introduces Java Enum and how it can be used. An enum is an "enumerated type", which resembles a class very much.

The purpose of enums is to provide a type with a predefined set of allowed values. A typical example is a type for compass directions:

  • NORTH
  • EAST
  • WEST
  • SOUTH

If those four values are the only valid values for a direction, you can (and should) use an enum type to guarantee type safety (make sure only valid values are passed around).

If we have the direction enum:

public enum Direction {
  NORTH,
  EAST,
  WEST,
  SOUTH
}

Then a method which accepts a Direction argument, cannot be passed an invalid direction:

public void turn(Direction dir) {
  // Do something with dir...
}

It will only be possible to call that method with one of the four possible directions:

// Some client code, calling the turn method
car.turn(Direction.NORTH);

Enum with a custom toString()

All Java enums have a toString() method which returns a String with the name of the constant. Looking at the Direction enum above, printing out Direction.NORTH would trigger the toString() and print "NORTH".

What if we want a different String representation? Just override toString().

Let's say we want a toString() that instead returns the constant name but with a capital first letter and the rest in lower case. We could do this:

  enum Status {
    PUBLIC,
    PROTECTED,
    PRIVATE;

    @Override
    public String toString() {
      return name().charAt(0) +
        name().toLowerCase().substring(1, name().length());
    }
  }

With this implementation of toString(), the toString() of e.g. Status.PROTECTED is "Protected".

An interesting use of an enum

Let's say, we'd like an enum which has some behavior which differs from constant to constant. We can add methods to enums:

  enum StringFrom {
    REVERSE {
      public String of(String s) {
        return new StringBuilder(s).reverse().toString();
      }
    },
    TO_LOWER {
      public String of(String s) {
        return s.toLowerCase();
      }
    },
    TO_UPPER {
      public String of(String s) {
        return s.toUpperCase();
      }
    },
    UPPER_FIRST {
      public String of(String s) {
        return Character.toUpperCase(s.charAt(0)) +
          s.toLowerCase().substring(1, s.length());
      }
    };

    public abstract String of(String s);
  }

With StringFrom, each constant has its own version of the method of(String).

For instance, what do you think the following will print?

System.out.println(StringFrom.UPPER_FIRST.of(StringFrom.REVERSE.of("slipup desserts")));

Expand using link to the right to see the answer.

Stressed pupils

This is how you could use the enum:

    // Make sure the name has a capital first letter
    // and the rest in lower case:
    String name = "rikard";
    String correct = StringFrom.UPPER_FIRST.of(name);
    System.out.println(correct); // Prints "Rikard"

An even more interesting use of enum

Now, a perhaps even more interesting use of an enum is the following example, which lets you repeat a method call which takes a String argument a number of times.

Let's start with how it will be used. If I want to invoke System.out.println five times with the argument of "Are we there yet?", I'd use the enum like this:

Repeat.FIVE.times(System.out::println, "Are we there yet?");

If I want to invoke the same method three times with the argument "Beetlejuice", I'd do:

Repeat.THREE.times(System.out::println, "Beetlejuice");

How can we create such an enum? Here's the source code:

  enum Repeat {
    ONE,
    TWO,
    THREE,
    FOUR,
    FIVE;
    public void times(java.util.function.Consumer<String> con, String s) {
      for (int i = 0; i < ordinal() + 1; i++) {
        con.accept(s);
      }
    }
  }

This is how it works.

First, each constant in an enum has an ordinal number, starting with zero and increasing with one for each constant, in the lexical order as they appear in the source code of the enum.

In this case, all constants have an instance method called times which accepts a java.util.function.Consumer<String> interface reference and a normal String reference.

The thing about Consumer<T>, is that you can pass a method reference for a method that accepts a T argument. Now, System.out::println happens to be a method reference to a method which accepts a String, so it matches the first argument of times.

In the method, we make use of the method ordinal(), which all enum constants have, and which returns the ordinal number for each constant. We use this in a plain old for loop and call accept(s) on the consumer (in our case the reference to System.out.println.

The syntax for calling it looks quite nice too, since we are quite declarative; Repeat.TWO.times(System.out::println, "Ha!");

You can read more about consumers and method references here: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html and here: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html.

Enum with field and constructor

Imagine that you were writing a mail order system and need a class to calculate the delivery cost. There are three types of deliveries, EXPRESS (100 SEK fee), PRIORITY_MAIL (50 SEK fee) and SNAIL_MAIL (10 SEK fee).

You could use an enum for the types of delivery, but you want to associate a fee to each constant. This is how you do it:

  public enum Type {
    EXPRESS(100),
    PRIORITY_MAIL(50),
    SNAIL_MAIL(10);

    Type(int price) {
      this.price = price;
    }

    private int price;

    public int price() {
      return price;
    }

    public String toString() {
      return name() + "(" + price + " SEK)";
    }
  }

Now, if you have a Delivery class, you can make the enum a nested type in the class, so that the name of the enum becomes Delivery.Type (which is rather self-explanatory).

The Delivery could have an instance method which calculates the price for delivery based on two factors:

  • The country to deliver to
  • The type of delivery

Let's for simplicity's sake say that deliveries to Sweden starts at 100 plus the fee for the type, and all other countries cost 200 plus the fee for the type. A simplified instance method double cost() of the Delivery class could then look like this:

  public double cost() {
    double cost = 0.0;
    switch (address.country()) {
      case "Sweden":
        cost += 100;
        break;
      default:
        cost += 200;
    }
    cost += type.price();
    return cost;
  }

Where type is an instance variable of type Delivery.Type (the enum).

Expand using link to the right to see a simplified set-up with a small demo class.

public class Delivery {

  public static class Address {
    private String recipient;
    private String street;
    private String country;
    public Address(String recipient, String street, String country) {
      this.recipient = recipient;
      this.street = street;
      this.country = country;
    }

    public String recipient() {
      return recipient;
    }

    public String street() {
      return street;
    }

    public String country() {
      return country;
    }

    public String toString() {
      return recipient + ", " + street + ", " + country;
    }
  }
  
  public enum Type {
    EXPRESS(100),
    PRIORITY_MAIL(50),
    SNAIL_MAIL(10);

    Type(int price) {
      this.price = price;
    }

    private int price;

    public int price() {
      return price;
    }

    public String toString() {
      return name() + "(" + price + " SEK)";
    }
  }

  private Address address;
  private String goods;
  private Type type;

  public Delivery(Address address, String goods, Type type) {
    this.address = address;
    this.goods = goods;
    this.type = type;
  }

  public double cost() {
    double cost = 0.0;
    switch (address.country()) {
      case "Sweden":
        cost += 100;
        break;
      default:
        cost += 200;
    }
    cost += type.price();
    return cost;
  }

  public String toString() {
    return address + ", " + goods + ", " + type;
  }
}

class DeliveryDemo {
  public static void main(String[] args) {
    // Create a delivery, calculate price
    Delivery.Address addr =
      new Delivery.Address("Mr Holmes", "10 Baker Street, London", "UK");
    Delivery deliv = new Delivery(addr,
                                  "Violin 2000",
                                  Delivery.Type.PRIORITY_MAIL);
    double cost = deliv.cost();
    System.out.println("Cost for " + deliv + " is " + cost);
    addr =
      new Delivery.Address("Herr Holm", "Storgatan 20, Sala", "Sweden");
    deliv = new Delivery(addr,
                         "Violin 2000",
                         Delivery.Type.PRIORITY_MAIL);
    cost = deliv.cost();
    System.out.println("Cost for " + deliv + " is " + cost);
  }
}

To compile and run, save the above in Delivery.java and do:

$ javac Delivery.java && java DeliveryDemo
Cost for Mr Holmes, 10 Baker Street, London, UK, Violin 2000, PRIORITY_MAIL(50 SEK) is 250.0
Cost for Herr Holm, Storgatan 20, Sala, Sweden, Violin 2000, PRIORITY_MAIL(50 SEK) is 150.0

Enums in the Java API

There are quite a few enums in the Standard Java API:

  • java.lang.annotation.ElementType
  • java.lang.annotation.RetentionPolicy
  • java.lang.management.MemoryType
  • java.lang.reflect.Type
  • java.math.RoundingMode
  • java.net.StandardProtocolFamily
  • java.nio.file.AccessMode
  • java.nio.file.attribute.AclEntryFlag
  • java.nio.file.attribute.AclEntryPermission
  • java.nio.file.attribute.AclEntryType
  • java.nio.file.attribute.PosixFilePermission
  • java.nio.file.FileVisitOption
  • java.nio.file.FileVisitResult
  • java.nio.file.LinkOption
  • java.nio.file.StandardCopyOption
  • java.nio.file.StandardOpenOption
  • java.security.cert.CRLReason
  • java.security.cert.PKIXReason
  • java.security.CryptoPrimitive
  • java.sql.ClientInfoStatus
  • java.sql.JDBCType
  • java.sql.PseudoColumnUsage
  • java.sql.RowIdLifetime
  • java.time.chrono.HijrahEra
  • java.time.chrono.IsoEra
  • java.time.chrono.MinguoEra
  • java.time.chrono.ThaiBuddhistEra
  • java.time.DayOfWeek
  • java.time.format.FormatStyle
  • java.time.format.ResolverStyle
  • java.time.format.SignStyle
  • java.time.format.TextStyle
  • java.time.Month
  • java.time.temporal.ChronoField
  • java.time.temporal.ChronoUnit
  • java.util.concurrent.TimeUnit

The above list proves the point that enums are quite common and popular.

See the lecture video for more on enums. We will add an exercise chapter as well, when we have the time to do so!

Slides and videos

Videos: Java Enums (Full playlist) | Java Enums | Java Enums - an introduction

Links

Further reading

Where to go next

We are working on some exercises on enums for you. Hopefully, it will soon be ready.

Previous goes back to the More programming with Java book, until we have set an order of the chapters for that book. Next goes to the exercises on Enums. TOC (table of contents) also goes to the More programming with Java book.

« PreviousBook TOCNext »