Olá! Tudo bem? No caso podemos destrinchar essa linha código em algumas partes:
Set< Aluno >: É o tipo da variável alunos, que vai ser um Set de objetos da classe Aluno. Esse "<>" se chama Generics, sua função nesse caso é basicamente indicar qual vai ser o tipo dos elementos que vão ser colocados nesse Set.
alunos: É o nome da variável.
new HashSet<>(): É o tipo "real" da variável alunos, uma instância do tipo HashSet, nesse caso os Generics ("<>") estão vazios porque eles já estão sendo usados do lado esquerdo, então não é preciso especificar de novo, mas você pode colocar se quiser. Nesse caso HashSet<>(), HashSet< Aluno >() e HashSet() terão o mesmo resultado, já que o Generics já foram especificados antes.
Você pode perceber que estamos tendo uma variável do tipo Set, que na verdade é um HashSet. O que acontece é que o Java vai considerar a variável como um Set, então se você tentar usar alguma função específica da classe HashSet, você terá um problema de compilação. Como a classe Set na verdade é uma interface, que por isso não pode ser instanciada, precisamos escolher uma implementação dessa interface, como o HashSet. A vantagem dessa abordagem, de usar tipos genéricos no tipo das variáveis é que é possível trocar facilmente por outra implementação de Set, por exemplo:
// Tipo: Set de alunos, interface genérica - Implementação: HashSet, LinkedHashSet ou outro tipo de Set
Set<Aluno> alunos1 = new HashSet<>();
Set<Aluno> alunos2 = new LinkedHashSet();
Set<Aluno> alunos3 = new ConcurrentSkipListSet<Aluno>();
Nesse código, é possível ver que todas essas classes podem ser usadas, já que todas implementam a interface Set, além de que o uso do Generics no lado direito é opcional. A diferença entre essas implementações de Set são alguns detalhes que ocorrem por debaixo dos panos, mais relacionados à performance ou a alguns métodos próprios, então usando a interface Set como tipo, você pode testar qual é a melhor opção e trocar de forma prática e fácil, alterando só uma linha do código. Espero ter ajudado!