Pull to refresh

Внутренние и вложенные классы java. Часть 3

Reading time11 min
Views25K
Внутренние и вложенные классы java. Часть 3
Локальные классы

<<< Часть 1
<<< Часть 2

Локальный класс — это вложенный класс, объявленный внутри другого класса и некоторого блока кода этого класса, то есть объявленный между фигурными скобками {}. Этот блок может быть статическим блоком, циклом, телом if и т.д.

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

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

Напишем пример:

/*Учебный пример №12 */
package localclasses;

/**
 *
 * @author Ar20L80
 */
public class OuterStaticInit {
     static 
     { 
         class LocalInit{
         LocalInit(){
         System.out.println("From static iniz"); 
         }
         }
     LocalInit localInit = new LocalInit();
     System.exit(0); 
     } 
     public static void main(String[] args) {
        System.out.println("From main"); 
    }
}




В нашем примере локальный класс «просуществовал» только в области локальной статичной инициализации. Тело «main» не было вызвано. Единственный модификатор, который допускается применять в объявлении локального класса, – это final, предотвращающий, как обычно, возможность расширения класса. Члены локального класса могут быть объявлены как закрытыми, так и открытыми. К ним применяются модификаторы, которые применимы к обычному классу.( java 8)

Еще раз повторим коротко: Локальный класс – это класс, объявленный в блоке Java кода.

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

/**
 * Учебный пример №13
 * @author Ar20L80
 */
public class OuterLocal {
    OuterLocal(){
        // начало блока конструктора

        /*объявление локального класса в конструкторе OuterLocal*/
        class LocalInnerClass {
            LocalInnerClass(){}
        }
    /* создаем экземпляр в том же блоке*/
        LocalInnerClass localObj = new LocalInnerClass();
      // окончание блока конструктора
    }



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

 
 /**
 * Учебный пример №14
 * @author Ar20L80
 */
public class OuterLocal {
    OuterLocal(){
        
        /*объявление локального класса в конструкторе OuterLocal*/
        class LocalInnerClass {
            LocalInnerClass(){}
        }
    /* создаем экземпляр в том же блоке*/
        LocalInnerClass localObj = new LocalInnerClass();
    }
    
    
    public static void main(String[] args) {
       //  LocalInnerClass localObj = new LocalInnerClass(); не можем создать объект локального класса 
	   // вне области видимости содержащего его блока
    }
}
 
 



Свойства локального класса:
Подобно вложенным нестатическим классам, локальные классы связаны с окружающим экземпляром и имеют доступ ко всем членам, включая private члены окружающего класса. Локальный класс нельзя объявить с каким-либо модификатором доступа, кроме как static final.

 /*Учебный пример пример №15 */
public class OuterStaticLocal {
    
	OuterStaticLocal(){
	   // static class Local{}  ошибка не может быть статичным
	}
} 



Потому что, модификаторы доступа для членов класса мы можем применять только к членам класса. Эти модификаторы не доступны для объявления локальных переменных или классов, находящихся в блоке и не являющиеся членами класса.

  /**
 * Учебный пример №16 
 * @author Ar20L80
 */
public class OuterLocal2 {
    OuterLocal2(){
      final  class LocalInnerClass {
            LocalInnerClass(){}
        }
    }
} 


Как и нестатические вложенные классы, и по тем же причинам, локальные классы не могут иметь static поля, исключение составляют константы, объявленные как static final.

 /**
 *  Учебный пример №17
 
 * @author Ar20L80
 * тут я вернул переменную локального класса через iTemp внешнего класса
 */
public class OuterClass {
     
    private int iTemp;
    OuterClass(){
    // объявим внутри конструктора класс
    
    /* здесь мы не можем использовать private, public
    применительно к локальному классу*/
     final  class LocalInnerClass01{ 
      /* сам локальный класс может содержать 
         как private, так и public */
      private static final int INT_FIN = 10;
      LocalInnerClass01(){
       iTemp = Return_INT_FIN();
      }
      int Return_INT_FIN(){
      return INT_FIN;
      }
      
     }
    
     class LocalInnerClass02{
      // public static int i=11; ошибка не может быть не константой внутри 
     // локального вложенного класса
     }
     
     /* создаем локальные объекты  локальных классов в том же конструкторе*/
     LocalInnerClass01 localInnerClass1 = new LocalInnerClass01();
     LocalInnerClass02 localInnerClass2 = new LocalInnerClass02();
     
     
    }
     
    public static void main(String[] args) {
     OuterClass outerClass = new OuterClass();
     
     System.out.println(outerClass.iTemp ); // = 10
    //OuterClass.LocalInnerClass1 innerObject = outerClass.new LocalInnerClass1(); ошибка - это не 
   // внутренний класс, а локальный. И мы не имеем к нему доступа.
    }

    
    
}



Применение:
Основное применение локальные классы находят в тех случаях, когда необходимо написать класс, который будет использоваться внутри одного метода. Создание локального класса – способ не загромождать пространство имен.

Если класс определяется в теле метода, то его называют локальным внутренним классом. Пример доступа к переменным внешнего класса из локального внутреннего класса:

 /* Учебный пример №18
  пример показывает доступ из метода локального класса к закрытым переменным внешнего класса
 */
package localclasses;

/**
 *
 * @author Ar20L80
 */
public class LocalClassAndVars {
     private int prIntVar = 1;
     private static int prStIntVar = 2;
     private final int prFinVar  = 3;
     
     
     LocalClassAndVars(){
      
     }
     void anyMethodOfOuter(){
     
         class LocalClass{
           void anyMethodLocal(){
              prIntVar = 10;
              prStIntVar = 20;
              // prFinVar  = 30; финальная нельзя менять, 
              // но мы можем прочитать
              int localVar = prFinVar;
           }
         }
     
       LocalClass localOb = new LocalClass();
       localOb.anyMethodLocal();
     }
     
     public static void main(String[] args) {
       
         LocalClassAndVars   outerObj = new LocalClassAndVars();
         System.out.println("prIntVar = "+ outerObj.prIntVar + " prStIntVar = "+ LocalClassAndVars.prStIntVar
         + " prFinVar = "+ outerObj.prFinVar);
         
         outerObj.anyMethodOfOuter();
         
          System.out.println("prIntVar = "+ outerObj.prIntVar + " prStIntVar = "+ LocalClassAndVars.prStIntVar
         +" prFinVar = "+ outerObj.prFinVar);
         
    }
}
/*
вывод:
prIntVar = 1 prStIntVar = 2 prFinVar = 3
prIntVar = 10 prStIntVar = 20 prFinVar = 3
*/



Цитата из книги Effective Java TM. Programming Language Guide. Joshua Bloch. Издательство «Лори»:
«Локальный класс можно декларировать везде, где разрешается декларировать локальную переменную, и он подчиняется тем же самым правилам видимости. Локальный класс имеет несколько признаков, объединяющих его с каждой из трех других категорий вложенных классов. Как и классы-члены, локальные классы имеют имена и могут использоваться многократно. Как и анонимные классы, они имеют окружающий их экземпляр тогда и только тогда, когда применяются в нестатическом контексте. Как и анонимные классы, они должны быть достаточно короткими, чтобы не мешать удобству чтения метода или инициализатора, в котором они содержатся»


Выводы:
1. Локальные классы определяются в блоке кода и область их видимости — этот блок кода. Объекты локального класса могут создаваться в блоке кода, котором они описаны.
2. Локальные классы не имеют модификаторов доступа private или public, потому что они принадлежат не классу, а тому блоку кода, в котором они описаны.
3. Локальные классы, объявленные в статичном блоке, могут обращаться только к статичным полям внешнего класса.
4. Локальные классы могут иметь модификаторы доступа final
5. Локальные классы не могут быть статичными, за исключением static final.
6. В java8 мы можем обращаться из локального класса не только к финальным переменным внешнего класса, и к не финальным методам внешнего класса, если они не были изменены до момента инициализации класса.
7. Локальные классы имеют доступ к закрытым переменным внешнего класса, в контексте экземпляра.
То есть локальные классы ведут себя в этом отношении как внутренние классы.

Анонимные (безымянные) классы

Анонимным классом называется класс, в котором программист не задает явно имя экземпляру.
Простой пример:
 new AnyClass(); 

В анонимном классе явно не задается конструктор класса, а используется конструктор базового класса.
Мы можем создать анонимный класс, передав некоторое значение в конструктор базового класса.
new AnyClass(80);


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

new AnyClass(){ //   Тело нового анонимного класса. Новый функционал вашего класса
};
 

Здесь точка с запятой, в отличии от языка с++ означает не только окончание класса, а окончание блока кода.

Давайте немного более расширим пример:
 
 /* Учебный пример №19 */
public class AnyClass1 {
    
    public static void main(String[] args) {
        new AnyClass1(){
            
            void anyNewMethod(){ // добавили новый метод нашему анонимному классу
                System.out.println("Hello from anonimous");
            }
        };
    }
} 


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

У нас есть возможность выполнить наш метод из блока инициализации.
Дописываем блок инициализации и выполняем наш метод:

 
 /*Учебный пример пример №20 */
public class AnyClass1 {
    
    public static void main(String[] args) {
        new AnyClass1(){
            {
                this.anyNewMethod();
            }
            void anyNewMethod(){
                System.out.println("Hello from anonimous");
            }
        };
    }
} 




Напишу еще один учебный пример с пояснениями:
/*

 */
package anonymous;

/**
 *
 * @author vvm
 */
public class Outer {

    Outer() {
    }

    void method() {
        System.out.println("method экземпляра класса Outer");
    }

    public static void main(String[] args) {
        Outer outter = new Outer() {// в этом месте создается анонимный класс
            {// статический блок инициализации анонимного класса
                super.method(); // вызов метода класса Outer

                method(); // здесь мы можем вызвать method() анонимного класса
                // здесь мы можем вызвать метод nmethod() анонимного класса
                nmethod();
            }

            @Override
            void method() { // переопределение метода в анонимном классе
                System.out.println("method экземпляра анонимного класса ");
            }

            void nmethod() {
                //новый метод который мы добавили
                System.out.println("nmethod экземпляра анонимного класса ");
            }
        }; // здесь заканчивается анонимный класс

        // далее мы можем обратиться только к методу outter.method();
        outter.method();
        // из этого места мы не можем обратиться к методу nmethod()
        //  потому как в Outer этого метода нет и у нас не ссылки на анонимный класс
        //  можете раскоментировать строку ниже чтобы проверить
        // outter.nmethod();
    }
}



Анонимными классами не следует злоупотреблять, иначе у вас получится код, в котором будет сложно разобраться и поддерживать. Анонимные (внутренние, вложенные, локальные) применяются для сокрытия реализации.

Какое применение находят анонимные классы?
В основном для создания «слушателей» какого-то события.
Пример:

 /* Учебный пример №21 */
item.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            // TODO  дописать функциональность

        }

    });



Здесь мы добавили к item новый «слушатель» события и далее переопределяем его метод под наши требования. Фактически мы используем анонимный класс new ActionListener() и переопределяем методы родительского класса ActionListener. Если нам понадобится, то мы можем получить имя анонимного класса. Развитие идеи анонимного класса с одним методом получило в виде лямбд в java8. Сама запись new ActionListener(); означает что мы создаем анонимный класс без имени унаследованный от ActionListener. Добавление функциональности нашему классу new ActionListener(){/*здесь мы пишем новый добавленный нами функционал вместо комментария*/};

В этом примере мы получим имя анонимного класса:

 
package anonymous;
/**
 * Учебный пример  №22
 * @author Ar20L80
 */
public class Outer1 {
    
    public static void main(String[] args) {
         Runnable anonym = () -> {
            };
         
            String str = anonym.getClass().toString();
            System.out.println(str); 
    }
	  /*  раскомментировать если не поддерживается лямбда
            public static void main(String[] args) 
            {
              Runnable anonym = new Runnable() {
               public void run() {
                                }
                };
              */
}
 


Вызов метода анонимного класса:
 
 package anonymous;

/**
 *Учебный пример пример №23
 * @author Ar20L80
 */
public class Outer2 {
    Outer2(){}
    
    public static void main(String[] args) {
       Runnable outerPlusMethod;
        
        outerPlusMethod = () -> {
            System.out.println("Hello from anonymous");
       };
        
     outerPlusMethod.run(); // вызов добавленного нами метода локального анонимного класса
        
    }
    
}
 




Анонимный вложенный класс не является членом содержащего его класса. Анонимный класс определяется и порождает экземпляр в момент использования. Он не определяется вместе с остальными членами включающего его класса. Анонимный класс можно поместить в любом месте программы, где разрешается применять локальные переменные.
В зависимости от контекста анонимный класс, член другого класса, ведет себя по-разному.
В нестатическом контексте появляется окружающий его экземпляр, в статическом контексте ведет себя как статический.
Цитата из книги «Effective Java TM. Programming Language Guide. Joshua Bloch». Издательство «Лори»:
Существуют четыре категории вложенных классов, каждая из которых занимает свое место. Если вложенный класс должен быть виден за пределами одного метода или он слишком длинный для того, чтобы его можно было удобно разместить в границах метода, используйте класс-член. Если каждому экземпляру класса-члена необходима ссылка на включающий его экземпляр, делайте его нестатическим, в остальных случаях он должен быть статическим. Предположим, что класс находится внутри метода. Если вам нужно создавать экземпляры этого класса только в одном месте программы и уже есть тип, который характеризует это класс, сделайте его анонимным классом. В противном случае, это должен быть локальный класс.


Анонимный класс может быть не только наследуемым от другого класса(как правило, абстрактного), но и имплементировать интерфейс.
Широкое применение анонимные классы находят в создании многопоточности на java,
а также для создания «слушателей» события и «адаптеров».

Реальный пример использования анонимных классов на основе адаптеров:

/*  учебный пример №24
 *  Приложение на основе AWT
 *  По мотивам книги "Java. Полное руководство. 8-е издание. Герберт Шилдт"
 */
package awt_study;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
 *
 * @author Ar20L80
 */
public class AppWindow extends Frame{
    String keymsg = "This is a test";
    String mousemsg = "";
    int mouseX = 30, mouseY = 30;
    public AppWindow(){
      
       addKeyListener(new MyKeyAdapter(this)); // анонимный класс new MyKeyAdapter(this)
       addMouseListener(new MyMouseAdapter(this));
       
       // vvm добавляем слушатель перемещения мышки
       addMouseMotionListener(new MyMouseAdapter(this));
        
       addWindowListener(new MyWindowAdapter());
       
       
    }
    
    @Override
    public void paint(Graphics g){
        g.drawString(keymsg, 10, 40);
        g.drawString(mousemsg, mouseX, mouseY);
    }
    // создание окна
    public static void main(String[] args) {
        AppWindow appwin = new AppWindow();
        appwin.setSize( new Dimension(320,240));
        appwin.setTitle("Приложение на основе AWT");
         
        appwin.setVisible(true);
        }
    }  

    // класс адаптера нажатия клавиш
    class MyKeyAdapter extends KeyAdapter{
        AppWindow appWindow;
        public MyKeyAdapter(AppWindow appWindow){
            this.appWindow = appWindow;
        }
        @Override
        public void keyTyped(KeyEvent ke){
            appWindow.keymsg += ke.getKeyChar();
            appWindow.repaint();
        };
    }
      // класс адаптера для мышки   
     class MyMouseAdapter extends MouseAdapter{
            AppWindow appWindow;
            public MyMouseAdapter(AppWindow appWindow){
                this.appWindow = appWindow;
            }
            @Override
            public void mousePressed(MouseEvent me){
                 
            appWindow.mouseX = me.getX();
            appWindow.mouseY = me.getY();
            appWindow.mousemsg = "Mouse Down at " + appWindow.mouseX +
                    ", " + appWindow.mouseY;
            appWindow.repaint();
            }
         
            @Override
            //vvm добавляем перемещение мышки
            public void mouseMoved(MouseEvent me){
            appWindow.mouseX = me.getX();
            appWindow.mouseY = me.getY();
            appWindow.mousemsg = "Mouse moved at "+ appWindow.mouseX +
                    ", " + appWindow.mouseY;
             
            appWindow.repaint();
            }
     }
 
     // класс адаптера окна для обработки выхода
     class MyWindowAdapter extends WindowAdapter{
         @Override
         public void windowClosing(WindowEvent we){
             System.exit(0);
             }
     }



На этом позвольте закончить. Надеюсь я написал довольно ясно для понимания и кратко.
А также написал и протестировал достаточно ясный код.

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

Для вашего комфорта, я создал репозиторий на github.
Теперь вы можете скачать актуальные версии кода, для ваших экспериментов, тестов и изучения:
github.com/vvm64/InnerAndNestedCl

Литература

Майкл Морган. «Java 2. Руководство разработчика» ISBN 5-8459-0046-8
Брюс Эккель. «Философия Java.» ISBN 5-272-00250-4
Герберт Шилдт «Java. Полное руководство. 8-е издание.» ISBN: 978-5-8459-1759-1

Ссылки.

Документация Oracle: >>>
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+2
Comments1

Articles

Change theme settings