内部类详解(Oracle官方)
嵌套类(Nested Classes)
Java允许将一个类定义在另一个类内部。这样的类称为嵌套类,我们统称内部类,Oracle官方给出了不同的名字,如下
1 class OuterClass { 2 ... 3 class NestedClass { 4 ... 5 } 6 }
术语:嵌套类分为两类:静态和非静态。
静 态:静态嵌套类
非静态:内部类
1 class OuterClass { 2 ... 3 static class StaticNestedClass { 4 ... 5 } 6 class InnerClass { 7 ... 8 } 9 }
嵌套类作为外部类的一个成员存在。非静态嵌套类(内部类)拥有外部类的成员的权限,即使他们被定义为私有。相反,静态嵌套类没有外部封闭类其他成员的权限。作为外部类的一个成员,嵌套类可以被private
, public
, protected
, or package private修饰。外部类只可以被 public
or package private修饰。
为什么使用嵌套类(Why Use Nested Classes?)
- 逻辑上对只用于一个地方的类分组
如果一个类只对另外一个类有用,逻辑上他被嵌入这个类。内嵌“帮助类”可以使包更合理化。
- 提升封装性
考虑两个顶级类,A和B,B需要访问A的可能声明为私有的成员。通过把类B隐藏在类A中,B可以访问A的私有成员。此外B也可以从外界隐藏。
- 代码更容易阅读和维护
嵌套小类在顶级类中,可以使代码更接近使用的地方。
静态嵌套类(Static Nested Classes)
就像类方法和变量一样,静态嵌套类和外部类关联一起。像静态方法一样,静态内部类无法直接访问定义在外部类中实例变量和实例方法,只能通过对象引用。
静态嵌套类通过如下方式使用
OuterClass.StaticNestedClass
创建内部嵌套类的实例:
1 OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
内部类(Inner Classes)
和实例方法、实例变量一样,一个内部类和一个外部类实例想关联,可以直接访问外部类对象所有方法和变量。因为内部类和一个实例相关联,所以它不能定义任何静态成员。内部类的实例对象,存在于外部类实例之中。
内部定义静态变量会报错:The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression。只能定义静态常量
1 class OuterClass { 2 ... 3 class InnerClass { 4 ... 5 } 6 }
实例化内部类的格式
1 OuterClass outerObject = new OutterClass(); 2 OuterClass.InnerClass innerObject = outerObject.new InnerClass();
内部类分为两种:局部内部类和匿名内部类
遮蔽(Shadowing)
在特定范围内(例如一个内部类或者一个方法定义),类型的声明(例如成员变量或者参数名称)和外部类拥有同样的名字,内部类会遮盖外部类。不能直接通过名字引用遮盖类型。
1 public class ShadowTest { 2 3 public int x = 0; 4 5 class FirstLevel { 6 7 public int x = 1; 8 9 void methodInFirstLevel(int x) { 10 System.out.println("x = " + x); 11 System.out.println("this.x = " + this.x); 12 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 13 } 14 } 15 16 public static void main(String... args) { 17 ShadowTest st = new ShadowTest(); 18 ShadowTest.FirstLevel fl = st.new FirstLevel(); 19 fl.methodInFirstLevel(23); 20 } 21 }
输出如下:
x = 23 this.x = 1 ShadowTest.this.x = 0
在内部类中访问外部类的成员变量,需要通过以下方式(OuterClass.this.variable)
1 ShadowTest.this.x
序列化(Serialization)
强烈不建议序列化内部类,包括局部内部类和匿名内部类。
1. 内部类的例子(Inner Class Example)
◆ 局部类和匿名类(Local and Anonymous Classes)
局部类声明在方法体中,匿名类是声明在方法体中的非命名类。
◆ 修饰符(Modifiers)
private
, public
, protected
2. 局部内部类(Local Classes)
局部内部类定义在由一组0或多个声明的块中,作用范围也是块。典型的如,定义在方法中的内部类。
块(Blocks)
1 class BlockDemo { 2 public static void main(String[] args) { 3 boolean condition = true; 4 if (condition) { // begin block 1 5 System.out.println("Condition is true."); 6 } // end block one 7 else { // begin block 2 8 System.out.println("Condition is false."); 9 } // end block 2 10 } 11 }
局部类只能被abstract和final修饰
◆ 声明局部内部类
1 public class LocalClassExample { 2 static String regularExpression = "[^0-9]"; 3 4 public static void validatePhoneNumber(String phoneNumber1, String phoneNumber2) { 5 6 final int numberLength = 10; 7 8 // Valid in JDK 8 and later: 9 // int numberLength = 10; 10 11 class PhoneNumber { 12 13 String formattedPhoneNumber = null; 14 15 PhoneNumber(String phoneNumber) { 16 // numberLength = 7; 17 String currentNumber = phoneNumber.replaceAll(regularExpression, ""); 18 if (currentNumber.length() == numberLength) 19 formattedPhoneNumber = currentNumber; 20 else 21 formattedPhoneNumber = null; 22 } 23 24 public String getNumber() { 25 return formattedPhoneNumber; 26 } 27 28 // Valid in JDK 8 and later: 29 public void printOriginalNumbers() { 30 System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2); 31 } 32 } 33 34 PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1); 35 PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2); 36 37 // Valid in JDK 8 and later: 38 myNumber1.printOriginalNumbers(); 39 40 if (myNumber1.getNumber() == null) 41 System.out.println("First number is invalid"); 42 else 43 System.out.println("First number is " + myNumber1.getNumber()); 44 if (myNumber2.getNumber() == null) 45 System.out.println("Second number is invalid"); 46 else 47 System.out.println("Second number is " + myNumber2.getNumber()); 48 49 } 50 51 public static void main(String... args) { 52 validatePhoneNumber("123-456-7890", "456-7890"); 53 } 54 }
打印如下
First number is 1234567890 Second number is invalid
◆ 访问外部类的成员
局部类可以访问外部类的变量,也可以访问方法的局部变量,且只能访问声明为final的局部变量。实际上局部类访问的是变量副本。
Java8之后,声明在方法中的局部类,可访问方法的参数。
局部类中变量对外部相同名字的变量产生遮盖作用。
局部类和内部类相同,都不能定义任何静态成员(变量和方法)。静态方法中的局部类只能访问外部类的静态成员。静态资源都是属于类的,静态方法可直接调用,所以局部类只能方位静态成员。
3. 匿名内部类(Anonymous Classes)
匿名内部类可以使代码更简洁,允许同时声明和实例化一个类。除了没有名字之外,其他都和局部类一样。如果你只使用一次局部类,请用匿名内部类。
◆ 声明匿名内部类(Declaring Anonymous Classes)
1 public class HelloWorldAnonymousClasses { 2 3 interface HelloWorld { 4 public void greet(); 5 public void greetSomeone(String someone); 6 } 7 8 public void sayHello() { 9 10 class EnglishGreeting implements HelloWorld { 11 String name = "world"; 12 public void greet() { 13 greetSomeone("world"); 14 } 15 public void greetSomeone(String someone) { 16 name = someone; 17 System.out.println("Hello " + name); 18 } 19 } 20 21 HelloWorld englishGreeting = new EnglishGreeting(); 22 23 HelloWorld frenchGreeting = new HelloWorld() { 24 String name = "tout le monde"; 25 public void greet() { 26 greetSomeone("tout le monde"); 27 } 28 public void greetSomeone(String someone) { 29 name = someone; 30 System.out.println("Salut " + name); 31 } 32 }; 33 34 HelloWorld spanishGreeting = new HelloWorld() { 35 String name = "mundo"; 36 public void greet() { 37 greetSomeone("mundo"); 38 } 39 public void greetSomeone(String someone) { 40 name = someone; 41 System.out.println("Hola, " + name); 42 } 43 }; 44 englishGreeting.greet(); 45 frenchGreeting.greetSomeone("Fred"); 46 spanishGreeting.greet(); 47 } 48 49 public static void main(String... args) { 50 HelloWorldAnonymousClasses myApp = 51 new HelloWorldAnonymousClasses(); 52 myApp.sayHello(); 53 } 54 }
◆ 匿名内部类语法(Syntax of Anonymous Classes)
1 HelloWorld frenchGreeting = new HelloWorld() { 2 String name = "tout le monde"; 3 public void greet() { 4 greetSomeone("tout le monde"); 5 } 6 public void greetSomeone(String someone) { 7 name = someone; 8 System.out.println("Salut " + name); 9 } 10 };
-
new
操作符 -
要实现的接口或者扩展的类的名称,例子中为接口
HelloWorld。
-
圆括号内包含构造参数。注意:接口实例化无构造参数,如例所示。
-
类声明的主体。确切地说,允许方法声明,不允许新的语句。
因为匿名类就是一个表达式,它必须是语句的一部分
◆ 访问局部变量,声明和访问匿名类的成员
和局部类一样,匿名类可以捕获变量;他们拥有相同的访问封闭范围局部变量的权限:
-
匿名类可以访问外部类的成员。
-
匿名类不能访问封闭范围内没有声明为final(或者实际不为final)的局部变量。
-
和嵌套类一样,匿名类的类型声明(比如变量),会对封闭范围内拥有相同名字的其他声明产生遮蔽作用。
匿名类和局部类对各自的成员拥有相同的限制:
-
不能声明静态初始化器或者成员接口。
-
匿名类可以声明常量。
以下是可以在匿名类中声明的:
-
字段
-
额外的方法(即使它们没有实现任何超类型的方法)
-
实例初始化
-
局部类
但是,您不能在匿名类中声明构造函数。
◆ 匿名内部类的例子
1 import javafx.event.ActionEvent; 2 import javafx.event.EventHandler; 3 import javafx.scene.Scene; 4 import javafx.scene.control.Button; 5 import javafx.scene.layout.StackPane; 6 import javafx.stage.Stage; 7 8 public class HelloWorld extends Application { 9 public static void main(String[] args) { 10 launch(args); 11 } 12 13 @Override 14 public void start(Stage primaryStage) { 15 primaryStage.setTitle("Hello World!"); 16 Button btn = new Button(); 17 btn.setText("Say ‘Hello World‘"); 18 btn.setOnAction(new EventHandler<ActionEvent>() { 19 20 @Override 21 public void handle(ActionEvent event) { 22 System.out.println("Hello World!"); 23 } 24 }); 25 26 StackPane root = new StackPane(); 27 root.getChildren().add(btn); 28 primaryStage.setScene(new Scene(root, 300, 250)); 29 primaryStage.show(); 30 } 31 }
匿名类是实现包含两个或多个方法的接口的理想方式。
1 mport javafx.application.Application; 2 import javafx.event.ActionEvent; 3 import javafx.event.EventHandler; 4 import javafx.geometry.Insets; 5 import javafx.scene.Group; 6 import javafx.scene.Scene; 7 import javafx.scene.control.*; 8 import javafx.scene.layout.GridPane; 9 import javafx.scene.layout.HBox; 10 import javafx.stage.Stage; 11 12 public class CustomTextFieldSample extends Application { 13 14 final static Label label = new Label(); 15 16 @Override 17 public void start(Stage stage) { 18 Group root = new Group(); 19 Scene scene = new Scene(root, 300, 150); 20 stage.setScene(scene); 21 stage.setTitle("Text Field Sample"); 22 23 GridPane grid = new GridPane(); 24 grid.setPadding(new Insets(10, 10, 10, 10)); 25 grid.setVgap(5); 26 grid.setHgap(5); 27 28 scene.setRoot(grid); 29 final Label dollar = new Label("$"); 30 GridPane.setConstraints(dollar, 0, 0); 31 grid.getChildren().add(dollar); 32 33 final TextField sum = new TextField() { 34 @Override 35 public void replaceText(int start, int end, String text) { 36 if (!text.matches("[a-z, A-Z]")) { 37 super.replaceText(start, end, text); 38 } 39 label.setText("Enter a numeric value"); 40 } 41 42 @Override 43 public void replaceSelection(String text) { 44 if (!text.matches("[a-z, A-Z]")) { 45 super.replaceSelection(text); 46 } 47 } 48 }; 49 50 sum.setPromptText("Enter the total"); 51 sum.setPrefColumnCount(10); 52 GridPane.setConstraints(sum, 1, 0); 53 grid.getChildren().add(sum); 54 55 Button submit = new Button("Submit"); 56 GridPane.setConstraints(submit, 2, 0); 57 grid.getChildren().add(submit); 58 59 submit.setOnAction(new EventHandler<ActionEvent>() { 60 @Override 61 public void handle(ActionEvent e) { 62 label.setText(null); 63 } 64 }); 65 66 GridPane.setConstraints(label, 0, 1); 67 GridPane.setColumnSpan(label, 3); 68 grid.getChildren().add(label); 69 70 scene.setRoot(grid); 71 stage.show(); 72 } 73 74 public static void main(String[] args) { 75 launch(args); 76 } 77 }
4. 拉姆达表达式(Lambda Expressions)
方法引用(Method Referens)
5. 如何选择嵌套类、局部类、匿名内部类、Lambda表达式
-
局部类:如果需要创建多个类的实例,访问该类的构造函数,或者引入新的命名类型(例如,稍后需要调用其他方法)。
-
匿名类:如果需要声明字段或其他方法。
-
Lambda表达式:
-
如果需要封装单个行为单元,并传递给其他代码。例如,当流程结束或错误时,对集合的每个元素执行特定的动作。
-
如果需要一个函数接口的简单实例,而前面的任何条件都不适用(例如,不需要构造函数、命名类型、字段或其他方法)。
-
-
嵌套类:如果需求与本地类的需求类似,希望类型更广泛地可用,并且不需要访问本地变量或方法参数。
-
如果需要访问封闭实例的非公共字段和方法,请使用非静态嵌套类(或内部类)。如果不需要此访问,请使用静态嵌套类。
-
原文:https://www.cnblogs.com/blouson/p/NestedClasses.html