内部类简单来说就是定义在一个类内部的类。一直很难理解为什么要使用内部类,对内部类的理解始终停留在表明。今天详细学习了Java内部类的机制,总结下内部类的使用。归纳大纲如下:
1. 内部类的基础结构
2. 内部类的优点和使用场景
3. 内部类的分类
4. 内部类的继承
若有不正之处,请批评指教,共同成长!请尊重作者劳动成果,转载请标明原文链接
package c10;
public class Parcel1 {
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 void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
Parcel1.Contents contents = p.new Contents();
Parcel1.Destination dest = p.new Destination("Tasmania");
}
}
输出结果:Tasmania
内部类其实是一个编译时的概念(内部类与普通类的不同体现在编译上),如上例所示,编译完成后,分别生成三个class文件:Parcel1.class, Parcel1$Contents.class, Destination$Contents.class。
.class
文件包含了如何创建该类型对象的全部信息。内部类生成的.class
文件有严格的规则:外部类名称+$+内部类名称
。如果是匿名内部类,编译器会简单地产生一个数字作为其表示符,如Parcel1$1.class
.
Thinking in Java中通过一个章节详细讨论了为什么需要内部类,可能是因为笔者的理解能力有限,直到今天也无法明确体会作者的意思,总结起来有以下这些:
1. 内部类最吸引人的原因是:每个内部类都能独立继承一个(接口或类)的实现,所以无论外围类是否已经继承了某个(接口或类)实现,对于内部类都没有影响
2. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象的信息相互独立
3. 单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或集成同一个类
4. 内部类并没有令人迷惑的”is-a”关系,是一个独立的实体
也就是说通过内部类可以变相的实现类的多重继承(我们知道Java中只能引用多个接口,而不能继承多个类)。比如这样:
//基类A、B、C
class A {}
abstract class B {}
class C {}
//派生类通过内部类同时继承ABC
class Z extends A {
class Z1 extends B{
C makeC() { return new C(){}; }
}
}
也看了网上各位大神的文章,总结归纳,自己对为什么使用内部类的理解是这样的:
使用内部类会破坏良好的代码结构(第一次看到会觉得怪怪的),但为类的设计者提供了一种途径来隐藏类的实现细节(这些往往是客户端程序员所不关注的),同时也是代码变的更加灵活。
笔者认为内部类之所以很难理解,正是因为语法覆盖了大量难以理解的技术(如果都像基础内部类那样,就没有多少意思了)。内部类可以分为四种:成员内部类,局部内部类,嵌套类,匿名内部类。
成员内部类拥有对外部类所有元素的访问权。
在成员内部类要引用外部类对象时,使用outer.this
来表示外部类对象;
而需要创建内部类对象,可以使用outer.inner obj = outer.new inner();
(注意,在拥有外部类对象之前是不可能创建内部类对象的,除非你创建的是嵌套类)
举个例子:
package c10;
public class Parcel {
private int num = 11;
class Contents {
private int num = 12;
public void print() {
int num = 13;
System.out.println("局部变量:" + num);
System.out.println("内部局部变量:" + this.num);
System.out.println("外部局部变量:" + Parcel.this.num);
}
}
public static void main(String[] args) {
Parcel p = new Parcel();
Parcel.Contents c = p.new Contents();
c.print();
}
}
局部变量:13
内部局部变量:12
外部局部变量:11
当你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但又不希望这个类是公共可用的时,可以通过以下方式实现:
定义在方法中的内部类:
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() {
return label;
}
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.destination("Tasmania");
}
}
Tasmania
PDestination
类是destination()
方法的一部分,在之外不能被访问。注意return
语句中的向上转型,返回的是Destination
的引用,它是PDestination
的基类。
定义在作用域中的内部类:
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();
}
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
p.track();
}
}
一个匿名内部类的例子如下,匿名类是内部类比较常用的方式,简化了代码,更加灵活:
package c10;
//注释后,编译报错:Contents cannot be resolved to a type
//interface Contents { }
public class Parcel7 {
public Contents contents() {
return new Contents() {
private int i = 11;
public int value(){ return i; }
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
需要注意:
1. new匿名类前,这个类需要定义,否则编译报错;
2. 当所在的方法的形参需要被内部类里面使用时,该形参必须为final,否则编译报错,如下例所示:
package c10;
interface Destination {}
public class Parcel10 {
public Destination destination(
final String dest,final float price) {
return new Destination() {
private int cost;
{
cost = Math.round(price);
if( cost > 100 ) {
System.out.println("Over budget");
}
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Nanjing", 101.396F);
}
}
通过匿名内部类,可以写出一个完美的工厂模式:
package c10;
interface Service {
void method1();
void method2();
}
interface ServiceFacotry {
Service getService();
}
class Implementation1 implements Service {
private Implementation1() {}
@Override
public void method1() {
System.out.println("Implementation1 method1");
}
@Override
public void method2() {
System.out.println("Implementation1 method2");
}
public static ServiceFacotry factory =
new ServiceFacotry() {
@Override
public Service getService() {
return new Implementation1();
}
};
}
class Implementation2 implements Service {
private Implementation2() {}
@Override
public void method1() {
System.out.println("Implementation2 method1");
}
@Override
public void method2() {
System.out.println("Implementation2 method2");
}
public static ServiceFacotry factory =
new ServiceFacotry() {
@Override
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFacotry fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(Implementation1.factory);
serviceConsumer(Implementation2.factory);
}
}
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为static。嵌套类意味着:
1. 要创建嵌套类的对象,并不需要先创建外部类的对象
2. 不能从嵌套类的对象中访问非静态的外部类对象
3. 嵌套类和普通的内部类还有一个区别:普通内部类不能有static
数据和static
属性,也不能包含嵌套类,但嵌套类可以。而嵌套类不能声明为private
,一般声明为public
,方便调用。
package c10;
public class Parcel11 {
private static int age = 12;
static class Contents {
public void print() {
System.out.println(age);
}
}
public static void main(String[] args) {
Contents c = new Contents();
c.print();
}
}
12
内部类的继承,是指内部类被继承,普通类extends
内部类。而这时候代码上要有点特别处理,具体看以下例子:
public class InheritInner extends WithInner.Inner {
// InheritInner() 是不能通过编译的,一定要加上形参
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}
class WithInner {
class Inner {
}
}
可以看到子类的构造函数里面要使用父类的外部类对象.super()
;而这个对象需要从外面创建并传给形参。