Estes são idiomas muito diferentes, especialmente nesta área. Digamos que você tenha uma aula. Nesse caso, tornaremos um controle de usuário, algo como uma caixa de texto. Chame de UIControl. Agora queremos colocar isso em outra classe. Nesse caso, como estamos usando uma interface do usuário para o nosso exemplo, a chamaremos de classe CleverPanel. Nossa instância CleverPanel vai querer saber sobre o que está acontecendo com sua instância UIControl por vários motivos. Como fazer isso?
Em C #, a abordagem básica é verificar vários eventos, configurando métodos que serão executados quando cada evento interessante for acionado. Em Java, que não possui eventos, a solução usual é passar um objeto com vários métodos de manipulação de "eventos" para um método UIControl:
boolean stillNeedIt = ... ;
uiControl.whenSomethingHappens( new DoSomething() {
public void resized( Rectangle r ) { ... }
public boolean canICloseNow() { return !stillNeedIt; }
public void closed() { ... }
...
} );
Até agora, a diferença entre C # e Java não é profunda. No entanto, temos a interface DoSomething que não é necessária em C #. Além disso, essa interface pode incluir muitos métodos que não são necessários na maioria das vezes. Em C #, simplesmente não lidamos com esse evento. Em Java, criamos uma classe que fornece uma implementação nula para todos os métodos de interface, DoSomethingAdapter. Agora substituímos DoSomething por DoSomethingAdapter e não precisamos escrever nenhum método para uma compilação limpa. Acabamos substituindo os métodos necessários para que o programa funcione corretamente. Então, acabamos precisando de uma interface e usando herança em Java para corresponder ao que fizemos com os eventos em C #.
Este é um exemplo, não uma discussão abrangente, mas fornece os fundamentos do motivo pelo qual há tanta herança em Java em vez de C #.
Agora, por que o Java funciona dessa maneira? Flexibilidade. O objeto passado para whenSomethingHappens poderia ter sido passado para o CleverPanel de outro lugar completamente. Pode ser algo que várias instâncias do CleverPanel devem passar para seus objetos do tipo UIControl para ajudar um objeto do CleverWindow em algum lugar. Ou o UIControl poderia entregá-lo a um de seus componentes.
Além disso, em vez de um adaptador, pode haver uma implementação DoSomething em algum lugar que tenha milhares de linhas de código por trás. Nós poderíamos criar uma nova instância disso e passá-la. Talvez seja necessário substituir um método. Um truque comum em Java é ter uma classe grande com um método como:
public class BigClass implements DoSomething {
...many long methods...
protected int getDiameter() { return 5; }
}
Então, no CleverlPanel:
uiControl.whenSomethingHappens( new BigClass() {
@Override
public int getDiameter() { return UIPanel.currentDiameter; }
} );
A plataforma Java de código-fonte aberto faz muito disso, o que tende a pressionar os programadores a fazerem mais - tanto porque eles o seguem como um exemplo e simplesmente para usá-lo. Eu acho que o design básico da linguagem está por trás do design da estrutura da Sun e por trás dos programadores Java que usam as técnicas quando não estão usando a estrutura.
É muito fácil criar uma classe em tempo real em Java. A classe, anônima ou nomeada, só precisa ser referenciada em um pequeno bloco de código enterrado profundamente em um método. Ele pode ser criado completamente novo ou com pequenas modificações em uma classe existente muito grande. (E a classe existente pode ser de nível superior em seu próprio arquivo, ou aninhada em uma classe de nível superior, ou definida apenas dentro de um único bloco de código). A nova instância de classe pode ter acesso total a todos os dados do objeto de criação. E a nova instância pode ser transmitida e usada em todo o programa, representando o objeto que o criou.
(Como um aparte, observe que um grande uso de herança aqui - como em outros lugares em Java - é simplesmente para fins DRY. Permite que diferentes classes reutilizem o mesmo código. Observe também a facilidade de herança em Java que incentiva isso. )
Novamente, esta não é uma discussão abrangente; Estou apenas arranhando a superfície aqui. Mas sim, há uma diferença surpreendente em como a herança é usada entre Java e C #. Eles são, nesse sentido, idiomas muito diferentes. Não é sua imaginação.