Java语言规范第七章-包(Java Language Specification – Chapter7 Package)
同一个package不可以包含同名的两个成员,否则将发生编译错误。
例如
#由于java.awt包中已经包含了子包image,所以不能再包含名为image的类或者接口。
#如果存在名为mouse的包,并且包含了Button类,那么就不能存在任何名为mouse.Button或者mouse.Button.Click的子包。
#如果已经存在com.sun.java.jag类型(类或者接口),那么就不能存在com.sun.java.jag或者com.sun.java.jag.scrabble.
包名可以为Unicode字符,当这样的包存在于只接受ASCII字符的文件系统中时,需要对文件名字进行转义,即使用@字符,后面连接四位十六进制的数字。
例如:
children.activities.crafts.papierM/u00e2ch/u00e9
可以写成完整的Unicode为:
children.activities.crafts.papierMâché
可以映射到如下目录:
children/activities/crafts/papierM@00e2ch@00e9
@字符在某些特定的文件系统中是不合法的,那么将使用其他的字符代替,前提是该字符不是java中合法的标志符。
哪些编译单元是可见的由宿主系统决定,但是java.lang和java.io必须是可见的。每个包中的类或者接口默认的导入java.lang包中所有的类型。
ImportDeclaration:
SingleTypeImportDeclaration
TypeImportOnDemandDeclaration
SingleStaticImportDeclaration
StaticImportOnDemandDeclaration
重复的import语句是被忽略的。
import java.util.Iterator;//ok
import java.util.Iterator;//ok
import java.util.Iterator;//ok
import javax.swing.text.html.HTMLDocument.Iterator;
// The import javax.swing.text.html.HTMLDocument.Iterator collides with another import statement
import java.util.Iterator;// The import java.util.Iterator conflicts with a type defined in the same file
public class Iterator {
}
import java.sql.*;
import java.util.*;
public class Iterator {
public Date date(){//The type Date is ambiguous
return new Date();
}
}
import static jp.co.valup.jls.PackageStatic.VERSION;
public class PackageDemo {
public static final String VERSION = "v2";
public static void main(String[] args){
System.out.println(VERSION);//v2
}
}
import static jp.co.valup.jls.PackageStaticOnDemand.*;
import static jp.co.valup.jls.PackageStatic.VERSION;
public class PackageDemo {
public static void main(String[] args) {
System.out.println(VERSION);// v1
}
}
单独导入的VERSION将会覆盖.*导入的VERSION。
Java语言规范第八章-类(Java Language Specification – Chapter8 Class)
并不是所有的modifiers适用于所有的类声明。
public只适用于顶层类和成员类。
protected和private只适用于成员类。
static只适用于成员类。
如果出现多个modifiers,那么它们出现的顺序要根据ClassModifier的定义。
当子类无法实现父类的所有的abstract方法时,将会发生编译错误。例如父类包含了两个相同签名(同名,相同参数个数,参数的类型对应相同)的方法,但是方法的返回值不同。
public interface InterfaceSample {
void setColor(int color);
}
public abstract class AbstractClassDemo implements InterfaceSample {
abstract int setColor(int color);
}
只有当需要子类继承实现父类时,才声明abstract类,如果要防止实例化类,那么一种方式是将默认的无参构造方法定义为private,并仅包含该构造方法,通常这样的类包含类变量或者类方法,例如java.lang.Math类。
内部类是嵌套类的一种,当嵌套类没有显式或者隐式的声明为static的时候,嵌套类就是内部类。内部类不能声明静态初始化或者成员接口。内部类不能声明静态成员,除非是常量。例如:
class HasStatic{
static int j = 100;
}
class Outer{
class Inner extends HasStatic{
static final int x = 3; // ok - compile-time constant
static int y = 4; // compile-time error, an inner class
}
static class NestedButNotInner{
static int z = 5; // ok, not an inner class
}
interface NeverInner{} // interfaces are never inner
}
内部类可以继承非常量的静态成员。内部类意外的嵌套类是可以声明静态成员的。成员接口是隐式的静态成员,所以不被认为是内部类。
A statement or expression occurs in a static context if and only if the innermost method, constructor, instance initializer, static initializer, field initializer, or explicit constructor invocation statement enclosing the statement or expression is a static method, a static initializer, the variable initializer of a static variable, or an explicit constructor invocation statement.
当且仅当最内层(innermost)方法、构造方法、实例初始化、静态初始化、成员初始化、显式构造方法调用是静态方法、静态初始化、静态变量初始化、显式构造方法调用的时候,才可以在静态上下文中出现语句或者表达式。
如果类C直接被类O包含,并且类C没有被声明为静态类,那么类C是类O的直接内部类;如果类C直接或者间接被类O包含(C->C2->...->O),那么类C是类O的内部类。
类O的直接内部类C的实例l与类O的一个实例相关联,被称为实例l的直接闭包。
当内部类的实例引用外部类的成员时,外部类实例的变量被使用。不可以在内部类中对外部类中的final变量赋值(外部类中声明了final变量,但是没有赋值。)。
当在静态上下文中声明内部类I的实例时,该实例没有外部类的实例。然而,在静态方法或者静态初始化中直接被声明,那么I具有包含块,是包含I的声明的最里层。
对于内部类C的任何父类S,其中类S是类SO的直接内部类,那么存在一个SO的实例与l关联。
任何没有在内部类中声明,但是在内部类中使用的的变量,方法参数以及异常控制参数都必须被声明为final。任何没有在内部类中声明,但是在内部类中使用的局部变量必须在内部类之前进行赋值。
内部类包括local内部类,匿名内部类和非静态内部类。例如:
class Outer {
int i = 100;
static void classMethod() {
final int l = 200;
class LocalInStaticContext{
int k = i; // compile-time error
int m = l; // ok
}
}
void foo() {
class Local { // a local class
int j = i;
}
}
}
内部类LocalInStaticContext的声明出现在静态上下文之中(静态方法),在静态方法中无法访问Outer类的实例变量。特别是不允许在LocalInStaticContext类中出现Outer类的成员变量。但是,内部类中可以访问静态方法中定义的final类型的局部变量。
不是在静态上下文中定义的内部类可以自由的访问外部类的成员变量。成员变量是针对实例来定义的。所以每个内部类都包含一个外部类的实例。
更进一步的例子:
class WithDeepNesting{
boolean toBe;
WithDeepNesting(boolean b) { toBe = b;}
class Nested {
boolean theQuestion;
class DeeplyNested {
DeeplyNested(){
theQuestion = toBe || !toBe;
}
}
}
}
这里,每个WithDeepNesting.Nested.DeeplyNested的实例包含一个WithDeepNesting.Nested的实例和WithDeepNesting的实例。
如果在编译时发现声明了循环的类,那么将抛出ClassCircularityError。
public class CircularityClass extends Original{
}
class Original extends Ancestor{
}
class Ancestor extends CircularityClass{
}
Cycle detected: a cycle exists in the type hierarchy between Ancestor and CircularityClass
只所以不允许循环继承,是因为在初始化子类的时候,会调用父类的默认的构造器或者无参的构造器(声明了无参构造器),如果允许循环继承的话,会导致无限循环。
被继承的接口在implements语句中不允许出现两次或者两次以上。
允许在一个类中通过声明一个方法来实现多个接口。例如:
interface Fish { int getNumberOfScales(); }
interface Piano { int getNumberOfScales(); }
class Tuna implements Fish, Piano {
// You can tune a piano, but can you tuna fish?
public int getNumberOfScales() { return 91; }
}
但是,不可以定义返回值类型或者存取控制符不同的类,例如:
interface Fish { int getNumberOfScales(); }
interface StringBass { double getNumberOfScales(); }
class Bass implements Fish, StringBass {
// 无论声明为什么类型,该声明都不正确。
public ??? getNumberOfScales() { return 91; }
}
例如,当将返回值类型声明为long时,会发生如下的编译错误:The return type is incompatible with Fish.getNumberOfScales()
没有办法定义一个名为getNumberOfScales的方法来使得方法的签名和返回值类型同时实现Fish接和StringBass接口中的方法。因为类中不允许定义相同签名但是不同返回值的方法。
interface I<T>{
}
class B implements I<Integer>{}
class C extends B implements I<String>{}//The interface I cannot be implemented more than once with different arguments: I<Integer> and I<String>
当接口声明中包含泛型时,不可以同时继承/实现两种泛型。
类的成员包括从直接父类继承的,从接口继承的以及本类声明的。子类无法继承父类的package和private成员,只能继承public和protected。
构造器,静态初始化和实例初始化都不是类的成员,因此不被继承。
class Point {
int x, y;
private Point() { reset(); }
Point(int x, int y) { this.x = x; this.y = y; }
private void reset() { this.x = 0; this.y = 0; }
}
//ERROR:由于ColoredPoint没有定义构造方法,所以将使用默认的构造方法ColoredPoint() { super(); },但是父类的无参构造方法是private的,所以无法被访问。
class ColoredPoint extends Point {
int color;
void clear() { reset(); }
// ERROR: 子类不能继承父类的私有方法。
}
class Test {
public static void main(String[] args) {
ColoredPoint c = new ColoredPoint(0, 0);
// ERROR:构造方法ColoredPoint(int, int)没有被定义,说明子类并不能继承父类的构造方法。
c.reset();
// ERROR:子类不能继承父类的私有方法。
}
}
package points;
public class Point {
int x, y;
public void move(int dx, int dy) { x += dx; y += dy; }
}
package points;
public class Point3d extends Point {
int z;
public void move(int dx, int dy, int dz) {
x += dx; y += dy; z += dz;
}
}
import points.Point3d;
class Point4d extends Point3d {
int w;
public void move(int dx, int dy, int dz, int dw) {
x += dx; y += dy; z += dz; w += dw; // compile-time errors
}
}
由于Point3d和Point位于同一个package中,所以编译没有错误,子类继承了父类的成员变量。但是Poing4d在不同的包里,无法继承package存取控制的父类的成员变量。
package points;
public class Point {
public int x, y;
public void move(int dx, int dy) {
x += dx; y += dy;
}
}
package morePoints;
class Point3d extends points.Point {
public int z;
public void move(int dx, int dy, int dz) {
super.move(dx, dy); z += dz;
}
public void move(int dx, int dy) {
move(dx, dy, 0);
}
}
public class OnePoint {
public static points.Point getOne() {
return new Point3d();
}
}
虽然Point3d不能同包以外的类直接访问,但是通过父类或者副接口,可以获取Point3d的实例。此时调用point.Point类的move方法,调用的是Point3d的方法。同时x和y也可以被访问。这是由于子类型化是在运行时决定的,而非编译时。此时定义public的Point4d来继承Point3d:
package morePoints;
public class Point4d extends Point3d {
public int w;
public void move(int dx, int dy, int dz, int dw) {
super.move(dx, dy, dz); w += dw;
}
}
Point4d继承了成员z,并且Point4d是public类,所以可以在任何包中访问。
成员变量的static final transient volatile访问控制符虽然不要求按照指定的顺序,但是建议按照这个顺序。
final类成员必须在声明时或者static初始化时进行赋值。
final成员变量必须在声明时或者在每个构造器的结尾处赋值。
transient变量表示该变量不是对象持久化状态的一部分。
final和volatile不可以同时使用。
在静态初始化块和静态方法中,不允许访问成员变量和成员方法以及this和super关键字。反之可以。
在运行时,final静态变量或者通过编译时常量初始化的类变量先被初始化。这同样适用于接口。 这些变量是常量。
初始化成员变量时,可以使用本类中声明的以及父类中声明的static变量。所以下面的程序没有编译错误:
class Test {
float f = j;
static int j = 1;
}
成员变量必须先出现,后使用。
使用之前必须先进行定义:
class Test {
int i = j; // compile-time error: incorrect forward reference
int j = 1;
}
而下面的定义和使用没有错误:
class Test {
Test() { k = 2; }
int j = 1;
int i = j;
int k;
}
在没有定义j之前无法引用j:
class Z {
static int i = j + 2;
static int j = 4;
}
class Z {
static { i = j + 2; }
static int i, j;
static { j = 4; }
}
但是通过方法来访问没有问题:
class Z {
static int peek() { return j; }
static int i = peek();
static int j = 1;
}
class Test {
public static void main(String[] args) {
System.out.println(Z.i);
}
}
该程序会打印结果0。
这是因为当peek方法访问j的时候,j还没有被初始化,所以采用默认的值,即0。
一个更详细的例子如下:
class UseBeforeDeclaration {
static {
x = 100; // ok - assignment
int y = x + 1; // error - read before declaration
int v = x = 3; // ok - x at left hand side of assignment
int z = UseBeforeDeclaration.x * 2; // ok - not accessed via simple name
Object o = new Object(){
void foo(){x++;} // ok - occurs in a different class
{x++;} // ok - occurs in a different class
};
}
{
j = 200; // ok - assignment
j = j + 1; // error - right hand side reads before declaration
int k = j = j + 1;
int n = j = 300; // ok - j at left hand side of assignment
int h = j++; // error - read before declaration
int l = this.j * 3; // ok - not accessed via simple name
Object o = new Object(){
void foo(){j++;} // ok - occurs in a different class
{ j = j + 1;} // ok - occurs in a different class
};
}
int w = x = 3; // ok - x at left hand side of assignment
int p = x; // ok - instance initializers may access static fields
static int u = (new Object(){int bar(){return x;}}).bar();
// ok - occurs in a different class
static int x;
int m = j = 4; // ok - j at left hand side of assignment
int o = (new Object(){int bar(){return j;}}).bar();
// ok - occurs in a different class
int j;
}
子类中定义的变量会覆盖父类定义的同名变量(static非static)。
class Point {
int x = 2;
}
class Test extends Point {
double x = 4.7;
void printBoth() {
System.out.println(x + " " + super.x);
}
public static void main(String[] args) {
Test sample = new Test();
sample.printBoth();
System.out.println(sample.x + " " +
((Point)sample).x);
}
}
在上面的例子中,由于Test类中的x覆盖了Point类中的x,所以Test类并没有继承它的父类的x成员,注意,是类Point的成员x没有被类Test继承,但是Test的实例仍然实现了该成员。也就是说,每个Test类的实例包含两个成员,类型为int和double,并且名字相同。但是在Test类内,x只对应Test类中声明的x,如果要访问Point类中的x,需要使用super.x。
interface Frob { float v = 2.0f; }
class SuperTest { int v = 3; }
class Test extends SuperTest implements Frob {
public static void main(String[] args) {
new Test().printV();
}
void printV() { System.out.println(v); }
}
此时会发生编译错误,因为变量v是二义的。可以通过使用qualified name和super来进行使用:
interface Frob { float v = 2.0f; }
class SuperTest { int v = 3; }
class Test extends SuperTest implements Frob {
public static void main(String[] args) {
new Test().printV();
}
void printV() {
System.out.println((super.v + Frob.v)/2);
}
}
对于接口,使用qualified name,而对于父类,则使用super,由于java不允许多重继承,即一个类不可以有多个直接父类,所以不会出现多个super的情况。
当通过多重路径继承某个常量或者变量时,不会差生二义性,例如:
public interface Colorable {
int RED = 0xff0000, GREEN = 0x00ff00, BLUE = 0x0000ff;
}
public interface Paintable extends Colorable {
int MATTE = 0, GLOSSY = 1;
}
class Point { int x, y; }
class ColoredPoint extends Point implements Colorable {
. . .
}
class PaintedPoint extends ColoredPoint implements Paintable
{
. . . RED . . .
}
PaintedPoint中的RED使用的是Colorable中的常量。
abstract方法不可以是private、final和static。
抽象方法可以被抽象方法override。
非抽象实例方法可以被抽象方法override。
在运行时阶段,代码生成器会优化final方法生成内联的代码。
当成员方法override静态方法时,将发生编译异常。
但是overriding methods is differ from hiding fields, hiding fields允许覆盖static变量。
class StringSorter {
// takes a collection of strings and converts it to a sortedlist
List toList(Collection c) {...}
}
class Overrider extends StringSorter{
List toList(Collection c) {...}
}
现在,开发人员需要将StringSorter修改为:
class StringSorter {
// takes a collection of strings and converts it to a list
List<String> toList(Collection<String> c) {...}
}
此时不会有编译错误。但是会发生编译时的警告。
class Point {
int x = 0, y = 0;
void move(int dx, int dy) { x += dx; y += dy; }
int getX() { return x; }
int getY() { return y; }
int color;
}
class RealPoint extends Point {
float x = 0.0f, y = 0.0f;
void move(int dx, int dy) { move((float)dx, (float)dy); }
void move(float dx, float dy) { x += dx; y += dy; }
int getX() { return (int)Math.floor(x); }
int getY() { return (int)Math.floor(y); }
}
上面的代码是编译无错误的,子类RealPoint对父类Point的move方法和getX,getY方法分别进行了override,同时在子类RealPoint内部,有一个overload的move方法,参数类型与继承的move不同。另外,子类的float类型的x和y对父类的x和y进行了hide。考虑下面的测试程序:
class Test {
public static void main(String[] args) {
RealPoint rp = new RealPoint();
Point p = rp;
rp.move(1.71828f, 4.14159f);
p.move(1, -1);
show(p.x, p.y);//(0,0)
show(rp.x, rp.y);//(2.71828,3.14159)
show(p.getX(), p.getY());//(2,3)
show(rp.getX(), rp.getY());//(2,3)
}
static void show(int x, int y) {
System.out.println("(" + x + ", " + y + ")");
}
static void show(float x, float y) {
System.out.println("(" + x + ", " + y + ")");
}
}
两次调用move方法,都会调用RealPoint类的方法,但是在show的时候,由于子类对父类的成员变量进行了hide,所以无法通过子类直接获取int类型的x和y,所以通过声明父类的实例p来获取,通过子类直接获取的是float类型的x和y。但是无论是p还是rp,调用getX和getY时,都是在调用子类的方法。
因为类方法/静态方法可以直接使用类名来调用,所以该调用是在编译时完成的。所以隐藏hiding静态方法和成员方法是不同的。考虑下面的方法:
class Super {
static String greeting() { return "Goodnight"; }
String name() { return "Richard"; }
}
class Sub extends Super {
static String greeting() { return "Hello"; }
String name() { return "Dick"; }
}
class Test {
public static void main(String[] args) {
Super s = new Sub();
System.out.println(s.greeting() + ", " + s.name());
}
}
将会输出如下的内容:
Goodnight, Dick
由于调用greeting方法时使用的是Super声明的类型s,这是在编译时决定的。而name方法是成员方法,这个方法的调用是在运行时决定的。
class BadPointException extends Exception {
BadPointException() { super(); }
BadPointException(String s) { super(s); }
}
class Point {
int x, y;
void move(int dx, int dy) { x += dx; y += dy; }
}
class CheckedPoint extends Point {
// Exception BadPointException is not compatible with throws clause in Point.move(int, int)
void move(int dx, int dy) throws BadPointException {
if ((x + dx) < 0 || (y + dy) < 0)
throw new BadPointException();
x += dx; y += dy;
}
}
子类override父类的方法时,抛出的异常应该是父类的子集(等于父类抛出的异常或者少于父类抛出的异常)。
子类中定义的成员类或者成员接口(包含在其他类/接口中的类/接口)将覆盖父类中成员类或者成员接口。
class Point {
int A;
int A(){return 0;}
class A{}
}
class CheckedPoint extends Point {
class A{}
}
一个类可能继承两个或者多个具有相同名称的类型,例如两个接口或者一个父类一个接口。如果引用具有二义性的类或者接口,将会发生编译时异常。
package example.a;
interface A{}
package example.b;
interface A{}
package example.cl.d;
class A{}
package example.c;
imports example.a.A;
imports example.b.A;
imports example.cl.d.A;
class C implements A,A{}
class C extends A implements A{}
此时在类C中会发生编译时错误。需要使用full qualified name来进行约束。
The import jp.co.valup.jls.inter.A collides with another import statement.
如果使用*方式import时,将会发生其他消息的编译时错误。
The type A cannot be a superinterface of C; a superinterface must be an interface.
static关键字可以用来定义类型C,但是C的外部类T不可以是inner类(非static类)。
public class A{
class B{
static class C{}
}
}
上面的代码将会发生编译时错误。
The member type C cannot be declared static; static types can only be declared in static or top level types.
它的结果是C是一个非静态类。就像静态方法中不允许出现类的实例一样。C也不可以有T的实例。
成员接口总是隐式的声明为静态接口。在声明时显式的写出static是可以但不是必要的。
成员初始化:成员初始化(instance initializer)在创建实例时被执行。
如果成员初始化时抛出异常将导致编译时错误,除非该异常或者该异常的父类在构造器中被显式的用throws声明。匿名类中的成员初始化可以抛出任何类型的异常。
这种区分是经过深思熟虑的。因为匿名类只在程序的一个单一点上被实例化,所以在匿名类的外部是可以直接判断抛出的异常是什么类型。而命名的类,是可以在任何地方被初始化的。所以获取异常信息的渠道之能是通过构造方法。
如果实例初始化不能正常的完成,那么将发生编译时错误。如果在实例初始化block中存在return语句,将发生编译时错误。
有时在实例声明后直接使用实例是有限制的。
实例初始化是可以使用this关键字以及super关键字的。
public class Test {
// Default constructor cannot handle exception type Exception thrown by
// implicit super constructor. Must define an explicit constructor
{
print();
this.print();
super.equals(this);
}
//Unhandled exception type Exception
static {print("Hello");}
public void print() throws Exception{}
public static void print(String str) throws Exception{}
}
静态初始化没有处理异常或者没有正常完成都会发生编译时错误。
静态初始化和类变量初始化按照文本出现先后顺序进行初始化。
静态初始化中出现return语句导致编译时错误。
静态初始化中出现this或者super关键字导致编译时错误。rror occurs.
如果对类的构造器没有指定访问控制符,那么构造器使用默认的访问控制。如果没有为enum类型的构造器指定访问控制符,那么使用private,为enum类型构造器指定public或者protected发生编译时错误。
和方法不同,构造方法不可以是abstract,static,final,native,strictfp或者synchronized。
定义构造方法为synchronized会将构造中的对象被锁定。这使得其他线程无法访问访该对象,知道该对象的所有构造方法完成。
在构造方法中直接或者间接的调用自己,会导致编译时错误。enum类型的构造方法显式的调用父类的构造方法将导致编译时错误。
调用本类或者超类的构造方法要放在构造方法的第一行。考虑下面的程序:
public class Test {
public Test(){}
class SubTest extends Test{
public SubTest(){
}
public SubTest(int count){
this();
super();
}
}
}
在子类SubTest的带有int类型参数的构造器中,无论this和super的顺序如何,都将导致编译时错误。
调用构造方法可以分为两类:
同类中的调用:使用this来调用同类中的其他构造方法。
父类中的调用:使用super关键字或者主表达式。用来调用直接父类的构造方法。调用父类构造方法又可以分为两类:
使用super来调用。
使用表达式来调用:适用于父类是inner class的情况。
class Outer {
class Inner{}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner(){(new Outer()).super();}
}
在使用显式构造方法调用语句时,在构造方法内部不可以使用任何本类或者父类定义的实例变量或者实例方法。
例如:
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
class ColoredPoint extends Point {
static final int WHITE = 0, BLACK = 1;
int color;
ColoredPoint(int x, int y) {
this(x, y, color);//error:Cannot refer to an instance field color while explicitly invoking a constructor.
}
}
由于在this中使用了成员变量color。
class Top {
int x;
class Dummy {
Dummy(Object o) {}
}
class Inside extends Dummy {
Inside() {
super(new Object() { int r = x; }); // error
}
Inside(final int y) {
super(new Object() { int r = y; }); // correct
}
}
}
如果构造方法中包含了匿名类实例的创建,那么创建时不可以引用包含构造方法所属的类的成员变量或者其父类的成员变量。
如果一个类没有声明任何构造方法,那么缺省无参的构造方法被系统自动提供。
如果类是Object,那么缺省无参的构造方法的body是空的。
如果类不是Object,那么缺省无参的构造方法只调用超类的构造方法。
缺省的构造方法不包含throws子句。
如果父类不包含无参的构造方法,那么将发生编译错误。因为子类构造时,会自动调用父类的无参的构造方法。
在enum类型中,缺省的构造方法是隐式的private。在其他情况,缺省构造方法的public/protected/default/private与类的声明一致。即如果类是public的,那么缺省的构造方法也是public的。但是这并不代表类在可访问时,其构造方法就是可访问的。
package p1;
public class Outer {
protected class Inner{}
}
package p2;
class SonOfOuter extends p1.Outer {
void foo() {
new Inner(); // compile-time access error
}
}
Inner类的构造方法是protected的,但是它是相对于Inner的,即Inner的子类或者同一包中的类可以访问该构造方法。而protected的Innter类是相对于Outer的,可以被同一包中的类或者Outer类的子类访问,所以SonOfOuter可以访问Inner,但是不可以访问Inner的protected构造方法。
enum的body中可以包含enum常量,enum常量定义了一个enum类型。除了enum常量,enum没有别的实例。
显式的实例化一个enum类型是会发生编译错误的。Enum中的final clone方法确保enum常量不会被cloned,而通过序列化机制的特殊处理也确保不会在反序列化的时候产生重复的实例。禁止使用反射机制来实例化enum类型。以上四个限制确保了enum 常量以外没有任何的实例。
由于对于每个enum常量,只有一个实例,所以可以使用==来代替equals对两个enum进行比较。