JAVA进阶学习08

一、List系列集合

请添加图片描述

1.1 List集合的常用方法

以下是相对于父类增加的方法:
相较于父类增加的多是含索引的方法(add、remove)
请添加图片描述
代码演示:

//1.创建一个ArrayList集合对象(有序、有索引、可以重复)
List<String> list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("至尊宝");
list.add("牛夫人"); 
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]

//2.public void add(int index, E element): 在某个索引位置插入元素
list.add(2, "紫霞仙子");
System.out.println(list); //[蜘蛛精, 至尊宝, 紫霞仙子, 至尊宝, 牛夫人]

//3.public E remove(int index): 根据索引删除元素, 返回被删除的元素
System.out.println(list.remove(2)); //紫霞仙子
System.out.println(list);//[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]

//4.public E get(int index): 返回集合中指定位置的元素
System.out.println(list.get(3));

//5.public E set(int index, E e): 修改索引位置处的元素,修改后,会返回原数据
System.out.println(list.set(3,"牛魔王")); //牛夫人
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛魔王]

1.2 List集合的遍历方式

List<String> list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("糖宝宝");

//1.普通for循环
for(int i = 0; i< list.size(); i++){
    //i = 0, 1, 2
    String e = list.get(i);
    System.out.println(e);
}

//2.增强for遍历
for(String s : list){
    System.out.println(s);
}

//3.迭代器遍历
Iterator<String> it = list.iterator();
while(it.hasNext()){
    String s = it.next();
    System.out.println(s);
}
//list相较于set多了列表迭代器的方法(ListIterator)
//该迭代器相较于collection多了边遍历边添加的方法
//和倒序遍历的方法

//4.lambda表达式遍历
list.forEach(s->System.out.println(s));

1.3 ArryList底层原理

ArrayList集合底层是基于数组结构实现的,也就是说当你往集合容器中存储元素时,底层本质上是往数组中存储元素。

在这里插入图片描述

1.4 LinkedList底层原理

LinkedList集合是基于双向链表实现了,所以相对于ArrayList新增了一些可以针对头尾进行操作的方法

请添加图片描述
底层代码实现:

二、Set系列集合

该集合在内存中存储时是一个散列,一般是由一个哈希值来确定存储的位置。
请添加图片描述
对于Set集合的应用:

//Set<Integer> set = new HashSet<>();	//无序、无索引、不重复
//Set<Integer> set = new LinkedHashSet<>(); //有序、无索引、不重复
Set<Integer> set = new TreeSet<>(); //可排序(升序)、无索引、不重复
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
set.add(777);
set.add(777);
System.out.println(set); //[555, 666, 777, 888]
//当前treeset对数据自动排序且不重复

2.1 HashSet集合底层原理

HashSet集合底层是基于哈希表实现的

  • JDK8以前:哈希表 = 数组+链表
  • JDK8以后:哈希表 = 数组+链表+红黑树’

请添加图片描述
请添加图片描述
几点疑问

  1. 哈希表为什么是无序的:其根据哈希值存储,不像List是利用栈或队列存储
  2. 哈希表为什么没有索引:哈希表无法正常索引,在哈希值下还可能有复杂的红黑树链表结构
  3. 哈希表去重原理:利用hashCode和equals方法结合使用

2.2 HashSet去重原理

HashSet存储元素去重依赖于两个方法:一个是hashCode方法用来确定在底层数组中存储的位置,另一个是用equals方法判断新添加的元素是否和集合中已有的元素相同。

我们一般在要存储的对象中会重写这两个方法

public class Student{
    private String name; //姓名
    private int age; //年龄
    private double height; //身高
 
    //无参数构造方法
    public Student(){}
    //全参数构造方法
    public Student(String name, int age, double height){
        this.name=name;
        this.age=age;
        this.height=height;
    }
    //...get、set、toString()方法自己补上..
    
    //按快捷键生成hashCode和equals方法
    //alt+insert 选择 hashCode and equals
    //equals方法用来比较两对象的属性值
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.height, height) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }
	//hashCode方法用来生成两对象的hash值
	/*/在生成的过程中首先用name来得到一个初始的hash值,然后乘质数31,之后转化为一个长整型。
    @Override
    public int hashCode() {
        int result;
        long temp;
        result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        temp = Double.doubleToLongBits(height);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
}

对应的测试类:

public class Test{
    public static void main(String[] args){
        Set<Student> students = new HashSet<>();
        Student s1 = new Student("至尊宝",20, 169.6);
        Student s2 = new Student("蜘蛛精",23, 169.6);
        Student s3 = new Student("蜘蛛精",23, 169.6);
        Student s4 = new Student("牛魔王",48, 169.6);
        
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        
        for(Student s : students){
            System.out.println(s);
        }
    }
}

2.3 LinkedHashSet底层原理

LinkedHashSet与hashSet集合方法相同,是其子类

LinkedHashSet它底层采用的是也是哈希表结构,只不过额外新增了一个双向链表来维护元素的存取顺序。如下下图所示:
请添加图片描述

该类解决了HashSet不能记录存储顺序的问题,但是也在存储时带来更多的开销

2.4 TreeSet集合

treeSet类相比于父类没有新的方法

Set<Integer> set1= new TreeSet<>();
set1.add(8);
set1.add(6);
set1.add(4);
set1.add(3);
set1.add(7);
set1.add(1);
set1.add(5);
set1.add(2);
System.out.println(set1); //[1,2,3,4,5,6,7,8]

Set<Integer> set2= new TreeSet<>();
set2.add("a");
set2.add("c");
set2.add("e");
set2.add("b");
set2.add("d");
set2.add("f");
set2.add("g");
System.out.println(set1); //[a,b,c,d,e,f,g]

2.5 TreeSet排序出错

当排序自己定义的对象时,集合需要自己指定排序规则根据特定的属性排序。
此时有两种办法可以解决该问题:

  • 第一种:让元素的类实现Comparable接口,重写compareTo方法,在使用add方法时直接调用比较方法。
  • 第二种:在创建TreeSet集合时,通过构造方法传递Compartor比较器对象

方式一

//第一步:先让Student类,实现Comparable接口
//注意:Student类的对象是作为TreeSet集合的元素的
public class Student implements Comparable<Student>{
    private String name;
    private int age;
    private double height;
	//无参数构造方法
    public Student(){}
    //全参数构造方法
    public Student(String name, int age, double height){
        this.name=name;
        this.age=age;
        this.height=height;
    }
    //...get、set、toString()方法自己补上..
    
    //第二步:重写compareTo方法
    //按照年龄进行比较,只需要在方法中让this.age和o.age相减就可以。
    /*
    原理:
    在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的
    结果是正数、负数、或是零,决定元素放在后面、前面还是不存在。
    */
    @Override
    public int compareTo(Student o) {
        //this:表示将要添加进去的Student对象
        //o: 表示集合中已有的Student对象
        return this.age-o.age;
    }
}

方式二

//创建TreeSet集合时,传递比较器对象排序
/*
原理:当调用add方法时,底层会先用比较器,根据Comparator的compare方是正数、负数、或是零,决定谁在后,谁在前。
*/
//下面代码中是按照学生的年龄升序排序
Set<Student> students = new TreeSet<>(new Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2){
        //需求:按照学生的身高排序
        return Double.compare(o1.age,o2.age); 
    }
});

//创建4个Student对象
Student s1 = new Student("至尊宝",20, 169.6);
Student s2 = new Student("紫霞",23, 169.8);
Student s3 = new Student("蜘蛛精",23, 169.6);
Student s4 = new Student("牛魔王",48, 169.6);

//添加Studnet对象到集合
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students); 

三、泛型

3.1 泛型的简介

所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:< E >),称为泛型、泛型接口、泛型方法、它们统称为泛型。
该方法一般针对引用数据类型使用

请添加图片描述
ArrayList集合的设计者在定义ArrayList集合时,就已经明确ArrayList集合时给别人装数据用的,但是别人用ArrayList集合时候,装什么类型的数据他不知道,所以就用一个<E>表示元素的数据类型。

当没有泛型约束时
在这里插入图片描述
从上图中可见在集合中可以添加任意类型的数据,此时的数据类型是以Object类的形式存入


  • 泛型的好处:在编译阶段可以避免出现一些非法的数据。

  • 泛型的本质:把具体的数据类型传递给类型变量。

3.2 泛型的相关细节

  1. 指定泛型数据后,在传入数据时,可以向该其中传入该类型或其子类
  2. java中的泛型是一个**伪泛型,在实际存储时也是存储Object类(基本数据类型无法转为Object类存储)

3.3 泛型类

一般需要自己定义的泛型类非常少,多是要阅读别人写好的代码

定义格式:

//这里的<T,W>其实指的就是类型变量,可以是一个,也可以是多个。
public class 类名<T,W>{
    
}

可以自己定义一个MyArrayList泛型类,模拟一下自定义泛型类的使用。

//定义一个泛型类,用来表示一个容器
//容器中存储的数据,它的类型用<E>先代替用着,等调用者来确认<E>的具体类型。
public class MyArrayList<E>{
    private Object[] array = new Object[10];
    //定一个索引,方便对数组进行操作
    private int index;
    
    //添加元素
    public void add(E e){
        array[index]=e;
        index++;
    }
    
    //获取元素
    public E get(int index){
        return (E)array[index];
    }
}

在另一个文件中再写一个类
public class Test{
    public static void main(String[] args){
        //1.确定MyArrayList集合中,元素类型为String类型
        MyArrayList<String> list = new MyArrayList<>();
        //此时添加元素时,只能添加String类型
        list.add("张三");
        list.add("李四");
        
         //2.确定MyArrayList集合中,元素类型为Integer类型
        MyArrayList<Integer> list1 = new MyArrayList<>();
        //此时添加元素时,只能添加String类型
        list.add(100);
        list.add(200);
        
    }
}

==可见泛型类中的泛型格式一般在类的实例化时在尖括号中说明的 ==

3.4 泛型方法

泛型方法与泛型类的区别:

  • 泛型类中申明的泛型定义跟在类名后,类中的所有方法都可以使用该泛型
  • 泛型方法的泛型声明在方法的返回值类型之前的位置,只有该方法可以使用

定义格式:

public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){
    若返回值只有泛型,可以省略返回值类型的书写
}

代码演示:

public class Test{
    public static void main(String[] args){
        //调用test方法,传递字符串数据,那么test方法的泛型就是String类型
        String rs = test("test");
    
        //调用test方法,传递Dog对象,那么test方法的泛型就是Dog类型
    	Dog d = test(new Dog()); 
    }
    
    //这是一个泛型方法<T>表示一个不确定的数据类型,由调用者确定
    public static <T> T test(T t){
        return t;
    }
}
	
	public static <E> void addAll(ArryList<E> arry,E e){

	}

可见泛型方法中定义的泛型种类一般由调用时的参数来确定

3.5 泛型接口

泛型接口其实指的是在接口中把不确定的数据类型用<类型变量>表示。定义格式类似于泛型类的定义。

//这里的类型变量,一般是一个字母,比如<E>
public interface 接口名<类型变量>{
    
}

代码演示:

public interface Data<T>{
    public void add(T t);
    
    public ArrayList<T> getByName(String name);
}

//此时确定Data<E>中的E为Teacher类型,
//接口中add和getByName方法上的T也都会变成Teacher类型
public class TeacherData implements Data<Teacher>{
   	public void add(Teacher t){
        
    }
    
    public ArrayList<Teacher> getByName(String name){
        
    }
}

若子类中仍不能确定泛型的类型可继续用< E >

可见泛型接口中的泛型类型确定,是在接口实现时在尖括号中说明的

3.6 泛型的继承和通配符

泛型不具备继承性,但是数据可以继承,即泛型不能实现多态用父类接收子类对象 ,但父类的泛型可以存子类的数据。

可以使用通配符来约束

class Ye {

}

class Fu extends Ye{
}

class Zi extends Fu {
}

public class GemericTest1 {
    public static void main(String[] args) {

        ArrayList<Ye> list1=new ArrayList<>(); 
        ArrayList<Fu> list2=new ArrayList<>(); 
        ArrayList<Zi> list3=new ArrayList<>(); 
        
        //不含通配符的方法
        method(list1);
        //method(list2);//报错
        //形参是父类容器,不能传递子类容器,无法实现多态
        
        list1.add(new Ye());
        list1.add(new Fu());
        //父类的容器可以装子类的对象
        
        method2(list1);
        method2(list2);
        
        method3(list1);
        method3(list2);
        
        method4(list1);
        
        
    }
    //不含通配符
    public static void method(ArrayList<Ye> list) {}
    
    //通配符?可以接收任何类型的数据
    public static void method2(ArrayList<?> list){}
    //通配符? extends 类名  只能接收该类机器子类
    public static void method3(ArrayList<? extends Ye> list){}
    //通配符? super 类名  只能接收该类及其父类
    public static void method4(ArrayList<? super Zi> list){}
}


总结:父类的泛型容器形参不能接子类的泛型容器,但父类的容器能存子类对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值