Java核心技术-接口、lambda表达式与内部类

本章将主要介绍:

接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现一个或多个接口。

lambda表达式:这是一种表示可以在将来的某个时间点执行的代码块的简洁方法。

内部类机制:内部类定义在另一个类的内部,其中的方法可以访问包含它们的外部类的域。

代理:一种实现任意接口的对象。

1 接口

1.1 接口的概念

概念:接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

“如果类遵从某个特定的接口,那么就履行这项服务”:

Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口。

接口中的所有方法自动地属于public。因此,在接口中声明方法时,不必提供关键字public。

接口中可以定义常量,但绝不能含有实例域,Java SE 8之后,可以在接口中提供简单的方法(不能引用实例域)

提供实例域和方法实现的任务应该由实现接口的类完成,可以将接口看成是没有实例域的抽象类。

为了让类实现一个接口,需要一下步骤:

1.将类声明为实现给定的接口(implements关键字)。

2.对接口中的所有方法进行定义(实现接口时,必须把方法声明为public)。

不直接提供compareTo方法而实现接口的原因:

Java是一种强类型语言,在调用方法的时候,编译器将检查这个方法是否存在。而实现了Compareble接口的类必定存在这个方法。

1.2 接口的特性

接口不是类,所以不能用new实例化一个接口,然而,却能声明接口的变量,接口变量必须引用实现了接口的类对象。

使用instanceof检查一个对象是否属于某个特定的类,使用instance检查一个对象是否实现了某个特定的接口

接口中的常量被自动设为public static final。

尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。(使用逗号将实现的每个接口分开)

1.3 接口与抽象类

接口与抽象类的区别:

使用抽象类表示通用属性存在每个类只能扩展与一个类的问题,但每个类可以实现多个接口。

1.4 静态方法

在Java SE 8中,允许在接口中增加静态方法。

目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和使用工具类,如Collection/Collections或Path/Paths。

这个伴随类实现了相应接口的部分或全部方法。

现在只要在实现接口时,加入静态方法,就不再需要再为使用工具方法另外提供一个伴随类。

1.5 默认方法

可以为接口方法提供一个默认实现。必须用default修饰符标记这样的一个方法。

实现这个接口的类可以选择性的覆盖接口中的默认方法。

默认方法的一个重要用法是——接口演化(给原先设计的接口添加默认方法,不需要重新编译之前实现接口的所有类且这些类调用新添加的方法时将调用接口中的实现)

1.6 解决默认方法冲突

考虑这样的情况:如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,会发生什么情况?(二义性)

Java的处理规则如下:

1.超类优先(考虑到接口新增默认方法的兼容性)

2.接口冲突:考虑三种情况

1.两个接口中都有这个方法的默认实现——必须覆盖这个方法来解决冲突(可以选择两个冲突方法中任意一个提供的默认实现)

2.两个接口中有一个有这个方法的默认实现——必须覆盖这个方法来解决冲突(可以选择两个冲突方法中提供默认实现的那一个)

3.两个接口中都没有这个方法的默认实现——可以实现这个方法,也可以不实现这个方法。如果不实现,这个类本身就是抽象的


2 接口示例

2.1 接口与回调

回调是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。

ActionListener listen=new TimePrinter();
Timer time=new Timer(10000,listen)

2.2 Comparator接口

考虑这样的情况:

一个类本身已经继承Comparable接口,有一个按字典顺序排序的comparaTo方法。现在我们希望按长度递增的顺序对这个类进行排序。而不是按字典排序。

解决这种情况,考虑使用一个数组和一个比较器作为参数,比较器是实现了Comparator接口的类的实例。

Comparator<String> comp=new LengthComparator();
if(comp.compare(words[i],words[j])>0...

将这个调用与words[i].compareTo(words[j])做比较。这个compare方法要在比较器对象上调用,而不是在字符串本身上调用。

Comparator与Comparable的区别:

1.使用Comparator进行比较时需要先建立一个Comparator具体实例

2.Comparator调用对象是比较器,comparable调用对象是字符串本身

3.利用Comparator可以实现更多样的比较方式

2.3 对象克隆

本节讨论Cloneable接口,这个接口指示一个类提供了一个安全的clone方法

考虑这样的情况:

当我们为一个包含对象引用的变量建立副本时,原变量和副本都是同一个对象的引用。这说明任何一个变量的改变都会影响另一个变量。

Employee origina=new Employee("john Public",50000);
Employee copy=original;
copy.raiseSalary(10);

如果希望copy是一个新对象,它的初始状态与original相同,但是之后它们各自会有自己不同的状态,这种情况下就可以使用clone方法。

Employee copy=original.clone();
copy.raiseSalary(10);

默认的克隆操作是浅拷贝,并没有克隆对象中引用的其他对象。

如果原对象中的所有子对象都是不可变的,那么使用浅拷贝是安全的。

如果子对象属于一个不可变类,就需要使用深拷贝

对于每一个类,需要确定:

1.默认的clone方法是否满足要求;

2.是否可以在可变的子对象上调用clone来修补默认的clone方法;

如果选择第一项或第二项,类必须:

1.实现Cloneable接口;

2.重新定义clone方法,并指定public访问修饰符。

Object中的clone方法声明为protected,因此要在实现类中重新定义为public,才能在其他类中调用实例化的这个实现类对象的clone方法。

Cloneable接口是标记接口,标记接口不包含任何方法;它唯一的作用就是允许在类型查询中使用instanceof

如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException。

所有数组类型都有一个public的clone方法


3 lambda表达式

采用一种简洁的语法定义代码块

3.1 为什么引入lambda表达式

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。

比如传递一个实现ActionListener接口的类实例到定时器或实现Comparator接口的类实例到sort方法。

3.2 lambda表达式的语法

(String first,String second)
    ->first.length()-second.length()

这就是你看到的第一个lambda表达式。lambda表达式就是一个代码块,以及必须传入代码的变量规范。

即使lambda表达式没有参数,仍然要提供空括号

()->{for(int i=100;i>=0;i--)System.out.println(i);}

如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型

Comparator<String> comp
    =(first,second)
        ->first.length()-second.length();

如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号:

ActionListener listener=event->
    System.out.println("The time is"+new Date());

无需指定lambda表达式的返回类型

如果lambda表达式只在某些分支返回一个值,而在另外一些分支不返回值,这是不合法的。

(int x)->{if(x>=0)return 1;}

3.3 函数式接口

概念:只有一个抽象方法的接口称为函数式接口

可以用lambda表达式替换函数式接口

例如:Comparator就是只有一个方法的接口,所以可以提供一个lambda表达式

Arrays.sort(words,
    (first,second)->first.length()-second.length());

在底层,Arrays.sort方法会接收实现了Comparator<String>的某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式的体。(要接受lambda表达式可以传递到函数式接口)

最好把lambda表达式看作是一个函数,而不是一个对象。

实际上,在Java中,对lambda表达式能做的也只是能转换为函数式接口。

3.4 方法引用

要用::操作符分隔方法名与对象或类名,主要有三种情况:

*object::instanceMethod

*Class::staticMethod

*Class::instanceMethod

前两种情况,方法引用等价于提供方法参数的lambda表达式:

表达式System.out::println是一个方法引用,它等价于lambda表达式x->System.out.println(x)

表达式Math::pow是一个方法引用,它等价于lambda表达式(x,y)->Math.pow(x,y)

对于第三种情况,第一个参数会成为方法的目标:

表达式String:;compareToIgnoreCase是一个方法引用,它等价于(x,y)->x.compareToIgnoreCase(y)

类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例

可以在方法引用中使用this和super参数

this::equals等同于x->this,equals(x)

3.5 构造器引用

构造器引用与方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。具体调用哪一个构造器取决于上下文。

可以用数组类型建立构造器引用,例如,int[]::new是一个构造器引用,等价于lambda表达式x->new int[x]

Java有一个限制,无法构造泛型类型T的数组。数组构造器引用对于克服这个限制很有用。

3.6 变量作用域

通常,你可能希望能够在lambda表达式中访问外围方法或类中的变量。

例子:

public static void repeatMessage(String text,int delay)
{
    ActionListener listener=event->
        {
            System.out.priintln(text);
            Toolkit.getDefaultToolkit().beep();
        };
    new Timer(delay,listener).start();
}

lambda表达式的代码可能会在repeatMessage调用返回很久以后才运行,而那时这个参数变量已经不存在了。如何保留text变量呢?

一个lambda表达式由下面三部分组成:

1.一个代码块;

2.参数;

3.自由变量的值,这是指非参数而且不在代码中定义的变量。

在Java中,lambda表达式就是闭包。

猜你喜欢

转载自www.cnblogs.com/dyj-blog/p/8979635.html