面向对象三大特征
一 ,static关键字
1.1 static修饰静态成员方法用途
特征:
1. 摆脱类对象,效率高,节约内存空间,提高开发效率
2. 类内成员变量和成员方法都不可以使用,但是不影响使用外来数据。
3. 静态成员方法通常用于工具类的封装使用。
Arrays数组工具类
sort排序方法
binarySearch 二分法查询方法
toString 数组内容字符串展示返回方法
1.2 static修饰静态代码块
特征:
1. static修饰的静态代码块,不能使用this关键字,不能使用类内的非静态成员
2. static修饰的静态代码块,可以使用类内的其他静态成员‘
3. static修饰的静态代码块中,定义的变量都是局部变量,静态代码块。首先是是一个代码块,拥有代码块的特征,其次才是通过static修饰之后,可以随着类文件的加载直接运行,有且只运行一次
static {
int num = 10;
num = 20;
}
二 ,接口
2.1 Java中接口使用
格式:
interface 接口名 {
成员变量
成员方法
}
类【遵从】接口
implements
class 类名 implements 接口 {
}
接口中成员变量和成员方法缺省属性原因
从生活角度
USB接口规定了尺寸和连接方式,但是该接口做什么内容,是由设备决定的!!!
尺寸是固定 ==> 成员变量 缺省属性是public static final
设备做什么事情,不管但是规定连接方式 ==> 成员方法,需要设备自己完成
缺省属性 public abstract修饰
2.2 接口使用总结
1. 接口中的
成员变量缺省属性 public static final
成员方法缺省属性 public abstract
2. 一个非abstract类遵从interface接口,需要强制完成接口中所有缺省属性为public abstract的成员方法
3. 接口和接口之间,允许使用extends关键字继承,并且允许一个接口,继承多个接口
interface A extends B, C
生活中: 协议直接的向下兼容问题
4. 接口中可以使用default关键字修饰方法,default方法拥有方法体,可以认为是非强制实现方法,不要求遵从接口的非abstract强制实现,JDK1.8新特征
三,多态
多态
父类的引用指向子类的对象
或者说接口的引用指向遵从接口的类对象
这就是多态
作用:
1. 拓宽方法的参数范围
例如:
方法参数为Animal类型
可以传入Animal类型本身,或者去子类对象都可以
方法参数为USB接口类型
只要是直接或者间接遵从USB接口的类对象可以作为方法的参数传入
2. 拓宽方法的返回值范围
3. 简化代码开发,提高开发效率,整合数据类型
四,异常
4.1 代码中异常
Throwable类
Java中所以异常的超类,在Java中所有的异常,错误的基类就是Throwable类。
Throwable
--| Exception 异常 可以处理,代码还有拯救的可能性
--| Error 错误 GG思密达
Throwable常用方法:
Constructor:
Throwable(); Throwable构造方法,Throwable类对象中,存储的异常或者
错误信息为null
Throwable(String message); Throwable构造方法,Throwable类对象
中,存储的异常或者错误信息为message
Method:
String getMessage(); 获取Throwable对象中存储的异常或者错误信息
String toString(); 返回当前异常或者错误的简要描述
void printStackTrace(); 展示错误的前因后果,【红色字体】
4.2 异常处理之捕获异常
try - catch 结构
try - catch - finally 结构 后期设计到资源问题,再讲解
格式:
try {
// 有可能出现异常代码
} catch (/* 对应处理的异常对象 */) {
// 处理方式
}
4.3 异常处理之抛出异常
throw
在方法内抛出异常
throws
在【方法声明】位置,告知调用者当前方法有哪些异常抛出
声明的异常需要生成对应的文档注释
4.4 抛出和捕获的对比
捕获之后,代码可以正常运行,要保证处理之后的异常不会在导致其他问题。
例如:
用户名密码错误,不能采用捕获异常。
用户指定路径问题,也不能采用捕获异常。
抛出的确可以解决很多问题,并且可以让代码健壮性很强。到用户层面说什么都不能抛出异常。
所谓不能抛出,是指不能讲错误信息直接甩到用户脸上。
用户密码错误情况:
1. 捕获异常
2. 通过异常处理 catch将错误抛出
3. 给予用户的友好提示
4.5 运行时异常(RuntimeException)
运行时异常:
JVM在运行的过程中可以非检查异常
例如:
ArrayIndexOutOfBoundException
NullPointerException
StringIndexOutOfBoundException
ArithmeticException
这些异常在代码中如果出现,不需要代码中强制进行捕获或者抛出处理
4.6 自定义异常
代码运行的过程中存在一定的生活化
例如:
用户名密码错误
NoGirlFriendException 没有女朋友异常
自定义异常格式:
class 自定义异常类名 extends Exception {
// No Fields Constructor
// String Field Constructor
}
自定义异常类名:
必须Exception结尾!!!
五,泛型
5.1 泛型在方法中使用
使用静态方法举例说明泛型在方法中的使用格式
格式:
public static <T> 返回值类型[自定义泛型] 方法名(必须存在一个对应泛型的参数) {
方法体内可以使用自定义泛型
}
使用注意事项:
1. 自定义泛型声明在返回值之前,已保证方法的参数和返回值都可以使用对应的泛型
2. 方法声明的泛型,在方法的参数中必须有一个参数是对应声明的自定义泛型。当前参数是用于约束方法内所有使用到泛型的位置对应的具体数据类型是什么。
5.2 泛型在类中使用
格式:
class 类名<自定义泛型无意义大写字母占位符> {
在类内的成员变量和成员方法都可以使用自定义泛型
建议: 成员变量不建议使用自定义泛型
}
使用注意事项:
1. 类声明自定义泛型,需要通过创建对象的方式来约束
TypeA<String> typeA = new TypeA<String>(); Eclipse标准写法
TypeA<String> typeA = new TypeA<>(); IDEA写法
2. 类声明泛型约束之后,在类内的所有成员方法中使用的泛型都是类约束的泛型具体数据类型
3. 如果没有约束类声明泛型,所有使用到泛型的位置都是Object,【不推荐】
4. 类声明的自定义泛型不能用于类内的静态方法【没有对象】
5.3 泛型在接口中使用
接口
interface 接口名 {
成员变量缺省属性:
public static final 定义时必须初始化,并且初始化之后无法修改
成员方法缺省属性:
public abstract 方法没有方法体
有方法体如何使用:
default修饰默认方法,非强制实现方法
}
泛型在接口中使用格式
interface 接口名<自定义泛型无意义占位符> {
问题: 接口的泛型是否可以用于成员变量中
T t
定义时必须初始化,但是当前泛型的数据类型是不明确的,无法进行初
始化过程。和final有关。
接口中的泛型有且只能用于成员方法!
}
使用注意事项:
1. 接口声明的泛型,有且只能用于类内的成员方法。
问题: default修饰的方法是否可以使用接口声明的自定义泛型
2. 遵从带有自定义泛型的接口的类,一种可以遵从过程中明确告知泛型具体类型,一种是在创建类对象是明确泛型具体数据类型。
3. class TypeA<T> implements A<String>
正确
接口的泛型明确接口中的方法,使用泛型对应的具体数据类型
类声明的泛型用于自定义约束自己类内的方法
4. class TypeA<E> implements A<T>
错误
接口中的泛型没有明确数据类型,也无法通过类创建对象的过程中明确
泛型对应的具体数据类型,无法编译
六,匿名内部类
package com.qfedu.b_anonymous;
/*
* 匿名内部类演示
*/
interface A {
void test();
}
/**
* 非abstract修饰TypeA遵从接口A,要求强制实现接口A中的test方法
* @author Anonymous
*
*/
class TypeA implements A {
@Override
public void test() {
System.out.println("TypeA遵从A接口,实现A接口中的test方法");
}
}
public class Demo1 {
public static void main(String[] args) {
TypeA typeA = new TypeA();
typeA.test();
/*
* A接口的引用a, new 调用A接口的构造方法【注意】这里不是创建A接口对象
* Anonymous Inner Type提示是完成了一个要求是【遵从】接口A必须实现的方法
*
* 大括号{}里面的内容和一个普通类遵从接口A的效果是一模一样的。
* 大括号里面的内容可以认为是【类的本体】
* 但是大括号之前没有类名,这就是匿名内部类。
*
* new关键字在内存堆区申请了空间,创建了一个隐含遵从接口A的匿名内部类对象
* 并且把该对象的空间首地址,赋值给接口A的引用数据类型变量,还有一个知识点
* 接口的引用指向遵从接口的类对象,这就是多态!!!
*
* low!!!
*/
A a = new A() {
@Override
public void test() {
System.out.println("匿名内部类的对象赋值给接口的引用");
}
};
a.test();
// 匿名内部类的匿名对象直接调用实现的方法 little low!!!
new A() {
@Override
public void test() {
System.out.println("匿名内部类的匿名对象直接调用方法");
}
}.test()
// 匿名内部类的匿名对象直接作为方法的参数 终极奥义!!!
testInterface(new A() {
@Override
public void test() {
System.out.println("匿名内部类的匿名对象直接作为方法的参数");
}
});
}
/**
* 需要接口A的实现类对象作为方法的参数
*
* @param a 接口A的实现类对象
*/
public static void testInterface(A a) {
a.test();
}
}
七,集合
7.1 集合架构
Java中集合的【总接口】Collection<E>。Java中所有和集合有关的内容,都是Collection<E>接口的子接口或者实现类
interface Collection<E>
--| interface List<E> List接口,有序可重复
----| class ArrayList<E>
【重点】可变长数组结构
原码实现,了解其中的特征,性能....
----| class LinkedList<E>
【重点】双向链表结构
----| class Vector<E>
【远古时代】JDK1.0 线程安全的ArrayList<E>,如果不考虑线程安全问
题,建议使用ArrayList<E>
--| interface Set<E> Set接口,无序不可重复
----| HashSet<E> 底层存储数据的方式是采用哈希表方式
----| TreeSet<E> 底层存储数据的方式一个平衡二叉树方式
7.2,Collection接口下的常用方法
增:
boolean add(E e);
存入元素到当前集合对象中,这里要求的数据类型是E类型,也就是泛型对于
的具体数据类型
boolean addAll(Collection<? extends E> c);
class Dog extends Animal
class Cat extends Animal
class Tiger extends Animal
==> ? extends E 泛型的上限
要求存入的集合c中,存储的元素要么是E类型,要么是E类的子类
删:
void clear();
清空整个集合
boolean remove(Object obj);
删除集合中的指定元素
boolean removeAll(Collection<?> c);
删除两个集合的交集
boolean retainAll(Collection<?> c);
保留两个集合的交集
查:
int size();
返回集合中有效元素个数
boolean isEmpty();
判断当前集合是否为空
boolean contains(Object obj);
判断指定元素在当前集合中是否存在
boolean containsAll(Collection<?> c);
判断集合c是不是当前集合的子集合
7.3 迭代器
通过集合对象获取对应的Iterator<E>迭代器
Iterator<E> iterator();
常用方法:
boolean hasNext();
判断当前Iterator是否可以继续运行。
E next();
获取Iterator当前指向元素,并且指向下一个元素。
void remove();
删除
【注意】
1. remove方法有且只能删除通过next方法获取的元素
2. remove方法如果想要使用,必须紧挨着next方法
7.4 List集合接口特征和方法
特征:
有序,可重复
有序: 添加顺序和存储顺序一致
可重复:相同元素可以同时添加
List<E>接口下的实现类,存在一定的下标操作机制
ArrayList<E> 底层数组形式操作,可以通过下标直接访问
LinkedList<E> 底层是一个双向链表结构,下标 ==> 计数器
特定的方法:
增:
add(E e);
List接口下,当前方法是添加元素到集合的末尾,尾插法
addAll(Collection<? extends E> c);
List接口下,当前方法是添加另一个集合到当前集合末尾,要求添加的
集合中保存的元素和当前集合保存元素一致,或者说是当前集合保存元
素的子类
add(int index, E e);
在指定的下标位置,添加指定元素
addAll(int index, Collection<? extends E> c);
在指定的下标位置,添加指定的集合,集合要求同上一个addAll方法
删:
void clear();
清空整个集合
remove(Object obj);
删除集合中的指定元素
removeAll(Colletion<?> c);
删除两个集合的交集
retainAll(Colletion<?> c);
保留两个集合的交集
E remove(int index);
删除集合中指定下标的元素。返回值是被删除的元素
改:
E set(int index, E e);
使用指定元素替换指定下标index的元素,返回值是被替换掉的元素。
查:
int size();
有效元素个数
boolean isEmpty();
判断当前集合是否为空
boolean contains(Object obj);
boolean containsAll(Collection<?> c);
int indexOf(Object obj);
找出指定元素在集合中的第一次出现位置
int lastIndexOf(Object obj);
找出指定元素在集合中最后一次出现位置
E get(int index);
获取指定下标的元素
List<E> subList(int fromIndex, int endIndex);
获取当前集合的子集合
【特征】
获取数据的范围是 fromIndex <= n < endIndex
要头不要尾
7.4.1 ArrayList 可变长数组
特征:
数组形式的操作方式,查询效率高,但是删除,增加效率低。
数组:
Object类型数组
方法:
ArrayList使用的方法基本上都是从List接口中遵从实现的方法。
特征:
ensureCapacity(int minCapacity);
判断当前容量是否足够
trimToSize();
截断整个数组容量 ==> size有效元素个数
时间换空间,空间换时间
(1) 自定义实现的ArrayList
package com.system.student.qfedu.util;
import java.util.Arrays;
/**
* 自定义实现MyArraylist
*
* @author Anonymous
*
* @param <E> 自定义泛型
*/
public class MyArrayList<E> {
/**
* 准备一个底层数组,用于存储数据内容
*/
private Object[] elements;
/**
* 初始化默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 最大数组容量, -8是为了腾出一定的空间,保存数组的必要内容
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 当前底层数组中保存的有效元素个数
*/
private int size = 0;
/**
* 无参数构造方法,但是需要提供给用户一个初始化容量来保存必要的数据
*/
public MyArrayList() {
/*
* 使用this关键字调用类内的其他构造方法 、
* 根据实际参数里类型来选对应对应的构造方法,必须在当前构造
* 方法代码块的第一行
*/
this(DEFAULT_CAPACITY);
}
/**
* 用户指定保存元素容量的初始化过程,要求用户指定的容量范围是有效的
*
* @param initCapacity 用户指定的初始化容量,但是不能小于等于0 ,不能大于 MAX_ARRAY_SIZE
*/
public MyArrayList(int initCapacity) {
// 用户传入参数的合法性判断过程
if (initCapacity < 0 || initCapacity > MAX_ARRAY_SIZE) {
// 抛出异常
// IllegalArgumentException 是一个RuntimeException运行时异常的子类
// 不需要强制声明抛出异常
throw new IllegalArgumentException("IllegalArgumentException : " + initCapacity);
}
elements = new Object[initCapacity];
}
/*
* 增加方法
*/
/**
* 添加元素到当前集合的末尾
*
* @param e 要求是符合泛型约束的指定数据类型
* @return 添加成功返回true, 否则返回false
*/
public boolean add(E e) {
// 直接调用在指定下标位置添加元素的方法,只不过这里指定下标位置就是
// 尾插法下标位置
return add(size, e);
}
/**
* 在底层数组的指定下标位置保存对应的元素
*
* @param index 指定下标位置,不能超出有效范围,0<= index <= size
* @param e 符合泛型约束的数据类型
* @return 添加成功返回true, 否则返回false
*/
public boolean add(int index, E e) {
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException(index);
}
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = e;
size += 1;
return true;
}
/*
* addAll方法 1. 需要得到添加集合中元素内容,有效元素个数 2. 确认容量问题 3. size = srcSize + newSize
*/
/**
* 添加另一个集合到当前集合的末尾
*
* @param list MyArrayList类型,自定义ArrayList,要求存储元素和当前集合一致,或者 是其子类
* @return 添加成功返回true,添加失败返回false
*/
public boolean addAll(MyArrayList<? extends E> list) {
Object[] array = list.toArray();
int newSize = array.length;
ensureCapacity(size + newSize);
for (int i = 0; i < newSize; i++) {
elements[i + size] = array[i];
}
size += newSize;
return true;
}
/**
* 添加另一个符合当前集合要求的List,到当前集合内
*
* @param index 指定插入的下标位置
* @param list 符合当前添加要求的另一个MyArrayList集合
* @return 添加成功返回true
*/
public boolean addAll(int index, MyArrayList<? extends E> list) {
// 用户指定的下标判断
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException(index);
}
Object[] array = list.toArray();
int newSize = array.length;
ensureCapacity(size + newSize);
// 移动操作
for (int i = size - 1; i >= index; i--) {
elements[i + newSize] = elements[i];
}
// 存入另一个集合中的元素
for (int i = index; i < index + newSize; i++) {
elements[i] = array[i - index];
}
// 有效元素个数需要修改
size += newSize;
return true;
}
/**
* 删除指定元素
*
* @param obj 指定删除的元素
* @return 删除成功返回true
*/
public boolean remove(Object obj) {
int index = indexOf(obj);
return null != remove(index);
}
/**
* 删除下标元素
*
* @param index 指定的下标范围
* @return 删除成功返回对应元素,失败返回null
*/
public E remove(int index) {
if (-1 == index) {
return null;
}
E e = get(index);
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
// 原本最后一个有效元素位置上的内容赋值为null
elements[size - 1] = null;
size -= 1;
return e;
}
/**
* 获取集合中指定下标的元素
*
* @param index 指定下标的范围,但是不能超出有效下标范围
* @return 返回对应的元素
*/
@SuppressWarnings("unchecked")
public E get(int index) {
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException(index);
}
return (E) elements[index];
}
/**
* 查询指定元素在集合中的第一次出现下标位置
*
* @param obj 指定的元素
* @return 返回值大于等于0表示找到元素,否则返回-1
*/
public int indexOf(Object obj) {
int index = -1;
for (int i = 0; i < size; i++) {
// equals 判断对象是否一致地方的方法
if (obj.equals(elements[i])) {
index = i;
break;
}
}
return index;
}
/**
* 查询指定元素在集合中的最后一次出现下标位置
*
* @param obj 指定的元素
* @return 返回值大于等于0表示找到元素,否则返回-1
*/
public int lastIndexOf(Object obj) {
int index = -1;
for (int i = size - 1; i >= 0; i--) {
// equals 判断对象是否一致地方的方法
if (obj.equals(elements[i])) {
index = i;
break;
}
}
return index;
}
/**
* 返回MyArrayList集合中所有有效元素的Object数组
*
* @return 包含所有集合元素的Object类型数组
*/
public Object[] toArray() {
// size是有效元素个数,通过该方法可以获取到一个只有当前数组中有效元素的数组
return Arrays.copyOf(elements, size);
}
/**
* 替换指定下标的元素
*
* @param index 指定下标元素,但是必须在有效范围以内
* @param e 符合泛型约束的对应数据类型
* @return 被替换的元素
*/
public E set(int index, E e) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException(index);
}
E temp = get(index);
elements[index] = e;
return temp;
}
/**
* 判断指定元素是否存在
*
* @param obj 指定元素
* @return 存在返回true,不存在返回false
*/
public boolean contains(Object obj) {
return indexOf(obj) > -1;
}
/**
* 作业 情况1
* 集合1 {1, 3, 5, 7, 9}
* 集合2 {3, 5, 7} 子集合!!True
* 集合3 {3, 7, 5} 不是!!! false
*
* 情况2
* 集合1 {1, 3, 3, 5, 9, 3, 5, 7, 9}
* 集合2 {3, 5} true
* 集合3 {5, 3}
* 集合4 {1, 3, 6, 7, 9, 3, 5, 7, 9} true
*
* @param list
* @return
*/
public boolean containsAll(MyArrayList<?> list) {
// 判断list是否有内容 已经当前list对应的地址是不是null
if (null == list || list.isEmpty()) {
throw new NullPointerException();
}
// 1. 计数器 找出list参数集合中下标为0的元素在集合中出现的位置次数
int count = 0;
boolean flag = true;
// 2. 存储list参数集合中下班未0的元素出现的位置
int[] indexArr = new int[this.size];
for (int i = 0; i < this.size; i++) {
// 在当前集合中下标为i的元素和list参数集合中下标为0的元素比较
if (this.get(i).equals(list.get(0))) {
indexArr[count] = i;
count += 1;
}
}
// 4. 判断是否存list.get(0)的元素
if (0 == count) {
return false;
}
// 5. 进入循环,开始匹配,从找到的所有list[0]匹配
for (int i = 0; i < count; i++) {
// 6. 遍历操作当前集合
// 从当前集合中对应查询位置 + 1开始,循环次数是list.size() - 1
for (int j = indexArr[i] + 1; j < indexArr[i] + list.size(); j++) {
// list从下标1开始获取元素
int index = 1;
if (!this.get(j).equals(list.get(index++))) {
flag = false;
break;
}
flag = true;
}
if (flag) {
break;
}
}
return flag;
}
/**
* 判断集合是否是空的
*
* @return 如果为空,返回true, 否则返回false
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 获取当前集合中有效元素个数
*
* @return 有效元素个数
*/
public int size() {
return size;
}
/**
* 获取当前集合的子集合,截取范围是fromIndex <= n < endIndex
*
* @param fromIndex fromIndex <= endIndex 不得小于0
* @param endIndex endIndex >= fromIndex 小于等于size
* @return 截取得到的一个MyArrayList子集合对象
*/
public MyArrayList<E> subList(int fromIndex, int endIndex) {
if (fromIndex > endIndex || fromIndex < 0 || endIndex > size) {
throw new ArrayIndexOutOfBoundsException();
}
MyArrayList<E> listTemp = new MyArrayList<E>(endIndex - fromIndex);
for (int i = fromIndex; i < endIndex; i++) {
listTemp.add(this.get(i));
}
return listTemp;
}
@Override
public String toString() {
if (isEmpty()) {
return "[]";
}
String str = "[";
for (int i = 0; i < size - 1; i++) {
str += elements[i] + ", ";
}
return str + elements[size - 1] + "]";
}
/*
* 这里需要类内使用的可以用于判定当前容量是否满足添加要求的方法 如果满足直接进入添加模式,如果不满足,需要执行grow方法,完成 底层数组的扩容问题。
*/
/**
* 每一次添加元素,都需要进行容量判断,如果满足可以进行添加操作 不满足需要制定grow方法
*
* @param minCapacity 要求的最小容量
*/
private void ensureCapacity(int minCapacity) {
if (minCapacity > elements.length) {
// 完成一个底层数组的扩容方法
grow(minCapacity);
}
}
/**
* 底层数组的扩容方法,原理是创建新数组,移植数据,保存新数组地址
*
* @param minCapacity 要求的最小容量
*/
private void grow(int minCapacity) {
// 1. 获取原数组容量
int oldCapacity = elements.length;
// 2. 计算得到新数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 3. 判断新数组容量是否满足要求
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
// 新数组容量是大于允许的最大数组容量
if (newCapacity > MAX_ARRAY_SIZE) {
// 二次判断minCapacity是否小于MAX_ARRAY_SIZE
if (minCapacity < MAX_ARRAY_SIZE) {
// 最小要求是不大于MAX_ARRAY_SIZE,代码可以运行
newCapacity = minCapacity;
} else {
throw new OutOfMemoryError("Overflow MAX_ARRAY_SIZE");
}
}
/*
* 4. 使用数组工具类方法完成操作 Arrays.copyOf(源数据数组,可以是任意类型,采用泛型约束, 指定的新数组容量); a.
* 根据指定的新数组容量创建对应泛型数据类型的新数组 b. 从源数据数组中拷贝内容到新数组中 c. 返回新数组首地址
*/
elements = Arrays.copyOf(elements, newCapacity);
}
}
(2) ArrayList性能问题
增加慢:
1. 增加元素有可能出现调用grow方法,grow需要进行数组的扩容操作,操作过程中需要大
量的移动和拷贝过程,浪费时间
2. 在某一个指定位置添加元素,会导致从指定位置开始,之后的元素整体向后移动,涉及
移动复制操作,浪费时间。
删除慢:
1. 按照ArrayList可变长数组要求,删除元素之后,之后的内容都需要整体向前移动。
查询快
7.4.2 LinkedList特征
底层是一个双向链表
链子 自行车链子 船锚 手链
自行车链子
维修很方便,前断,后断,链接搞定!!! 找到损坏的位置,需要一个一个来
链表结构
1. 增删快
2. 查询很慢很慢很慢
LinkedList需要了解的方法:
LinkedList使用的方法,大部分都是从List接口中遵从实现的方法
但是有一些特征方法
addFirst(E e);
addLast(E e); ==> add(E e);
E getFirst();
E getLast();
removeFirst();
removeLast();
以上方法组合可以完堆栈队列操作
堆
先进后出
弹夹
addLast(E e); E getLast(); removeLast();
队列
先进先出
addLast(E e); E getFrist(); removeFirst();
自定义MyLinkedList
package com.qfedu.c_util;
import java.util.NoSuchElementException;
public class MyLinkedList<E> {
/**
* 有效元素个数
*/
private int size = 0;
/**
* 第一个Node节点首地址
*/
private Node<E> first;
/**
* 最后一个Node节点的首地址
*/
private Node<E> last;
/**
* 私有化静态成员内部类
*
* @author Anonymous
*
* @param <E> 和MyListedList一直的泛型
*/
private static class Node<E> {
/**
* 在LinkedList中保存的Node节点内元素内容
*/
E item;
/**
* 下一个Node节点引用,保存下一个节点的空间首地址
*/
Node<E> next;
/**
* 上一个Node节点的,保存上一个节点空间的首地址
*/
Node<E> prev;
/**
* Node<E>没有无参数构造方法,创建对应Node对象,需要保存前后节点位置,同时需要
* 包装需要存储的数据,在当前Node节点中
*
* @param prev 前节点位置
* @param element 存储元素
* @param next 后节点位置
*/
public Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.prev = prev;
this.next = next;
}
}
/**
* 空MyListedList集合,只是准备了一个所谓的链表头
*/
public MyLinkedList() {}
/**
* 添加一个符合当前MyLinkedList泛型约束数据类型一致的元素到集合中
*
* @param e 符合MyLinkedList 要求的元素
* @return 添加成功返回true
*/
public boolean add(E e) {
// 原本最后一个节点位置,当前元素包装成Node节点之后的存储位置
Node<E> l = last;
// 创建得到一个新的Node节点
Node<E> newNode = new Node<>(l, e, null);
// 因为当前元素需要保存到LinkedList结尾 last一定要被赋值newNode
last = newNode;
// 第一个有效节点时,first同时指向当前节点
if (null == first) {
first = newNode;
} else {
// 原本最后一个节点next指向新的newNode
l.next = newNode;
}
// 有效元素 += 1
size += 1;
return true;
}
/**
* 同是尾插法操作
*
* @param e 符合泛型约束的具体数据类型
*/
public void addLast(E e) {
add(e);
}
/**
* 在LinkedList链表头添加一个元素
*
* @param e 符合泛型约束的具体数据类型
*/
public void addFirst(E e) {
/*
* 三个地址:
* 1. LinkedList链表头 first ==> newNode
* 2. newNode.next --> old FirstNode
* 3. old FirstNode.prev --> newNode
*/
// 原始的first节点
Node<E> f = first;
// 创建新newNodex
Node<E> newNode = new Node<E>(null, e, f);
// LinkedList链表头内first一定执行newNode
first = newNode;
if (null == first) {
last = newNode;
} else {
// 原始的first节点,prev赋值为newNode
f.prev = newNode;
}
size += 1;
}
/**
* 获取第一个节点元素
*
* @return 第一个Node保存内容
*/
public E getFirst() {
Node<E> f = first;
if (null == f) {
throw new NoSuchElementException();
}
return f.item;
}
/**
* 获取最后一个节点元素
*
* @return 最后一个Node保存内容
*/
public E getLast() {
Node<E> l = last;
if (null == l) {
throw new NoSuchElementException();
}
return l.item;
}
/**
* 或者指定下标(指定计数,第几个节点内存储的元素)
* @param index 指定的下标位置,计数
* @return 对应的元素内容
*/
public E get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
if (index < (size >> 1)) {
Node<E> n = first;
for (int i = 0; i < index; i++) {
n = n.next;
}
return n.item;
} else {
Node<E> n = last;
for (int i = size - 1; i > index; i--) {
n = n.prev;
}
return n.item;
}
}
/**
* 删除最后一个元素
*
* @return 返回值是保存的元素内容
*/
public E removeLast() {
// 最后一个节点
Node<E> l = last;
// 最后一个节点的前节点
Node<E> prev = last.prev;
if (null == l) {
throw new NoSuchElementException();
}
// 原最后一个节点的前节点空间地址赋值为null
l.prev = null;
// last指向原本的最后一个节点的前节点
last = prev;
// 前节点为null
if (null == prev) {
first = null;
} else {
prev.next = null;
}
// 取出节点中保存的数据内容
E e = l.item;
// 原最后节点内所有数据全部为null,GC更快的销毁内存
l.item = null;
size -= 1;
return e;
}
}