commit
522ba9491c
|
@ -0,0 +1,543 @@
|
|||
---
|
||||
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 when should I use an enum?
|
||||
<!-- 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 -->
|
Loading…
Reference in New Issue