文章目录

  • Java中的泛型机制
  • 泛型类:
  • 泛型接口:
  • 泛型方法:
  • 泛型的通配符以及向上限定、向下限定:
  • 新式for循环
  • asList方法
  • set类集合
  • 散列集(HashSet)
  • LinkedHashSet类
  • 树集(TreeSet)


Java中的泛型机制

  • 泛型:是JDK1.5之后引入的一个概念,它的存在是指将类型明确工作推迟到创建对象或调用方法时才明确;
  • 语法格式:<数据类型,数据类型,……>
  • 泛型可以用在类、接口、方法上
  • 泛型的好处:1、避免了向下转型;2、将问题提前到编译期
  • 泛型的特点:泛型只在编译期有效,在运行期间就擦除了;
import java.util.ArrayList;
public class MyTest1
{
    public static void main(String[] args) 
    {
        ArrayList list = new ArrayList();
        list.add(new Student("张三",18));
        list.add(13);
        Object o = list.get(0);
        Object o1 = list.get(1);
        //我们可以看到使用集合获取方法获取到的元素都是默认为Object类型,如果我们需要使用必须向下转型
    }
}
  • 泛型可以避免向下转型,同时可以提高代码的扩展性

泛型类:

//测试类:
public class MyTest2
{
    public static void main(String[] args)
    {
        MyClass<String, Integer, Character> myClass1 = new MyClass("张三",19,'男');
        Character r = myClass1.show();//调用方法时,编译器就会自动提示你这是一个Character类返回值
        //MyClass{t=张三, u=19, r=男}
        System.out.println(r);
    }
}
//工具类
public class MyClass<T,U,R>
{
    T t;
    U u;
    R r;

    public MyClass(T t, U u, R r) {
        this.t = t;
        this.u = u;
        this.r = r;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public U getU() {
        return u;
    }

    public void setU(U u) {
        this.u = u;
    }

    public R getR() {
        return r;
    }

    public void setR(R r) {
        this.r = r;
    }

    public MyClass() {
    }

    public R show(){
        System.out.println("MyClass{" +
                "t=" + t +
                ", u=" + u +
                ", r=" + r +
                '}');
        return r;
    }
}

泛型接口:

//测试类:
public class MyTest2
{
    public static void main(String[] args)
    {
        MyTest3 test = new MyTest3();
        String show = test.show(115);
        System.out.println(show);
    }
}
//明确了泛型的类:
public class MyTest3 implements MyInterface<String,Integer>
{
    @Override
    public String show(Integer integer) {
        System.out.println("这是实现了泛型接口的类");
        return integer.toString();
    }
}
//泛型接口:
public interface MyInterface<T,R>
{
    public T show(R r);
}

如果实现了接口的类不想明确类型,将交由创建对象时明确类型

//实现了泛型接口的类:
public class MyTest3<T,R> implements MyInterface<T,R>
{
    @Override
    public T show(R r) {
        return null;
    }
}
//泛型接口:
public interface MyInterface<T,R>
{
    public T show(R r);
}
//测试类:
public class MyTest2
{
    public static void main(String[] args)
    {
        new MyInterface<String,Integer>(){
            @Override
            public String show(Integer integer) {
                System.out.println(integer.toString());
                return null;
            }
        }.show(20);
    }
}

泛型方法:

//工具类:
public class MyClass
{
    public<T> void show(T t){
        System.out.println("这是泛型方法"+t);
    }
}
//测试类:
public class MyTest3
{
    public static void main(String[] args)
    {
        MyClass myClass = new MyClass();
        myClass.show("abc");//这是泛型方法abc
        myClass.show(12);//这是泛型方法12
        myClass.show(new Object());//这是泛型方法java.lang.Object@1540e19d
        myClass.show('好');//这是泛型方法好
    }
}

泛型的通配符以及向上限定、向下限定:

//测试类:
import java.util.ArrayList;
public class MyTest3
{
    public static void main(String[] args)
    {
        //这里的<?>代表后面new对象的时候可以是任意类型,它是泛型的通配符
        ArrayList<?> obj1=new ArrayList<>();
        
        //这里的<? super Cat>代表向上限定,也就是后面new对象的时候只能是Cat类或者是它的父类
        ArrayList<? super Cat> obj2=new ArrayList<Cat>();//可以是Animal类,或者Object类,也可以是它本身Cat类

        //这里的<? extends Animal>代表向下限定,也就是后面new对象的时候只能是Animal类或者是它的子类
        ArrayList<? extends Animal> obj3=new ArrayList<>();//可以是Animal类,或者Cat类、Dog类
    }
}
//Animal类:
class Animal{}
class Cat extends Animal{}
class Dog extends Animal{}

新式for循环

  • JDK1.5之后,有一个新的语法规则,可以用来一次处理容器中的每一个元素,而不必为指定下标值而分心。
  • 语法格式:for(容器的数据类型 容器中元素的名字:容器名) {语法块}
    因此我们只需要明确遍历的这个容器数据类型是什么,容器名字叫什么。
  • 注意:这种增强for循环不能在遍历该容器的时候去增加或者删除里面的元素,否则就会报出并发修改异常的错误,但相反,在普通for循环当中就不会发生这种问题。
import java.util.ArrayList;
public class MyTest1
{
    public static void main(String[] args)
    {
        int[] arr={10,20,30,40,50};
        for (int i : arr)
        {
            System.out.println(i);
        }

        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        list.add("田七");
        for (String s : list)
        {
            //在程序遍历的途中去增删元素,与之前迭代器出现的问题一样
            //ConcurrentModificationException——并发修改异常
            if(s.equals("王五"))
            {
                list.add("s001");
            }
            if(s.equals("赵六"))
            {
                list.remove("赵六");
            }
        }
    }
}

asList方法

  • 之前我们在Collection集合当中见到一个方法是将当前集合转为数组,代码如下:
import java.util.ArrayList;
import java.util.Arrays;
public class MyTest2
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        list.add("田七");
        Object[] array = list.toArray();
        System.out.println(Arrays.toString(array));
        //[张三, 李四, 王五, 赵六, 田七]
    }
}
  • 关于可变参数:

我们可以看到asList()方法中的参数是泛型类的,并且参数为可变的,关于可变参数,顾名思义,就是可以接受多个参数;
它的语法:(参数数据类型… 参数名 )。可变参数本质上是一个数组,并且需要注意的是,可变参数必须位于参数列表的最后位置。

public class MyTest3
{
    public static void main(String[] args)
    {
        int max= addMethod(32*48,28*54);
        System.out.println(max);
    }

    private static int addMethod(int... i)
    {
        int max=i[0];
        for (int j = 1; j < i.length; j++)
        {
            if(i[j]>max)
                max=i[j];
        }
        return max;
    }
}
  • 反过来,也存在一个将数组转为集合的方法,是Arrays类里面的静态的方法:

代码如下:

public class MyTest3
{
    public static void main(String[] args)
    {
        int[] arr={10,20,30,40,50,60};
        List<int[]> list = Arrays.asList(arr);
        int[] a = list.get(0);
        System.out.println(Arrays.toString(a));
        //[10, 20, 30, 40, 50, 60]
        //上面的代码可以看出,这个方法将原来数组arr中的所有元素当作一个集合元素存在了集合当中
        System.out.println(list.get(0)[2]);//30

        Integer[] arr1={10,20,30,40,50,60};
        List<Integer> list1 = Arrays.asList(arr1);
        Integer num = list1.get(0);
        System.out.println(num);//10
        //list1调用get()取元素后纠错发现返回值是一个Integer类型的值
        //和上面的结果不一样,为什么?
        //其实细想就可以明白,集合中只能存储引用类型的数据,上面的数组显然是被当做一个元素存进集合里了
        //而Integer类型的数据是引用类型的数据,集合在存储的时候把每一个数据都当作集合的元素
        for (Integer ele : arr1) {
            System.out.println(ele);
        }

        Integer[] arr2={110,120,130,140,150,160};
        List<Integer[]> list2 = Arrays.asList(arr1, arr2);
        System.out.println(list2.get(0)[0]);//10
        System.out.println(list2.get(1)[0]);//110
        //上面的代码执行之后可以发现,这个方法在你传入多个引用类型的数组之后
        //就会将两个数组分别当作元素存入集合当中
        //也就是这时候集合当中存储的是两个元素,他们都是Integer[]数组类型
        
		//arr2.add();//调不到
    }
}
  • 【总结】

1、如果给你的数组是基本类型的数组,他会把数组对象装到集合当中;
2、如果给你的数组是引用类型的数组,他将数组元素取出来,再存进去;
3、如果你传入的是两个以上的对象数组,他会将数组装到集合当中;
4、如果你用转换过来的集合调用add()和remove()方法,发现调不到,这是因为这个集合只能取元素,不能再添加或删除元素;

set类集合

  • 查看API :

一个不包含重复元素的 collection;更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素;正如其名称所暗示的,此接口模仿了数学上的 set 抽象。

在所有构造方法以及 add、equals 和 hashCode 方法的协定上,Set 接口还加入了其他规定,这些规定超出了从 Collection 接口所继承的内容。出于方便考虑,它还包括了其他继承方法的声明(这些声明的规范已经专门针对 Set 接口进行了修改,但是没有包含任何其他的规定)。

散列集(HashSet)

  • 链表和数组可以按照人们的意愿排列元素的次序。但是要想查看某个指定的元素,却又忘了它的位置,就需要访问所有的元素,直到找到为止。如果集合当中包含了很多元素,将会耗费许多时间。
  • 有一种众所周知的数据结构,可以快速的查找所需的对象,这就是散列表。散列表为每个对象计算一个整数,称为散列码,它是由不同数据域的对象产生的不同的散列码。String类的散列码是由hashCode()产生的。如果自定义,就要负责实现这个类的hashCode()方法,注意,自己实现的hashCode()应该与equals()兼容。
  • 在Java中,散列表用数组+链表实现。每个链表被称为。要想查表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。或许会很幸运,在这个桶里面没有其他元素,此时将元素直接插入桶中即可。当然,有时也会遇到被桶占满的情况,这是不可避免的。这种现象被称为散列冲突。这时,需要新对象与桶中的所有元素进行对比,查看这个对象是否存在。如果散列码是合理且随机分布的,桶的数目也足够大,需要比较的次数就会很少。
  • 如果想要更多的控制散列表的运行性能,就要制定一个初始的桶数。桶数是指用于收集具有相同散列值的桶的数目。如果要插入的元素太多,就会增加冲突的可能性,降低运行性能。
  • 如果散列表太慢满,就需要再散列。例如,如果装填因子为0.75,也就是表中存储的元素超过75%,就需要再散列。
  • Java集合类库中提供的HashSet类,它实现了基于散列表的集。它的底层数据结构是哈希表,就像新华字典;集合内的元素无序且唯一;并且允许存储null值;实现不同步,线程不安全效率高。
import java.util.HashSet;
public class Set集合
{
    public static void main(String[] args)
    {
        HashSet<String> set = new HashSet<>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        set.add("ddd");
        set.add("eee");
        set.add("fff");
        set.add("bbb");
        set.add("ccc");
        System.out.println(set);
        //[aaa, ccc, bbb, eee, ddd, fff]
        //很明显,元素无序且唯一

        HashSet<Integer> set1 = new HashSet<>();
        set1.add(100);
        set1.add(200);
        set1.add(300);
        set1.add(400);
        set1.add(500);
        set1.add(200);
        set1.add(300);
        set1.add(400);
        System.out.println(set1);
        //[400, 100, 500, 200, 300]
    }
}
  • 下面来看一段代码:
测试类:
import java.util.HashSet;
public class Set集合2
{
    public static void main(String[] args)
    {
        HashSet<Student> stuHash = new HashSet<>();
        stuHash.add(new Student("张三", 18));
        stuHash.add(new Student("李四", 19));
        stuHash.add(new Student("王五", 20));
        stuHash.add(new Student("张三", 18));
        stuHash.add(new Student("李四", 19));
        stuHash.add(new Student("王五", 20));
        stuHash.add(new Student("赵六", 21));
        for (Student hash : stuHash) 
        {
            System.out.println(hash);
        }
        //依旧是无序,但是却存在重复元素,为什么?
        /*Student{name='李四', age=19}
		  Student{name='李四', age=19}
		  Student{name='赵六', age=21}
		  Student{name='张三', age=18}
		  Student{name='王五', age=20}
		  Student{name='张三', age=18}
	      Student{name='王五', age=20}*/
    }
}
Student类:
import java.util.Objects;
public class Student
{
    private String name;
    private int 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 Student() {}

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 为什么会出现这种情况?我们可以查看.add方法的源码,看看这个方法在接收了对象之后进行了怎样的处理:
部分源码:
public boolean add(E e) 
{
//你在调用hashSet集合当中的add方法时,添加的元素被e接收,然后又调用map里面的put方法
	return map.put(e, PRESENT)==null;
}

//可以看到这里的map本质是hashMap类,也就是hashSet底层是用hashMap存储的
private transient HashMap<E,Object> map;

public V put(K key, V value) 
{
//put方法里面调用putVal(),这里的key就是传过来的添加的对象,而value是一个固定的值
	return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key)
{
//hash这个方法在接收了你添加的对象之后,返回该对象的hashcode()值(前提是这个对象不是null),并对这个值进行了位运算
	int h;
	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key——使用hash方法计算出来的hashcode值
     * @param key the key——你要添加的对象
     * @param value the value to put——固定的值
     * @param onlyIfAbsent if true, don't change existing value——固定的值
     * @param evict if false, the table is in creation mode.——固定的值
     * @return previous value, or null if none
     */
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;
        if ((p = tab[i = (n - 1) & hash]) == null)
        	//申请一个新的节点,来存放键与值
            tab[i] = newNode(hash, key, value, null);
        else 
        {
            Node<K,V> e; //定义一个节点
            K k;
            //源码里面调用了key(也就是你添加的对象)的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;
}
  • 看过上面的源码之后,发现.add()底层调用了你添加的对象的equals()和hashCode(),我们认为一个Student对象,只要它的成员变量的值一样,就认为两个对象的值一样,但是由于自己定义的Student类里面没有重写这两个方法,因此默认调用父类Objects类里面的这两个方法,比较的是地址值,每new一个对象,即使成员变量值一样,也会在堆区开辟一块新的空间,因此编译器并不认为成员变量相同的对象一样。修改后的代码:
import java.util.HashSet;
public class Test
{
    public static void main(String[] args)
    {
        HashSet<Student> stuHash = new HashSet<>();
        stuHash.add(new Student("张三", 18));
        stuHash.add(new Student("李四", 19));
        stuHash.add(new Student("王五", 20));
        stuHash.add(new Student("张三", 18));
        stuHash.add(new Student("李四", 19));
        stuHash.add(new Student("王五", 20));
        stuHash.add(new Student("赵六", 21));
        for (Student hash : stuHash)
        {
            System.out.println(hash);
        }

        //无序且唯一
        /*Student{name='赵六', age=21}
        Student{name='张三', age=18}
        Student{name='王五', age=20}
        Student{name='李四', age=19}*/
    }
}

Student类:

import java.util.Objects;
public class Student
{
    private String name;
    private int 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 Student() {}

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

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

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

    @Override
    public int hashCode()
    {
        return Objects.hash(name, age);
    }
}
  • hashSet之所以能保证元素的唯一性是因为重写了equals(),那为什么要重写hashCode(),其实是为了减少碰撞。如果你给代码中的hashCode()返回值写为死值0,再去统计equals()方法,发现次数很多,而如果你在hashCode()中返回的是关于对象的一个特殊的值,equals()调用次数明显下降很多。
测试类:
import java.util.HashSet;
public class Test
{
    public static void main(String[] args)
    {
        HashSet<Student> stuHash = new HashSet<>();
        stuHash.add(new Student("张三", 18));
        stuHash.add(new Student("李四", 19));
        stuHash.add(new Student("王五", 20));
        stuHash.add(new Student("张三", 18));
        stuHash.add(new Student("李四", 19));
        stuHash.add(new Student("王五", 20));
        stuHash.add(new Student("赵六", 21));
        for (Student hash : stuHash)
        {
            System.out.println(hash);
        }

//无序且唯一
//hashCode()返回值为0的时候,equals()的调用
/*Student{name='张三', age=18}-----Student{name='李四', age=19}
Student{name='张三', age=18}-----Student{name='王五', age=20}
Student{name='李四', age=19}-----Student{name='王五', age=20}
Student{name='张三', age=18}-----Student{name='张三', age=18}
Student{name='张三', age=18}-----Student{name='李四', age=19}
Student{name='李四', age=19}-----Student{name='李四', age=19}
Student{name='张三', age=18}-----Student{name='王五', age=20}
Student{name='李四', age=19}-----Student{name='王五', age=20}
Student{name='王五', age=20}-----Student{name='王五', age=20}
Student{name='张三', age=18}-----Student{name='赵六', age=21}
Student{name='李四', age=19}-----Student{name='赵六', age=21}
Student{name='王五', age=20}-----Student{name='赵六', age=21}
Student{name='张三', age=18}
Student{name='李四', age=19}
Student{name='王五', age=20}
Student{name='赵六', age=21}*/
//hashCode()返回值为关于对象的值的时候,equals()的调用
/*Student{name='张三', age=18}-----Student{name='张三', age=18}
Student{name='李四', age=19}-----Student{name='李四', age=19}
Student{name='王五', age=20}-----Student{name='王五', age=20}
Student{name='李四', age=19}
Student{name='张三', age=18}
Student{name='王五', age=20}
Student{name='赵六', age=21}*/
    }
}
Student中的方法:
@Override
public boolean equals(Object o)
{
	System.out.println(o+"-----"+this);
	if (this == o)
    	return true;
	if (o == null || getClass() != o.getClass())
    	return false;
	Student student = (Student) o;
	return age == student.age && Objects.equals(name, student.name);
}

@Override
public int hashCode()
{
	return 0;
	//return this.name.hashCode()+this.age*11;返回值有关于对象
	//自动生成的代码返回值:
	//return Objects.hash(name, age);
}

LinkedHashSet类

  • 元素有序且唯一;链表保证了有序,哈希表保证了唯一;
  • 注意,此实现不是同步的;线程不安全,效率高。
import java.util.HashSet;
import java.util.LinkedHashSet;
public class LinkedHashSet集合
{
    public static void main(String[] args)
    {
        //LinkedHashSet:元素有序且唯一,链表保证了有序,哈希表保证了唯一
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("abc");
        set.add("def");
        set.add("ccc");
        set.add("ddd");
        set.add("eeg");
        set.add("def");
        set.add("ccc");
        set.add("eee");
        System.out.println(set);
        //[abc, def, ccc, ddd, eeg, eee]

        HashSet<String> list = new HashSet<>();
        list.add("abc");
        list.add("def");
        list.add("abc");
        list.add("def");
        list.add("abc");
        list.add("def");
        list.add("abc");
        list.add("def");

        LinkedHashSet<String> set1 = new LinkedHashSet<>(list);
        System.out.println(set1);
        //[abc, def]
    }
}

树集(TreeSet)

  • TreeSet类与散列集十分类似,不过,它比散列集有所改进。树集是一个有序集合。可以以任意顺序将元素插到集合当中。在对集合进行遍历的时,每个值将自动的按照排序后的顺序呈现。
import java.util.TreeSet;
public class Test
{
    public static void main(String[] args)
    {
        TreeSet<Object> sorter = new TreeSet<>();
        sorter.add("Bob");
        sorter.add("Amy");
        sorter.add("Carl");
        for (Object o : sorter)
        {
            System.out.print(o+"\t");
            //Amy  Bob	Carl
        }
    }
}
  • 正如TreeSet类名所示,排序是用树结构完成的(红黑树)。将一个元素添加到树中要比添加到散列表中慢,但是,与检查数组或链表中的重复元素相比还是快很多。
  • TreeSet集合比较器排序:
package com.baidu.MyDemo2;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSet1
{
    public static void main(String[] args)
    {
        TreeSet<Student> set = new TreeSet<>();

        set.add(new Student("张三22222222222", 23));
        set.add(new Student("李四11111111111", 23));
        set.add(new Student("王五1333333333333333333333", 21));
        set.add(new Student("赵六55555555555555", 22));
        set.add(new Student("田七6666666666666", 19));
        set.add(new Student("李八7777777777777777", 18));
        set.add(new Student("刘星88888888888", 17));
        for (Student stu : set) { System.out.println(stu); }
    }
}
  • 你给TreeSet类的集合存学生对象的时候,一存就会报错,为什么?

前面说这个集合可以根据你的需要进行排序,但是它在存储学生对象的时候,没有办法进行比较;

  • 排序分为自然排序和比较器排序:

1、如果new一个TreeSet对象的时候采用空参构造,就默认为是自然排序。你给集合当中添加Integer类型的数据时,就不会报错,这是因为Integer类当中实现了Comparable接口,重写了这个接口中的compare()。因此自定义的Student类如果想要添加到树集当中,就必须实现接口Comparable,并重写compare()。
2、比较器排序,采用有参构造,你给这个TreeSet对象的构造方法中传递一个比较器,实现接口Comparator,并重写compare()。
3、compare()的返回值代表元素应该添加到哪,后面添加的元素与根节点相比较,为0说明集合当中存在同样的对象,就存不进去了;因此如果你在代码当中将compare()返回值写死为0,就会出现一种现象,集合中只存进去根节点元素,其余都存不进去。为正数添加到红黑树的左边,为负数添加到红黑树的右边;如果compare()的返回值写成任意正负数,全部的元素都存储在了一个数组当中。

比较器排序:

import java.util.Comparator;
import java.util.TreeSet;
public class TreeSet1
{
    public static void main(String[] args)
    {
        //在这里我们发现比较器只使用一次,那我们需要写一个类来实现接口,然后new这个类的对象吗?
        //我们可以直接使用匿名内部类:
        TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //按照姓名长度来排序
                int num=o1.getName().length()-o2.getName().length();
                int num1=(num==0)?(o1.getName().compareTo(o2.getName())):num;
                int num2=(num1==0)?(o1.getAge()-o2.getAge()):num1;
                return num2;
            }
        });

        set.add(new Student("张三22222222222", 23));
        set.add(new Student("李四11111111111", 23));
        set.add(new Student("王五1333333333333333333333", 21));
        set.add(new Student("赵六55555555555555", 22));
        set.add(new Student("田七6666666666666", 19));
        set.add(new Student("李八7777777777777777", 18));
        set.add(new Student("刘星88888888888", 17));
        for (Student stu : set) { System.out.println(stu); }
         /*Student{name='刘星88888888888', age=17}
            Student{name='张三22222222222', age=23}
            Student{name='李四11111111111', age=23}
            Student{name='田七6666666666666', age=19}
            Student{name='赵六55555555555555', age=22}
            Student{name='李八7777777777777777', age=18}
            Student{name='王五1333333333333333333333', age=21}*/
    }
}

自然排序:

//测试类:
import java.util.TreeSet;
public class TreeSet2
{
    public static void main(String[] args)
    {
        TreeSet<Student> treeSet = new TreeSet<>();
        //空参构造,采用的时候是自然排序
        treeSet.add(new Student("张三22222222222", 23));
        treeSet.add(new Student("李四11111111111", 23));
        treeSet.add(new Student("王五1333333333333333333333", 21));
        treeSet.add(new Student("赵六55555555555555", 22));
        treeSet.add(new Student("田七6666666666666", 19));
        treeSet.add(new Student("李八7777777777777777", 18));
        treeSet.add(new Student("刘星88888888888", 17));
        for (Student student : treeSet) {
            System.out.println(student);
        }
    }
}
//Student类:
public class Student implements Comparable<Student>
{
    private String name;
    private int age;
    
    @Override
    public int compareTo(Student o) 
    {
        //需求是按照姓名的长短来排序:
        int i = this.name.length() - o.name.length();
        //如果长度一样,还得比较姓名内容
        int i1=i==0?this.name.compareTo(o.name):i;
        //当长度一样,内容一样,还得比较年龄
        int i2=i1==0?this.age-o.age:i1;
        return i2;
        //里面的比较逻辑需要你自己写,它只要一个返回值正负0
    }
    ...
}
  • 其实不光是TreeSet集合有比较器这一说,其它集合也会用到,比如ArrayList里面的sort(),就用到了;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class MyTest1
{
    public static void main(String[] args)
    {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(14);
        list.add(21);
        list.add(13);
        list.add(47);
        list.add(1112);
        list.add(222);
        list.add(309);
        list.add(4);
        //对list集合中的元素排序
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        });
        System.out.println(list);
        //[4, 13, 14, 21, 47, 222, 309, 1112]

        Integer[] arr={10,2,16,43,23,67};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
        //[2, 10, 16, 23, 43, 67]

        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        System.out.println(Arrays.toString(arr));
        //[67, 43, 23, 16, 10, 2]
    }
}