Java Nested Classes(内部类~第一篇英文技术文档翻译)
鄙人最近尝试着翻译了自己的第一篇英文技术文档。
Java Nested Classes Reference From Oracle Documentation
嵌套类-Nested Classes
在Java中我们可以在一个类的内部,再定义另外一个类,其中里面的那个类被称为嵌套类,示例如下。
class OuterClass {
...
class NestedClass {
...
}
}
术语:嵌套类有两种类型:静态和非静态,当嵌套类被static修饰时,被称为静态嵌套类(static nested classes),没有被static修饰时的嵌套类被称作内部类(inner classes)
class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
嵌套类是外部基类(即外部类)的成员,非静态嵌套类(内部类)可以获取到外围基类的其他成员,其中也包括被声明为private的成员。静态嵌套类则不可以获取基类的其他成员。当做为作为外部类的成员,嵌套类可以被定义为private,public,protected或者package private。如果我们需要在其他外部类中使用内部类,则一定要将嵌套类声明为public或者 package private。
为什么使用嵌套类-Why Use Nested Classes?
使用嵌套类有以下几个明显的优势:
- 当仅会在一处用到某个类时,通过嵌套类可以在逻辑上与基类(外部类)保持一种紧密的联系关系:当一个类只会在另一个类中使用,那么就可以把这个类嵌入到另外一个类中,可以使得两者之间有着紧密的联系,嵌套类又称之为'辅助类'。
- 通过合理的使用可以使得整个包下的类定义更加的简洁:更强的封装性:A和B两个类,B作为A类的嵌套类,如果不将其中B类B类设置为private的话,那么B类就拥有访问A类成员的权限。
- 更好的可读性和更高的可维护性:在编码时内部的嵌套类总是需要和最外层类保持一种形式上的关联关系。
静态嵌套类-Static Nested Classes
静态嵌套类不能直接引用外部基类的实例变量和实例方法,对于这样的实例变量仅可以通过对象引用来获取。
通过使用外围基类名称来获取静态嵌套类
OuterClass.StaticNestedClass
如果我们想创建一个静态嵌套类的对象,则可以使用如下的方式
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
内部类-Inner Classes
内部类可以通过外部类实例,直接获取基类对象的变量和方法,同理因为内部类是通过实例引用来和外部类建立关系的,所以在内部类中不能定义任何的静态成员。只有当外部类实例对象被创建出来之后,才可以实例化内部类。
class OuterClass {
...
class InnerClass {
...
}
}
内部类实例只能存在于外部类实例中,并且可以直接访问其外部类实例的方法和字段。
在实例化内部类前,要先实例化外部类实例。可以通过如下方式,通过外部对象实例来创建内部类对象。
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
内部类有两种类型:局部类(local classes) 和 匿名类(anonymous classes).
局部类-Local Classes
局部类是一种被定义在代码块中的类,局部类通常时定义在方法体中。
如何声明局部类:
可以在任何一个方法之中定义一个局部类,如for循环中,或者在if子句中。
下面的LocalClassExample,是用来验证两个手机号,在这个类的validatePhoneNumber方法中,定义了一个名为PhoneNumber的局部类。
public class LocalClassExample { static String regularExpression = "[^0-9]"; public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) { final int numberLength = 10; // Valid in JDK 8 and later: // int numberLength = 10; class PhoneNumber { String formattedPhoneNumber = null; PhoneNumber(String phoneNumber){
// numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
} public String getNumber() {
return formattedPhoneNumber;
} // Valid in JDK 8 and later: // public void printOriginalNumbers() {
// System.out.println("Original numbers are " + phoneNumber1 +
// " and " + phoneNumber2);
// }
} PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2); // Valid in JDK 8 and later: // myNumber1.printOriginalNumbers(); if (myNumber1.getNumber() == null)
System.out.println("First number is invalid");
else
System.out.println("First number is " + myNumber1.getNumber());
if (myNumber2.getNumber() == null)
System.out.println("Second number is invalid");
else
System.out.println("Second number is " + myNumber2.getNumber()); } public static void main(String... args) {
validatePhoneNumber("123-456-7890", "456-7890");
}
}
通过删除原有手机号中除0-9之外的字符后,检查新的字符串中是否有十个数字,输出结果如下:
First number is 1234567890
Second number is invalid
获取外部类成员
局部类可以获取外部类的成员信息,在上一个例子中,PhoneNumber局部类的构造方法里通过LocalClassExample.regularExpression,就拿到了外部类中的regularExpression成员。
另外,局部类中也能使用局部变量,但是在局部类中只能使用被final修饰后的变量,当一个局部类要使用定义在外部代码块中的局部变量或者参数时,他会俘获(这个变量就是他的了)这个变量或者参数。
比如,PhoneNumber的构造方法中,能够/会,俘获numberLength,因为这个变量在外围块中被声明为final,这样的话numberLength 就成为了一个被俘获的变量了,有了主人。
但是在java 1.8版本中局部类能够使用定义在外部块中的final或者effectively final的变量或者参数,如果一个变量或者参数的值在初始化后便不会被改变,则被称为effectively final。
比如在下面的代码中,变量numberLength没有被显示的声明为final,在初始化后有在方法中又将numberLength的值修改为7:
PhoneNumber(String phoneNumber) {
numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
因为这个赋值语句numberLength = 7,变量numberLength 便不再是 effectively final了,在这种情形下,内部类尝试在if (currentNumber.length() == numberLength)
这行代码中获取numberLength时,编译器时会提示"local variables referenced from an inner class must be final or effectively final"
。
在java8中,如果在方法中声明了局部类,那么可以在局部类中拿到方法的入参,就像下面的方法:
public void printOriginalNumbers() {
System.out.println("Original numbers are " + phoneNumber1 +
" and " + phoneNumber2);
}
局部类中的printOriginalNumbers方法获取到了方法validatePhoneNumber中的phoneNumber1 和phoneNumber2两个参数变量。
局部类与内部类的相似点
局部类像内部类一样,二者都不能定义和声明静态成员,在静态方法validatePhoneNumber中定义的PhoneNumber局部类,只能引用外部类中的静态成员。
如果将变量regularExpression定义为非静态,那么在java编译器编译的时候会提示"non-static variable regularExpression cannot be referenced from a static context."错误信息。
因为要获取外围代码块中的实例成员,所以局部类不能时静态的,所以在局部类中不能包含有静态声明。
不能在代码块中,尝试定义或者声明接口,因为接口本质上就是静态的,比如下面的代码是不能编译成功的,因为在greetInEnglish方法内部包含有HelloThere接口:
public void greetInEnglish() {
interface HelloThere {
public void greet();
}
class EnglishHelloThere implements HelloThere {
public void greet() {
System.out.println("Hello " + name);
}
}
HelloThere myGreeting = new EnglishHelloThere();
myGreeting.greet();
}
当然在局部类中也不能声明静态方法,下面的代码同样,在编译时会报"modifier 'static' is only allowed in constant variable declaration"
,因为EnglishGoodbye.sayGoodbye这个方法被声明为静态方法了。
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static void sayGoodbye() {
System.out.println("Bye bye");
}
}
EnglishGoodbye.sayGoodbye();
}
局部类中只有变量时常量的时候,才可能会出现有静态成员变量的情况,下面的代码中有静态成员但也可以编译通过,因为静态变量EnglishGoodbye.farewell是常量。
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static final String farewell = "Bye bye";
public void sayGoodbye() {
System.out.println(farewell);
}
}
EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
myEnglishGoodbye.sayGoodbye();
}
匿名类-Anonymous Classes
匿名类可以使你的代码看上去更加的精简,可以在声明一个匿名类的同时对它进行初始化,除了没有类名以外,它跟局部类很像,对于只会使用一次的局部类的场景我们可以用匿名类来代替。
局部类就是一个类,而匿名类则更像是一个表达式,那么我们便可以在另外的表达式中使用匿名类。
下面的例子中 HelloWorldAnonymousClasses通过使用匿名类创建局部变量frenchGreeting 和spanishGreeting,通过使用局部类来创建和初始化englishGreeting。
public class HelloWorldAnonymousClasses { interface HelloWorld {
public void greet();
public void greetSomeone(String someone);
} public void sayHello() { class EnglishGreeting implements HelloWorld {
String name = "world";
public void greet() {
greetSomeone("world");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hello " + name);
}
} HelloWorld englishGreeting = new EnglishGreeting(); HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
}; HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
} public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
如何使用和定义一个匿名类
我们可以通过frenchGreeting的创建过程来一探匿名类的组成。
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
匿名类的组成部分
- new 操作符
- 要实现的接口名,或者要继承的父类的名称,在此例中匿名类实现了HelloWorld接口。
- 括号,跟一般初始化一个类实例别无二致,需要填入构造方法中的构造参数,
注:用匿名类实现接口时,没有构造方法,那么括号中不需要填参数即可。
- 类主体,即匿名类的实现。
因为匿名类被当做表达式一样被使用,如在定义frenchGreeting对象时,匿名类的全部定义都是该表达式的一部分, 这也解释了为什么匿名类定义的最后要以;结尾,因为表达式以分号;结尾。
访问外部类的局部变量、声明和使用匿名类成员
像局部类一样,匿名类同样也可以俘获变量,对于外部区域的局部变量拥有一样的访问特性。
- 匿名类可以访问外部其封闭类的成员
- 匿名类无法访问那些不是final或者effectively final的局部变量
- 匿名类中的声明的类型变量,会覆盖掉外部区域中的同名的变量
对于匿名类中的成员,匿名类具有跟局部类相同的限制
- 不能在匿名类中声明静态代码块,或者再定义内部成员接口
- 匿名类中仅当变量为常量时,才可以出现静态成员
小结,在匿名类中可以声明如下内容
- 列表项目
- 字段
- 额外的方法(即使不实现任何父类的方法)
- 实例代码块
- 局部类
但是,不可以在匿名类中声明构造方法
匿名类的一个实例
匿名类在java GUI中使用的较为频繁
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage; public class HelloWorld extends Application {
public static void main(String[] args) {
launch(args);
} @Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() { @Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
}); StackPane root = new StackPane();
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
变量覆盖问题-Shadowing
在内部类或者方法定义中声明的变量类型跟外围区域有相同的名称,那么内部的声明会覆盖掉外部区域中的声明,不能直接通过变量名拿到外部区域中定义的变量,如下所示:
public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
} public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
输出如下
x = 23
this.x = 1
ShadowTest.this.x = 0
示例代码中定义了三个名为x的变量,ShadowTest中的成员变量,内部类FirstLevel中成员变量,以及方法methodInFirstLevel中的参数。
方法methodInFirstLevel中的x会覆盖掉内部类FirstLevel中的x。因为当你在方法methodInFirstLevel中使用变量x时,实际上使用的的是方法参数的值。
如果想引用内部类FirstLevel中的x,需要使用this关键字,来代表引用的时内部类中方法外围的x。
System.out.println("this.x = " + this.x);
如果向引用最外面的基类变量x,则需要指明外部类的类名
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
序列化问题-Serialization
我们强烈不建议对内部类、局部类及匿名类,实现序列化。
当Java编译器编译内部类的构造方法时,会生成synthetic constructs。即一些在源码中未曾出现过的类、方法、字段和其他的构造方法也会被编译出来。
synthetic constructs方式,可以在不改变JVM的前提下,只通过java编译器就可以实现java的新特性。然而,不同的编译器实现synthetic constructs的方式有所不同,这也就意味着,对于同样的.java源码,不同的编译器会编译出来不同的.class文件。
因此,对于一个内部类序列化后,使用不同的JRE进行反序列化的话,可能会存在兼容性的问题。
Java Nested Classes(内部类~第一篇英文技术文档翻译)的更多相关文章
- Java - Nested Classes
(本文参考:http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html) Nested Classes class OuterClas ...
- JVM之--Java内存结构(第一篇)
最近在和同事朋友聊天的时候,发现一个很让人思考的问题,很多人总觉得JVM将java和操作系统隔离开来,导致很多人不用熟悉操作系统,甚至不用了解JVM本身即可完全掌握Java这一门技术,其实个人的观点是 ...
- Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)
横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点: ...
- JAVA知识积累 JSP第一篇【JSP介绍、工作原理、生命周期、语法、指令、行为】
什么是JSP JSP全名为Java Server Pages,java服务器页面.JSP是一种基于文本的程序,其特点就是HTML和Java代码共同存在! 为什么需要JSP JSP是为了简化Servle ...
- Java笔记(基础第一篇)
一.初识java 1.Java是一种可以编写跨平台的.面向对象的程序设计语言. Java开发分成以下3个方向: (1). java SE:主要用于桌面程序的开发.是java EE和java ME的基础 ...
- 初学Java ssh之Spring 第一篇
之前虽然毕业前实习的工作是使用的C# .NET语言,但是,毕业后还是果断应聘Java.虽然自己对Java的理解不如C#深入,只是对基础知识比较熟悉,但还是义无返顾了··· 虽然应聘经历比较坎坷,但最终 ...
- docker第一篇 容器技术入门
Container 容器是一种基础工具,泛指任何可以容纳其它物品的工具. Linux Namespaces (docker容器技术主要是通过6个隔离技术来实现) namespace 系统调用参数 ...
- 第一篇英文短文《It All Starts With A Dream》
http://www.ximalaya.com/#/17209107/sound/6883165 Dreaming. Do you or don’t you? Do you dream about t ...
- Java图像处理最快技术:ImageJ 学习第一篇
ImageJ是世界上最快的纯Java的图像处理程序. 它能够过滤一个2048x2048的图像在0.1秒内(*). 这是每秒40万像素!ImageJ的扩展通过使用内置的文本编辑器和Java编译器的Ima ...
随机推荐
- cocos2d-x学习之旅(五):1.5 使用eclipse编译cocos2d-x示例项目,创建cocos2d-x android项目并部署到真机
今天将cocos2d-x的示例项目tests编译到android真机运行,以及如何创建cocos2d-x的android项目. 打开cocos2d-x的tests项目,路径为:D:\cocos2d-x ...
- jenkins中windows节点设置开机自启动slave-agent
做web UI自动化时,为了提高效率,用了多台windows节点来跑自动化,但slave-agent每次在关机后都得手工启动,麻烦,网上看到了一系列说启动任务中,感觉还是不考虑,这里使用windows ...
- springmvc如何访问到静态的文件,如jpg,js,css
如何你的DispatcherServlet拦截"*.do"这样的有后缀的URL,就不存在访问不到静态资源的问题. 如果你的DispatcherServlet拦截"/&qu ...
- Accelerated Failure Time Models加速失效时间模型AFT
Weibull distribution 或者 σ是未知的scale参数,独立于X的常量, σ>0 是服从某一分布的随机变量 残差(residuals)=
- Docker 安装Centos,Tomcat,Jdk等相关的自定义(Dockerfile)镜像
一.安装Centos镜像 这里Centos 安装 国内daocloud网站提供的官方镜像 docker pull daocloud.io/library/centos:latest 利用docker ...
- 9.11 Django视图 view和路由
2018-9-11 16:34:16 2018-9-11 19:00:24 越努力,.越幸运! Django框架参考: https://www.cnblogs.com/liwenzhou/p/8296 ...
- CTP API 开发之二 :制作CTP java版 API
目前上期技术CTP系统提供的API版本是C++版本 SWIG是一个能将C/C++接口转换为其他语言的工具,目前可以支持Python,Java,R等语言. 本文主要介绍Windows 32/64位平台下 ...
- python3学习笔记(3)_dict-set
# !/usr/bin/env python3 # -*- coding:utf8 -*- #dict 和 set #dict dictionary 用于存放 键值对的, 无序,key 不可变 #姓名 ...
- Set-cookie无效(失效)
今天做爬虫的时候遇到网站响应response返回的数据中有Set-Cookie,但是使用Linux的curl请求网页保存cookie始终为空,换句话说也就是Set-Cookie设置无效,所以我一直Go ...
- linux 批量文件查找并替换
linux 批量文件查找并替换 sed -i "s/oldstring/newstring/g" `grep oldstring -rl path` 如: sed -i " ...