欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

Day19Java泛型,增强for循环,Collection集合中Set接口的子类HashSet与TreeSet

时间:2023-06-07

目录

泛型

泛型概述

泛型的优点:

泛型高级

泛型通配符

泛型的定义位置

泛型类

 泛型方法

 泛型接口

 增强For循环

增强for循环概述

 静态导入

 概述:

可变参数

List集合练习

集合的嵌套遍历

Set接口

Hashset

 linkedHashSet

TreeSet   

自然排序                                                                                                                   

比较器排序


泛型

之前说过集合可以存放不同引用数据类型的数据,当集合中既含有String类型的数据,又含有其它类型的数据时,我们在遍历时会出现这样的问题:

代码引例:

import java.util.ArrayList;import java.util.Iterator;public class Test1 { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("hello"); list.add(20); list.add(12.48); list.add(new Students());// 遍历 Iterator iterator = list.iterator(); while (iterator.hasNext()){ String s=(String)iterator.next(); System.out.println(s); } }}

该集合中存放了四种引用数据类型的数据,当在遍历集合,进行向下转型操作时会报错:

 ClassCastException:类型转换异常

这就是由于我们在集合中存放了不仅String类型的数据,还有Integer类型,Double类型和Student类型数据,但在强制类型转换时将遍历到的元素都转换成字符串类型,因此发生异常

解决办法:在存储数据时,将可以存储的数据类型限定为字符串类型,这样在强转时就不会报错

类似于在定义数组String[] arr=new String[3]时就已经将数组限定为只可以存放String类型的数据

Java提供了一种技术实现这种限定:泛型

泛型概述

把明确数据类型的工作,提前到了编译时期,在创建集合的时候明确存储元素的数据类型,有些类似于把数据当作参数一样传递,所以又称之为:参数化类型

泛型语句定义格式:<引用数据类型>        注意:尖括号中只能是引用数据类型

泛型的优点:

1、将我们运行时期出现的问题提前到了编译时期

2、不需要再做强制类型转换

3、优化了代码。消除了不必要的黄色警告线,使代码看起来更加整洁

通过观察API发现:泛型可以出现在类,接口,方法上,类似于都叫泛型,一般情况泛型出现在大多使用的集合中

加入泛型后的代码:

import java.util.ArrayList;import java.util.Iterator;public class FanXinTest { public static void main(String[] args) { ArrayList list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("java"); list.add("bigdata"); Iterator iterator = list.iterator();// 遍历 while(iterator.hasNext()){ String next = iterator.next(); System.out.println(next); } }}

泛型高级

泛型通配符<?>

任意类型,如果没有明确,那就是Object以及任意的Java类

? extends E  

向下限定,E及其子类

? super E

向上限定,E及其父类

代码举例:

在此之前创建三个类:Animal类,Cat类和Dog类,其中Cat类和Dog类继承自Animal类,类中不需要写具体代码。

import java.util.ArrayList;public class GenericMyDemo { public static void main(String[] args) { ArrayList animals1 = new ArrayList<>(); ArrayList dogs1 = new ArrayList<>(); ArrayList cats1 = new ArrayList<>();// 任意调用,没有明确就是Object以及其他的Java类 ArrayList<?> animals2 = new ArrayList(); ArrayList<?> dogs2 = new ArrayList(); ArrayList<?> cats2 = new ArrayList(); ArrayList<?> objects1=new ArrayList();// 向下限定 ArrayList<? extends Animal > animals3 =new ArrayList(); ArrayList<? extends Animal> cats3=new ArrayList(); ArrayList<? extends Animal> dogs3=new ArrayList();// ArrayList<? extends Animal> objects2=new ArrayList();// 向上限定 ArrayList<? super Animal> cats4 =new ArrayList(); ArrayList<? super Animal> dogs4 =new ArrayList(); }}

泛型的定义位置

泛型类

就是把泛型定义在类上

定义格式:

public class 类名<泛型类型1,...>   

注意:这里的泛型类型可以不止一个,但是泛型类型必须是引用数据类型,这里<>中的内容仅仅表示的是一种参数数据类型,参数类型是一种变量,该变量要符合变量的命名规则,可以是任意符合标识符规则的名字

一般情况下在定义时习惯使用一个大写字母表示。

举例:

//这里是自定义一个泛型类public class GenericDemo1 { private T obj; public T getObj(){ return obj; } public void setObj(T obj){ this.obj=obj; }}

泛型类的测试类

//这里是泛型类的测试类public class GenericDmeo1Test { public static void main(String[] args) { GenericDemo1 demo1 = new GenericDemo1<>(); demo1.setObj("hello"); String obj = demo1.getObj(); System.out.println(obj); }}

输出结果: 

 泛型方法

就是把泛型定义在方法上

定义格式:public <泛型类型> 返回类型 方法名(泛型类型 形参)

代码举例:

由于将来不知道需要传入什么类型的数据,因此定义泛型方法

public class Gdemo2 {// 泛型方法 public void show(R r){ System.out.println(r); }}

泛型方法的测试类

public class Gtest2 { public static void main(String[] args) { Gdemo2 gdemo2 = new Gdemo2();// Stirng类型 gdemo2.show("hello");// Integer类型 gdemo2.show(19);// Double类型 gdemo2.show(15.23); }}

输出结果:

 泛型接口

就是把泛型定义在接口上

定义格式:public interface 接口名<泛型类型1,...>

代码举例:

public interface Gdemo3 { void show(W w);}

该接口的实现类

public class Gdemo3Impl implements Gdemo3{ @Override public void show(W w) { System.out.println(w); }}

测试类

public class Gtest3{ public static void main(String[] args) { Gdemo3Impl gdemo3 = new Gdemo3Impl(); gdemo3.show("world");// gdemo3.show(20);已经限定为String类型,不可以使用其他类型 }}

输出结果:

 增强For循环

总结:

JDK1.5之后出现的新特性:泛型,增强for循环,包装类,Scanner,枚举

增强for循环概述

增强for循环就是简化数组和Collection集合的遍历

语句定义格式:

for(元素数据类型 变量名(自定义) : 数组或者Collection集合){

        使用变量即可,该变量就是元素

}

增强for循环的好处:简化遍历

代码举例:

使用增强for循环遍历数组:

public class ForTest { public static void main(String[] args) {// 数组存储数据遍历 int[] arr={11,232,34,55,6};// 使用普通for循环遍历 for(int i=0;i

输出结果:

 使用增强for循环遍历集合:

import java.util.ArrayList;public class ForTest2 { public static void main(String[] args) { // 集合存储对象遍历 ArrayList list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("java"); list.add("bigdata"); list.add("hbase");// 使用普通for循环遍历 for(int i=0;i

如果该集合为null的话,直接使用增强for循环会报错:NullPointerException 空指针异常

所以我们应该在增强for循环之前加入一个判断:判断该集合是否为空即可

import java.util.ArrayList;public class ForTest2 { public static void main(String[] args) { // 集合存储对象遍历 ArrayList list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("java"); list.add("bigdata"); list.add("hbase");// 使用普通for循环遍历 for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } System.out.println("-------增强for循环遍历-------");// 使用增强for循环遍历 for (String s : list) { System.out.println(s); } list = null;//将集合赋值为空 if (list!=null) { for (String s : list) { System.out.println(s); } }else{ System.out.println("该集合为空"); } }}

输出结果:

增强for循环是代替迭代器的,如何验证?

迭代器会出现并发修改异常,若增强for循环也会出现并发修改异常,则说明增强for循环是代替迭代器的。

代码同迭代器中代码类似:

代码实现:在增强for循环中加入equals,若相同,则添加元素hadoop到集合中

for (String s : list) { if("hello".equals(s)){ list.add("hadoop"); } }

输出结果:ConcurrentModificationException 并发修改异常

 说明增强for循环其实就是替代迭代器的

解决办法:迭代器遍历,迭代器修改;集合遍历,集合修改

 这里只给出前一种解决办法:

ListIterator iterator = strings.listIterator(); while (iterator.hasNext()){ String next = iterator.next(); if("java".equals(next)){ iterator.add("spark"); } } System.out.println(strings);

输出结果:

 静态导入

 概述:

格式:import static 包名...类名.方法名;

可以直接导入到方法级别

注意事项:方法必须是静态的

举例:

在调用Math类中的一些方法时我们需要重复的使用Math.方法名();从其它类中调用方法也是如此,这时只需要使用静态导入即可解决。

注意:当本类中的方法与静态导入的方法名冲突时,调用的是本类中的方法

代码如下:

其他类定义:

public class QitaClass { public static void show(String s){ System.out.println(s); } public static void fun(){ System.out.println("这世界很美好!!!"); }}

测试类:

import static java.lang.Math.abs;import static java.lang.Math.pow;import static test.Test.src.com.QitaClass.fun;import static test.Test.src.com.QitaClass.show;public class DaoTest { public static void main(String[] args) {// Math类中的方法 System.out.println(Math.abs(-100)); System.out.println(Math.pow(2, 3));// 使用静态导入改进:导入Math类中需要调用的方法:// import static java.lang.Math.abs;// import static java.lang.Math.pow; System.out.println(abs(-23)); System.out.println(pow(2, 4)); fun();//此时调用的时本类中的fun()方法 show("你好世界");// 若仍然想调用其他类中的fun方法,解决办法为:加上前缀调用 test.Test.src.com.QitaClass.fun(); } public static void fun(){ System.out.println("本类中的fun()方法:this world is very good !!!"); }}

 输出结果:

可变参数

格式:

修饰符 返回值类型 方法名(数据类型..、变量名){}

注意:这里的变量其实是一个数组,如果一个方法有可变参数,并且有多个参数,可变参数一定是最后一个参数。

即若使用可变参数定义方法时,有其他数据类型参与的时候,将可变参数的定义放在最后。

Arrays工具类中的一个方法:public static List asList(T..、a)

该方法其实就是将数组转变为一个集合

代码举例:

import java.util.Arrays;import java.util.List;public class ChangeDemo { public static void main(String[] args) { int a=1; int b=2; int c=4; sum(a,b); sum(a,b,c); sum("赵云",45,20,30);// Arrays工具类中的一个方法 List list = Arrays.asList("这是", "数组", "转变", "集合"); for(String s : list){ System.out.println(s); } }// public static void sum(int a,int b){//求两个数的和// System.out.println(a+b);// }// public static void sum(int a,int b,int c){//求三个数的和// System.out.println(a+b+c);// }// 使用可变参数改进:含有多个参数,将可变参数放在最后 public static void sum(String s,int..、ints){ int sum=0; for (int i : ints){ sum+=i; } System.out.println(s+"的成绩总分为:"+sum); }// 使用可变参数改进求几个数的和 public static void sum(int..、ints){ int sum=0; for(int i : ints){ sum+=i; } System.out.println(ints.length+"个数的和为:"+sum); }}

输出结果:

注意:同一个方法定义中,可变参数只能出现一次:即不会出现如下代码行:

public static void sum(int..、ints,int..、ints2){}

List集合练习

集合的嵌套遍历

目前学校有五年级学生和六年级学生。每个年纪有很多的学生,每个学生都是一个对象,可以使用集合表示一个班级的学生

五年级学生:ArrayList classList5;

六年级学生:ArrayList classList6;

无论是几年级的学生都属于学校的年级,于是学校本身也可以通过集合表示:

ArrayList> school

像这种现象就叫做集合嵌套

 代码举例:

学生类:

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

集合嵌套类:

import java.util.ArrayList;import java.util.Iterator;public class SchoolDemo { public static void main(String[] args) {// 创建学校对象 ArrayList> school = new ArrayList<>();// 创建年纪对象 ArrayList classList5 = new ArrayList(); ArrayList classList6 = new ArrayList<>();// 添加数据到集合 school.add(classList5); school.add(classList6); Student s1 = new Student("诸葛孔明", 15); Student s2 = new Student("司马仲达", 12); Student s3 = new Student("周公瑾", 16); Student s4 = new Student("陈宫", 15); classList5.add(s1); classList5.add(s2); classList5.add(s3); classList5.add(s4); Student s5 = new Student("刘被", 23); Student s6 = new Student("曹操", 24); Student s7 = new Student("孙佺", 23); classList6.add(s5); classList6.add(s6); classList6.add(s7);// 增强for循环嵌套遍历 for(ArrayList sc : school){ for(Student student : sc){ System.out.println(student); } } System.out.println("-----------------------普通for循环遍历---------------------"); for (int i=0;i< school.size();i++){ if (i==0){ System.out.println("===========五年级学生=============="); for (int j =0;j iterator = school.iterator(); while(iterator.hasNext()){ ArrayList sc = iterator.next(); Iterator iterator1 = sc.iterator(); while(iterator1.hasNext()){ Student student = iterator1.next(); System.out.println(student); } } }}

输出结果:增强for循环遍历和迭代器遍历结果相同:

 普通for循环遍历结果:

 获取十个不重复的1到20以内的随机数:

import java.util.ArrayList;import java.util.Random;public class RanTest { public static void main(String[] args) { Random random = new Random(); ArrayList list = new ArrayList<>(); int count=0; while(count<10){ int i = random.nextInt(20)+1; if(!list.contains(i)){ list.add(i); count++; } } System.out.println(list); }}

输出结果(每次都会不同):

 键盘录入多个数据,以0结束,在控制台输出这些数据中的最大值

import java.util.ArrayList;import java.util.Arrays;import java.util.Scanner;public class MaxDemo { public static void main(String[] args) { Scanner sc = new Scanner(System.in); ArrayList list = new ArrayList<>(); boolean flag=true; System.out.println("请录入数据:"); while(flag){ int num = sc.nextInt(); if(num==0){ flag=false; System.out.println("数据录入完成!"); }else{ list.add(num); } } Object[] obj = list.toArray(); Arrays.sort(obj); System.out.println("最大值为:"+obj[obj.length-1]); }}

输出结果:

Set接口

一个不包含重复元素的Collection

Set集合是唯一的,并且元素的顺序是无序的集合

Hashset

底层结构是哈希表(元素是链表的数组)

哈希表依赖于哈希值存储

代码举例:使用Set集合存储字符串并遍历

import java.util.HashSet;import java.util.Set;public class Test1 { public static void main(String[] args) { Set set = new HashSet<>(); set.add("hello"); set.add("world"); set.add("hello"); set.add("bigdata"); set.add("hive"); set.add("java"); set.add("bigdata"); for(String set2 : set){ System.out.println(set2); } }}

输出结果:

 存储自定义对象并遍历

import java.util.HashSet;import java.util.Set;public class Test2 { public static void main(String[] args) { Set set = new HashSet(); Student s1 = new Student("小赵", 18); Student s2 = new Student("小钱", 17); Student s3 = new Student("小孙", 19); Student s4 = new Student("小李", 17); Student s5 = new Student("小赵", 18); set.add(s1); set.add(s2); set.add(s3); set.add(s4); set.add(s5); for (Student students : set) { System.out.println(students); } }}

输出结果:这里应该不包含重复元素,但是结果包含了重复元素:

造成这样的原因需要去源码中查看add方法的实现源码:

经过查看源码发现该HashSet中的add方法与HashCode和equals方法有关,而Student类中没有重写HashCode和equals方法,因此调用的是Object类中的HashCode和equals方法,故比较的是地址值,对于不同的对象含有不同的地址值,因此无法去重。

解决办法:重写HashCode方法和equals方法即可

@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); }

 输出结果:

 linkedHashSet

1、底层数据结构是哈希表和链表

2、哈希表保证元素唯一

3、链表保证了元素的有序(存储和取出的顺序一致)

4、线程不安全,效率高

public class HashSet implements Set

public class linkedHashSet extends HashSet implements Set

代码举例:

import java.util.linkedHashSet;public class Test3 { public static void main(String[] args) { linkedHashSet hashSet = new linkedHashSet<>(); hashSet.add("hello"); hashSet.add("world"); hashSet.add("java"); hashSet.add("bigdata"); hashSet.add("hello"); hashSet.add("world"); hashSet.add("hadoop"); hashSet.add("bigdata");// 遍历 for (String strings : hashSet){ System.out.println(strings); } }}

输出结果:

TreeSet   

元素唯一,元素可按照某种规则进行排序

两种排序方式:自然排序 ;比较器排序

代码举例:

Treeset存储Integer类型的数据并遍历:

import java.util.TreeSet;public class Ttest1 { public static void main(String[] args) { TreeSet treeSet = new TreeSet<>(); treeSet.add(23); treeSet.add(24); treeSet.add(14); treeSet.add(56); treeSet.add(23); treeSet.add(70); treeSet.add(16); treeSet.add(14);// 遍历 for (Integer integer : treeSet){ System.out.println(integer); } }}

 输出结果:

    

自然排序                                                                                                                   

TreeSet存储学生对象并遍历:         

 按照正常的写法,当我们运行的时候,会报错: java.lang.ClassCastException 类转换异常

这是由于在创建TreeSet对象时使用的是无参构造方法,进行的是自然排序,而在这里的底层源码中有向下转型代码:Comparable<? super K> k = (Comparable<? super K>) key;

但是Student类中没有实现Comparable接口,无法向下转型

解决办法:在Student类中实现Comparable接口即可

Student1类:

import java.util.Objects;public class Student1 implements Comparable { private String name; private int age; public Student1() { } public Student1(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 "Student{" + "name='" + name + ''' + ", age=" + age + '}'; } @Override public int compareTo(Student1 o) {// 这里的返回值具体返回什么应该由我们自己来定义 int i = this.age - o.age; int i2 = i == 0 ? this.name.compareTo(o.name) : i; return i2; }}

测试类:

import java.util.TreeSet;public class Ttest2 { public static void main(String[] args) { TreeSet students = new TreeSet(); Student1 s1 = new Student1("云", 19); Student1 s2 = new Student1("赵云", 18); Student1 s3 = new Student1("赵子龙", 20); Student1 s4 = new Student1("常山赵子龙", 21); Student1 s5 = new Student1("赵子龙", 20); students.add(s1); students.add(s2); students.add(s3); students.add(s4); students.add(s5); for (Student1 s : students){ System.out.println(s); } }}

输出结果:这里会发现是按照年龄的大小来进行排序的

需求:使用TreeSet存储学生对象并以姓名的长度来排序

学生类:

import java.util.Objects;public class Student3 implements Comparable { private String name; private int age; public Student3() { } public Student3(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(Student3 o) {// 比较姓名的长度是否相等 int i = this.name.length() - o.name.length();// 若相等,比较姓名的内容是否相同 int i2 = i == 0 ? this.name.compareTo(o.name) : i;// 若相同,比较年龄是否相等 int i3 = i2 == 0 ? this.age - o.age : i2; return i3; } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; }}

测试类:

import java.util.TreeSet;public class Ttest3 { public static void main(String[] args) { TreeSet students3 = new TreeSet<>(); Student3 s1 = new Student3("yun", 19); Student3 s2 = new Student3("zhaoyun", 18); Student3 s3 = new Student3("zhaozilon", 20); Student3 s4 = new Student3("changshanzhaozilon", 21); Student3 s5 = new Student3("yun", 22); students3.add(s1); students3.add(s2); students3.add(s3); students3.add(s4); students3.add(s5); for(Student3 sc : students3){ System.out.println(sc); } }}

输出结果:

比较器排序

利用TreeSet创建对象时的带参数的构造方法来进行比较器排序

TreeSet(Comparator<? super E> comparator)

构造一个新的,空的数集,根据指定的比较器进行排序

这里需要实现Comparator接口

代码举例:

实现Comparator接口的实现类ComparatorImpl:

import java.util.Comparator;public class ComparatorImpl implements Comparator { @Override public int compare(Student4 o1, Student4 o2) {// 比较姓名的长度 int i = o1.getName().length() - o2.getName().length();// 比较姓名的内容 int i2 = i == 0 ? o1.getName().compareTo(o2.getName()) : i;// 比较年龄的大小 int i3 = i2 == 0 ? o1.getAge() - o2.getAge() : i2; return i3; }}

这里是具体的测试类 

import java.util.TreeSet;public class Ttest4 { public static void main(String[] args) { ComparatorImpl comparator = new ComparatorImpl(); TreeSet student4s = new TreeSet<>(comparator);//使用带参构造// 创建学生对象 Student4 s1 = new Student4("yun", 19); Student4 s2 = new Student4("zhaoyun", 18); Student4 s3 = new Student4("zhaozilon", 21); Student4 s4 = new Student4("chanshangzhaozilon", 20); Student4 s5 = new Student4("chanshangzhaoyun", 20); Student4 s6 = new Student4("zhaoyun", 22); Student4 s7 = new Student4("yun", 19);// 添加到集合 student4s.add(s1); student4s.add(s2); student4s.add(s3); student4s.add(s4); student4s.add(s5); student4s.add(s6); student4s.add(s7);// 遍历 for(Student4 student4 : student4s){ System.out.println(student4); } }}

 输出结果:

这里还可以使用匿名内部类进行comparator接口的实现:

import java.util.Comparator;import java.util.TreeSet;public class Ttest4 { public static void main(String[] args) {// ComparatorImpl comparator = new ComparatorImpl();// TreeSet student4s = new TreeSet<>(comparator);// 使用匿名内部类改进: TreeSet student4s = new TreeSet<>(new Comparator() { @Override public int compare(Student4 o1, Student4 o2) {// 比较姓名的长度 int i = o1.getName().length() - o2.getName().length();// 比较姓名的内容 int i2 = i == 0 ? o1.getName().compareTo(o2.getName()) : i;// 比较年龄的大小 int i3 = i2 == 0 ? o1.getAge() - o2.getAge() : i2; return i3; } }); Student4 s1 = new Student4("yun", 19); Student4 s2 = new Student4("zhaoyun", 18); Student4 s3 = new Student4("zhaozilon", 21); Student4 s4 = new Student4("chanshangzhaozilon", 20); Student4 s5 = new Student4("chanshangzhaoyun", 20); Student4 s6 = new Student4("zhaoyun", 22); Student4 s7 = new Student4("yun", 19); student4s.add(s1); student4s.add(s2); student4s.add(s3); student4s.add(s4); student4s.add(s5); student4s.add(s6); student4s.add(s7); for(Student4 student4 : student4s){ System.out.println(student4); } }}

 输出结果不变

通过查看逻辑来看,这两种排序实现的其实就是按照树的中序遍历方式进行排序的

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。