544 lines
8.9 KiB
Markdown
544 lines
8.9 KiB
Markdown
---
|
|
title: The Humble Enumeration
|
|
---
|
|
|
|
# What in the heck is an enum?
|
|
<!-- pause -->
|
|
|
|
```java {0|1|1-2|1-3|1-5}
|
|
enum MentalState {
|
|
WAIT,
|
|
OH_GOD,
|
|
WHAT
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## And _why?_
|
|
<!-- pause -->
|
|
|
|
```java {0|1|1-2|1-3|1-5}
|
|
enum UsefulFor {
|
|
EVERYTHING,
|
|
A_FEW_THINGS,
|
|
NOTHING_AT_ALL
|
|
}
|
|
```
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# What in the heck is an enum?
|
|
|
|
```java
|
|
enum MentalState {
|
|
WAIT,
|
|
OH_GOD,
|
|
WHAT
|
|
}
|
|
```
|
|
|
|
## And _why?_
|
|
|
|
```java
|
|
enum UsefulFor {
|
|
EVERYTHING, // ???
|
|
A_FEW_THINGS, // ???
|
|
NOTHING_AT_ALL // ???
|
|
}
|
|
```
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Enumerated types
|
|
<!-- pause -->
|
|
|
|
## An enum is essentially a known set of distinct values.
|
|
<!-- pause -->
|
|
|
|
### A couple of common enums would be things like:
|
|
<!-- pause -->
|
|
|
|
* Playing card suits (HEART, SPADE, CLUB, DIAMOND)
|
|
<!-- pause -->
|
|
* Days of the week (SUNDAY, MONDAY, etc.)
|
|
<!-- pause -->
|
|
* Log levels (DEBUG, INFO, WARNING, ERROR, FATAL)
|
|
<!-- pause -->
|
|
* Etc.
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Okay, that's great, but how do I know when to use one?
|
|
<!-- pause -->
|
|
|
|
## (Or, "Booleans? More like, _Fooleans!_")
|
|
<!-- pause -->
|
|
|
|
So, picture this. You've got some code that analyzes a computer network.
|
|
<!-- pause -->
|
|
|
|
There is a lovely Computer _class_:
|
|
<!-- pause -->
|
|
|
|
```java
|
|
public class Computer {
|
|
String hostname;
|
|
double maxPowerConsumption;
|
|
boolean on;
|
|
}
|
|
```
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
And a lovely _function_ that calculates how much power a collection of computers might use:
|
|
<!-- pause -->
|
|
|
|
```java
|
|
public double estimateWattage(List<Computer> computers) {
|
|
double sum = 0;
|
|
|
|
for (var computer : computers) {
|
|
if (computer.on) {
|
|
sum += computer.maxPowerConsumption;
|
|
}
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
```
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
<!-- jump_to_middle -->
|
|
# This works great.
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
<!-- jump_to_middle -->
|
|
## Until someone says...
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
<!-- jump_to_middle -->
|
|
"What about computers in standby?"
|
|
<!-- pause -->
|
|
|
|
# Oh.
|
|
<!-- pause -->
|
|
|
|
# True.
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Well, okay, maybe let's add a new _inStandby_ field to Computer?
|
|
<!-- pause -->
|
|
|
|
```java {5}
|
|
public class Computer {
|
|
String hostname;
|
|
double maxPowerConsumption;
|
|
boolean on;
|
|
boolean inStandby;
|
|
}
|
|
```
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
## and update our wattage function:
|
|
<!-- pause -->
|
|
|
|
```java {7-8}
|
|
public double estimateWattage(List<Computer> computers) {
|
|
double sum = 0;
|
|
|
|
for (var computer : computers) {
|
|
if (computer.on) {
|
|
sum += computer.maxPowerConsumption;
|
|
} else if (computer.inStandby) {
|
|
sum += computer.maxPowerConsumption * 0.01;
|
|
}
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
That works.
|
|
<!-- pause -->
|
|
|
|
Probably.
|
|
<!-- pause -->
|
|
|
|
But it's starting to look a little ugly.
|
|
<!-- pause -->
|
|
|
|
And, wait. Is a computer in standby... powered on? Or is it off?
|
|
|
|
<!-- end_slide -->
|
|
|
|
Hmm...
|
|
<!-- pause -->
|
|
|
|
# No matter how you answer the question, you've got yourself a problem.
|
|
<!-- pause -->
|
|
|
|
## There is now an _invalid_ state that an instance of Computer could end up in.
|
|
<!-- pause -->
|
|
|
|
# Let's say we decide that a computer inStandby is _on._
|
|
<!-- pause -->
|
|
|
|
## That means that having an instance where `.inStandby == true` and `.on == false` is _illegal._
|
|
<!-- pause -->
|
|
|
|
And every part of your code better understand that, because the compiler does ___not.___
|
|
<!-- pause -->
|
|
|
|
### The technical term for this is,
|
|
<!-- pause -->
|
|
|
|
#### "Yucky"
|
|
<!-- pause -->
|
|
|
|
### or
|
|
<!-- pause -->
|
|
|
|
##### "Ew"
|
|
|
|
<!-- end_slide -->
|
|
|
|
Hmmmmmm....
|
|
<!-- pause -->
|
|
|
|
# Well. We could try a String.
|
|
<!-- pause -->
|
|
|
|
```java {0|4}
|
|
public class Computer {
|
|
String hostname;
|
|
double maxPowerConsumption;
|
|
String powerState;
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## But then, how do we know if Computer's powerState is valid?
|
|
<!-- pause -->
|
|
|
|
### Are we going to _enjoy_ writing code like this?
|
|
<!-- pause -->
|
|
|
|
```java
|
|
public static final String IN_STANDBY = "IN_STANDBY";
|
|
|
|
if (computer.powerState.toUpperCase().equals(IN_STANDBY)) {
|
|
sum += computer.maxPowerConsumption * 0.01;
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
#### My guess is, not so much.
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Instead,
|
|
<!-- pause -->
|
|
|
|
# What if we try an enum?
|
|
<!-- pause -->
|
|
|
|
```java
|
|
enum PowerState {
|
|
POWERED_ON,
|
|
STANDBY,
|
|
POWERED_OFF
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## Enums can describe more than two distinct states.
|
|
<!-- pause -->
|
|
|
|
### But they're a lot more strict than a String.
|
|
<!-- pause -->
|
|
|
|
#### And we can use _that_ inside our Computer class.
|
|
<!-- pause -->
|
|
|
|
```java {0|4}
|
|
public class Computer {
|
|
String hostname;
|
|
double maxPowerConsumption;
|
|
PowerState powerState;
|
|
}
|
|
```
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Then estimateWattage() cleans up pretty nicely!
|
|
<!-- pause -->
|
|
|
|
```java {6-14}
|
|
public double estimateWattage(List<Computer> computers) {
|
|
double sum = 0;
|
|
|
|
for (var computer : computers) {
|
|
switch (computer.powerState) {
|
|
case POWERED_ON:
|
|
sum += computer.maxPowerConsumption;
|
|
break;
|
|
case STANDBY:
|
|
sum += computer.maxPowerConsumption * 0.01;
|
|
break;
|
|
case POWERED_OFF:
|
|
// Do nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
```
|
|
<!-- end_slide -->
|
|
|
|
|
|
# And adding a new power state has become a _lot_ easier:
|
|
<!-- pause -->
|
|
|
|
```java {5-7}
|
|
switch (computer.powerState) {
|
|
case POWERED_ON:
|
|
sum += computer.maxPowerConsumption;
|
|
break;
|
|
case IDLE:
|
|
sum += computer.maxPowerConsumption * 0.12;
|
|
break;
|
|
case STANDBY:
|
|
sum += computer.maxPowerConsumption * 0.01;
|
|
break;
|
|
case POWERED_OFF:
|
|
// Do nothing
|
|
break;
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## This is pretty much the extent of enum capabilites in C, C++, and C#
|
|
<!-- pause -->
|
|
|
|
### But now _this_ is starting to look repetitive.
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# So, what if we do some Java?
|
|
<!-- pause -->
|
|
|
|
## Could we store that multiplier on the enum itself?
|
|
<!-- pause -->
|
|
|
|
```java {2-99}
|
|
enum PowerState {
|
|
POWERED_ON(1.0),
|
|
IDLE(0.12),
|
|
STANDBY(0.01),
|
|
POWERED_OFF(0);
|
|
|
|
public final double wattageMultiplier;
|
|
|
|
PowerState(double mult) {
|
|
this.wattageMultiplier = mult;
|
|
}
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
# Hey, we can!
|
|
<!-- pause -->
|
|
|
|
## Java lets us associate any arbitrary data with an enum.
|
|
<!-- pause -->
|
|
|
|
### Though it's good practice to stick with something immutable.
|
|
<!-- pause -->
|
|
|
|
(Also: note the semicolon after the last value. It was optional before!)
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Now, estimateWattage() doesn't need to know what the multipliers are!
|
|
<!-- pause -->
|
|
|
|
```java
|
|
public double estimateWattage(List<Computer> computers) {
|
|
double sum = 0;
|
|
|
|
for (var computer : computers) {
|
|
var power = computer.powerState;
|
|
sum += computer.maxPowerConsumption * power.wattageMultiplier;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## It just needs to know that a multiplier _exists._
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# And, what the heck, let's use streams.
|
|
<!-- pause -->
|
|
|
|
```java
|
|
public double estimateWattage(List<Computer> computers) {
|
|
return computers.stream()
|
|
.mapToDouble(computer ->
|
|
computer.maxPowerConsumption *
|
|
computer.powerState.wattageMultiplier)
|
|
.sum();
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## _Functional!_
|
|
<!-- pause -->
|
|
|
|
### If you've futzed with streams a lot, you may notice an opportunity here.
|
|
<!-- pause -->
|
|
|
|
## If Computer had some sort of method for:
|
|
<!-- pause -->
|
|
|
|
```java
|
|
computer.maxPowerConsumption *
|
|
computer.powerState.wattageMultiplier
|
|
```
|
|
<!-- pause -->
|
|
|
|
# We could use that directly in mapToDouble(), via a _method reference_
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# So, let's define that method.
|
|
<!-- pause -->
|
|
|
|
```java {2-99}
|
|
public class Computer {
|
|
// snip
|
|
|
|
public double currentPowerConsumption() {
|
|
return maxPowerConsumption * powerState.wattageMultiplier;
|
|
}
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## Simple enough.
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Cool.
|
|
<!-- pause -->
|
|
|
|
# And that leaves us with this fairly-neat implementation:
|
|
<!-- pause -->
|
|
|
|
```java
|
|
public double estimateWattage(List<Computer> computers) {
|
|
return computers.stream()
|
|
.mapToDouble(Computer::currentPowerConsumption)
|
|
.sum();
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
## It _almost_ doesn't even need a dedicated method anymore.
|
|
<!-- pause -->
|
|
|
|
### But I'll leave that as an exercise for the reader.
|
|
<!-- end_slide -->
|
|
|
|
|
|
# Side note
|
|
<!-- pause -->
|
|
|
|
## In Java, you can also define methods directly on an enum.
|
|
<!-- pause -->
|
|
|
|
### For example, say we still wanted a way to check if a computer is on or off.
|
|
<!-- pause -->
|
|
|
|
#### We could add a method directly to PowerState:
|
|
<!-- pause -->
|
|
|
|
```java {7-99}
|
|
enum PowerState {
|
|
POWERED_ON(1.0),
|
|
IDLE(0.12),
|
|
STANDBY(0.01),
|
|
POWERED_OFF(0);
|
|
|
|
// snip
|
|
|
|
public boolean isOn() {
|
|
return this != POWERED_OFF;
|
|
}
|
|
}
|
|
```
|
|
<!-- pause -->
|
|
|
|
#### And that keeps the logic all in one place.
|
|
|
|
<!-- end_slide -->
|
|
|
|
|
|
# In summary,
|
|
<!-- pause -->
|
|
|
|
## Wait, when should you use enums?
|
|
<!-- pause -->
|
|
|
|
# Basically any time you have a _known_ set of _distinct_ states.
|
|
<!-- pause -->
|
|
|
|
## When should you definitely _not_ use an enum?
|
|
<!-- pause -->
|
|
|
|
### Things like:
|
|
<!-- pause -->
|
|
|
|
* Names
|
|
<!-- pause -->
|
|
* Numbers
|
|
<!-- pause -->
|
|
* IPs
|
|
<!-- pause -->
|
|
* Prices
|
|
<!-- pause -->
|
|
* Etc.
|
|
<!-- pause -->
|
|
|
|
Because they aren't known beforehand, and there are too many to enumerate yourself.
|
|
<!-- pause -->
|
|
|
|
## So, use an enum when you have at least two states.
|
|
<!-- pause -->
|
|
|
|
# But probably less than a hundred.
|
|
<!-- pause -->
|