1、为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。
如果只需要一个对接口的引用,那么直接通过外围类实现那个接口。
那么内部类实现一个接口和外围类实现这个接口有什么区别呢?
后者不是总能享受到接口带来的方便,又是需要用到接口的实现。所以。使用内部类的最重要的原因:
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类提供了、可以继承多个具体的或抽象的类的能力,使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。内部类允许继承多个非接口类型(类或抽象类)。
如果拥有的是抽象的类或具体的类,而不是借口,那就只能使用内部类才能实现多重继承。
如果使用内部类,还可以获得其他一些特性:
1.1、内部类可以有很多实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
1.2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
1.3、创建内部类对象的时刻并不依赖于外围类对象的创建。
1.4、内部类并没有令人迷糊的"is-a"关系,它就是一个独立的实体。
使用内部内实现抽象的类或具体的类的多重继承
class D{
}
abstract class E{
}
class Z extends D{
E returnE(){
return new E(){};
}
}
public class MultiExtend {
static void takesD(D d){
System.out.println("d");
}
static void takesE(E e){
System.out.println("e");
}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.returnE());
}
}
interface Incrementable {
void increment();
}
2、闭包与调用
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建他的作用域。通过这个定义,可以看出内部类是明向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作有所的成员,包括private成员。
Java最引人争议的问题之一为:人们认为Java应该包含某种类似指针的机制,以允许回调。通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。如果回调是通过指针实现的,那么就只能希望程序员不会误用指针。Java更小心仔细,没有在语言中包括指针。通过内部类提供闭包比指针更灵活、安全。
通过内部类完成闭包
class Calleel implements Incrementable {
private int i = 0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement{
public void increment(){
System.out.println("other operation");
}
static void f(MyIncrement mi){
mi.increment();
}
}
class Calleel2 extends MyIncrement{
private int i =0;
@Override
public void increment(){
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable{
@Override
public void increment(){
Calleel2.this.increment();
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller{
private Incrementable callbackReference;
Caller(Incrementable cbh){callbackReference = cbh;}
void go(){callbackReference.increment();}
}
public class CallBacks {
public static void main(String[] args) {
Calleel calleel = new Calleel();
Calleel2 calleel2 = new Calleel2();
// other operation 1
MyIncrement.f(calleel2);
Caller caller = new Caller(calleel);
Caller caller1 = new Caller(calleel2.getCallbackReference());
// 1
caller.go();
//2
caller.go();
// other operation 2 这个2为 calleel的1 +1 想要实现其他接口但是重名了部分 于是用内部类独立实现接口而不用覆盖原有方法
caller1.go();
caller1.go();
}
}
这个例子展示了外围类实现一个接口与内部类实现此接口直接的区别。Callee2继承MyIncrement,后者已经有了一个不同的increment()方法,并且与Incrementable接口期望的increment()方法完全不相关。所以如果Callee2继承了MyIncrement,就不能为了Incrementable的用途而覆盖Increment()方法,于是只能使用内部类独立地实现Incrementable。当创建一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。
内部类Closure实现了Incrementable,以提供一个返回Caleel2的钩子(hook)——而且是一个安全的钩子。无论谁获得此Incrementable的引用,都只能调用increment(),除此之外没有其他功能。
回调的价值在于它的灵活性——可以在运行时动态地决定需要调用什么方法。
3、内部类与控制框架
应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决你的特定问题。模版方法包含算法的基本结果,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开。
控制框架是一类特殊的应用程序框架,用来解决响应事件的需求。主要用来响应事件的系统被称作事件驱动系统。
一个用来管理并触发事件的实际控制框架
public class Controller {
private List<Event> eventList = new ArrayList<Event>();
public void addEvent(Event c){eventList.add(c);}
public void run(){
while(eventList.size()>0){
for(Event e: new ArrayList<Event>(eventList)){
if(e.ready()){
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}
}
}
在这个设计当中 你并不知道Event到底做了什么。这正是此设计的关键所在,“使变化的事物与不变的事物相互分离”。“变化向量”就是各种不同的Event对象所具有的不同行为,而你通过创建不同的Event来表现不同的行为。
这正是内部类要做的事情,内部类允许:
控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来,内部类用来表示解决问题所必需的各种不同的action().
内部类能很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌。
4、内部类的标识符
每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息。内部类也必须生成一个.class文件以包含它们的class对象信息。命名规则:外围类的名字加上‘$’,再加上内部类的名字。例如:LocalClass$InnerLocalCounter
如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符,如果内部类是嵌套在别的内部类之中,只需要将他们的名字加在其外围类标识符与‘$’的后面。
5、容器
程序总是根据运行时才知道的某些条件去创建新对象。需要在任意时刻和任意位置创建任意数量的对象。Java提供了一套完整的容器类来解决这个问题,其中基本的类型是List、Set、Queue和Map。这些对象类型也称为集合类,但由于类库使用Collection这个名字来指代该类库的一个特殊子集,所以用范围更广的“容器”称呼他们。
6、Java容器基本概念
Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象的产生的顺序(通常与他们被插入的顺序相同)
Map。一组成对的键值对对象,允许你使用键查找值,ArrayList允许你使用数字来查找值,某种意义上,将数字与对象关联在一起。映射表允许我们使用另一个对象来查找某个对象,也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起,或者被称为字典,因为你可以使用键对象来查找值对象,就像在字典中使用单词来定义一样。
TreeSet按照比较结果的升序保存对象,LinkedHashSet按照被添加的顺序保存对象
7、List
List承诺将元素维护在特定的序列中。
有两种类型的List
基本的ArrayList 长于随机访问元素,但是在List的中间插入和移除元素时较慢
LinkedList,通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是特性集比ArrayList更大。
8、迭代器
只是使用容器,不知道或者不关心容器的类型,想要做到不重写代码就可以应用到不同类型的容器,使用迭代器来完成。
迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结果。此外,迭代器通常被称为轻量级对象:创建它的代价小。因此,迭代器有些奇怪的限制,例如java的Iterator只能单向移动。有子类ListIterator可以双向移动,但是只能用于List类的访问。
9、Stack
栈 通常是指后进先出(LIFO)的容器。有时栈也被称为叠加栈,因为最后压入栈的元素,第一个弹出栈。
10、Set
Set不保存重复的元素,最常用来测试归属性,可以很容易地询问某个对象是否在某个Set中,查找成为了Set中最重要的操作,HashSet也专门对快速查找进行了优化。
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像之前的两个不同的List,实际上Set就是Collection,只是行为不同。(继承与多态思想的典型应用:表现不同的行为)Set是基于对象的值来确定归属性的。
出于速度原因,HashSet使用了散列。TreeSet将元素存储在红-黑树数据结构中,HashSet使用散列函数。LinkedHashList因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
11、Queue
队列是一个典型的先进先出(FIFO)的容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中很重要,因为它们可以安全地将对象从一个任务传输给另一个任务。
LinkedList提供了方法以支持队列的行为,并且实现了Queue接口,因此LinkedList可以用作Queue的一种实现。
Thinking In Java Part06(内部类/容器)
最新推荐文章于 2020-07-06 10:44:41 发布