【JAVA SE】第十四章 集合框架、语法糖和泛型

第十四章 集合框架、语法糖和泛型

一、集合框架

1.概念

Java 集合框架(Java Collections Framework,JCF)是为表示和操作集合而规定的一种统一的标准的体系结构

Java 集合框架包含如下内容:

  • 接口:是代表集合的抽象数据类型,例如 Collection、List、Set、Map等。之所以定义多个接口,是为了以不同的方式操作集合对象
  • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap
  • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,因为相同的方法可以在相似的接口上有不同的实现

在这里插入图片描述
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射

  • Collection 中可以容纳一组集合元素(Element)
  • Map 没有继承 Collection 接口,与 Collection 是并列关系。Map 提供键(key)到值的映射。一个 Map
    中不能包含相同的键,每个键只能映射一个值
    在这里插入图片描述
    在这里插入图片描述

2.接口

集合接口

接口描述
Iterable实现这个接口允许对象能进行迭代(按顺序访问线性结构中的每一项)
CollectionCollection 接口存储一组不唯一,无序的对象
ListList 接口存储一组不唯一,有序(按照插入顺序存储数据)的对象
QueueQueue 在 Collection 的基础上添加了增删改查操作,并且队列默认使用FIFO(先进先出)规则
DequeDeque 是一种线性集合,可以从两端操作的队列
SetSet 接口存储一组唯一,无序的对象
SortedSetSortedSet 接口存储有序的集合(数学概念上的)
NavigableSetNavigableSet 接口具有为指定搜索目标报告最接近匹配项的导航方法

集合类

描述
ArrayListArrayList 是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括 null。每一个 ArrayLis t都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。 ArrayList 擅长于随机访问,同时 ArrayList 是非同步的
Vector与 ArrayList 相似,但是 Vector 是同步的。所以说 Vector 是线程安全的动态数组。它的操作与 ArrayList 几乎一样
StackStack 继承自 Vector,实现一个后进先出的堆栈。Stack 提供 5 个额外的方法使得 Vector 得以被当作堆栈使用。基本的 push 和 pop 方法,还有 peek 方法得到栈顶的元素,empty 方法测试堆栈是否为空,search 方法检测一个元素在堆栈中的位置。Stack 刚创建后是空栈
LinkedListLinkedList 是一个双向链表,所以它除了有 ArrayList 的基本操作方法外还额外提供了 get,remove,insert 方法在 LinkedList 的首部或尾部。由于实现的方式不同,LinkedList 不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在 List 中进行插入和删除操作。与 ArrayList 一样,LinkedList 也是非同步的
ArrayDequeArrayDeque 实现了 Deque 接口,使用了可变数组,所以没有容量上的限制。需要注意的是,ArrayDeque不支持 null 值,而且 ArrayDeque 是非同步的的
HashSetHashSet 底层通过包装 HashMap 来实现,HashSet 在添加一个值的时候,实际上是将此值作为HashMap 中的 key 来进行保存。HashSet 不保证元素的顺序(这里所说的没有顺序是指元素插入的顺序与输出的顺序不一致)。HashSet 按 Hash 算法来存储集合的元素,因此具有很好的存取和查找性能。HashSet 允许使用 null 元素,且 HashSet 是非同步的
LinkedHashSetLinkedHashSet 具有可预测的迭代顺序,也就是我们插入的顺序。LinkedHashSet 是非同步的
EnumSetEnumSet 是一个专为枚举设计的集合类,EnumSet 中的所有元素都必须是指定枚举类型的枚举值,EnumSet 的集合元素也是有序的。EnumSet 集合不允许加入 null 元素
TreeSetTreeSet 是基于 TreeMap 实现的。TreeSet 中的元素支持 2 种排序方式:自然排序或者根据创建 TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。TreeSet 是非同步的

图接口

接口描述
MapMap 是由一系列键值对组成的集合,提供了 key 到 Value 的映射
SortedMapSortedMap 接口使 Key 保持在升序排列
NavigableMapNavigableMap 接口使导航存储在映射中的键和值成为可能

图类

描述
DictionaryDictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似
HashtableHashTable 是较为远古的使用 Hash 算法的容器结构了,现在基本已被淘汰,单线程转为使用 HashMap ,多线程使用ConcurrentHashMap。HashTable 的操作几乎和 HashMap 一致,主要的区别在于 HashTable 为了实现多线程安全,在几乎所有的方法上都加上了 synchronized 锁,而加锁的结果就是 HashTable 操作的效率十分低下。Hashtable可以插入 null
HashMapHashMap 是一个散列表,它存储的内容是键值对 (key-value) 映射。该类实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步
LinkedHashMapLinkedHashMap 保留插入的顺序,具有可预知的迭代顺序,允许使用null值和null键
WeakHashMap使用弱密钥的哈希表
EnumMapEnumMap 是保存枚举类型的Map,它要求 map 的 key 是枚举类型
AbstractMapAbstractMap 是一个抽象类,它实现了Map接口的绝大部分API函数
TreeMapTreeMap 实现 SortMap 接口,内部实现是红黑树。能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。TreeMap 不允许 key 的值为 null,且是非同步的

二、语法糖

1.概念

语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机专家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性

Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等

2.解语法糖

语法糖的存在主要是方便开发人员使用,但其实,Java 虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖

在 com.sun.tools.javac.main.JavaCompiler 的源码中,你会发现在 compile() 中有一个步骤调用 desugar(),这个方法就是用于解语法糖

三、泛型

1.概念

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法

2.泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map

泛型类在实例化类的时候指明泛型的具体类型

泛型类的基本格式:

class 类名称 <泛型标识>{
  private 泛型标识 变量类型 var; 
  .....

  }
}

实例:

public class Box<T> {
   
  private T t;
 
  public void add(T t) {
    this.t = t;
  }
 
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("Sisyphus"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

运行结果:

整型值为:10
字符串为:Sisyphus

3.泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

//定义一个泛型接口
public interface Generator<T> {
    public T next();
}

实现泛型接口的类(未传入泛型实参):

/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

实现泛型接口的类(传入泛型实参):

/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口 Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的 Generator 接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

4.泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用

定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前
  • 每个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开
  • 类型参数只能代表引用型类型,不能是基本类型

实例:

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
 
    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
 
        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组
 
        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组
 
        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}

运行结果:

整型数组元素为:
1 2 3 4 5

双精度型数组元素为:
1.1 2.2 3.3 4.4

字符型数组元素为:
H E L L O

5.类型通配符

类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类

import java.util.*;
 
public class GenericTest {
     
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        getData(name);
        getData(age);
        getData(number);
       
   }
 
   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }
}

运行结果:

data :icon
data :18
data :314

解析:因为getData()方法的参数是List类型的,所以name,age,number都可以作为这个方法的实参,这就是通配符的作用

类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型

import java.util.*;
 
public class GenericTest {
     
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        //getUperNumber(name);//1
        getUperNumber(age);//2
        getUperNumber(number);//3
       
   }
 
   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }
   
   public static void getUperNumber(List<? extends Number> data) {
          System.out.println("data :" + data.get(0));
       }
}

运行结果:

data :18
data :314

解析:getUperNumber(name);会报错,因为 getUperNumber() 方法中的参数已经限定了参数泛型上限为 Number,所以泛型为 String 的参数是不在这个范围之内的

特别地,类型通配符下限通过形如 List<? super Number> 来定义,表示类型只能接受 Number 及其三层父类类型

6.应用场景

帮助数据类型的检查

String[] a = new String[5];
a[0] = 1;
a[1] = 1.6;
a[2] = 'c';

我们在使用数组时,如果像上面那样赋值,那么还未编译就会报错,方便程序员检查

List list = new ArrayList();
list.add("Sisyphus");
list.add(1);
list.add(1.6);
list.add('c')

我们在使用集合时,无论我们赋什么类型的值,集合都不会进行约束,那如果我们想像数组那样约束该怎么做呢?

我们可以引入泛型

List<String> list = new ArrayList<>();
list.add("Sisyphus");
list.add(1);
list.add(1.6);
list.add('c');

这样我们赋值的时候,只要是非字符串类型都会报错

泛型可以写出更加通用的代码`

如果我们需要遍历打印 3 个数据类型均不同的数组,我们可以通过重载的方式,写 3 个方法

Integer[] a = {1,2,3};
Double[] b = {1.1,2.2,3.3};
String[] c = {"Amy","Bod","Chris"};

public void print(Integer[] arr){
	for(Integer i : arr){
		System.out.println(i);
	}
}

public void print(Double[] arr){
	for(Double i : arr){
		System.out.println(i);
	}
}

public void print(String[] arr){
	for(String i : arr){
		System.out.println(i);
	}
}

但这样代码太冗余了,我们完全可以采用泛型方法

Integer[] a = {1,2,3};
Double[] b = {1.1,2.2,3.3};
String[] c = {"Amy","Bod","Chris"};

public <E>void print(E[] arr){
	for(<E> i : arr){
		System.out.println(i);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

313YPHU3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值