Vampire number generator
Also I had a question over this line specifically:

if (sortVampire(fangOne + fangTwo + fangThree).equals(sortVampire(vampire)))

  
  Instead of concatenating all three fangs, would it be worth it to use
  a StringBuilder in this scenario?


Not in this case. The compiler is smart enough to replace the concatenation of the String with the use of a StringBuilder. Using a StringBuilder to create a String from multiple concatenation is typically useful when it is done inside a loop.

For 3 fangs of length 2, your code is straight-forward and will be performant. A couple of notes:


There is a mathematical property that could be used to further improve performance. For bigger lengths, it can improve considerably the performance.
To determine whether all of the fangs have the same digits as their product, you are currently creating a list of characters and sorting those lists to test for equality. You could do that in a simpler and faster way without sorting anything.




To support arbitrary vampireLength, i.e. an arbitrary count of numbers to multiply to make up the final vampire number, we can resort to a recursive algorithm. For that, we will define a method generate

List&lt;String&gt; generate(int fangLength, int vampireLength, int... previousNumbers)


It takes as input the fangLength (count of digits in each fang), the vampireLength (count of fangs) and the previousVampires (array of all the numbers considered so far). This previousNumbers will be sorted ascendingly by construction. It returns the list of all the solutions.


The base case is when vampireLength is 0: it means that previousNumbers contains all the numbers to decide whether their product makes a vampire number. In this case, we perform the multiplication of all those numbers and compare the digits together: if they are the same then we can build the String representing the solution and return it in a singleton list; if they are not, we can return an empty list.
In the recursive case, we need to loop over all the possible numbers. First, the upper bound to consider: with a count of digits fangLength to consider, the upper bound is simply \$10^{\text{fangLength}}\$. Then, the lower bound: if there were no previous numbers then it is 10 (the starting point); else it is the last considered previousNumbers (so the greater). For each of those possible numbers i, we concatenate all the solutions by invoking the generate method again, adding i to the previousNumbers.


A sample code would be:

private static List&lt;String&gt; generate(int fangLength, int vampireLength, int... previousNumbers) {
    if (vampireLength == 0) {
        int product = Arrays.stream(previousNumbers).reduce(1, (a, b) -&gt; a * b);
        if (sameDigits(product, previousNumbers)) {
            String op = Arrays.stream(previousNumbers).mapToObj(String::valueOf).collect(Collectors.joining(" * "));
            return Collections.singletonList(op + " = " + product);
        }
        return Collections.emptyList();
    }
    List&lt;String&gt; list = new ArrayList&lt;&gt;();
    int end = (int) Math.pow(10, fangLength);
    int start = previousNumbers.length == 0 ? 10 : previousNumbers[previousNumbers.length - 1];
    for (int i = start; i &lt; end; i++) {
        int[] currentVampires = Arrays.copyOf(previousNumbers, previousNumbers.length + 1);
        currentVampires[currentVampires.length - 1] = i;
        list.addAll(generate(fangLength, vampireLength - 1, currentVampires));
    }
    return list;
}

private static boolean sameDigits(int target, int... numbers) {
    int[] array = new int[10];
    Arrays.stream(numbers).flatMap(i -&gt; String.valueOf(i).chars()).forEach(c -&gt; array[c - '0']++);
    String.valueOf(target).chars().forEach(c -&gt; array[c - '0']--);
    return Arrays.stream(array).allMatch(i -&gt; i == 0);
}


Some notes in this code:


It is using the Stream API to make the code more fluent: the product is calculated by reducing over the array of numbers and multiplying each of them together; the String solution is build by joining all the numbers, converted to a String, separated by " * ".
The sameDigits method was rewritten to implement the simpler algorithm of checking whether the digits are the same.