美文网首页Java
Java 特殊作用的接口

Java 特殊作用的接口

作者: AlienPaul | 来源:发表于2020-08-05 23:01 被阅读0次

Comparable

Comparable接口用于自定义比较规则。对于Java的原生类型,他们之间的大小比较是依照数学上的大小来比较,自然比较好理解。但是对于自定义的复合类型(通常来说是Java bean),Java怎么比较他们之间的大小呢?

比如说我们自定义了Apple这个类:

class Apple {
    private String name;
    private int weight;
    // setter getter省略
}

Comparable接口正好用于这种场景。Comparable接口只有一个方法compareTo

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

用于定义大小比较的逻辑。

compareTo方法的返回值含义如下:

  • 小于0,当前对象小于比较对象
  • 等于0,当前对象等于计较对象
  • 大于0,当前对象大于比较对象

下面我们举一个完整的例子:

public class ComparableDemo {
    public static void main(String[] args) {
        List<Apple> apples = new ArrayList<>();
        apples.add(new Apple("a", 2));
        apples.add(new Apple("b", 3));
        apples.add(new Apple("c", 1));
        apples.add(new Apple("d", 10));
        apples.add(new Apple("e", 7));
        Collections.sort(apples);
        System.out.println(apples);
    }
}

class Apple implements Comparable<Apple> {
    private String name;
    private int weight;

    public Apple() {
    }

    public Apple(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

// setter getter 省略

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + '\'' +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(Apple o) {
        // 这里我们定义了Apple类型的大小比较逻辑,只比较weight字段
        return this.weight - o.weight;
    }
}

输出结果为:

[Apple{name='c', weight=1}, Apple{name='a', weight=2}, Apple{name='b', weight=3}, Apple{name='e', weight=7}, Apple{name='d', weight=10}]

我们发现Apple最终按照weight字段升序排列。

Comparator

Comparator的作用和Comparable基本相同。但是Comparator不和Java bean绑定,即这个接口不是让Java bean去实现的,而是在需要比较大小的场合(例如排序),作为大小比较规则传入。

我们仍以Apple这个类为例子。去掉Apple类实现的Comparable接口,如下所示:

class Apple {
    private String name;
    private int weight;

    public Apple() {
    }

    public Apple(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

// setter getter 省略

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + '\'' +
                ", weight=" + weight +
                '}';
    }
}

我们接下来调用Collections.sort另一种重载方法:

public static <T> void sort(List<T> list, Comparator<? super T> c)

和上一个例子中的sort方法不同,这里的sort方法不要求T类型对象实现Comparable接口,但是需要传入一个Comparator比较器。
最终代码如下所示:

public class ComparatorDemo {
    public static void main(String[] args) {
        List<Apple> apples = new ArrayList<>();
        apples.add(new Apple("a", 2));
        apples.add(new Apple("b", 3));
        apples.add(new Apple("c", 1));
        apples.add(new Apple("d", 10));
        apples.add(new Apple("e", 7));
        Collections.sort(apples, new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                // 和Comparable的写法基本类似
                return o1.getWeight() - o2.getWeight();
            }
        });
        System.out.println(apples);
    }
}

注意:在这个例子中,Comparator的写法过于复杂,我们可以利用Comparator为我们提供的辅助方法来构建所需的Comparator。我们可以讲其简化如下:

Collections.sort(apples, Comparator.comparingInt(Apple::getWeight));

下面我们讲解下Comparator接口的辅助方法。

Comparator的辅助方法

reversed方法:将已有Comparator的逻辑颠倒,即原先的大于变小于,小于变大于。

default Comparator<T> reversed() {
    return Collections.reverseOrder(this);
}

thenComparing方法:用于串联多个Comparator,如果第一个Comparator比较的结果相等,则使用第二个Comparator进行比较

default Comparator<T> thenComparing(Comparator<? super T> other) {
    Objects.requireNonNull(other);
    return (Comparator<T> & Serializable) (c1, c2) -> {
        int res = compare(c1, c2);
        return (res != 0) ? res : other.compare(c1, c2);
    };
}

Java官方给出的例子:

Comparator<String> cmp = Comparator.comparingInt(String::length)
   .thenComparing(String.CASE_INSENSITIVE_ORDER);

thenComparing还有几重载版本:
通过key提取器(Java bean到用于比较的字段)和key比较器构建:

default <U> Comparator<T> thenComparing(
        Function<? super T, ? extends U> keyExtractor,
        Comparator<? super U> keyComparator)
{
    return thenComparing(comparing(keyExtractor, keyComparator));
}

通过key提取器构建,这里要求key的类型必须实现了Comparable接口,因此不必再提供key比较器。

default <U extends Comparable<? super U>> Comparator<T> thenComparing(
        Function<? super T, ? extends U> keyExtractor)
{
    return thenComparing(comparing(keyExtractor));
}

对于常见的基本类型比较,Comparable接口提供了简化版的thenComparatingXXX方法,如下所示:

default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
    return thenComparing(comparingInt(keyExtractor));
}

default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
    return thenComparing(comparingLong(keyExtractor));
}

default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
    return thenComparing(comparingDouble(keyExtractor));
}

naturalOrder方法:返回自然顺序比较结果(要求对象类型实现Comparable接口,比较逻辑为compareTo方法内容)

public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
    return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}

nullsFirst:返回一个null友好的Comparator,不会因为比较对象为null,报出NullPointerException
比较的逻辑为对于a和b两个待比较对象:

  • 如果a为null,b也为null,则a和b相等
  • 如果a为null,b不为null,则a > b
  • 如果b为null,a不为null,则b > a
  • 如果a和b都不为null,则按照参数comparator的逻辑再次比较
  • 如果a和b都不为null,且参数comparator为null,则认为a和b相等
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
    return new Comparators.NullComparator<>(true, comparator);
}

nullsLast:和nullFirst类似,不同的地方在于如果比较双方有一个为null,null的一方会认为较小,和nullsFirst逻辑相反。

public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
    return new Comparators.NullComparator<>(false, comparator);
}

comparing:根据key提取器和key比较器生成一个新的Comparator

public static <T, U> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor,
        Comparator<? super U> keyComparator)
{
    Objects.requireNonNull(keyExtractor);
    Objects.requireNonNull(keyComparator);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                          keyExtractor.apply(c2));
}

comparing另一版本:根据key比较器生成一个新的Comparator,要求Key的类型必须实现Comparable接口。

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

对于常见类型的比较器,Comparator提供了更为便捷的方式构建Comparator

public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}

public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}

Serializable

Serializable是一个Marker Interface(没有任何方法定义的Interface,用作标记)。

如果一个类型支持序列化,那么它需要实现Serializable接口。

Cloneable

Serializable一样,也是一个Marker Interface。如果一个类可以通过调用Objectclone方法复制,则这个类必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常。

Iterable

Java内置支持如下所示的foreach循环:

for (String s : someList) {
    // ...
}

对于上面这个例子,Java怎么知道someList这个类型是如何被迭代的呢?答案藏在Iterable接口。somelist实现了Iterable接口。Iterable接口有一个iterator方法,要出返回一个适用于迭代该对象的迭代器。

下面我们举一个例子,我们自己实现一个最简单的集合类MyList,支持使用foreach方法迭代。扩容、删除元素和整理元素全都不考虑。代码如下:

// 实现Iterable接口
class MyList<T> implements Iterable<T> {

    // 容量为10
    private static final int CAPACITY = 10;
    // 存储集合元素的载体数组
    private Object[] container = new Object[CAPACITY];
    // 存储当前有几个元素
    private int count = 0;

    @Override
    public Iterator<T> iterator() {
        // 返回一个新的iterator
        return new Iterator<T>() {
            // 记录当前迭代到的元素index
            private int index = 0;
            @Override
            public boolean hasNext() {
                // 如果index小于count,说明没有迭代完,还有下一个元素
                return index < count;
            }

            @Override
            public T next() {
                // 返回元素,然后index自增
                return (T) container[index++];
            }
        };
    }

    // 新增元素的方法
    public void add(T obj) {
        if (count >= CAPACITY) throw new RuntimeException("List is full");
        container[count++] = obj;
    }
}

编写好我们自己的MyList类之后,我们可以使用foreach方式迭代它,代码如下:

public static void main(String[] args) {
    MyList<String> stringMyList = new MyList<>();
    stringMyList.add("hello");
    stringMyList.add("world");
    stringMyList.add("haha");
    
    for (String s : stringMyList) {
        System.out.println(s);
    }
}

AutoCloseable

Java中有一些类,在使用完毕后必须close掉,以释放资源占用(例如IO类)。通常来说我们会这么使用它们。

public static void main(String[] args) {
    FileReader reader = null;
    try {
        reader = new FileReader("~/demo.txt");
        // 其他逻辑
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            if (reader != null) reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里的FileReader在使用完毕之后,务必要记得在finally代码块中关闭。写起来十分复杂。
Java 7中新增了try with代码块语法。在代码执行完try块的时候,会自动调用资源的close方法,可以大量减少不必要的样板代码编写。

// 创建资源的语句写在try后的小括号内
try (FileReader reader = new FileReader("~/demo.txt")) {
    reader.read();
    // ...
} catch (IOException e) {
    e.printStackTrace();
}
// 代码在执行到try代码块之后,会自动调用`close`方法

为了更清楚的向大家演示AutoCloseable的作用,我们自定义一个MyReader,代码如下所示:

class MyReader implements AutoCloseable {
    public void doSomething() {
        System.out.println("Do something");
    }
    @Override
    public void close() {
        // close是AutoClose中唯一一个方法,用来承载资源关闭时候执行的逻辑
        System.out.println("I will close");
    }
}

同样,我们使用try-with代码块调用自己的MyReader

public static void main(String[] args) {
    try (MyReader reader = new MyReader()) {
        reader.doSomething();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("Do something else");
}

程序的输出如下:

Do something
I will close
Do something else

写在最后

本博客会持续更新日常用到的Java特殊接口。欢迎大家跟帖补充。
后续会更新:

  • Future
  • 函数式接口

相关文章

  • Java 特殊作用的接口

    Comparable Comparable接口用于自定义比较规则。对于Java的原生类型,他们之间的大小比较是依照...

  • Java中的关键字

    Java自身定义的一些单词,赋予了其特殊作用。 常见关键字:

  • android JNI NDK入门

    1、JNI(Java Native Interface) Java本地接口,又叫Java原生接口。它允许Java调...

  • 深入理解Java接口

    从java接口是什么到为什么,理解java接口,主要解决三个问题 1.java接口是什么2.java接口为什么3....

  • Eclipseji编辑器——创建java接口

    打开新建 Java 接口向导 新建 Java 接口向导可以创建新的 Java 接口。打开向导的方式有: 点击 Fi...

  • 认识java(一)

    原创 java输入输出 java方法 java常用容器类和接口 外部排序接口:compatator内部排序接口:c...

  • JNI&NDK

    JNI: Java Native Interface (Java本地接口,本地接口即C和C++开发的接口) → 调...

  • JNI调用java自定义类

    一,注册java方法和jni方法相对应 二,通过接口传递java类java定义接口 c++定义接口 三,java层...

  • JAVA 核心笔记 || [8] 接口

    JAVA 接口 JAVA 接口可以有方法和变量,但是方法是抽象的 JAVA 接口 指定做什么 而不是 具体怎么去...

  • java成神之路---集合框架

    标签(空格分隔): java java集合类库的设计思想:“接口与实现分离” java类库中的集合接口和迭代器接口...

网友评论

    本文标题:Java 特殊作用的接口

    本文链接:https://www.haomeiwen.com/subject/ptbizhtx.html