专业的JAVA编程教程与资源

网站首页 > java教程 正文

java 核心技术-12版 卷Ⅰ- 6.1 接口

temp10 2024-09-14 12:52:18 java教程 13 ℃ 0 评论

原文

第6章 接口、lambda 表达式与内部类

  • 接口
  • lambda表达式
  • 内部类
  • 服务加载器
  • 代理

到目前为止,你已经学习了 Java 中面向对象编程的核心概念: 类和继承。本章将介绍几种常用的高级技术。尽管这些内容可能不太容易理解,但一定要掌握,以完善你的 Java工具箱。

?首先介绍第一种技术,即接口 (interface),接口用来描述类应该做什么,而不指定它们具体应该如何做。一个类可以实现 (implement)一个或多个接口。只要符合所要求的接口,就可以使用实现了这个接口的类(即实现类)的对象。讨论接口以后,我们会继续介绍lambda 表达式,这是一种简洁的方法,用来创建可以在将来某个时间点执行的代码块。通过使用lambda 表达式,可以用一种精巧而简洁的方式表示使用回调或可变行为的代码。

java 核心技术-12版 卷Ⅰ- 6.1 接口

?接下来,我们将讨论内部类 (inner class) 机制。理论上讲,内部类有些复杂,内部类定义在另外一个类的内部,它们的方法可以访问其外部类的字段。内部类技术在设计合作类集合时很有用。

?在本章的最后还将介绍代理(proxy),这是实现任意接口的对象。代理是一种非常专业的构造,可以用来构建系统级的工具。如果是第一次阅读本书,可以先跳过那一节。

6.1 接口

?在下面的小节中,你会了解 Java 接口是什么以及如何使用,另外还会了解 Java 最新的几个版本中接口的功能有怎样的提升。

6.1.1接口的概念

?在Java 程序设计语言中,接口不是类,而是对希望符合这个接口的类的一组需求。我们经常听到某个服务的提供商这样说:“如果你的类符合某个特定接口,我就会履行这项服务。”下面给出一个具体的示例。Arrays类中的 sort 方法承诺可以对对象数组进行排序,但要求满足下面这个条件:对象所属的类必须实现 Comparable 接口。

下面是 Comparable 接口的代码:

public interface Comparable{
	int compareTo(Object other);
}

在这个接口中,compareTo方法是抽象的,它没有具休实现。任何实现 Comparable 接口的类都需要包含一个 compareTo 方法,这个方法必须接受一个 Object 参数,并返回一个整数。否则、这个类也应当是抽象的,也就是说,你不能构造这个类的对象。

?

注释:在Java5中,Comparable接口已经提升为一个泛型类型

public interface Comparable<T>
{
	int compareTo(T other): // parameter has type T
}

例如、在实现 Comparable<Employee>接口的类中,必须提供以下方法

int compareTo<Employee other)

仍然可以使用不带类型参数的“原始”Comparable类型。这样一来,compareTo 方法就有一个 Object 类型的参数,你必须手动将 compareTo 方法的这个参数强制转换为所希望的类型。稍后我们再做这个工作,所以不用担心同时学习两个新概念。

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

?当然,还有一个接口没有明确说明的额外要求:调用x.compareTo(y) 的时候,这个 compareTo方法实际上必须能够比较两个对象,并返回比较的结果,即x和y哪一个更大。当x小于y时,返回一个负数;当x等于y时,返回0;否则返回一个正数。

?这个特定接口只有一个方法,而有些接口可能包含多个方法。稍后可以看到,接口还可以定义常量。不过,更重要的是要知道接口不能提供什么。接口绝不会有实例字段,在 Java8之前,接口中的方法都是抽象方法。(在 6.1.4节和6.1.5节中可以看到,现在接口中还可以有其他方法。当然,那些方法不能引用实例字段一一接口没有实例。)

?

?现在,假设希望使用Arrays类的 sort 方法对 Employee 对象数组进行排序,Employee 类就必须实现Comparable 接口。

?为了让类实现一个接口,需要完成下面两个步骤:

1.将类声明为实现给定的接口。

2.对接口中的所有方法提供定义。

?要声明一个类实现某个接口,需要使用关键字 implements:

class Employee implements Comparable

?当然,现在 Employee类需要提供 compareTo 方法。假设我们希望根据员工的薪水进行比较。以下是 compareTo 方法的一个实现:

public int compareTo(Object otherObject){

	Employee other = (Employee) otherObject;
  return Double.compare(salary, other.salary);
}

在这里,我们使用了静态 Double.compare 方法。如果第一个参数小于第二个参数,它会返回一个负值;如果二者相等则返回 0; 否则返回一个正值。

警告:在接口声明中,没有将 compareTo 方法声明为 public,这是因为,接口中的所有方法都自动是public方法。不过,在实现接口时,必须把方法声明为 public;否则,编译器将认为这个方法的访问属性是包可访问,这是类中默认的访问属性,之后编译器就会报错,指出你试图提供更严格的访问权限。

?我们可以做得更好一些。可以为泛型 Comparable 接口提供一个类型参数

class Employee implements Comparable<Employee>{
	public int compareTo(Employee other){
    	return Double.compare(salary,other.salary);
    }
}

请注意,对 Object 参数进行强制类型转换总是让人感觉不太顺眼,但现在已经不见了。

?提示:Comparable 接口中的 compareTo 方法将返回一个整数。如果两个对象不相等,返回哪个正值或者负值并不重要。在对两个整数字段进行比较时,这种灵活性非常有用。例如,假设每个员工都有一个唯一的整数 id,你希望根据员工ID号进行排序,那么可以直接返回 idother,id。如果第一个ID号小于另一个ID,这个值将是一个负值;如果两个ID相等,这个值就是 0;否则,这将是一个正值。但有一点需要注意:整数的范围要足够小,以避免减法运算溢出。如果能够确信 ID 为非负数,或者它们的绝对值不会超过(Integer,MAX VALUE-1)/2,就不会出现问题。否则,可以调用静态Integer.compare 方法。

当然,这里的相减技巧不适用于浮点数。因为如果 salary和other,salary 很接近但又不相等,它们的差经过四舍五人后有可能变成 0。如果x<y,Double.compare(x,y)调用会返回 -1;如果x>y则返回1。

注释: Comparable接口的文档建议 compareTo 方法应当与 equals 方法兼容。也就是说,当x.equals(y)时 x.compareTo(y)就应当等于0。Java API中大多数实现 Comparable接口的类都遵从了这个建议。不过有一个重要的例外,就是 BigDecimal。考虑x= new BigDecimal("1.0")和y=new BigDecimal("1.00")。这里xequals(y)为false,因为两个数的精度不同。不过x.compareTo(y)为0。理想情况下应该不返回0,但是没有明确的方法能够确定这两个数哪一个更大。

?现在,我们已经看到,要让一个类使用排序服务必须让它实现 compareTo 方法。这是理所当然的,因为要向 sort 方法提供对象的比较方式。但是为什么不能在 employee 类中直接提供

个compareTo方法(而不实现 omparable 接口)呢?使用接口的主要原因在于: Java 程序设计语言是一种强类型 (strongly typed)语言。调用方法时,编译器要能检查这个方法确实存在。在 sort 方法中可能会有类似下面的语句:

if(a[i].compareTo(a[j]) > 0{
	// rearrange a[i] and a[j]
  // ...
}

编译器必须确认 a[i]一定有一个 compareTo方法。如果a是一个 Comparable 对象的数组就可以确保肯定有 compareTo方法,因为每个实现 Comparable 接口的类都必须提供这个方法。

注释:你可能认为,如果将Arrays 类中的 sort 方法定义为接受一个 Comparable[] 数组, 倘若有人调用 sort 方法时所提供数组的元素类型没有实现 Comparable 接口,编译器就能报错。遗憾的是,事实并非如此。实际上,sort 方法接受一个 Object[]数组,并使用一个笨拙的强制类型转换:

// approach used in the standard library--not recommended

if (((Comparable) a[i]).compareTo(a[j])>0)

// rearrange ali] and alj)

如果a[i] 不属于一个实现了 Comparable 接口的类,虚拟机就会抛出一个异常。

程序清单 6-1 给出了对 Employee 类(见程序清单6-2)实例数组进行排序的完整代码

程序清单6-1 interfaces/EmployeeSortTest.java

package interfaces;

import java.util.*;

/**
 * This program demonstrates the use of the Comparable interface.
 * @version 1.30 2004-02-27
 * @author Cay Horstmann
 */
public class EmployeeSortTest
{
   public static void main(String[] args)
   {
      var staff = new Employee[3];

      staff[0] = new Employee("Harry Hacker", 35000);
      staff[1] = new Employee("Carl Cracker", 75000);
      staff[2] = new Employee("Tony Tester", 38000);

      Arrays.sort(staff);

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
   }
}

程序清单6-2 interfaces/Employee.java

package interfaces;

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;
   }

   /**
    * Compares employees by salary
    * @param other another Employee object
    * @return a negative value if this employee has a lower salary than
    * otherObject, 0 if the salaries are the same, a positive value otherwise
    */
   public int compareTo(Employee other)
   {
      return Double.compare(salary, other.salary);
   }
}

API java.lang.Comparable<T> 1.0

  • int compareTo(T other) 对这个对象与 other 进行比较。如果这个对象小于 other 则返回一个负整数;如果二者相等则返回 0:否则返回一个正整数。

API java.util.Arrays 1.2

  • static void sort(Object[] a) 对数组 a 中的元素进行排序。要求数组中的元素必须属于实现了 Comparable 接口的类, 并且元素之间必须是可比较的。

API java.lang.Integer 1.0

  • static int compare(int x,int y) 7 如果 x<y 返回一个负整数;如果x和y 相等,则返;否则返回一个正整数。

API java.lang.Double 1.0

  • static int compare (double x,double y) 1.4 如果 x<y 返回一个负整数;如果x和y 相等,则返;否则返回一个正整数。

注释:语言标准规定:“对于任意的x和y,实现者必须确保 sgn(x.compareTo(y)) = -sgn(y.compareTo(x))。(也就是说,如果y.compareTo(x)抛出一个异常,那么x.compareTo(y)也应该抛出一个异常。)”这里的 sgn 是一个数的符号:如果n是负值,sgn(n)为 -1; 如果n等于0,sgn(n)为0;如果n是正值,sgn(n)为1。简单地讲,如果翻转 compareTo的参数,结果的符号也应该翻转(但具体值不一定)。

?与 equals 方法一样,使用继承时有可能会出现问题。

?这是因为Manager 扩展了 Employee,而 Employee 实现了 Comparable<Employee>,而不是Comparable<Manager>。如果 Manager 要盖 compareTo,就必须做好准备比较经理与员工,绝不能简单地将员工强制转换成经理:

class Manager extends Employee{

	public int compareTo(Employee other){
		Manager otherManager = (Manager) other; // NO
    }

}

? ?违反了“反对称”规则。如果x是一个 Employee 对象,y是一个Manager 对象,调用x.compareTo(y)不会抛出异常,它只是将x和y都作为员工进行比较。但是反过来y.compareTo(x)将会抛出一个 ClassCastException。

?这种情况与第5章中讨论的 equals 方法一样,补救方式也一样。有两种不同的情况。

?如果不同子类中的比较有不同的含义,就应该将属于不同类的对象之间的比较视为非法。每个 compareTo 方法首先都应该进行以下检测:

if (getClass() != other.getClass()) throw new ClassCastException();

?如果存在一个比较子类对象的通用算法,那么只需要在超类中提供一个 compareTo方法,并将这个方法声明为 final。

?例如,假设你希望经理大于普通员工,而不论薪水多少,那么诸如Executive和Secretary 等其他子类呢? 如果要按照职位排序,那就应该在 Employee 类中提供一个 rank方法。让每个子类覆盖 rank,并实现一个考虑 rank值的 compareTo 方法。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表