包装类、泛型、集合

目录

包装类

认识包装类

什么是包装类

​编辑

为什么要用包装类

使用包装类

自动装箱和拆箱

​编辑

享元模式

泛型

泛型类

泛型接口

泛型方法

泛型通配符

集合

Collection

通用API

集合遍历

Iterable(迭代器)

迭代器遍历

增强for遍历

List

Set

Map

​编辑

常用API

遍历

1、keySet

2、values

3、entrySet


包装类

认识包装类

什么是包装类

Java中包装类是指8种基本数据类型对应的引用类型。

基本类型 包装类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

java中包装类的继承关系

为什么要用包装类

1)封装了许多有用的方法

2)支持null值(注册时需求可以不写年龄,但是age不能等于0)

3)支持泛型

做项目写实体类时,我们推荐使用包装类。

使用包装类

静态常量

静态方法

常用的成员方法或静态成员方法:

java 复制代码
  public static void main(String[] args) {
        // 1.静态常量
        System.out.println(Integer.MAX_VALUE); // 2147483647
        System.out.println(Integer.MIN_VALUE); // -2147483648

        // 2.创建对象
        Integer i1 = Integer.valueOf(100);
        Integer i2 = Integer.valueOf("100");
        System.out.println(i1);
        System.out.println(i2);

        // 3.调用方法
        System.out.println(i1.equals(i2)); // true
        int num1 = i1.intValue();
        float num2 = i1.floatValue();
        System.out.println("num1: " + num1);
        System.out.println("num2: " + num2);

        // 4.调用静态方法
        String hexString = Integer.toHexString(100);
        System.out.println(hexString);

        int num3 = Integer.parseInt(hexString, 16);
        System.out.println("num3: " + num3);
    }

自动装箱和拆箱

开发中就会存在一个出现非常频繁的需求:把包装类与基本类型互相转换

JDK5开始Java引入了自动装箱和拆箱机制,实现了包装类与基本类型之间的自动转换。

当我们把基本类型当成包装类来用的时候就是自动装箱。

自动装箱,其本质就是jvm帮我们调用了valueOf方法

自动拆箱,本质就是调用了包装类的intValue()方法

注意 :自动拆箱本质是调用了包装类的成员方法,例如Integer中的intValue()方法。

所以包装类的变量如果为null,自动拆箱将触发空指针异常。


享元模式

问:以下代码的运行结果是什么?

java 复制代码
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2);//true

Integer i3 = 200;
Integer i4 = 200;
System.out.println(i3 == i4);//false

享元模式(Flyweight) 是一种设计模式,其作用是通过共享对象 来减少创建对象的数量,以达到节省内存提高性能 的目的。实现方式就是在第一次创建对象时将对象缓 存起来,下次就不再创建,直接使用。



泛型

泛型类

1)泛型(Generics),也叫参数化类型(TypeParameter),也就是说类型是不确定的,可以像参数一样传递和赋值。

2)类上定义泛型,语法是 类名<泛型类型> { }

java 复制代码
public class 类名 <泛型> {
    // ...
}

3)泛型类型可以自定义,但是通常我们有一些约定:

用单个字母表示泛型类型

T:表示Type

E:表示Element(集合多数)

K:表示Key

V:表示Value

类上的泛型不确定,在创建本类对象的时候才确定的

java 复制代码
public class GenericList<E>{


}
java 复制代码
GenericList<String> strList = new GenericList<>();
    strList.add("word");

泛型可以约束集合类型

泛型无需自己强转,定义的是什么类型,取出来就是什么类型

注意

定义在类上的泛型是在创建类的对象时才传入类型参数,确定真实数据类型的。所以类上的泛型是属于对象的泛型,在实例化阶段才能使用,它只能用成员变量、成员方法上。

因此,类中的静态方法、静态变量是无法使用类上定义的泛型的


泛型接口

java 复制代码
public interface InterfaceName<泛型> {
    // 泛型接口中的方法都可以直接使用泛型
}

泛型接口就是在接口名后面定义了泛型的接口。

java 复制代码
public interface IList<E>{
    void add(E e);
    E get(int index);
    int size();
}

泛型接口无法创建对象,只能通过实现类实现类的对象来传递泛型类型。

java 复制代码
public class IntList implements IList<Integer>{
}
java 复制代码
public class GenericList<E> implements IList<E>{
    
}

泛型方法

静态方法 无法使用类上定义的泛型,需要在方法上单独声明泛型,这样的方法就是泛型方法

static <泛型类型> 返回值 方法名(泛型类型 参数)

java 复制代码
public <泛型> 返回值类型 方法名(参数类型 参数名, ...) {
}

注意:泛型方法的泛型要写在返回值类型之前,其它修饰符之后。

定义一个方法,可以接收任意元素类型的数组,并打印其中的元素,可以这样写:

java 复制代码
public static <T> void printArray(T[] array) {
    for (int i = 0; i < array.length; i++) {
        System.out.println(array[i]);
    }
}
java 复制代码
    public static void main(String[] args) {
        String[] arr = {"张三", "李四", "王五"};

        printArray(arr);
    }

    public static <T> void printArray(T[] array) {
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }

在方法调用的时候才确定泛型中的具体类型。

泛型方法不仅在方法内部可以使用泛型,返回值也同样可以是泛型,例如:

java 复制代码
    public static void main(String[] args) {
        String[] arr = {"张三", "李四", "王五"};

        // 传入String类型数组,返回值就是String
        String element = getElement(arr, 1);
        System.out.println(element);
        
        Integer[] nums = {10, 20, 30};
        
        // 传入Integer类型数组,返回值就是Integer
        Integer num = getElement(nums, 1);
        System.out.println(num);
    }
    
    // 方法的返回值也使用泛型,在调用方法传参时,泛型确定了,返回值也确定了。
    public static <T> T getElement(T[] array, int index) {
        if (index < 0 || index >= array.length) {
            return null;
        }
        return array[index];
    }
    
    public static <T> void printArray(T[] array) {
        // ... 略
    }

泛型通配符

当使用泛型类或泛型接口时,通常需要给泛型传入真实的数据类型。但如果你使用的时候依然不确定该传递什么数据类型,就可以使用泛型通配符:?

1.无限制通配符,可以理解为object,可以接收任意泛型类型,但无法访问真实类型中的成员,也无法向集合中写入数据

java 复制代码
public static void print(IList<?> list) {
    Object o = list.get(0);
}

2.上限通配符,可以理解为Number的某个子类,可以接收任意Number子类类型的泛型,比如Integer、Long

java 复制代码
public static void print(GenericList<? extends Number> list) {
    Number number = list.get(0);
}

3.下限通配符,可以理解为Number的某个父类,无法访问Number成员

java 复制代码
public static void print(GenericList<? super Number> list) {
    
}

集合

数组是内存中的连续空间,如果有其他程序占用内存,会紧接着数组存储。

数组大小不可变,数组查找速度慢

集合框架包含很多种不同的集合,大体上可以分两大类:

  • Collection:每个元素都是单个数据 的集合,也叫单列集合

  • Map:每个元素都是成对数据 的集合,也叫双列集合


最上面的红色部分全都是接口(Interface),而下方灰色的的则是各种不同的实现类(class)

  • List: 表示元素为固定序列的集合,元素都可以用索引(index)进行随机访问

  • Queue:表示队列类型的集合,强调元素读写的顺序,通常只能按照特定的顺序读写数据

  • Set:表示无重复元素的集合,强调元素的唯一性,元素顺序则无法保证


Collection

通用API

Collection定义了集合中的通用操作方法,包括:

|-------------|-------------------------------------|------------------------------|
| 返回值 | 方法 | 描述 |
| boolean | add(E e) | 添加元素到集合 |
| boolean | remove(Object o) | 从当前集合中移除与指定元素一样的第一个元素 |
| int | size() | 返回集合中的元素个数 |
| boolean | contains(Object o) | 判断当前集合中是否包含指定元素 |
| boolean | isEmpty() | 判断当前集合中是否为空集合 |
| void | clear() | 清空当前集合中所有元素 |
| boolean | addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到当前集合 |
| boolean | removeAll(Collection<?> c) | 移除当前集合中与指定集合中一样的元素 |
| boolean | retainAll(Collection<?> c) | 保留当前基于与指定集合中都存在的元素(求交集) |
| <T> T | toArray(T[] arr) | 返回一个包含集合中所有元素的数组,其类型是指定的数组类型 |

上面表格中加黑加粗的是相对更常用的一些方法。

java 复制代码
    public static void main(String[] args) {
        // 1.创建集合对象,这里使用Collection下的一个实现类,ArrayList
        Collection<String> c1 = new ArrayList<>();

        // 2.添加元素
        c1.add("hello");
        c1.add("world");
        c1.add("java");

        System.out.println("初始:" + c1);
        // 3.获取集合中元素数量
        System.out.println("size: " + c1.size());

        // 4.删除元素
        c1.remove("world");
        System.out.println("删除world:" + c1);
        System.out.println("size: " + c1.size());

        // 5.判断集合中是否包含某个元素
        System.out.println("contains hello: " + c1.contains("hello"));
        System.out.println("contains world: " + c1.contains("world"));

        // 6.判断集合是否为空
        System.out.println("isEmpty: " + c1.isEmpty());
        c1.clear();
        System.out.println("after clear, isEmpty: " + c1.isEmpty());

        System.out.println("after clear: " + c1);
    }
java 复制代码
初始:[hello, world, java]
size: 3
删除world:[hello, java]
size: 2
contains hello: true
contains world: false
isEmpty: false
after clear, isEmpty: true
after clear: []

总结:

集合相比数组的最大区别是:集合长度可变,数组长度固定

集合的常用API有哪些:

1)add:添加元素

2)remove:删除元素

3)clear:清空集合

4)size:集合元素数量

5)contains:是否包含指定元素


集合遍历

Iterable(迭代器)

迭代器的方法:

返回值类型 方法名 描述
boolean hasNext() 如果有未迭代的元素则返回 true,否则返回 false
E next() 返回下一个未迭代的元素
void remove() 从集合中删除最近一次next方法返回的元素,一次next后只能调用一次remove

每个集合都应该有一个自己的迭代器。每个集合都可以实现自己的迭代方法进行遍历。

java 复制代码
public interface Iterator<E>{

    //判断是否有更多为迭代的元素,有则返回true
    boolean hasNext();

    //返回下一个要迭代的元素
    E next();

    //删除上一次迭代的元素,一定用在next后
    void remove();
}
迭代器遍历

迭代器遍历集合的原理:

Iterator中会维护一个指向下一个未迭代元素的指针,每次调用next()方法迭代一个元素后,指针都会向后移动一次,直到所有元素都迭代完毕。

因此,使用Iterator的流程通常是这样的:

  1. 利用hasNext方法判断是否有未迭代的元素

  2. 如果有,则用next方法获取下一个未迭代的元素,此时迭代器指针会移动到集合中的下一个未迭代元素

  3. 回到步骤1

java 复制代码
 public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("hello");
        c.add("world");
        c.add("java");

        // 1.获取迭代器对象
        Iterator<String> it = c.iterator();
        // 2.判断迭代器中是否有未迭代的元素
        while (it.hasNext()) {
            // 3.获取迭代器中的下一个未迭代元素
            String element = it.next();
            System.out.println(element);
        }
    }
增强for遍历

语法:

java 复制代码
for(元素类型 变量名 : 集合) {
    // ...
}
java 复制代码
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("hello");
        c.add("world");
        c.add("java");

        for (String str : c) {
            System.out.println(str);
        }
    }

List

List是有序 集合,允许重复 元素,而且可以精确控制每个元素在集合中的位置,通过索引插入或访问元素。

常见方法:

返回值 方法 描述
boolean add(int index, E e) 添加元素到集合中的指定index位置
E set(int index, E e) 用指定的元素替换此list中指定位置的元素,返回被替换的元素
E get(int index) 返回集合中指定index位置的元素
E remove(int index) 删除list中指定位置的元素,并返回该元素
List<E> subList(int from, int to) 返回此List中从from到to索引之间的部分,不包含to
static List<E> of(E ... elements) 返回一个包含任意数量元素的不可修改的List
java 复制代码
package com.itheima.p24;

import java.util.ArrayList;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("小丽");
        list.add("小妮");
        list.add("小美");
        list.add("小美");

        // 1.向指定位置插入元素
        list.add(3, "小泰");

        // 2.修改指定位置的元素
        list.set(0, "小吉");

        // 3.利用索引遍历元素
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        // 4.删除指定位置的元素
        String remove = list.remove(2);
        System.out.println("删除的元素是:" + remove);
        System.out.println(list);
    }
}

输出结果:

java 复制代码
小吉
小妮
小美
小泰
小美
删除的元素是:小美
[小吉, 小妮, 小泰, 小美]

可以看出:

  • List集合可以有重复元素

  • List集合可以用索引操作

  • List集合遍历出元素的顺序是确定的

  • List除了可以用迭代器、增强for,也可以用fori遍历

需要注意的是,如果我们使用List.of创建List集合,创建好的集合是不可变的 ,也就是说不能修改、删除、添加集合中的元素是只读的。

java 复制代码
List<String> list2 = List.of("hello", "world", "java");
list2.add("java");
list2.remove("java");
list2.set(0, "java");

程序会报错!!!

Set

Set是无序集合不允许 重复元素,无法通过索引 插入或访问元素。

返回值 方法 描述
static Set<E> of(E ... elements) 返回一个包含任意数量元素的不可修改的Set

Set集合最常见的实现类是HashSet

创建Set集合的元素,一定要实现equals方法,否则集合不知道是否重复。代码示例中String类型是引用类型,需要用equals进行比较,这是一个隐含条件。

java 复制代码
  public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("小黑");
        set.add("小马");
        set.add("小马");
        set.add("小珍");
        set.add("小珍");
        set.add("小牛");
        set.add("小牛");

        System.out.println(set);

        for (String s : set) {
            System.out.println(s);
        }
    }
java 复制代码
[小珍, 小马, 小牛, 小黑]
小珍
小马
小牛
小黑

可以看出,尽管我们向Set集合中重复添加了大量元素,但最终重复元素都只能存储一份,证明了Set集合是不允许重复元素的。

另外添加元素的顺序是:"黑,马,珍,牛",最终打印的却是:"珍,马,牛,黑",可见元素遍历顺序与添加顺序不一致。

当使用Set.of方法创建Set集合时,如果元素有重复会直接报错:

java 复制代码
Set s = Set.of("小黑", "小马", "小马", "小珍", "小珍", "小牛", "小牛");
System.out.println(s);

程序会报错!!!

总结:

List集合有什么特点?

有序:存储和遍历的顺序一致

有索引:可以用索引操作元素

允许重复:允许存储重复元素

Set集合有什么特点

无序:存储和遍历的顺序不一定相同(大概率是不同)

无索引:不能用索引操作元素

不允许重复:不允许存储重复元素(存储是Java对象时需要重写equals和hashcode)


Map

Collection集合存储的元素都是单个值,而Map集合的元素则是成对出现,每一个元素都要包含key和value两部分,或者换句话说,Map中的元素是key和value的键值对

Map集合可以看作是把key与value之间建立了一种映射关系,因此Map中的大多数操作都是与key有关:

  • 根据key查询value

  • 根据key删除键值对

  • ...

Map集合有很多不同实现类,其底层实现原理也各不相同:

常用API

返回值 方法 描述
V put(K key, V value) 将指定的value与指定的key关联,如果key已经存在则覆盖旧值并返回
V get(K key) 返回指定key对应的value,不存在则返回null
V remove(Object key) 从map中删除指定key对应的键值对,并返回其value
int size() 返回此map中键值对的数量
boolean containsKey(K key) 判断此map中是否包含指定的key的映射
boolean isEmpty() 判断此map是否为空
void clear() 清空此map中的所有键值映射
java 复制代码
   public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("乔峰", "阿朱");
        map.put("杨过", "小龙女");
        map.put("郭靖", "黄蓉");
        map.put("张无忌", "周芷若");
        map.put("张无忌", "赵敏");
        map.put("令狐冲", "东方不败");

        System.out.println(map);// {令狐冲=东方不败, 杨过=小龙女, 乔峰=阿朱, 郭靖=黄蓉, 张无忌=赵敏}

        String value = map.get("张无忌");
        System.out.println(value); // 赵敏

        System.out.println("contains: " + map.containsKey("张无忌")); // contains: true
        map.remove("张无忌");
        System.out.println("contains: " + map.containsKey("张无忌")); // contains: false

        System.out.println("isEmpty: " + map.isEmpty()); // false
        map.clear();
        System.out.println("isEmpty: " + map.isEmpty()); // true
    }

Map集合具备以下特点:

  • Map的键(key)不可重复

  • Map的值(value)可以重复

  • 每一个key有且只有一个value与之对应


遍历

Map集合是双列集合,存储的是键值对信息,所以没有办法用传统方式遍历。

返回值 方法 描述
Set<K> keySet() 返回包含此map中所有key的Set集合
Collection<V> values() 返回包含此map中所有value的Collection集合
Set<Map.Entry<K,V>> entrySet() 返回此map中所有的键值对的Set集合
  • keySet :返回的是Map中所有key的Set集合,这是因为key是不可重复的,而Set刚好具备不可重复的特性

  • values :返回的是Map中所有value的集合,这里用Collection,因为value值是可重复的,不能用set

  • entrySet :返回的则是键值对的集合,注意,这里键值对是一个整体,是Map的一个内部类型Map.Entry,其中既有键,也有值

我们遍历Map集合也就有了三种不同的方式。

1、keySet

keySet方法返回的是Map中所有key的Set集合,而拿到key以后,我们就可以调用Mapget方法,根据key获取value了

java 复制代码
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("乔峰", "阿朱");
        map.put("杨过", "小龙女");
        map.put("郭靖", "黄蓉");
        map.put("张无忌", "赵敏");
        map.put("令狐冲", "东方不败");

        loopByKeySet(map);
    }

    private static void loopByKeySet(Map<String, String> map) {
        // 1.获取所有的key
        Set<String> keySet = map.keySet();
        // 2.遍历key
        for (String key : keySet) {
            // 3.根据key获取value
            String value = map.get(key);
            System.out.println(key + " ♥ " + value);
        }
    }

2、values

values方法返回的是Map中所有value的集合。由于Map是从key到value的映射,我们可以根据key找value,但反过来就不行。

因此,使用values方法你只能拿到所有value,拿不到key。

java 复制代码
private static void loopByValues(Map<String, String> map) {
    // 1.获取所有的value
    Collection<String> values = map.values();
    // 2.遍历value
    for (String value : values) {
        System.out.println("? ♥ " + value);
    }
}

3、entrySet

entrySet方法返回的则是键值对 的集合,注意,这里键值对是一个整体 ,是Map的一个内部类型Map.Entry,其中既有键,也有值,也提供了获取键和值的方法。

java 复制代码
public interface Map<K,V> {

    Set<Map.Entry<K, V>> entrySet();
    
    // ...

    interface Entry<K, V> { // 在类的内部也可以定义类,我们称为内部类
        
        K getKey();
    
        
        V getValue();
        
        // ...
    }
}

由于Map中的每一个键值对都是唯一的,所以,entrySet方法的返回值是一个Set集合。而集合的元素类型就是Map中的Entry类型,写作Map.Entry.

另外它与Map一样有两个泛型,K表示key的类型,V表示value的类型,因此完整写法就是Map.Entry<K,V>。如果再把它放到Set<>集合的泛型中,就是Set<Map.Entry<K,V>>

拿到了Set集合,我们就能遍历它,获取其中的每一个Entry,再调用Entry中的getKeygetValue获取键和值。

java 复制代码
private static void loopByEntrySet(Map<String, String> map) {
    // 1. 获取所有的键值对(Map.Entry)
    Set<Map.Entry<String, String>> entrySet = map.entrySet();
    // 2. 遍历集合,获取每一个键值对
    for (Map.Entry<String, String> entry : entrySet) {
        // 3. 获取键值对中的键和值
        String key = entry.getKey();
        String value = entry.getValue();
        System.out.println(key + " ❤ " + value);
    }
}

Map最强大的功能就在于根据key可以快速查找Value,例如,我需要实现根据id查找Girl的功能。

如果是用Collection集合,你只能遍历整个集合,一个个匹配集合中的元素,直到找到id匹配的为止

java 复制代码
private static Girl searchInCollectionById(int id) {
    List<Girl> list = List.of(
            new Girl(1, "小花", 162, "A"),
            new Girl(2, "小美", 162, "D"),
            new Girl(3, "小丽", 168, "C"),
            new Girl(4, "小琪", 170, "B")
    );

    for (Girl girl : list) {
        if (girl.getId() == id) {
            return girl;
        }
    }
    return null;
}

如果是用Map集合,只需要一个get方法:

java 复制代码
private static Girl searchInMapById(int id) {
    Map<Integer, Girl> map = new HashMap<>();
    map.put(1, new Girl(1, "小花", 162, "A"));
    map.put(2, new Girl(2, "小美", 162, "D"));
    map.put(3, new Girl(3, "小丽", 168, "C"));
    map.put(4, new Girl(4, "小琪", 170, "B"));

    return map.get(id);
}

相关推荐
华科易迅2 小时前
Spring装配对象方法-构造方法
java·后端·spring
是小蟹呀^2 小时前
Java 内部类详解:成员内部类、静态内部类、局部内部类与匿名内部类
java·内部类
于先生吖2 小时前
国际语言适配拼车系统 JAVA 后端源码 + 同城顺风车功能全解析
java·开发语言
czlczl200209252 小时前
KRaft原理
java·zookeeper
毕设源码-朱学姐3 小时前
【开题答辩全过程】以 基于SSM的宜佳家具电商平台为例,包含答辩的问题和答案
java
客卿1233 小时前
最小生成树(贪心)--构造回文串(字符串 + 回文判断 + 构造)
java·开发语言·算法
天启HTTP3 小时前
多线程环境下,动态IP怎么分配最合理
java·服务器·网络
hzb666663 小时前
xd_day32-day40
java·javascript·学习·安全·web安全·tomcat·php
东北甜妹3 小时前
Python脚本
java·开发语言·前端