关于Java 抽象类、接口、内部类的总结

1,383 阅读9分钟

抽象类

抽象方法

语法形式:

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字段,也不能包含嵌套类。

接口内部的类

正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。因为放到接口中的任何类都自动地是 publicstatic 的。因为类是 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();
        }
    }
}