Java基础教程 - 9 集合

更好的阅读体验:点这里 ( www.doubibiji.com
更好的阅读体验:点这里 ( www.doubibiji.com
更好的阅读体验:点这里 ( www.doubibiji.com

9 集合

什么是集合?

集合就是可以存储多个数据的一种数据类型,集合内的每一个数据称之为元素,其中的元素可以是任意类型的数据,包括字符串、数字、布尔,甚至是集合等等。

在前面已经学习了数组,数组也是可以批量的存储数据,但是集合比数组要强大的多。

数字一旦初始化以后,长度就固定了,是无法改变的,而且类型也确定了,同时提供的方法有限,对添加、删除、插入、搜索等操作就很不方便。

在Java中常用的集合分为3类:

  • List
  • Set
  • Map

不同的集合有不同的特点,例如:

  • 是否支持重复的元素,有的集合中的元素不能重复,有的可以。
  • 是否有序,有的集合中的元素是有序的,可以通过index来获取元素,有的集合是无序的,无法通过index获取元素。

下面一一介绍。

9.1 List

List 列表就是一个普通的集合,满足你最原始的想象,主要有如下特点:

  • 列表中的元素可以重复;
  • 列表中的元素可以修改,可以增加、修改、删除;
  • 列表中的元素是有序的,可以通过索引来访问;
  • 列表中可以存储不同数据类型的数据;

和数组很像,但是会自动扩容,支持不同类型的数据。

1 创建List

// 创建一个String类型的列表,<String>表示泛型
List<String> strList = new ArrayList<>();

// 创建一个Integer类型的列表
List<Integer> numbers = new ArrayList<>();

// 不指定类型,什么元素都可以放,和 List<Object> 一样
List objList = new ArrayList<>();

在创建集合的时候,可以通过 <类型> 泛型指定集合中元素的类型,关于泛型,后面在进阶篇再讲解。

指定泛型后,后面在获取集合中元素的时候,获取的数据就是泛型指定的类型;如果不指定泛型,那么元素在取出的时候是Object类型,那么赋值给指定类型的变量就需要强制转换,后面获取元素的时候再讲。


还可以在创建的时候,指定List初始化的大小,在使用的时候,如果元素超过了初始容量,会自动进行扩容。

如果知道列表中要放多少数据,建议在新建数组的时候指定列表大初始大小,这样避免扩容,从而耗费性能,因为列表的底层还是使用数组实现的,默认长度是10,而数组是无法动态修改大小的,所以在扩容的时候会创建一个新的列表,将之前列表中的数据拷贝到新列表中。

// 创建一个初始容量为5的ArrayList,用于存储Integer类型的元素  
List<Integer> list = new ArrayList<>(5);  

还可以创建不可变的List,不可变的List不能添加、修改、删除元素,只能读取元素,否则会报错:

List<String> colorList = List.of("red", "green", "blue");
System.out.println(colorList);    // [red, green, blue]

如果想快速创建包含元素的可变 List,可以使用如下方式:

// 将List.of(1, 2, 3)作为参数创建一个新的可变List
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));	

2 基础操作

添加元素

通过 add()addAll() 方法可以添加元素。

List<Integer> numList1 = new ArrayList<>();
numList1.add(1);        // 添加元素
numList1.add(2);

List<Integer> numList2 = List.of(3, 4);
numList1.addAll(numList2);      // 将numList2中所有的元素都添加到numList1中
访问元素

使用 get() 方法,通过下标来访问列表中的元素,从0开始,注意不要越界。

List<Integer> numbers = List.of(1, 2, 3);
Integer integer = numbers.get(0);   // 访问元素:1
// 访问元素
System.out.println(integer); // 1

// 放入三个类型的数据,分别是integer、String、List列表
List itemList = List.of(1, "abc", List.of(1, 2, 3));
// 因为没有使用泛型,所以需要强制转换
Integer i = (Integer) itemList.get(0);
String s = (String) itemList.get(1);
List<Integer> list = (List<Integer>) itemList.get(2);

如果使用泛型,直接取出数据就是泛型的类型,如果没有使用泛型,那么取出的类型需要根据类型进行强制转换。

获取长度

通过 size() 方法,可以获取到List 的长度。

List<Integer> numbers = List.of(1, 2, 3);

// 获取长度
System.out.println(numbers.size()); // 3
修改元素

修改元素可以使用 set() 方法,修改指定位置的元素。

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));

numbers.set(1, 5);      // 修改元素
System.out.println(numbers); // [1, 5, 3]
插入元素

插入元素也是使用 addaddAll() 方法,使用第一个参数指定插入的位置即可。

List<Integer> numList1 = new ArrayList<>();
numList1.add(1);        // 添加元素
numList1.add(1, 2);     // 在index为1的位置添加元素

List<Integer> numList2 = List.of(3, 4);

// 将numList2插入到numList1中
numList1.addAll(2, numList2);      
System.out.println(numList1);			 // [1, 2, 3, 4]
删除元素

删除元素可以通过 元素index 来删除。

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));

numbers.remove(Integer.valueOf(2));	// 通过元素移除,将值为2的元素移除
numbers.remove(1);		// 通过index移除,移除index为1的元素

System.out.println(numbers); // [1]
清空List
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));

numbers.clear();		// 清空List
System.out.println(numbers); // []

3 遍历List

当遍历 Java 中的列表时,您可以使用以下方法:

使用 for 循环

List<String> fruits = new ArrayList<>(List.of("apple", "banana", "orange"));

for (int i = 0; i < fruits.size(); i++) {
    System.out.println(fruits.get(i));
}

for 循环中,使用列表的长度 fruits.size() 作为循环条件,每个元素通过 get(index) 方法获取。

使用 for-each 循环

List<String> fruits = new ArrayList<>(List.of("apple", "banana", "orange"));

for (String fruit : fruits) {
    System.out.println(fruit);
}

for-each 循环中,将每个列表元素 fruit 赋值给变量 String fruit,然后在循环体中执行相应的操作。这种方法比使用 for 循环更简洁,特别是当不需要访问每个元素的索引时。

使用迭代器

List<String> fruits = new ArrayList<>(List.of("apple", "banana", "orange"));

// 获取迭代器
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

在使用迭代器遍历列表时,首先需要获取列表的迭代器 fruits.iterator(),然后可以使用 while 循环和迭代器的 hasNext()next() 方法来访问列表的每个元素。

遍历的时候删除元素

我们可能会有这样的场景,遍历一个 List,将其中满足条件的元素删除,例如删除一个 List 中的偶数,可能编写代码如下:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

for (Integer num : numbers) {
    if (num % 2 == 0) {
        numbers.remove(num);    // 删除元素
    }
}

但是执行代码,却发现报错,如下:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
	at com.demo.ListTest.main(ListTest.java:16)

当在迭代器遍历集合的同时,又对集合进行了结构性修改(比如添加、删除元素)时就会抛出 ConcurrentModificationException 异常。

那怎么在遍历的时候删除元素呢?

可以使用迭代器的 remove() 方法来安全地删除元素,举个栗子:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

// 使用迭代器遍历列表
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
    Integer num = iterator.next();
    // 在遍历时如果想要删除某个元素,使用迭代器的 remove 方法
    if (num % 2 == 0) {
        iterator.remove();
    }
}

System.out.println(numbers); // 输出:[1, 3, 5]

4 排序

将列表中的元素按照从小到大排列:

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

public class SortListExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");

        Collections.sort(list);

        System.out.println(list); // 输出:[Apple, Banana, Orange]
    }
}

从大到小排列:

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

public class SortListExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");

        // 使用 reverseOrder() 方法实现降序排序
        Collections.sort(list, Collections.reverseOrder());

        System.out.println(list); // 输出应该是:[Orange, Banana, Apple] 
    }
}

但是使用自定义的对象放入到 List 中就无法排序了,因为自定义对象没有实现 Comparable接口,所以自定义对象可以实现 Comparable接口,重写 compareTo 方法,可以实现按照自定的属性进行排序:

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

class Student implements Comparable {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

  	
    @Override
    public int compareTo(Object target) {
        if (target instanceof Student) {
            Student student = (Student) target;
            return this.name.compareTo(student.name);  //按照姓名从小到大排列
        } else {
            throw new RuntimeException("类型不匹配");
        }
    }
}

public class SortListExample {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("zhangsan", 15));
        list.add(new Student("lisi", 16));
        list.add(new Student("wangwu", 17));

        for (Student s : list) {
            System.out.println(s.getName());
        }
    }
}

利用compare(Object target)方法,比较当前对象和target的大小,如果想从小到大排列,则当前对象的属性小于 target 的属性,返回负数,如果相等,返回0,如果大于,返回正数,则就实现了从小到大排列,反之从大到小排列。


如果不想实现 Comparable接口,也可以使用Collections.sort()方法并传入一个Comparator 比较器对象。

举个栗子:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class SortListExample {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("zhangsan", 15));
        list.add(new Student("lisi", 17));
        list.add(new Student("wangwu", 16));

        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                if(o1 instanceof Student && o2 instanceof Student){
                    // 年龄相等就算相同, 按照年龄从小到大排列
                    return Integer.compare(o1.getAge(), o2.getAge());
                }else{
                    throw new RuntimeException("类型不匹配");
                }
            }
        });

        for (Student s : list) {
            System.out.println(s.getName());
        }
    }
}

5 其他常用操作

判断List是否为空

可以通过 isEmpty()方法判断 List 是否为空。

List<Integer> numbers = new ArrayList<>();
if (numbers.isEmpty()) {
    System.out.println("集合为空");
}

numbers.add(1);
numbers.add(2);
numbers.add(3);
if (!numbers.isEmpty()) {
    System.out.println("集合不为空");
}

一般使用的时候,可以搭配 null 来判断一个 List 为非空,例如: if (null != numbers && !numbers.isEmpty())

检查包含某个元素
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));

System.out.println(numbers.contains(2)); // true

9.2 Set

Set与List的区别:Set是一种不允许重复元素的集合类型,而且Set是无序的。

Set更重要的是用来做一些运算,例如求两个集合的交集、并集、差集等。

Set最主要的特点:

  • 不支持重复的元素

  • 内容是无序的,不能下标来访问元素

  • Set是可以被修改的

  • 可以存储不同的数据类型

List 有序,是添加元素的时候,后添加的元素会排列到前面元素的后面。而无序,是每次添加的元素之间没有顺序,这里设计数据结构的相关知识,后面可以通过学习数据结构来了解。

1 创建Set

// 创建一个String类型的Set
Set<String> colorSet = new HashSet<>();

// 创建一个Integer类型的Set
Set<Integer> numSet = new HashSet<>();

// 不指定类型,什么元素都可以放,和 Set<Object> 一样
Set objList = new HashSet<>();

还可以在创建的时候,指定 Set 初始化的大小,在使用的时候,如果元素超过了初始容量,会自动进行扩容。

// 创建一个初始容量为5的HashSet,用于存储Integer类型的元素  
Set<Integer> list = new HashSet<>(5);  

还可以创建不可变的 Set,不可变的 Set 不能添加、修改、删除元素,只能读取元素:

Set<String> colorList = Set.of("red", "green", "blue");
System.out.println(colorList);    // [red, green, blue]

如果想快速创建包含元素的可变 Set,可以使用如下方式:

// Set.of(1, 2, 3)作为参数创建一个新的可变Set
Set<Integer> numbers = new HashSet<>(Set.of(1, 2, 3));
System.out.println(numbers);

2 基础操作

添加元素

通过 add()addAll() 方法可以添加元素。

Set<Integer> numSet1 = new HashSet<>();
// 添加元素
numSet1.add(1);        
numSet1.add(2);

Set<Integer> numSet2 = Set.of(3, 4);
numSet1.addAll(numSet2);      // 将numSet2中所有的元素都添加到numSet1中
访问元素

因为Set是无序的,所以无法通过下标来访问Set中的元素,如果想访问Set中的元素,只能通过遍历的方式来获取Set中的元素。

获取长度

通过 size() 方法,可以获取到 Set 的长度。

Set<Integer> numbers = Set.of(1, 2, 3);
// 获取长度
System.out.println(numbers.size()); // 3
删除元素

删除元素只能通过 元素 来删除,无法通过 index 来删除。

Set<Integer> numSet = new HashSet<>(Set.of(1, 2, 3));
// 删除元素
numSet.remove(2);
System.out.println(numSet); // [1, 3]

因为Set是无序的,所以无法通过下标来删除元素。

清空Set
Set<Integer> numSet = new HashSet<>(Set.of(1, 2, 3));
// 清空set
numSet.clear();
System.out.println(numSet); // []
将Set转换为List
Set<Integer> numSet = Set.of(1, 2, 3);
// 将set作为参数传递给List,其实这种方式也可以将List转换为Set
List<Integer> numList = new ArrayList<>(numSet);
System.out.println(numList);

3 遍历Set

当遍历 Set 时,您可以使用以下方法:

使用 for-each 循环

Set<String> colorSet = Set.of("red", "green", "blue");

for (String color : colorSet) {
    System.out.println(color);
}

在这个示例中,我们使用 for-each 循环遍历了 Set 中的每个元素,并打印每个元素。

因为 Set 是无序的,所以无法使用简单的 for 循环通过 index 来遍历。

使用迭代器

Set 类型也实现了 Iterable 接口,因此您也可以使用迭代器来遍历 Set 中的元素。例如:

Set<String> colorSet = Set.of("red", "green", "blue");

// 获取迭代器
Iterator<String> iterator = colorSet.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

在这个示例中,我们首先获取了 Set 的迭代器,并使用 while 循环和 hasNext() 方法遍历了 Set 中的每个元素,并使用 next() 方法访问当前元素。

4 其他常用操作

因为 List 和 Set 都是继承自 Collection 接口,很多方法都是 Collection 接口中规范的,所以很多 List 有的操作 Set 也是有的。

判断Set是否为空

可以通过 isEmpty() 方法判断 Set 是否为空,通过 isNotEmpty() 方法判断 Set 是否不为空。

Set<Integer> numSet = new HashSet<>();
if (numSet.isEmpty()) {
    System.out.println("Set为空");
}

numSet = new HashSet<>(Set.of(1, 2, 3, 4, 5));
if (!numSet.isEmpty()) {
    System.out.println("Set不为空");
}
检查包含某个元素
Set<Integer> numSet = new HashSet<>(Set.of(1, 2, 3));
System.out.println(numSet.contains(2)); // true
并集

使用 addAll() 方法将两个 Set 合并为一个Set。下面是一个示例:

Set<Integer> set1 = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> set2 = new HashSet<>(Set.of(3, 4, 5));
set1.addAll(set2);
System.out.println(set1); // [1, 2, 3, 4, 5]
交集

使用 retainAll() 方法获取两个Set的交集。下面是一个示例:

Set<Integer> set1 = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> set2 = new HashSet<>(Set.of(3, 4, 5));
// 求交集
set1.retainAll(set2);
System.out.println(set1); // [3]
差集

使用 removeAll() 方法获取两个 Set 的差集。下面是一个示例:

Set<Integer> set1 = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> set2 = new HashSet<>(Set.of(3, 4, 5));
// 求差集
set1.removeAll(set2);
System.out.println(set1); // [1, 2]

9.3 Map

Map是一种键值对的数据结构,其中每个键对应一个值。

例如有一份数据:

姓名成绩
zhangsan94
lisi96
wangwu91

使用List、Set的方式存储上面的数据是很不方便的。

而使用Map存储就很适合,可以将姓名作为key,成绩作为value。

{
  "zhangsan": 94,
  "lisi": 96,
  "wangwu": 91
}

这样可以很容易通过姓名key得到对应的成绩value。

1 创建Map

// 创建一个String-String类型的Map
Map<String, String> map1 = new HashMap<>();

// 创建一个String-Integer类型的Map
Map<String, Integer> map2 = new HashMap<>();

Map<String, String> map 其中 <String, String> 分别表示 keyvalue 对应的数据类型。

同样,也是可以使用 Map.of() 来创建不可变的 Map,不可变的 Map 不能添加、修改、删除元素,只能读取元素:

Map<String, Integer> scoreMap = Map.of("zhangsan", 90, "lisi", 99,"wangwu", 80);
System.out.println(scoreMap);    // {wangwu=80, lisi=99, zhangsan=90}

如果想快速创建包含元素的可变 Map,可以使用如下方式:

Map<String, Integer> scoreMap = new HashMap<>(Map.of("zhangsan", 90, "lisi", 99,"wangwu", 80));
System.out.println(scoreMap);    // {wangwu=80, lisi=99, zhangsan=90}

2 基础操作

添加元素

添加元素使用 put() 方法:

Map<String, Integer> numbers = new HashMap<>();
// 添加元素
numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);

System.out.println(numbers);	// {one=1, two=2, three=3}
访问元素

访问元素的时候,通过 key 来访问。

Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);

// 获取元素
System.out.println(numbers.get("two")); // 2

如果访问不存在的key,则得到结果为null。

获取长度
Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);

// 获取长度
System.out.println(numbers.size()); // 3
删除元素

删除的时候,通过 key 来删除

Map<String, Integer> numbers = new HashMap<>(Map.of("one", 1, "two", 2, "three", 3));

// 删除元素
numbers.remove("two");
System.out.println(numbers);    // {three=3, one=1}
清空Map
Map<String, Integer> numbers = new HashMap<>(Map.of("one", 1, "two", 2, "three", 3));

// 清空map
numbers.clear();
System.out.println(numbers);    // {}

3 遍历操作

使用 for...in 循环

Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);

for (Map.Entry<String, Integer> entry : numbers.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println("key:" + key + ", value:" + value);
}

这个示例使用了 for...in 循环遍历 Map,其中的 entrySet() 方法返回一个包含键值对的Set,然后在循环中使用 getKey()getValue() 方法访问键和值。

使用 forEach() 方法

Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);

// 使用forEach方法遍历
numbers.forEach方法遍历((key, value) -> {
    System.out.println("key:" + key + ", value:" + value);
});

这个示例使用了 forEach() 方法遍历 Map,其中的 lambda 表达式接收键和值,并在控制台打印出每个学生的分数。

使用 keysvalues 属性

Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);

// 首先得到key组成的set,然后遍历set
for (String key : numbers.keySet()) {
    Integer value = numbers.get(key);
    System.out.println("key:" + key + ", value:" + value);
}

for (Integer value : numbers.values()) {
    System.out.println("value:" + value);
}

这个示例使用了 keySet()values() 方法分别遍历 Map 的键和值,然后在循环中使用键或值变量访问相应的键或值。

4 其他常用操作

判断Map是否为空

可以通过 isEmpty() 方法判断 Map 是否为空,通过 isNotEmpty() 方法判断 Map 是否不为空。

Map<String, Integer> numbers = new HashMap<>();
if (numbers.isEmpty()) {
    System.out.println("Map为空");
}

numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);
if (!numbers.isEmpty()) {
    System.out.println("Map不为空");
}
检查包含某个键
Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);
// 是否包含某个key
System.out.println(numbers.containsKey("two"));     // true
检查包含某个值
Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);
// 是否包含某个value
System.out.println(numbers.containsValue(2)); // true
获取Map中的所有键
Map<String, Integer> numbers = Map.of("one", 1, "two", 2, "three", 3);
// 获取所有的key
Set<String> keys = numbers.keySet();
System.out.println(keys); // [three, one, two]  无序的

9.4 集合的体系结构

其实看上面的 List 和 Set 方法是基本一样的,因为他们实现了共同的 Collection 接口。Map 是另外与 Collection 接口类似的顶级接口。

常用的集合的体系结构如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需要注意:ArrayList、HashSet、HashMap 都是非线程安全的,所以不能用在多个线程中共享数据,如果要使用线程安全的类,需要使用CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap。


下面再介绍一下可能会用到的其他的类,另外一些没介绍的,可以百度一下如何使用。

1 TreeSet

TreeSetSortedSet 接口的实现类,添加到 TreeSet 中的元素会自动排序和去重。

所以 TreeSet 中只能放同类型的数据,不同类型的数据无法进行比较排序。

TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。

举个栗子:

自然排序法

自然排序要求存储在TreeSet中的元素实现Comparable接口,并覆写compareTo(Object o)方法。

import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        Set<String> treeSet = new TreeSet();
        treeSet.add("edg");
        treeSet.add("abc");
        treeSet.add("bcd");
        treeSet.add("abc");

        for (String s : treeSet) {
            System.out.println(s);
        }
    }
}

输出结果:

abc
bcd
edg

从上面的代码可以看出,对加入的字符串进行排序,默认使用的是自然排序。因为String 类实现了Comparable接口


自然排序排列对象

如果将自定义的对象添加的 TreeSet 中的时候,发现会报错,因为该对象没有实现 Comparable 接口。

所以如果使用自然排序,放入到 TreeSet 中的类对象,需要实现 Comparable 接口。

import java.util.Set;
import java.util.TreeSet;

class Student implements Comparable {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Object target) {
        if (target instanceof Student) {
            Student student = (Student) target;
            return this.name.compareTo(student.name);  //按照姓名从小到大排列
        } else {
            throw new RuntimeException("类型不匹配");
        }
    }
}

public class TreeSetTest {
    public static void main(String[] args) {
        Set<Student> treeSet = new TreeSet();
        treeSet.add(new Student("zhangsan", 15));
        treeSet.add(new Student("zhangsan", 15));
        treeSet.add(new Student("lisi", 16));
        treeSet.add(new Student("wangwu", 17));

        for (Student s : treeSet) {
            System.out.println(s.getName());
        }
    }
}

上面的 Student 类实现了 Comparable 接口,并实现了 compareTo() 方法。利用compare(Object target)方法,比较当前对象和target的大小,如果想从小到大排列,则当前对象的属性小于 target 的属性,返回负数,如果相等,返回0,如果大于,返回正数,则就实现了从小到大排列,反之从大到小排列。上面是直接通过name调用String 类的 compareTo 方法实现返回值。

需要注意,添加到 TreeSet 中,比较两个对象是否相等,是通过 compareTo 方法判断的,不是equals。


定制排序

当默认的自然排序不满足需求时,可以使用定制排序,定制排序通常通过传递一个实现了Comparator接口的比较器对象给TreeSet的构造函数来实现,通过实现compare(Object o1, Object o2)方法来实现排序逻辑,使用定制排序,可以灵活地定义元素之间的顺序,而不必要求元素自身实现Comparable接口。

举个栗子:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

// 不用实现接口了
class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class TreeSetTest {
    public static void main(String[] args) {
        Comparator<Student> comparator = new Comparator<Student>() {
            //按照年龄从小到大排列
            @Override
            public int compare(Student o1, Student o2) {
                if(o1 instanceof Student && o2 instanceof Student){
                    // 年龄相等就算相同
                    return Integer.compare(o1.getAge(), o2.getAge());
                }else{
                    throw new RuntimeException("类型不匹配");
                }
            }
        };

        Set<Student> treeSet = new TreeSet(comparator);
        treeSet.add(new Student("zhangsan", 15));
        treeSet.add(new Student("zhangsan", 15));
        treeSet.add(new Student("lisi", 15));
        treeSet.add(new Student("wangwu", 17));

        for (Student s : treeSet) {
            System.out.println(s.getName());
        }
    }
}

执行结果:

zhangsan
wangwu

2 Properties

Properties 类是Hashtable的子类,主要用于处理属性文件,Properties 里的key value 都是字符串类型。

存储数据使用setProperty(String key,Stringvalue)方法,获取数据使用getProperty(String key)方法。

举个例子:

首先在项目根目录下新建一个 .properties 文件:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

编辑内容如下:

username=doubibiji
password=123456

上面就是使用了 key=value 的方式定义两个配置。

下面使用 Properties 读取配置:

package com.doubibiji;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class PropertiesTest {
    public static void main(String[] args){
        FileInputStream fis = null;
        try {
            Properties pros = new Properties();
            fis = new FileInputStream("test.properties");
            //加载文件中的配置到Properties对象中
            pros.load(fis); 

          	// 获取配置
            String username = pros.getProperty("username");
            String password = pros.getProperty("password");

            System.out.println("username=" + username); // username=doubibiji
            System.out.println("password=" + password); // password=123456
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

如果存在中文乱码问题,可以使用如下方式解决:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/635814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

煤矿ai智能监控系统

煤矿ai智能监控系统利用智能视频分析技术和YOLO深度学习技术&#xff0c;煤矿ai智能监控系统可以对煤矿现场进行全方位的监测和分析。煤矿ai智能监控系统能够精确识别和分析皮带锚杆、矸石、木板、堆煤等运输设备和物料的运行状态&#xff0c;同时也可以识别煤量的大小以及非法…

Rocketmq集群再搭建

注意上面这个官方架构是 主从模式&#xff0c;还无法做到高可用&#xff0c;因为master节点宕机&#xff0c;从节点没法变成主节点&#xff0c;需要人工重启&#xff0c;够用了。 1. 先准备1台虚拟机&#xff0c;装好1台再克隆2台 根据上面的图&#xff0c;3台机器中有一台…

k8s之yaml文件详解

文章目录 k8s之yaml文件详解一、关于yaml文件1、k8s支持的文件格式2、YAML语言格式3、查看api资源版本标签4、编写nginx-test.yaml资源配置清单4.1 编写资源配置清单4.2 创建资源对象4.3 查看创建的pod资源 5、创建service服务对外提供访问并测试5.1 编写nginx-svc-test.yaml5.…

【有手就行】使用你自己的声音做语音合成,CPU都能跑,亲测有效

此文介绍在百度飞桨上一个公开的案例&#xff0c;亲测有效。 厌倦了前篇一律的TTS音色了吗&#xff1f;打开短视频听来听去就是那几个声音&#xff0c;快来试试使用你自己的声音来做语音合成吧&#xff01;本教程非常简单&#xff0c;只需要你能够上传自己的音频数据就可以(建议…

08.CNN

文章目录 Observation 1Pooling - Max PoolingFlattenApplication&#xff1a;Playing Go使用验证集选择模型食物分类 Observation 1 Pooling - Max Pooling Pooling主要为了降低运算量&#xff0c;现在一般不用了&#xff0c;全convolution Flatten Application&#xff1a;P…

【Linux学习】进程基础API

下面是有关进程基础API的相关介绍&#xff0c;希望对你有所帮助&#xff01; 小海编程心语录-CSDN博客 目录 1. 僵尸进程与孤儿进程 1.1 孤儿进程 1.2 僵尸进程 2. 监视子进程 2.1 wait() 2.2 waitpid() 3. 执行新程序 exec族函数 4. 守护进程 1. 僵尸进程与孤儿进程…

机器学习实验----逻辑回归实现二分类

目录 一、介绍 二、sigmoid函数 &#xff08;1&#xff09;公式&#xff1a; &#xff08;2&#xff09;sigmoid函数的输入 预测函数&#xff1a; 以下是sigmoid函数代码&#xff1a; 三、梯度上升 &#xff08;1&#xff09;似然函数 公式&#xff1a; 概念&#xff…

Golang | Leetcode Golang题解之第100题相同的树

题目&#xff1a; 题解&#xff1a; func isSameTree(p *TreeNode, q *TreeNode) bool {if p nil && q nil {return true}if p nil || q nil {return false}queue1, queue2 : []*TreeNode{p}, []*TreeNode{q}for len(queue1) > 0 && len(queue2) > …

10个顶级的论文降重指令,让你的论文降重至1.9%

10个顶级的论文降重指令&#xff0c;本硕博写论文必备&#xff01; 在ChatGPT4o对话框中输入&#xff1a;写一个Spring BootVue实现的车位管理系统的论文大纲&#xff0c;并对其具体章节进行详细描述。 几小时即可完成一份1万字论文的编写 在GPTS中搜索论文降重&#xff0c;使…

FFmpeg开发笔记(三十)解析H.264码流中的SPS帧和PPS帧

《FFmpeg开发实战&#xff1a;从零基础到短视频上线》一书的“2.1.1 音视频编码的发展历程”介绍了H.26x系列的视频编码标准&#xff0c;其中H.264至今仍在广泛使用&#xff0c;无论视频文件还是网络直播&#xff0c;H.264标准都占据着可观的市场份额。 之所以H.264取得了巨大…

并发编程常见面试题

文章目录 为什么要使用线程池为什么不建议使用 Executors静态工厂构建线程池synchronized的实现原理Synchronized和Lock的区别什么是AQS什么是阻塞队列 为什么要使用线程池 关于线程池的作用和线程池的执行流程参考&#xff1a;java线程池 为什么不建议使用 Executors静态工厂…

BUSCO安装及使用(生物信息学工具-019)

01 背景 Benchmarking Universal Single-Copy Orthologs (BUSCO)是用于评估基因组组装和注释的完整性的工具。通过与已有单拷贝直系同源数据库的比较&#xff0c;得到有多少比例的数据库能够有比对&#xff0c;比例越高代表基因组完整度越好。基于进化信息的近乎全基因单拷贝直…

521源码-免费教程-Linux系统硬盘扩容教程

本教程来自521源码&#xff1a;更多网站源码下载学习教程&#xff0c;请点击&#x1f449;-521源码-&#x1f448;获取最新资源 首先&#xff1a;扩容分区表 SSH登陆服务器输入命令&#xff1a;df -TH&#xff0c;获得数据盘相关信息 可以看到演示服务器的数据盘分区是&…

如何让外网访问内网服务?

随着互联网的快速发展&#xff0c;越来越多的企业和个人需要将内网服务暴露给外网用户访问。由于安全和隐私等因素的考虑&#xff0c;直接将内网服务暴露在外网是非常不安全的做法。如何让外网用户安全访问内网服务成为了一个重要的问题。 在这个问题上&#xff0c;天联公司提供…

【吊打面试官系列】Java高并发篇 - AQS 支持几种同步方式 ?

大家好&#xff0c;我是锋哥。今天分享关于 【AQS 支持几种同步方式 &#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; AQS 支持几种同步方式 &#xff1f; 1、独占式 2、共享式 这样方便使用者实现不同类型的同步组件&#xff0c;独占式如 ReentrantLock&…

第一份工资

当我拿到我人生的第一份工资时&#xff0c;那是一种难以言表的激动。我记得那个下午&#xff0c;阳光透过窗户洒在了我的办公桌上&#xff0c;我看着那张支票&#xff0c;心中满是欣喜和自豪。那是我独立生活的开始&#xff0c;也是我对自己能力的一种肯定。 我记得我是如何支配…

《Rust奇幻之旅:从Java和C++开启》第1章Hello world 2/5

讲动人的故事,写懂人的代码 很多程序员都在自学Rust。 🤕但Rust的学习曲线是真的陡,让人有点儿怵头。 程序员工作压力大,能用来自学新东西的时间简直就是凤毛麟角。 📕目前,在豆瓣上有7本Rust入门同类书。它们虽有高分评价,但仍存在不足。 首先,就是它们介绍的Rust新…

web自动化文件上传弹框处理

目录 文件上传介绍文件上传处理Alert 弹窗介绍Alert 弹窗处理 课程目标 掌握文件上传的场景以及文件上传的处理方式。掌握 Alert 弹窗的场景以及 Alert 弹窗的处理方式。 思考 碰到需要上传文件的场景&#xff0c;自动化测试应该如何解决&#xff1f; 文件上传处理 找到文…

大数据量MySQL的分页查询优化

目录 造数据查看耗时优化方案总结 造数据 我用MySQL存储过程生成了100多万条数据&#xff0c;存储过程如下。 DELIMITER $$ USE test$$ DROP PROCEDURE IF EXISTS proc_user$$CREATE PROCEDURE proc_user() BEGINDECLARE i INT DEFAULT 1;WHILE i < 1000000 DOINSERT INT…

SkyEye对接CANoe:助力汽车软件功能验证

01.简介 CANoe&#xff08;CAN open environment&#xff09;是德国Vector公司专为汽车总线设计而开发的一款通用开发环境&#xff0c;作为车载网络和ECU开发、测试和分析的专业工具&#xff0c;支持从需求分析到系统实现的整个系统的开发过程。CANoe丰富的功能和配置选项被OE…