引入

  • 大家先考虑一个场景, 有一个整形数组, 我们希望通过调用一个工具类的排序方法就能对该数组进行排序. 请看下面的代码:
public class Strategy {
public static void main(String[] args) {
int[] arr = {5, 3, 1, 7, 2};
new DataSorter().sort(arr);//调用工具类进行排序
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
} class DataSorter{//用于排序的工具类
public void sort(int[] arr){//调用sort方法进行排序, 此处使用冒泡排序
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
if(arr[j] > arr[j + 1])
swap(arr, j, j + 1);
}
}
} private void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}

 

Comparable接口的来龙去脉

  • 通过上面的代码, 我们能够轻易地对整形数组进行排序, 那么如果现在有了新需求, 需要对浮点类型数据进行排序, 排序工具类应该如何做呢?
  • 或许你会想, 不如就新添加一个排序方法, 方法的参数类型为float类型, 把int类型数组的排序算法复制一遍不就可以了吗?
  • 那如果我继续追问, 如果现在要对一只猫进行排序, 那应该怎么做呢? 猫的类如下
class Cat{
private int age;//猫的年龄
private int weight;//猫的体重 //get / set 方法...
}
  • 你也许会顺着原来的思路回答, 照样copy一份排序的算法, 修改方法参数, 然后在比较的地方指定比较猫的年龄或体重不就可以了吗?
public void sort(Cat[] arr){//以猫数组作为参数
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
if(arr[j].getAge() > arr[j + 1].getAge())//根据猫的年龄作比较
swap(arr, j, j + 1);
}
}
}
  • 但仔细想想, 如果还要继续比较小狗, 小鸡, 小鸭等各种对象, 那么这个排序工具类的代码量岂不是变得很大? 为了能让排序算法的可重用性高一点, 我们希望排序工具中的sort()方法可以对任何调用它的对象进行排序.
  • 你可能会想: 到对任何对象都能排序, 把sort()方法的参数改为Object类型不久可以了嘛. 这个方向是对的, 但是问题是, 当拿到两个Object类型对象, 应该根据什么规则进行比较呢?
  • 这个时候我们自然而然地就希望调用工具类进行排序的对象本身就具备自己的比较法则, 这样在排序的时候就能直接调用对象的排序法则进行排序了.
  • 我们把比较法则抽象为Comparable接口, 凡是要进行比较的类都要实现Comparable接口, 并且定义自己的比较法则, 也就是CompareTo()方法.
  • 这样当我们在封装工具时, 就可以直接对实现了Comparable接口的对象进行比较, 不用担心比较的细节了.
public class Strategy {
public class Strategy {
public static void main(String[] args) {
// Integer[] arr = {5, 3, 1, 7, 2};//注意这里把int改为Integer, Integer是Object的子类
Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
DataSorter ds = new DataSorter();
ds.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
}
} class DataSorter{//用于排序的工具类
public void sort(Object[] arr){//参数类型为Object
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
Comparable c1 = (Comparable) arr[j];//先转为Comparable类型
Comparable c2 = (Comparable) arr[j + 1];
if(c1.CompareTo(c2) == 1)//调用CompareTo()进行比较, 不关心具体的实现
swap(arr, j, j + 1);
}
}
} private void swap(Object[] arr, int i, int j){
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} class Cat implements Comparable{
private int age;
private int weight; @Override
public int CompareTo(Object o) {
if(o instanceof Cat){//先判断传入的是否是Cat类对象, 不是则抛异常
Cat c = (Cat) o;
if(this.age > c.age) return 1;
else if (this.age < c.age) return -1;
else return 0;
}
throw null == o ? new NullPointerException() : new ClassCastException();
} // get / set ...
//toString() ...
} interface Comparable{
public int CompareTo(Object o);
}

 

引入Comparator接口

  • 相信看了上面的Comparable接口来由, 大家会感觉整个设计又美好了一些, 但是其中还有漏洞. 我们在Cat类的CompareTo()方法中, 对猫的比较策略是写死的, 现在我们按猫的年龄比较大小, 如果哪天我们想按照猫的体重比较大小, 又要去修改源码了. 有没有扩展性更好的设计?
  • 我们可以让用户自己定义一个比较器类, 对象可以根据用户指定的比较器比较大小.
  • 整个逻辑是: 如果这个对象需要进行比较, 那么它必须实现Comparable接口, 但是它具体是怎么比较的, 则通过具体的Comparator比较器进行比较.
  • 当然这里少不了多态, 我们首先要定义一个比较器接口Comparator, 用户的比较器需要实现Comparator接口, 下面上代码:
public class Strategy {
public static void main(String[] args) {
Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
DataSorter ds = new DataSorter();
ds.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
} class DataSorter{//用于排序的工具类
public void sort(Object[] arr){//参数类型为Object
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
Comparable c1 = (Comparable) arr[j];//先转为Comparable类型
Comparable c2 = (Comparable) arr[j + 1];
if(c1.CompareTo(c2) == 1)//背后已经转换为使用Comparator的定义的规则进行比较
swap(arr, j, j + 1);
}
}
} private void swap(Object[] arr, int i, int j){
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} class Cat implements Comparable{
private int age;
private int weight;
private Comparator comparator = new CatAgeComparator();//默认持有年龄比较器 @Override
public int CompareTo(Object o) {
return comparator.Compare(this, o);//调用比较器比较而不是直接在此写比较法则
} // get / set / toString ...
} interface Comparable{
public int CompareTo(Object o);
} interface Comparator{
public int Compare(Object o1, Object o2);
} //用户自己定义的, 按照猫的年龄比较大小的比较器
class CatAgeComparator implements Comparator{
@Override
public int Compare(Object o1, Object o2) {
Cat c1 = (Cat) o1;
Cat c2 = (Cat) o2;
if(c1.getAge() > c2.getAge()) return 1;
else if(c1.getAge() < c2.getAge()) return -1;
else return 0;
}
} //按照猫的体重比较大小的比较器
class CatWeightComparator implements Comparator{
@Override
public int Compare(Object o1, Object o2) {
Cat c1 = (Cat) o1;
Cat c2 = (Cat) o2;
if(c1.getWeight() > c2.getWeight()) return 1;
else if(c1.getWeight() < c2.getWeight()) return -1;
else return 0;
}
}

 

什么是策略模式?

  • 在上面的例子中, 我们自己定义了Comparable接口和Comparator接口, 其实这两个接口都是Java自带的, 通过上面的代码示例, 想必大家也应该知道了为什么会有这两个接口.
  • 其实Comparable定义的就是一种比较的策略, 这里的策略你可以理解为一个功能, 然而策略有了, 我们还需要有具体的策略实现, 于是便有了Comparator接口.

 

  • 这里再举一个例子方便大家理解.
  • 现在有一个坦克小游戏, 坦克要能够发射炮弹, 那么我们可以认为发射炮弹就是一种策略, 但是具体到发送什么炮弹, 这可以由具体的策略实现.
  • 到GitHub上看看该坦克游戏
  • 首先定义发射炮弹这种策略
public interface Fire {
public void fire();//发射炮弹的策略
}
  • 为了实现发射炮弹这种策略, 定义策略的具体实现, 也就是定义发射炮弹动作
public interface FireAction {
public void fireAction(Tank tank);
}
  • 坦克想要发送炮弹必须实现Fire()接口, 而且坦克拥有发射炮弹的动作, 至于动作的具体实现, 这里默认给出只发射一颗炮弹的动作.
public class Tank implements TankHitListener, Fire {
//省略各种属性方法...
private FireAction fireAction = new NormalFireAction();//默认动作是只发射一颗炮弹 @Override
public void fire() {
fireAction.fireAction(this);
} //...

使用了策略模式有什么好处?

  • 以上面的坦克游戏为例, 当把发射炮弹定义为一种策略后, 能发射炮弹的对象就不只坦克一个了, 如果游戏中有机关, 可以让机关也实现fire()接口, 获得发射炮弹的能力.
  • 而且在定义策略后我们可以根据策略给出不同的实现方式, 比方说坦克发射炮弹的动作是每次只发射一颗炮弹, 而机关是每次向八个方向发射一颗炮弹. 非常灵活.
  • 结束

 

为什么会有Comparable与Comparator接口? 引入策略模式的更多相关文章

  1. Java中Comparable和Comparator接口区别分析

    Java中Comparable和Comparator接口区别分析 来源:码农网 | 时间:2015-03-16 10:25:20 | 阅读数:8902 [导读] 本文要来详细分析一下Java中Comp ...

  2. Comparable和Comparator接口是干什么的?列出它们的区别。

    Comparable和Comparator接口是干什么的?列出它们的区别. Java提供了只包含一个compareTo()方法的Comparable接口.这个方法可以个给两个对象排序.具体来说,它返回 ...

  3. JDK设计模式之——策略模式(Comparable和Comparator接口)

    策略模式:其实就是java的多态...父类引用指向子类对象. 使用策略模式,改善排序算法上文中需要排序的是一个数组 让他可以对任何类型的数组进行排序 1.利用 接口 Comparable<T&g ...

  4. java Comparable 和 Comparator接口区别

    Comparable 简介 Comparable 是排序接口. 若一个类实现了Comparable接口,就意味着“该类支持排序”.  即然实现Comparable接口的类支持排序,假设现在存在“实现C ...

  5. 【Java】Comparable和Comparator接口的区别

    Java提供了只包含一个compareTo()方法的Comparable接口.这个方法可以个给两个对象排序.具体来说,它返回负数,0,正数来表明已经存在的对象小于,等于,大于输入对象. Java提供了 ...

  6. Java实现单词自定义排序|集合类、工具类排序、comparable、comparator接口

    课题 针对单词进行排序,先按字母的长度排序,长者在前: 在长度相等的情况下,按字典降序排序. 例如,有单词序列"apple banana grape orange",排序后输出结果 ...

  7. Comparable和Comparator接口是干什么的?列出它们的区别

    Java提供了只包含一个compareTo()方法的Comparable接口.这个方法可以个给两个对象排序.具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象. Java提供了 ...

  8. Java Comparable 和 Comparator 接口详解

    本文基于 JDK8 分析 Comparable Comparable 接口位于 java.lang 包下,Comparable 接口下有一个 compareTo 方法,称为自然比较方法.一个类只要实现 ...

  9. Java测试开发--Comparable和Comparator接口(五)

    Comparable 简介Comparable 是排序接口.若一个类实现了Comparable接口,就意味着"该类支持排序".此外,"实现Comparable接口的类的对 ...

随机推荐

  1. IntelliJ IDEA中 todo的使用

    在代码的注释部分加入TODO 大小写忽略,如下图所示 查看项目中有哪些待办项,所下图所示

  2. 推荐一个比crontab更好用的东西:crongo

    This is a crontab service that supports hot plug and high performance. In addition, it supports seco ...

  3. sql server 阻塞查询

    在生产环境下,有时公司客服反映网页半天打不到,除了在浏览器按F12的Network响应来排查,确定web服务器无故障后.就需要检查数据库是否有出现阻塞 当时数据库的生产环境中主表数据量超过2000w, ...

  4. 常用的HTML富文本编译器UEditor、CKEditor、TinyMCE、HTMLArea、eWebEditor、KindEditor简介

    1.UEditor UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点,开源基于BSD协议,允许自由使用和修改代码... 主要特点: 轻量级: ...

  5. 拇指玩」制作的「谷歌安装器」app

    作者:匿名用户链接:https://www.zhihu.com/question/57468448/answer/153000587来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  6. OAthe2 Login use OkHttpClient and OAuth2RestTemplate

    http://samchu.logdown.com/posts/1437422-oathe2-login-use-okhttpclient-and-oauth2resttemplate?utm_sou ...

  7. jQuery学习之旅 Item8 DOM事件操作

    1.jquery页面载入事件 1.传统加载事件 <body onload="函数名()"> 页面全部html和css代码加载完成之后再调用指定的onload函数 win ...

  8. Python 作用域, 局部与全局变量

    全局与局部变量 在子程序(函数)中定义的变量称为局部变量, 在程序的一开始定义的变量称为全局变量 全局变量作用于整个程序, 局部变量作用域是定义该变量的子程序 当全局变量与局部变量重名时: 在定义局部 ...

  9. Celery beat实现定时/轮询任务

    Celery定时任务 配置 启用Celery的定时任务需要设置CELERYBEAT_SCHEDULE .  Celery的定时任务都由celery beat来进行调度.celery beat默认按照s ...

  10. MySQL 大表优化方案

    当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...