反射、泛型、容器【java进阶笔记三】

目录

一、反射

类加载的区别:

怎么用?

怎么创建class对象

生成对象的步骤

补充:反射破坏单例

通过反射获取属性 Field

反射应用

二、泛型

泛型的本质:类型参数化

① 泛型类

② 泛型方法

③ 泛型类派生出的子类

④ 泛型通配符

⑤ 类型擦除

⑥ 桥接方法

三、集合容器

ArrayList源码解析

LinkedList源码解析

HashMap源码解析


一、反射

类加载的区别:

RTTI:编译期完成类的加载(classLoader:加载-验证-准备-解析-初始化)

反射:(越过编译期)在运行期完成类的加载。

反射的应用场景:写代码的时候(编译期)不知道用哪个类,哪个方法。例如需要看实际的业务来选择操作类

怎么用?

需要先了解JVM类加载流程和内存结构:

【java源文件.java】==》

类编译器(将java源码编译为class文件)==》

类加载器(将class文件加载到JVM)

而class文件里包含了大量信息:属性、方法、字段、接口、父类、访问标记、常量池、版本号、魔数等

结论:因此反射的入口是 .class文件

怎么创建class对象

/**
* 示例:创建Class对象的3种方式
*/
@Test
public void test() throws Throwable{
    // 方式一  类.class
    Class personClazz = Person.class;
​
    // 方式二  实例.getClass()
    Person person = new Person();
    Class personClazz1 = person.getClass();
​
    // 方式三  Class.forName("类的全路径")
    Class personClazz2 = Class.forName("com.yuyu.reflect.Person");
}

生成对象的步骤

RTTI:Person person = new Person();

步骤:① 编译器加载 Person.class 文件 ==》

​           ② 查找构造函数 Person() ==》

​           ③ 通过构造函数 Person() 创建对象

反射:

步骤:① 运行期加载 Person.class 文件

​           Class personClass = Class.forName("com.yuyu.Person");

​           ② 通过 class 对象得到构造函数 Person()

​           Constructor constructor = personClass.getConstructor();

​           ③ 通过构造函数 Person() 创建对象

​           Person person = (Person) constructor.newInstance();

@Test
public void test2() throws Throwable{
    /** 首先:获得Person的字节码 */
    Class personClazz = Class.forName("com.muse.reflect.Person");
​
    /** 其次:通过Class对象,创建构造方法对象 */
    Constructor constructor1 = personClazz.getConstructor(); // 初始化无参构造方法
    Constructor constructor2 = personClazz.getConstructor(String.class, Integer.class, Byte.class, Boolean.class); // 初始化有参构造方法对象
​
    /** 最后:通过构造方法创建对象 */
    // 调用无参数构造方法创建Person对象
    Person person1 = (Person) constructor1.newInstance();
    person1.setName("muse1");
    System.out.println("person1=" + person1);
​
    // 调用有参数构造方法创建Person对象
    Person person2 = (Person) constructor2.newInstance("muse2", 10, (byte) 1, true);
    System.out.println("person2=" + person2);
}

补充:反射破坏单例

@Test
public void test2() throws Throwable{    
    /** 补充内容:反射通过私有构造方法创建对象,破坏单例模式 */
    Class singletonPersonClazz = SingletonPerson.class;
    // Constructor constructor3 = singletonPersonClazz.getConstructor();
    // .getDeclaredConstructor()获得的信息更多
    Constructor constructor3 = singletonPersonClazz.getDeclaredConstructor();
    //获得私有构造方法的使用权限
    constructor3.setAccessible(true);
    SingletonPerson singletonPerson = (SingletonPerson) constructor3.newInstance();
    SingletonPerson singletonPerson1 = SingletonPerson.getInstance();
    SingletonPerson singletonPerson2 = SingletonPerson.getInstance();
    System.out.println("singletonPerson==singletonPerson1 is " + (singletonPerson == singletonPerson1));//false
    System.out.println("singletonPerson==singletonPerson2 is " + (singletonPerson == singletonPerson2));//false
    System.out.println("singletonPerson1==singletonPerson2 is " + (singletonPerson1 == singletonPerson2));//true
}

通过反射获取属性 Field

@Test
public void test3() throws Throwable{
    // 第一步:获得Class
    Class personClazz = Person.class;
​
    // 第二步:获得构造方法
    Constructor<Person> constructor = personClazz.getConstructor();
    Person person = constructor.newInstance(); // 初始化生成Person对象
​
    // 第三步:通过Class对象,获得Field对象  Person[类]的name属性。
    // .getFiled()获取 public 属性
    Field nameField = personClazz.getField("name");
    // .getDeclareField() 获取非 public属性: private、protect、default 属性,并通过 setAccessible(true) 获得访问权限
    Field sexField = personClazz.getDeclaredField("sex");
    sexField.setAccessible(true);
    //通过反射可以获得私有方法,同样使用 .getDeclareMethod(),并通过 setAccessible(true) 获得访问权限
    Method method = personClazz.getDeclaredMethod("privateMethod");
    method.setAccessible(true);
​
    // 第四步:操作Field,获得属性值
    String name = String.valueOf(nameField.get(person));
    System.out.println(name);
    //非 public 属性的操作和 public 属性操作一样
    System.out.println(sexField.get(person));
    
    //私有方法的操作
    System.out.println("private方法:privateMethod()=" + method.invoke(person));
}

反射应用

我们采用反射机制来实现一个工具BeanUtils,可以将一个对象属性名相同的值赋值给另一个对象。(PO、VTO、PO层之间的实体类属性赋值)

public class BeanUtils {
​
    public static void convertor(Object originObj, Object targetObj) throws Throwable {
        // 第一步,获得class对象
        Class orginClazz = originObj.getClass();
        Class targetClazz = targetObj.getClass();
​
        // 第二步,获得Field
        Field[] orginFields = orginClazz.getDeclaredFields();
        Field[] targetFields = targetClazz.getDeclaredFields();
​
        // 第三步:赋值
        for (Field originField : orginFields) {
            for (Field targetField : targetFields) {
                //属性名相同的赋值给另一个对象
                if (originField.getName().equals(targetField.getName())) {
                    originField.setAccessible(true);
                    targetField.setAccessible(true);
                    // 通过对象操作 Field
                    targetField.set(targetObj, originField.get(originObj));
                }
            }
        }
    }
​
    public static void main(String[] args) throws Throwable{
        // Service层返回的
        Person person = new Person("com/muse", 10, (byte)1, true);
​
        // 需要返回实体对象 person 对象 和 person1 对象只有一个属性名字不同
        Person1 person1 = new Person1();
​
        //调用工具类使用反射完成赋值
        BeanUtils.convertor(person, person1);
​
        System.out.println("person" + person);
        System.out.println("person1" + person1);
    }
}

二、泛型

泛型的本质:类型参数化

允许在定义 类、接口、方法 时使用类型形参,当使用时指定具体类型。

所有使用该泛型参数的地方都被统一化,保证类型一致。如果未指定具体类型,默认是Obj ec t类型。集合体系中的所有类都增加了泛型,泛型也主要用在集合

内容:

① 泛型类;② 泛型方法;③ 泛型类派生出的子类;④ 泛型通配符;⑤类型擦除;⑥桥接方法

① 泛型类

public class ClassGenericity {
    public static void main(String[] args) {
        /** 创建ObjectTool对象并指定元素类型为String */
        ObjectTool<String> stringTool = new ObjectTool<>();
        stringTool.setObj("muse");
        System.out.println(stringTool.getObj());
​
        /** 创建ObjectTool对象并指定元素类型为Integer */
        ObjectTool<Integer> integerTool = new ObjectTool<>();
        // integerTool.setObj("muse"); // 编译报错
        integerTool.setObj(10);
        System.out.println(integerTool.getObj());
    }
​
    /**
     * 构建可以存储任何类型对象的工具类
     */
    static class ObjectTool<T> {
        private T obj;
        public T getObj() {
            return obj;
        }
        public void setObj(T obj) {
            this.obj = obj;
        }
    }
}

② 泛型方法

泛型类作用在整个类,作用范围较大。泛型方法作用在整个方法,作用范围较小。

public class MethodGenericity{
    public static void main(String[] args) {
        //创建对象
        ObjectTool tool = new ObjectTool();
​
        /** 调用方法,传入的参数是什么类型,T就是什么类型 */
        tool.show("hello");
        tool.show(12);
        tool.show(12.5f);
    }
​
    static class ObjectTool {
        //定义泛型方法
        public <T> void show(T t) {
            System.out.println(t);
        }
    }
}

③ 泛型类派生出的子类

两种情况:① 子类明确泛型类的类型;② 子类不明确泛型类的类型

public class SubclassGenericity {
    public static void main(String[] args) {
        // 测试第一种情况
        Inter<String> i = new InterImpl1();
        i.show("hello");
​
        // 编译错误
        //Inter<Integer> ii = new InterImpl1();
        //ii.show(1);
​
        // 第二种情况测试
        Inter<String> iii = new InterImpl2();
        iii.show("100");
​
        Inter<Integer> ii = new InterImpl2();
        ii.show(1);
    }
}
​
/**
 * 把泛型定义在接口上
 */
interface Inter<T> {
    void show(T t);
}
​
/**
 * 实现一:子类明确泛型类的类型参数变量
 */
class InterImpl1 implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}
​
/**
 * 实现二:子类不明确泛型类的类型参数变量,实现类也要定义出T的类型
 */
class InterImpl2<T> implements Inter<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

④ 泛型通配符

存在向上向下转换的问题。例如 List<Object> 和 Object 的范围是不一样的,List<Integer> 类型参数不能往 List<Object>里传。

public class TypeWildcard {
​
    private final static List<Integer> INTEGER_LIST = Lists.newArrayList(1, 2, 3, 4);
​
    private final static List<Object> OBJECT_LIST = Lists.newArrayList("a", 1, 'c', 6.0F, 100L, true);
    
    public static void main(String[] args) {
        TypeWildcard typeWildcard = new TypeWildcard();
        System.out.println("------------test1-------------");
        typeWildcard.test1(INTEGER_LIST);
​
        System.out.println("\n------------test2-------------");
        // typeWildcard.test2(STRING_LIST);//编译会报错,List<Object>只能传List<Object>类型的参数
        typeWildcard.test2(OBJECT_LIST);
    }
    
    public void test1(List list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }
​
    public void test2(List<Object> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    } 
}

① 类型名称 < ? > 对象名称

对类型完全不关心,只能读,不能写

    public void test4(List<?> list) {
        // list.add(list.get(0)); // 编译错误,只能读,不能写
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }

② 类型名称 < ? extends 类 > 对象名称

泛型的上限: 只能接收该类型及其子类

上 界 < ? e x t e n d s T > 不 能 往 里 存 , 只 能 往 外 取 。

    //上限是知道天花板,所以只能通过天花板类型取数据,但如果存的话不知道是哪个类型的子类,所以不能往里存
    public void test5(List<? extends Number> list) {
        Number number = list.get(0);
        //list.add(number); //编译错误
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }

③ 类型名称 < ? super 类 > 对象名称

泛型的下限: 只能接收该类型及其父类型

下 界 < ? s u p e r T > 不 影 响 往 里 存 ,但 往 外 取 只 能 放 在 O b j e c t 对 象 里 。

    //下限只知道地板,所以能通过地板类型及其子类存入数据,但往外取数据的时候不知道具体是哪个父类,所以取要用超级父类Object类型
    public void test6(List<? super Number> list) {
        Number number = (Number) list.get(0);
        list.add(number);
        // list.add(list.get(0)); // 编译错误,默认取出的是Object类型,但只能存Number及其子类
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }

④ 最常用:类型名称 < T > 对象名称 指定类型

    public <T> void test3(List<T> list) {
        list.add(list.get(0));
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }

PECS (Producer Extends Consumer Super)

作为生产者 Producer,使用 extend ,只能读数据提供给消费者(即 extend 服务于读)

作为消费者 Consumer,使用 super , 能写入生产者提供的数据 (即 super 服务于写)

    //【读取】
    public static void testPECSextends() {
        List<Dog> dogs = Lists.newArrayList(new Dog());
        // 上限 extend ,只能接收该类型及其子类
        List<? extends Animal> animals = dogs;
​
        /**
         * animals是一个Animal的子类的List,由于Dog是Animal的子类,因此将dogs赋给animals是合法的,但是编译器会阻止将new Cat()加入animals。
         * 因为编译器只知道animals是Animal的某个子类的List,但并不知道究竟是哪个子类,为了类型安全,只好阻止向其中加入任何子类。那么可不可以加入
         * new Animal()呢?很遗憾,也不可以。事实上,不能够往一个使用了? extends的数据结构里写入任何的值。
         */
        // animals.add(new Cat()); // 编译失败
        // animals.add(new Animal()); // 编译失败
        // animals.add(new Dog()); // 编译失败
​
        /**
         * 由于编译器知道它总是Animal的子类型,但并不知道具体是哪个子类。因此我们总可以从中读取出Animal对象:
         */
        Animal animal = animals.get(0);
        Object obj = animals.get(0);
        // Dog dog = animals.get(0); // 编译失败
    }
​
​
​
    //【写入】
    public static void testPECSsuper() {
        List<Animal> animals = Lists.newArrayList();
        // 下限 super ,只能接收该类型及其父类型
        List<? super Dog> dogs = animals;
        /**
         * 这里的animals是一个Animal的超类(父类,superclass)的List。同样地,出于对类型安全的考虑,我们可以加入Dog对象或者其任何子类(如WhiteDog)对象,
         * 但由于编译器并不知道List的内容究竟是Dog的哪个超类,因此不允许加入特定的任何超类型。
         */
        dogs.add(new Dog());
        dogs.add(new WhiteDog());
        // dogs.add(new Animal()); // 编译失败
        // dogs.add(new Cat()); // 编译失败
        // dogs.add(new Object()); // 编译失败
​
        /**
         * 而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。
         */
        Object obj = dogs.get(0);
        // Dog dog = dogs.get(0); // 编译失败
        // Animal animal = dogs.get(0); // 编译失败
    }

⑤ 类型擦除

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛型的java程序后,生成的class文件中将不再带有泛型信息,以此使程序运行效率不受影响,这个过程称之为 ”类型擦除“。(目的是向下兼容,兼容1.5之前的 jdk 版本)

⑥ 桥接方法

由于类型被擦除了,为了维持多态性,所以编译器就自动生成了桥接方法

//public interface Animal<T>{
//    void eat(T t);
//}
​
//public class Cat implements Animal<String>(){
//    @Override
//    public void eat(String s){
//        system.out.println("cat eat " + s);
//    }   
//}
1> 类型擦除
public interface Animal{
    void eat(Object t);
}
​
public class Cat implements Animal(){
    public void eat(String s){
        system.out.println("cat eat " + s);
    }
    2> 桥接方法(自动生成)
    @Override
    public void eat(Object s){
        //类型强转
        eat((String)s);//类型的限制作用
    }  
}

三、集合容器

ArrayList源码解析

// 1、实例一个 ArrayList 对象
ArrayList arrayList = new ArrayList();
​
// 1.2、通过构造函数实例对象
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
}
// 【变量源码】
// ArrayList 真正存储数据的是一个 Object 类型的数组,
transient Object[] elementData;
//且默认初始化大小为 0
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
​
/**
  * 【问题1】elementData为什么被transient修饰?
  * 答:Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻
  * 止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,
  * 如 int 型的是 0,对象型的是 null。
  * 但是ArrayList在序列化的时候会调用writeObject,直接将size和element写入
  * ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取
  * size和element,再恢复到elementData。
  *
  * 【问题2】为什么不直接用elementData来序列化,而采用上述的方式来实现序列化呢?
  * 答:原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充
  * 容量,那么有些空间可能就没有实际存储元素,采用上述的方式来实现序列化时,就可以保
  * 证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。
*/
 
// 2、对 ArrayList 对象进行操作
arrayList.add("a");
​
// 2.1 add 新增元素操作
// eg1:第一次新增元素e="a1"
    public boolean add(E e) {
        /** 用于查看是否有存储空间:确定是否需要扩容,如果需要,则进行扩容操作*/
        ensureCapacityInternal(size + 1);  
​
        // eg1:size=0,elementData[0]="a1",然后size++自增为1
        elementData[size++] = e;
        return true;
    }
// 【变量源码】
// size 默认为 0 ,需要进入到 ensureCapacityInternal() 函数查看存储空间
private int size;
​
// 2.2 ensureCapacityInternal()  确认存储空间
// eg1:第一次新增元素,所以size=0,则:minCapacity=size+1=1
    private void ensureCapacityInternal(int minCapacity) {
        // eg1:第一次新增元素,calculateCapacity方法返回值为DEFAULT_CAPACITY=10
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
// elementData 当前 默认为 0
// minCapacity 当前为 size + 1 = 1
​
// 2.2.1 进入到 calculateCapacity() 计算容量
// eg1:第一次新增元素,elementData={} minCapacity=1
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // eg1:满足if判断,DEFAULT_CAPACITY=10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
// 【变量源码】
// 判断 elementData 是否为空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 默认第一次操作容量为 10 
private static final int DEFAULT_CAPACITY = 10;
​
// 2.2.2 进入到 ensureExplicitCapacity() 查看是否需要扩容
// eg1:第一次新增元素,minCapacity=10
    private void ensureExplicitCapacity(int minCapacity) {
        // eg1: modCount++后,modCount=1
        modCount++;
​
        /** 如果所需的最小容量大于elementData数组的容量,则进行扩容操作 */
        if (minCapacity - elementData.length > 0) { // eg1:10-0=10,满足扩容需求
            // eg1:minCapacity=10
            grow(minCapacity);
        }
    }
​
// 2.3 进入到 grow() 进行扩容
// eg1:第一次新增元素,minCapacity=10,即:需要将elementData的0长度扩容为10长度。
    private void grow(int minCapacity) {
​
        /** 原有数组elementData的长度*/
        int oldCapacity = elementData.length; // eg1:oldCapacity=0
​
        /** 新增 oldCapacity 的一半整数长度作为 newCapacity 的额外增长长度 */
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // eg1:newCapacity=0+(0>>1)=0
​
        /** 
          * 新的长度 newCapacity 依然无法满足需要的最小扩容量 minCapacity,则新的扩容
          * 长度为 minCapacity 
          */
        if (newCapacity - minCapacity < 0) {
            // eg1:newCapacity=10
            newCapacity = minCapacity;
        }
​
        /** 新的扩容长度 newCapacity 超出了最大的数组长度 MAX_ARRAY_SIZE */
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            newCapacity = hugeCapacity(minCapacity);
        }
​
        /** 扩展数组长度为newCapacity,并且将旧数组中的元素赋值到新的数组中 */
        // eg1:newCapacity=10, 扩容 elementData的length=10
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
// 【变量源码】
// MAX_ARRAY_SIZE=2147483639=01111111 11111111 11111111 11110111
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
​
// 2.4 判断是否过大造成内存溢出
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }
// 【变量源码】
// 01111111 11111111 11111111 11111111 = 2147483647
@Native public static final int   MAX_VALUE = 0x7fffffff;
​
// 3、空间确认完成后回到 add() 进行元素新增
elementData[size++] = e;

LinkedList源码解析

// 1、实例一个 LinkedList 对象
LinkedList linkedList = new LinkedList();
​
// 1.2、通过构造函数实例对象   
public LinkedList() {
}

// 2、对 LinkedList 对象进行操作
linkedList.add("a");
​
// 2.1 add 新增元素操作  得到一个真假的新增结果
// eg1: e="a1"
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
​
// 【Node 节点源码】
    private static class Node<E> {
        E item; // 结点元素
        Node<E> next; // 后置结点指针
        Node<E> prev; // 前置结点指针
​
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
​
// 2.2 进入 linkLast(e) 函数完成添加操作
    //将新添加的元素 e 作为链表的最后一个元素, 并维护进去
    // eg1: e="a1"
    void linkLast(E e) {
        //首先得到链表最后的节点,首次初始化默认为空
        final Node<E> l = last;
​
        // eg1: newNode    null<--"a1"-->null
        /** 创建一个e的Node节点,前置指向原last节点,后置指向null */
        final Node<E> newNode = new Node<>(l, e, null);
​
        /** 将newNode节点赋值为last节点 */
        last = newNode;
​
        // eg1: l=null
        if (l == null) {
            /** 如果是第一个添加的元素,则first指针指向该结点*/
            first = newNode; // eg1: first指向newNode
        } else {
            /** 如果不是第一个添加进来的元素,则更新l的后置结点指向新添加的元素结点newNode*/
            l.next = newNode;
        }
        size++;
        modCount++;
    }
// 【变量源码】
// 默认最后一个节点为空(-->null)【last 起指针作用】
transient Node<E> last;
// 默认首个节点为空(null<--)【first 起指针作用】
transient Node<E> first;
// 默认大小为 0
transient int size = 0;
​
// 3.1 remove 删除元素操作   操作返回被删除节点的值
    // eg1:链表中保存了{"a1","a2","a3","a4"},删除第一个元素,即:index=0
    public E remove(int index) {
        /** 
          * 校验传入的参数index是否超出了链表的最大下标且下标不为负数,
          * 如果超出,则抛出:IndexOutOfBoundsException异常
          */
        checkElementIndex(index);
        // eg1:node(index)返回需要删除的结点,即:"a1"
        return unlink(node(index)); /** 断开待删除结点的链接 */
    }
​
// 3.2 先 checkElementIndex(index) 检验传入参数是否合法(节点是否存在)
    private void checkElementIndex(int index) {
        // index >= 0 && index < size 是否超出了链表的最大下标且下标不为负数
        if (!isElementIndex(index)) {
            // 如果超出,则抛出:IndexOutOfBoundsException异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    //  是否超出了链表的最大下标且下标不为负数
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
​
// 3.3 然后拿到节点 node(index) 再将此节点的前后节点指向null unlink(node)
    // 链表二分法查找节点
    Node<E> node(int index) {
        /** 如果需要获取的index小于总长度size的一半,则从头部开始向后遍历查找 */
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++) {
                x = x.next; // 从first结点向后next查找,直到index下标node,返回node
            }
            return x;
        } else { /** 从尾部开始向前遍历查找 */
            Node<E> x = last;
            for (int i = size - 1; i > index; i--) {
                x = x.prev; // 从last结点向前prev查找,直到index下标node,返回node
            }
            return x;
        }
    }
​
// 3.4 将获取到的节点前后指针指向 null
// eg1: x  null<--"a1"-->"a2"
    E unlink(Node<E> x) {
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
​
        /** x.prev为null,表示x结点为第一个元素*/
        if (prev == null) {
            first = next; // 更新first头指针为x结点的后置结点
        } else {
            prev.next = next; // 将x的前置结点与x的后置结点相连接
            x.prev = null; // 断开x的前置指针
        }
​
        /** x.next为null,表示x结点为最后一个元素*/
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev; // 将x的后置结点与x的前置结点相连接
            x.next = null; // 断开x的后置指针
        }
​
        x.item = null;
        size--;
        modCount++;
        return element;
    }

HashMap源码解析

HashMap源码解析【java进阶笔记二】_yuyu的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值