Algorithm

题目描述

反转一个单链表。

Example

输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

题目解答

迭代解法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution3 {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode current = head;
        while (current != null) {
            ListNode tempNode = current.next;
            current.next = prev;
            prev = current;
            current = tempNode;
        }
        return prev;
    }
}

第一次搞链表的题,还真是给难住了。此题的核心就是当前节点的指针要指向前一个节点,处理过程中还要考虑节点的丢失问题,所以要有一个变量保存当前节点的前一个节点,还要有一个变量来保存当前节点的下一个节点,方便后续的指针前移。此解法的时间复杂度为O(n),因其不需要额外的空间,所以空间复杂度为O(1)。

递归解法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Solution3 {
    public ListNode reverseList2(ListNode node) {

        if (node == null || node.next == null) {
            return node;
        }
        ListNode current = reverseList2(node.next);
        node.next.next = node;
        node.next = null;
        return current;
    }
}

此解的核心在于当有一部分是反转时,后续的反转问题,对于node.next.next = node这句,使用A->B两个节点进行示例,A.next为B,A.next.next为B.next,A.next.next = A为B.next = A,这样就实现了A与B两个节点的反转。这个递归看着还是有点懵,多看看就好了。

Review

Deeply Nested Code 本文是Idea作者代码味道系列的第二篇。主要讲述了开发中多种嵌套的坏味道,这种坏味道在开发中很常见。作者用实例来对坏味道进行了处理。

坏味道的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public MappedField getMappedField(final String storedName) {
    for (final MappedField mf : persistenceFields) {
        for (final String n : mf.getLoadNames()) {
            if (storedName.equals(n)) {
                return mf;
            }
        }
    }
    return null;
}

这个代码里面有两个for和一个if嵌套,这个代码主要逻辑就是在persistenceFields中查找与storedName的MappedField。

使用JAVA8来重写

作者使用JAVA8中的stream和flatMap来进行重构,但是最后只能返回一个带Optional的String,并不能返回那个MappedField。如果是在lambda中再进行for与if的处理看起来会更难受。

更好的进行封装

作者把代码中的第二个for与if抽取出来,形成一个方法,叫做hasName,这个方法就是用来告诉调用者mf中有没有与传入的名相同的名字。这时的代码变成了:

1
2
3
4
5
6
7
8
public MappedField getMappedField(final String storedName) {
    for (final MappedField mf : persistenceFields) {
        if (hasName(storedName)) {
            return mf;
        }
        return null;
    }
}

下一步就是把hasName方法移动MappedField(这里面使用F6就可以进行方法的移动,太神奇了)类中。这样封装后调用者就不必关心MappedField是怎么存储名字的了,封装的优点体现出来了。

额外的处理步骤

在进行了上面的处理后,就可以使用JAVA8的表达式来进行处理了,Stream,filter,findFirst一起。最终也可以返回一个带MappedField的Optional,漂亮。

总结

在Java8以前java中会出现很多for嵌套的语句,对于一些常见的底层集合操作来说可以理解,但是对于这种Domain类来说多层嵌套是不好的,可以对其进行封装,对于一些不需要调用方了解的东西可以在内部进行封装。考虑好类的职责,让代码编写更舒服。

Tip

在学习Linux命令中,对于一个不熟悉的命令,我们可以使用man命令来获取帮助,还可以使用tldr来获取命令的常用操作说明。对于原生的man命令,在命令查看不是很友好,可以使用以下三种办法来进行优化:

  1. 使用oh-my-zsh插件colored-man-pages来使man命令支持颜色显示

  2. mac中把man命令输出到Preview应用中来查看,比如我要查看rsync命令是怎么使用

    1
    
    man -t rsync | open -f -a Preview.app
    
  3. Emacs大法,在emacs中调用man命令就可以随心所欲的查找学习了

Share

今天分享的是Fix NoSuchMethodErrors这篇文章,在开发中总是会遇到NoSuchMethodError(看名字这是一个error,查了一个类继承关系,最终确实是继承自Error)这种情况,文章给出了处理和排查这种问题的方法,药到病除。