Couldn't find code for this anywhere - many examples ignore the possibility of characters that have special meaning in regular expressions being present.
This is what I ended up with as a solution, posted here in case it helps anyone else (and in case there is a better way to do it).
Updated per Derek F's feedback:
public static Boolean soqlLikeMatch(String candidate, String soqlLike) {
// Escape regex special characters but keep the % and _ and space characters,
// \w matches alphanumeric characters,
// four backslashes translate to one escaped backslash i.e. \\
String regex = soqlLike.replaceAll('([^\\w%_ ])', '\\\\$1');
// Make case insensitive,
// lock down start/end match,
// convert SOQL wildcards to regex ones
regex = '(?i)' + '^' + regex.replace('%', '.*?').replace('_', '.{1}?') + '$';
return Pattern.matches(regex, candidate);
}