Java галантерейные товары глубокое понимание внутренних классов Java

Java

Класс может быть определен в другом классе или методе, такой класс называется внутренним классом — «Мышление на Java».

Говоря о внутренних классах, вы знакомы с ними и будете часто использовать их при создании экземпляров контейнеров. Тем не менее, многие люди до сих пор не знакомы с конкретной грамматикой, принципом и реализацией внутреннего класса.Вот краткое изложение, в надежде улучшить понимание внутреннего класса с помощью этого краткого изложения.


Что такое внутренний класс?

Как видно из начала статьи, определение внутреннего класса: класс, определенный в другом классе или методе. В соответствии с различными сценариями использования внутренние классы можно разделить на четыре типа: внутренние классы-члены, локальные внутренние классы, анонимные внутренние классы и статические внутренние классы. Характеристики и меры предосторожности каждого из них различны, и мы объясним их одну за другой ниже.

внутренний класс-член

Как следует из названия, внутренний класс члена — это класс, определенный внутри класса как член класса. следующим образом:

public class Outer {
    
   public class Inner{
       
   }

}

Особенности следующие:

  1. Члены внутренних классов могут быть изменены модификаторами разрешений (например.public,private等) модифицирован
  2. Члены внутреннего класса могут получить доступ ко всем членам внешнего класса (включаяprivate)член
  3. Внутренний класс-член по умолчанию содержит ссылку на объект внешнего класса.
  4. как будто с помощьюthis一样,当成员名或方法名发生覆盖时,可以使用外部类的名字加.this指定访问外部类成员。 Такие как:Outer.this.name
  5. Члены внутреннего класса не могут быть определеныstaticчлен
  6. Синтаксис создания внутреннего класса члена:
Outer outer=new Outer();
Outer.Inner inner=outer.new Inner();

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

Локальный внутренний класс — это класс, определенный в методе или области видимости, и он отличается от внутреннего класса-члена только правами доступа.

public class Outer{
    public void test(){
        class Inner{
            
        }
    }
}

Особенности следующие:

  1. Локальные внутренние классы не могут иметь модификаторы доступа

  2. Локальный внутренний класс не может быть определен какstatic

  3. Невозможно определить локальный внутренний классstaticчлен

  4. Локальный внутренний класс по умолчанию содержит ссылку на объект внешнего класса.

  5. Также можно использовать локальные внутренние классы.Outer.thisФормулировка синтаксиса для доступа к внешним членам класса

  6. Локальный внутренний класс хочет использовать переменную в методе или поле, которое должно бытьfinalиз

    После JDK1.8 нетfinalретушь,effectively finalвозможно. Что это значит? просто нетfinalмодификации, но если вы добавитеfinalКомпилятор не сообщит об ошибке.

анонимный внутренний класс

Анонимные внутренние классы — это внутренние классы без имени, объединенные с наследованием.

public class Outer{
    public List<String> list=new ArrayList<String>(){
        {
            add("test");
        }
    };
}

Это синтаксис, который мы используем чаще всего. Характеристики анонимных внутренних классов следующие:

  1. Анонимные внутренние классы используют отдельный блок для представления блока инициализации.{}
  2. Анонимный внутренний класс хочет использовать переменную в методе или поле, которое должно бытьfinalИзменено, после JDK1.8effectively finalтакже может
  3. Анонимный внутренний класс по умолчанию содержит ссылку на объект внешнего класса.
  4. Анонимный внутренний класс указывает на наследствополагатьсятип

вложенный класс

Используются вложенные классы.staticвнутренний класс украшенного члена

public class Outer {
    
   public static class Inner{
       
   }

}

Особенности следующие:

  1. Вложенный класс — это единственный внутренний класс из четырех, который не содержит ссылки на объект внешнего класса.

  2. Вложенные классы могут быть определеныstaticчлен

  3. Вложенные классы могут обращаться к любым статическим элементам данных и методам внешнего класса.

    Конструкторы можно рассматривать как статические методы и, следовательно, они доступны.


Зачем нужны внутренние классы?

Как видно из вышеизложенного, характеристики внутренних классов аналогичны характеристикам сторон классов, но внутренние классы имеют громоздкий и детальный синтаксис. Поскольку внутренние классы имеют так много деталей, на которые следует обратить внимание, почему Java поддерживает внутренние классы?

1. Улучшить множественное наследование
  1. Когда C++ использовался в качестве объектно-ориентированного языка программирования на заре, самой трудной задачей было множественное наследование, которое не очень подходило для связывания кода и понимания кода пользователями, а также было более известно тем, что оно множественное наследование бриллиантов смерти Вопросы наследования. Поэтому Java не поддерживает множественное наследование.
  2. Позже разработчики Java обнаружили, что без множественного наследования некоторые проблемы проектирования и программирования с дружественным кодом становится очень трудно решить. Так был создан внутренний класс. Внутренний класс имеет характеристики неявного содержания объекта внешнего класса и возможности взаимодействия с ним, что прекрасно решает проблему множественного наследования.
2. Решить несколько проблем реализации/наследования
  1. Иногда в классе необходимо несколько раз реализовать один и тот же интерфейс разными способами.Если внутреннего класса нет, нужно несколько раз определить разное количество классов, но использование внутренних классов может очень хорошо решить эту проблему. Можно реализовать один и тот же интерфейс, то есть реализовать инкапсуляцию кода, и реализовать разные реализации одного и того же интерфейса.

  2. Внутренние классы могут инкапсулировать реализацию композиции внутри.


Почему синтаксис внутренних классов такой сложный?

Это основное внимание в этой статье. Синтаксис внутреннего класса настолько сложен, потому что он представляет собой комбинацию новых типов данных и синтаксического сахара. Если вы хотите понять внутренние классы, вы должны начать с сути.

Внутренние классы делятся на 4 типа в соответствии с разными сценариями применения. Сценарии его применения вполне можно сравнить с методами класса.
Давайте воспользуемся шаблоном сравнения методов класса один за другим, чтобы ответить, почему внутренние классы имеют такие характеристики.

Внутренний класс-член -> метод-член

Внутренние классы-члены спроектированы точно так же, как методы-члены.
Метод вызова участника:outer.getName()
Создайте новый объект внутреннего класса:outer.new Inner()
Все они вызываются в зависимости от объекта. Как говорится в «Мышлении на Java»,outer.getName()действительно выглядитOuter.getName(outer), то есть передача вызывающего объекта в качестве параметра методу.
То же верно и для создания нового внутреннего класса:Outer.new Inner(outer)

Ниже мы используем реальную ситуацию, чтобы доказать: Создайте новый класс с внутренними классами:

public class Outer {

    private int m = 1;

    public class Inner {
    
        private void test() {
            //访问外部类private成员
            System.out.println(m);
        }
    }
}

Скомпилируйте, вы обнаружите, что в целевом каталоге компиляции будут сгенерированы два файла .class:Outer.classа такжеOuter$Inner.class.

PS: Не уверен, почему Java всегда и过不去,就连变量命名规则都要比C++多一个能由Сочинение :)

БудуOuter$Inner.classПоместите его в IDEA, чтобы открыть, он будет автоматически декомпилирован, и проверьте результат:

public class Outer$Inner {
    public Outer$Inner(Outer this$0) {
        this.this$0 = this$0;
    }

    private void test() {
        System.out.println(Outer.access$000(this.this$0));
    }
}

Как видите, компилятор автоматически сгенерировал конструктор по умолчанию, который является конструктором параметров со ссылкой на внешний тип.

Члены класса могут видеть внешние ссылки на объекты:finalдекоративный.

следовательно:

  1. Члены внутренних классов являются членами уровня класса, поэтому их можно изменить с помощью модификаторов доступа.
  2. Внутренний класс-член содержит ссылку на объект внешнего класса при создании внутреннего класса, поэтому внутренний класс-член может получить доступ ко всем членам внешнего класса.
  3. Синтаксис диктует: потому что это часть внешнего класса, даже еслиprivateК объектам также можно обращаться через внутренние классы. . Доступ через директиву Outer.access$
  4. Точно так же, как нестатические методы не могут получить доступ к статическим членам, нестатические внутренние классы также спроектированы так, чтобы не иметь статических переменных, поэтому внутренние классы не могут быть определены.staticЦели и методы.

но может определитьstatic finalпеременная, это не конфликтует, потому что определеннаяfinalПоле должно быть определено во время компиляции, и соответствующая переменная будет заменена определенным значением при компиляции класса, поэтому с точки зрения JVM доступ к внутреннему классу не осуществляется.

Локальный внутренний класс --> локальный блок кода

Локальные внутренние классы можно понимать как локальные блоки кода. Его самая большая особенность заключается в том, что он может получить доступ только к внешнимfinalПеременная. Не спешите спрашивать, почему.
Определите локальный внутренний класс:

public class Outer {

    private void test() {

        int  m= 3;
        class Inner {
            private void print() {
                System.out.println(m);
            }
        }
    }

}

Скомпилируйте и обнаружите, что сгенерированы два файла .classOuter.classа такжеOuter$1Inner.classБудуOuter$1Inner.classПоместите его в IDEA для декомпиляции:

class Outer$1Inner {
    Outer$1Inner(Outer this$0, int var2) {
        this.this$0 = this$0;
        this.val$m = var2;
    }

    private void print() {
        System.out.println(this.val$m);
    }
}

Как видите, компилятор автоматически генерирует конструктор по умолчанию с двумя параметрами.
Увидев это, может быть, должно быть ясно: давайте конвертируем код:

public class Outer {
    private void test() {
        int  m= 3;
        Inner inner=new Outer$1Inner(this,m);
        
        inner.print();
        }
    }

}

То есть во внутренней стороне, это на самом деле значение M, копирует на внутренний класс.print()Метод просто выводит m, если мы напишем такой код:

    private void test() {

        int  m= 3;

        class Inner {

            private void print() {
               m=4;
            }
        }
        
       System.out.println(m);  
    }

По нашему мнению, значение m следует изменить на 4, но реальный эффект таков:

private void test(){
    int m = 3;
    
    print(m);
    
    System.out.println(m);
}

private void print(int m){
    m=4;
}

m копируется в метод как параметр. Следовательно, изменение его значения не имеет никакого эффекта, поэтому, чтобы не запутать программиста произвольным изменением m без достижения какого-либо эффекта, m должно бытьfinalретушь.

После такого большого круга, почему компилятор выдаёт такой эффект?
На самом деле, любой, кто понимает концепцию замыканий, должен знать почему. И все виды странного синтаксиса в Java, как правило, зависят от жизненного цикла. В приведенной выше программе m — локальная переменная, определенная в стеке, иnew Outer$1Inner(this,m);Сгенерированные объекты определяются в куче. Если m не копируется в объект как переменная-член, оставьте область действия m,InnerОбъект, на который указывает, является недопустимым адресом. Поэтому компилятор автоматически создает члены для всех параметров, используемых разделяемым классом.

Почему в других языках нет этого явления?
Это восходит к классическому вопросу: передается ли Java по значению или по ссылке. Из-за Явыalways pass-by-value, для реальных ссылок, Java не может пройти мимо. Суть вышеупомянутой проблемы заключается в том, что если m изменится, другие копии m не смогут это воспринять. Другие языки решают эту проблему другими способами.
Для С++ это проблема указателя.

Понять реальную причину, они также должны знать, когдаfinal, когда не нужноfinal.

public class Outer {
    private void test() {
        class Inner {
        int m=3;
            private void print() {
                System.out.println(m);//作为参数传递,本身都已经 pass-by-value。不用final
                int c=m+1; //直接使用m,需要加final
                
            }
        }
    }

}

В Java 8 политика была смягчена, чтобы разрешитьeffectively finalПеременная, собственно, компилятор в процессе компиляции, помогу добавитьfinalВот и все. И вы должны убедиться, что компилятор добавляетfinalПосле этого программа не сообщает об ошибке.

  1. В локальных внутренних классах есть функция, не имеющая модификатора разрешений. Так же, как локальные переменные не могут иметь одинаковые модификаторы доступа.

  2. Как видно из вышеизложенного, внешний объект также передается в локальный класс, поэтому локальный класс может получить доступ к внешнему объекту.

Вложенный класс --> статический метод

О вложенных классах и говорить нечего, как и о статических методах, к ним можно обращаться напрямую, и они также могут определять статические переменные. Также не может получить доступ к нестатическим членам.
Стоит отметить, что в «Think in Java» сказано, что конструктор можно рассматривать как статический метод, поэтому вложенный класс может получить доступ к конструктору внешнего класса.

Анонимный класс --> локальный метод + унаследованный синтаксический сахар

Анонимные классы можно рассматривать как расширения первых трех классов. В частности, анонимные классы можно рассматривать как:

  • Члены наследования внутреннего класса +
  • Локальный внутренний класс + наследование
  • Вложенные внутренние классы + наследование

Синтаксис анонимного класса:

new 继承类名(){
  
  //Override 重载的方法    
    
}

Возвращенный результат преобразуется в унаследованный класс.

Объявите анонимный класс:

public class Outer {

    private  List<String> list=new ArrayList<String>(){
        {
            add("test");
        }
    };

}

Это классическое использование анонимного класса. Также при компиляции приведенного выше кода будут созданы два файла .class.Outer.class,Outer$1.classБудуOuter$1.classПоместите его в IDEA для декомпиляции:

class Outer$1 extends ArrayList<String> {
    Outer$1(Outer this$0) {
        this.this$0 = this$0;
        this.add("1");
    }
}

Вы можете видеть, что полный синтаксис анонимного класса — это наследование + внутренний класс.
Поскольку анонимные классы могут быть объявлены как переменные-члены, локальные переменные и статические переменные-члены, их комбинация представляет собой синтаксический сахар нескольких внутренних классов плюс наследование, которое здесь не будет доказываться.
Здесь стоит отметить, что анонимные классы не могут объявить конструкторы, такие как нормальные классы через синтаксический сахар, потому что у них нет названия класса, но компилятор может распознать{}и поместите код в конструктор во время компиляции.

{}Их может быть более одного, которые будут выполняться последовательно в сгенерированном конструкторе.


Как правильно использовать внутренние классы

Во втором подразделе мы обсудили вариант использования внутреннего класса, но как его использовать элегантно и в правильном варианте использования? В этом разделе мы подробно обсудим.

1.Остерегайтесь утечек памяти

Раздел 24 «Эффективная Java» проясняет это. Предпочитайте статические внутренние классы. Почему это? Из приведенного выше анализа мы можем узнать, что, за исключением вложенных классов, другие внутренние классы неявно содержат объекты внешнего класса. Это источник утечек памяти Java. Посмотрите на код:

Определить внешний:

public class Outer{

    public  List<String> getList(String item) {

        return new ArrayList<String>() {
            {
                add(item);
            }
        };
    }
}

Используйте внешний:

public class Test{

   public static List<String> getOutersList(){
   
    Outer outer=new Outer();
    //do something
    List<String> list=outer.getList("test");
   
    return list;    
   }
   public static void main(String[] args){
       List<String> list=getOutersList();
       
      
      //do something with list
   }
   
}

Я считаю, что такой код должен быть написан одноклассником, что связано с проблемой привычки:

Методы, не использующие методы-члены класса и переменные-члены, лучше всего определять как статические.

Давайте сначала изучим приведенный выше код, самая большая проблема — это утечка памяти:
Во время использования мы определяемOuterОбъект завершает ряд действий

  • использоватьouterПолучилArraListобъект
  • БудуArrayListВ результате верните.

В норме вgetOutersListметод, мыnewВышло два объекта:outerа такжеlist, а выходя из этого метода, мы простоlistСсылка на объект передается,outerСсылка на уничтожается при выходе из стека методов. Логически говоря,outerВ этот момент объект должен быть бесполезен и должен быть уничтожен при следующем сборе памяти.

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

Как избежать этой ситуации?

Это просто:Методы, не использующие методы-члены класса и переменные-члены, лучше всего определять как статические.

public class Outer{

    public static List<String> getList(String item) {

        return new ArrayList<String>() {
            {
                add(item);
            }
        };
    }
}

Класс, определенный таким образом, является вложенным классом + наследство и не содержит ссылок на внешние классы.

2. применяется только для реализации класса, реализующего интерфейс

  • Шаблон элегантного фабричного метода

Мы видим, что в шаблоне фабричного метода каждая реализация должна будет реализовать Fractory для реализации интерфейса, который генерирует объект, и этот интерфейс на самом деле тесно связан с исходным классом, поэтому мы можем определить Fractory в конкретном классе, существует как внутренний класс

  • Простая реализация интерфейса
       new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("test");
           }
       }

       ).start();
    }

Старайтесь не использовать Thread напрямую, здесь только для демонстрации Для Java 8 вместо таких приложений рекомендуется использовать лямбда-выражения

  • Внедрить несколько интерфейсов одновременно
public class imple{

    public static Eat getDogEat(){
        return new EatDog();
    }

    public static Eat getCatEat(){
        return new EatCat();
    }

    private static class EatDog implements Eat {
        @Override
        public void eat() {
            System.out.println("dog eat");
        }
    }
    private static class EatCat implements Eat{
        @Override
        public void eat() {
            System.out.println("cat eat");
        }
    }
}

3.Элегантный один случай

public class Imple {

    public static Imple getInstance(){
        return ImpleHolder.INSTANCE;
    }


    private static class ImpleHolder{
        private static final Imple INSTANCE=new Imple();
    }
}

4. Десериализовать JavaBean, принятый JSON Иногда необходимо десериализовать вложенный JSON

{
    "student":{
        "name":"",
        "age":""
    }
}

Подобно этому. Мы можем напрямую определить вложенные классы для десериализации

public JsonStr{
    
    private Student student;
    
    public static Student{
        private String name;
        private String age;
        
        //getter & setter
    }

    //getter & setter
}

Но обратите внимание, что здесь следует использовать вложенные классы, потому что нам не нужно обмениваться данными с внешними классами.

Главная мысль:

  • Вложенные классы могут получить доступ к конструктору внешнего класса
  • Поместите первый доступ к внутреннему классу в метод, чтобы только при вызове метода был доступ к внутреннему классу в первый раз, реализуя ленивую загрузку.

Существует множество применений внутренних классов, которые здесь не перечислены.


Суммировать

Понимание внутреннего класса можно понять по методике, но многие черты внутреннего класса надо лишить синтаксического сахара и понять, зачем им это нужно, чтобы полностью понять, понять все особенности внутреннего класса, чтобы лучше используйте внутренний класс, в процессе использования внутреннего класса вы должны помнить: если вы можете использовать вложенный класс, используйте вложенный класс.Если внутреннему классу нужно соединиться с внешним классом, используйте только внутренний класс. наконецМетоды, не использующие методы-члены класса и переменные-члены, лучше всего определять как статические.Утечки памяти внутреннего класса можно предотвратить.

Плоды труда уважайте, при перепечатке просьба указывать источник.


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

Справочная статья:
Каково значение предъявления внутренних классов в Java?
Почему во внутренних классах-членах не может быть статических членов и методов?