目录
一、泛型
之前说过集合可以存放不同引用数据类型的数据,当集合中既含有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、泛型概述
把明确数据类型的工作,提前到了编译时期,在创建集合的时候明确存储元素的数据类型,有些类似于把数据当作参数一样传递,所以又称之为:参数化类型
泛型语句定义格式:<引用数据类型> 注意:尖括号中只能是引用数据类型
(1)泛型的优点:
1、将我们运行时期出现的问题提前到了编译时期
2、不需要再做强制类型转换
3、优化了代码。消除了不必要的黄色警告线,使代码看起来更加整洁
通过观察API发现:泛型可以出现在类,接口,方法上,类似于<E>都叫泛型,一般情况泛型出现在大多使用的集合中
加入泛型后的代码:
import java.util.ArrayList;
import java.util.Iterator;
public class FanXinTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("bigdata");
Iterator<String> iterator = list.iterator();
// 遍历
while(iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}
}
}
2、泛型高级符号
泛型通配符<?>
任意类型,如果没有明确,那就是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<Animal> animals1 = new ArrayList<>();
ArrayList<Dog> dogs1 = new ArrayList<>();
ArrayList<Cat> cats1 = new ArrayList<>();
// 任意调用,没有明确就是Object以及其他的Java类
ArrayList<?> animals2 = new ArrayList<Animal>();
ArrayList<?> dogs2 = new ArrayList<Dog>();
ArrayList<?> cats2 = new ArrayList<Cat>();
ArrayList<?> objects1=new ArrayList<Object>();
// 向下限定
ArrayList<? extends Animal > animals3 =new ArrayList<Animal>();
ArrayList<? extends Animal> cats3=new ArrayList<Cat>();
ArrayList<? extends Animal> dogs3=new ArrayList<Dog>();
// ArrayList<? extends Animal> objects2=new ArrayList<Object>();
// 向上限定
ArrayList<? super Animal> cats4 =new ArrayList<Animal>();
ArrayList<? super Animal> dogs4 =new ArrayList<Object>();
}
}
3、泛型的定义位置
(1)泛型类
就是把泛型定义在类上
定义格式:
public class 类名<泛型类型1,...>
注意:这里的泛型类型可以不止一个,但是泛型类型必须是引用数据类型,这里<>中的内容仅仅表示的是一种参数数据类型,参数类型是一种变量,该变量要符合变量的命名规则,可以是任意符合标识符规则的名字
一般情况下在定义时习惯使用一个大写字母表示。
举例:
//这里是自定义一个泛型类 public class GenericDemo1<T> { 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<String> demo1 = new GenericDemo1<>(); demo1.setObj("hello"); String obj = demo1.getObj(); System.out.println(obj); } }
输出结果:
(2)泛型方法
就是把泛型定义在方法上
定义格式:public <泛型类型> 返回类型 方法名(泛型类型 形参)
代码举例:
由于将来不知道需要传入什么类型的数据,因此定义泛型方法
public class Gdemo2 { // 泛型方法 public <R> 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); } }
输出结果:
(3)泛型接口
就是把泛型定义在接口上
定义格式:public interface 接口名<泛型类型1,...>
代码举例:
public interface Gdemo3<W> { void show(W w); }
该接口的实现类
public class Gdemo3Impl<W> implements Gdemo3<W>{ @Override public void show(W w) { System.out.println(w); } }
测试类
public class Gtest3{ public static void main(String[] args) { Gdemo3Impl<String> gdemo3 = new Gdemo3Impl<String>(); gdemo3.show("world"); // gdemo3.show(20);已经限定为String类型,不可以使用其他类型 } }
输出结果:
二、增强For循环
总结:
JDK1.5之后出现的新特性:泛型,增强for循环,包装类,Scanner,枚举
1、增强for循环概述
增强for循环就是简化数组和Collection集合的遍历
语句定义格式:
for(元素数据类型 变量名(自定义) : 数组或者Collection集合){
使用变量即可,该变量就是元素
}
增强for循环的好处:简化遍历
代码举例:
(1)使用增强for循环遍历数组:
public class ForTest {
public static void main(String[] args) {
// 数组存储数据遍历
int[] arr={11,232,34,55,6};
// 使用普通for循环遍历
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
// 使用增强for循环遍历
System.out.println("-------增强for循环遍历-------");
for (int num : arr){
System.out.println(num);
}
}
}
输出结果:
(2)使用增强for循环遍历集合:
import java.util.ArrayList;
public class ForTest2 {
public static void main(String[] args) {
// 集合存储对象遍历
ArrayList<String> 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);
}
}
}
如果该集合为null的话,直接使用增强for循环会报错:NullPointerException 空指针异常
所以我们应该在增强for循环之前加入一个判断:判断该集合是否为空即可
import java.util.ArrayList;
public class ForTest2 {
public static void main(String[] args) {
// 集合存储对象遍历
ArrayList<String> 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<String> 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 <T> List<T> 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<String> 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<Student> classList5;
六年级学生:ArrayList<Student> classList6;
无论是几年级的学生都属于学校的年级,于是学校本身也可以通过集合表示:
ArrayList<ArrayList<Student>> 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<ArrayList<Student>> school = new ArrayList<>();
// 创建年纪对象
ArrayList<Student> classList5 = new ArrayList<Student>();
ArrayList<Student> 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<Student> 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<school.get(i).size();j++){
System.out.println(school.get(i).get(j));
}
}
else if(i==1){
System.out.println("===========六年级学生==============");
for(int j=0;j<school.get(i).size();j++){
System.out.println(school.get(i).get(j));
}
}
}
System.out.println("----------------------迭代器遍历--------------------------");
Iterator<ArrayList<Student>> iterator = school.iterator();
while(iterator.hasNext()){
ArrayList<Student> sc = iterator.next();
Iterator<Student> iterator1 = sc.iterator();
while(iterator1.hasNext()){
Student student = iterator1.next();
System.out.println(student);
}
}
}
}
输出结果:增强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<Integer> 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<Integer> 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集合是唯一的,并且元素的顺序是无序的集合
1、Hashset
底层结构是哈希表(元素是链表的数组)
哈希表依赖于哈希值存储
代码举例:使用Set集合存储字符串并遍历
import java.util.HashSet;
import java.util.Set;
public class Test1 {
public static void main(String[] args) {
Set<String> 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<Student> set = new HashSet<Student>();
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);
}
输出结果:
2、 LinkedHashSet
1、底层数据结构是哈希表和链表
2、哈希表保证元素唯一
3、链表保证了元素的有序(存储和取出的顺序一致)
4、线程不安全,效率高
public class HashSet<E> implements Set<E>
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>
代码举例:
import java.util.LinkedHashSet;
public class Test3 {
public static void main(String[] args) {
LinkedHashSet<String> 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);
}
}
}
输出结果:
3、TreeSet
元素唯一,元素可按照某种规则进行排序
两种排序方式:自然排序 ;比较器排序
代码举例:
Treeset存储Integer类型的数据并遍历:
import java.util.TreeSet;
public class Ttest1 {
public static void main(String[] args) {
TreeSet<Integer> 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);
}
}
}
输出结果:
(1)自然排序
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<Student1> {
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<Student1> students = new TreeSet<Student1>();
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<Student3> {
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<Student3> 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);
}
}
}
输出结果:
(2)比较器排序
利用TreeSet创建对象时的带参数的构造方法来进行比较器排序
TreeSet(Comparator<? super E> comparator)
构造一个新的,空的数集,根据指定的比较器进行排序
这里需要实现Comparator接口
代码举例:
import java.util.TreeSet;
public class Ttest4 {
public static void main(String[] args) {
ComparatorImpl comparator = new ComparatorImpl();
TreeSet<Student4> 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<Student4> student4s = new TreeSet<>(comparator);
// 使用匿名内部类改进:
TreeSet<Student4> student4s = new TreeSet<>(new Comparator<Student4>() {
@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);
}
}
}
输出结果不变
通过查看逻辑来看,这两种排序实现的其实就是按照树的中序遍历方式进行排序的