СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

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

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


Локальные классы

Локальный класс — это вложенный класс, объявленный внутри другого класса и некоторого блока кода этого класса, то есть объявленный между фигурными скобками {}.
Этот блок может быть статическим блоком, циклом, телом 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");
            }
        };
    }
} 



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

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

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

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

        }

    });



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

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

 
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);
             }
     }



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

Литература

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

Ссылки.

Документация Oracle: >>>

комментарии (1)

0
Ar20L80 ,  
Спасибо, что дочитали до третьей части.