u02-容器基础

215 阅读8分钟

1. 容器泛型

概念: 泛型的术语意思就是,适用于许多许多的类型,是JDK5引入的概念,实现了参数化类型的概念。

  • 设计坐标类,可以操作int类型的坐标值。
  • 升级坐标类,可以同时操作int类型,double类型和String类型的坐标。

源码: /javase-advanced/

  • src: c.y.generic.PointTest
/**
 * @author yap
 */
public class PointTest {

    private static class Point {
        private Integer integerX;
        private Integer integerY;
        private Double doubleX;
        private Double doubleY;
        private String stringX;
        private String stringY;

        Integer getIntegerX() {
            return integerX;
        }

        void setIntegerX(Integer integerX) {
            this.integerX = integerX;
        }

        Integer getIntegerY() {
            return integerY;
        }

        void setIntegerY(Integer integerY) {
            this.integerY = integerY;
        }

        Double getDoubleX() {
            return doubleX;
        }

        void setDoubleX(Double doubleX) {
            this.doubleX = doubleX;
        }

        Double getDoubleY() {
            return doubleY;
        }

        void setDoubleY(Double doubleY) {
            this.doubleY = doubleY;
        }

        String getStringX() {
            return stringX;
        }

        void setStringX(String stringX) {
            this.stringX = stringX;
        }

        String getStringY() {
            return stringY;
        }

        void setStringY(String stringY) {
            this.stringY = stringY;
        }
    }

    @Test
    public void pointDemo() {
        Point point = new Point();
        point.setIntegerX(10);
        point.setIntegerY(20);
        System.out.println(point.getIntegerX() + " : " + point.getIntegerY());
        point.setDoubleX(10.5);
        point.setDoubleY(20.5);
        System.out.println(point.getDoubleX() + " : " + point.getDoubleY());
        point.setStringX("东经108°25′");
        point.setStringY("北纬108°25′");
        System.out.println(point.getStringX() + " : " + point.getStringY());
    }
}

1.1 菱形语法

概念: 泛型声明格式也叫菱形语法,使用菱形语法可以不在坐标类中指定成员的具体类型,而是由外部调用者来决定和显示声明,这样的设计使得类的设计变得更加的灵活和简单。

  • 泛型声明:
    • class 类名<泛型, 泛型, ...>{ }
    • 构造器名后面不能使用泛型,但是构造器参数可以。
    • 泛型不能使用基本数据类型。
  • 泛型使用:
    • 标准写法:类名<泛型> 实例 = new 类名<泛型>()
    • 不建议写法:类名<泛型> 实例 = new 类名()
    • JDK8写法:类名<泛型> 实例 = new 类名<>()
    • 泛型擦除:类名 实例 = new 类名():泛型擦除默认在容器内部将泛型向上转型为 Object

源码: /javase-advanced/

  • src: c.y.generic.PointWithGenericTypeTest
/**
 * @author yap
 */
public class PointWithGenericTypeTest {

    private static class Point<T> {
        private T x;
        private T y;

        T getX() {
            return x;
        }

        void setX(T x) {
            this.x = x;
        }

        T getY() {
            return y;
        }

        void setY(T y) {
            this.y = y;
        }
    }

    @Test
    public void build() {
        Point<Integer> pointA = new Point<>();
        pointA.setX(10);
        pointA.setY(30);
        System.out.println(pointA.getX() + " : " + pointA.getY());

        Point<Double> pointB = new Point<>();
        pointB.setX(10.5);
        pointB.setY(20.5);
        System.out.println(pointB.getX() + " : " + pointB.getY());

        Point<String> pointC = new Point<>();
        pointC.setX("东经125°42′ - 130°10′");
        pointC.setY("北纬44°04′ - 46°40′");
        System.out.println(pointC.getX() + " : " + pointC.getY());
    }
}

1.2 泛型检查

概念: javac会对程序的泛型进行运行前检查,若泛型使用错误会编译失败,以此来保证程序的健壮性和安全性,而JVM运行代码的时候会忽略掉所有泛型检查以提高运行效率,所以我们可以利用反射(运行时期的技术手段)来越过泛型检查。

源码: /javase-advanced/

  • src: c.y.generic.SkipGenericTypeCheckTest
/**
 * @author yap
 */
public class SkipGenericTypeCheckTest {

    private static class Demo<T> {
        private T value;

        void setValue(T value) {
            this.value = value;
        }

        void printValue(){
            System.out.println(value);
        }
    }

    @Test
    public void skipGenericTypeCheckByReflect() throws Exception {
        Demo<String> demo = new Demo<>();
        // demo.setValue(10);// compile fail
        Class<?> klass = demo.getClass();
        Method method = klass.getDeclaredMethod("setValue", Object.class);
        method.invoke(demo, 10);
        demo.printValue();
    }
}

1.3 方法泛型

概念: 泛型除了定义在类上,也可以定义在方法上,方法上的泛型可以直接使用类上声明的泛型符号,也可以自己单独声明自定义的泛型符号,原则如下:

  • 静态方法不可以直接使用类上的泛型,因为泛型只有在实例化的过程中才能被确定下来,
  • 自定义的方法泛型必须在 void 或返回值类型之前使用菱形语法进行定义:
    • public <V, K> K method(V t, K k){}
    • public static <Q> void method(Q q){}

源码: /javase-advanced/

  • src: c.y.generic.MethodGenericTypeTest
/**
 * @author yap
 */
public class MethodGenericTypeTest<T> {

	private static <V> V method(V v) {
        String result =
                v instanceof Integer ? "type of Integer" :
                v instanceof Double ? "type of Double" :
                v instanceof String ? "type of String" : "type of Others";
        System.out.println("the param is " + result);
        return v;
    }

    @Test
    public void methodGenericType() {
        Integer resultA = MethodGenericTypeTest.method(15);
        Double resultB = MethodGenericTypeTest.method(15.0);
        String resultC = MethodGenericTypeTest.method("15.0");
        System.out.println(resultA + ", " + resultB + ", " + resultC);
    }
}

1.4 泛型限定

概念: 在方法的返回值或参数类型中,我们可以使用一些特殊的泛型声明格式来对入参的泛型进行限定。

  • <?>:未知类型限定,支持一切泛型。
  • <? super Child>:只接受Child和Child的父类。
  • <? extends Parent>:只接受Parent和Parent的子类。

源码: /javase-advanced/

  • src: c.y.generic.GenericTypeLimitTest
/**
 * @author yap
 */
public class GenericTypeLimitTest {

    private static class Person<T> {}
    private static class Parent {}
    private static class Child extends Parent {}

	@Test
	public void genericTypeLimit() {
		Person<Child> personA = new Person<>();
		Person<Parent> personB = new Person<>();
		allLimit(personA);
		allLimit(personB);
		superLimit(personA);
		superLimit(personB);
		extendsLimit(personA);
		extendsLimit(personB);
	}

	private void allLimit(Person<?> person) {}
	private void superLimit(Person<? super Child> person) {}
	private void extendsLimit(Person<? extends Parent> person) {}
}

1.5 反射泛型

概念: java.lang.reflect 包中提供了一个参数化类型接口 ParameterizedType,它是类型接口 Type 的子接口,表示参数化类型(凡是带有泛型的参数类型都属于它的范畴)。

  • Method 相关API方法:
    • Type[] getGenericParameterTypes():获取方法的所有参数类型及其泛型类型,没有泛型的参数类型也会被获取到。
    • Type getGenericReturnType():获取方法的返回值类型及其泛型类型。
  • Type 相关API方法:
    • String getTypeName():获取参数类型的类全名。
  • ParameterizedType 相关API方法:
    • Type[] getActualTypeArguments():获取泛型类型对应的真实java类型。

源码: /javase-advanced/

  • src: c.y.generic.ReflectGenericTypeTest
/**
 * @author yap
 */
public class ReflectGenericTypeTest {

    private static class Demo<T, K> { }

    private Demo<String, Object> method(Demo<Integer, Double> demo) {
        return null;
    }

    private Method method;

    @Before
    public void before() throws NoSuchMethodException {
        method = ReflectGenericTypeTest.class.getDeclaredMethod("method", Demo.class);
    }

    @Test
    public void getGenericActualParamTypeOfMethod() {
        for (Type paramType : method.getGenericParameterTypes()) {
            System.out.println(paramType.getTypeName());
            if (paramType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) paramType;
                for (Type actualParamType : parameterizedType.getActualTypeArguments()) {
                    System.out.println("\t" + actualParamType.getTypeName());
                }
            }
        }
    }

    @Test
    public void getGenericActualReturnTypeOfMethod() {
        Type returnType = method.getGenericReturnType();
        if (returnType instanceof ParameterizedType) {
            System.out.println(returnType.getTypeName());
            ParameterizedType parameterizedType = (ParameterizedType) returnType;
            for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
                System.out.println("\t" + actualTypeArgument.getTypeName());
            }
        }
    }
}

2. 数据结构-链表

概念: 链表和数组都是存储数据的一种线性结构,链表就像一条锁链,锁链中的每一个铁环,就可以看做是链表的最基本单位:Node 节点。

  • 链表不连续:在每次添加新元素到链表中的时候,链表的节点都是重新new出来的,即使数据类型一样,他们之间的内存地址也是没有规律,互相不连续的,所以链表中的查询操作需要从头节点开始遍历,效率比数组低。
  • 链表不定长:在内存中,链表的节点数量是动态分配的,不固定,所以添加,插入,删除等操作对于链表来说,只需要创建新节点,然后改变几个相关的引用值,就可以完成,效率比数组高。

2.1 单向链表

概念: 在单链表中,我们只要知道了链表头节点的内存地址,就可以顺着之后每一个节点的next后继指针域一直寻找到尾节点,这就是链表的遍历过程,但是单链表的遍历,只能从头到尾,不能从尾到头,是单方向的。

  • 单链表的节点一般分为两个部分:
    • data数据域:用来存储节点数据,如一个字符串、一个User对象等。
    • next后继指针域:用来保存下一个节点的内存地址,以串起整个链表结构。
  • 单链表的尾节点的next指向null,在遍历链表时可以以此为遍历终止的条件。

源码: /javase-advanced/

  • src: c.y.generic.SingleLinkedListTest
/**
 * @author yap
 */
public class SingleLinkedListTest {

    private static class SingleLinkedListDemo<E> {

        private static class Node<E> {
            private E data;
            private Node<E> next;

            private Node(E data) {
                this.data = data;
            }

            @Override
            public String toString() {
                return "[" + data + "-> " + (next == null ? "null" : next.data) + "]";
            }
        }

        private Node<E> head;

        private SingleLinkedListDemo(E headData) {
            this.head = new Node<>(headData);
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder("single-linked-list: ");
            Node<E> current = this.head;
            while (current != null) {
                result.append("[");
                result.append(current.data);
                result.append("-> ");
                result.append(current.next == null ? "null" : current.next.data);
                result.append("] ");
                current = current.next;
            }
            return result.toString();
        }

        /**
         * 重新设置链表头
         *
         * 创建一个新的节点newNode,注入节点内容
         * newNode节点的next指向原头节点
         * newNode变更为链表头节点
         * @param data 链表头节点数据
         * @return 当前链表
         */
        private SingleLinkedListDemo<E> resetHead(E data) {
            Node<E> newNode = new Node<>(data);
            newNode.next = this.head;
            this.head = newNode;
            return this;
        }

        /**
         * 在链表的尾部追加一个节点
         *
         * 创建一个新的节点newNode,注入节点内容
         * 从头开始向后一直寻找,找到链表的尾节点(currentNode)
         * currentNode节点的next指向newNode
         * @param data 节点数据
         * @return 当前链表
         */
        private SingleLinkedListDemo<E> add(E data) {
            Node<E> currentNode = this.head;
            while (currentNode.next != null) {
                currentNode = currentNode.next;
            }
            currentNode.next = new Node<>(data);
            return this;
        }

        /**
         * 在链表指定位置插入一个节点
         *
         * 如果pos<=0,视为重置头节点操作,直接调用resetHead()
         * 创建一个新的节点newNode,注入节点内容
         * 从头开始向后寻找2次(假设pos值为2)
         * 找到链表中原2号位置上的节点(currentNode)
         * 同时找到链表中原2-1号位置上的节点(preNode)
         * 如果寻找过程中就已经到了节点末尾,直接调用add(E data)
         * preNode节点的next指向newNode
         * newNode节点的next指向currentNode
         * @param data 节点数据
         * @param pos 指定位置,从0开始
         * @return 当前链表
         */
        private SingleLinkedListDemo<E> add(E data, int pos) {

            if (pos <= 0) {
                this.resetHead(data);
                return this;
            }

            Node<E> newNode = new Node<>(data);
            Node<E> currentNode = this.head;
            Node<E> preNode = this.head;
            for (int i = 0; i < pos; i++) {
                if (currentNode.next == null) {
                    add(data);
                    return this;
                }
                preNode = currentNode;
                currentNode = currentNode.next;
            }
            preNode.next = newNode;
            newNode.next = currentNode;
            return this;
        }

        /**
         * 获取指定节点数据对应的节点
         *
         * 从头开始向后一直寻找
         * 寻找的过程中不断地用指定值比对每个节点的data
         * 比对成功返回对应节点
         * 比对失败返回null
         * @param data 节点数据
         * @return 节点数据所在的节点
         */
        private Node<E> get(E data) {
            Node<E> result = null;
            Node<E> currentNode = this.head;
            while (currentNode != null) {
                if (data.equals(currentNode.data)) {
                    result = currentNode;
                    break;
                }
                currentNode = currentNode.next;
            }
            return result;
        }

        /**
         * 删除指定节点数据对应的节点
         *
         * 从头开始向后一直寻找
         * 找到链表中对应指定内容的节点(currentNode)
         * 同时找到currentNode的上一个的节点(preNode)
         * 一旦找到,则将preNode节点的next指向currentNode的next
         * @param data 节点数据
         * @return 当前链表
         */
        private SingleLinkedListDemo<E> delete(E data) {
            Node currentNode = this.head;
            Node preNode = this.head;
            while (currentNode != null) {
                if (data.equals(currentNode.data)) {
                    preNode.next = currentNode.next;
                    break;
                }
                preNode = currentNode;
                currentNode = currentNode.next;
            }
            return this;
        }
    }

    private SingleLinkedListDemo<String> linkList;

    @Before
    public void before() {
        linkList = new SingleLinkedListDemo<>("1111");
    }

    @Test
    public void iterator() {
        System.out.println(linkList);
    }

    @Test
    public void resetHead() {
        System.out.println(linkList);
        System.out.println(linkList.resetHead("2222"));
        System.out.println(linkList.resetHead("3333"));
        System.out.println(linkList.resetHead("4444"));
    }

    @Test
    public void add() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println(linkList.add("4444"));
    }

    @Test
    public void addWithPos() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222", 0));
        System.out.println(linkList.add("3333", 9));
        System.out.println(linkList.add("4444", 1));
        System.out.println(linkList.add("5555", 2));
    }

    @Test
    public void get() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println("node: " + linkList.get("2222"));
        System.out.println("node: " + linkList.get("3333"));
        System.out.println("node: " + linkList.get("4444"));
    }

    @Test
    public void delete() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println(linkList.add("4444"));
        System.out.println(linkList.delete("2222"));
        System.out.println(linkList.delete("5555"));
    }
}

2.2 双向链表

概念: 双向链表在访问其中一个节点的时候,既能够向后找到下一个节点,又能向前找到上一个节点。

  • 双链表的节点一般分为三个部分:
    • pre前驱指针域:用来保存上一个节点的内存地址。
    • data数据域:用来存储节点数据,如一个字符串、一个User对象等。
    • next后继指针域:用来保存下一个节点的内存地址。
  • 双链表的尾节点的next指向null,在遍历链表时可以以此为遍历终止的条件。

源码: /javase-advanced/

  • src: c.y.generic.DoubleLinkedListTest
/**
 * @author yap
 */
public class DoubleLinkedListTest<T> {

    private static class DoubleLinkedListDemo<E> {

        private static class Node<E> {
            private E data;
            private Node<E> next;
            private Node<E> pre;

            private Node(E data) {
                this.data = data;
            }

            @Override
            public String toString() {
                return "["
                        + (pre == null ? "null" : pre.data)
                        + "<- " + data + "->"
                        + (next == null ? "null" : next.data)
                        + "]";
            }
        }

        private Node<E> head;

        private DoubleLinkedListDemo(E headData) {
            this.head = new Node<>(headData);
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder("double-linked-list: ");
            Node<E> current = this.head;
            while (current != null) {
                result.append("[");
                result.append(current.pre == null ? "null" : current.pre.data);
                result.append(" <-");
                result.append(current.data);
                result.append("-> ");
                result.append(current.next == null ? "null" : current.next.data);
                result.append("] ");
                current = current.next;
            }
            return result.toString();
        }

        /**
         * 重新设置链表头
         *
         * 创建一个新的节点newNode,注入节点内容
         * 原头节点的pre指向newNode
         * newNode的next指向原头节点
         * newNode变更为链表头
         *
         * @param data 节点内容
         * @return 当前链表
         */
        private DoubleLinkedListDemo<E> resetHead(E data) {
            Node<E> newNode = new Node<>(data);
            this.head.pre = newNode;
            newNode.next = this.head;
            this.head = newNode;
            return this;
        }

        /**
         * 在链表的尾部追加一个节点
         *
         * 创建一个新的节点newNode,注入节点内容
         * 从头开始向后一直寻找,找到链表的尾节点(currentNode)
         * currentNode节点的next指向newNode
         * newNode节点的pre指向currentNode
         *
         * @param data 节点内容
         * @return 当前链表
         */
        private DoubleLinkedListDemo<E> add(E data) {
            Node<E> newNode = new Node<>(data);
            Node<E> currentNode = this.head;
            while (currentNode.next != null) {
                currentNode = currentNode.next;
            }
            currentNode.next = newNode;
            newNode.pre = currentNode;
            return this;
        }

        /**
         * 在链表指定位置插入一个节点
         *
         * 如果pos<=0,视为重置头节点操作,直接调用resetHead()
         * 创建一个新的节点newNode,注入节点内容
         * 从头开始向后寻找2次(假设pos值为2)
         * 找到链表中原2号位置上的节点(currentNode)
         * 同时找到链表中原2-1号位置上的节点(preNode)
         * 如果寻找过程中就已经到了节点末尾,直接调用add(E data)
         * newNode节点的pre指向preNode
         * preNode节点的next指向newNode
         * newNode节点的next指向currentNode
         * currentNode节点的pre指向newNode
         *
         * @param data 节点数据
         * @param pos  指定位置,从0开始
         * @return 当前链表
         */
        private DoubleLinkedListDemo<E> add(E data, int pos) {

            if (pos <= 0) {
                this.resetHead(data);
                return this;
            }

            Node<E> newNode = new Node<>(data);
            Node<E> currentNode = this.head;
            Node<E> preNode = this.head;
            for (int i = 0; i < pos; i++) {
                if (currentNode.next == null) {
                    add(data);
                    return this;
                }
                preNode = currentNode;
                currentNode = currentNode.next;
            }
            newNode.pre = preNode;
            preNode.next = newNode;
            newNode.next = currentNode;
            currentNode.pre = newNode;
            return this;
        }

        /**
         * 获取指定节点数据对应的节点
         *
         * 从头开始向后一直寻找
         * 寻找的过程中不断地用指定值比对每个节点的data
         * 比对成功返回对应节点
         * 比对失败返回null
         *
         * @param data 节点内容
         * @return 节点数据所在的节点
         */
        private Node get(E data) {
            Node<E> result = null;
            Node<E> currentNode = this.head;
            while (currentNode != null) {
                if (data.equals(currentNode.data)) {
                    result = currentNode;
                    break;
                } else {
                    currentNode = currentNode.next;
                }
            }
            return result;
        }

        /**
         * 删除指定节点数据对应的节点
         *
         * 从头开始向后一直寻找
         * 找到链表中对应指定内容的节点(currentNode)
         * currentNode节点的上一个节点的next指向currentNode节点的next
         * currentNode节点的下一个节点的pre指向currentNode节点的pre
         *
         * @param data 节点内容
         * @return 当前链表
         */
        private DoubleLinkedListDemo<E> delete(E data) {
            Node<E> currentNode = this.head;
            while (currentNode != null) {
                if (data.equals(currentNode.data)) {
                    currentNode.pre.next = currentNode.next;
                    currentNode.next.pre = currentNode.pre;
                    break;
                } else {
                    currentNode = currentNode.next;
                }
            }
            return this;
        }

    }

    private DoubleLinkedListDemo<String> linkList;

    @Before
    public void before() {
        linkList = new DoubleLinkedListDemo<>("1111");
    }

    @Test
    public void iterator() {
        System.out.println(linkList);
    }

    @Test
    public void resetHead() {
        System.out.println(linkList);
        System.out.println(linkList.resetHead("2222"));
        System.out.println(linkList.resetHead("3333"));
        System.out.println(linkList.resetHead("4444"));
    }

    @Test
    public void add() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println(linkList.add("4444"));
    }

    @Test
    public void addWithPos() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222", 0));
        System.out.println(linkList.add("3333", 9));
        System.out.println(linkList.add("4444", 1));
        System.out.println(linkList.add("5555", 2));
    }

    @Test
    public void get() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println("node: " + linkList.get("2222"));
        System.out.println("node: " + linkList.get("3333"));
        System.out.println("node: " + linkList.get("4444"));
    }

    @Test
    public void delete() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println(linkList.add("4444"));
        System.out.println(linkList.delete("2222"));
        System.out.println(linkList.delete("5555"));
    }
}

2.3 循环链表

概念: 环链表就是首尾相连的单链表,即尾节点的next指向头节点,从而形成一个环。

  • 循环表的分为两种:
    • 单循环链表:首尾相连的单链表。
    • 双循环链表:首尾相连的双链表。
  • 循环链表的尾节点的next指向头节点,在遍历链表时可以以此为遍历终止的条件。

源码: /javase-advanced/

  • src: c.y.generic.CycleLinkedListTest
/**
 * @author yap
 */
public class CycleLinkedListTest {

    private static class CycleLinkedListDemo<E> {

        private Node<E> head;

        private static class Node<E> {
            private E data;
            private Node<E> next;

            private Node(E data) {
                this.data = data;
            }

            @Override
            public String toString() {
                return "[" + data + "-> " + (next == null ? "null" : next.data) + "]";
            }
        }

        private CycleLinkedListDemo(E headData) {
            this.head = new Node<>(headData);
            this.head.next = this.head;
        }

        /**
         * 在链表的脖子位置插入一个新节点
         *
         * 创建一个新的节点newNode,注入节点内容
         * 备份头节点(headNode)
         * 备份脖子节点(neckNode)
         * headNode的next指向newNode
         * newNode的next指向neckNode
         *
         * @param data 节点内容
         * @return 当前链表
         */
        public CycleLinkedListDemo<E> add(E data) {
            Node<E> newNode = new Node<>(data);
            Node<E> headNode = this.head;
            Node<E> neckNode = headNode.next;
            headNode.next = newNode;
            newNode.next = neckNode;
            return this;
        }

        /**
         * 获取指定节点数据对应的节点
         *
         * 备份头节点(currentNode)
         * 先寻找一次,并改变currentNode的指向为下一个,否则循环不进入
         * 只要currentNode不是头,就一直向后寻找
         * 寻找的过程中不断地用指定值比对每个节点的data
         * 比对成功返回对应节点
         * 比对失败返回null
         *
         * @param data 节点内容
         * @return 节点数据所在的节点
         */
        private Node<E> get(E data) {
            Node<E> result = null;
            Node<E> currentNode = this.head;
            do {
                if (data.equals(currentNode.data)) {
                    result = currentNode;
                    break;
                }
                currentNode = currentNode.next;
            } while (currentNode != this.head);
            return result;
        }

        /**
         * 删除指定节点数据对应的节点
         *
         * 备份头节点(headNode)
         * 备份前一个节点(preNode)
         * 先寻找一次,并改变currentNode的指向为下一个,否则循环不进入
         * 只要currentNode不是头,就一直向后寻找
         * 寻找的过程中不断地用指定值比对每个节点的data
         * 比对成功将preNode的next指向currentNode的.next
         * 比对不成功将currentNode备份为preNode,将currentNode指向下一个
         *
         * @param data 节点内容
         * @return 当前链表
         */
        private CycleLinkedListDemo<E> delete(E data) {
            Node currentNode = this.head;
            Node preNode = this.head;
            do {
                if (data.equals(currentNode.data)) {
                    preNode.next = currentNode.next;
                    break;
                }
                preNode = currentNode;
                currentNode = currentNode.next;
            } while (currentNode != this.head);
            return this;
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder("cycle-linked-list: ");
            Node<E> current = head;
            do {
                result.append("[");
                result.append(current.data);
                result.append("-> ");
                result.append(current.next == null ? "null" : current.next.data);
                result.append("] ");
                current = current.next;
            } while (current != head);
            return result.toString();
        }
    }

    private CycleLinkedListDemo<String> linkList;

    @Before
    public void before() {
        linkList = new CycleLinkedListDemo<>("1111");
    }

    @Test
    public void add() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println(linkList.add("4444"));
    }

    @Test
    public void get() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println("node: " + linkList.get("2222"));
        System.out.println("node: " + linkList.get("3333"));
        System.out.println("node: " + linkList.get("4444"));
    }

    @Test
    public void delete() {
        System.out.println(linkList);
        System.out.println(linkList.add("2222"));
        System.out.println(linkList.add("3333"));
        System.out.println(linkList.add("4444"));
        System.out.println(linkList.delete("2222"));
        System.out.println(linkList.delete("5555"));
    }
}