抽象类
抽象方法
语法形式:
abstract void f();
从语法中可以看出,抽象方法是仅有声明而没有方法体的不完整方法。
抽象类
包含抽象方法的类就是抽象类。如果一个类包含一个或多个抽象的方法,那么该类必须被限定为抽象的,否则编译器就会报错。
也可以这么说,抽象类是不完整的类,因此当我们试图产生抽象类的对象时,编译器就会报错。这样,编译器会确保抽象类的纯粹性,从而使用者不用担心会误用抽象类。
可以从一个抽象类中继承,并创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这么做,那么导出类便也是抽象类,且编译器会强制我们使用 abstract 关键字来限制这个类。
也可以创建没有任何抽象方法的抽象类。例如:一个类,让其包含任何 abstract 方法都显得没有实际意义,而且我们也想要阻止产生这个类的任何对象。
public abstract class AbstractClassTest {
public void f() {
}
public void abs() {
}
}
接口
interface 关键字使抽象的概念更向前迈进了一步。interface 关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。interface 关键字允许创建者确定方法名、参数列表和返回类型,但是没与任何方法体。接口只提供了形式,而未提供任何具体实现。因此,接口被用来建立类于类之间的协议。同时,接口也不仅仅是一个极度抽象的类,它还允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
可以选择在接口中显示地将方法声明为 public 的,但即使你不这么做,它们也是public的。因此,当要实现一个接口时,在接口中被定义的方法必须被定义为是public的。
通过继承来扩展接口
通过继承,可以很容易地在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。
接口中的字段
接口中的任何字段都自动是 public static final 的,所以接口就成为了一种很便捷的用来创建常量组的工具。Java中标识具有常量初始化值的 static final 时,会使用大写字母的风格。
在接口中定义的字段不能是 空 final,但是可以被非常量表达式初始化。例如:
public interface Lethal { void kill(); Random RAN = new Random(); int RAN_INT = RAN.nextInt(100); }
内部类
将一个类的定义放在另一个类的定义内部,这就是内部类。
为什么需要内部类?
- 内部类提供了某种进入其外围类的窗口,使得内部类的代码操作创建它的外围类的对象。
- 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的详细相互独立。
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
- 创建内部类对象的时刻并不依赖于外围类对象的创建。
- 内部类并没有令人迷惑的 is - a 关系,它就是一个独立地实体。
外部类和内部类访问权限
在Java中,外部类可以访问内部类的所有成员变量和成员方法,包括private,protected。
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员(方法和字段),而不需要任何条件。此外,内部类还拥有其外围类的所有元素的访问权。
内部类自动拥有对其外围类所有成员的访问权,这是如何做到的呢?
当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密的捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。内部类的对象只能与其外围类的相关联的情况下才能被创建(就像你看到的,在内部类是非static类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。
使用.this 与 .new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。
public class DotThis {
public void f() {
System.out.println("DotThis.f()");
}
public class Inner {
public DotThis outer() {
return DotThis.this;
}
}
public Inner inner() {
return new Inner();
}
}
有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这时需要使用 .new 语法。
public class DotNew {
public class Inner{}
public static void main(String[] args){
DotNew dn =new DotNew();
DotNew.Inner dni=dn.new Inner();
}
}
从中可以看出,要想创建内部类的对象,必须使用外部类的对象来创建该内部类对象,这样,同时也解决了内部类名字作用域的问题。
在拥有外部类对象之前是不可能创建内部类对象的,这是因为内部类对象会暗暗的连接到创建它的外部类对象上。但是,如果你创建的是 嵌套类(静态内部类),那么它就不需要持有对外部类对象的引用。
方法和作用域内的内部类
可以在一个方法里面或者在任意的作用域内定义内部类,称为方法和作用域内的内部类。
为什么需要方法或作用域内的内部类
- 实现了某类型的接口,于是可以创建并返回对其的引用。
- 需要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
方法内的内部类
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination {
private String label;
public PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() {
return label;
}
}
return new PDestination(s);
}
}
从示例中可以看出,
PDestination类是destination方法的一部分,而不是Parcel5的一部分。所以在destination之外不能访问PDestination。
作用域内的内部类
public class Parcel6 {
private void internalTracking(boolean b) {
if (b) {
class TrackingSlip {
private String id;
public TrackingSlip(String s) {
id = s;
}
String getSlip() {
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
}
public void track() {
internalTracking(true);
}
}
匿名内部类
所谓匿名内部类,就是没有名字的内部类。看起来似乎是正要创建一个对象,但是然后却说:等一等,我想在这里插入一个类的定义。
public class Parce17 {
public Contents contents() {
return new Contents() {
private int i = 11;
@Override
public int value() {
return i;
}
};
}
}
在匿名内部类末尾的分号,并不是用来标记此内部类结束的,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。如果传递给匿名内部类的参数,不在匿名内部类中使用,参数可以不必是final的。如果你忘记了,将会得到一个编译时错误信息。例如:
public class Parce19 {
public Destination destination( final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
}
如果想在匿名内部类中做一些类似构造起的行为,该如何实现呢? 在匿名内部类中不可能有命名构造器(因为它根本就没有名字),但是通过 实例初始化,就能够达到为匿名内部类构建一个构造器的效果。例如:
public abstract class Base {
public 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 initilizer");
}
@Override
public void f() {
System.out.println("In annoymous f()");
}
};
}
}
//客户端调用
Base base= AnonymousConstructor.getBase(47);
base.f();
// 输出结果
Base constructor . i =47
Inside instance initilizer
In annoymous f()
静态内部类(嵌套类)
如果不需要内部类对象与其外围类对象之间有联系,那么就可以将内部类声明为static。这通常称为嵌套类。普通的内部类对象隐式的保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样的了,意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象。
- 不能从嵌套类的对象中访问非静态的外围类对象。
- 普通的内部类不能有
static 数据和static字段,也不能包含嵌套类。 - 嵌套类可以包含
static 数据和static字段,也不能包含嵌套类。
接口内部的类
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。因为放到接口中的任何类都自动地是 public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。甚至可以在内部类中实现其外围接口。
如果你想要创建某些公共的代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。
嵌套类还有一个好处,就是可以使用嵌套类来放置测试代码。
例如:
public class TestBed {
public void f() {
System.out.println("f()");
}
public static class Tester {
public static void main(String[] args) {
TestBed t = new TestBed();
t.f();
}
}
}