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"));
}
}