接口(interface)
接口概念
在 Java 程序设计语言中, 接口不是类 , 而是对类的一组需求描述, 这些类要遵从接口描述的统一格式进行定义 。
Arrays 类中的 sort 方法承诺可以对对象数组进行排序, 但要求满足下列前提 : 对象所属的类必须实现了 Comparable 接口 ,因为sort排序的时候需要用到接口中的函数compareTo作比较。
下面是 Comparable 接口的代码 :
public interface Comparable<T> {
public int compareTo(T o);
}
这就是说 , 任何实现 Comparable 接口的类都需要包含 compareTo 方法, 并且这个方法的参数必须是一个 Object 对象 , 返回一个整型数值 。
接口中的所有方法自动地属于 public 。 因此, 在接口中声明方法时 , 不必提供关键字public。抽象类中的抽象方法默认和普通的类相同。和interface不同。
当然, 接口中还有一个没有明确说明的附加要求 : 在调用 x.compareTo(y) 的时候 , 这个compareTo方法必须确实比较两个对象的内容 , 并返回比较的结果 。 当 x 小于 y 时, 返回一个负数 ; 当 x 等于 y 时 , 返回 0 ; 否则返回一个正数 。
案例:
现在 , 假设希望使用 Arrays 类的 sort 方法对 Employee 对象数组进行排序 , Employee 类就必须实现 Comparable 接口 。
public class Employee implements Comparable<Employee>
{
private String name;
private double salary;
public Employee(String name, double salary)
{
this.name = name;
this.salary = salary;
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public int compareTo(Employee other)
{
return Double.compare(salary, other.salary);
}
}
接口特性
接口不是类, 尤其不能使用 new 运算符实例化一个接口 :
x = new Comparable( . . . ) ; / / ERROR
然而, 尽管不能构造接口的对象, 却能声明接口的变量 :
Comparable x ; // OK
接口变量必须引用实现了接口的类对象 :
x = new Employee ( .. . ) ; //
接下来, 如同使用 instanceof 检查一个对象是否属于某个特定类一样, 也可以使用instance 检查一个对象是否实现了某个特的接口 :
if ( anObject instanceof Comparable) {
. . . }
与可以建立类的继承关系一样, 接口也可以被扩展 。 例如, 假设有一个称为 Moveable 的接口 ,可以以它为基础扩展一个叫做 Powered 的接口 :
public interface Moveable
{
void move (double x , double y ) ;
}
public interface Powered extends Moveable
{
double milesPerCallon() ;
}
接口可以有常量,但是接口中的域将被自动设为 public static final ,接口中的方法都自动地被设置为 public
接口与抽象类
为什么不将 Comparable 直接设计成如下所示的抽象类 。
abstract class Comparable // why not?
{
public abstract int compareTo(Object other) ;
}
然后 , Employee 类再直接扩展这个抽象类, 并提供 compareTo 方法的实现 :
class Employee extends Comparable // why not ?
{
public int compareTo(Object other) {
. . . }
}
使用抽象类表示通用属性存在这样一个问题 : 每个类只能扩展于一个类 。 假设 Employee 类已经扩展于一个类, 例如 Person , 它就不能再像下面这样扩展第二个类了 :
class Employee extends Person , Comparable
但每个类可以像下面这样实现多个接口 :
cl ass Employee extends Person implements Comparable // OK
静态方法
在 Java SE 8 中, 允许在接口中增加静态方法 。 理论上讲 , 没有任何理由认为这是不合法的 。 只是这有违于将接口作为抽象规范的初衷 。**
public class EmployeeSortTest
{
public static void main(String[] args)
{
System.out.println(A.returnId());
}
}
interface A{
public static int returnId(){
return 1;
}
}
目前为止, 通常的做法都是将静态方法放在伴随类中 。 在标准库中 , 你会看到成对出现的接口 和实用工具类 , 如 Collection/ Collections 或 Path / Paths 。**
默认方法
可以为接口方法提供一个默认实现 。 必须用 default 修饰符标记这样一个方法 。
public interface Comparable<T> // 实际标准包中没有这个类
{
default int compareTo ( T other ) {
return 0; }
// By default , all elements are the same
}
当然 , 这并没有太大用处 , 因为 Comparable 的每一个实际实现都要覆盖这个方法 。 不过有些情况下 , 默认方法可能很用。
解决默认方法冲突
如果先在一个接口中将一个方法定义为默认方法 , 然后又在超类或另一个接口中定义了同样的方法, 会发生什么情况 ? 诸如 Scala 和 C + + 等语言对于解决这种二义性有一些复杂的规则 。 幸运的是, Java 的相应规则要简单得多 。 规则如下 :
1 ) 超类优先 。 如果超类提供了一个具体方法 , 同名而且有相同参数类型的默认方法会被忽略 。
2 ) 接口冲突 。 如果一个超接口提供了一个默认方法 , 另一个接口提供了一个同名而且参数类型 ( 不论是否是默认参数 ) 相同的方法, 必须覆盖这个方法来解决冲突 。
案例:
interface A{
default public int returnId(){
return 9;};
}
interface B{
public int returnId();
}
现在来考虑另一种情况 , 一个类扩展了一个超类 ,同时实现了一个接口 , 并从超类和接口继承了相同的方法 。
interface A{
default public int returnId(){
return 9;}
}
interface B{
public int returnId();
}
class D{
public int returnId(){
return 9;}
}
class C extends D implements B{
}
接口示例
Comparator 接口
现在假设我们希望按长度递增的顺序对字符串进行排序, 而不是按字典顺序进行排序 。
要处理这种情况 , Arrays. Sort 方法还有第二个版本, 有一个数组和一个比较器 ( comparator )
Arrays.sort (friends , new LengthComparator() );
例如:
对象克隆
要了解克隆的具体含义, 先来回忆为一个包含对象引用的变量建立副本时会发生什么 。原变量和副本都是同一个对象的引用。
Employee original = new Employee ( "John Public" , 50000 ) ;
Employee copy = original ;
copy raiseSalary ( 10 ) ; // oops - also changed original
如果希望 copy 是一个新对象, 它的初始状态与 original 相同 , 但是之后它们各自会有自己不同的状态, 这种情况下就可以使用 clone 方法 。
Employee copy = original , cl one () ;
copy raiseSalary ( 10 ) ; / / OK original unchanged
不过并没有这么简单 。 clone 方法是 Object 的一个 protected 方法 , 这说明你的代码不能直接调用这个方法 。 只有Employee 类可以克隆 Employee 对象 。 这个限制是有原因的 。 想想看 Object 类如何实现 clone 。 它对于这个对象一无所知 , 所以只能逐个域地进行拷贝 。 如果对象中的所有数据域都是数值或其他基本类型, 拷贝这些域没有任何问题 、 但是如果对象包含子对象的引用, 拷贝域就会得到相同子对象的另一个引用, 这样一来, 原对象和克隆的对象仍然会共享一些信息 。
浅拷贝会有什么影响吗 ? 这要看具体情况 。 如果原对象和浅克隆对象共享的子对象是不可变的 , 那么这种共享就是安全的 。 如果子对象属于一个不可变的类, 如 String , 就 是 这 种情况 。 或者在对象的生命期中, 子对象一直包含不变的常量 , 没有更改器方法会改变它, 也没有方法会生成它的引用, 这种情况下同样是安全的 。
不过 , 通常子对象都是可变的, 必须重新定义 clone 方法来建立一个深拷贝 , 同时克隆所有子对象 。 在这个例子中, hireDay 域是一个 Date , 这是可变的 , 所以它也需要克隆 。 (出于这个原因 , 这个例子使用 Date 类型的域而不是 LocalDate 来展示克隆过程 。 如果 hireDay是不可变的 LocalDate 类的一个实例, 就无需我们做任何处理了 。)
对于每一个类, 需要确定 :
1 ) 默 认 的 clone 方法是否满足要求 ;
2 ) 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法 ;
3 ) 是否不该使用 clone
实际上第 3 个选项是默认选项 。 如果选择第 1 项或第 2 项, 类必须 :
1 ) 实现 Cloneable 接口 ;
2 ) 重新定义 clone 方法, 并指定 public 访问修饰符 。
lambda 表达式
lambda 表达式采用一种简洁的语法定义代码块, 以及如何编写处理 lambda 表达式的代码 。
lambda 表达式
lambda 表达式是一个可传递的代码块, 可以在以后执行一次或多次 。
如果想按长度而不是默认的字典顺序对字符串排序, 可以向 sort 方法传人一个 Comparator 对象 :如果想按长度而不是默认的字典顺序字符串排序, 可以向 sort 方法传人一个 Comparator 对象 :
class LengthTest implements Comparator<String>{
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
compare 方法不是立即调用 。 实际上 , 在数组完成排序之前, sort 方法会一直调用compare 方法, 只要元素的顺序不正确就会重新排列元素, 将比较元素所需的代码段放在sort 方法中 , 这个代码将与其余的排序逻辑集成
这两个例子有一些共同点 , 都是将一个代码块传递到某个对象 ,这个代码块会在将来某个时间调用 。
到目前为止, 在 Java 中传递一个代码段并不容易, 不能直接传递代码段 _ Java 是一种面向对象语言, 所以必须构造一个对象 , 这个对象的类需要有一个方法能包含所需的代码**.(学到这里,我发现这个东西类似于函数指针)**
lambda 表达式的语法
first 和 second 是什么 ? 它们都是字符串 。 Java 是一种强类型语言, 所以我们还要指定它们的类型 :
( String first , String second )
-> first . length() - second . length()
Java 中的一种 lambda 表达式形式 : 参数, 箭头 ( -> ) 以及一个表达式 。 如果代码要完成的计算无法放在一个表达式中 , 就可以像写方法一样, 把这些代码放在 {}中,并包含显式的 return 语句 。 例如 :
(String first, String second) ->
{
if ( first . length() < second . length() ) return -1 ;
else if ( first . length() > second . length() ) return 1;
else return 0;
}
即使 lambda 表达式没有参数 , 仍然要提供空括号, 就像无参数方法一样 :
( ) - > {
for ( int i = 100; i > = 0; i --) System out println ( i ) ; }
无需指定 lambda 表达式的返回类型 。 lambda 表达式的返回类型总是会由上下文推导得出 。 例如, 下面的表达式:
( String first , String second ) - > first length() - second.length();
可以在需要 int 类型结果的上下文中使用 。(我的理解是 first length() - second.length()会自动识别为int类型)
如果一个 lambda 表达式只在某些分支返回一个值, 而在另外一些分支不返回值,这是不合法的 。 例如 , ( int x ) - > { if (x >= 0 ) return 1 ; } 就不合法 。
如果方法只有一个参数 , 而且这个参数的类型可以推导得出 , 那么甚至还可以省略小括号 :
Timer t = new Timer(1000, event ->
System.out.println("The time is " + new Date()));
案例代码:
import java.util.*;
/**
* This program demonstrates the use of lambda expressions.
* @version 1.0 2015-05-12
* @author Cay Horstmann
*/
public class LambdaTest
{
public static void main(String[] args)
{
String[] planets = new String[] {
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune" };
System.out.println("Sorted by length:");
Arrays.sort(planets , new LengthTest());
System.out.println(Arrays.toString(planets));
System.out.println("Sorted in dictionary order:");
Arrays.sort(planets);
System.out.println(Arrays.toString(planets));
System.out.println("Sorted by length:");
Arrays.sort(planets, (first, second) -> first.length() - second.length()); // 运用lambda表达式
System.out.println(Arrays.toString(planets));
}
}
class LengthTest implements Comparator<String>{
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
函数式接口
lambda 表达式与这些接口是兼容的 ,有很多封装代码块的接口Comparator
对于只有一个抽象方法的接口, 需要这种接口的对象时 , 就可以提供一个 lambda 表达式 ,这种接口称为函数式接口 ( functional interface) 。
Java8中的函数式接口定义是只有一个抽象方法的接口,但是这个Comparator中,好像是有多个方法。但是实际上只有一个。int compare(T o1,T o2)defualt方法 默认方法有点类似静态方法。equals方法是Object的方法,
所以可以提供一个 lambda 表达式 :
Arrays.sort(planets, (first, second) -> first.length() - second.length());
在底层 , Arrays . sort 方法会接收实现了 Comparator 的某个类的对象 。 在这个对象上调用 compare 方法会执行这个 lambda 表达式的体 。 这些对象和类的管理完全取决于具体实现, 与使用传统的内联类相比, 这样可能要高效得多 。 最好把 lambda 表达式看作是一
个函数 , 而不是一个对象 , 另外要接受 lambda 表达式可以传递到函数式接口 。
方法引用
有时 , 可能已经有现成的方法可以完成你想要传递到其他代码的某个动作 。
假设你想对字符串排序, 而不考虑字母的大小写 。 可以传递以下方法表达式 :
Arrays.sort(planets , String::compareToIgnoreCase);
从这些例子可以看出 , 要用: : 操作符分隔方法名与对象或类名 。 主要有 3 种情况 :
object :: instanceMethod
Class::staticMethod
Class :: instanceMethod
在前 2 种情况中 , 方法引用等价于提供方法参数的 lambda 表达式 。 类似地 , Math :: pow 等价于( x,y) ->
System. out:: println 等价于 x - > System. out. println( x)
对于第 3 种情况 , 第 1 个参数会成为方法的目标 。 例如 , String :: compareToIgnoreCase 等同于 (x, y ) - > x. compareToIgnoreCase( y ).
这一点我的理解是:类不能直接调 实例方法,所以需要一个相应的对象调用方法,这个方法有一个参数,自然就是lambda表达式中的(x ,y ),内容自然就是x.方法(y)
lambada后续内容遇到再补上…
内部类
内部类 ( inner class ) 是定义在另一个类中的类 。 为什么需要使用内部类呢 ?
其主要原因有以下三点 :
• 内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据 。
• 内部类可以对同一个包中的其他类隐藏起来 。
• 当想要定义一个回调函数且不想编写大量代码时, 使用匿名 ( anonymous)内部类比较便捷 。
使用内部类访问对象状态
内部类的语法比较复杂 。 鉴于此情况, 我们选择一个简单但不太实用的例子说明内部类的使用方式 。 下面将进一步分析 TimerTest 示例, 并抽象出一个 TalkingClock 类 。 构造一个语音时钟时需要提供两个参数 : 发布通告的间隔和开关铃声的标志 。
package innerClass;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
/**
* This program demonstrates the use of inner classes.
* @version 1.11 2015-05-12
* @author Cay Horstmann
*/
public class InnerClassTest
{
public static void main(String[] args)
{
TalkingClock clock = new TalkingClock(1000, true);
clock.start();
// keep program running until user selects "Ok"
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}
/**
* A clock that prints the time in regular intervals.
*/
class TalkingClock
{
private int interval;
private boolean beep;
/**
* Constructs a talking clock
* @param interval the interval between messages (in milliseconds)
* @param beep true if the clock should beep
*/
public TalkingClock(int interval, boolean beep)
{
this.interval = interval;
this.beep = beep;
}
/**
* Starts the clock.
*/
public void start()
{
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
public class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (beep)
Toolkit.getDefaultToolkit().beep();
}
}
}
令人惊讶的事情发生了 。 TimePrinter 类没有实例域或者名为 beep 的变量 , 取而代之的是beep 引用了创建 TimePrinter 的 TalkingClock 对象的域 。 这是一种创新的想法 。 从传统意义上讲, 一个方法可以引用调用这个方法的对象数据域。 内部类既可以访问自身的数据域, 也可以访问创建它的外围类对象的数据域 .
这个引用在内部类的定义中是不可见的 。 然而, 为了说明这个概念 , 我们将外围类对象的引用称为 outer 。 于是 actionPerformed 方法将等价于下列形式 :
public void actionPerformed ( ActionEvent event )
{
System out println("At the tone , the time is " + new DateO ) ;
if (outer.beep)
Toolkit getDefaultToolkit() beep() ;
}
内部类的特殊语法规则
使用外围类引用的正规语法还要复杂一些 。 表达式
OuterClass.this // 表示外围类引用
可以像下面这样编写 TimePrinter 内部类的 actionPerformed 方法 :
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (TalkingClock.this.beep)
Toolkit.getDefaultToolkit().beep();
}
如果如果 TimePrinter 是一个公有内部类 , 对于任意的语音时钟都可以构造一个 TimePrinter :
TalkingClock jj = new TalkingClock(1000 , true);
TalkingClock.TimePrinter listener = jj.new TimePrinter();
需要注意 , 在外围类的作用域之外, 可以这样引用内部类 :
OuterClass.InnerClass
内部类中声明的所有静态域都必须是 final 。 原因很简单 。 我们希望一个静态域只有一个实例, 不过对于每个外部对象 , 会分别有一个单独的内部类实例 。 如果这个域不是 final , 它可能就不是唯一的 。
理解:
把一个类声明为内部类,通常是因为它和外围类是有一定联系的。现在我们把手机声明为外围类,手机屏幕声明为内部类
public class MobilePhone {
private String name;
public void create(Screen screen) {
...
// 通过给定的手机屏做了一个手机
...
}
// 内部类,只有内部类可以声明为私有
public class Screen {
//屏幕大小
public static final double size = 5.6;
}
}
现在张三和李四都创建了一个手机。现在假设张三嫌屏幕太小。于是想通过MobilePhone.Screen.size = 10;修改下屏幕的大小。我们知道类的static属性是共享的,那么可想而知李四的屏幕也跟着“膨胀”了。这样会吓到别人滴。为了避免这种风险,对于“共享变量(static)”,一定要final。
内部类的形式
局部内部类
如果仔细地阅读一下 TalkingClock 示例的代码就会发现 , TimePrinter 这个类名字只在start 方法中创建这个类型的对象时使用了一次 。
当遇到这类情况时, 可以在一个方法中定义局部类 。
public void start(int interval, boolean beep)
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
局部类不能用 public 或 private 访问说明符进行声明 。 它的作用域被限定在声明这个局部类的块中 。局部类有一个优势, 即对外部世界可以完全地隐藏起来 。 即使 TalkingClock 类中的其他代码也不能访问它 。 除 start 方法之外 , 没有任何方法知道 TimePrinter 类的存在 。
与其他内部类相比较 , 局部类还有一个优点 。 它们不仅能够访问包含它们的外部类, 还可以访问局部变量 。 不过 , 那些局部变量必须事实上为 final 。 这说明, 它们一旦赋值就绝不会改变 。
package localInnerClass;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
/**
* This program demonstrates the use of local inner classes.
* @version 1.01 2015-05-12
* @author Cay Horstmann
*/
public class LocalInnerClassTest
{
public static void main(String[] args)
{
TalkingClock clock = new TalkingClock(111);
clock.start(1000, true);
// keep program running until user selects "Ok"
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}
/**
* A clock that prints the time in regular intervals.
*/
class TalkingClock
{
private int beep ;
public TalkingClock(int i){
this.beep = i;
}
/**
* Starts the clock.
* @param interval the interval between messages (in milliseconds)
* @param beep true if the clock should beep
*/
public void start(int interval, boolean beep)
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
System.out.println(TalkingClock.this.beep);
System.out.println(beep);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
}
匿名内部类
将局部内部类的使用再深人一步 。 假如只创建这个类的一个对象, 就不必命名了 。 这种类被称为匿名内部类 ( anonymous inner class ) 。
先说一下什么时候使用匿名内部类,即使用前提和条件:必须存在继承和实现关系的时候才可以使用,其实这也很好理解,首先,匿名内类没有名字,那该如何描述以及new个对象呢?对,没错,要通过继承它的父类或者实现一个接口来达成这一目的。
我的理解:
实现接口:
class TalkingClock
{
/**
* Starts the clock.
* @param interval the interval between messages (in milliseconds)
* @param beep true if the clock should beep
*/
public void start(int interval, boolean beep)
{
ActionListener listener = new ActionListener() // listener是一个引用,指向了实现ActionListener接口的一个匿名对象,{
}中就是实现接口的函数
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
}
继承夫类:
public class AnimalTest {
private final String ANIMAL = "动物";
public void accessTest() {
System.out.println("匿名内部类访问其外部类方法");
}
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void printAnimalName() {
System.out.println(bird.name);
}
}
// 这个bird引用指向:一个Animal的匿名子类,覆盖了父类的方法,并且这种匿名类不能有构造函数
Animal bird = new Animal("布谷鸟") {
@Override
public void printAnimalName() {
accessTest(); // 访问外部类成员
System.out.println(ANIMAL); // 访问外部类final修饰的变量
super.printAnimalName();
}
};
public void print() {
bird.printAnimalName();
}
public static void main(String[] args) {
AnimalTest animalTest = new AnimalTest();
animalTest.print();
}
}
静态内部类
有时候 , 使用内部类只是为了把一个类隐藏在另外一个类的内部 , 并不需要内部类引用外围类对象 。 为此 , 可以将内部类声明为 static , 以便取消产生的引用 。
我的理解是:静态内部类不需要访问实例对象,可以访问静态变量,可以把这种内部类看成是外部类的一个属性。
package staticInnerClass;
/**
* This program demonstrates the use of static inner classes.
* @version 1.02 2015-05-12
* @author Cay Horstmann
*/
public class StaticInnerClassTest
{
public static void main(String[] args)
{
double[] d = new double[20];
for (int i = 0; i < d.length; i++)
d[i] = 100 * Math.random();
ArrayAlg.Pair p = ArrayAlg.minmax(d);
System.out.println("min = " + p.getFirst());
System.out.println("max = " + p.getSecond());
ArrayAlg.Pair a = new ArrayAlg.Pair(1,2);
}
}
class ArrayAlg
{
/**
* A pair of floating-point numbers
*/
public static class Pair
{
private double first;
private double second;
/**
* Constructs a pair from two floating-point numbers
* @param f the first number
* @param s the second number
*/
public Pair(double f, double s)
{
first = f;
second = s;
}
/**
* Returns the first number of the pair
* @return the first number
*/
public double getFirst()
{
return first;
}
/**
* Returns the second number of the pair
* @return the second number
*/
public double getSecond()
{
return second;
}
}
/**
* Computes both the minimum and the maximum of an array
* @param values an array of floating-point numbers
* @return a pair whose first element is the minimum and whose second element
* is the maximum
*/
public static Pair minmax(double[] values)
{
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for (double v : values)
{
if (min > v) min = v;
if (max < v) max = v;
}
return new Pair(min, max);
}
}