Typescript高级: 对泛型和多态的应用, 实现Java中的ArrayList和LinkedList

ArrayList

1 ) 概述

  • 在Java中,ArrayList是一个非常常用且强大的数据结构,它提供了动态数组的功能
  • 能够方便地添加、删除和访问元素。在TypeScript中,虽然并没有内置的ArrayList类型
  • 但我们可以通过类与接口来模拟实现ArrayList的功能

2 )实现

ts 复制代码
interface List<T> {
    size: number;
    add(item: T): void;
    get(index: number): T | undefined;
    remove(item: T): boolean;
    show(): void;
}

class ArrayList<T> implements List<T> {
    public array: T[];
    private index: number = 0;
    public size: number = 0;
  
    constructor() {
      this.array = [];
    }
  
    // 添加元素,重载 add 方法
    add(item: T): void;
    add(item: any): void;
    add(item: number): void;
    add(item: string): void;
    add(item: any): void {
      this.array.push(item);
      this.size++;
      this.index = this.array.length; // 更新索引为数组最后一个元素的索引+1
    }
  
    // 获取元素
    get(index: number): T | undefined {
      return this.array[index];
    }
  
    // 删除元素,重载 remove 方法
    remove(item: T): boolean;
    remove(item: any): boolean;
    remove(item: number): boolean;
    remove(item: string): boolean;
    remove(item: any): boolean {
      const index = this.array.indexOf(item);
      if (index !== -1) {
        this.array.splice(index, 1);
        this.size--;
        return true;
      }
      return false;
    }
  
    // 显示全部数据
    show(): void {
      console.log(this.array);
    }
  
    // 更新元素,这里并没有在接口中定义,作为额外功能添加
    update(index: number, newValue: T): boolean {
      if (index >= 0 && index < this.size) {
        this.array[index] = newValue;
        return true;
      }
      return false;
    }
}

const arrayList = new ArrayList<string>();

// 添加元素
arrayList.add("Hello");
arrayList.add("World");
arrayList.add(42); // 这将不会报错,因为 TypeScript 中的泛型在运行时会被擦除,并且 add 方法被重载以接受 any 类型

// 显示元素
arrayList.show(); // 输出: [ 'Hello', 'World', 42 ]

// 获取元素
console.log(arrayList.get(0)); // 输出: Hello

// 更新元素
arrayList.update(1, "TypeScript");
arrayList.show(); // 输出: [ 'Hello', 'TypeScript', 42 ]

// 删除元素
console.log(arrayList.remove("TypeScript")); // 输出: true
arrayList.show(); // 输出: [ 'Hello', 42 ]

// 获取数组大小
console.log(arrayList.size); // 输出: 2

3 )说明

  • 在基础List类型中,我们定义了如下方法和属性

    • add(item: T): 添加一个元素到ArrayList中
    • get(index: number): 根据索引获取元素
    • size: 存储ArrayList中元素的数量
    • remove(item: T): 删除一个元素
    • show(): 显示ArrayList中的所有元素
  • ArrayList 这个类继承List接口,并实现其所有的方法

  • 这里面实现了方法的多态和泛型

    • 1 )多态(Polymorphism)的主要优势在于提高了代码的可维护性和扩展性
    • 通过使用多态,我们可以编写更加灵活和可重用的代码
    • 因为我们可以定义通用的接口或方法,而不必关心具体实现细节
    • 2 ) 泛型(Generics)是TypeScript中一种强大的工具
    • 它允许我们在不丢失类型信息的前提下,编写可重用的组件
    • 这些组件可以与多种不同的类型一起工作,而不仅仅是一个类型
    • 泛型的主要作用是提供类型安全,同时确保代码的可复用性
    • 通过使用泛型,我们可以在保证类型安全的同时
    • 让函数与多种类型一起工作
  • 总结来说

    • 泛型和多态是TypeScript中两个强大的特性
    • 它们分别通过提供类型安全和允许统一操作不同数据类型来增强代码的可复用性和灵活性
    • 在实际开发中,结合使用泛型和多态可以帮助我们编写更加健壮、可维护和可扩展的代码

LinkedList

1 ) 概述

  • 在TypeScript中,实现一个双向链表(Doubly Linked List)相比于ArrayList会更加复杂
  • 因为我们需要维护每个节点的两个指针:一个指向前一个节点,另一个指向下一个节点
  • 同时,我们还要确保List接口的所有方法都能得到正确实现
  • 在数据结构中,链表是一种动态分配内存空间的线性数据结构
  • 由一系列的节点组成,每个节点通常包含两部分:
    • 一部分用于存储数据
    • 另一部分用于存储指向下一个节点的指针
  • 而双向链表,顾名思义,就是每个节点不仅包含指向下一个节点的指针
  • 还包含指向前一个节点的指针, 这使得双向链表在插入和删除节点时更为灵活

2 )实现

ts 复制代码
interface List<T> {
    add(element: T): void;
    get(index: number): T | undefined;
    size: number;
    remove(element: T): boolean;
}

class N<T> {
    value: T;
    next: N<T> | null;
    prev: N<T> | null;

    constructor(value: T, next: N<T> | null = null, prev: N<T> | null = null) {
        this.value = value;
        this.next = next;
        this.prev = prev;
    }
}

class LinkedList<T> implements List<T> {
    private head: N<T> | null = null;
    private tail: N<T> | null = null;
    public size = 0;

    add(element: T): void {
        const newNode = new N(element, null, this.tail);
        if (this.tail) {
            this.tail.next = newNode;
        } else {
            this.head = newNode;
        }
        this.tail = newNode;
        this.size++;
    }

    get(index: number): T | undefined {
        if (index < 0 || index >= this.size) {
            return undefined;
        }
        let current = this.head;
        for (let i = 0; i < index; i++) {
            current = current!.next;
        }
        return current?.value;
    }

    remove(element: T): boolean {
        let current = this.head;
        while (current) {
            if (current.value === element) {
                if (current.prev) {
                    current.prev.next = current.next;
                } else {
                    this.head = current.next;
                }
                if (current.next) {
                    current.next.prev = current.prev;
                } else {
                    this.tail = current.prev;
                }
                this.size--;
                return true;
            }
            current = current.next;
        }
        return false;
    }
}

// 使用示例
const linkedList = new LinkedList<number>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
console.log(linkedList.get(1)); // 输出: 2
console.log(linkedList.size); // 输出: 3
linkedList.remove(2);
console.log(linkedList.get(1)); // 输出: 3

3 )说明

  • 首先,我们定义了一个 List 接口,它规定了链表需要实现的基本方法:

    • 添加元素(add)
    • 获取指定位置的元素(get)
    • 获取链表大小(size)
    • 以及删除元素(remove)
    • 这个接口为后续的链表实现提供了统一的规范
  • 在 LinkedList 的实现中,节点被定义为一个名为 N 的内部类

    • 每个节点包含三个属性:value 用于存储数据,next 指向下一个节点,prev 指向前一个节点
    • 这样的结构使得链表能够双向遍历,从而更容易实现某些操作,如删除节点
  • LinkedList 类实现了 List 接口,并包含了维护链表状态的重要属性

    • head 指向链表的第一个节点,tail 指向链表的最后一个节点,size 记录链表的大小
  • 添加元素:add 方法在链表尾部添加一个新节点

    • 首先,创建一个新的节点,并设置其 next 为 null,prev 为当前的尾节点
    • 然后,更新尾节点为新节点,并如果原链表为空(即 head 和 tail 都为 null)
    • 则将 head 也指向新节点, 最后,链表的大小加一
  • 获取元素:get 方法通过遍历链表找到指定位置的元素

    • 它首先检查索引是否合法,然后从 head 开始遍历,直到找到对应位置的节点或遍历完整个链表
    • 这个过程的时间复杂度是 O(n),其中 n 是链表的大小
  • 删除元素:remove 方法遍历链表,查找并删除指定的元素

    • 当找到匹配的节点时,它更新相邻节点的指针以跳过该节点
    • 并调整 head 或 tail 如果被删除的是头节点或尾节点。最后,链表的大小减一
  • 总结

    • 双向链表 LinkedList 的实现简单而高效,通过双向指针实现了灵活的节点操作
    • 它的主要优势在于插入和删除节点时的性能,尤其是当需要在特定位置进行这些操作时
    • 然而,链表在随机访问元素方面的性能不如数组,因为需要从链表头部开始遍历以找到指定位置的元素
    • 在实际应用中,链表通常用于需要频繁插入和删除元素的场景,如实现缓存、LRU(最近最少使用)算法等
    • 而数组则更适用于需要频繁访问元素的场景,如搜索、排序等
    • 因此,在选择使用链表还是数组时,需要根据具体的应用场景和需求进行权衡
相关推荐
Martin -Tang12 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发12 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端