Silently9527

Silently9527 查看完整档案

成都编辑四川理工学院  |  软件工程 编辑  |  填写所在公司/组织 silently9527.cn 编辑
编辑

个人动态

Silently9527 发布了文章 · 今天 08:00

修炼码德系列:简化条件表达式

image

前言

与面向过程编程相比,面向对象编程的条件表达式相对来说已经比少了,因为很多的条件行为都可以被多态的机制处理掉;但是有时候我们还是会遇到一些小伙伴写出来的条件表达式和面向过程编程没什么差别,比如我遇到过这段代码:

样例

整段代码有三层,每一层还有if-else,本身的这段代码的逻辑就够难以理解了,更加恶心的是这个方法的调用方以及调用的其他方法,同样也是这样的if-else嵌套几层;
加之这段代码还有一个很大的问题是传入的参数对象,在内部以及调用的其他方法中被修改多次修改,这样就更难懂了;靠普通人的单核CPU想看懂太难了,维护这段代码我感觉身体被掏空

身体被掏空

有时候我们可能会遇到比较复杂的条件逻辑,需要我们想办法把分成若干个小块,让分支逻辑和操作细节分离;看一个程序员的码德如何,先看他的条件表达式是否够简洁易懂;今天我们来分享一下简化条件表达式的常用方法,修炼自己的码德;本文中大部分的例子来源于《重构改善既有代码设计》


分解条件表达式

复杂的条件逻辑是最常导致复杂度上升的地方之一,另外如果分支内部逻辑也很多,最终我们会得到一个很大的函数,一个长的方法可读性本身就会下降,所以我们需要把大的方法才分的多个的方法,为每个方法取一个容易清楚表达实现内部逻辑的方法名,这样可读性就会上大大提高。

举例:

if (date.before (SUMMER_START) || date.after(SUMMER_END)) {
    charge = quantity * _winterRate + _winterServiceCharge;
} else {
    charge = quantity * _summerRate
}

这种代码很多人可能都觉得没必要去提取方法,但是如果我们想要看懂这段代码,还是必须的去想想才知道在做什么;接下来我们修改一下

if (notSummer(date)) {
    charge = winterCharge(quantity);
} else {
    charge = summerCharge(quantity);
}

private boolean notSummer(Date date){
    date.before (SUMMER_START) || date.after(SUMMER_END)
}

private double summerCharge(int quantity) {
    return quantity * _summerRate;
}

private double winterCharge(int quantity) {
    return quantity * _winterRate + _winterServiceCharge;
}

这样修改之后是不是很清楚,好的代码本身不需要写注释(代码具有自说明性),更不需要在方法内部写任何注释,有时候我们会看到有同学会在方法内部隔几行就会写一点注释,这说明本身代码的自说明性不够好,可以通过刚才这个例子的方式提高代码的可读性


合并条件表达式

当遇到一段代码多个if条件判断,但是条件内部的逻辑缺类似,我们可以把条件合并在一起,然后抽取方法。

举例1:

double disabilityAmount () {
    if(_seniortiy <2 ) 
        return 0;
    if(_monthsDisabled > 12)
        return 0;
    if(_isPartTime)
        return 0;
    // 省略...
}

这里的条件返回的结果都是一样的,那么我们先把条件合并起来

double disabilityAmount () {
    if(_seniortiy <2 || _monthsDisabled > 12 || _isPartTime) {
        return 0;
    }
    // 省略...
}

接下来我们再来把判断条件判断条件抽取成方法提高可读性

double disabilityAmount () {
    if(isNotEligibleForDisableility()) {
        return 0;
    }
    // 省略...
}

boolean isNotEligibleForDisableility() {
    return _seniortiy <2 || _monthsDisabled > 12 || _isPartTime;
}

举例2:

if(onVacation()) {
    if(lengthOfService() > 10) {
        return 2;
    }
}
return 1;

合并之后的代码

if(onVacation() && lengthOfService() > 10){
    return 2
}
return 1;

接着我们可以使用三元操作符更加简化,修改后的代码:

return onVacation() && lengthOfService() > 10 ? 2 : 1;

通过这两个例子我们可以看出,先把条件逻辑与分支逻辑抽离成不同的方法分离开,然后我们会发现提高代码的可读性是如此的简单,得心应手;所以抽离好的方法是关键;我觉得此处应该有掌声

我膨胀了


合并重复的条件片段

我们先来看一个例子,10岁以下的小朋友票价打5折

if(ageLt10(age)) {
    price = price * 0.5;
    doSomething();
} else {
    price = price * 1;
    doSomething();
}

我们发现不同的分支都执行了相同的末段代码逻辑,这时候我们可以把这段代码提取到条件判断之外,这里举得例子较为简单,通常工作中遇到的可能不是这样一个简单的方法,而是很多行复杂的逻辑条件,我们可以先把这个代码提取成一个方法,然后把这个方法的调用放在条件判断之前或之后

修改之后的代码

if(ageLt10(age)) {
    price = price * 0.5;
} else {
    price = price * 1;
}
doSomething();

当我们遇到try-catch中有相同的逻辑代码,我们也可以使用这种方式处理


卫语句取代嵌套条件表达式

方法中一旦出现很深的嵌套逻辑让人很难看懂执行的主线。当使用了if-else表示两个分支都是同等的重要,都是主线流程;向下图表达的一样,
if-else

但是大多数时候我们会遇到只有一条主线流程,其他的都是个别的异常情况,在这种情况下使用if-else就不太合适,应该用卫语句取代嵌套表达式。
if-reture

举例1:

在薪酬系统中,以特殊的规则处理死亡员工,驻外员工,退休员工的薪资,这些情况都是很少出现,不属于正常逻辑;

double getPayAmount() {
    double result;
    if(isDead){
        result = deadAmount();
    } else {
        if(isSeparated) {
            result = separatedAmount();
        } else {
            if(isRetired) {
                result = retiredAmount();
            } else {
                result = normalPayAmount();
            }
        }
    }
    return result;
}

在这段代码中,我们完全看不出正常流程是什么,这些偶尔发生的情况把正常流程给掩盖了,一旦发生了偶然情况,就应该直接返回,引导代码的维护者去看一个没用意义的else只会妨碍理解;让我们用return来改造一下

double getPayAmount() {
    if(isDead){
        return deadAmount();
    }
    if(isSeparated) {
        return separatedAmount():
    }
    if(isRetired) {
        return retiredAmount();
    }
    return normalPayAmount();
}

多态取代条件表达式

有时候我们会遇到if-else-if或者switch-case这种结构,这样的代码不仅不够整洁,遇到复杂逻辑也同样难以理解。这种情况我们可以利用面向对象的多态来改造。

举例:
假如你正在开发一款游戏,需要写一个获取箭塔(Bartizan)、弓箭手(Archer)、坦克(Tank)攻击力的方法;经过两个小时的努力终于完成了这个功能;开发完成后的代码如下:

int attackPower(Attacker attacker) {
    switch (attacker.getType()) {
        case "Bartizan":
            return 100;
        case "Archer":
            return 50;
        case "Tank":
            return 800;
    }
    throw new RuntimeException("can not support the method");
}

经过自测后没有任何问题,此时你的心情很爽

心情很爽

当你提交代码交由领导review的时候,领导(心里想着这点东西搞两个小时,上班摸鱼太明显了吧)直接回复代码实现不够优雅,重写

1. 枚举多态

你看到这个回复虽然心里很不爽,但是你也没办法,毕竟还是要在这里混饭吃的;嘴上还是的回答OK

你思考了一会想到了使用枚举的多态来实现不就行了,说干就干,于是你写了下一个版本

int attackPower(Attacker attacker) {
   return AttackerType.valueOf(attacker.getType()).getAttackPower();
}

enum AttackerType {
    Bartizan("箭塔") {
        @Override
        public int getAttackPower() {
            return 100;
        }
    },
    Archer("弓箭手") {
        @Override
        public int getAttackPower() {
            return 50;
        }
    },
    Tank("坦克") {
        @Override
        public int getAttackPower() {
            return 800;
        }
    };

    private String label;

    Attacker(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }

    public int getAttackPower() {
        throw new RuntimeException("Can not support the method");
    }
}

这次再提交领导review,顺利通过了,你心想总于get到了领导的点了

2. 类多态

没想到你没高兴几天,又接到个新的需求,这个获取攻击力的方法需要修改,根据攻击者的等级不同而攻击力也不同;你考虑到上次的版本攻击力是固定的值,使用枚举还比较合适,而这次的修改要根据攻击者的本身等级来计算攻击了,如果再使用枚举估计是不合适的;同时你也想着上次简单实现被领导怼了,这次如果还是在上次的枚举版本上来实现,估计也不会有好结果;最后你决定使用类的多态来完成

int attackPower(Attacker attacker) {
    return attacker.getAttackPower();
}

interface Attacker {
    default int getAttackPower() {
        throw new RuntimeException("Can not support the method");
    }
}

class Bartizan implements Attacker {
    public int getAttackPower() {
        return 100 * getLevel();
    }
}

class Archer implements Attacker {
    public int getAttackPower() {
        return 50 * getLevel();
    }
}

class Tank implements Attacker {
    public int getAttackPower() {
        return 800 * getLevel();
    }
}

完成之后提交给领导review,领导笑了笑通过了代码评审;

3. 策略模式

你本以为这样就结束了,结果计划赶不上变化,游戏上线后,效果不是太好,你又接到了一个需求变更,攻击力的计算不能这么粗暴,我们需要后台配置规则,让部分参加活动玩家的攻击力根据规则提升。

你很生气,心里想着:没听说过杀死程序员不需要用枪吗,改三次需求就可以了,MD这是想我死吗。

改需求

生气归生气,但是不敢表露出来,谁让你是领导呢,那就开搞吧

考虑到本次的逻辑加入了规则,规则本身可以设计的简单,也可以设计的很复杂,如果后期规则变得更加复杂,那么整个攻击对象类中会显得特别的臃肿,扩展性也不好,所以你这次不再使用类的多态来实现,考虑使用策略模式,完成后代码如下:

//定义计算类的接口
interface AttackPowerCalculator {
    boolean support(Attacker attacker);

    int calculate(Attacker attacker);
}

//箭塔攻击力计算类
class BartizanAttackPowerCalculator implements AttackPowerCalculator {

    @Override
    public boolean support(Attacker attacker) {
        return "Bartizan".equals(attacker.getType());
    }

    @Override
    public int calculate(Attacker attacker) {
        //根据规则计算攻击力
        return doCalculate(getRule());
    }
}

//弓箭手攻击力计算类
class ArcherAttackPowerCalculator implements AttackPowerCalculator {

    @Override
    public boolean support(Attacker attacker) {
        return "Archer".equals(attacker.getType());
    }

    @Override
    public int calculate(Attacker attacker) {
        //根据规则计算攻击力
        return doCalculate(getRule());
    }
}

//坦克攻击力计算类
class TankAttackPowerCalculator implements AttackPowerCalculator {

    @Override
    public boolean support(Attacker attacker) {
        return "Tank".equals(attacker.getType());
    }

    @Override
    public int calculate(Attacker attacker) {
        //根据规则计算攻击力
        return doCalculate(getRule());
    }
}

//聚合所有计算类
class AttackPowerCalculatorComposite implements AttackPowerCalculator {
    List<AttackPowerCalculator> calculators = new ArrayList<>();

    public AttackPowerCalculatorComposite() {
        this.calculators.add(new TankAttackPowerCalculator());
        this.calculators.add(new ArcherAttackPowerCalculator());
        this.calculators.add(new BartizanAttackPowerCalculator());
    }

    @Override
    public boolean support(Attacker attacker) {
        return true;
    }

    @Override
    public int calculate(Attacker attacker) {
        for (AttackPowerCalculator calculator : calculators) {
            if (calculator.support(attacker)) {
                calculator.calculate(attacker);
            }
        }
        throw new RuntimeException("Can not support the method"); 
    }
}

//入口处通过调用聚合类来完成计算
int attackPower(Attacker attacker) {
    AttackPowerCalculator calculator = new AttackPowerCalculatorComposite();
    return calculator.calculate(attacker);
}

你再次提交代码给领导review,领导看了很满意,表扬你说:小伙子,不错,进步很快嘛,给你点赞哦;你回答:感谢领导认可(心里想那是当然,毕竟我已经摸清了你的点在哪里了)

觉得本次你的这个功能完成的还比较满意的,请点赞关注评论走起哦

骄傲

引入断言

最后一个简化条件表达式的操作是引入断言,这部分比较简单,并且Spring框架本身也提供了断言的工具类,比如下面这段代码:

public void getProjectLimit(String project){
    if(project == null){
        throw new RuntimeException("project can not null");
    }
    doSomething();
}

加入Spring的断言后的代码

public void getProjectLimit(String project){
    Assert.notNull(project,"project can not null");
    doSomething();
}
写在最后
感谢大家可以耐心地读到这里。
当然,文中或许会存在或多或少的不足、错误之处,有建议或者意见也非常欢迎大家在评论交流。
最后,希望朋友们可以点赞评论关注三连,因为这些就是我分享的全部动力来源🙏

往期Java8系列文章:

查看原文

赞 1 收藏 0 评论 0

Silently9527 关注了用户 · 12月2日

敖丙 @aobing

关注 3892

Silently9527 回答了问题 · 12月1日

解决数据库定时查询大量数据

这种一般来说数据都放入到es中,然后有后台离线计算项目处理

关注 2 回答 1

Silently9527 回答了问题 · 12月1日

springboot 开启https后,项目启动第一次可以成功访问,关闭浏览器再访问报错

为什么https要在项目中配置,建议是在NGINX中配置

server {
     #SSL 访问端口号为 443
     listen 443 ssl; 
     #填写绑定证书的域名
     server_name cloud.tencent.com; 
     #证书文件名称
     ssl_certificate 1_cloud.tencent.com_bundle.crt; 
     #私钥文件名称
     ssl_certificate_key 2_cloud.tencent.com.key; 
     ssl_session_timeout 5m;
     #请按照以下协议配置
     ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 
     #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
     ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
     ssl_prefer_server_ciphers on;
     location / {
        #网站主页路径。此路径仅供参考,具体请您按照实际目录操作。
         root /var/www/cloud.tencent.com; 
         index  index.html index.htm;
     }
 }

参考文档:
https://cloud.tencent.com/doc...

关注 2 回答 2

Silently9527 回答了问题 · 12月1日

请问这个时间戳的值是怎么得到的?

这个应该是程序做了签名,没有拿到源码,只能靠猜,应该是没办法破解

关注 2 回答 1

Silently9527 回答了问题 · 11月30日

@ManyToOne如何更新主键

这种直接用语句更新好点

关注 2 回答 1

Silently9527 关注了专栏 · 11月30日

全栈修炼

分享 大前端开发 相关知识,全栈程序员的成长之路,公众号【全栈修炼】。

关注 1057

Silently9527 发布了文章 · 11月30日

如何高效的使用并行流

image

在Java7之前想要并行处理大量数据是很困难的,首先把数据拆分成很多个部分,然后把这这些子部分放入到每个线程中去执行计算逻辑,最后在把每个线程返回的计算结果进行合并操作;在Java7中提供了一个处理大数据的fork/join框架,屏蔽掉了线程之间交互的处理,更加专注于数据的处理。


Fork/Join框架

Fork/Join框架采用的是思想就是分而治之,把大的任务拆分成小的任务,然后放入到独立的线程中去计算,同时为了最大限度的利用多核CPU,采用了一个种工作窃取的算法来运行任务,也就是说当某个线程处理完自己工作队列中的任务后,尝试当其他线程的工作队列中窃取一个任务来执行,直到所有任务处理完毕。所以为了减少线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行;在百度找了一张图

image

  • 使用RecursiveTask

使用Fork/Join框架首先需要创建自己的任务,需要继承RecursiveTask,实现抽象方法

protected abstract V compute();

实现类需要在该方法中实现任务的拆分,计算,合并;伪代码可以表示成这样:

if(任务已经不可拆分){
    return 顺序计算结果;
} else {
    1.任务拆分成两个子任务
    2.递归调用本方法,拆分子任务
    3.等待子任务执行完成
    4.合并子任务的结果
}
  • Fork/Join实战

任务:完成对一亿个自然数求和

我们先使用串行的方式实现,代码如下:

long result = LongStream.rangeClosed(1, 100000000)
                .reduce(0, Long::sum);
System.out.println("result:" + result);

使用Fork/Join框架实现,代码如下:

public class SumRecursiveTask extends RecursiveTask<Long> {
    private long[] numbers;
    private int start;
    private int end;

    public SumRecursiveTask(long[] numbers) {
        this.numbers = numbers;
        this.start = 0;
        this.end = numbers.length;
    }

    public SumRecursiveTask(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        int length = end - start;
        if (length < 20000) {  //小于20000个就不在进行拆分
            return sum();
        }
        SumRecursiveTask leftTask = new SumRecursiveTask(numbers, start, start + length / 2); //进行任务拆分
        SumRecursiveTask rightTask = new SumRecursiveTask(numbers, start + (length / 2), end); //进行任务拆分
        leftTask.fork(); //把该子任务交友ForkJoinPoll线程池去执行
        rightTask.fork(); //把该子任务交友ForkJoinPoll线程池去执行
        return leftTask.join() + rightTask.join(); //把子任务的结果相加
    }


    private long sum() {
        int sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }


    public static void main(String[] args) {
        long[] numbers = LongStream.rangeClosed(1, 100000000).toArray();

        Long result = new ForkJoinPool().invoke(new SumRecursiveTask(numbers));
        System.out.println("result:" +result);
    }
}
Fork/Join默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().available- Processors()得到的。 但是你可以通过系统属性java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 这是一个全局设置,因此它将影响代码中所有的并行流。目前还无法专为某个 并行流指定这个值。因为会影响到所有的并行流,所以在任务中经历避免网络/IO操作,否则可能会拖慢其他并行流的运行速度

parallelStream

以上我们说到的都是在Java7中使用并行流的操作,Java8并没有止步于此,为我们提供更加便利的方式,那就是parallelStreamparallelStream底层还是通过Fork/Join框架来实现的。

  • 常见的使用方式

1.串行流转化成并行流

LongStream.rangeClosed(1,1000)
                .parallel()
                .forEach(System.out::println);

2.直接生成并行流

 List<Integer> values = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            values.add(i);
        }
        values.parallelStream()
                .forEach(System.out::println);
  • 正确的使用parallelStream

我们使用parallelStream来实现上面的累加例子看看效果,代码如下:

public static void main(String[] args) {
    Summer summer = new Summer();
    LongStream.rangeClosed(1, 100000000)
            .parallel()
            .forEach(summer::add);
    System.out.println("result:" + summer.sum);

}

static class Summer {
    public long sum = 0;

    public void add(long value) {
        sum += value;
    }
}

运行结果如下:

result

运行之后,我们发现运行的结果不正确,并且每次运行的结果都不一样,这是为什么呢?
这里其实就是错用parallelStream常见的情况,parallelStream是非线程安全的,在这个里面中使用多个线程去修改了共享变量sum, 执行了sum += value操作,这个操作本身是非原子性的,所以在使用并行流时应该避免去修改共享变量。

修改上面的例子,正确使用parallelStream来实现,代码如下:

long result = LongStream.rangeClosed(1, 100000000)
        .parallel()
        .reduce(0, Long::sum);
System.out.println("result:" + result);

在前面我们已经说过了fork/join的操作流程是:拆子部分,计算,合并结果;因为parallelStream底层使用的也是fork/join框架,所以这些步骤也是需要做的,但是从上面的代码,我们看到Long::sum做了计算,reduce做了合并结果,我们并没有去做任务的拆分,所以这个过程肯定是parallelStream已经帮我们实现了,这个时候就必须的说说Spliterator

Spliterator是Java8加入的新接口,是为了并行执行任务而设计的。

public interface Spliterator<T> {
    boolean tryAdvance(Consumer<? super T> action);

    Spliterator<T> trySplit();

    long estimateSize();

    int characteristics();
}

tryAdvance: 遍历所有的元素,如果还有可以遍历的就返回ture,否则返回false

trySplit: 对所有的元素进行拆分成小的子部分,如果已经不能拆分就返回null

estimateSize: 当前拆分里面还剩余多少个元素

characteristics: 返回当前Spliterator特性集的编码


总结

  1. 要证明并行处理比顺序处理效率高,只能通过测试,不能靠猜测(本文累加的例子在多台电脑上运行了多次,也并不能证明采用并行来处理累加就一定比串行的快多少,所以只能通过多测试,环境不同可能结果就会不同)
  2. 数据量较少,并且计算逻辑简单,通常不建议使用并行流
  3. 需要考虑流的操作时间消耗
  4. 在有些情况下需要自己去实现拆分的逻辑,并行流才能高效

感谢大家可以耐心地读到这里。
当然,文中或许会存在或多或少的不足、错误之处,有建议或者意见也非常欢迎大家在评论交流。
最后,希望朋友们可以点赞评论关注三连,因为这些就是我分享的全部动力来源🙏
查看原文

赞 0 收藏 0 评论 0

Silently9527 发布了文章 · 11月26日

面试者必看:Java8中的默认方法

封面图

背景

在Java8之前,定义在接口中的所有方法都需要在接口实现类中提供一个实现,如果接口的提供者需要升级接口,添加新的方法,那么所有的实现类都需要把这个新增的方法实现一遍,如果说所有的实现类能够自己控制的话,那么还能接受,但是现实情况是实现类可能不受自己控制。比如说Java中的集合框架中的List接口添加一个方法,那么Apache Commons这种框架就会很难受,必须修改所有实现了List的实现类


现在的接口有哪些不便

  1. 向已经发布的接口中添加新的方法是问题的根源,一旦接口发生变化,接口的实现者都需要更新代码,实现新增的接口
  2. 接口中有些方法是可选的,不是所有的实现者都需要实现,这个时候实现类不得不实现一个空的方法,或者是提供一个Adapter对接口中所有的方法做空实现,在Spring中我们可看到很多这种例子,比如WebMvcConfigurerAdapter
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    }


    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
    
    省略其他代码...
}

在Java8以后这些类都被标注成了过期@Deprecated


默认方法的简介

为了解决上述问题,在Java8中允许指定接口做默认实现,未指定的接口由实现类去实现。如何标识出接口是默认实现呢?方法前面加上default关键字。比如Spring中的WebMvcConfigurer

public interface WebMvcConfigurer {

    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
    
    省略其他代码...
}

从现在看来,可能大家都会有个疑问,默认方法和抽象类有什么区别呢?

    1. 默认方法不能有实例变量,抽象类可以有
    1. 一个类只能继承一个抽象类,当时可以实现多个接口

默认方法的使用场景

  • 可选方法

为接口提供可选的方法,给出默认实现,这样实现类就不用显示的去提供一个空的方法;这种场景刚才我们在上面已经说到了,spring中有大量的例子

  • 实现多继承

继承是面向对象的特性之一,在Java中一直以来都是单继承的原则,Java8中默认方法为实现多继承提供了可能(由于接口中不能有实例对象,所以能够抽象的到接口中的行为一般都是比较小的模块);从个人的经历来看,做游戏是训练自己面向对象思维的最好方式(以后有机会分享一下小游戏的制作),因为现在大部分学Java的同学学完Java基础后就直接进入JavaWeb的学习,整合各种框架,只能在通用的三层架构(Controller、Service、Dao)中写自己的逻辑。

相信很多人在都做个坦克大战的游戏,如果用Java8中的默认方法如何设计好多继承呢?
类设计图

这里我们举个简单的例子,定义了三个接口:

  1. Moveable:允许移动的物体,把移动的逻辑放入到这个接口中的默认方法
  2. Attackable: 允许攻击的物体,把攻击的通用逻辑放入到默认方法,不通用的逻辑通过模板方法给实现类处理
  3. Location: 获取物体的坐标

通过这些接口的组合方式,我们就可以为游戏创建不同的实现类,比如说坦克、草地、墙壁...


解决默认方法冲突规则(面试者必看)

通过上面的例子,我们体验的默认方法给我们带来了多继承的便利,但是让我们思考下,如果出现了不同的类出现的相同签名的默认方法,实际在运行的时候应该如何选择呢?客官不慌,有办法的

    1. 类中的方法优先级最高。如果类或者父类(抽象类也OK)中声明了相同签名的方法,那么优先级最高
    1. 如果第一条无法确定,那么最具体的的实现的默认方法;很绕,举例子:B接口继承了A,B就更加具体,那么B中的方法优先级最高
    1. 如果上面两个都无法判断,那么编译会报错,需要在实现接口,然后手动显式调用
public class C implements A, B  {
    void pint() {
        B.supper.print();  // 显式调用
    }
}

菱形继承问题

为了说明上面的三个原则,我们直接来看看最复杂的菱形继承问题

菱形继承问题

public interface A {
    default void print(){
        System.out.println("Class A");
    }
}

public interface B extend A {}

public interface C extend A {}

public class D implement B, C {
    public static void main(String[] args) {
       new D().print()
    }
}

这种情况下B,C都没有自己的实现,实际上就只有A有实现,那么会打印Class A

如果说这个时候把接口B接口改一下

public interface B extends A {
    default void print(){
        System.out.println("Class B");
    }
}

根据原则(2),B继承于A,更加具体,所以打印结果应该是B

如果说把接口C修改一下

public interface C extends A {
    default void print(){
        System.out.println("Class C");
    }
}

这时候我们发现编译报错,需要我们自己手动指定,修改D中的代码

public class D implements B, C {

    @Override
    public void print() {
        C.super.print();
    }

    public static void main(String[] args) {
        new D().print();
    }
}

总结

  • Java8中的默认方法需要使用default来修饰
  • 默认方法的使用场景可选方法和多继承
  • 三个原则解决相同签名的默认方法冲突问题
查看原文

赞 0 收藏 0 评论 0

Silently9527 回答了问题 · 11月22日

java 报错求原因?

https://gongshang.mingluji.com:443
这个链接是https 你有没有绕过证书呢

关注 2 回答 1

认证与成就

  • 获得 6 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 11月13日
个人主页被 473 人浏览