Java的泛型---set接口

目录

Interface Set

Set接口

HashSet

LinkedHashSet

TreeSet

TreeSet存储对学生对象遍历


Interface Set<E>

观察API文档我们发现:

 public interface Set<E>

extends Collection<E>

不包含重复元素的集合。 更正式地,集合不包含一对元素e1e2 ,使得e1.equals(e2) ,并且最多一个空元素。 正如其名称所暗示的那样,这个接口模拟了数学抽象。

Set接口除了继承自Collection接口的所有构造函数的合同以及add,equals和hashCode方法的合同外 , 还 增加了其他规定。 其他继承方法的声明也包括在这里以方便。 (伴随这些声明的规范已经量身定做Set接口,但它们不包含任何附加的规定。)

构造函数的额外规定并不奇怪,所有构造函数都必须创建一个不包含重复元素的集合(如上所定义)。

注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。

一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。

此接口是成员Java Collections Framework


Set接口

一个不包含重复元素的 collection,就是元素唯一且元素无序(存储和取出不一致)的集合。

存储字符串并遍历,参考代码:

/*
存储字符串并遍历
 */
import java.util.HashSet;
public class SetDemo {
    public static void main(String[] args) {
        //定义字符串集合
        HashSet<String> arr = new HashSet<>();

        //添加元素到集合
        arr.add("hello");
        arr.add("world");
        arr.add("java");
        arr.add("bigdata");
        arr.add("hadoop");
        arr.add("hello");
        arr.add("hello");
        arr.add("java");
        arr.add("spark");
        arr.add("flink");
        arr.add("world");
        arr.add("hadoop");

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

输出结果:

flink
world
java
bigdata
spark
hello
hadoop


HashSet

查看API文档我们知道:

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable

此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证; 特别是,它不能保证订单在一段时间内保持不变。 这个类允许null元素。

这个类提供了基本操作(add,remove,contains和size)固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。

请注意,此实现不同步。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

  Set s = Collections.synchronizedSet(new HashSet(...)); 

该类iterator方法返回的迭代器是故障快速的 :如果集合在迭代器创建之后的任何时间被修改,除了通过迭代器自己的remove方法之外,迭代器会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。

通过观察API文档我们得出结论:

  • 类允许为null
  • 不保证set的迭代顺序
  • 底层数据结构是哈希表(元素是链表的数组)
  • 哈希表依赖于哈希值存储
  • 添加功能底层依赖两个方法:

                   int hashCode()

                   boolean equals(Object obj)

存储自定义对象并遍历,参考代码:

创建Student2对象:

import java.util.Objects;

public class Student2 {
    private String name;
    private int age;

    public Student2() {
    }

    public Student2(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 String toString() {
        return "Student2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student2 student2 = (Student2) o;
        return age == student2.age &&
                Objects.equals(name, student2.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(name, age);
    }
}

创建HashSetDemo测试类:

/*
        存储自定义对象并遍历
 */
import java.util.HashSet;

public class HashSetDemo {
    public static void main(String[] args) {
        //创建集合对象
        HashSet<Student2> hashSet = new HashSet<>();
        
        //创建学生对象
        Student2 s1 = new Student2("刘德华", 45);
        Student2 s2 = new Student2("刘德华", 45);
        Student2 s3 = new Student2("郭富城", 50);
        Student2 s4 = new Student2("张学友", 60);
        
        //将学生对象添加到集合中
        hashSet.add(s1);
        hashSet.add(s2);
        hashSet.add(s3);
        hashSet.add(s4);
        
        //使用增强for遍历集合
        for (Student2 s : hashSet){
            System.out.println(s);
        }
    }
}

 输出结果:

Student2{name='郭富城', age=50}
Student2{name='张学友', age=60}
Student2{name='刘德华', age=45}


LinkedHashSet

查看API文档我们知道:

public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, Serializable

哈希表和链表实现了Set接口,具有可预测的迭代次序。 这种实现不同于HashSet,它维持于所有条目的运行双向链表。 该链表定义了迭代排序,它是将元素插入集合(插入顺序的顺序 。 请注意,如果一个元件被重新插入到组插入顺序不受影响 。 (元件e重新插入一组s如果当s.contains(e)将返回true之前立即调用s.add(e)被调用。)

此实现可以让客户从提供的指定,通常杂乱无章的排序HashSet ,而不会导致与其相关的成本增加TreeSet 。 它可以用于生成与原始文件具有相同顺序的集合的副本,而不管原始集的实现:

  void foo(Set s) {
         Set copy = new LinkedHashSet(s);
         ...
     } 

如果模块在输入上进行设置,复制它,并且稍后返回其顺序由该副本确定的结果,则此技术特别有用。 (客户一般都喜欢以相同的顺序返回事情。)

该类提供了所有可选的Set操作,并允许null元素。 像HashSet,它提供了基本操作(add,contains和remove)稳定的性能,假定散列函数散桶中适当的元件。 性能可能略低于HashSet ,由于维护链表的额外费用,但有一个例外:LinkedHashSet的迭代需要与集合的大小成比例的时间,无论其容量如何。 HashSet的迭代可能更昂贵,需要与其容量成比例的时间。

链接哈希集具有影响其性能的两个参数: 初始容量负载因子 。 它们的定义精确到HashSet 。 但是请注意,该惩罚为初始容量选择非常高的值是该类比HashSet不太严重的,因为迭代次数对于这个类是由容量不受影响。

请注意,此实现不同步。 如果多个线程同时访问链接的散列集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,则应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

  Set s = Collections.synchronizedSet(new LinkedHashSet(...)); 

该类iterator方法返回的迭代器是故障快速的 :如果在创建迭代器之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove方法之外,迭代器将会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。

这个班是Java Collections Framework的会员

通过观察API文档我们得出结论:

  • 元素有序唯一
  • 由链表保证元素有序
  • 由哈希表保证元素唯一

参考代码:

import java.util.LinkedHashSet;
import java.util.TreeSet;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        //创建LinkedHashSet集合对象
        LinkedHashSet<String> arr = new LinkedHashSet<>();

        //创建TreeSet集合对象
        TreeSet<String> strings = new TreeSet<>();

        //添加元素到LinkedHashSet集合
        arr.add("hello");
        arr.add("world");
        arr.add("java");
        arr.add("bigdata");
        arr.add("hadoop");
        arr.add("hello");
        arr.add("hello");
        arr.add("java");
        arr.add("spark");
        arr.add("flink");
        arr.add("world");
        arr.add("hadoop");

        //添加元素到TreeSet集合
        strings.add("hello");
        strings.add("world");
        strings.add("java");
        strings.add("bigdata");
        strings.add("hadoop");
        strings.add("hello");
        strings.add("hello");
        strings.add("java");
        strings.add("spark");
        strings.add("flink");
        strings.add("world");
        strings.add("hadoop");


        System.out.println("遍历LinkedHashSet集合:");
        for (String s : arr){
            System.out.println(s);
        }

        System.out.println("=========================");

        System.out.println("遍历TreeSet集合:");
        for (String s1 : strings) {
            System.out.println(s1);
        }

        }
    }

输出结果:

遍历LinkedHashSet集合:
hello
world
java
bigdata
hadoop
spark
flink
=========================
遍历TreeSet集合:
bigdata
flink
hadoop
hello
java
spark
world

通过观察代码和输出结果我们得出结论:

  • LinkedHashSet底层数据结构是哈希表和双向链表
  • 哈希表保证了元素唯一
  • 链表保证了元素的有序(存储和取出顺序一致)

 通过输出结果我们发现,结果去重了,而且输出的结果顺序与添加的顺序不一样,这是为什么呢?

我们查看原码:

public interface Set<E> extends Collection<E>{

}

public class HashSet<E> extends AbstractSet<E> implements Set<E>{
    private static final Object PRESENT = new Object();

    private transient HashMap<E,Object> map;

    public boolean add(E e) {  
        //E -- String  
        //e -- "hello"
        return map.put(e, PRESENT)==null;
    }
}

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{

    public V put(K key, V value) {
        //key -- "hello"
        //value -- new Object()
        return putVal(hash(key), key, value, false, true);
    }

    //简单理解为调用元素类的hashCode()方法计算哈希值
    static final int hash(Object key) { //"hello"
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        //理解为哈希表存储的是一个一个的结点数组
        Node<K,V>[] tab; 
        Node<K,V> p; 
        int n, i;

        //判断哈希表是否已经初始化完毕,如果没有初始化,就在这里初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        //根据元素对象计算好的哈希值再进行一次与运算,计算出的是该元素存储在哈希表中的位置
        //如果该元素的位置是null,说明该位置没有元素,可以进行存储
        //就创建新的结点,存储元素
        //分析到这一步我们得出第一个结论,存储的位置与元素类中的hashCode()有关。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {

            //如果该元素的位置不是null,说明这个位置上已经有元素了,可以确定是哈希值是一样的
            //但是呢,我们并不能确定两个值是不是一样的
            Node<K,V> e; K k;
            
            //先将存入元素的哈希值与该位置中元素的哈希值做比较
            //如果哈希值都不一样,继续走判断instanceof()
            //如果哈希值都一样,会调用元素的equals(k)方法进行比较
            //如果equals(k)方法进行比较结果是false,继续向下执行,最终会将元素插入到集合中或者不插入
            //如果equals(k)方法进行比较结果是true,表示元素的哈希值和内容都一样,表示元素重复了
            //就覆盖,从现象上来看,其实就是不赋值
            //说到这里我们已经知道了add()方法和hashCode()以及equals()方法有关
            //会不会去重取决于元素类型有没有重写hashCode()以及equals()方法
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

}

TreeSet

通过查看API文档我们知道:

public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable

A NavigableSet实现基于TreeMap 。 的元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。

此实现提供了基本的操作(保证的log(n)时间成本addremovecontains )。

需要注意的是由一组(无论是否提供了明确的比较器)保持的顺序必须与equals一致 ,如果它是要正确实现Set接口。 (参见ComparableComparator一致的精确定义与equals)。这是因为该Set接口在来定义equals的操作,但一个TreeSet例如使用其执行所有元件比较compareTo (或compare )方法,于是两个通过该方法认为相等的元素从集合的角度来看是相等的。 集合的行为明确定义的,即使其排序与equals不一致; 它只是没有遵守Set界面的总体合同。

请注意,此实现不同步。 如果多个线程并发访问树,并且至少有一个线程修改该集合,则必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSortedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

  SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...)); 

该类iterator方法返回的迭代器是故障快速的 :如果在迭代器创建之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove方法之外,迭代器将抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。

这个班是Java Collections Framework的会员

通过观察API文档我们得出结论:

  • 使用元素的自然顺序对元素进行排序
  • 或者根据创建Set时提供的Comparator进行排序
  • 具体取决于使用的构造方法
  • 底层数据结构是红黑树(红黑树是一种自平衡的二叉树)

参考代码:

import java.util.TreeSet;

public class TreeSetDemo1 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Integer> ts = new TreeSet<>();

        //添加元素到集合中
        ts.add(20);
        ts.add(18);
        ts.add(23);
        ts.add(24);
        ts.add(66);
        ts.add(12);
        ts.add(18);
        ts.add(20);
        ts.add(23);
        ts.add(2);

        //使用增强for循环遍历集合
        for (Integer t : ts) {
            System.out.println(t);
        }
    }
}

输出结果:

2
12
18
20
23
24
66

 通过观察代码和运行结果我们得出结论:TreeSet元素唯一,元素顺序可以按照某种规则进行排序:自然排序、比较器排序。

那去重又是为什么呢?我们查看原码:

public abstract class AbstractCollection<E> implements Collection<E>{}

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>{}

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable{
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    TreeSet(NavigableMap<E,Object> m) {  //NavigableMap<E,Object> m = new TreeMap<E,Object>()
        this.m = m;
    }
    
    public boolean add(E e) {
        //E -- Integer
        //e -- 20
        return m.put(e, PRESENT)==null;
    }
}   


public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>{

    private transient Entry<K,V> root;

    public TreeMap() {
        comparator = null;
    }

    public V put(K key, V value) {
        //key -- 18
        //value -- new Object()
        Entry<K,V> t = root;
        //判断根有没有元素,如果没有,为当前元素值创建一个结点,当作树的根结点
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }


        int cmp; //0
        Entry<K,V> parent; //null
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator; // cpr = null;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
}

TreeSet存储对学生对象遍历

参考代码1:

创建Student3类:

public class Student3 {
    private String name;
    private int age;

    //创建无参的构造方法
    public Student3() {
    }

    //创建有参的构造方法
    public Student3(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //创建getXxx()和setXxx()方法
    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;
    }

    //重写toString方法

    @Override
    public String
    toString() {
        return "Struden3{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

创建TreeSeDemo2对象:

import java.util.TreeSet;

public class TreeSetDemo2 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Object> ts = new TreeSet<>();

        //创建学生集合对象
        Student3 s1 = new Student3("张三", 24);
        Student3 s2 = new Student3("李四", 32);
        Student3 s3 = new Student3("王二", 53);
        Student3 s4 = new Student3("麻子", 14);
        Student3 s5 = new Student3("靓仔", 23);
        Student3 s6 = new Student3("华仔", 67);
        Student3 s7 = new Student3("老街", 35);

        //将学生添加到集合中
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        ts.add(s7);

        //使用增强for循环遍历集合
        for (Object t : ts) {
            System.out.println(t);
        }
    }
}

输出结果:

 输出结果报错了,这是为什么呢?我们查看原码:

public abstract class AbstractCollection<E> implements Collection<E>{}

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>{}

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable{
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    TreeSet(NavigableMap<E,Object> m) {  //NavigableMap<E,Object> m = new TreeMap<E,Object>()
        this.m = m;
    }
    
    public boolean add(E e) {
        //E -- Integer
        //e -- 20
        return m.put(e, PRESENT)==null;
    }
}   


public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>{

    private transient Entry<K,V> root;

    public TreeMap() {
        comparator = null;
    }

    public V put(K key, V value) {
        //key -- 18
        //value -- new Object()
        Entry<K,V> t = root;
        //判断根有没有元素,如果没有,为当前元素值创建一个结点,当作树的根结点
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }


        int cmp; //0
        Entry<K,V> parent; //null
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator; // cpr = null;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
}

 通过查看原码我们得知:由于我们这里创建TreeSet对象调用 的是无参构造方法,所以走的是自然排序,而底层原码有一步向下转型:

Comparable<? super K> k = (Comparable<? super K>) key;

原因是我们Student3类没有实现Comparable接口,无法向下转型,所以报错了。

那么实现一下Student3的Comparable接口:

 我们再运行一下:

return 0;

 return 1;

 return -1;

 通过不同的返回值我们发现:其实这里的返回值是根据我们的TreeSet的规则进行升序或降序的,比如我们想在去重的前提下,按照年龄进行排序:

returen this.age = o.age;

 但题目的要求是年龄一样,姓名不一定不一样

 通过运行结果我们发现姓名一样,年龄一样,只保留一开始添加到集合中的学生对象。(达到了题目的要求)

完整代码如下:

Student3:

public class Student3 implements Comparable<Student3>{
    private String name;
    private int age;

    //创建无参的构造方法
    public Student3() {
    }

    //创建有参的构造方法
    public Student3(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //创建getXxx()和setXxx()方法
    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;
    }

    //重写toString方法

    @Override
    public String
    toString() {
        return "Struden3{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    @Override
    public int compareTo(Student3 o) {
        
        //返回第一个学生对象
//        return 0;
        
        //升序
//        return 1;
        
        //降序
//        return -1;

        //年龄去重
//        return this.age - o.age;

        //姓名年龄都去重
        int i = this.age - o.age;
        int i2 = i == 0 ? this.name.compareTo(o.name) : i;
        return i2;
    }

}

TreeSetDemo2:

import java.util.TreeSet;

public class TreeSetDemo2 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Object> ts = new TreeSet<>();

        //创建学生集合对象
        Student3 s1 = new Student3("张三", 24);
        Student3 s2 = new Student3("李四", 32);
        Student3 s3 = new Student3("王二", 53);
        Student3 s4 = new Student3("麻子", 14);
        Student3 s5 = new Student3("靓仔", 23);
        Student3 s55 = new Student3("靓仔", 23);
        Student3 s555 = new Student3("靓仔", 18);
        Student3 s6 = new Student3("华仔", 67);
        Student3 s7 = new Student3("老街", 35);


        //将学生添加到集合中
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        ts.add(s7);
        ts.add(s55);


        //使用增强for循环遍历集合
        for (Object t : ts) {
            System.out.println(t);
        }
    }
}

输出结果:

Struden3{name='麻子', age=14}
Struden3{name='靓仔', age=23}
Struden3{name='张三', age=24}
Struden3{name='李四', age=32}
Struden3{name='老街', age=35}
Struden3{name='王二', age=53}
Struden3{name='华仔', age=67}


🔶到底啦!给靓仔一个关注吧!🔶

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Java 泛型Java 5 引入的一个新特性,它可以让程序员在编写代码时更加灵活地处理数据类型。泛型可以让代码更加简洁,可读性更强,同时还可以提高代码的安全性。 使用泛型的主要目的是为了避免类型转换的问题。在 Java 5 之前,对于容器类(如 List、Set、Map 等),它们可以存储任意类型的对象,但是获取容器中的元素时需要进行类型转换,这往往会导致运行时异常。而使用泛型之后,可以在编译时就确定容器中元素的类型,从而避免类型转换的问题。 Java 中的泛型主要包括以下几个部分: 1. 类型参数:使用尖括号(<>)来定义,可以理解为占位符,表示一种未知类型。 2. 泛型类:使用类型参数来定义类,在类中可以使用类型参数来定义属性、方法返回值、方法参数等。 3. 泛型接口:与泛型类类似,可以使用类型参数来定义接口中的方法。 4. 泛型方法:在普通方法中使用类型参数来定义方法返回值、方法参数等。 下面是一个简单的泛型类的例子: ``` public class Box<T> { private T data; public Box(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` 在这个例子中,Box 类使用了一个类型参数 T,表示 Box 中存储的数据类型是未知的。在构造方法和属性的 getter/setter 方法中都使用了泛型类型 T,表示返回值或参数的类型都是 T 类型。这样,使用 Box 类时就可以指定具体的类型,例如: ``` Box<String> stringBox = new Box<String>("Hello"); String data = stringBox.getData(); ``` 在这个例子中,创建了一个 Box 类型为 String 的对象,并调用了它的 getData 方法获取数据,不需要进行类型转换,因为在编译时就已经确定了数据类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

liangzai2048

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

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

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

打赏作者

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

抵扣说明:

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

余额充值