面向对象基础四
抽象类
在实际设计中,有这样的需要,就是某个基类不允许创建对象。我们可以在定义该类时,在前面加上 abstract 关键字,该类不能被实例化,如果要创建抽象类的对象,编译器会报错。
抽象类也有构造方法,因为其子类会调用父类的构造方法,该构造方法在子类创建对象时被调用。
抽象方法
在定义方法时,如果在前面加上abstract关键字,这时该方法没有方法体,这种方法称为抽象方法。
例如:
public abstract void m1();
抽象方法只能出现在抽象类中,子类继承父类时,如果子类被定义为非抽象类,则在子类中必须实现父类继承来的抽象方法,除非它也被定义为抽象类。
抽象类体中,可以包含抽象方法,也可以不包含抽象方法。但类体中包含抽象方法的类必须要声明为抽象类。
不管是抽象类还是抽象方法都不能被final修饰,这是因为final类不能被继承,final方法不能被复写,而抽象类需要被继承,抽象方法需要被复写,很显然这两者是冲突的。
接口(interface)
接口也是一类引用类型,interface 比抽象类更进一步,在interface中所有的方法均不能有方法体。interface 非常有用,因为在Java中类不允许多重继承,但是某个类可以实现多个interface,接口和接口之间可以多继承。
接口的特点
- 在抽象的类中,并非所有的方法都是抽象的,那些抽象的方法在子类中实现。而在
interface中所有的方法都是抽象的,不能有方法体。 - 接口中没有构造方法,无法被实例化。
- 接口中的成员变量和方法的存取控制权限必须是
public,所以存取控制符修饰符可以省略,就是指public。 - 接口中也可以有成员变量,但是这些成员变量必须是
static和final,在接口中要给域初始化,这两个关键字可以省略。 - 接口中只能出现:常量、方法
接口的定义与实现
以interface取代class关键字就是定义接口的形式。如果interface需要公开,则interface 前也可以有public,因此也要求文件名也必须和接口名一致。
一个类可以实现多个接口,使用关键字 implements 后跟若干接口名,接口名之间用逗号分隔。实现接口的类除非定义为抽象类,否则,一个非抽象的类在实现接口时,需要将接口中所有的方法进行实现。
因为接口中的方法如果没有存取控制符修饰的话,就是public,所以在实现接口的类中,应在方法前加上存取控制符 public。
如下例:
interface A{
int i=0;
void m();
}
class B implements A{
//int i=9;
public void m(){}
public static void main(String s[]){
B x=new B();
System.out.println(x.i);
}
}
再如:
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Adventure {
public static void t(CanFight x) { x.fight(); }
public static void u(CanSwim x) { x.swim(); }
public static void v(CanFly x) { x.fly(); }
public static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero h = new Hero();
t(h); // Treat it as a CanFight
u(h); // Treat it as a CanSwim
v(h); // Treat it as a CanFly
w(h); // Treat it as an ActionCharacter
}
}
注意在接口CanFight 和类ActionCharacter 中fight( )一样,所以在Hero中没有提供定义。
接口和抽象类的区别
- 相同点
- 都包含抽象方法
- 都不能被实例化
- 都是引用数据类型。可以声明抽象类及接口变量,并将子类的对象赋给抽象类变量,或将实现接口的类的变量赋给接口变量
- 不同点
- 只能继承一个抽象类,但可以实现多个接口,接口可以多继承
- 抽象类的访问权限控制同普通类,接口的访问权限只有public即是默认权限,所以public可以省略
- 接口中只能声明常量,不能声明变量
- 接口中只能声明抽象方法
- 使用接口的好处
- 可以使项目分层,所有层都面向接口开发,开发效率提高了。
- 接口使代码和代码之间的耦合度降低,就像内存条和主板的关系,变得“可插拔”。可以随意切换。
- 接口和抽象类都能完成某个功能,优先选择接口。
例子:
public interface CustomerService{
//定义一个退出系统的方法
void logout();
}
public class CustomerServiceImpl implements CustomerService{
//对接口中的抽象方法进行实现。
public void logout(){
System.out.println("成功退出系统!");
}
}
public class Test{
//入口
public static void main(String[] args){
//要执行CustomerServiceImpl中的logout方法.
//以下程序面向接口去调用
CustomerService cs = new CustomerServiceImpl(); //多态
//调用
cs.logout();
}
}
内部类
内部类就是在一个类中所定义的类,它可以在一个类中定义,也可以在一个方法中定义,如下:
class EnclosingClass {
...
static class StaticNestedClass { ... }
class InnerClass { ... }
}
- 内部类像类的其他成员一样,有自己的作用域,且内部类的名称不能与所嵌套的类重名。
- 内部类可以是由static修饰。
- 内部类也可以由abstract修饰,该内部类不能被实例化。
- 因为内部类在一个类的内部被定义,则修饰它的的访问控制修饰符可以是private、protected、public、缺省等权限。
- 非静态内部类的成员不能声明为static类型
- 使用内部类也相当于变相的实现了多重继承
//: Parcel2.java
// Returning a reference to an inner class.
public class Parcel{
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 cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel p = new Parcel2();
p.ship("Tanzania");
Parcel q = new Parcel2();
// Defining references to inner classes:
Parcel.Contents c = q.cont();
// Parcel2.Contents c = p.new Contents();
//如果Contents为static的话,可以new Parcel2.Contents();
Parcel.Destination d = q.to("Borneo");
}
}
非静态内部类
非静态内部类可以等同看做实例变量 非静态内部类中不能有静态声明. 非静态内部类可以访问外部类所有的数据.
静态内部类
静态内部类可以等同看做静态变量 静态内部类可以直接访问外部类的静态数据,无法直接访问非静态成员。 内部类重要的作用:可以访问外部类中私有的数据。
局部内部类
局部内部类等同于局部变量,它定义在方法内部,可出现在形式参数定义处或者方法体处,不能使用访问控制符,不能使用static。局部内部类可以访问方法中的局部变量,但该局部变量必须是使用final修饰的。这是为什么呢? 这是因为:
- JAVA语言的编译程序的设计者当然希望能无条件访问局部变量,从理论上这是很自然的要求,但是,编译技术是无法实现的或实现代价极高。
- 困难在何处?到底难在哪儿?局部变量的生命周期与局部内部类的对象的生命周期的不一致性!
- 设方法
f()被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i。当方法f()运行结束后,局部变量i就已死亡了,不存在了。但局部内部类对象inner_object还可能一直存在(在没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束死亡。这时:出现了一个"荒唐"结果:局部内部类对象inner_object要访问一个已不存在的局部变量i! - 如何才能实现?当变量是
final时,通过将final局部变量复制一份,复制品直接作为局部内部中的数据成员。这样,当局部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品"(即:这个复制品就代表了那个局部变量)。因此,当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是复制品),给人的感觉:好像是局部变量的生命期延长了。 - 那么,核心的问题是,怎么才能使得访问
复制品与访问真正的原始的局部变量,其语义效果是一样的呢? 当变量是final时,若是基本数据类型,由于其值不变,因而,其复制品与原始的量是一样。语义效果相同。(若:不是final,就无法保证复制品与原始变量保持一致了。因为,在方法中改的是原始变量,而局部内部类中改的是复制品) - 当变量是
final时,若是引用类型,由于其引用值不变(即:永远指向同一个对象)。因而,其复制品与原始的引用变量一样,永远指向同一个对象(由于是final,从而保证:只能指向这个对象,再不能指向其它对象)。达到:局部内部类中访问的复制品与方法代码中访问的原始对象,永远都是同一个,即:语义效果是一样的。否则:当方法中改变原始变量,而局部内部类中改复制品时,就无法保证:复制品与原始变量保持一致了(因此:它们原本就应该是同一个变量。)
public class OuterClass{
//方法
public void m1(){
//局部变量
final int i = 10;
//局部内部类
//局部内部类不能用访问控制权限修饰符修饰。
class InnerClass{
//内部类不能有静态声明
//public static void m1(){}
//成员方法
public void m2(){
System.out.println(i); // 10
}
}
//调用m2
InnerClass inner = new InnerClass();
inner.m2();
}
//入口
public static void main(String[] args){
OuterClass oc = new OuterClass();
oc.m1();
}
}
匿名内部类
匿名内部类:指的是类没有名字。 由于匿名内部类没有类名,因此匿名内部类不能显式声明构造方法。 匿名内部类在定义时即被实例化,所以匿名内部类不能定义为抽象类。
/*
匿名内部类:指的是类没有名字.
*/
public class Test{
//静态方法
public static void t(CustomerService cs){
cs.logout();
}
//入口
public static void main(String[] args){
//使用匿名内部类的方式执行t方法
//整个这个"new CustomerService(){}"就是个匿名内部类
t(new CustomerService(){
public void logout(){
System.out.println("exit!");
}
});
//匿名内部类的优点:少定义一个类.
//缺点:无法重复使用!
}
}
//接口
interface CustomerService{
//退出系统
void logout();
}