Algumas classes não agrupam objetos concretos, mas servem para agrupar tipos ou classes de objetos. Para entender melhor voltemos ao exemplo dos veículos e carros. Sabemos que carros podem ser realmente construídos, são objetos concretos que existem fisicamente no universo. Mas a classe Veículos, não representa um grupo de objetos físicos apenas por ela mesma, representa um agrupamento de classes de objetos, e estas sim são classes de objetos concretos. Se tentarmos construir um veículo, obrigatoriamente teremos que escolher que tipo de veículo será construído. Vai ser um carro, uma moto, um avião, um barco ou o que? Não é possível construir um veículo genérico que não esteja associado a algum subtipo (subclasse) de veículo. Então a classe Veículos depende das suas subclasses, ou classes descendentes, para criar objetos. Apenas por ela mesma não é possível construir objetos. Neste caso dizemos que a classe Veículos é uma classe abstrata.
As classes abstratas são usadas na POO para serem ancestrais de outras classes e não para serem instanciadas. Ao tornar uma classe abstrata, o programador impede que sejam criadas instâncias dela. Frequentemente é criada uma classe ancestral para conter o código que é comum a todas suas classes descendentes. Essa classe ancestral é extremamente útil porque evita a replicação de código nas classes descendentes, mas não faz sentido a criação de objetos dessa classe no sistema. Por exemplo, suponha que um sistema acadêmico precise implementar classes para professores e alunos. Parte dos atributos e métodos podem ser comuns a essas duas classes e podem perfeitamente ser colocados em uma classe que seja ancestral para as duas classes, por exemplo a classe Pessoa. Então a classe Pessoa contém todo o código que é comum às classes Professor e Aluno, mas não faz nenhum sentido criar instâncias diretamente da classe Pessoa. Neste caso tornar a classe Pessoa uma classe abstrata, garante que nenhuma instância dessa classe poderá ser criada.

Métodos abstratos

Classes abstratas podem conter métodos abstratos. Um método abstrato é declarado na classe, mas não tem seu código implementado, ele será implementado em alguma classe descendente. Uma classe abstrata pode ou não ter métodos abstratos, mas se uma classe contém métodos abstratos, então obrigatoriamente a classe deve ser declarada abstrata. Uma classe descendente de uma classe abstrata precisa implementar o código de todos os métodos abstratos para se tornar concreta e poder ser instanciada. Caso contrário, a classe descendente continua sendo abstrata e precisa ser declarada como tal.
Em java, usa-se o modificador abstract para indicar que uma classe ou um método é abstrato.
Exemplo:
public abstract class NomeClasse {
 ...
 public abstract <Tipo> NomeMetodo (<lista de parâmetros>);
 ...
}
 
Note que após a lista de parâmetros é colocado um ponto-e-vírgula encerrando a declaração em vez de abrir chaves para iniciar o corpo do método.
Apesar de um método abstrato não ter seu código implementado na classe que o declara, esta classe pode usar e fazer chamadas a ele. Isso acontece porque só vai existir um objeto (uma instância) se existir pelo menos uma classe descendente que implemente todos os métodos abstratos. Então durante a execução é garantido que o método vai ter sua implementação disponível. Veja o exemplo abaixo:

public abstract class Shape {
 protected double centerX, centerY;
 
 public Shape(double centerX, double centerY) {
 this.centerX = centerX;
 this.centerY = centerY;
 }
 
 public abstract double getArea();
 
 public void printArea(){
 System.out.format("Area: %.1f\n", getArea());
 }
}
 
Note que o método printArea() faz uma chamada ao método getArea() que é um método abstrato. A classe shape não tem uma implementação para o método getArea, mas como é uma classe abstrata, nunca vai instanciar objetos diretamente. Para que exista um objeto executando o código da classe Shape tem que existir uma classe descendente que implemente o método getArea().