Java学习笔记(十四)

上一篇博客我们探讨了 Java 的接口,主要是设计自己的接口并使用它。

那么这篇博客,我们来探讨一下 Java 的内置接口:

我们都知道,在 Java 程序设计语言中,接口不是类,它是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

我们经常听到服务提供商这样说:“如果类遵从某个特定接口,那么就履行这项服务”。下面给出一个具体的示例。Arrays 类中的 sort 方法承诺可以对对象数组进行排序,但要求满足下列前提: 对象所属的类必须实现了 Comparable 接口。

Comparable 接口是 Java SDK 中的一个内置的泛型接口。

下面是 Comparable 接口的代码:

public interface Comparable<T>{
    int compareTo(T other);
}

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

  1. 将类声明为实现给定的接口。
  2. 对接口中的所有方法进行定义。

要将类声明为实现某个接口,需要使用关键字 implements。当然,也需要实现 CompareTo 方法:

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

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

下面这个代码给出了对一个 Employee 类实例数组进行排序的完整代码,用于对一个员工数组排序:

Employee 类代码

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

}

测试代码

package interfaces;

import java.util.*;

/**
 * This program demonstrates the use of the Comparable interface
 * @version 1.8 2018-2-11
 * @author ShenXueYan
 */

public class EmployeeSortTest {

    public static void main(String[] args) {

        Employee[] 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());;
        }

    }

}

运行结果截图:
这里写图片描述

从以上内容中,我们已经了解了如何对一个对象数组排序,前提是这些对象是实现了 Comparable 接口的类的实例。例如,可以对一个字符串数组排序,因为 String 类实现了 Comparable< String > ,而且 String.compareTo 方法可以按照字典顺序比较字符串。

现在我们希望按照长度递增的顺序对字符串进行排序,而不是按照字典顺序进行排序。当然,我们肯定是不能让 String 类用两种不同的方法实现 compareTo 方法——更何况,String 类也不应该由我们来修改。

要处理这种情况,Arrays.sort 方法还有第二个版本,有一个数组和一个比较器(comparator)作为参数,比较器是实现了 Comparator 接口的类的实例。

public interface Comparator<T>{
    int compare(T first,T second);
}

要按长度比较字符串,可以如下定义一个实现 Comparator< String > 的类:

class LengthComparator implements Comparator<String>{
    public int compare(String first, String second){
        return first.length()-second.length();
    }
    ...
}

具体完成比较时,需要建立一个实例:

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

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

要对一个数组排序,需要为 Arrays.sort 方法传入一个 LengthComparator 对象:

String[] friends = {"Peter","Paul","Mary","Marz"};
Arrays.sort(friends, new LengthComparator());

使用 lambda 表达式可以更容易地使用 Comparator。

Java 程序设计语言中还有一个非常重要的内置接口,称为 Cloneable 。如果某个类实现了这个 Cloneable 接口,Object 类中的 clone 方法就可以创建类对象的一个拷贝。

那么我们开始讨论 Cloneable 接口:

要了解克隆的具体含义,先来回忆为一个包含对象引用的变量建立副本时会发生什么。

Employee original = new Employee("John Public", 50000);
Employee copy = original;
copy.raiseSalary(10); // oops--also changed original

在这种情况下,原变量和副本都是同一个对象的引用。这说明。任何一个变量改变都会影响另一个变量。

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

Employee copy = original.clone();
copy.raiseSalary(10); // OK--original unchanged

不过并没有这么简单。 clone 方法是 Object 的一个 protected 方法,这说明你的代码不能调用这个方法。只有 Employee 类可以克隆 Employee 对象。

默认的克隆操作是浅拷贝,如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域完全没有任何问题。但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆对象仍然会共享一些信息。

浅拷贝会有什么影响吗? 这要看具体情况。如果原对象和浅克隆对象时不可变的,那么这个共享就是安全的。如果子对象属于一个不可变的类,如 String,就是这种情况。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。

不过,通常子对象都是可变的,必须重新定义 clone 方法来建立一个深拷贝,同时克隆所有子对象。

注释: Object 类中 clone 方法声明为 projected。子类只能调用受保护的 clone 方法来克隆它自己的对象。必须重新定义 clone 为 public 才允许所有方法克隆对象。

在这里,Cloneable 接口的出现与接口的正常使用并没有关系。具体地说,它没有指定 clone 方法,这个方法是从 Object 类继承的。这个接口只是作为一个标记,指示类设计者了解克隆过程。对象对于克隆很“偏执”,如果一个对象请求克隆,但没有实现这个接口,就会生成一个受查异常。

即使 clone 的默认(浅拷贝)实现能满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone()。下面给出一个例子:

public class Employee implements Cloneable {
    public Employee clone() throws CloneNotSupportedException{
        return (Emplyee)super.clone();
    }
    ...
}

要建立深拷贝,还需要做更多的工作,克隆对象中可变的实例域。

下面给出完整实例代码:

Employee 类

package clone;

import java.util.Date;
import java.util.GregorianCalendar;

public class Employee implements Cloneable {

    private String name;
    private double salary;
    private Date hireDay;

    public Employee(String name, double salary) {

        this.name = name;
        this.salary = salary;
        hireDay = new Date();
    }

    public Employee clone() throws CloneNotSupportedException{

        Employee cloned = (Employee)super.clone();

        cloned.hireDay = (Date)hireDay.clone();
        return cloned;
    }

    public void setHireDay(int year, int month, int day) {

        Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
        hireDay.setTime(newHireDay.getTime());      
    }

    public void raiseSalary(double byPercent) {

        double raise = salary * byPercent / 100;
        salary += raise;
    }

    public String toString() {

        return "Employee[name=" + name + ", salary=" + salary + ", hireDay=" + hireDay + "]";
    }

}

测试类

package clone;

public class CloneTest {

    public static void main(String[] args) {
        try {
            Employee original = new Employee("Jone Q. public", 50000);
            original.setHireDay(2000, 1, 1);
            Employee copy = original.clone();
            copy.raiseSalary(10);
            copy.setHireDay(2002, 12, 31);
            System.out.println("original=" + original);
            System.out.println("copy=" + copy);
        }
        catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

}

克隆并不常用,有些人认为应该完全避免使用 clone,而实现另一个方法达到相同的目的。什么方法? I don’t know.

最最重要的一点:

注释: 所有数组类型都有一个 public 的 clone 方法,而不是 protected。可以用这个方法建立一个新数组,包含原数组去所有元素的副本。例如:
int[] luckyNumbers = {2,3,5,7,11,13};
int[] cloned = luckyNumbers.clone();
cloned[5] = 12; // doesn’t change luckyNumbers[5]


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值