JavaSE笔记_day14_泛型、Set集合

180 阅读10分钟

一.泛型

1.泛型的概述

  • 泛型:广泛的类型,定义一个类型,类中方法参数或返回值,不能确定类型,那么可以泛型表示。
  • 格式:在类型的后面,使用<>,在<>中写上一个大写的英文字母,可以是E,K,V,T,W,Q。 
       <E> --> 泛型的定义
  • 优点:
      (1)提高代码的安全性,将运行环节抛出的错误,提前到编译环节显式

      (2)在进行元素的获取时,不需要再进行转型

  • 注意事项 
      前后<>保持一致或后面省略尖括号中内容或省略尖括号。

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FanXing {
    public static void main(String[] args) {
        //notFanXingMethod();
        fanXingMethod();
    }
    public static void notFanXingMethod(){
        //三种方法创建集合
        /*1.Collection coll = new ArrayList();
        2.List list1 = new ArrayList();*/
        ArrayList list = new ArrayList();/*3.没有泛型时*/
        list.add("a");
        list.add("b");
        list.add(1);//整形不能转为字符串 class java.lang.Integer cannot be cast to class java.lang.String
        for(int i = 0;i<list.size();i++){
            Object obj = list.get(i);
            String s = (String)obj;//向下转型报错
            System.out.println(s);
        }
    }
    public static void fanXingMethod(){
        //定义一个带有泛型的集合,该集合将来存储的是String类型
        ArrayList<String> list = new ArrayList<>();
        /*list.add(1);存储报错,不能存储整形*/
        list.add("1");
        list.add("2");
        list.add("abc");
        for (int i = 0; i < list.size(); i++) {
            String s= list.get(i);
            System.out.println(s);
        }
    }
}

2.带有泛型的类

格式: 
    修饰符 class 类名<泛型1,泛型2...> {
    	
    }
    public class ArrayList<E>

  • 说明: 
    1.泛型E,表示Element,元素的含义, 
    2.泛型可以作为一个已知的类型,在整个类中使用(类上的泛型可当做类的成员变量使用) 
    3.当创建一个类对象时,确定泛型具体类型

3.带有泛型的方法

格式: 
    修饰符<泛型1,泛型2...> 返回值类型 方法名(参数列表){
        retrun;
    }

  • 说明: 
       1.如果方法不是静态的,那么方法上面可以自己定义泛型,可以使用类上的泛型(可当做成员变量使用) 
       2.静态方法,如果要使用泛型,只能在方法上,自己定义泛型。 
          原因:静态属于类,静态优先于对象存在的,静态方法不能使用类的泛型,类上的泛型,只有在创建对象时,才能确定具体类型。 
       3.方法上定义的泛型只能在方法中使用

import java.util.ArrayList;
import java.util.Arrays;
public class FanXingClass<E> {
    //定义一个带有泛型的类,泛型定义为E,E表示一个广泛类型
    //1.泛型可以在整个类中作为一个已知类使用(可以当成成员变量使用)
    //使用泛型E
    ArrayList<E> al = new ArrayList<>();
    //方法中使用类的泛型
    public void addE(E e){
        al.add(e);
        System.out.println(al.get(0));//12
    }
    //静态方法只能使用自己定义的泛型,无法使用类上的泛型
    //方法功能:替换arr数组中a和b两个索引的元素
    public static <E> void changeE(E[] arr,int a,int b ){
        E e = arr[a];
        arr[a] = arr[b];
        arr[b] = e;
    }
    public static void main(String[] args){
        //2.创建类的同时,确定泛型的具体类型
        //FanXingClass<Integer> --> 泛型E就表示Integer类型
        FanXingClass<Integer> fx = new FanXingClass();
        // 3.调用带有泛型的方法,需要传递实际参数,参数类型E-->Integer类型
        fx.addE(12);
        //4.调用静态的带有泛型的方法
        Integer[] i = {12,13,14,15};
        int a = 1;
        int b = 2;
        FanXingClass.changeE(i,a,b);
        System.out.println( Arrays.toString(i));//[12, 14, 13, 15]
    }
}

4.带有泛型的接口

格式: 
    修饰符 interface 接口名<泛型1,泛型2...>{
        //抽象方法 
    }

  • 说明: 带有泛型的接口,实现类情况 
     1.实现类不带有泛型,要求实现类在实现接口时,接口上的泛型需要指定成具体数据类型。 
     2.实现类带有泛型,那么实现的接口就不需要给出具体泛型类型 
     3.接口上的泛型可以在整个接口中,当做已知类使用 
     举例:Collection<E> --> LIst<E> --> ArrayList<E>

//定义一个带有泛型的接口
//关注的是带有泛型接口的实现类
public interface MyInterface<E> {
    public abstract void get(E e);//方法参数使用泛型
    public abstract E fun();//方法返回值使用泛型
}
//1.定义一个不带有泛型的实现类,要求接口的泛型必须确定 
class MyInterfaceImpl1 implements MyInterface <String>{
    @Override//重写的方法已经将所有泛型替换为String了
    public void get(String s) {}
    @Override
    public String fun() { return null; }
}
//2.定义一个带有泛型的实现类
class MyInterfaceImpl2<E> implements MyInterface<E>{//将来创建类对象时确定具体的泛型类型
    @Override
    public void get(E e) { }
    @Override
    public E fun() { return null; }
}

5.泛型的通配符和泛型擦除

?:表示泛型的通配符,可以匹配任意的泛型类型
1.containsAll(Collection<?> c):匹配任意类型的集合
2.addAll(Collection<? extends E> c):方法调用集合的类型作为E存在,参数的集合形式,只能是方法调用集合的子类或者是本类类型
3.TreeSet(Comparator<? super E> compartor):需要传递的参数是E类型本身,或者是E的父类
泛型擦除:
    进行代码编写时,可以添加泛型,加了泛型时为了提高代码的安全性
    当源文件编译成.class文件时,泛型是不存在的,.class文件中没有泛型的

import java.util.ArrayList;
public class FanXingTongPei {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        System.out.println(list1.containsAll(list2));//false
        //addAll(Collection<? extends E>)
        //E的泛型类型,来自于list1集合中的泛型String
        //? extends E --> ? extends String 表示追加的集合中的元素,只能是String类型本身或者是String的子类
        ArrayList<Object> list3 = new ArrayList<>();
        //list1.addAll(list2);//list2并不是String类型,报错
        list3.addAll(list1);
    }
}

二.Set集合

1.Set集合的概述

  • List集合特点:有序、有索引、可重复 
  • Set集合,是接口,来自于java.util.包 
  • 接口不能实例化对象,于是找到了一个实现类HashSet 
  • Set集合特点:无序、无索引、不可重复

import java.util.HashSet;
import java.util.Set;
public class SetDemo {
    public static void main(String[] args) {
        //创建一个Set集合容器
        Set<String> set = new HashSet<>();
        //向容器中添加元素
        set.add("c");
        set.add("a");
        set.add("hello");
        set.add("a");
        set.add("c");
        set.add("A");
        System.out.println(set);//[a, A,c, hello]
    }
}

2.Set集合的遍历

将Set集合中的元素一个一个的获取
    1.toArray():将Set集合中的内容,放置到一个Object[]中,然后通过遍历数组,就可以实现遍历Set集合中的元素;
    2.toArray(T[] t):返回一个包含Set集合元素的T类型的数组t;
    3.iterator():迭代器,返回的类型iterator<>,迭代器中的泛型与集合中的泛型保持一致
    4.增强forforEach:
        for(数据类型 变量名:集合或数组){
					
        }
        (1)集合或数组表示需要进行遍历的容器
        (2)数据类型表示集合或者数组中的元素类型
	(3)变量名:表示从集合或数组中获取到的每一个元素的表示
	注意:
	    增强for通过集合或数组中是否具有下一个元素进行遍历的,与容器是否具有索引,无关
	    增强for底层是迭代器,因此在使用增强for进行集合遍历时,如果同时向集合中添加元素,报出并发修改异常
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetBianLi {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        set.add(9);
        set.add(-2);
        set.add(13);
        set.add(29);
        f1(set);
        f2(set);
        f3(set);
        f4(set);
    }
    //1.toArray():
    public static void f1(Set<Integer> set){
        Object[] obj = set.toArray();
        for (int i = 0; i < obj.length; i++) {
            Integer a = (Integer) obj[i];
            System.out.print(a+" ");//-2 9 13 29
        }
        System.out.println("====================");
    }
    //2.toArray(T[] t)
    public static void f2(Set<Integer> set){
        //准备一个大小与set集合一致的Integer类型的数组,将集合中的元素,放置到Integer类型的数组中
        Integer[] it = new Integer[set.size()];
        set.toArray(it);//it为Integer类型属于泛型,返回一个Integer类型的数组it
        for (int i = 0; i < it.length; i++) {
            System.out.print(it[i]+" ");//-2 9 13 29
        }
    }
    //3.iterator():
    public static void f3(Set<Integer> set){
        Iterator it = set.iterator();
            while(it.hasNext()){
                System.out.print(it.next()+" ");//-2 9 13 29
	    }
    }
    //4.增强for
    public static void f4(Set<Integer> set){
        for(Integer a : set){
            System.out.print(a+" ");//-2 9 13 29
        }
    }
}

练习:随机生成10个1-100的随机数,存储在合适的集合中,并且遍历集合
      要求:集合中存储的随机数不重复

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class SetTest {
    public static void main(String[] args) {
        //1.创建一个随机数对象
        Random r= new Random();
        //2.生成1-100的随机数
        //3.放入Set集合中
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < 10; i++) {////while(set.size()<10){}
            set.add( r.nextInt(99)+1);
        }
        //4.遍历
        for(Integer a : set){
            System.out.print(a+" ");//65 7 8 10 12 29 94 78 31 
        }
    }
}

3.Set集合保证元素唯一

1.Set集合存储JDK定义好的引用数据类型
    原理:因为JDK中定义好的引用数据类型,都重写了Object中的hashCode和equals方法。
2.Set集合存储自定义引用数据类型
    如果Set集合中存储的引用数据类型需要去重复,那么将hashcode()和equals()自动重写
分析:
    (1)Object 源代码:
        hashcode():将对象在内存中的地址值,转化成一个int类型的整数
        equals():比较的是对象的地址值是否相等
    (2)重写后的方法:
        hashcode():将对象的成员变量获取到,将所有成员所对应的哈希值计算出来
        equals():比较的是成员变量的值是否相等
3.Set集合去重复原理
    1.先将对象的hashCode值计算出来,判断set集合中,是否有相同的哈希值,如果没有,直接添加元素成功。
    2.如果添加的对象的哈希值与set集合中其他元素的哈希值相同,然后判断相同哈希值的equals方法的比较结果
        (1)equals()比较,判断两个对象的成员变量值是否完全相同,如果完全一致,重复,添加失败。
        (2)equals()比较,判断两个对象的成员变量值是否不完全相同,内容不一致,添加set集合成功

import java.util.HashSet;
public class SetOnly {
    public static void main(String[] args) {
        fun();
        fun1();
    }
    //Set集合存储JDK定义好的应用数据类型,去重复
    public static void fun(){
        HashSet<String> set1 = new HashSet<>();
        set1.add("a");
        set1.add("a");
        set1.add("b");
        System.out.println(set1);//[a, b]
        HashSet<Integer> set2 = new HashSet<>();
        set2.add(12);
        set2.add(25);
        set2.add(12);
        set2.add(25);
        System.out.println(set2);//[25, 12]
    }
    //Set集合存储自定义的Person类型
    public static void fun1(){
        HashSet<Person> set1 = new HashSet<>();
        Person p = new Person("张三",20);
        set1.add(p);
        set1.add(new Person("李四",23));
        set1.add(new Person("张三",20));
        set1.add(new Person("李四",23));
        //System.out.println(set1);//[com.itcast.bjzhang.demo04.Person@1d81eb93, com.itcast.bjzhang.demo04.Person@2f4d3709, com.itcast.bjzhang.demo04.Person@b4c966a, com.itcast.bjzhang.demo04.Person@4e50df2e]
				   //如果不想打印地址,Person类重写toString()
        //System.out.println(set1);//[Person{name='李四', age=23}, Person{name='李四', age=23}, Person{name='张三', age=20}, Person{name='张三', age=20}]
				   //没有去重,若想去重复,Person类重写hashcode() and equals()方法
        System.out.println(set1);//[Person{name='张三', age=20}, Person{name='李四', age=23}]
    }
}

import java.util.Objects;
public class Person {
    private String name;
    private int age;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return getAge() == person.getAge() &&
        Objects.equals(getName(), person.getName());
    }
    @Override
    public int hashCode() {
        return Objects.hash(getName(), getAge());
    }
    @Override
    public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
        '}';
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {
    }
    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;
    }
}

4.LinkedHashSet

  • LinkedHashSet是hashSet的子类,LinkedHashSet存储元素有序

import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet();
        set.add("c");
        set.add("a");
        set.add("hello");
        set.add("a");
        set.add("c");
        set.add("A");
        set.add(null);
        System.out.println(set);//[c, a, hello, A, null]
    }
}