Beginng_Java7(译):探索高级语言功能(第三章)(第1 嵌套类型)(完)

第1章和第2章向您介绍了Java的基本语言功能及其对类和对象的支持。 第3章通过向您介绍Java的高级语言功能,特别是与嵌套类型,包,静态导入相关的功能,为此奠定了基础。
异常,断言,注释,泛型和枚举。

嵌套类型

在任何类之外声明的类称为顶级类。 Java还支持嵌套类,这些类是声明为其他类或作用域成员的类。 嵌套类可帮助您实现顶级类架构。

有四种嵌套类:静态成员类,非静态成员类,匿名类和本地类。 后三类被称为内部类。

本节介绍静态成员类和内部类。 对于每种嵌套类,我都会为您提供简要介绍,抽象示例和更实用的示例。 然后,本节简要介绍了类中嵌套接口的主题。

静态成员类

静态成员类是封闭类的静态成员。 虽然是封闭的,但它没有该类的封闭实例,并且无法访问封闭类的实例字段并调用其实例方法。 但是,它可以访问封闭类的静态字段并调用其静态方法,甚至是那些声明为私有的成员。 清单3-1给出了一个静态成员类声明。

清单3-1。 声明一个静态成员类

class EnclosingClass
{
private static int i;
private static void m1()
{
System.out.println(i);
}
static void m2()

{
EnclosedClass.accessEnclosingClass();
}
static class EnclosedClass
{
static void accessEnclosingClass()
{
i = 1;
m1();
}
void accessEnclosingClass2()
{
m2();
}
}
}

清单3-1声明了一个名为EnclosingClass的顶级类,其中包含类字段i,类方法m1()和m2(),以及静态成员类EnclosedClass。 此外,EnclosedClass声明类方法accessEnclosingClass()和实例方法accessEnclosingClass2()。

因为accessEnclosingClass()声明为static,所以m2()必须在此方法的名称前加上EnclosedClass,并使用成员访问运算符来调用此方法。

清单3-2给出了一个应用程序的源代码,它演示了如何调用EnclosedClass的accessEnclosingClass()类方法,并实例化EnclosedClass并调用其accessEnclosingClass2()实例方法。

清单3-2。 调用静态成员类的类和实例方法

class SMCDemo
{
public static void main(String[] args)
{
EnclosingClass.EnclosedClass.accessEnclosingClass(); // Output: 1
EnclosingClass.EnclosedClass ec = new EnclosingClass.EnclosedClass();
ec.accessEnclosingClass2(); // Output: 1
}
}

清单3-2的main()方法显示,必须在封闭类的名称前面加上其封闭类的名称,以调用类方法; 例如,EnclosingClass.EnclosedClass.accessEncl singClass();.

此列表还显示,在实例化所包含的类时,必须在封闭类的名称前面加上其封闭类的名称; 例如,EnclosingClass.EnclosedClass ec = new EnclosingClass.EnclosedClass();. 然后,您可以以正常方式调用实例方法; 例如,ec.accessEnclosingClass2();.

静态成员类有其用途。 例如,代码清单3-3的Double和Float静态成员类提供了其封闭Rectangle类的不同实现。 由于其32位浮点字段,Float版本占用的内存较少,而由于其64位双字段,Double版本提供更高的准确性。

清单3-3。 使用静态成员类声明其封闭类的多个实现

abstract class Rectangle
{
abstract double getX();
abstract double getY();
abstract double getWidth();
abstract double getHeight();
static class Double extends Rectangle
{
private double x, y, width, height;
Double(double x, double y, double width, double height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
double getX() { return x; }
double getY() { return y; }
double getWidth() { return width; }
double getHeight() { return height; }
}
static class Float extends Rectangle
{
private float x, y, width, height;
Float(float x, float y, float width, float height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
double getX() { return x; }
double getY() { return y; }
double getWidth() { return width; }
double getHeight() { return height; }
}
// Prevent subclassing. Use the type-specific Double and Float
// implementation subclass classes to instantiate.
private Rectangle() {}

boolean contains(double x, double y)
{
return (x >= getX() && x < getX()+getWidth()) &&
(y >= getY() && y < getY()+getHeight());
}
}

清单3-3的Rectangle类演示了嵌套的子类。 Double和Float静态成员类中的每一个都是抽象Rectangle类的子类,提供私有浮点或双精度浮点字段,并覆盖Rectangle的抽象方法以将这些字段的值作为双精度值返回。

Rectangle是抽象的,因为实例化这个类是没有意义的。 因为使用新实现直接扩展Rectangle也没有意义(Double和Float嵌套子类应该足够),所以它的默认构造函数被声明为private。 相反,您必须实例化Rectangle.Float(以节省内存)或Rectangle.Double(当需要精度时),如清单3-4所示。

清单3-4。 实例化嵌套的子类

class SMCDemo
{
public static void main(String[] args)
{
Rectangle r = new Rectangle.Double(10.0, 10.0, 20.0, 30.0);
System.out.println("x = "+r.getX());
System.out.println("y = "+r.getY());
System.out.println("width = "+r.getWidth());
System.out.println("height = "+r.getHeight());
System.out.println("contains(15.0, 15.0) = "+r.contains(15.0, 15.0));
System.out.println("contains(0.0, 0.0) = "+r.contains(0.0, 0.0));
System.out.println();
r = new Rectangle.Float(10.0f, 10.0f, 20.0f, 30.0f);
System.out.println("x = "+r.getX());
System.out.println("y = "+r.getY());
System.out.println("width = "+r.getWidth());
System.out.println("height = "+r.getHeight());
System.out.println("contains(15.0, 15.0) = "+r.contains(15.0, 15.0));
System.out.println("contains(0.0, 0.0) = "+r.contains(0.0, 0.0));
}
}

清单3-4首先通过新的Rectangle.Double(10.0,10.0,20.0,30.0)实例化Rectangle的Double子类,然后调用它的各种方法。 接下来,在调用此实例上的Rectangle方法之前,清单3-4通过新的Rectangle.Float(10.0f,10.0f,20.0f,30.0f)实例化Rectangle的Float子类。

编译两个列表(javac SMCDemo.java或javac * .java)并运行应用程序(java SMCDemo)。 然后,您将观察以下输出:

x = 10.0
y = 10.0
width = 20.0
height = 30.0
contains(15.0, 15.0) = true
contains(0.0, 0.0) = false
x = 10.0
y = 10.0
width = 20.0
height = 30.0
contains(15.0, 15.0) = true
contains(0.0, 0.0) = false

Java的类库包含许多静态成员类。 例如,java.lang.Character类包含一个名为Subset的静态成员类,其实例表示Unicode字符集的子集。 java.util.AbstractMap.SimpleEntry,java.io.ObjectInputStream.GetField和java.security.KeyStore.PrivateKeyEntry是其他示例。


■注意编译包含静态成员类的封闭类时,编译器会为静态成员类创建一个类文件,该类的名称由其封闭类的名称,美元符号字符和静态成员类的名称组成。 例如,编译清单3-1,您将发现EnclosingClass $ EnclosedClass.class以及EnclosingClass.class。 此格式也适用于非静态成员类。


非静态成员类

非静态成员类是封闭类的非静态成员。 非静态成员类的每个实例都隐式地与封闭类的实例相关联。 非静态成员类的实例方法可以调用封闭类中的实例方法,并访问封闭类实例的非静态字段。 清单3-5显示了一个非静态成员类声明。

清单3-5。 声明非静态成员类

class EnclosingClass
{
private int i;
private void m()
{
System.out.println(i);
}
class EnclosedClass
{
void accessEnclosingClass()
{
i = 1;
m();
}
}
}

清单3-6。 调用非静态成员类的实例方法

class NSMCDemo
{
public static void main(String[] args)
{
EnclosingClass ec = new EnclosingClass();
ec.new EnclosedClass().accessEnclosingClass(); // Output: 1
}
}

清单3-6的main()方法首先实例化EnclosingClass并将其引用保存在局部变量ec中。 然后,main()使用此引用作为new运算符的前缀,以实例化EnclosedClass,其引用随后用于调用accessEnclosingClass(),其输出1。


■注意前缀new并引用封闭类是很少见的。 相反,您通常会从构造函数或其封闭类的实例方法中调用封闭类的构造函数。


假设您需要维护待办事项列表,其中每个项目都包含名称和描述。 经过一番思考后,您创建了清单3-7的ToDo类来实现这些项目。
清单3-7。 将待办事项实现为名称 - 描述对

class ToDo
{
private String name;
private String desc;
ToDo(String name, String desc)
{
this.name = name;
this.desc = desc;
}
String getName()
{
return name;
}
String getDesc()
{
return desc;
}
@Override
public String toString()
{
return "Name = "+getName()+", Desc = "+getDesc();
}
}

接下来,您将创建一个ToDoList类来存储ToDo实例。 ToDoList使用其ToDoArray非静态成员类在可增长的数组中存储ToDo实例 - 您不知道将存储多少实例,并且Java数组具有固定长度。 请参见清单3-8。

清单3-8。 在ToDoArray实例中最多存储两个ToDo实例

class ToDoList
{
private ToDoArray toDoArray;
private int index = 0;
ToDoList()
{
toDoArray = new ToDoArray(2);
}
boolean hasMoreElements()
{
return index < toDoArray.size();
}
ToDo nextElement()
{
return toDoArray.get(index++);
}
void add(ToDo item)
{
toDoArray.add(item);
}
private class ToDoArray
{
private ToDo[] toDoArray;
private int index = 0;
ToDoArray(int initSize)
{
toDoArray = new ToDo[initSize];
}
void add(ToDo item)
{

if (index >= toDoArray.length)
{
ToDo[] temp = new ToDo[toDoArray.length*2];
for (int i = 0; i < toDoArray.length; i++)
temp[i] = toDoArray[i];
toDoArray = temp;
}
toDoArray[index++] = item;
}
ToDo get(int i)
{
return toDoArray[i];
}
int size()
{
return index;

}
}
}

除了提供add()方法来存储ToDoArray实例中的ToDo实例外,ToDoList还提供了hasMoreElements()和nextElement()方法来迭代并返回存储的实例。 清单3-9演示了这些方法。

清单3-9 创建并迭代ToDo实例的ToDoList

class NSMCDemo
{
public static void main(String[] args)
{
ToDoList toDoList = new ToDoList();
toDoList.add(new ToDo("#1", "Do laundry."));
toDoList.add(new ToDo("#2", "Buy groceries."));
toDoList.add(new ToDo("#3", "Vacuum apartment."));
toDoList.add(new ToDo("#4", "Write report."));
toDoList.add(new ToDo("#5", "Wash car."));
while (toDoList.hasMoreElements())
System.out.println(toDoList.nextElement());
}
}

编译所有三个列表(javac NSMCDemo.java或javac * .java)并运行应用程序(java NSMCDemo)。 然后,您将观察以下输出:

Name = #1, Desc = Do laundry.
Name = #2, Desc = Buy groceries.
Name = #3, Desc = Vacuum apartment.
Name = #4, Desc = Write report.
Name = #5, Desc = Wash car

Java的类库提供了许多非静态成员类的示例。 例如,java.util包的HashMap类声明私有HashIterator,ValueIterator,KeyIterator和EntryIterator类,用于迭代hashmap的值,键和条目。 (我将在中讨论HashMap
第5章)


■注意封闭类中的代码可以通过使用封闭类的名称和成员访问运算符限定保留字this来获取对其封闭类实例的引用。 例如,如果accessEnclosingClass()中的代码需要获取对其EnclosingClass实例的引用,则它将指定EnclosingClass.this。


匿名类

匿名类是没有名称的类。 此外,它不是其封闭类的成员。 相反,同时声明一个匿名类(作为类的匿名扩展或作为接口的匿名实现),并实例化任何指定表达式合法的地方。 清单3-10
演示匿名类声明和实例化。

清单3-10。 声明并实例化扩展类的匿名类

abstract class Speaker
{
abstract void speak();
}
class ACDemo
{
public static void main(final String[] args)
{
new Speaker()
{
String msg = (args.length == 1) ? args[0] : "nothing to say";
@Override
void speak()
{
System.out.println(msg);
}
}
.speak();
}
}

清单3-10介绍了一个名为Speaker的抽象类和一个名为ACDemo的具体类。 后一个类的main()方法声明了一个匿名类,它扩展了Speaker并覆盖了其speak()方法。 调用此方法时,如果没有参数,则输出main()的第一个命令行参数或默认消息; 例如,java ACDemo Hello输出Hello。

匿名类没有构造函数(因为匿名类没有名称)。 但是,它的类文件确实包含执行实例初始化的()方法。 此方法调用超类的noargument构造函数(在任何其他初始化之前),这是在new之后指定Speaker()的原因。

匿名类实例应该能够访问周围范围的局部变量和参数。 但是,实例可能比构思它的方法(由于将实例的引用存储在字段中)更长,并尝试访问方法返回后不再存在的局部变量和参数。

因为Java不允许这种非法访问,这很可能会使Java虚拟机(JVM)崩溃,所以它允许匿名类实例只访问声明为final的局部变量和参数。 在匿名类实例中遇到最终的局部变量/参数名称时,编译器会执行以下两种操作之一:

•如果变量的类型是原始的(例如int或double),则编译器将其名称替换为变量的只读值。

•如果变量的类型是引用(例如java.lang.String),则编译器在类文件中引入合成变量(制造变量)和在合成变量中存储局部变量/参数引用的代码。

清单3-11演示了另一种匿名类声明和实例化。

清单3-11。 声明并实例化实现接口的匿名类

interface Speakable
{
void speak();
}
class ACDemo
{
public static void main(final String[] args)
{
new Speakable()
{
String msg = (args.length == 1) ? args[0] : "nothing to say";
@Override
public void speak()
{
System.out.println(msg);
}
}
.speak();
}
}

清单3-11与清单3-10非常相似。 但是,此列表的匿名类不是子类化Speaker类,而是实现名为Speakable的接口。 除了调用java.lang.Object()(接口没有构造函数)的()方法之外,清单3-11的行为与清单3-10类似。

虽然匿名类没有构造函数,但您可以提供实例初始化程序来处理复杂的初始化。 例如,新的Office(){{addEmployee(new Employee(“John Doe”));}}; 实例化Office的匿名子类,并通过调用Office的addEmployee()方法将一个Employee对象添加到此实例。

为了方便起见,您经常会发现自己正在创建和实例化匿名类。 例如,假设您需要返回具有“.java”后缀的所有文件名的列表。 以下示例显示了匿名类如何简化使用java.io包的File和FilenameFilter类来实现此目标:

String[] list = new File(directory).list(new FilenameFilter()
{
@Override
public boolean accept(File f, String s)
{
return s.endsWith(".java");
}
});

本地类

本地类是在声明局部变量的任何地方声明的类。 此外,它与局部变量具有相同的范围。 与匿名类不同,本地类具有名称并且可以重用。 与匿名类一样,本地类在非静态上下文中使用时只包含实例。

本地类实例可以访问周围范围的局部变量和参数。 但是,必须将访问的局部变量和参数声明为final。 例如,清单3-12的本地类声明访问最终参数和最终局部变量。

清单3-12。 宣布本地课程

class EnclosingClass
{
void m(final int x)
{
final int y = x*2;
class LocalClass
{
int a = x;
int b = y;
}
LocalClass lc = new LocalClass();
System.out.println(lc.a);
System.out.println(lc.b);
}
}

清单3-12声明EnclosingClass及其实例方法m()声明一个名为LocalClass的本地类。 这个本地类声明了一对实例字段(a和b),它们在实例化LocalClass时初始化为最终参数x和最终局部变量y的值:new EnclosingClass()。m(10);,例如。
清单3-13演示了这个本地类。
清单3-13。 展示本地课程

class LCDemo
{
public static void main(String[] args)
{
EnclosingClass ec = new EnclosingClass();
ec.m(10);
}
}

在实例化EnclosingClass之后,清单3-13的main()方法调用m(10)。 被调用的m()方法将此参数乘以2,实例化LocalClass,其()方法将参数和doubled值分配给其实例字段对(代替使用构造函数执行此任务),并输出 LocalClass实例字段。 以下输出结果:

10
20

本地类有助于提高代码清晰度,因为它们可以移动到更接近需要的位置。 例如,清单3-14声明了一个Iterator接口和一个重构的ToDoList类,其iterator()方法将其本地Iter类的实例作为Iterator实例返回(因为Iter实现了Iterator)。

清单3-14。 Iterator接口和重构的ToDoList类

interface Iterator
{
boolean hasMoreElements();
Object nextElement();
}
class ToDoList
{
private ToDo[] toDoList;
private int index = 0;
ToDoList(int size)
{
toDoList = new ToDo[size];
}
Iterator iterator()
{
class Iter implements Iterator
{
int index = 0;
@Override
public boolean hasMoreElements()
{
return index < toDoList.length;
}
@Override
public Object nextElement()
{
return toDoList[index++];
}
}
return new Iter();
}
void add(ToDo item)
{
toDoList[index++] = item;
}
}

清单3-15演示了Iterator,重构的ToDoList类和清单3-7的ToDo类。

清单3-15。 使用可重用的迭代器创建并迭代ToDo实例的ToDoList

class LCDemo
{
public static void main(String[] args)
{
ToDoList toDoList = new ToDoList(5);
toDoList.add(new ToDo("#1", "Do laundry."));
toDoList.add(new ToDo("#2", "Buy groceries."));
toDoList.add(new ToDo("#3", "Vacuum apartment."));

toDoList.add(new ToDo("#4", "Write report."));
toDoList.add(new ToDo("#5", "Wash car."));
Iterator iter = toDoList.iterator();
while (iter.hasMoreElements())
System.out.println(iter.nextElement());
}
}

从iterator()返回的Iterator实例以与添加到列表时相同的顺序返回ToDo项。 虽然只能使用返回的Iterator对象一次,但只要需要新的Iterator对象,就可以调用iterator()。 这个功能比清单3-9中的一次性迭代器有了很大的改进。

类中的接口

接口可以嵌套在类中。 一旦声明,接口就被认为是静态的,即使它没有被声明为静态。 例如,清单3-16声明了一个名为X的封闭类以及两个名为A和B的嵌套静态接口。
清单3-16。 在类中声明一对接口

class X
{
interface A
{
}
static interface B
{
}
}

您将以相同的方式访问清单3-16的接口。 例如,您将指定C类实现X.A {}或D类实现X.B {}。

与嵌套类一样,嵌套接口通过嵌套类实现,有助于实现顶级类体系结构。 总的来说,这些类型是嵌套的,因为它们不能(如清单3- 14的Iter本地类)或者不需要与顶级类出现在同一级别并污染其包
命名空间。


■注意第2章对接口的介绍向您展示了如何在接口体中声明常量和方法头。 您还可以在接口的主体中声明接口和类。 因为没有充分的理由这样做(java.util.Map.Entry,在第5章讨论,是一个例外),最好避免在接口中嵌套接口和/或类。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值