Java Внутренний Класс

Внутренний класс Java определяется внутри тела другого класса. Внутренний класс Java может быть объявлен как private, public, protected или с доступом по умолчанию, тогда как внешний класс может иметь только public или доступ по умолчанию. Вложенные классы Java делятся на два типа.

  1. статический вложенный класс

    Если вложенный класс является статическим, то он называется статическим вложенным классом. Статические вложенные классы могут обращаться только к статическим членам внешнего класса. Статический вложенный класс такой же, как и любой другой класс верхнего уровня, и он вложен только для удобства упаковки. Объект статического класса можно создать следующим оператором.

    OuterClass.StaticNestedClass nestedObject =
         new OuterClass.StaticNestedClass();
    
  2. Java внутренний класс

    Любой нестатический вложенный класс известен как внутренний класс в Java. Внутренний класс Java связан с объектом класса и может обращаться ко всем переменным и методам внешнего класса. Поскольку внутренние классы связаны с экземпляром, в них не может быть статических переменных. Объекты внутреннего класса Java являются частью объекта внешнего класса, и чтобы создать экземпляр внутреннего класса, сначала нам нужно создать экземпляр внешнего класса. Внутренний класс Java может быть создан таким образом;

    OuterClass outerObject = new OuterClass();
    OuterClass.InnerClass innerObject = outerObject.new InnerClass();
    

Существуют два особых вида внутренних классов Java.

  1. локальный внутренний класс

    Если класс определен в теле метода, он известен как локальный внутренний класс. Поскольку локальный внутренний класс не ассоциирован с объектом, мы не можем использовать модификаторы доступа private, public или protected с ним. Единственные разрешенные модификаторы – это abstract или final. Локальный внутренний класс может получить доступ ко всем членам внешнего класса и к локальным финальным переменным в области, в которой он определен. Кроме того, он также может получить доступ к не финальной локальной переменной метода, в котором он определен, но не может их изменять. Так что если вы попытаетесь напечатать значение не финальной локальной переменной, это будет разрешено, но если вы попытаетесь изменить его значение из внутреннего класса метода, вы получите ошибку времени компиляции. Локальный внутренний класс можно определить как:

    package com.journaldev.innerclasses;
    
    public class MainClass {
    
    	private String s_main_class;
    
    	public void print() {
    		String s_print_method = "";
    		// локальный внутренний класс внутри метода
    		class Logger {
    			// может получить доступ к переменным внешнего класса
    			String name = s_main_class; 
    			// может получить доступ к не финальным переменным метода
    			String name1 = s_print_method; 
    
    			public void foo() {
    				String name1 = s_print_method;
    				// Нижеприведенный код вызовет ошибку времени компиляции:
    				// Локальная переменная s_print_method, определенная в охватывающей области, должна быть финальной или эффективно финальной 
    				// s_print_method= ":";
    			}
    		}
    		// создание экземпляра локального внутреннего класса в методе для использования
    		Logger logger = new Logger();
    
    	}
    }
    

    Мы также можем определить локальный внутренний класс внутри любого блока, такого как статический блок, блок if-else и т. д. Однако в этом случае область класса будет очень ограничена.

    public class MainClass {
    
    	static {
    		class Foo {
    			
    		}
    		Foo f = new Foo();
    	}
    	
    	public void bar() {
    		if(1 < 2) {
    			class Test {
    				
    			}
    			Test t1 = new Test();
    		}
    		// Ниже будет ошибка из-за области класса
    		//Test t = new Test();
    		//Foo f = new Foo();
    	}
    }
    
  2. анонимный внутренний класс

    Локальный внутренний класс без имени известен как анонимный внутренний класс. Анонимный класс определяется и создается в одном операторе. Анонимный внутренний класс всегда расширяет класс или реализует интерфейс. Поскольку у анонимного класса нет имени, невозможно определить конструктор для анонимного класса. Анонимные внутренние классы доступны только в том месте, где они определены. Немного сложно определить, как создать анонимный внутренний класс, мы увидим его использование в реальном времени в тестовой программе ниже.

Вот класс Java, демонстрирующий, как определить внутренний класс Java, статический вложенный класс, локальный внутренний класс и анонимный внутренний класс. 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;

    //Конструктор OuterClass
    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;
    }

    //статический вложенный класс, имеющий доступ к статическим переменным/методам OuterClass
    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;
        }
    }

    //внутренний класс, нестатический и имеющий доступ ко всем переменным/методам внешнего класса
    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;
        }
    }

    //локальный внутренний класс
    public void print(String initial) {
        //локальный внутренний класс 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);
    }

    //анонимный внутренний класс
    public String[] getFilesInDir(String dir, final String ext) {
        File file = new File(dir);
        //анонимный внутренний класс implementing FilenameFilter interface
        String[] filesList = file.list(new FilenameFilter() {

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

        });
        return filesList;
    }
}

Вот тестовая программа, демонстрирующая, как создавать и использовать внутренний класс в Java. InnerClassTest.java

package com.journaldev.nested;

import java.util.Arrays;
//вложенные классы могут использоваться в импорте для удобного создания экземпляров
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);
        
        //пример статически вложенных классов
        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);
        
        //пример внутреннего класса
        InnerClass innerClass = outer.new InnerClass();
        System.out.println(innerClass.getName());
        System.out.println(innerClass);
        innerClass.setValues();
        System.out.println(innerClass);
        
        //вызов метода с использованием локального внутреннего класса
        outer.print("Outer");
        
        //вызов метода с использованием анонимного внутреннего класса
        System.out.println(Arrays.toString(outer.getFilesInDir("src/com/journaldev/nested", ".java")));
        
        System.out.println(Arrays.toString(outer.getFilesInDir("bin/com/journaldev/nested", ".class")));
    }

}

Вот вывод примера программы внутреннего класса Java выше.

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]

Обратите внимание, что при компиляции OuterClass создаются отдельные файлы классов для внутреннего класса, локального внутреннего класса и статического вложенного класса.

Преимущества внутреннего класса Java

  1. Если класс полезен только одному классу, имеет смысл хранить его вложенным и вместе. Это помогает в упаковке классов.
  2. Внутренние классы Java реализуют инкапсуляцию. Обратите внимание, что внутренние классы могут получать доступ к закрытым членам внешнего класса, и в то же время мы можем скрыть внутренний класс от внешнего мира.
  3. Помещение небольших классов внутри классов верхнего уровня делает код ближе к месту его использования и обеспечивает более читабельный и поддерживаемый код.

Это все, что касается внутренних классов Java.

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