Enums, lambdas, and functional interfaces
A fixed set of named values, treating behavior as data, and the functional interface that makes it work.
Finished reading?
Mark this session so you can track where you are.
A fixed set of named values, treating behavior as data, and the functional interface that makes it work.
Finished reading?
Mark this session so you can track where you are.
Two ideas in this session, and they both come down to the same instinct: stop scattering information the language could hold for you. First, a type whose values are a small, fixed, named set. Then a way to hand a piece of behavior to a method the way you already hand it a number.
enum and use its values safely, including inside a switch.Math::max.Suppose a program needs to track the day of the week. With the tools you have, you might reach for plain int constants, the kind you met in the static and final session.
public class Main { static final int MON = 0; static final int TUE = 1; static final int WED = 2; public static void main(String[] args) { int today = 99; System.out.println("today is " + today); }}today is 99
This compiles and runs, and that is exactly the problem. today is typed as int, so the language is perfectly happy to let it hold 99, or -4, or any other number that is not a day at all. Nothing stops a typo. A method that takes a “day” really just takes an int, and the reader has to remember that 2 secretly means Wednesday. The meaning lives in your head, not in the program.
When the valid values of something form a small, fixed set, an
intis too loose. It allows thousands of values you never meant to be legal, and it throws away the names.
An enum(short for enumeration, meaning “to list out”) is a type you define whose only legal values are the ones you name. You list them once, and from then on a variable of that type can hold one of those values and nothing else.
public class Main { enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } public static void main(String[] args) { Day today = Day.WED; System.out.println("Today is " + today); if (today == Day.SAT || today == Day.SUN) { System.out.println("The shop is closed"); } else { System.out.println("The shop is open"); } }}Today is WED The shop is open
Read the declaration first. enum Day introduces a brand new type called Day, just like writing a class introduces a new type. Inside the braces is the complete list of values it can ever have: MON through SUN. These are written in capitals by convention because each one is a constant, a value that never changes.
Now Day today = Day.WED; reads almost like English: today is a Day, set to Wednesday. You reach a specific value with Day.WED, the type name, a dot, then the value. And here is the payoff: try to write Day today = Day.FUNDAY; and the program will not even compile, because FUNDAY is not in the list. The whole class of typo bugs from the intversion is gone before the program ever runs.
"Today is " + today, Java prints the name you wrote: WED. That is far friendlier than printing 2 and leaving the reader to decode it. You did not write a toString for this; enums give you that name for free.Because an enum has a small known set of values, it pairs naturally with switch. Inside the case labels you write the value name on its own, without the type prefix, because the switch already knows it is looking at aDay.
public class Main { enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } public static void main(String[] args) { Day today = Day.FRI; switch (today) { case SAT: case SUN: System.out.println("Weekend"); break; case FRI: System.out.println("Almost the weekend"); break; default: System.out.println("A normal working day"); } }}Almost the weekend
Trace it. today is FRI. The switch jumps to case FRI, prints Almost the weekend, and the break stops it falling into default. The two weekend cases stacked together, SAT then SUN with no body between them, share the same action: that is the deliberate fall-through you met in the switch session, used on purpose.
public class Main { enum Direction { NORTH, EAST, SOUTH, WEST } public static void main(String[] args) { for (Direction d : Direction.values()) { System.out.println(d + " is at position " + d.ordinal()); } }}NORTH is at position 0 EAST is at position 1 SOUTH is at position 2 WEST is at position 3
An enum is really a special kind of class, so each value can carry its own data and the enum can have methods. You will not need this often, but it is worth seeing once. Suppose each planet should know its own gravity, so we can compute weight.
public class Main { enum Planet { MERCURY(3.7), EARTH(9.8), JUPITER(24.8); private final double gravity; Planet(double gravity) { this.gravity = gravity; } double weightFor(double mass) { return mass * gravity; } } public static void main(String[] args) { double mass = 70.0; System.out.println("On Earth: " + Planet.EARTH.weightFor(mass)); System.out.println("On Jupiter: " + Planet.JUPITER.weightFor(mass)); }}On Earth: 686.0 On Jupiter: 1736.0
Each value is now written with a number after it, like EARTH(9.8), which feeds the enum's constructor and gets stored in the gravity field for that value. Then weightFor is an ordinary method using that field. Planet.EARTH.weightFor(70.0) is 70.0 * 9.8, which is686.0. Keep this in your back pocket; for most days a plain list of names is all you want.
Now the second idea. So far, when you wanted to give a method some information, you passed a value: a number, a string, an object. But sometimes the thing you want to hand over is not data, it is anaction. “Here is a method, take these two numbers and do thisto them.” The question is how you package an action so it can be passed like a value.
You already have the tool: an interface. Define an interface with one method that describes the shape of the action, and anything implementing it is a packaged action you can pass around.
public class Main { interface Operation { int apply(int a, int b); } static int compute(Operation op, int a, int b) { return op.apply(a, b); } public static void main(String[] args) { Operation add = new Add(); int result = compute(add, 3, 4); System.out.println("3 add 4 = " + result); }} class Add implements Main.Operation { public int apply(int a, int b) { return a + b; }}3 add 4 = 7
Look closely at compute. Its first parameter is an Operation, which is to say, an action. It does not know or care what the action does; it just calls op.apply(a, b) and returns the result. We hand it an Add object, and compute(add, 3, 4) runs add.apply(3, 4), which is 3 + 4, so the answer is 7. The behavior was passed in. That is the whole idea, and it is powerful: compute can do addition, or anything else, depending purely on which Operation you give it.
Now notice that the interface has exactly one method. That turns out to matter enough to have a name. An interface with exactly one abstract method is called a functional interface. “One method” means there is no ambiguity about what implementing it means: there is only one job to fill in.
A functional interface is an interface with exactly one abstract method. Because there is only one method to provide, Java can let you provide just that method's body, with no class name and no ceremony. That shortcut is the lambda.
Runnable from the threads session has exactly one method, run(). That makes it a functional interface. So does Comparator, with its single comparemethod, which you will meet when you sort things. The pattern is everywhere once you know to look for “an interface with one method.”Writing a whole separate Add class just to carry one line of logic, a + b, is a lot of packaging for very little content. You met a way to trim it down in the inner classes session: the anonymous class, which lets you implement the interface on the spot without naming a class.
public class Main { interface Operation { int apply(int a, int b); } static int compute(Operation op, int a, int b) { return op.apply(a, b); } public static void main(String[] args) { Operation add = new Operation() { public int apply(int a, int b) { return a + b; } }; System.out.println("3 add 4 = " + compute(add, 3, 4)); }}3 add 4 = 7
Better, but still noisy. The only part that actually says anything is return a + b;. Everything around it, new Operation() { public int apply(int a, int b) { ... } }, is scaffolding the compiler could work out for itself. It already knows the type is Operation, it already knows the one method is apply, and it already knows the parameter types. Why make you write all of it again?
A lambdais a compact way to write exactly that one method's body, and nothing else. You give the parameters, an arrow ->, and the result. Java fills in the rest from the interface.
public class Main { interface Operation { int apply(int a, int b); } static int compute(Operation op, int a, int b) { return op.apply(a, b); } public static void main(String[] args) { Operation add = (a, b) -> a + b; Operation multiply = (a, b) -> a * b; System.out.println("3 add 4 = " + compute(add, 3, 4)); System.out.println("3 multiply 4 = " + compute(multiply, 3, 4)); }}3 add 4 = 7 3 multiply 4 = 12
Read (a, b) -> a + b as “given a and b, produce a + b.” The left of the arrow is the parameters; the right is what to return. There is no method name because Operation only has one method, so there is nothing to choose. There are no parameter types because Java reads them off apply(int a, int b). The lambda is the implementation of apply, written as small as it can be.
And add is still an Operation, a value you can store in a variable and pass to compute exactly as before. compute(multiply, 3, 4)runs the multiply lambda's body, 3 * 4, giving 12. Same machinery as the class version, almost none of the noise.
new Operation() { ... } and the method header.public int apply(int a, int b).(a, b) -> a + b only means something once Java knows which functional interface it is filling in. Here that comes from Operation add = ... or from a parameter that expects an Operation. Write a lambda with nothing telling Java what interface it implements and you get a compile error, not a running program.Because a lambda is just a value, you can keep several of them together and pick between them at runtime. Here is a small table of operations, each a lambda, walked with a loop you could have written back in Phase 1.
public class Main { interface Operation { int apply(int a, int b); } public static void main(String[] args) { Operation[] ops = { (a, b) -> a + b, (a, b) -> a - b, (a, b) -> a * b }; String[] names = { "add", "subtract", "multiply" }; for (int i = 0; i < ops.length; i++) { System.out.println(names[i] + "(6, 2) = " + ops[i].apply(6, 2)); } }}add(6, 2) = 8 subtract(6, 2) = 4 multiply(6, 2) = 12
An array of behaviors. ops[0] is the adding action, ops[2]is the multiplying action, and the loop calls each on the same two numbers. Five sessions ago an array held numbers or strings; now it holds actions, and nothing about arrays had to change. That is “behavior as a value” in plain sight.
Sometimes the body of your lambda would do nothing but call a method that already exists, with the same arguments. Math.max(a, b) already takes two ints and returns the larger, which is exactly the shape Operation wants. For that case Java offers an even shorter form, themethod reference, written with two colons.
public class Main { interface Operation { int apply(int a, int b); } static int compute(Operation op, int a, int b) { return op.apply(a, b); } public static void main(String[] args) { Operation larger = Math::max; System.out.println("max of 8 and 5 is " + compute(larger, 8, 5)); }}max of 8 and 5 is 8
Math::max reads as “use the existing method Math.maxas this action.” It is shorthand for the lambda (a, b) -> Math.max(a, b), and it does the same thing: compute(larger, 8, 5) returns 8. Treat method references as a tidy convenience for the moment you would otherwise write a lambda that forwards straight to a method. The lambda is the idea; the :: form is just a nicer spelling of one common case.
Try each one yourself first, then open the answer.
enum Suit { HEARTS, DIAMONDS, CLUBS, SPADES } safer than four int constants 0 to 3 for the suits of a card?Operation with its single int apply(int a, int b) one?Operation sub = new Operation() { public int apply(int a, int b) { return a - b; } };Day switch, what does the program print if today is set to Day.SUN instead of Day.FRI? Trace it.(a, b) -> a + b has no written type. So how does Java know it is an Operation and not something else?Take these away. They continue exactly what we just did.
enum TrafficLight { RED, YELLOW, GREEN }. Write a switch over a TrafficLight value that prints Stop, Slow down, or Go. Type it into a real Java file, run it for each of the three values, and confirm the outputs by hand first.Operation example. Add two more lambdas: one for integer division (a, b) -> a / b and one for remainder (a, b) -> a % b. Pass each to compute with the numbers 17 and 5, and predict both results before you run it.Math::max that would fit the shape (int, int) -> int, and write the equivalent plain lambda for it. Explain why the two forms do the same thing.