Статья, которая даст вам полное представление о внутренних классах Java.

Java задняя часть переводчик Parcel
Статья, которая даст вам полное представление о внутренних классах Java.

исходный адрес

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

Поместите определение класса внутрь определения другого класса, тогда этот класс является внутренним классом


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

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


Элегантность внутренних классов:

Каждый внутренний класс может независимо наследовать реализацию (интерфейса), независимо от того, унаследовал ли внешний класс реализацию (интерфейса) или нет, это не влияет на внутренний класс.

Внутренние классы в основном имеют следующие категории:

Уведомление:

  1. После определения внутреннего класса-члена вы должны использовать объект внешнего класса для создания объекта внутреннего класса вместо прямого перехода к новому объекту внутреннего класса,
    который:内部类 对象名 = 外部类对象.new 内部类( );
  2. Внешний класс не может напрямую использовать члены и методы внутреннего класса.Вы можете сначала создать объект внутреннего класса, а затем получить доступ к его переменным-членам и методам через объект внутреннего класса.
  3. Вы можете сначала создать объект внутреннего класса, а затем получить доступ к его переменным-членам и методам через объект внутреннего класса.HelloWorld.this.name

A: Внутренний класс члена

Назад в категорию

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

public class Outer {
    
    private static int i = 1;
    private int j = 10;
    private int k = 20;


    public static void outerF1() {
    }

    /**
     * 外部类的静态方法访问成员内部类,与在外部类外部访问成员内部类一样
     */
    public static void outerF4() {
        //step1 建立外部类对象
        Outer out = new Outer();
        //step2 根据外部类对象建立内部类对象
        Inner inner = out.new Inner();
        //step3 访问内部类的方法
        inner.innerF1();
    }

    public static void main(String[] args) {

        /*
         * outerF4();该语句的输出结果和下面三条语句的输出结果一样
         *如果要直接创建内部类的对象,不能想当然地认为只需加上外围类Outer的名字,
         *就可以按照通常的样子生成内部类的对象,而是必须使用此外围类的一个对象来
         *创建其内部类的一个对象:
         *Outer.Inner outin = out.new Inner()
         *因此,除非你已经有了外围类的一个对象,否则不可能生成内部类的对象。因为此
         *内部类的对象会悄悄地链接到创建它的外围类的对象。如果你用的是静态的内部类,
         *那就不需要对其外围类对象的引用。
         */
        Outer out = new Outer();
        Outer.Inner outin = out.new Inner();
        outin.innerF1();
    }

    public void outerF2() {
    }

    /**
     * 外部类的非静态方法访问成员内部类
     */
    public void outerF3() {
        Inner inner = new Inner();
        inner.innerF1();
    }

    /**
     * 成员内部类中,不能定义静态成员
     * 成员内部类中,可以访问外部类的所有成员
     */
    class Inner {
        // static int innerI = 100;内部类中不允许定义静态变量
        // 内部类和外部类的实例变量可以共存
        int j = 100;
        int innerI = 1;


        void innerF1() {
            System.out.println(i);
            //在内部类中访问内部类自己的变量直接用变量名
            System.out.println(j);
            //在内部类中访问内部类自己的变量也可以用this.变量名
            System.out.println(this.j);
            //在内部类中访问外部类中与内部类同名的实例变量用外部类名.this.变量名
            System.out.println(Outer.this.j);
            //如果内部类中没有与外部类同名的变量,则可以直接用变量名访问外部类变量
            System.out.println(k);
            outerF1();
            outerF2();
        }
    }
}

Примечание. Внутренние классы — это концепция времени компиляции, и после успешной компиляции они становятся двумя совершенно разными классами.

Для внешнего класса с именем external и внутреннего класса с именем inner, определенного внутри него. После завершения компиляции появляются external.class и external$inner.class.


##

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

Назад в категорию

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


public class Outer {

    private int s = 100;
    private int outI = 1;

    public static void main(String[] args) {
        // 访问局部内部类必须先有外部类对象
        Outer out = new Outer();
        out.f(3);
    }

    public void f(final int k) {
        final int s = 200;
        int i = 1;
        final int j = 10;


        /**
         * 定义在方法内部
         */
        class Inner {
            // 可以定义与外部类同名的变量
            int s = 300;
            int innerI = 100;

            // static int m = 20; 不可以定义静态变量
            Inner(int k) {
                innerF(k);
            }
            void innerF(int k) {
                // java如果内部类没有与外部类同名的变量,在内部类中可以直接访问外部类的实例变量
                System.out.println(outI);
                // 可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的
                System.out.println(j);
                //System.out.println(i);
                // 如果内部类中有与外部类同名的变量,直接用变量名访问的是内部类的变量
                System.out.println(s);
                // 用this.变量名访问的也是内部类变量
                System.out.println(this.s);
                // 用外部类名.this.内部类变量名访问的是外部类变量
                System.out.println(Outer.this.s);
            }
        }
        new Inner(k);
    }
}

C: Статический внутренний класс (вложенный класс):

Назад в категорию

注意:前两种内部类与变量类似,所以可以对照参考变量

Если вам не нужна связь между внутренним объектом класса и окружающим его объектом класса, вы можете объявить внутренний класс статическим. Его часто называют вложенным классом. Чтобы понять, что означает static применительно к внутреннему классу, вы должны помнить, что обычный объект внутреннего класса неявно содержит ссылку на объект окружающего класса, который его создал. Однако это не тот случай, когда внутренний класс является статическим. Вложенные классы означают:

  1. Для создания объекта вложенного класса не требуется объект его объемлющего класса.
  2. К нестатическому охватывающему объекту класса нельзя получить доступ из объекта вложенного класса.

Одноэлементный режим: из-за механизма загрузки статических внутренних классов определено, что его можно использовать для обработки одноэлементного режима, а производительность объективно связана с одноэлементным режимом>>.я указываю


public class Outer {
    private static int i = 1;
    private int j = 10;

    public static void outerF1() {
    }

    public static void main(String[] args) {
        new Outer().outerF3();
    }

    public void outerF2() {
    }

    public void outerF3() {
        // 外部类访问内部类的静态成员:内部类.静态成员
        System.out.println(Inner.inner_i);
        Inner.innerF1();
        // 外部类访问内部类的非静态成员:实例化内部类即可
        Inner inner = new Inner();
        inner.innerF2();
    }

    /**
     * 静态内部类可以用public,protected,private修饰
     * 静态内部类中可以定义静态或者非静态的成员
     */
    static class Inner {
        static int inner_i = 100;
        int innerJ = 200;

        static void innerF1() {
            // 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
            System.out.println("Outer.i" + i);
            outerF1();
        }


        void innerF2() {
            // 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
            // System.out.println("Outer.i"+j);
            // outerF2();
        }
    }
}

Разница между статическим внутренним классом и внутренним классом-членом

Генерация статического внутреннего класса не требует членов внешнего класса Объекты статических внутренних классов можно генерировать напрямую:Outer.Inner in = new Outer.Inner();Вместо необходимости генерировать путем создания объекта внешнего класса. Это фактически делает статический внутренний класс классом верхнего уровня. (Обычно вы не можете поместить какой-либо код внутрь интерфейса, но вложенный класс может быть частью интерфейса, потому что он статичен. Простое помещение вложенного класса в пространство имен интерфейса не нарушает правила интерфейса.)


D: Анонимный внутренний класс (от мышления в java 3th)

Назад в категорию

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

Когда вам нужно использовать анонимные внутренние классы?

Анонимный внутренний класс подходит, если выполняются следующие условия:

  • Используется только один экземпляр класса.
  • Классы используются сразу после их определения.
  • Классы очень маленькие (SUN рекомендует до 4 строк кода)
  • Именование классов не делает ваш код более понятным.

При использовании анонимных внутренних классов помните о следующих принципах:

  1. Анонимные внутренние классы обычно не могут иметь конструкторов.
  2. Анонимные внутренние классы не могут определять статические члены, методы и классы.
  3. Анонимные внутренние классы не могут быть публичными, защищенными, приватными, статическими.
  4. Можно создать только один экземпляр анонимного внутреннего класса.
  5. Анонимный внутренний класс должен стоять за новым, с помощью которого он неявно реализует интерфейс или реализует класс.
  6. Поскольку анонимные внутренние классы являются локальными внутренними классами, к ним применяются все ограничения локальных внутренних классов.

Следующий пример выглядит немного странно:

// 在方法中返回一个匿名内部类
public class Parcel6 {
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        Contents c = p.cont();
    }

    public Contents cont() {
        return new Contents() {
            private int i = 11;


            public int value() {
                return i;
            }
        }; // 在这里需要一个分号
    }
}

  cont()Метод объединяет следующие два действия: генерацию возвращаемого значения и определение класса, представляющего это возвращаемое значение!    Далее, этот класс анонимен, у него нет имени. Что еще хуже, похоже, вы собираетесь создать объект Contents:

return new Contents()

   Но, не дойдя до точки с запятой в конце оператора, вы говорите: «Подождите, я хочу вставить сюда определение класса»:

return new Contents() {
    private int i = 11;

    public int value() {
        return i;
    }
};

   Этот странный синтаксис означает: «Создать объект анонимного класса, который наследуется от Contents.» Ссылка, возвращаемая новым выражением, автоматически преобразуется в ссылку на Contents. Синтаксис анонимных внутренних классов представляет собой краткую форму следующего примера:

class MyContents implements Contents {
    private int i = 11;

    public int value() {
        return i;
    }
}
return new MyContents();

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

public class Parcel7 {
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Wrapping w = p.wrap(10);
    }

    public Wrapping wrap(int x) {
        // Base constructor call:
        // Pass constructor argument.
        return new Wrapping(x) { 
            public int value() {
                return super.value() * 47;
            }
        }; // Semicolon required
    }
}

   Просто передайте соответствующие параметры конструктору базового класса, в данном случае передав x в new Wrapping(x). Точка с запятой в конце анонимного внутреннего класса не используется для обозначения конца внутреннего класса (как в C++). По сути, он отмечает конец выражения, которое содержит внутренний класс. Следовательно, это согласуется с точкой с запятой, используемой в другом месте.

   Если вы определяете переменную-член в анонимном классе, вы также можете инициализировать ее:

public class Parcel8 {
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Destination d = p.dest("Tanzania");
    }

    // Argument must be final to use inside
    // anonymous inner class:
    public Destination dest(final String dest) {
        return new Destination() {
            private String label = dest;

            public String readLabel() {
                return label;
            }
        };
    }
}

   Если у вас есть анонимный внутренний класс, который использует объект, определенный вне его, компилятор потребует, чтобы его ссылки на параметры были окончательными, напримерdest()параметры в . Если вы забудете, вы получите сообщение об ошибке во время компиляции. Если вы просто присваиваете значение переменной-члену, метод в этом примере подходит. Но что, если вы хотите сделать что-то похожее на конструктор? Невозможно иметь именованный инициализатор в анонимном классе (потому что у него вообще нет имени!), но с инициализацией экземпляра вы можете добиться эффекта «создания» инициализатора для анонимного внутреннего класса. Делайте так:

abstract class Base {
    public Base(int i) {
        System.out.println("Base constructor, i = " + i);
    }

    public abstract void f();
}


public class AnonymousConstructor {
    public static Base getBase(int i) {
        return new Base(i) {
            {
                System.out.println("Inside instance initializer");
            }

            public void f() {
                System.out.println("In anonymous f()");
            }
        };
    }

    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}

   В этом примере переменная i не обязательно должна быть конечной. Поскольку i передается конструктору базового класса анонимного класса, он не используется непосредственно внутри анонимного класса. Следующий пример — это форма «посылка» с инициализацией экземпляра. Обратите внимание, что аргументы функции dest() должны быть окончательными, поскольку они используются внутри анонимного класса.


public class Parcel9 {
    public Destinationdest(final String dest, final float price) {
        return new Destination() {
            private int cost;
            private String label = dest;

            // Instance initialization for each object:
            {
                cost = Math.round(price);
                if (cost > 100)
                    System.out.println("Over budget!");
            }

            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.dest("Tanzania", 101.395F);
    }
}

   В части инициализации экземпляра вы можете видеть, что есть фрагмент кода, который не может быть выполнен как часть инициализации переменной-члена (то есть оператор if). Таким образом, для анонимных классов фактическим эффектом инициализации экземпляра является конструктор. Конечно, он ограничен: вы не можете перегрузить инициализацию экземпляра, поэтому у вас может быть только один конструктор.


Доступ снаружи из нескольких уровней вложенных классов

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


class MNA {
    private void f() {
    }

    class A {
        private void g() {
        }

        public class B {
            void h() {
                g();
                f();
            }
        }
    }
}

public class MultiNestingAccess {
    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnaab = mnaa.new B();
        mnaab.h();
    }
}

   Как видите, в MNA.A.B вызов методов g() и f() не требует никаких условий (даже если они определены как приватные). В этом примере также показан основной синтаксис создания нескольких вложенных объектов внутреннего класса из разных классов. Синтаксис ".new" создает правильную область действия, поэтому вам не нужно уточнять имя класса при вызове конструктора.


###Проблема перегрузки внутреннего класса

Что произойдет, если вы создадите внутренний класс, затем наследуете его внешний класс и переопределяете внутренний класс? То есть можно ли перегружать внутренние классы? Это может показаться полезной идеей, но «переопределение» внутреннего класса, как если бы это был метод внешнего класса, на самом деле мало что дает:


class Egg {
    private Yolk y;


    public Egg() {
        System.out.println("New Egg()");
        y = new Yolk();
    }

    protected class Yolk {
        public Yolk() {
            System.out.println("Egg.Yolk()");
        }
    }
}


public class BigEgg extends Egg {
    public static void main(String[] args) {
        new BigEgg();
    }

    public class Yolk {
        public Yolk() {
            System.out.println("BigEgg.Yolk()");
        }
    }
}

Результат:

New Egg()
Egg.Yolk()

  Конструктор по умолчанию автоматически генерируется компилятором, вот конструктор по умолчанию для вызова базового класса. Вы можете подумать, что, поскольку объект BigEgg был создан, следует использовать «перегруженный» Yolk, но из вывода видно, что это не так.

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

class Egg2 {
    private Yolk y = new Yolk();


    public Egg2() {
        System.out.println("New Egg2()");
    }

    public void insertYolk(Yolk yy) {
        y = yy;
    }

    public void g() {
        y.f();
    }

    protected class Yolk {
        public Yolk() {
            System.out.println("Egg2.Yolk()");
        }


        public void f() {
            System.out.println("Egg2.Yolk.f()");
        }
    }
}


public class BigEgg2 extends Egg2 {
    public BigEgg2() {
        insertYolk(new Yolk());
    }

    public static void main(String[] args) {
        Egg2 e2 = new BigEgg2();
        e2.g();
    }

    public class Yolk extends Egg2.Yolk {
        public Yolk() {
            System.out.println("BigEgg2.Yolk()");
        }


        public void f() {
            System.out.println("BigEgg2.Yolk.f()");
        }
    }
}

Результат:

Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()

в настоящее времяBigEgg2.Yolkпройти черезextends Egg2.YolkЭтот внутренний класс явно наследуется, а методы в нем перегружаются.Egg2изinsertYolk()метод делаетBigEgg2сделать свой собственныйYolkОбъект преобразуется в восходящее преобразование, а затем передается по ссылке y. так когдаg()перечислитьy.f(), выполняется перегруженная новая версия f(). второй звонокEgg2.Yolk()даBigEgg2.Yolkконструктор вызывает конструктор своего базового класса. Вы можете видеть, что вызовg()когда новая версияf()назывался.


Проблемы наследования внутренних классов (думая о java 3th p294)

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


class WithInner {
    class Inner {
        Inner() {
            System.out.println("this is a constructor in WithInner.Inner");
        }

        ;
    }
}


public class InheritInner extends WithInner.Inner {
    // ! InheritInner() {} // Won't compile
    InheritInner(WithInner wi) {
        wi.super();
        System.out.println("this is a constructor in InheritInner");
    }


    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}


  Результат:

this is a constructor in WithInner.Inner
this is a constructor in InheritInner

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

enclosingClassReference.super();

О функциях обратного вызова Java

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


public class CallBack {

    public static void main(String[] args) {
        CallBack callBack = new CallBack();
        callBack.toDoSomethings(100, new CallBackInterface() {
            public void execute() {
                System.out.println("我的请求处理成功了");
            }
        });

    }

    public void toDoSomethings(int a, CallBackInterface callBackInterface) {
        long start = System.currentTimeMillis();
        if (a > 100) {
            callBackInterface.execute();
        } else {
            System.out.println("a < 100 不需要执行回调方法");
        }
        long end = System.currentTimeMillis();
        System.out.println("该接口回调时间 : " + (end - start));
    }
}
public interface CallBackInterface {

    void execute();
}

Основана ли реализация обратных вызовов Java на анонимных внутренних классах?

Обратный вызов в Java можно назвать замечательным исполнением анонимных внутренних классов, а красивый стиль кодирования действительно опьяняет~

Часть исходного кода этой статьи