java嵌套静态类和内部类
嵌套类是声明为其他类或作用域成员的类。 嵌套类是更好地组织代码的一种方法。 例如,假设您有一个非嵌套类(也称为顶级类 ), 该类将对象存储在可调整大小的数组中,然后是返回每个对象的迭代器类。 您可以声明迭代器类为可调整大小的数组集合类的成员,而不是污染顶级类的名称空间。 之所以可行,是因为两者密切相关。
在Java中,嵌套类分为静态成员类或内部类 。 内部类是非静态成员类,本地类或匿名类。 在本教程中,您将学习如何在Java代码中使用静态成员类和内部类型的三种类型。
避免嵌套类中的内存泄漏
另请参阅与本教程相关的Java技巧,您将在这里学习为什么嵌套类易受内存泄漏的影响 。
Java中的静态类
在我的Java 101教程Java中的 类和对象中 ,您学习了如何将静态字段和静态方法声明为类的成员。 在Java中的类和对象初始化中 ,您学习了如何将静态初始化器声明为类的成员。 现在,您将学习如何声明静态类 。 正式称为静态成员类 ,这些是嵌套类,您可以使用static
关键字在与其他静态实体相同的级别上声明。 这是静态成员类声明的示例:
class C
{
static int f;
static void m() {}
static
{
f = 2;
}
static class D
{
// members
}
}
本示例介绍了具有静态字段f
顶级类C
,静态方法m()
,静态初始化器和静态成员类D
请注意, D
是C
的成员。 静态字段f
,静态方法m()
和静态初始化器也是C
成员。 由于所有这些元素都属于C
类,因此被称为封闭类 。 D
类被称为封闭类 。
机箱和访问规则
尽管它是封闭的,但是静态成员类无法访问封闭类的实例字段并调用其实例方法。 但是,它可以访问封闭类的静态字段并调用其静态方法,甚至是那些声明为private
成员。 为了演示,清单1宣称的EnclosingClass
与嵌套SMClass
。
清单1.声明一个静态成员类(EnclosingClass.java,版本1)
class EnclosingClass
{
private static String s;
private static void m1()
{
System.out.println(s);
}
static void m2()
{
SMClass.accessEnclosingClass();
}
static class SMClass
{
static void accessEnclosingClass()
{
s = "Called from SMClass's accessEnclosingClass() method";
m1();
}
void accessEnclosingClass2()
{
m2();
}
}
}
清单1声明了一个名为EnclosingClass
的顶级类,其中包含类字段s
,类方法m1()
和m2()
以及静态成员类SMClass
。 SMClass
声明类方法accessEnclosingClass()
和实例方法accessEnclosingClass2()
。 请注意以下几点:
-
m2()
对SMClass
的accessEnclosingClass()
方法的调用需要SMClass.
前缀,因为accessEnclosingClass()
被声明为static
。 -
accessEnclosingClass()
能够访问EnclosingClass
的s
场并调用它的m1()
方法,即使双方已宣布private
。
清单2将源代码提供给SMCDemo
应用程序类,该类演示了如何调用SMClass
的accessEnclosingClass()
方法。 它还演示了如何实例化SMClass
并调用其accessEnclosingClass2()
实例方法。
清单2.调用静态成员类的方法(SMCDemo.java)
public class SMCDemo
{
public static void main(String[] args)
{
EnclosingClass.SMClass.accessEnclosingClass();
EnclosingClass.SMClass smc = new EnclosingClass.SMClass();
smc.accessEnclosingClass2();
}
}
如清单2所示,如果要从封闭的类中调用顶级类的方法,则必须在封闭的类的名称之前加上封闭的类的名称。 同样,为了实例化一个封闭的类,您必须在该类的名称之前加上其封闭类的名称。 然后,您可以按常规方式调用实例方法。
编译清单1和清单2,如下所示:
javac *.java
当您编译包含静态成员类的封闭类时,编译器会为该静态成员类创建一个类文件,该类文件的名称由其封闭类的名称,美元符号字符和静态成员类的名称组成。 在这种情况下,在编译结果EnclosingClass$SMCClass.class
和EnclosingClass.class
。
运行应用程序,如下所示:
java SMCDemo
您应该观察以下输出:
Called from SMClass's accessEnclosingClass() method
Called from SMClass's accessEnclosingClass() method
示例:静态类和Java 2D
Java的标准类库是类文件的运行时库,该文件存储已编译的类和其他引用类型。 该库包含许多静态成员类的示例,其中一些可以在java.awt.geom
包中的Java 2D几何形状类中找到。 (您将在下一个Java 101教程中了解软件包。)
在java.awt.geom
中找到的Ellipse2D
类描述了一个椭圆,该椭圆由框架矩形根据(x,y)左上角以及宽度和高度范围定义。 以下代码片段显示了此类的体系结构基于Float
和Double
静态成员类,它们都是Ellipse2D
子类:
public abstract class Ellipse2D extends RectangularShape
{
public static class Float extends Ellipse2D implements Serializable
{
public float x, y, width, height;
public Float()
{
}
public Float(float x, float y, float w, float h)
{
setFrame(x, y, w, h);
}
public double getX()
{
return (double) x;
}
// additional instance methods
}
public static class Double extends Ellipse2D implements Serializable
{
public double x, y, width, height;
public Double()
{
}
public Double(double x, double y, double w, double h)
{
setFrame(x, y, w, h);
}
public double getX()
{
return x;
}
// additional instance methods
}
public boolean contains(double x, double y)
{
// ...
}
// additional instance methods shared by Float, Double, and other
// Ellipse2D subclasses
}
Float
和Double
类扩展了Ellipse2D
,提供了浮点和双精度浮点Ellipse2D
实现。 开发人员使用Float
减少内存消耗,特别是因为您可能需要成千上万个这样的对象来构建单个2D场景。 当需要更高的精度时,我们使用Double
。
您不能实例化抽象的Ellipse2D
类,但可以实例化Float
或Double
。 您还可以扩展Ellipse2D
来描述基于椭圆的自定义形状。
例如,假设您要引入一个Circle2D
类,该类在java.awt.geom
包中不存在。 以下代码片段显示了如何使用浮点实现创建Ellipse2D
对象:
Ellipse2D e2d = new Ellipse2D.Float(10.0f, 10.0f, 20.0f, 30.0f);
下一个代码片段显示了如何创建具有双精度浮点实现的Ellipse2D
对象:
Ellipse2D e2d = new Ellipse2D.Double(10.0, 10.0, 20.0, 30.0);
现在,您可以通过在返回的Ellipse2D
引用(例如e2d.getX()
)上调用方法来调用Float
或Double
声明的任何方法。 以相同的方式,您可以调用Float
和Double
通用的任何方法,这些方法在Ellipse2D
中声明。 一个例子是:
e2d.contains(2.0, 3.0)
这样就完成了对静态成员类的介绍。 接下来,我们将研究内部类,它们是非静态成员类,本地类或匿名类。 您将学习如何使用所有三种内部类类型。
内部类,类型1:非静态成员类
您以前在Java 101系列中已经学习了如何将非静态(实例)字段,方法和构造函数声明为class的成员 。 您还可以声明非静态成员类 ,它们是与实例字段,方法和构造函数在同一级别声明的嵌套非静态类。 考虑以下示例:
class C
{
int f;
void m() {}
C()
{
f = 2;
}
class D
{
// members
}
}
在这里,我们介绍具有实例字段f
,实例方法m()
,构造函数和非静态成员类D
顶级类C
所有这些实体都是类C
成员,该类将它们包围起来。 但是,与前面的示例不同,这些实例实体与C
实例关联,而不与C
类本身关联。
非静态成员类的每个实例都与其封闭类的实例隐式关联。 非静态成员类的实例方法可以调用封闭类的实例方法并访问其实例字段。 为了证明这一点接入,清单3中声明的EnclosingClass
与嵌套NSMClass
。
清单3.用嵌套的非静态成员类声明一个包含类(EnclosingClass.java,版本2)
class EnclosingClass
{
private String s;
private void m()
{
System.out.println(s);
}
class NSMClass
{
void accessEnclosingClass()
{
s = "Called from NSMClass's accessEnclosingClass() method";
m();
}
}
}
清单3声明了一个名为一个顶层类EnclosingClass
与实例字段s
,实例方法m()
和非静态成员类NSMClass
。 此外, NSMClass
声明实例方法accessEnclosingClass()
。
由于accessEnclosingClass()
是非静态的, NSMClass
必须先实例化NSMClass
然后才能调用此方法。 该实例化必须通过EnclosingClass
一个实例进行,如清单4所示。
清单4. NSMCDemo.java
public class NSMCDemo
{
public static void main(String[] args)
{
EnclosingClass ec = new EnclosingClass();
ec.new NSMClass().accessEnclosingClass();
}
}
清单4的main()
方法首先实例化EnclosingClass
并将其引用保存在局部变量ec
。 在main()
方法然后使用EnclosingClass
参考作为前缀到new
运营商,以实例化NSMClass
。 然后,使用NSMClass
引用来调用accessEnclosingClass()
。
我应该使用“ new”来引用封闭类吗?
很少使用new
前缀来引用封闭类。 取而代之的是,您通常会从封闭类的构造函数或其封闭类的实例方法中调用封闭类的构造函数。
清单3和清单4如下编译:
javac *.java
当您编译包含非静态成员类的封闭类时,编译器会为非静态成员类创建一个类文件,该类文件的名称由其封闭类的名称,美元符号字符和非静态成员类的名称组成名称。 在这种情况下,在编译结果EnclosingClass$NSMCClass.class
和EnclosingClass.class
。
运行应用程序,如下所示:
java NSMCDemo
您应该观察以下输出:
Called from NSMClass's accessEnclosingClass() method
何时(以及如何)限定“ this”
封闭类的代码可以通过使用封闭类的名称和成员访问运算符( .
)限定保留字this
来获取对其封闭类实例的引用。 例如,如果内码accessEnclosingClass()
需要获得到其基准EnclosingClass
例如,它将指定EnclosingClass.this
。 因为编译器会生成代码来完成此任务,所以很少指定此前缀。
示例:HashMap中的非静态成员类
标准类库包括非静态成员类和静态成员类。 在此示例中,我们将查看HashMap
类,该类是java.util
包中Java Collections Framework的一部分。 HashMap
描述了基于哈希表的地图实现,其中包括几个非静态成员类。
例如, KeySet
非静态成员类描述了映射中包含的键的基于集合的视图 。 以下代码片段将封闭的KeySet
类与其HashMap
封闭类相关联:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
// various members
final class KeySet extends AbstractSet<K>
{
// various members
}
// various members
}
<K,V>
和<K>
语法是泛型的示例, 泛型是帮助编译器强制执行类型安全的一组相关语言功能。 我将在即将到来的Java 101教程中介绍泛型。 现在,您只需要知道这些语法可帮助编译器强制执行可存储在映射和键集中的键对象的类型以及可存储在映射中的值对象的类型。
HashMap
提供了一个keySet()
方法,该方法在必要时实例化KeySet
并返回此实例或缓存的实例。 这是完整的方法:
翻译自: https://www.infoworld.com/article/2074000/core-java-classes-within-classes.html
java嵌套静态类和内部类