Classe Interna Java

A classe interna do Java é definida dentro do corpo de outra classe. A classe interna do Java pode ser declarada como privada, pública, protegida ou com acesso padrão, enquanto uma classe externa pode ter apenas acesso público ou padrão. As classes internas do Java são divididas em dois tipos.

  1. classe interna estática

    Se a classe interna for estática, é chamada de classe interna estática. As classes internas estáticas podem acessar apenas membros estáticos da classe externa. Uma classe interna estática é igual a qualquer outra classe de nível superior e é aninhada apenas por conveniência de empacotamento. Um objeto de classe estática pode ser criado com a seguinte declaração.

    OuterClass.StaticNestedClass objetoAninhado =
         new OuterClass.StaticNestedClass();
    
  2. classe interna java

    Qualquer classe aninhada não estática é conhecida como classe interna em Java. A classe interna Java está associada ao objeto da classe e pode acessar todas as variáveis e métodos da classe externa. Como as classes internas estão associadas à instância, não podemos ter variáveis estáticas nelas. O objeto da classe interna Java faz parte do objeto da classe externa e, para criar uma instância da classe interna, primeiro precisamos criar uma instância da classe externa. A classe interna Java pode ser instanciada assim;

    ClasseExterna objetoExterno = new ClasseExterna();
    ClasseExterna.ClasseInterna objetoInterno = objetoExterno.new ClasseInterna();
    

Existem dois tipos especiais de classes internas em Java.

  1. Classe interna local

    Se uma classe é definida dentro do corpo de um método, ela é conhecida como classe interna local. Como a classe interna local não está associada a um objeto, não podemos usar modificadores de acesso private, public ou protected com ela. Os únicos modificadores permitidos são abstract ou final. Uma classe interna local pode acessar todos os membros da classe envolvente e variáveis locais finais no escopo em que é definida. Além disso, também pode acessar uma variável local não final do método no qual está definida, mas não pode modificá-las. Portanto, se você tentar imprimir o valor de uma variável local não final, será permitido, mas se tentar alterar seu valor de dentro da classe interna local do método, você receberá um erro de tempo de compilação. A classe interna local pode ser definida como:

    pacote com.journaldev.innerclasses;
    
    public class MainClass {
    
    	private String s_main_class;
    
    	public void print() {
    		String s_print_method = "";
    		// classe interna local dentro do método
    		class Logger {
    			// capaz de acessar variáveis da classe envolvente
    			String name = s_main_class; 
    			// capaz de acessar variáveis do método não final
    			String name1 = s_print_method; 
    
    			public void foo() {
    				String name1 = s_print_method;
    				// O código abaixo gerará um erro de compilação:
    				// A variável local s_print_method definida em um escopo externo deve ser final ou efetivamente final 
    				// s_print_method= ":";
    			}
    		}
    		// instanciar classe interna local no método para usar
    		Logger logger = new Logger();
    
    	}
    }
    

    Também podemos definir uma classe interna local dentro de qualquer bloco, como bloco estático, bloco if-else etc. No entanto, nesse caso, o escopo da classe será muito limitado.

    public class MainClass {
    
    	static {
    		class Foo {
    			
    		}
    		Foo f = new Foo();
    	}
    	
    	public void bar() {
    		if(1 < 2) {
    			class Test {
    				
    			}
    			Test t1 = new Test();
    		}
    		// Abaixo gerará erro devido ao escopo da classe
    		//Test t = new Test();
    		//Foo f = new Foo();
    	}
    }
    
  2. classe interna anônima

    Uma classe interna local sem nome é conhecida como classe interna anônima. Uma classe anônima é definida e instanciada em uma única declaração. As classes internas anônimas sempre estendem uma classe ou implementam uma interface. Como uma classe anônima não tem nome, não é possível definir um construtor para uma classe anônima. As classes internas anônimas são acessíveis apenas no ponto onde são definidas. É um pouco difícil definir como criar uma classe interna anônima, veremos seu uso em tempo real no programa de teste abaixo.

Aqui está uma classe java mostrando como definir classe interna java, classe aninhada estática, classe interna local e uma classe interna anônima. OuterClass.java

package com.journaldev.nested;

import java.io.File;
import java.io.FilenameFilter;

public class OuterClass {
    
    private static String name = "OuterClass";
    private int i;
    protected int j;
    int k;
    public int l;

    // Construtor da classe externa
    public OuterClass(int i, int j, int k, int l) {
        this.i = i;
        this.j = j;
        this.k = k;
        this.l = l;
    }

    public int getI() {
        return this.i;
    }

    //Classe aninhada estática, pode acessar variáveis/métodos estáticos da classe externa
    static class StaticNestedClass {
        private int a;
        protected int b;
        int c;
        public int d;

        public int getA() {
            return this.a;
        }

        public String getName() {
            return name;
        }
    }

    //Classe interna, não estática e pode acessar todas as variáveis/métodos da classe externa
    class InnerClass {
        private int w;
        protected int x;
        int y;
        public int z;

        public int getW() {
            return this.w;
        }

        public void setValues() {
            this.w = i;
            this.x = j;
            this.y = k;
            this.z = l;
        }

        @Override
        public String toString() {
            return "w=" + w + ":x=" + x + ":y=" + y + ":z=" + z;
        }

        public String getName() {
            return name;
        }
    }

    //Classe interna local
    public void print(String initial) {
        //Classe interna local inside the method
        class Logger {
            String name;

            public Logger(String name) {
                this.name = name;
            }

            public void log(String str) {
                System.out.println(this.name + ": " + str);
            }
        }

        Logger logger = new Logger(initial);
        logger.log(name);
        logger.log("" + this.i);
        logger.log("" + this.j);
        logger.log("" + this.k);
        logger.log("" + this.l);
    }

    //Classe interna anônima
    public String[] getFilesInDir(String dir, final String ext) {
        File file = new File(dir);
        //Classe interna anônima implementing FilenameFilter interface
        String[] filesList = file.list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(ext);
            }

        });
        return filesList;
    }
}

Aqui está o programa de teste mostrando como instanciar e usar a classe interna em Java. InnerClassTest.java

package com.journaldev.nested;

import java.util.Arrays;
//Classes aninhadas podem ser usadas em imports para fácil instanciação
import com.journaldev.nested.OuterClass.InnerClass;
import com.journaldev.nested.OuterClass.StaticNestedClass;

public class InnerClassTest {

    public static void main(String[] args) {
        OuterClass outer = new OuterClass(1,2,3,4);
        
        //Exemplo de classes aninhadas estáticas
        StaticNestedClass staticNestedClass = new StaticNestedClass();
        StaticNestedClass staticNestedClass1 = new StaticNestedClass();
        
        System.out.println(staticNestedClass.getName());
        staticNestedClass.d=10;
        System.out.println(staticNestedClass.d);
        System.out.println(staticNestedClass1.d);
        
        //Exemplo de classe interna
        InnerClass innerClass = outer.new InnerClass();
        System.out.println(innerClass.getName());
        System.out.println(innerClass);
        innerClass.setValues();
        System.out.println(innerClass);
        
        //Chamando método usando classe interna local
        outer.print("Outer");
        
        //Chamando método usando classe interna anônima
        System.out.println(Arrays.toString(outer.getFilesInDir("src/com/journaldev/nested", ".java")));
        
        System.out.println(Arrays.toString(outer.getFilesInDir("bin/com/journaldev/nested", ".class")));
    }

}

Aqui está a saída do programa de exemplo de classe interna Java acima.

OuterClass
10
0
OuterClass
w=0:x=0:y=0:z=0
w=1:x=2:y=3:z=4
Outer: OuterClass
Outer: 1
Outer: 2
Outer: 3
Outer: 4
[NestedClassTest.java, OuterClass.java]
[NestedClassTest.class, OuterClass$1.class, OuterClass$1Logger.class, OuterClass$InnerClass.class, OuterClass$StaticNestedClass.class, OuterClass.class]

Observe que quando OuterClass é compilada, arquivos de classe separados são criados para a classe interna, classe interna local e classe aninhada estática.

Benefícios da Classe Interna Java

  1. Se uma classe é útil apenas para uma classe, faz sentido mantê-la aninhada e junto. Isso ajuda no empacotamento das classes.
  2. Java classes internas implementam encapsulamento. Note que as classes internas podem acessar membros privados da classe externa e, ao mesmo tempo, podemos ocultar a classe interna do mundo externo.
  3. Manter a classe pequena dentro de classes de nível superior coloca o código mais próximo de onde é usado e torna o código mais legível e fácil de manter.

Isso é tudo para a classe interna do Java.

Source:
https://www.digitalocean.com/community/tutorials/java-inner-class