[Java学习笔记] Java核心技术 卷1 第六章 接口与内部类

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java学习笔记] Java核心技术 卷1 第六章 接口与内部类相关的知识,希望对你有一定的参考价值。

第6章 接口与内部类

6.1 接口

一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

在接口声明中,方法自动public,可以不写修饰符。在实现接口时必须把方法声明为public。

一个接口中可以包含多个方法,还可以定义常量,自动设置public static final

声明在接口中的内部类自动成为static和public类。

接口中不能含有实例域,也不能在接口中实现方法。提供实例域和方法实现的任务应该由实现接口的那个类来完成。

可以将接口看成是没有实例域的抽象类。

在调用方法时编译器会检查这个方法是否存在,如果是实现了接口的对象的数组,接口中提供了该方法,那么就可以确定该方法一定存在。每个实现了接口的类都必须提供接口中定义的所有方法。

为了让类实现一个接口,需要1:将类声明为实现 implements 给定的接口 2:对接口中的所有方法进行定义。

public interface Comparable
{
   int compareTo(Object other);
}
class Employee implements Comparable
{
   ...
   public int compareTo(Object other)
   {
       Employee e=(Employee)other;
       return Double.compare(salary,e.salary);
    }
}

 

6.1.1 接口的特性

l  接口不是类,不能使用new运算符实例化一个接口但是可以声明接口变量,同时接口变量必须引用实现了接口的类对象。

x = new Comparable(...);//Error
Comparable x ;// ok
x = new Employee(...);

l  使用instanceof检查一个对象是否实现了某个特定的接口

if( anObject instanceof Comparable){...}

l  如同可以继承类,也可以扩展接口,扩展接口不用像实现接口那样实现所有方法

有些接口只定义了常量没有定义方法,任何实现了接口的类都自动继承了这些常量,并且可以直接饮用而不需像静态域那样类名.域名 的形式

public interface Moveable
{
   void move(double x , double y );
}
public interface Powered extends Moveable
{
   double milesPerGallon();
   double SPEEDLIMIT = 95; // public static final
}

l  尽管每个类只能拥有一个超类,但可以实现多个接口。使用逗号将实现的各个接口分隔开。

class Employee implements Cloneable,Comparable

6.1.2 接口与抽象类

抽象类也可以表示通用属性,但存在一个问题:每个类只能扩展于(继承)一个类。接口却可以实现多个。

6.1.3 标记接口

Cloneable接口是Java提供的几个标记接口之一,标记接口没有方法,使用它的唯一目的是可以用instanceof进行类型检查。

自己编写程序时不要使用这种技术。

6.2 对象克隆

拷贝一个变量时,原始变量与拷贝变量引用同一个对象,改变一个变量所引用的对象会对另一个变量产生影响。

clone方法是Object 类的一个protected方法,在用户编写的代码中不能直接调用它。Object类对具体的类对象一无所知,只能将各个域进行对应的拷贝。对于基本类型或数值拷贝没有问题,但如果对象包含了子对象的引用,拷贝的结果还是两个域引用同一个子对象。

所有默认的克隆操作是浅拷贝,并没有克隆包含在对象中的内部对象。

必须重新定义clone方法,以便实现克隆子对象的深拷贝。对每个类都要做出下列判断:

1:默认的clone方法能否满足要求

2:默认的clone方法是否能够通过调用可变子对象的clone得到修补。

3:是否不应该使用clone

如果要使用clone,必须:

1:实现Cloneable接口

2:使用public访问修饰符重新定义clone方法,并声明异常CloneNotSupportedException

即使clone的默认实现能满足要求,也应该实现Cloneable接口,将clone定义为public,然后调用super.clone();

Cloneable接口与接口的正常使用没有任何关系。它并没有制定clone方法。clone方法是从Object类继承而来。接口在这里作为一个标记,表明类设计者制定要进行克隆处理。如果一个对象需要克隆而没有实现Cloneable接口,就会产生一个已检查异常。

class Employee implements Cloneable
{
   ...
   public Employee clone() throws CloneNotSupportedException
   {
       Employee cloned=(Employee)super.clone();//Object.clone()
       cloned.hireDay=(Date)hireDay.clone();//克隆子对象
       return cloned;
   }
}

关于是否应该事先clone方法,如果客户需要深拷贝就应该实现它。克隆的应用也不是很普遍,在标准类库中只有不到5%的类实现了clone。

所有的数组类型都包含一个clone方法,而且被设为了public,可以利用这个方法创建一个包含所有数组元素拷贝的一个新数组。

6.3 接口与回调

回调可以指出某个特定事件发生时应该采取的动作。

以构造定时器为例:

在很多程序设计语言中,可以提供一个函数名,定时器周期性的调用它。但Java标准类库中的类采用的是面向对象方法。将某个类的对象传递给定时器,然后定时器,定时器调用这个对象的方法。由于对象可以携带一些附加的信息,所有传递一个对象比传递一个函数灵活的多。

public interface ActionListener
{ 
    void actionPerformed(ActionEvent event); 
}
class TimePrinter implenets ActionListener
{
   public void actionPerformed(ActionEvent event)
   {
       //do something small
    }
}

//构造这个类的对象传递给Timer构造器
ActionListener listener = new TimePrinter();
Timer t = new Timer(10000,listener);

6.4 内部类

为什么需要使用内部类:

1:内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据

2:内部类可以对同一个包中的其他类隐藏起来

3:当想要定义一个回调函数而不想写大量代码时,使用匿名内部类比较便捷

6.4.1 使用内部类访问对象状态

内部类的对象总有一个隐式引用,指向了创建它的外部类对象。将外围类的对象的引用称为outer。

outer不是Java关键字,外围类的引用在构造器中设置,编译器修改了所有内部类的构造器,添加了一个外围类的引用参数。

只有内部类可以是私有类。常规类只可以具有包可见性,或公有可见性。

6.4.2 内部类的特殊语法规则

使用外围类引用:OuterClass.this

if(TalkingCLock.this.beep) ...

编写内部类对象的构造器:outerObject.new InnerClass(contruction parameters)

ActionListener listener = this.new TimerPrinter();

在外围类的作用域之外引用内部类:

OuterClass.InnerClass

6.4.3 内部类安全性

如果内部类访问了私有数据域,就有可能通过附加在外围类梭子包中的其他类访问它们,但做这些事情需要高超的技巧和极大的决心。不可能无意之中就获得对类的访问权限,必须可以构建或修改类文件才有可能达到这个目的。

6.4.4 局部内部类

可以在一个方法中定义局部类,并且不能用public或private访问说明符进行声明,它的作用域被限定在声明这个局部类的块中。

局部类可以对外部世界完全隐藏起来,即使方法所在类中的其他代码也不能访问。除了定义它的方法外,没有任何方法知道它的存在。

局部类的另一个优势:不仅可以访问包含它们的外部类,还可以访问局部变量,但那些局部变量必须被声明为final

public void start(int interval,final boolean beep)
{
    class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            if(beep)//局部类访问局部变量
           Toolkit.getDefaultToolkit().beep();
        }
    }
    ActionListener listener = new TimePrinter();
    Time t = new Timer(interval,listener);
    t.start();
}

6.4.5 匿名内部类

假如只创建一个这个(内部)类的对象,就不必命名了,这种类被称为匿名内部类

匿名类不能有构造器,需要将参数传递给超类构造器;内部类实现接口时不能有任何构造参数。

构造器与类名同名,没有名字的类无法设置构造器。

SuperType可以是接口,内部类就要实现这个接口;也可以是一个类,内部类就要扩展它。

new SuperType(construction parameters)
{
   inner class methods and data
}
new InterfaceType()
{
   methods and data
}

public void start(int interval, final boolean beep)
{
   //创建一个实现AL接口的类的新对象
   ActionListener listener =  new ActionListener()
   {
       ...
   }
   ...
}

6.4.6 静态内部类

有时候使用内部类仅为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。为此可以将内部类声明为static,以便取消产生的引用。

只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。如果在静态方法中构造内部类,则必须是静态内部类。

声明在接口中的内部类自动称为static和public类。

6.5 代理

在编译时无法确定需要实现哪个接口时,利用代理可以在运行时创建一个实现了一组给定接口的新类。

例如假设有一个表示接口的Class对象,它的确切类型在编译时无法知道。想要构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器。但是不能实例化一个接口,需要在程序处于运行状态时定义一个新类。

为了解决上述问题,有些程序将会生成代码,然后将这些代码放在一个文件中,调用编译器,再加载结果类文件,很明显这样做的速度比较慢,并且需要将编译器与程序放在一起。

代理类则可以在运行时创建全新的类,这样代理类能够实现指定的接口,尤其是具有下列方法:

1:指定接口所需要的全部方法

2:Object类中的全部方法

但是不能在运行时定义这些方法的新代码。而要提供一个调用处理器,它是实现了InvocationHandler接口的类对象,在这个接口中只有一个方法

Object invoke(Object proxy, Method method ,Object[] args)

无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出调用处理的方式。

要创建一个代理对象,需要使用Proxy类的newProxyInstance方法,该方法有三个参数:

1:类加载器。作为Java安全模型的一部分,对于系统类和从其他类,可以使用不同的类加载器,目前使用null表示使用默认的类加载器

2:Class对象数组。每个元素都是需要实现的接口

3:调用处理器。

以及需要解决两个问题:

1:如何定义一个处理器

2:能够使用结果代理对象做些什么

6.6 待补充

内部类、代理 的示例代码

以上是关于[Java学习笔记] Java核心技术 卷1 第六章 接口与内部类的主要内容,如果未能解决你的问题,请参考以下文章

《Java核心技术 卷1 基础知识》三

201521044091 《Java学习笔记》 第六周学习总结

java 核心技术卷一笔记 6 .1.接口 lambda 表达式 内部类

java 核心技术卷一笔记 6 .1接口 lambda 表达式 内部类

《Java核心技术(卷一)》读书笔记——第六章:内部类

Java核心技术卷一笔记7