Java-08 泛型 、ArrayList

泛型 (Generics)

  • 从java 5开始,增加了泛型技术

什么是泛型?

  • 将类型变为参数,提高代码的复用率
// T:类型参数
public class Student<T> {
  private T score;
  public Student(T score ) {
    this.score = score;
  }
}

    Student<String> stu1 = new Student<String>("A");
    Student<Double> stu2 = new Student<Double>(88.8);
    
    // java 7之后 可以省略右边的类型
    Student<Integer> stu3 = new Student<>(44);

泛型类型的类型参数只能用于实例方法中,不能用于静态方法。因为泛型类型的类型参数是跟实例相关联的

public class Student<T> {
  private T score;
  public Student(T score ) {
    this.score = score;
  }
  // 报错
  public static void print(T e) {
    
  }
}

但是可以使用泛型方法

  public static <T1, T2>void print(T1 e, T2 s) {

  }

可以通过extends对类型参数进行限制,比如<T extends A>

extends后面可以跟上类名,接口名,代表T必须是A类型,或者继承/实现了A。 跟dart类似

public class Student<T extends Number> {

}

可以同时添加多个限制,比如<T extends A & B & C>,代表T必须同时满足A、B、C

不过有一个需要注意的是,类名需要放在接口的前面,且最多只能有一个类名(因为java不支持多类型)

原始类型(Raw Type)

什么是原始类型?

  • 没有传递具体的类型给泛型的类型参数

通配符

在泛型中,?被称为通配符。通常用作变量类型,返回值类型的类型参数。不能用作泛型方法调用、泛型类型实例化、泛型类型定义的类型参数

通配符 - 上界

可以通过extends设置类型参数的上界

// 类型参数必须是Number类型或者其子类型
  static void showBox(Box<? extends Number> box) {

  }
public class Main {
  public static void main(String[] args) {
    Box<Integer> box1 = new Box<>();
    Box<Double> box2 = new Box<>();
    Box<Object> box3 = new Box<>();

    Box<? extends Number> box4 = null;
    box4 = box1;
    box4 = box2;
    // 报错 因为限制了泛型类型必须为Number或其子类型
    box4 = box3;
    
    showBox(box1);
    showBox(box2);
    // 报错
    showBox(box3);
  }

  // 1. 使用泛型方法
  public static <T extends Number> void showBox(Box<T> box) {

  }
  // 2. 使用通配符
  // 类型参数必须是Number类型或者其子类型
  static void showBox(Box<? extends Number> box) {

  }
}

通配符- 下界

可以通过super设置类型参数的下届

  // 类型参数必须是Integer类型或者Integer的父类型
  void testLower(Box<? super Integer> box) {

  }

通配符 - 无限制

  // 无限制 类型参数是什么类型都可以
  void test(Box<?>box) {

  }

ArrayList的常用方法

    ArrayList list = new ArrayList();
    list.add(11);
    list.add(false);
    list.add(null);
    // 添加元素到指定的下标
    list.add(0, "Jack");

    System.out.println(list.size());

类似于OC中的可变数组

遍历

    ArrayList<Integer> list = new ArrayList<>();
    list.add(11);
    list.add(22);
    list.add(33);
    // 遍历
    // 经典
    int size = list.size();
    for (int i = 0; i < size; i++) {
      System.out.println(list.get(i));
    }
    // 迭代器
    Iterator<Integer> it = list.iterator();
    while(it.hasNext()) {
      System.out.println(it.next());
    }

    // for-each 本质也是使用迭代器 跟上面方法本质上是一样的
    for (Integer integer : list) {
      System.out.println(integer);
    }

    list.forEach(new Consumer<Integer>() {
      @Override
      public void accept(Integer t) {
        System.out.println(t);
      }
    });
    // lambda表达式
    list.forEach((i) -> {
      System.out.println(i);
    });

    list.forEach((i) -> System.out.println(i));

    // 方法引用简化
    list.forEach(System.out::println);

for-each 格式

    // for-each 格式
    for (元素类型 变量名 : 数组\Iterable) {

    }

实现了Iterable接口的对象,都可以使用for-each格式遍历元素, 比如ListSet

Iterabel在使用for-each格式遍历元素时,本质上使用了Iterator对象

public class ClassRoom implements Iterable<String> {
  private String[] students;
  public ClassRoom(String... students) {
    this.students = students;
  }

  public String[] getStudents() {
    return students;
  }

  public void setStudents(String[] students) {
    this.students = students;
  }
  @Override
  public Iterator<String> iterator() {
    // 返回一个迭代器
    return new ClassRoomIterator();
  }
  // 自定义一个迭代器
  private class ClassRoomIterator implements Iterator<String> {
    private int cursor;
    @Override
    public boolean hasNext() {
      // 是否有下一个元素 取决于游标指向的位置是否超过数组边界
      return cursor < students.length;
    }
    @Override
    public String next() {
      // 取出下一个元素
      // 1. 取出当前游标指向的元素
      // 2. 将游标指向下一个位置
      return students[cursor++];
    }
  }
}



public class Main {
  public static void main(String[] args) {
    ClassRoom room = new ClassRoom("Jack","Bob");
    for (String name : room) {
      System.out.println(name);
    }

    Iterator<String> it = room.iterator();
    while(it.hasNext()) {
      System.out.println(it.next());
    }
  }
}
循环删除元素

循环删除元素时 可以使用Iteratorremove方法

    ArrayList<Integer> list = new ArrayList<>();
    list.add(11);
    list.add(22);
    list.add(33);
    list.add(44);

    Iterator<Integer> it = list.iterator();
    while(it.hasNext()) {
      // 取出元素
      it.next();
      // 删除元素
      // 使用迭代器的remove方法 而不是list的remove方法
      it.remove();
    }
    System.out.println(list.toString());

ArrayList的扩容

ArrayList每次扩容都是上一次容量的1.5倍

// 计算新的容量
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 原来的容量右移一位 再加上原来的容量
        // 加法与位移运算的效率是高于乘法/除法的
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

当添加的元素数量很多时,可能会多次扩容,为了优化性能,可以考虑一次扩容(前提是知道扩容的大小)

      ArrayList<Integer> list = new ArrayList<>();
      for (int i = 0; i < 5; i++) {
        list.add(i);
      }
      // 提前扩容
      list.ensureCapacity(list.size() + 10000);
      for (int i = 0; i < 10000; i++) {
        list.add(i);
      }

当容量很大,但实际存储的元素很少时,可以考虑适当的缩容

      ArrayList<Integer> list = new ArrayList();
      for (int i = 0; i < 10000; i++) {
        list.add(i);
      }
      list.clear();

      for (int i = 0; i < 10; i++) {
        list.add(i);
      }
      // 缩容
      list.trimToSize();

上面的list的容量为10000, 但最后实际上只存放了10个元素,可以考虑缩容

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

trimToSize 把元素拷贝到更小的数组中(长度为size)

LinkedList

LinkedList是一个双向链表,跟ArrayList一样都实现了List接口。API跟ArrayList类似

      List<Integer> list = new LinkedList<>();
      for (int i = 0; i < 5; i++) {
        list.add(i);
      }

LinkedList跟ArrayList的区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272