此文章为本系列的第二篇,继续内部类部分,GO!
无底色文字:原文章
有底色文字:个人备注
代码:原文代码+个人测试代码+个人注释
首先把上一篇最后坑填一下,也就是静态内部类在创建时不需要外部类对象引用。
分为以下四类进行验证:
1. 外部类的静态方法创建静态内部类对象(确实不需要外部类对象引用:StatContents statContents = new StatContents();)
2. 外部类的静态方法创建静态内部类对象(确实不需要外部类对象引用:StatContents statContents = new StatContents();)
3. 其他类的静态方法创建静态内部类对象(确实不需要外部类对象引用: Parcel2.StatContents statContents = new Parcel2.StatContents();)
4. 其他类的普通方法创建静态内部类对象(确实不需要外部类对象引用:Parcel2.StatContents statContents = new Parcel2.StatContents();)
以下为测试的代码:
// innerclasses/Parcel2.java
// Returning a reference to an inner class
public class Parcel2 {
/**
* @author: sunzhen
* @date: 2019-08-29 19:10
* @param: null
* @return:
* @throws:
* @description: 测试静态方法使用内部类
*/
public static void test1(){
//外部类的静态方法中,创建内部类对象时,这个变量不需要加外部类名称的前缀(参考TestInnerClazz类的其他类创建内部类的例子)
/*Contents contents = new Contents(); 不能直接创建,除非内部类也为static,或将该方法的static去除*/
// 1. 接收
//Contents contents = new Parcel2().contents();
// 2. 利用.new语法创建内部类对象
Contents contents = new Parcel2().new Contents();
System.out.println(contents.value());
}
/**
* @author: sunzhen
* @date: 2019-08-29 19:10
* @param: null
* @return:
* @throws:
* @description: 测试静态方法使用静态内部类
*/
public static void testStatInner1(){
//与普通方法一样都可以直接创建静态内部类
StatContents statContents = new StatContents();
System.out.println(statContents.value());
}
/**
* @author: sunzhen
* @date: 2019-08-29 19:10
* @param: null
* @return:
* @throws:
* @description: 测试普通方法使用静态内部类
*/
public void testStatInner2(){
//与普通方法一样都可以直接创建静态内部类
StatContents statContents = new StatContents();
System.out.println(statContents.value());
}
static class StatContents {
private int i = 33;
public int value() {
return i; }
}
class Contents {
private int i = 11;
public int value() {
return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() {
return label; }
}
public Destination to(String s) {
//说明普通方法可以直接创建普通内部类对象
return new Destination(s);
}
public Contents contents() {
//说明普通方法可以直接创建普通内部类对象
return new Contents();
}
public void ship(String dest) {
Contents c = contents();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
//测试静态方法使用内部类,不需要在内部类类名加外部类类名前缀
test1();
//测试静态方法使用静态内部类,不需要在内部类类名加外部类类名前缀
testStatInner1();
//测试普通方法使用静态内部类,不需要在内部类类名加外部类类名前缀
new Parcel2().testStatInner2();
}
}
/**
* @author: sunzhen
* @date: 2019/8/29
* @time: 19:22
* @description: 配合Parcel2测试其内部类在该类(TestInnerClazz)中使用方式
*/
public class TestInnerClazz {
/**
* @author: sunzhen
* @date: 2019-08-29 19:28
* @param: null
* @return:
* @throws:
* @description: 其他类静态方法创建Parcel2类的Contents内部类对象。
*/
public static void test01(){
Parcel2 parcel = new Parcel2();
// 1. 接收
//Parcel2.Contents contents = parcel.contents();
// 2. 利用.new语法创建内部类对象
//内部类变量名需要加外部类类名前缀,创建对象需要外部类对象引用。
Parcel2.Contents contents = parcel.new Contents();
System.out.println(contents.value());
}
/**
* @author: sunzhen
* @date: 2019-08-29 19:28
* @param: null
* @return:
* @throws:
* @description: 其他类普通方法创建Parcel2类的Contents内部类对象。
*/
public void test02(){
Parcel2 parcel = new Parcel2();
// 1. 接收
//Parcel2.Contents contents = parcel.contents();
// 2. 利用.new语法创建内部类对象
//内部类变量名需要加外部类类名前缀,创建对象需要外部类对象引用。
Parcel2.Contents contents = parcel.new Contents();
System.out.println(contents.value());
}
/**
* @author: sunzhen
* @date: 2019-08-29 19:28
* @param: null
* @return:
* @throws:
* @description: 其他类静态方法创建Parcel2类的StatContents静态内部类对象。
*/
public static void test03(){
//内部类类名需要加外部类类名前缀,可直接创建内部类对象。
Parcel2.StatContents statContents = new Parcel2.StatContents();
System.out.println(statContents.value());
}
/**
* @author: sunzhen
* @date: 2019-08-29 19:28
* @param: null
* @return:
* @throws:
* @description: 其他类普通方法创建Parcel2类的StatContents静态内部类对象。
*/
public void test04(){
//内部类类名需要加外部类类名前缀,可直接创建内部类对象。
Parcel2.StatContents statContents = new Parcel2.StatContents();
System.out.println(statContents.value());
}
public static void main(String[] args) {
TestInnerClazz innerClazz = new TestInnerClazz();
//其他类静态方法创建Parcel2类的Contents内部类对象。
test01();
//其他类普通方法创建Parcel2类的Contents内部类对象。
innerClazz.test02();
//其他类静态方法创建Parcel2类的StatContents静态内部类对象。
test03();
//其他类普通方法创建Parcel2类的StatContents静态内部类对象。
innerClazz.test04();
}
}
然后开始今天的内容
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。)这是因为此内部类-某个接口的实现-能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。
我们可以创建前一个示例的接口:
// innerclasses/Destination.java
public interface Destination {
String readLabel();
}
// innerclasses/Contents.java
public interface Contents {
int value();
}
现在 Contents 和 Destination 表示客户端程序员可用的接口。记住,接口的所有成员自动被设置为 public。
当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型,看下面的例子:
// innerclasses/TestParcel.java
class Parcel4 {
private class PContents implements Contents {
private int i = 11;
@Override
public int value() {
return i; }
}
protected final class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() {
return label; }
}
public Destination destination(String s) {
return new PDestination(s);
}
public Contents contents() {
return new PContents();
}
}
public class TestParcel {
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Contents c = p.contents();
Destination d = p.destination("Tasmania");
// Illegal -- can't access private class:
//- Parcel4.PContents pc = p.new PContents();
}
}
在 Parcel4 中,内部类 PContents 是 private,所以除了 Parcel4,没有人能访问它。普通(非内部)类的访问权限不能被设为 private 或者 protected;他们只能设置为 public 或 package 访问权限。
PDestination 是 protected,所以只有 Parcel4 及其子类、还有与 Parcel4 同一个包中的类(因为 protected 也给予了包访问权)能访问 PDestination,其他类都不能访问 PDestination,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成 private 内部类(或 protected 内部类,除非是继承自它的子类),因为不能访问其名字,就像在 TestParcel 类中看到的那样。
private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给 Java 编译器提供了生成高效代码的机会。
这节看是看懂了,也实践了,就是觉得实际运用比较少。本来一个单独的类要么是public的要么是deafult的,但是内部类可以private,隐藏内部实现细节,在某些情况下这种代码更加高效优雅吧,以后用到再回来看看
到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。
这么做有两个理由:
在后面的例子中,先前的代码将被修改,以用来实现:
第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部类:
// innerclasses/Parcel5.java
// Nesting a class within a method
public class Parcel5 {
public Destination destination(String s) {
final class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() {
return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
}
PDestination 类是 destination() 方法的一部分,而不是 Parcel5 的一部分。所以,在 destination() 之外不能访问 PDestination,注意出现在 return 语句中的向上转型-返回的是 Destination 的引用,它是 PDestination 的基类。当然,在 destination() 中定义了内部类 PDestination,并不意味着一旦 dest() 方法执行完毕,PDestination 就不可用了。
你可以在同一个子目录下的任意类中对某个内部类使用类标识符 PDestination,这并不会有命名冲突。
下面的例子展示了如何在任意的作用域内嵌入一个内部类:
// innerclasses/Parcel6.java
// Nesting a class within a scope
public class Parcel6 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() {
return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// Can't use it here! Out of scope:
//- TrackingSlip ts = new TrackingSlip("x");
}
public void track() {
internalTracking(true); }
public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
}
TrackingSlip 类被嵌入在 if 语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义 Trackingslip 的作用域之外,它是不可用的,除此之外,它与普通的类一样。
这节主要就是讲了方法中也可以具有类,就是方法内部类(也称:局部内部类),然后其作用域也只在方法中。
下面的例子看起来有点奇怪:
// innerclasses/Parcel7.java
// Returning an instance of an anonymous inner class
public class Parcel7 {
public Contents contents() {
return new Contents() {
// Insert class definition
private int i = 11;
@Override
public int value() {
return i; }
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
contents() 方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个 Contents 对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义。
这种奇怪的语法指的是:“创建一个继承自 Contents 的匿名类的对象。”通过 new 表达式返回的引用被自动向上转型为对 Contents 的引用。上述匿名内部类的语法是下述形式的简化形式:
// innerclasses/Parcel7b.java
// Expanded version of Parcel7.java
public class Parcel7b {
class MyContents implements Contents {
private int i = 11;
@Override
public int value() {
return i; }
}
public Contents contents() {
return new MyContents();
}
public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
}
在这个匿名内部类中,使用了默认的构造器来生成 Contents。下面的代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办:
// innerclasses/Parcel8.java
// Calling the base-class constructor
public class Parcel8 {
public Wrapping wrapping(int x) {
// Base constructor call:
return new Wrapping(x) {
// [1]
@Override
public int value() {
return super.value() * 47;
}
}; // [2]
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
}
[1] 将合适的参数传递给基类的构造器。
[2] 在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此,这与别的地方使用的分号是一致的。
尽管 Wrapping 只是一个具有具体实现的普通类,但它还是被导出类当作公共“接口”来使用。
// innerclasses/Wrapping.java
public class Wrapping {
private int i;
public Wrapping(int x) {
i = x; }
public int value() {
return i; }
}
为了多样性,Wrapping 拥有一个要求传递一个参数的构造器。
在匿名类中定义字段时,还能够对其执行初始化操作:
// innerclasses/Parcel9.java
public class Parcel9 {
// Argument must be final or "effectively final"
// to use within the anonymous inner class:
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 final 的(也就是说,它在初始化后不会改变,所以可以被当作 final),就像你在 destination() 的参数中看到的那样。这里省略掉 final 也没问题,但是通常最好加上 final 作为一种暗示。
如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:
// innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class
abstract class Base {
Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println(
"Inside instance initializer"); }
@Override
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
输出为:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
在此例中,不要求变量一定是 final 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。
下例是带实例初始化的"parcel"形式。注意 destination() 的参数必须是 final 的,因为它们是在匿名类内部使用的(译者注:即使不加 final, Java 8 的编译器也会为我们自动加上 final,以保证数据的一致性)。
// innerclasses/Parcel10.java
// Using "instance initialization" to perform
// construction on an anonymous inner class
public class Parcel10 {
public Destination destination(final String dest, final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
@Override
public String readLabel() {
return label; }
};
}
public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Tasmania", 101.395F);
}
}
输出为:
Over budget!
在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是 if 语句)。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。
匿名内部类相较于其他用法还是比较常见的,适合于只用到一次的类。如果定义一个匿名内部类,并想使用一个在其外部定义的对象,那么参数需要时final的,不过 Java 8 可以不加,因为 Java 8 的编译器会为其自动加上 final,以保证数据的一致性