数据结构Data Structure

链表Linked List

LinkedList

Q45

认知

背景

Java 的基础数组 无法扩展,必须在创建时确定尺寸。

需要一个可以自行扩展的数据结构

什么是

能够自行扩展尺寸的数组

内存简图

原始内存
简化内存图

重点关注堆空间,堆空间变量为方块

指针变量化为箭头

栈空间变量不是重点,化为小圆圈

链表结构设计

理念
火车车头

对象

车头 勾着 第一节车厢

火车车厢

对象

车厢 存储着 一个数据

同时 勾着 下一结车厢

数量

一个链表 需要

1 个 车头 以及

0 ~ n 个 车厢

结构

链表样例

车头勾着车厢,车厢勾着下一结车厢,末尾车厢不勾

内存
简化内存

链表添加元素

在末尾车厢后面,再勾一个新车厢

内存
简化内存

空链表

有车头,没车厢

内存
简化内存

随堂练习

LinkedList 基础功能

Q45 E01

稳健性Robustness

背景

public 的方法就是对外公开的

谁都可以调用, 而且可能会不按照你预期调用

什么是

健壮性 / 鲁棒性 / 稳定性 / 稳健性

有能力处理运行时错误, 处理错误的输入

不管怎么运行都不崩溃

可能破坏稳健性的诱因

1.

类型错误

2.

范围错误

3.

边界情况

类型错误

void add(int value)

增加一个新的结点在链表末端, 值为 value

错误情况
1!
list.add("10");
分析

Java 的编译器 会在编译时 检测问题

如果有, 就飘红

范围错误

int get(int index)

返回第 index 个元素的值

错误情况
1 2
list.get(7);list.get(-1);
分析

如果 list 只有 3 个元素

Java 编译器不会帮你检查

需要自己进行验证, 保证数值范围合理

边界情况

void add(int value)

增加一个新的结点在链表末端, 值为 value

错误情况
1 2 3 4 5 6 7 8 9 10 11 12
public class LinkedList { ... public void add(int value) { Node node = new Node(value); Node current = first; while(current.getNext() != null) { current = current.getNext(); } current.setNext(node); } ...}
分析

如果当前 list 没有结点...

边界情况下, 处理代码可能有所不同

需要自己进行边界检测, 保证边界情况运行正常

特殊情况总结

类型错误

编译器帮你处理

范围错误

需要处理

边界情况

需要处理

随堂练习

特殊情况分析 训练

LinkedList 高级编辑

Q45 E03

特殊情况排查方法

先写一般情况的代码

分析特殊情况

分析现有代码能否接住特殊情况

不能,就要写 if 分支

随堂练习
String description()

早推出Early Exits

使用前
1 2 3 4 5 6 7 8 9 10 11
public int get(int index) { if (index >= 0 && index < size()) { Node current = first; for (int i = 0; i < index; i++) { current = current.getNext(); } return current.getValue(); } else { return -1; }}
使用后
1 2 3 4 5 6 7 8 9 10 11 12
public int get(int index) { // 早退出 if (index < 0 || index >= size()) { return -1; } Node current = first; for (int i = 0; i < index; i++) { current = current.getNext(); } return current.getValue();}

封装Encapsulation

内部功能抽离

方案

如果有了这个方法

Node getNode(int index)

那么 下面的几个方法 实现起来会很方便

int get(int index)
void insert(int index, int value)

private 方法

为内部服务的方法,本不应该曝光出去

1*2 3
private Node getNode(int index) { ...}

封装模型

对象模型
使用 private

使用 private 可对属性和方法进行访问保护

禁止外界直接访问属性和方法

private 的仅供内部使用

封装

外界只能看到 public 对外暴露的

不知道 也不该知道 里面有什么

不管 也不该管 里面怎么搞的

内部优化

可以添加不影响外部的 private 属性或方法

样例
1 2 3
public int lastValue(){ return getNode(size() - 1).getValue();}
问题

为了求 size 已经跑到最后一个节点了,

getNode 时又跑了一遍

优化
1 2 3+4 5 6*7 8 9 10 11+12 13 14
public class LinkedList { ... private int size; ... public int lastValue() { return getNode(size - 1).getValue(); } ... public void add(int value) { ... size ++; } ...}

泛型Generics

案例

问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class IceCreamBox { private int id; private IceCream iceCream; private double weight; public void put(IceCream iceCream) { this.iceCream = iceCream; } public boolean isEmpty() { return iceCream == null; } public IceCream takeOut() { IceCream iceCream = this.iceCream; this.iceCream = null; return iceCream; } // getters and setters}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class MilkTeaBox { private int id; private MilkTea milkTea; private double weight; public void put(MilkTea milkTea) { this.milkTea = milkTea; } public boolean isEmpty() { return milkTea == null; } public MilkTea takeOut() { MilkTea milkTea = this.milkTea; this.milkTea = null; return milkTea; } // getters and setters}

代码大面积重叠

解决方案
1*2 3*4 5 6*7*8 9 10 11*12 13 14*15*16*17*18 19 20 21
public class Box <T> { private int id; private T content; private double weight; public void put(T content) { this.content = content; } public boolean isEmpty() { return content == null; } public T takeOut() { T content = this.content; this.content = null; return content; } // getters and setters}
使用
1 2 3 4 5 6 7
public class Driver { public static void main(String[] args) { Box<MilkTea> milkTeaBox = new Box<>(); MilkTea milkTea = new MilkTea(); milkTeaBox.put(milkTea); }}

什么是

将类型中的类型 作为变量

Type Arguments

机制

new 时,传递泛型值,从而确定这个类到底是什么样

再使用,之后 会有方法的对应的类型提示

语法

声明
1
public class Box <T>
赋值
1
new Box<IceCream>();
使用
1
private T content;

命名规则

一般用一个大写字母, 也可以用单词, 大写开头

赋值规则

不可以赋值基础类型

包装类Wrapper Classes

背景

有些情况下, 一些语法只能接收对象类型

1!
new Node<int>();

需要一种结构把基础类型 转成 对象类型

样例

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class Int { private int value; public Int(int value) { this.value = value; } public int getValue() { return value; } public void setValue(int value) { this.value = value; }}
1 2 3 4 5 6 7 8 9 10
public class Main { public static void main(String[] args) { new Main().run(); } public void run(){ Int anInt = new Int(3); int value = anInt.getValue(); }}

装箱 和 拆箱Boxing and Unboxing

装箱
1
new Int(3);
拆箱
1
anInt.getValue();

配套泛型使用

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class Main { public static void main(String[] args) { new Main().run(); } public void run(){ Node<Int> intNode = new Node<Int>(); Int intObject = new Int(10); // 装箱 - 把基础类型放到包装对象里 intNode.setValue(intObject); // 把对象放到业务代码里 Int intObjectOut = intNode.getValue(); // 从业务对象里取出对象 int intValue = intObjectOut.getValue(); // 拆箱 - 把包装对象拆出基础类型 Console.println(intValue); }}

系统包装类

boolean Boolean
byte    Byte
char    Character
float   Float
int     Integer
long    Long
short   Short
double  Double

自动装箱拆箱

自动装箱
1 2
Integer a = new Integer(3);Integer a = 3;
自动拆箱
1 2
int value = a.intValue();int value = a;

列表List

是什么

系统的动态数组。

有两个

LinkedList

Oracle 官方参考文档

ArrayList

Oracle 官方参考文档

样例

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
public class Main { public static void main(String[] args) { new Main().run(); } public void run(){ LinkedList<Integer> list = new LinkedList<Integer>(); list.add(6); list.add(7); list.add(3); list.add(8); list.add(4); for (int i = 0; i < list.size(); i++) { int value = list.get(i); Console.println(value); } }}

列表内包含对象

1 2 3 4 5 6 7 8
public class Point { private int x; private int y; public String description() { return "(" + x + ", " + y + ")" } // ... 省略 getter setter 构造器}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public class Main { public static void main(String[] args) { new Main().run(); } public void run(){ LinkedList<Point> points = new LinkedList<Point>(); points.add(new Point(0, 0)); points.add(new Point(3, 0)); points.add(new Point(3, 4)); for (int i = 0; i < points.size(); i++) { Point point = points.get(i); Console.println(point.description()); } }}

For Each遍历专用 for 语句

案例

使用前
1 2 3 4
for (int i = 0; i < list.size(); i++) { Integer value = list.get(i); Console.println(value);}
使用后
1-2-3+4 5
for (int i = 0; i < list.size(); i++) { Integer value = list.get(i);for (Integer value : list) { Console.println(value);}

语法

for (元素类型 元素变量 : 集合变量) {
    ...
}

for 语句对比

一般 for 可以进行高级控制

标准 for 可以获得当前位置

遍历 for 只能遍历所有元素

数组也可以用

1 2 3 4
int[] values = {6, 7, 3, 8, 4};for (int value : values) { Console.println(value);}

ZZAX 微信公众

文档一更新,立刻告诉你