温馨提示:本系列博文(含示例代码)已经同步到 GitHub,地址为「java-skills」,欢迎感兴趣的童鞋
Star
、Fork
,纠错。
在 Java 的语言体系中,类和接口是两种常见的定义对象的形式,内部类则是类的一种特殊形式。接口和内部类为我们提供了一种将抽象定义与具体实现相分离的更加结构化的方法。
我们通过方法来描述对象的行为,一般来说,它含有方法体并且在方法体中描述了对象具体的行为细节。但是,在 Java 中还提供了一种不完整的方法机制,称之为抽象方法。与一般的方法相比,抽象方法仅有声明而没有方法体。例如,
abstract void methodName();
包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,则该类必须被限制为抽象的,即用abstract
关键字修饰类。否则的话,编译器就会报错!
如果我们细读上述的文字,会发现这样的一个细节,那就是:只要类中含有至少一个抽象方法,则该类就是抽象类;并没有限制抽象类中是否可以含有非抽象的方法,而且实际上抽象类中是允许含有非抽象方法的。在接口中,则不允许含有非抽象的方法!因此与抽象类相比,接口可以称之为“更加纯粹的抽象类”。接口也可以包含域,只不过这些域隐式地、自动的是static
和final
的。也正是由于这个原因,接口是一种很便捷的用来创建常量组的工具。
我们使用接口的原因有两个,分别为:
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。
此外,我们可以在类中定义接口,也可以在接口中定义接口,简而言之,我们可以进行接口的嵌套。不过在这里有一点需要我们注意,那就是:当我们实现某个接口的时候,并不需要实现嵌套在其内部的任何接口,而且被**private
**修饰的接口也不能在定义它的类之外被实现。当然,我们也可以指定所要实现的定义在接口内部的接口,具体格式如下:
ClassName implements OuterInterfaceName.InnerInterfaceName
在 GitHub 的「java-skills」项目中提供了测试的代码,感兴趣的同学可以自行下载体验。
内部类,其实就是将一个类定义在另一个类的内部,它允许我们将一些逻辑相关的类组织在一起。当生成一个内部类对象的时候,此对象与创建它的外围对象之间就建立了一种联系,它不需要任何特殊条件就能够访问外围对象的所有成员。此外,内部类还拥有其外围类的所有元素的访问权。
package com.hit.thought.chapter10;
/**
* author:Charies Gavin
* date:2018/3/2,9:23
* https:github.com/guobinhit
* description:测试内部类
*/
public class OuterClass {
OuterClass() {
System.out.println("Congratulations, Create OuterClass Completed!");
}
class InnerClass {
InnerClass() {
System.out.println("Congratulations, Create InnerClass Completed!");
}
public void sayHi() {
System.out.println("Hi!");
}
/**
* 在内部类中获取外部类对象,形式为 OuterClassName.this
*
* @return
*/
public OuterClass getOuterClass() {
return OuterClass.this;
}
}
/**
* 非静态方法
*/
public void nonStaticMethod() {
InnerClass innerClass = new InnerClass();
innerClass.sayHi();
}
/**
* 获取内部类的实例方法
*
* @return
*/
public InnerClass getInnerClass() {
return new InnerClass();
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.nonStaticMethod();
InnerClass innerClass = outerClass.getInnerClass();
innerClass.sayHi();
// 获取外部类对象的引用,并没有新建对象
innerClass.getOuterClass();
// 利用外部类对象直接创建内部类对象,使用 .new 语法
InnerClass oi = outerClass.new InnerClass();
}
}
如上面代码所示,
.
和this
;.new
语法。在这里,如果我们想要直接创建内部类对象的话,必须先创建外部类对象,然后通过外部类对象来创建内部类对象,在拥有外部类对象之前是不可能创建内部类对象的。但是,如果我们创建的时嵌套类(静态内部类),则不需要对外部类对象的引用。除了上面介绍的内部类之后,还有一种没有名字的内部类,我们称之为匿名内部类。参考如下代码:
public class OuterClass {
OuterClass() {
System.out.println("Congratulations, Create OuterClass Completed!");
}
/**
* 非静态内部类
*/
class InnerClass {
InnerClass() {
System.out.println("Congratulations, Create InnerClass Completed!");
}
public void sayHi() {
System.out.println("Hi!");
}
/**
* 在内部类中获取外部类对象,形式为 OuterClassName.this
*
* @return
*/
public OuterClass getOuterClass() {
return OuterClass.this;
}
}
/**
* 匿名类
*
* @return
*/
public InnerClass anonymousInnerClass() {
return new InnerClass() {
public void sayHi() {
System.out.println("Hello");
}
};
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.anonymousInnerClass().sayHi();
}
}
如上所示,我们在anonymousInnerClass()
中创建了一个匿名内部类,并在其中定义了一个sayHi()
方法。在这里,细心的同学可能会意识到:匿名内部类,实际上就是继承了方法返回(类)类型的类而已。既然继承了父类,自然也就可以覆盖父类中的方法,因此通过anonymousInnerClass()
方法调用sayHi()
方法的时候,返回的是子类(匿名内部类)中的版本。
在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的表达式的结束,只不过这个表达式恰巧包含了匿名内部类罢了。如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是**final
**的。例如,
/**
* 在匿名内部类中引用外部参数时,必须将参数定义为 final 类型
*
* @return
*/
public InnerClass anonymousInnerClass2(final String name) {
return new InnerClass() {
public void sayHi() {
System.out.println("Hello " + name);
}
};
}
如果不加final
的话,我们就会得到一条编译器给出的错误提示:
此外,在匿名内部类中不可能有命名的构造器,因为它根本就没有名字!对于匿名内部类而言,实例初始化的实际效果就是构造器。匿名内部类既可以扩展类,也可以实现接口,但不能两者兼备。
如果不需要内部类对象与外围类对象之间有联系,那么可以将内部类声明为static
类型,这就是我们所说的嵌套类。我们知道,普通的内部类对象隐式的保持了一个指向创建它的外围类对象的引用,但是对于嵌套类而言,并非如此:
普通内部类和嵌套类还有一些区别,例如:
this
关键字可以引用到外围类对象,但是嵌套类不可以;static
数据和static
字段,也不能包含嵌套类,但是嵌套类可以。正常情况下,不能在接口内部放置任何代码,但是嵌套类可以作为接口的一部分。代码示例如下,
public interface ClassInInterface {
void interfaceMethod();
class InnerClassInInterface{
void sayHello(){
System.out.println("Hello World!");
}
public static void main(String[] args) {
new InnerClassInInterface().sayHello();
}
}
}
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。而且,内部类允许继承多个类或者抽象类。当然,我们也可以继承内部类,不过在继承内部类的时候,那个指向外围类的“隐式的”引用必须被初始化,而在导出类中不再存在可以连接的默认对象。此外,在继承内部类的时候,还有一点需要特别注意,那就是:我们不能在导出类中使用默认的构造器(不能编译),需要新建一个带有所继承的内部类所在外部类参数类型的构造器并显示调用外部类构造器的构造器。好吧,我承认,上面的话说完之后我自己都有点晕,给个例子,相信大家看过之后就明白了:
class ContainInner {
class Inner {
public void sayName() {
// 获取类名
System.out.println("调用此方法的类名为:" + this.getClass().getSimpleName());
}
}
}
public class InheritInnerClass extends ContainInner.Inner {
/**
* 继承内部类,必须调用含参构造器,且参数类型为外部类类型
*
* @param ci
*/
InheritInnerClass(ContainInner ci) {
// 必须显示调用外部类构造器
ci.super();
}
public static void main(String[] args) {
ContainInner ci = new ContainInner();
InheritInnerClass iic = new InheritInnerClass(ci);
iic.sayName();
}
}
如果上面代码所示,InheritInnerClass
继承了内部类Inner
,并没有继承外部类ContainInner
,但是我们必须在InheritInnerClass
的中新建一个以ContainInner
类型为参数的构造器,且必须使用super()
函数显示调用ContainInner
的构造器。否则的话,代码将不能通过编译器。
我们也可以在代码块里面建立内部类,比较典型的是在方法里面建立内部类,称之为局部内部类。局部内部类和匿名内部类具有相同的行为和能力,两者唯一的区别或许就是局部内部类比匿名内部类多个名字啦!也正是基于此,使用局部内部类而不是匿名类内部类的理由是:
此外,内部类编译后产生的.class
文件的命名规则也是有严格规定的,格式为OuterClassName$InnerClassName.class
,如果内部类是匿名的,编译器就会简单地产生一个数字作为其标识符,并替换上述格式中的InnerClassName
位置。
———— ☆☆☆ —— 返回 -> 那些年,关于 Java 的那些事儿 <- 目录 —— ☆☆☆ ————
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。