Java笔记day24
一、先定义几个Student供使用
import java.util.Objects;
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 +
'}';
}
@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);
}
}
Student2相比与Student多重写了一个hashcode
(可以与equals一起生成)
@Override
public int hashCode() {
return Objects.hash(name, age);
}
Student3(七——3、TreeSet)后面使用的时候再说
二、泛型
1、泛型的引入
1)、举例
import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo1 {
public static void main(String[] args) {
//创建List集合对象
ArrayList list = new ArrayList();
list.add("hello");
list.add(10); //向上转型 10 -- int -- Integer
list.add("world");
list.add("java");
list.add("bigdata");
//获取迭代器对象
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
String s = (String)next;
System.out.println(s);
/*这里的编译是没有错的,但是在运行的时候会报错
ClassCastException:类型转换异常
按照正常的写法,在集合中添加一些不是同类型的数据,在遍历的时候向下转型,但是在这里报错了。
为什么呢?
因为在存储数据的时候,存储了String和Integer类型的数据,但是遍历的时候,
默认集合中只存放String类型的数据,但是在存储数据的时候有没有提示说不能添加Integer类型
的数据,如果能在存储的时候告诉我能存储哪些数据就能解决这个问题。
在数组中数据类型只能是一样的,所以Java的集合模仿数组在创建集合的时候就明确了元素的数据类型
创建后,再往集合中加入的元素,只能是定义好的数据类型相同的数据了,然后再向下转型就没有问题了。
Java中这样的技术叫做:泛型。
*/
}
}
}
2)、泛型:
把明确数据类型的工作,提前到了编译时期,在创建集合的时候明确数据类型,
这样的做法有点像把数据类型当作参数一样进行传递。所以泛型还有一个名字,叫做:参数化类型。
定义格式:
<引用数据类型>
注意:尖括号中的数据类型只能是引用数据类型
通过观察API发现,泛型可以出现在类,接口,方法上,看到一些类似与<E>,一般来说泛型出现在大多使用集合中。
import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo1 {
public static void main(String[] args) {
//创建List集合对象
//JDK1.7之后会自动进行类型推断
ArrayList<String> list = new ArrayList<Strign>();
list.add("hello");
//list.add(10); 当这里再次输入时,会直接报错
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 + "--" + next.length());
//做了泛型之后数据类型都一样就不需要强转了,可以直接输出
}
}
}
2、泛型的好处:
1、将我们之前运行时候出现的问题,提前到了编译时期
2、不需要强制类型转换了
3、优化了代码,消除不必要的黄色警告线
3、泛型类
1)、泛型类概述
定义:把泛型定义在类上的类
格式:public class 类名<泛型类型1,…>
注意:泛型类型必须是引用类型
这里的<>里面的内容仅仅表示的是一种参数数据类型,参数类型是一种变量,
既然是一种变量,就符合变量的命名规则,可以是任意符合标识符起名规则的名字。
public class GenericTool1<T> {
//T表示任意的引用数据类型,且并不一定所有的数据类型都一定要是T,也可以是其他的数据类型
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
2)、泛型类测试
public class GenericTest1 {
public static void main(String[] args) {
//不加泛型,默认是Object类型
//GenericTool1 gt1 = new GenericTool1();
//gt1.setObj("hello");
//gt1.setObj(20);
//gt1.setObj(12.34);
//gt1.setObj(new Student());
GenericTool1<String> gt2 = new GenericTool1<>();
gt2.setObj("hello");
//gt2.setObj(20); 此处因为数据类型不同所以会报错
String obj = gt2.getObj();
}
}
4、泛型方法
把泛型定义在方法上
格式:public <泛型类型> 返回类型 方法名(泛型类型 .)
泛型方法的使用
案例一:类名上有泛型,方法上没有
class GenericTool2<T> {
public void show(T t){
System.out.println(t);
}
}
public class GenericTest2 {
public static void main(String[] args) {
GenericTool2<String> gt2 = new GenericTool2<>();
gt2.show("hello");
//gt2.show(20); 已经定义了String泛型,所以不能再输入Integer类型数据
GenericTool2<Integer> gt3 = new GenericTool2<>();
gt3.show(20);
}
}
如果方法的参数类型和类的参数类型不一致,也就是说类上没有泛型,方法还能不能传参呢
注:如果类和方法都有泛型且泛型不一样也是可以的
案例二:类名上没有泛型,方法上有
class GenericTool2 {
public <T> void show(T t){
System.out.println(t);
}
}
public class GenericTest2 {
public static void main(String[] args) {
GenericTool2 gt = new GenericTool2();
gt.show("hello");
gt.show(20);
gt.show(true);
}
}
5、泛型接口
直接举例解释
interface GenericTool3<T> {
public abstract void show(T t);
}
class GenericTool3Impl<T> implements GenericTool3<T>{
@Override
public void show(T t) {
System.out.println(t);
}
}
public class GenericTest3 {
public static void main(String[] args) {
GenericTool3Impl<String> sgt1 = new GenericTool3Impl<>();
sgt1.show("hello"); //此处是不可以输入其他类型的数据类型的
}
}
5、泛型的高级用法
泛型通配符<?>
任意类型,如果没有明确,那么就是Object以及任意的Java类了
? extends E
向下限定,E及其子类
? super E
向上限定,E及其父类
开发时用的少主要是为了能看懂API和别的程序
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
import java.util.ArrayList;
public class GenericDemo2 {
public static void main(String[] args) {
//如果泛型里面的类型只用一个,并且明确数据类型的时候,前后必须要写一致
//不能出现不一致的情况
ArrayList<Animal> list1 = new ArrayList<Animal>();
ArrayList<Dog> list2 = new ArrayList<Dog>();
ArrayList<Object> list3 = new ArrayList<Object>();
//泛型通配符<?>
//任意类型,如果没有明确,那么就是Object以及任意的Java类了
ArrayList<?> objects1 = new ArrayList<Animal>();
ArrayList<?> objects2 = new ArrayList<Dog>();
ArrayList<?> objects3 = new ArrayList<Object>();
//? extends E 向下限定,E及其子类
ArrayList<? extends Animal> list4 = new ArrayList<Animal>();
ArrayList<? extends Animal> list5 = new ArrayList<Dog>();
ArrayList<? extends Animal> list6 = new ArrayList<Cat>();
//ArrayList<? extends Animal> list7 = new ArrayList<Object>(); 只能是本身或者子类,不能是Object
//? super E 向上限定,E及其父类
ArrayList<? super Animal> list7 = new ArrayList<Animal>();
ArrayList<? super Animal> list8 = new ArrayList<Object>();
//ArrayList<? super Animal> list9 = new ArrayList<Dog>(); 只能是父类,不能是子类
}
}
三、增强for()循环
1、到目前为止,学过哪些特性是JDK1.5之后出现的:
泛型,增强for,包装类,Scanner,枚举
2、增强for概述
简化数组和Collection集合的遍历
3、格式
for(元素数据类型 变量名(自定义) : 数组或者Collection集合) {
使用变量即可,该变量就是元素
}
4、好处:简化遍历
5、注意事项
1)、增强for的目标要判断是否为null
2)、把前面的集合代码的遍历用增强for改进
3)、 将来能用增强for的时候,就用,可以消除黄色警告线。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
public class ForDemo1 {
public static void main(String[] args) {
//定义一个数组
int[] arr = {1,2,3,4,5,6};
//普通for循环
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
System.out.println("使用增强for循环遍历:");
for(int x : arr){
System.out.println(x);
}
System.out.println("=================================");
ArrayList<String> strings = new ArrayList<>();
strings.add("hello");
strings.add("world");
strings.add("java");
strings.add("bigdata");
strings.add("hadoop");
for (String string : strings) {
System.out.println(string);
}
//strings = null;
//NullPointerException 编译不报错,但是运行的时候报错,会报空指针异常错误
//for(String s : strings){
//System.out.println(s);
//}
//所以我们在遍历之前需要判断一下是不是为null
if(strings!=null){
for (String s : strings){
System.out.println(s);
}
}
//一个迭代器使用完使用完之后,其指针会指向最后一个元素,所以如果想再次使用
//就要重新创建一个迭代器去迭代,而for循环不用重新创建,使用完了就可以重新使用
//增强for循环其实就是用来替代迭代器的
//如何验证呢?
//迭代器和for()循环在使用过程中如果去对集合进行添加操作都会出现并发修改异常验证
/*
for(String s:strings){
if("world".equals(s)){
strings.add("spark");
}
}
//跟迭代器一样,这里也会有并发修改异常
//ConcurrentModificationException
//要想一边遍历一边添加元素只能使用ListIterator迭代器
//用iterator.add()添加元素
ListIterator<String> iterator = strings.listIterator();
while (iterator.hasNext()){
String next = iterator.next();
if("world".equals(next)){
//strings.add("spark");
iterator.add("spark");
}
}
*/
}
}
四、静态导入概述
1、静态导入概述
格式:import static 包名….类名.方法名;
可以直接导入到方法的级别
2、注意事项
方法必须是静态的,如果有多个同名的静态方法,容易不知道使用谁?
这个时候要使用,必须加前缀。
由此可见,意义不大,所以一般不用,但是要能看懂。
import static java.lang.Math.abs;
import static java.lang.Math.pow;
//这里的abs和pow可以用*号代替,表示可以使用math中的所有方法
//但是这样使用会导致Java项目导出时Jar包很大,所以一般不用
import static com...(这里是自己写的类的路径.方法).fun;
import static com...(这里是自己写的类的路径.方法).show;
//注意这里的方法没有()
public class StaticImportDemo {
public static void main(String[] args) {
//不使用静态导入
//System.out.println(Math.abs(-100));
//System.out.println(Math.pow(2, 3));
//System.out.println(Math.max(200, 300));
//使用静态导入
System.out.println(abs(-100));
System.out.println(pow(2,5));
System.out.println(StaticClass.fun());
System.out.println(fun());
//当静态导入的方法名与本类中的方法名冲突了,
//调用的是本类中的方法
show("flink");
//如果想使用静态导入的方法怎么办?
//将前缀路径写完整
com....(类的路径).show("flink");
StaticClass.show("spark");
}
public static void show(String s) {
System.out.println("这是在当前类中的show方法" + s);
}
}
五、可变参数
1、举例:几个数相加,不使用可变参数
public class ArgsDemo {
public static void main(String[] args) {
//求两个数之和
int a = 10;
int b = 20;
//System.out.println(a+b);
sum(a, b);
//求三个数之和
int c = 30;
sum(a, b, c);
//求四个数之和
int d = 40;
sum(a, b, c, d);
}
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(int a, int b, int c, int d) {
System.out.println(a + b + c + d);
}
}
2、根据上面的案例发现,方法名一样,参数列表中的数据类型一样,只是个数不一样
这时候,每增加一个参数,方法就要新写一个,非常麻烦。
那该怎么解决这个问题呢?
java提供了一个技术叫做:可变参数
概述:
定义方法的时候,参数不确定的时候使用
格式:
修饰符 返回值类型 方法名(数据类型... 变量名){}
注意:
这里的变量其实是一个数组
如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个
Arrays工具类中的一个方法
public static <T> List<T> asList(T... a)
import java.util.Arrays;
import java.util.List;
public class ArgsDemo {
public static void main(String[] args) {
sum(312,123,1,312,13,13,13,13,13,1,34,5,4,3,131,3);
List<String> strings = Arrays.asList("hello", "world", "java", "bigdata");
for(String s : strings){
System.out.println(s);
}
//这是上面的工具类,但是要求数据类型是一样的,返回的是List类型
sum("你好",3123,21,2,1,211);
}
//使用可变参数的形式定义加法的方法
public static void sum(int... ints){
int sum = 0;
//System.out.println(ints); 这里输出的是地址值
for(int i=0;i<ints.length;i++){
sum = sum + ints[i];
}
System.out.println(sum);
}
//当方法定义的时候既有固定值,也有可变参数的数的时候,将可变参数的定义放在最后一个
//这里就相当于字符串拼接了,但是由于不知道可变参数的个数,Java也不能自己判断,所以
//可变参数要放在后面
public static void sum(String s,int... ints){
System.out.println(s);
int sum = 0;
//System.out.println(ints);
for(int i=0;i<ints.length;i++){
sum = sum + ints[i];
}
System.out.println(sum);
}
}
六、List集合练习
1、集合的嵌套练习:
需求:目前,振华高中有十三班和十四班两个班,十四班有很多学生,每个学生都是一个学生对象,
可以用一个集合表示一个班有45个人
十四班的学生:ArrayList<Student> classList14
十三班也可以用集合表示:
十三班的学生:ArrayList<Student> classList13
无论是十三班还是十四班也好,都是振华的班级。
这个高中也可以用集合表示:ArrayList<ArrayList<Student>> zhenhua
这样的现象叫做集合的嵌套。
import java.util.ArrayList;
import java.util.Iterator;
public class ListQianTaoDemo {
public static void main(String[] args) {
//定义一个十四班班级的集合
ArrayList<Student> classList14 = new ArrayList<>();
//定义一个十三班班级的集合
ArrayList<Student> classList13 = new ArrayList<>();
//定义一个振华高中集合
ArrayList<ArrayList<Student>> zhenhua = new ArrayList<>();
//将十四班和十三班放到数加学院中
shujia.add(classList14);
shujia.add(classList13);
//创建十四班的学生对象
//这里的Student用的是一开始给的Student类
Student s1 = new Student("余淮", 16);
Student s2 = new Student("耿耿", 16);
Student s3 = new Student("小鹿", 17);
Student s4 = new Student("灵儿", 20);
Student s5 = new Student("琪琪", 21);
//将学生对象添加到十四班集合中
classList14.add(s1);
classList14.add(s2);
classList14.add(s3);
classList14.add(s4);
classList14.add(s5);
//创建十三班的学生对象
Student s11 = new Student("张三", 18);
Student s22 = new Student("王舞", 19);
Student s33 = new Student("李斯", 17);
Student s44 = new Student("杨明", 20);
Student s55 = new Student("陈响", 17);
//将学生对象添加到十三班集合中
classList13.add(s11);
classList13.add(s22);
classList13.add(s33);
classList13.add(s44);
classList13.add(s55);
classList14.add(s55);
//遍历
//增强for循环遍历
for(ArrayList<Student> clazz : zhenhua){
for(Student s : clazz){
System.out.println(s);
}
}
//普通for循环遍历
for(int i=0;i<zhenhua.size();i++){
if(i==0){
System.out.println("==========十四期:==========");
for(int j=0;j<zhenhua.get(i).size();j++){
Student student = zhenhua.get(i).get(j);
System.out.println(student);
}
}else if(i==1){
System.out.println("==========十三期:==========");
for(int j=0;j<zhenhua.get(i).size();j++){
Student student = zhenhua.get(i).get(j);
System.out.println(student);
}
}
}
//迭代器遍历
Iterator<ArrayList<Student>> zhenhuaIter = zhenhua.iterator();
while (zhenhuaIter.hasNext()){
ArrayList<Student> clazz = zhenhuaIter.next();
Iterator<Student> clazzIter = clazz.iterator();
while (clazzIter.hasNext()){
Student student = clazzIter.next();
System.out.println(student);
}
}
}
}
2、获取10个1-20之间的随机数,要求不能重复
数组可以实现吗?不可以,因为要求不能重复,所以会涉及额到删减,
长度不好确定,所以选择集合
Random类:public int nextInt(int bound) :左闭右开
import java.util.ArrayList;
import java.util.Random;
public class RandomTest {
public static void main(String[] args) {
//1、创建随机数对象
Random random = new Random();
//2、创建集合对象存储随机数
ArrayList<Integer> arr = new ArrayList<>();
//3、定义一个变量统计集合中是否有10个元素
int count = 0;
while (count<10){
//4、产生随机数(生成的数有点类似与正态分布)
int i = random.nextInt(20) + 1;
//5、判断集合中是否有该随机数
if(!arr.contains(i)){
arr.add(i);
count++;
}
}
System.out.println(arr);
}
}
3、键盘录入多个数据,以0结束,要求在控制台输出这多个数据中的最大值
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
public class ArrayListTest1 {
public static void main(String[] args) {
//创建Scanner对象
Scanner sc = new Scanner(System.in);
//创建集合存储输入的数据
ArrayList<Integer> arr = new ArrayList<>();
while (true){
int number = sc.nextInt();
if(number==0){
break;
}else {
arr.add(number);
}
}
//Arrays工具类中有一个方法sort()
//集合转数组
Object[] objects = arr.toArray();
Arrays.sort(objects);
//最后一个是最大值
Integer maxNumber = (Integer)objects[objects.length - 1];
Integer minNumber = (Integer)objects[0];
System.out.println("最小值为:"+minNumber);
System.out.println("最大值为:"+maxNumber);
}
}
七、set接口概述
基本方法和前面类似
Set集合:元素唯一且元素无序(存储和取出顺序不一致)的集合
Set的三个实现类:HashSet,LinkedHashSet,TreeSet
Set集合中的元素为什么不会重复?看源码
源码过于复杂此处略,
HashSet
主要是计算数据的hash值,然后比较,一样就不插入,不一样就插入
底层数据结构是哈希表,依赖与哈希值存储,添加功能依靠int hashCode()和
boolean equals(Object obj)两个方法
1、HashSet
//不保证set的迭代顺序
//特别是它不保证该顺序恒久不变
import java.util.HashSet;
public class SetDemo1 {
public static void main(String[] args) {
HashSet<String> arr = new HashSet<>();
//添加元素到集合
arr.add("hello");
arr.add("world");
arr.add("java");
arr.add("bigdata");
arr.add("hadoop");
arr.add("hello");
arr.add("hello");
arr.add("java");
arr.add("spark");
arr.add("flink");
arr.add("world");
arr.add("hadoop");
for(String s : arr){
System.out.println(s);
}
//存储自定义对象
//创建集合对象
HashSet<Student2> hashSet = new HashSet<>();
//创建学生对象
Student2 s1 = new Student2("xiaowang", 18);
Student2 s2 = new Student2("xiaowang", 18);
Student2 s3 = new Student2("xiaoli", 19);
Student2 s4 = new Student2("xiaoliu", 20);
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(s4);
for(Student2 s:hashSet){
System.out.println(s); //会自动去重
}
}
}
2、LinkedHashSet
public class HashSet<E> implements Set<E>{}
public class LinkedHashSet<E> extends HashSet<E>{}
LinkedHashSet:底层数据结构是哈希表和双向链表(元素有序唯一)
哈希表保证了元素唯一
链表保证了元素的有序(存储和取出顺序一致)
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.TreeSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
LinkedHashSet<String> arr = new LinkedHashSet<>();
TreeSet<String> strings = new TreeSet<>();
//添加元素到集合
arr.add("hello");
arr.add("world");
arr.add("java");
arr.add("bigdata");
arr.add("hadoop");
arr.add("hello");
arr.add("hello");
arr.add("java");
arr.add("spark");
arr.add("flink");
arr.add("world");
arr.add("hadoop");
for (String s : arr){
System.out.println(s);
}
//输出顺序和输入是一样的,且唯一
}
}
3、TreeSet
TreeSet:元素唯一,元素的顺序可以按照某种规则进行排序
底层是红黑树
两种排序方式:
自然排序(natural ordering)
比较器排序(Comparator)
A NavigableSet实现基于TreeMap 。
的元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。
要想知道如何去重以及排序,就去看源码。
类似与树插入数据,大的值插在左边,小的值插在右边,(利用的是CompareTo比较)
然后输出的时候遍历树
例1、直接输入数据
import java.util.TreeSet;
public class TreeSetDemo1 {
public static void main(String[] args) {
//创建一个集合对象
TreeSet<Integer> ts = new TreeSet<>();
//添加元素到集合中
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(24);
ts.add(66);
ts.add(12);
ts.add(18);
ts.add(20);
ts.add(23);
ts.add(2);
//遍历
for(Integer i : ts){
System.out.println(i);
}
//去重,且按照从小到大的顺序输出
}
}
例2、TreeSet存储学生对象并遍历
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student3> ts = new TreeSet<>();
//创建学生对象
Student3 s1 = new Student3("周姐",24);
Student3 s2 = new Student3("李元浩",25);
Student3 s3 = new Student3("李湘赫",22);
Student3 s4 = new Student3("汉子哥",26);
Student3 s5 = new Student3("硬币哥",21);
Student3 s6 = new Student3("乌兹",20);
Student3 s7 = new Student3("李元浩",25);
Student3 s8 = new Student3("厂长",25);
//将学生对象插入到集合中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
for (Student3 s:ts){
System.out.println(s);
}
/*
按照正常的写法,我们一运行就报错了
java.lang.ClassCastException:类型转换异常
由于我这里创建TreeSet对象调用的是无参构造方法,所以走的是自然排序
而底层源码有一步向下转型
Comparable<? super K> k = (Comparable<? super K>) key;报错了
原因是我们Student3类没有实现Comparable接口,无法向下转型,所以报错了。
也就是说转型排序的问题没有处理好
*/
}
}
******************************************************************
Student3概述(Student3与Student1一样,但是要实现CompareTo接口和多了一个重写CompareTo方法)
public class Student3 implements Comparable<Student3> {
//实现接口要重写该接口的所有方法,也就是重写compareTo方法
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 String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student3 o) {
//return 0; 只有第一行周姐的数据
//return 1; 并不能去重,还是有两个李元浩
//return -1; 反向输出
//这里返回什么,其实应该根据我们的规则来排序
//比如我想在去重的前提下,按照年龄进行排序
//return this.age - o.age; 就会去重年龄一样的数据
//年龄一样,姓名不一定一样
//主要条件(题目要求的条件)
int i = this.age - o.age;
//隐含条件(需要自己挖掘)
//三目运算符给i2赋值,相同比较名字是否相同,不同直接返回i
int i2 = i == 0 ? this.name.compareTo(o.name) : i;
return i2;
}
}

浙公网安备 33010602011771号