As enum represent a fixed set of values in a more or less self-documented. Make code more explicit, more readable, and less vulnerable to programming errors.
A common alternative is to use String or int for constant values. The enum bring some advantages in relation to these types:
- Compiler does not allow typographical errors, as can happen with string literals. 
- The compiler does not allow values that are outside the enumerated set, which is a consequence of the enumerations being guys themselves. 
- No need to write preconditions, or manual testing to ensure that the argument of a method is within the accepted range of values. 
- The type invariant is free, again because the enumerations are types, and define in the beginning the valid values. 
- Enumerations can define demeanor (methods) for their constants, as in any usual class. 
- The constants of an enumeration may specify their behavior: each constant may have its own definition of a method. 
- The virtual machine guarantees thread Safety when loading the enumeration. 
- Can also be used in - switch.
 
There are still some creative uses of enumerations, such as state machines, as seen on this blog.
Let’s take a practical example of these advantages. Imagine a program that receives two colors and tries to combine them, according to the RGB system.
Using int
public static final int
    VERMELHO    = 1,
    AZUL        = 2,
    VERDE       = 4,
    AMARELO     = 5,
    CIANO       = 6,
    ROXO        = 3,
    BRANCO      = 7;
/**
 * @pre cor1 == VERMELHO || cor1 == AZUL || cor1 == VERDE ||
 *    cor1 == CIANO || cor1 == AMARELO || cor1 == ROXO || cor1 == BRANCO;
 * @pre cor2 == VERMELHO || cor2 == AZUL || cor2 == VERDE ||
 *    cor2 == CIANO || cor2 == AMARELO || cor2 == ROXO || cor2 == BRANCO;
 * @post cor1 == VERMELHO && cor2 == VERDE => return == AMARELO;
 * @post ...
 */
public static int combina(int cor1, int cor2) {
    if (!corValida(cor1)) return -1;
    if (!corValida(cor2)) return -1;
    return cor1 | cor2;
}
private boolean corValida(int cor) {
    return cor == VERMELHO || cor == VERDE || cor == AZUL ||
        cor == AMARELO || cor == CIANO || cor == ROXO || cor == BRANCO;
}
public static void main(String[] args) {
    int cor = combina(1, 2);
    // O que acontece quando VERMELHO e AZUL deixam de ser 1 ou 2?
    assert cor == ROXO;
}
Using enum
/*
 * Os codigos numéricos não fazem qualquer falta.
 * Seria possível resolver com switch, mas ficaria muito extenso para exemplo.
 * Contudo, um caso real deveria retirar os códigos,
 * para maiores garantias de que não há erro humano.
 */
public static enum Cor {
    VERMELHO(1), AZUL(2), VERDE(4), AMARELO(5), CIANO(6), ROXO(3), BRANCO(7);
    private final int codigo;
    Cor(int codigo) { this.codigo = codigo; }
    int codigo() { return codigo; }
    public static Cor porCodigo(int codigo) {
        for (Cor cor: Cor.values()) {
            if (codigo == cor.codigo()) return cor;
        }
        throw new IllegalArgumentException("codigo invalido");
    }
}
/**
 * @pre cor1 != null && cor2 != null;
 * @post cor1 == Cor.VERMELHO && cor2 == Cor.VERDE => return == Cor.AMARELO;
 * @post ...
 */
public static int combina(Cor cor1, Cor cor2) {
    int combinado = cor1.codigo() | cor2.codigo();
    return Cor.porCodigo(combinado);
}
public static void main(String[] args) {
    Cor cor = combina(Cor.VERMELHO, Cor.AZUL);
    assert cor == Cor.ROXO;
}
							
							
						 
I really liked the answer! However, considering that she asks a question from the point of view of someone who is beginner in language, I think it would be interesting to add some examples of code. :)
– Luiz Vieira
@Luizvieira Thank you so much for the tip! I added an example using
intand the same example withenum, to mark the difference between the two. :)– afsantos