Ajuda com herança e polimorfismo na pilha de execução

Olá! Estou com dúvida nessa questão aqui, é de concurso:

"Sejam as classes seguintes escritas em Java:

class A {int x; A(){setX(10);} int getX(){return x;} void setX(int y) {x=y;}}
class B extends A {B(){ x+=100; }}
class C extends B { C(){ this(10); x*=2; } C(int y) {setX(y+getX());}}
A execução da sentença new C().getX() retorna que valor?"

Eu escrevi no papel e fui tentar executar na cabeça, não consegui chegar na resposta que é 240, então coloquei no Eclipse e enchi de print pra tentar entender:

class A {

int x; 

A(){
	setX(10);
	System.out.println("Saiu do construtor de A valendo: "+x);

}

int getX(){
	
	System.out.println("Saiu do método getX de A valendo: "+x);

	return x;
}

void setX(int y)
{
	x=y;
	System.out.println("Usou metodo setX de A, atribuindo y que vale: "+y);

}

}

class B extends A {

B(){
	x+=100;
	System.out.println("Saiu do construtor de B valendo: "+x);

	}
}

class C extends B {

C()
{
	this(10);
	x*=2;
	
	System.out.println("Saiu do construtor de C valendo: "+x);
}

C(int y) 
{	
	System.out.println("Usou método C que soma " + y + " Ao getX que vale: " + getX());

	setX(y+getX());
}

}

Daí na classe teste rodei:

	System.out.println( new C().getX()) ;

Que deu no console:

Usou metodo setX de A, atribuindo y que vale: 10
Saiu do construtor de A valendo: 10
Saiu do construtor de B valendo: 110
Saiu do método getX de A valendo: 110
Usou método C que soma 10 Ao getX que vale: 110
Saiu do método getX de A valendo: 110
Usou metodo setX de A, atribuindo y que vale: 120
Saiu do construtor de C valendo: 240
Saiu do método getX de A valendo: 240
240

Então ao chamar new C().getX(); eu entendi que vai lá no construtor da super classe A, o construtor dela executa:

setX(10);

Vai pro construtor da Clase Filha B, que adiciona 100, dando 110.

Daí por que agora roda o método getX() da superclasse A de novo? A partir daí não entendi mais.

Ao executar um construtor, se você não chamar explicitamente o construtor da superclasse ou outro construtor da prórpia classe, o Java vai implicitamente chamar o construtor da superclasse.

Se você fizer um teste de mesa, vai perceber que, ao executar o new C().getX(), vai acontecer o seguinte:

+-----------------------------------------------------------------------------------+-----+
|                 PILHA DE EXECUÇÂO                                                 |  X  |
+-----------------------------------------------------------------------------------+-----+
+-> A classe Teste executa o construtor C()                                         |   0 | 
|   |                                                                               +-----+
|   +-> O construtor C() executa o construtor C(int) passando 10 ao parâmetro y     |   0 |
|   |   |                                                                           +-----+
|   |   +-> O construtor C(int) executa o construtor B()                            |   0 |
|   |   |   |                                                                       +-----+
|   |   |   +-> O construtor B() executa o construtor A()                           |   0 |
|   |   |   |   |                                                                   +-----+
|   |   |   |   +-> O construtor A() executa o setX(int) passando 10 ao parâmetro y |   0 |
|   |   |   |       |                                                               +-----+
|   |   |   |       +-> O setX(int) atribui o valor do parâmetro y ao atributo x    |  10 |
|   |   |   |                                                                       +-----+
|   |   |   +-> O construtor B() adiciona 100 ao atributo x                         | 110 |
|   |   |                                                                           +-----+
|   |   +-> O construtor C(int) executa o método getX()                             | 110 |
|   |   |                                                                           +-----+
|   |   +-> O construtor C(int) soma o parâmetro y ao retorno de getX()             | 110 |
|   |   |                                                                           +-----+
|   |   +-> O construtor C(int) executa o setX(int) passando o valor dessa soma     | 120 |
|   |                                                                               +-----+
|   +-> O construtor C() multiplica o atributo x por 2                              | 240 |
|                                                                                   +-----+
+-> A classe Teste executa o getX()                                                 | 240 |
+-----------------------------------------------------------------------------------+-----+

Grande Staroski, bom dia.

Então ao instanciar um new C(); o java foi na classe C e executou o construtor sem parâmetro C(), eu imaginei que antes desse construtor executar qualquer coisa ele chamaria o construtor da classe Mãe B, o que mudou esse curso? Essa linha:

	this(10); ?

Pelo que entendi do seu teste de mesa, esse “this” (nunca havia visto essa forma de usar o this pra chamar o outro construtor) passou o parâmetro 10 pro construtor C(int), porém esse último não roda nenhuma linha antes de chamar o construtor da classe Mãe B, isso ainda me foge o entendimento.

Antes que C(int) rodasse a primeira linha dele, chama o construtor da classe Mãe B, de forma análoga, o construtor B() antes de rodar qualquer linha, chama o construtor da superclasse A. Apenas o construtor da C() fugiu dessa regra de chamar o construtor super() antes de executar as próprias instruções, qual o poder dessa instrução afinal?

C()
{
this(10); ???

O restante da execução me ficou bem claro.

Exatamente, se você não colocasse essa linha, ele iria implicitamente chamar o super()

Então, a primeira instrução a ser executada por um construtor sempre é a chamada ao construtor da superclasse, que pode ser realizada explicitamente através do super().
Se você não colocar o super(), o compilador vai incluir implicitamente essa chamada no bytecode gerado.
Entretanto, quando você tem mais de um construtor na sua classe, você pode delegar a chamada para outro construtor, usando o this(<parametros-do-outro-construtor>).

C() {
    this(10); // está EXPLICITAMENTE chamando o construtor C(int), então NÃO VAI chamar o super()
    x *= 2;
    System.out.println("Saiu do construtor de C valendo: "+x);
}

C(int y) {
    // não está chamando nenhum outro construtor, então IMPLICITAMENTE VAI chamar o super()
    System.out.println("Usou método C que soma " + y + " Ao getX que vale: " + getX());
    setX(y+getX());
}

No construtor C(int) você pôs a mensagem "Usou método C que soma ...", talvez isso te confundiu, pois esse C(int y) não é um método, ele é um construtor, se ele fosse um método, ele teria que declarar um tipo de dado primitivo como retorno ou um tipo referência ou ser void.

Sim, hoje com um olhar descansado que reparei que haviam dois construtores na classe C e que havia confundido método com construtor.

Staroski, excelente, aprendi coisas novas hoje, fiz o teste de mesa aqui no caderno e me deu uma sensação boa de assimilar coisa nova. Obrigado!

1 curtida