User Professor Hans Noodles
Professor Hans Noodles
Level 41

Inner classes in a local method

Published in the Java Developer group
Hi! Inner classes in a local method - 1 Let's talk about another kind of nested classes. I'm talking about local classes (method-local inner classes). Before diving in, we must first remember their place in the structure of nested classes. Inner classes in a local method - 2From our diagram, we can see that local classes are a subspecies of inner classes, which we talked about in detail in previous materials. However, local classes have a number of important features and differences from ordinary inner classes. The main thing is in their declaration: A local class is declared only in a block of code. Most often, this declaration is inside some method of the outer class. For example, it might look like this:

public class PhoneNumberValidator {

   public void validatePhoneNumber(String number) {

        class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       // ...number validation code
   }
}
IMPORTANT! If you have Java 7 installed, this code will not compile when pasted into IDEA. We'll talk about the reasons for this at the end of the lesson. In short, how local classes work is highly dependent on the version of the language. If this code does not compile for you, you can either switch the language version in IDEA to Java 8, or add the word final to the method parameter so that it looks like this: validatePhoneNumber(final String number). After that, everything will work. This is a small program that validates phone numbers. Its validatePhoneNumber() method takes a string as input and determines whether it is a phone number. And inside this method, we declared our local PhoneNumber class. You might reasonably ask why. Why exactly would we declare a class inside a method? Why not use an ordinary inner class? True, we could have made the PhoneNumber class an inner class. But the final solution depends on the structure and purpose of your program. Let's recall our example from a lesson on inner classes:

public class Bicycle {

   private String model;
   private int maxWeight;

   public Bicycle(String model, int maxWeight) {
       this.model = model;
       this.maxWeight = maxWeight;
   }
  
   public void start() {
       System.out.println("Let's go!");
   }

   public class HandleBar {

       public void right() {
           System.out.println("Steer right!");
       }

       public void left() {

           System.out.println("Steer left!");
       }
   }
}
In it, we made HandleBar an inner class of the bike. What's the difference? First of all, the way the class is used is different. The HandleBar class in the second example is a more complex entity than the PhoneNumber class in the first example. First, HandleBar has public right and left methods (these are not setters/getters). Second, it is impossible to predict in advance where we may need it and its outer Bicycle class. There could be dozens of different places and methods, even in a single program. But with the PhoneNumber class, everything is much simpler. Our program is very simple. It has only one purpose: to check whether a number is a valid phone number. In most cases, our PhoneNumberValidator won't even be a standalone program, but rather a part of the authorization logic for a larger program. For example, various websites often ask for a phone number when users sign up. If you enter some nonsense instead of numbers, the website will report an error: "This is not a phone number!" The developers of such a website (or rather, its user authorization mechanism) can include something similar to our PhoneNumberValidator in their code. In other words, we have one outer class with one method, which will be used in one place in the program and nowhere else. And if it is used, then nothing will change in it: one method does its job — and that's it. In this case, because all the logic is gathered into one method, it will be much more convenient and proper to encapsulate an additional class there. It has no methods of its own except a getter and setter. In fact, we only need data from the constructor. It is not involved in other methods. Accordingly, there is no reason to take information about it outside the only method where it is used. We also gave an example in which a local class is declared in a method, but this is not the only option. It can be declared simply in a code block:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
Or even in the for loop!

public class PhoneNumberValidator {
  

   public void validatePhoneNumber(String phoneNumber) {

       for (int i = 0; i < 10; i++) {

           class PhoneNumber {

               private String phoneNumber;

               public PhoneNumber(String phoneNumber) {
                   this.phoneNumber = phoneNumber;
               }
           }
          
           // ...some logic
       }

       // ...number validation code
   }
}
But such cases are extremely rare. In most cases, the declaration will happen inside the method. So, we figured out declarations, and we also talked about the "philosophy" :) What additional features and differences do local classes have in comparison with inner classes? An object of a local class cannot be created outside the method or block in which it is declared. Imagine that we need a generatePhoneNumber() method that will generate a random phone number and return a PhoneNumber object. In our current situation, we cannot create such a method in our validator class:

public class PhoneNumberValidator {

   public void validatePhoneNumber(String number) {

        class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       // ...number validation code
   }

   // Error! The compiler does not recognize the PhoneNumber class
   public PhoneNumber generatePhoneNumber() {

   }

}
Another important feature of local classes is the ability to access local variables and method parameters. In case you forgot, a variable declared inside a method is known as a "local" variable. That is, if we create a local String usCountryCode variable inside the validatePhoneNumber() method some reason, we can access it from the local PhoneNumber class. However, there are a lot of subtleties that depend on the version of the language used in the program. At the beginning of the lesson, we noted that the code for one of the examples may not compile in Java 7, remember? Now let's consider the reasons for this :) In Java 7, a local class can access a local variable or method parameter only if they are declared as final in the method:

public void validatePhoneNumber(String number) {

   String usCountryCode = "+1";

   class PhoneNumber {

       private String phoneNumber;

       // Error! The method parameter must be declared as final!
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printUsCountryCode() {

           // Error! The local variable must be declared as final!
           System.out.println(usCountryCode);
       }

   }

   // ...number validation code
}
Here the compiler generates two errors. And everything is in order here:

public void validatePhoneNumber(final String number) {

   final String usCountryCode = "+1";

    class PhoneNumber {

       private String phoneNumber;

       
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printUsCountryCode() {

           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
Now you know why the code from the beginning of the lesson wouldn't compile: in Java 7, a local class has access only to final method parameters and final local variables. In Java 8, the behavior of local classes has changed. In this version of the language, a local class has access not only to final local variables and parameters, but also to those that are effective-final. Effective-final is a variable whose value has not changed since initialization. For example, in Java 8, we can easily display the usCountryCode variable on the console, even if it is not final. The important thing is that its value does not change. In the following example, everything works as it should:

public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Java 7 would produce an error here
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
But if we change the variable's value immediately after initialization, the code will not compile.

public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";
  usCountryCode = "+8";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Error!
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
No wonder a local class is a subspecies of the concept of the inner class! They also have common characteristics. A local class has access to all (even private) fields and methods of the outer class: both static and non-static. For example, let's add a static String phoneNumberRegex field to our validator class:

public class PhoneNumberValidator {

   private static String phoneNumberRegex = "[^0-9]";

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
Validation will be performed using this static variable. The method checks if the passed string contains characters that do not match the regular expression "[^0-9]" (that is, any character that is not a digit from 0 to 9). We can easily access this variable from the local PhoneNumber class. For example, write a getter:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Local classes are similar to inner classes, because they can't define or declare any static members. Local classes in static methods can only reference static members of the enclosing class. For example, if you don't define a variable (field) of the enclosing class as static, then the Java compiler generates an error: "Non-static variable cannot be referenced from a static context." Local classes are not static, because they have access to instance members in the enclosing block. As a result, they cannot contain most types of static declarations. You cannot declare an interface inside a block: interfaces are inherently static. This code does not compile:

public class PhoneNumberValidator {
   public static void validatePhoneNumber(String number) {
       interface I {}
      
       class PhoneNumber implements I{
           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }
       }

       // ...number validation code
   }
}
But if an interface is declared inside an outer class, the PhoneNumber class can implement it:

public class PhoneNumberValidator {
   interface I {}
  
   public static void validatePhoneNumber(String number) {
      
       class PhoneNumber implements I{
           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }
       }

       // ...number validation code
   }
}
Static initializers (initialization blocks) or interfaces cannot be declared in local classes. But local classes can have static members, provided that they are constant variables (static final). And now you know about local classes, folks! As you can see, they have many differences from ordinary inner classes. We even had to delve into the features of specific versions of the language in order to understand how they work :) Inner classes in a local method - 3In the next lesson, we'll talk about anonymous inner classes — the last group of nested classes. Good luck in your studies! :)
Comments (2)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Dương Tiến Đạt Level 34, Ha Noi City, Viet Nam
4 August 2020
I read this article twice, and I still haven't figured out what's the use of this type of class. Can anyone enlighten me?