day01-static-代码块-继承-权限修饰符-final
01-static关键字的特点
-
static 关键字的介绍
- static 是静态的意思,可以修饰成员变量,成员方法。被static 修饰的成员在内存中只存储一份,可以被共享,修改
-
特点
- 被类的所有对象共享
- 可以通过类名,直接调用,例如: Student.school
- 随着类的加载而加载,优先于对象存在
-
public class StudentTest { public static void main(String[] args) { Student.school = "黑马程序员";//随着类的加载而加载,优先于对象存在.....直接调用,例如: Student.school Student stu1 = new Student(); stu1.name = "张三"; stu1.age =18; //stu1.school = "黑马程序员"; Student stu2 = new Student(); stu2.name = "李四"; stu2.age =24; //stu2.school = "传智剥壳";//被类的所有对象共享 Student stu3 = new Student(); stu3.name = "王五"; stu3.age =25; //stu3.school = "传智剥壳";//被类的所有对象共享 stu1.show(); stu2.show(); stu3.show(); } } //创建学生类 class Student{ String name; int age; static String school; public void show(){ System.out.println(name + "-----" + age + "-----" + school); } }
02-成员变量的分类
-
静态成员变量(有static修饰,属于类,内存中只加载一次):常表示如在线人数信息、等需要被共享的信息,可以被共享访问。
静态成员变量的访问方式
- 推荐:类名.静态成员变量
- 不推荐:对象名.静态成员变量 assets\images
-
实例成员变量(无static修饰,存在于每一个对象中):常表示姓名、年龄、等,属于每一个对象的信息
03-static内存图解
04-static修饰成员方法的调用方式
-
成员方法的分类
- 静态成员方法(有static修饰,属于类),建议使用类名访问,也可以使用对象访问
- 实例成员方法(无static修饰,属于对象),只能用对象触发访问
-
public class UserTest { public static void main(String[] args) { //调用静态成员方法.通过类名调用 UserTest.method(); //实例成员方法,通过对象调用 // 1、 创建对象 UserTest user = new UserTest(); // 2、 对象.调用 //UserTest.show();编译报错 user.show(); } public static void method(){ System.out.println("static修饰的成员方法,即静态成员方法,建议使用类名调用"); } public void show(){ System.out.println("无static修饰的成员方法,即实例成员方法,只能通过对象名调用"); } }
05-static修饰成员方法的思路
-
使用场景
- 方法中,必须要用到非静态的成员变量,则定义为非静态方法
- 方法要实现的功能,如果可以不用成员变量,则可以定义为静态方法(一般体现在工具类中)
- 静态成员方法只能使用静态成员变量
-
06-数组工具类【模拟】
-
工具类:工具类存在的价值,只是为了给其他类提供服务的
-
如果该类中所有的方法,都是static修饰的,通常会多做一步,私有构造方法,
目的,为了避免他人创建对象,避免使用对象调用静态方法
-
public class ArraysTools { private ArraysTools(){} //打印数组[10,20,30,40,50,60] public static void printArray(int[] arr){ System.out.print("["); for (int i = 0; i < arr.length; i++) { if (i == arr.length - 1){ System.out.println(arr[i] + "]"); }else { System.out.print(arr[i] + ", "); } } } //数组求和 public static int getSum(int[] arr){ int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } //求数组中的最大值 public static int getMax(int[] arr){ int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max){ max = arr[i]; } } return max; } //求数组中的最小值 public static int getMin(int[] arr){ int min = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] < min){ min = arr[i]; } } return min; } //求数组的平均值 public static double getAvg(int[] arr){ double avg = (getSum(arr) - getMax(arr) - getMax(arr))*1.0 / (arr.length - 2); return avg; } }
07-static注意事项
- 静态方法只能访问静态的成员,不可以直接访问实例成员
- 实例方法可以访问静态的成员,也可以访问实例成员
- 静态方法中是不可以出现 this 关键字(this指向当前对象,谁调用就指向谁,先创建对象,才有this)
08-代码块
-
在java类下,使用{ } 括起来的代码被称为代码块
-
代码块的分类
-
局部代码块
位置:方法中定义
作用:限定变量的生命周期,及早释放,提高内存的利用率
-
构造代码块
位置:类中方法外
特点:每次构造方法执行时,都会执行该代码块中的代码,并且在构造方法执行前执行
作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
public class CodeBlock { public static void main(String[] args) { Student stu1 = new Student(); Student stu2 = new Student("张三"); } } class Student{ private String name; public Student(){ System.out.println("默认空参构造方法"); } public Student(String name){ System.out.println("带参构造方法"); this.name = name; } //构造代码块 { System.out.println("-------构造代码块在构造方法发执行前执行-------"); System.out.println("类中方法外 { },没有static修饰,是构造代码块"); System.out.println("创建对象,就会执行构造代码块"); } }
-
静态代码块
位置:类中方法外定义
特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
作用:在类加载的时候做一些数据初始化的操作
public class CodeBlock { public static void main(String[] args) { Student stu1 = new Student(); Student stu2 = new Student("张三"); } } class Student{ private String name; //静态代码块 static { System.out.println("类中方法外,static{},是静态代码块,随着类的加载而加载,只执行一次"); } public Student() { } public Student(String name) { this.name = name; } }
-
-
斗地主游戏,静态代码块的使用
public class Poker {
// 需求:在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据
/**
* 分析:
* 1、该房间需要一副牌
* 2、定义一个静态的ArrayList 集合存储54张牌对象,静态的集合只会加载一次
* 3、在启动游戏房间前,应该将54张牌初始化好
*/
public static ArrayList<String> list = new ArrayList<>();
static {
String[] colors = {"♥","♠","♦","♣"};
String[] nums = {"A","1","2","3","4","5","6","7","8","9","10","J","Q","K"};
for (int i = 0; i < colors.length; i++) {
String color = colors[i];
for (int j = 0; j < nums.length; j++) {
list.add(color+nums[j]);
}
}
list.add("小王");
list.add("大王");
//打乱集合中的顺序
Collections.shuffle(list);
System.out.println(list);
}
}
09-继承介绍和入门
-
继承的介绍
继承:让类于类之间产生关系(之父类关系),子类可以直接使用父类中,非私有的成员
继承的格式:
- 格式:public class 子类名 extends 父类名{ }
- 范例: public class Zi extends Fu { }
- Fu: 是父类,也称为基类,超类
- Zi:是子类,也称为派生类
-
什么时候使用继承?
- 当类与类之间,存在相同(共性)的内容,并且产生了 is a 的关系,就可以考虑使用继承,来优化代码
-
继承解决的问题:
- 共性抽取
-
继承的入门
猫 == 狗 =====>动物
public class Animal { private String name; private String color; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
public class Cat extends Animal{ }
public class Dog extends Animal{ }
public class AnimalTest { public static void main(String[] args) { //创建猫的对象 Cat c = new Cat(); c.setName("花花"); c.setColor("灰色"); System.out.println(c.getName() + "---" + c.getColor()); //创建狗的对象 Dog d = new Dog(); d.setName("小黑"); d.setColor("黑色"); System.out.println(d.getName() + "---" + d.getColor()); } }
10-继承中成员变量的访问特点
-
当继承关系产生后,子父类中,出现了重名的成员变量,调用的时候,会根据就近原则,优先使用子类的成员变量,
非要使用父类的成员变量的话,使用 super 进行区分
-
this :调用本类的成员
-
super : 调用父类的成员
public class Test {
public static void main(String[] args) {
Son son = new Son();
son.getNum();
}
}
class Father{
int num = 10;
}
class Son extends Father{
int num = 20;
public void getNum(){
int num = 40;
System.out.println("当前方法中的num:" +num); //40
System.out.println("son类中的num:" +this.num); //20
System.out.println("father类中的num:" +super.num); //10
}
}
11-方法重写(Override)(1)
public class Test {
public static void main(String[] args) {
Son son = new Son();
son.catchSheep();
}
}
class Father{
public void catchSheep(){
System.out.println("灰太狼:使用弓箭捉羊");
}
}
class Son extends Father{
@Override
public void catchSheep() {
//super.catchSheep();
System.out.println("小灰灰:使用猎枪捉羊");
}
}
-
问题: 当子父类关系产生后,子类定义的方法,跟父类重名,创建子类对象,调用方法,执行的是谁的逻辑?
答案: 会执行子类的方法逻辑,这虽然是就近原则,但实际上,是子类的方法,对父类的方法,进行了重写。
-
方法重载( Overload ):在同一个类中,方法名相同,参数不同,与返回值无关,参数不同:个数不同,类型不同,顺序不同
-
方法重写( Override ) : 在继承关系中,子父类出现了方法声明一模一样的方法:方法名,参数,返回值( 全部相同 ),方法的重写,也叫方法的覆盖重写。
-
什么时候需要方法重写呢?
子类觉得父类的逻辑不好,或是想增强逻辑,实现父类没有的功能,就可以对父类的方法进行覆盖重写
不改变父类方法的前提下,子类也有自己的实现。
-
-方法重写的注意事项
-
父类私有的成员方法不能重写,静态的方法不能重写
-
子类重写父类的成员方法,权限修饰符必须大于等于父类
-
@Override注解,标记一个方法是重写父类方法(语法检查)
12-权限修饰符
13-Java中继承的特点
-
java只支持单继承,不支持多继承,但是支持多层继承
-
任何一个类,都直接或是间接继承Object
14-继承中构造方法的访问特点
-
子类不能继承父类的构造方法
- 在子类初始化之前,是否需要先完成父类的初始化?
回答:有必要先完成父类的初始化
原因:因为子类在初始化的过程中,很有可能用到了父类的数据,如果父类没有提前完成初始化,子类访问父类的数据的时候, 就相当于使用一个变量进行运算操作,但是变量没有赋值。
- 子类是如何完成父类的初始化操作的?
思考:要初始化一个对象,要先执行构造方法
回答:子类只要调用到父类的构造方法,就可以完成父类的初始化操作。
-
子类如何调用到父类的构造方法呢?
在所有子类的构造方法中,都会默认隐藏一句代码,super(); 来访问父类的空参构造
class A{ public A(){ System.out.println("学习java基础"); } } class B extends A{ public B(){ super();//默认,super();可以访问到class A的空参构造 System.out.println("数据结构与算法分析"); } }
15-继承案例-学生和老师
-
测试类
public class Test { /** * 需求: * Person类: * 成员变量:姓名,年龄 * Teacher类: * 成员变量:姓名,年龄 * Student类: * 成员变量:姓名,年龄,成绩 * */ public static void main(String[] args) { Teacher teacher = new Teacher("张三",30); System.out.println(teacher.getName() + "---" + teacher.getAge()); Student stu = new Student("小明",18,100); System.out.println(stu.getName() + "---" +stu.getAge() + "---" + stu.getScore()); } }
-
Person类,标准的javabeen类
public class Person { private String name; private int age; //省略set,get,空参,带参方法 }
-
Teacher类
public class Teacher extends Person{ public Teacher() { } public Teacher(String name, int age) { super(name, age); } }
-
Student类
public class Student extends Person{ private int score; public Student() { } public Student(String name, int age, int score) { super(name, age); this.score = score; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } }
16-继承构造方法调用图解
17-this和super
-
this : 代表本类对象的引用
-
super : 代表父类存储空间的标识( 可以理解为父类对象的引用 )
-
注意:this() 和 super() 都在争夺构造方法第一行的位置,所以二者不能共存。
-
this();访问本类的构造方法
public class Test {
public static void main(String[] args) {
A a = new A(10,20,30,40);
}
}
class A{
int a;
int b;
int c;
//需求:需要增加一个成员变量 int d;
int d;
public A() {
}
public A(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
public A(int a, int b, int c,int d) {
//交给public A(int a, int b, int c)去初始化 a,b,c
this(a,b,c);
this.d = d;
}
}
18-final关键字的作用
-
final : 修饰符( 方法, 类 ,变量 )
-
方法: 被final 修饰的方法,不能被重写,但是子类可以继承使用
-
类: 被final 修饰的类,不能被继承,但是可以继承其他类
- 一个类中,所有的方法,都不希望子类重写,干脆就不要有子类,直接final修饰
-
变量: 被final 修饰的变量,只能赋值一次,无法修改
-
19-final修饰变量的细节补充
- 被final修饰的基本数据类型,其数据值不能更改
-
被final修饰的引用数据类型,其引用地址不能更改
-
final修饰成员变量特点
-
定义成员变量的时候就赋值
-
如果定义成员变量的时候没有赋值,须在构造方法中进行赋值
-
-
final 修饰变量的命名规范
-
如果final修饰的变量的变量名是一个单词,单词字母全部大写
-
如果final修饰的变量的变量名是多个单词组合,单词之间用 — 分隔
-
day02-包-抽象类-接口-多态-内部类
-包
-
包本质上是一个文件夹,用来对类文件进行分类管理,定义一个类时,需要指定类在哪个包下。
-
定义包的语句 package com.itheima.d1_abstract.demo1;
-
不同包下的访问,需要导包
导包的格式: import 包名.类名
import java.util.*;导入java.util包下的所有类
-
如果多个包下有相同的类,导包的时候需要导入某一个包下的类
import java.util.Scanner;
import com.itheima.d1_abstract.demo2.Student;
01-抽象类和抽象方法
抽象类的意义:限定子类的行为
- 抽象类
- 一个类存在抽象方法,这个类就必须定义为抽象类
- 就是一种特殊的父类,跟普通的父类的区别,在于内部可以定义抽象方法
- 抽象方法:
- 在父类中,一个方法的实现,父类不知道如何具体去实现,就定义为抽象方法
- 将共性行为(方法),抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法
- 定义格式:
- 抽象类的定义格式:
- public abstract class 类名 {}
- 抽象方法的定义格式:
- public abstract 返回值类型 方法名 (参数列表);
- 抽象类的定义格式:
02-抽象类注意事项
- 如果子类继承父类是抽象类,子类必须重写父类的所有的抽象方法
- 如果子类继承父类是抽象类,子类不重写父类的抽象方法,子类可以定义为抽象类
- 抽象类可以有构造方法,使得子类能通过super()进行访问
- 抽象类不能创建对象
- abstract与static不能共存,abstract修饰的方法,没有方法体,调用没有任何意义
- abstract 与 final 不能共存,final修饰的方法,不能让子类重写
- abstract 与private,被private修饰的方法,子类不能重写
模板方法模式解决了什么问题?
- 模板方法已经定义了通用结构,不能确定的功能定义成抽象方法
- 子类根据自己的需要复写方法即可
03-接口的介绍
接口:体现的思想是对规则的声明
Java中的接口更多体现的是对行为的抽象
-
定义接口
public interface 接口名{ //常量 public final static int num = 10; //抽象方法 public abstract void show(); }
04-类和接口之间的各种关系
-
类与类
单继承,一个类只能继承一个父类
-
类与接口
实现关系,可以单实现,也可以多实现,甚至可以在继承一个类的同时,实现多个接口
多个接口,如果有一模一样的方法声明,仅仅是方法声明,没有方法体,没有逻辑
父类只能有一个,亲爹,多继承会有逻辑冲突
class Fu{ public void method(){ System.out.println("学习Java程序开发。。。。"); } } interface InterA{ //学习 public abstract void method(); } interface InterB{ //学习 public abstract void method(); } class InterImpl1 implements InterA,InterB{ @Override public void method() { System.out.println("天天学习,天天快乐~"); } } class InterImpl2 extends Fu implements InterA,InterB{ }
-
接口与接口
多继承,一个接口可以继承多个接口,相当于把多个接口的功能集成到一个接口中
-
接口弥补了抽象类的局限性
抽象类:只能单继承
接口:可以多实现
抽象类的意义:限定子类的行为
-
JDK8以后,开始支持新性能
-
默认方法
默认被public default修饰,可选重写
-
静态方法
被 public static 修饰,接口名调用
-
私有方法
被private 修饰,只能在当前接口中被访问
-
抽象类和接口的区别
- 抽象类是把子类的共性进行抽取,而接口是把某一个单一功能进行抽取,谁需要就去实现接口。
接口的注意事项
- 接口不能实例化,不允许创建对象,并且接口没有构造方法
- 接口中的成员变量是常量,默认被public static final修饰
- 接口中成员方法,只能是抽象方法,默认被public abstract修饰
- 接口的实现类
- 一个类实现接口,必须重写所有的抽象方法
2. 将自己变成抽象类(不推荐使用)
- 一个类实现接口,必须重写所有的抽象方法
05-多态的前提条件
-
什么是多态?
-
在继承父类或者实现接口的基础上,允许同一个对象具有多种表示形态
Dog dog = new Dog(); Animal dog = new Dog();
-
-
多态的表现形式
- 父类 变量 = 子类对象;
- 接口 变量 = 实现类对象;
-
前提条件
- 要有继承关系 / 实现关系
- 要有方法重写
- 要有父类引用指向子类对象
-
多态的好处
-
当把方法的参数写成父类 / 接口类型时,调用方法的时候可以传递子类 / 实现子类对象
-
原本是什么类型,才能还原成什么类型
Dog dog = new Dog(); Animal animal = dog; Dog dog1 = (Dog) animal;
-
ClassCastException,异常报错,类型转换异常
-
Dog dog = new Dog(); Animal animal = dog; Cat cat = (Cat) animal; Feeder.feed(cat);
-
instanceof关键字
- 作用:测试它左边的对象是否是它右边的类的实例
06-多态创建对象后-成员的访问特点
-
成员变量:
- 编译看左边(父类),运行看左边(父类)
- 原因:当前是父类的引用,所以访问有一些局限性,只能看到堆内存,对象中,super那一小块区域
- 所以,访问的是父类的成员变量
-
成员方法
-
编译看左边(父类),运行看右边(子类)
细节:编译期检查父类中是否有这个方法
没有:编译出错
有:运行的时候,要走子类重写后的逻辑
为什么一定要走子类重写后的逻辑呢?
如果父类的方法,是抽象方法,那就没有逻辑可以执行
-
所以访问的是子类的成员方法
-
-
静态成员
编译看左边(父类),运行看左边(父类)
静态成员,随着类的加载而加载,有先于对象而存在
- 所以访问的是父类的静态成员
07-多态的好处和弊端
好处:
提高了代码的扩展性
把方法的参数写成父类,接口类型,调用方法的时候可以传递子类对象,实现类的对象
弊端:
父类引用不能直接访问子类特有的成员
如果要想要调用子类特有的成员,怎么办?
1. 直接创建子类对象
2. 使用 instanceof 判断父类引用是否是子类的实例,然后进行向下转型
08-多态的转型问题
规则:多态的写法,只能调用子父类共性的方法,不能直接调用子类特有的方法,必须转型后,才能调用
-
向上转型
从子到父,(父类引用指向子类对象),
Fu fu = new Zi();
Animal animal = new Dog();
-
可以类比数据类型转换的自动类型转换,小范围的数据类型可以直接给大范围的数据类型赋值。
double b = 10;
-
-
向下转型
从父到子, (父类引用转为子类对象),将父类引用,所记录的子类对象,重新再交给子类的引用
//父类引用先指向子类对象
Fu fu = new Zi();
//重新再交给子类的引用,进行向下转型
Zi zi = (Zi) fu;
-
可以类比数据类型转换中强制类型转换,数据范围大的转换给数据范围小的变量
int a = (int) 3.14;
-
09-内部类
定义:
内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解为(宿主)
public class Outer{
//内部类
public class Inner{
}
}
- 内部类通常可以直接访问外部类的成员,包括私有成员
10-成员内部类和静态成员内部类
-
成员内部类,
- 位置,和外部类的成员 平级
public class Test { public static void main(String[] args) { //成员内部类创建对象的格式 // 外部类的类名.内部类的类名 变量名 = new 外部类().new 内部类(); Outer.Inner oi = new Outer().new Inner(); oi.show(); } } class Outer{ int num = 1000; class Inner{ int num = 100; public void show(){ int num = 10; System.out.println("我是成员内部类Inner....show"); System.out.println("num:" + num);//10 访问的是方法中的show 的变量 System.out.println("num:" + this.num);//100 访问的是内部类中的 的变量 System.out.println("num:" + Outer.this.num);//1000 访问的是外部类中的 的变量 } } }
-
- 成员内部类可以访问外部类的成员,包括私有成员
-
- 当外部类与内部类以及方法中,有变量名一样的情况,要访问外部类的成员,可以跟上外部类的类名.this进行区分
-
静态内部类
- 静态内部类,只是成员内部类被static关键字修饰,特点和普通类一样
- 创建对象的格式
- 外部类的类名.内部类的类名 变量名 = new 外部类的类名.内部类的类名();
- Outer.Inner oi = new Outer.Inner();
-
-
public class Test { public static void main(String[] args) { //静态内部类创建对象的格式 // 外部类的类名.内部类的类名 变量名 = new 外部类的类名.内部类(); Outer.Inner oi = new Outer.Inner(); } } class Outer{ int num = 1000; static int index = 5; static class Inner{ public void show(){ System.out.println("我是静态内部类 static Inner ... show"); //System.out.println(num); 静态内部类,不能访问非静态的成员 System.out.println(index); } } }
-
11-局部内部类-了解
-
局部内部类放在方法、代码块、构造器等执行体中
-
局部内部类的类文件为:外部类$局部内部类.class
public class Test2 { public static void main(String[] args) { Student student = new Student(); student.show(); } } class Student{ public void show(){ System.out.println("Student.....show"); class Inner{ int num = 10; public void method(){ System.out.println("局部内部类,鸡肋语法,了解即可"); System.out.println("num:" + num); } } Inner i = new Inner(); i.method(); } }
11-匿名内部类-重点!
-
概述:匿名内部类,本质上是一个特殊的局部内部类(定义在方法内部)
前提:需要存在一接口或类
-
前引
public class Test3 { public static void main(String[] args) { method(new InnerImpl()); /* method();它应该接收什么参数呢? 传入的参数必须是 Innter接口的实现类对象 1.method(new Inner()); 不允许,接口没有构造方法,不允许创建对象 2.method(new InnerImpl()); Innter接口的实现类对象 */ } public static void method(Inner i){//形参是一个接口 i.show(); } } //定义一个接口 interface Inner{ //定义一个show 的抽象方法 void show(); } class InnerImpl implements Inner{ @Override public void show() { System.out.println("show......."); } }
-
匿名内部类:
- 将继承 / 实现,方法重写,创建对象,放在了一步进行
- 格式:
- new 类名(){} ------->继承这个类
- new 接口名(){ } --------> 实现这个接口
public class Test4 {
public static void main(String[] args) {
method(new Inner2(){
@Override
public void show() {
System.out.println("show....");
}
});
/**
* 匿名内部类
* new Inner2(){
* @Override
* public void show() {
* System.out.println("show....");
* }
* }
* 把接口的实现类,方法的重写集成于一体
*/
}
public static void method(Inner2 i){
i.show();
}
}
//定义一个接口
interface Inner2{
//定义一个show 的抽象方法
void show();
}
- 匿名内部类是抽象类的子类或是接口的实现类,它既是类也是对象
-
匿名内部类的使用场景:
一般不会主动创建匿名内部类*
当方法的参数是一个接口,需要传入一个接口类的实现对象
- 可以单独写一个class,中规中矩的实现和重写,创建对象
- 可以直接使用匿名内部类,简化当前代码
-
遍历集合:在JDK8版本以后,API 提供了一个forEach方法,可以遍历集合元素
forEach方法底层会自动遍历集合,得到每一个元素
然后调用Consumer接口实现类的accept方法,把元素赋值给accpet方法的参数
对于accept是怎么复写的,交给实现类去实现
ArrayList<String> list = new ArrayList<>(); list.add("abc"); list.add("123"); list.add("hello"); list.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });
day03-Lambda-Object-Math-System-BigDecimal-Date
01-Lambda表达式
-
Lambda表达式是 JDK8 开始后的一种新语法形式。
-
作用:简化匿名内部类的代码写法。
-Lambda表达式的简化
(匿名内部类被重写方法的形参列表) ->{
//被重写方法的方法体。
}
// -> 是语法形式,无实际含义
- 注意:Lambda表达式只能简化函数式接口的匿名内部类的写法形式
- 什么是函数式接口?
- 首先必须是接口、其次接口中有且仅有一个抽象方法的形式
- 通常我们会在接口上加上一个 @FunctionalInterface注解,标记该接口必须是满足函数式接口
- 什么是函数式接口?
Integer[] array = {1,4,3,6,9,7};
//降序排序,使用匿名内部类
Arrays.sort(array, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//使用Lambda表达式
Arrays.sort(array,(Integer o1,Integer o2)-> o1 - o2);
System.out.println(Arrays.toString(array));
-
Lamdba表达式简化格式
1.参数类型可以直接省略
2.如果语句体只有一条,大括号,return关键字可以省略
3.参数只有一个,小括号可以省略
-
Lamdba表达式的简化格式有哪些要求
-
什么时候可以省略参数类型?
任何时候
-
什么时候可以省略大括号和return关键字?
语句体只有一条语句时
-
什么时候可以省略小括号?
参数只有一个时
-
-
方法引用:对Lamdba进一步简化
如果Lamdba表达式中的代码,可以用一个方法代替,就可以使用方法引用
格式:
对象名 : : 方法名
list.forEach(System.out : : println)
ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("java"); list.add("world"); list.forEach(System.out::println);
-
Lambda表达式,是否是匿名内部类的语法糖?
语法糖:
两个东西,代码实现方式不一样,但是内部原理一样
集合遍历:迭代器遍历,增强for循环
匿名内部类和Lambda表达式的内部原理是不一致的
匿名内部类,会产生字节码文件
Lambda表达式,不会产生.class字节码文件
02-Object类
类
Object
是类层次结构的根类。每个类都使用Object
作为超类。所有对象(包括数组)都实现这个类的方法。
-
Objects:用于操作任意对象的工具类,可以避免空指针异常
-
Object类的toString()方法
-
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
-
一个类会产生字节码文件,Java程序把字节码也创建成一个类Class return getClass().getName() + "@" + Integer.toHexString(hashCode()) getClass().getName() ---> 全类名(包名 + 类名) :com.itheima.mytest.demo01.Student @ --- > 分隔符 Integer.toHexString(hashCode()) : hashCode() --->根据内存地址,用哈希算法,计算出的哈希值 Integer.toHexString(十进制数) ---> 把十进制数转为 16 进制 --> 776ec8df
-
toString()方法
- 子类没有重写Object的toString()方法
-
进行简单打印对象变量名的时候,打印的是地址值
System.out.println(stu1);//com.itheima.mytest.demo01.Student@776ec8df
-
使用对象调用toString()方法,打印的也是地址值
System.out.println(stu1.toString());//com.itheima.mytest.demo01.Student@776ec8df
-
当打印一个对象变量的时候,底层实现也会调用Object的toString()
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
- 子类重写toString()方法
当子类没有重写toString()方法的时候,打印对象名,用户得到的地址值,没有实际意义,然而用户希望看到一个类的属性值的时候,就可以对Object的toString()方法进行重写
-
@Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; }
-
重写后,得到对象的属性值
-
System.out.println(stu1);//Student{name='张三', age=23}
-
equals方法
-
子类没有重写,比较的是两个对象的地址值
System.out.println(stu1 == stu2);//false System.out.println(stu1.equals(stu2));//false
-
没有重写equals()方法的时候,对象调用equals()进行比较,本质就是使用 == 进行比较
public boolean equals(Object obj) { return (this == obj); }
-
-
子类重写equals(),可以比较两个对象的属性值
@Override public boolean equals(Object o) { //1、使用地址比较两个对象,地址一样,肯定是一个对象,直接return true if (this == o) return true; /* 2、当前对象能调用方法,this指向当前对象,当前对象不为 null 2.1、参数对象如果为null,没有可比性,return false 2.2、如果两个字节码文件不一样,说明不是同一个类, return false */ if (o == null || getClass() != o.getClass()) return false; //3、经过前两步,说明参数对象不为null,也是同一个类,且地址不一样,把参数对象向下转型 Student student = (Student) o; if (age != student.age) return false; return name != null ? name.equals(student.name) : student.name == null; }
Student stu1 = new Student("张三",23); Student stu2 = new Student("张三",23); System.out.println(stu1 == stu2);//false System.out.println(stu1.equals(stu2));//true
-
03-包装类
-
什么是基本类型包装类?
-
学习包装类干什么用呢?
包装类中会提供一些方法,便于对数据进行操作
比如:字符串与基本类型数据的相互转换
-
String和基本类型的相互转换
-
基本类型转换为字符串
String s1=Integer.toString(300)
String s2 = Double.toString(5678.88)
- 每个包装类都有一个toString方法
-
字符串转换为基本类型
int a = Integer.parseInt(“12345”)
double b = Double.parseDouble(“6.88”)
- 每个包装类都有一个parseXxx方法
-
-
NumberFormatException
格式转换异常
Integer num = Integer.parseInt("123a456");
-
自动装箱和拆箱
-
装箱:把基本类型转换为引用类型
- 自动装箱
Integer a = 10;//把int 类型的10,自动转换为Integer类型
- 手动装箱
Integer num = Integer.valueOf(10);
-
拆箱:把引用类型转换为基本类型
- 自动拆箱
int b = a; //a 是Integer类型,运算时会自动转换为int类型
- 手动拆箱
int b = a.intValue();
-
-
面试题
-
自动装箱时,会判断这个数是否在 -128 ~ 127 范围内,如果在这个范围,就不重新创建对象,否则就会重新创建对象
-
Integer a = 127; Integer b = 127; System.out.println(a == b);//true Integer c = 128; Integer d = 128; System.out.println(c==d);//false
-
自动装箱的原理
- 底层会自动调用 Integer.valueOf(数值)
- valueOf( ):
- 判断数值是否在:-128 ~ 127之间
- 在
- 不会重新创建,而是从底层的数组中,取出并返回
- 不在
- 重新 new Integer( )对象
-
-
比较
-
Integer num1 = new Integer(100); Integer num2 = new Integer(100); int num3 = 100; System.out.println(num1 == num2);//false,内存中有两个地址 //Integer引用类型,参与数学运算的时候,会自动拆箱为 int 基本数据类,基本数据类型比较的是数值 System.out.println(num3 == num2);//true
-
-
04-Math类(工具类)
-
Math
类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。 -
public final class Math,它是最终类,不能被继承,所有的方法都是static修饰
-
向上取整
public static double ceil(double a)
返回最小的(最接近负无穷大)double 值,该值大于等于参数,并等于某个整数
double num1 = Math.ceil(3.14); System.out.println(num1); //4.0 double num2 = Math.ceil(3.67); System.out.println(num2);//4.0 double num3 = Math.ceil(-1.3); System.out.println(num3);// -1.0
-
向下取整
public static double floor(double a)
返回最大的(最接近正无穷大)double 值,该值小于等于参数,并等于某个整数
double num1 = Math.floor(3.14); System.out.println(num1); //3.0 double num2 = Math.floor(3.67); System.out.println(num2);//3.0 double num3 = Math.floor(-1.3); System.out.println(num3);// -2.0
05-System类
-
在
System
类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性和环境变量的访问;加载文件和库的方法;还有快速复制数组的一部分的实用方法。 -
public static void exit(int status)
终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。
-
public static long currentTimeMillis()
返回以毫秒为单位的当前时间。
当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。
可以使用该方法,测试程序运行所消耗的时间;
可以计算 年 - 月 - 日
-
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
-
/* System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length) 参数: Object src 源数组 int srcPos 源数组中的起始位置。 Object dest 复制到的目标数组 int destPos 从复制到目标数组的起始位置开始粘贴 int length 要复制的数组元素的数量。 */
-
int[] arr = {10,20,30,40,50}; int[] arr2 = new int[5]; System.arraycopy(arr,1,arr2,2,3); System.out.println(Arrays.toString(arr2)); //[0, 0, 20, 30, 40]
06-BigDecimal类
- double类型小数参与运算时,会有精度损失
-
BigDecimal的作用是什么?
-
BigDecimal类可以用来对小数进行精确运算、控制小数的位数
-
BigDecimal的对象如何获取?
-
BigDecimal b1 = BigDecimal.valueOf(0.1);
-
public static BigDecimal valueOf(double val): 包装浮点数成为BigDecimal对象
//获取两个BigDecimal对象
BigDecimal num1 = BigDecimal.valueOf(0.09);
BigDecimal num2 = BigDecimal.valueOf(0.01);
//加法运算
BigDecimal add = num1.add(num2);
System.out.println(add);//0.10
//减法运算
BigDecimal sub = num1.subtract(num2);
System.out.println(sub);//0.08
//乘法运算
BigDecimal mul = num1.multiply(num2);
System.out.println(mul);//0.0009
//除法运算
BigDecimal div = num1.divide(num2);
System.out.println(div);//9
-
当除不尽的情况
BigDecimal num3 = BigDecimal.valueOf(10); BigDecimal num4 = BigDecimal.valueOf(3); BigDecimal div2 = num3.divide(num4); System.out.println(div2); //ArithmeticException 报错,异常
-
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode),方法,可以保留指定位数
- RoundingMode roundingMode有三个参数:
- 具体看API
- RoundingMode roundingMode有三个参数:
BigDecimal num3 = BigDecimal.valueOf(10);
BigDecimal num4 = BigDecimal.valueOf(3);
BigDecimal div2 = num3.divide(num4, 2, RoundingMode.HALF_UP);
System.out.println(div2); // 3.33
-
将BigDecimal对象转换为基本数据类型 doubleValue()
BigDecimal bd1 = BigDecimal.valueOf(0.01); BigDecimal bd2 = BigDecimal.valueOf(0.09); BigDecimal add = bd1.add(bd2); double doubleValue = add.doubleValue(); // 将此 BigDecimal 转换为 double。 public double doubleValue() System.out.println(doubleValue);//0.1
07-Date日期类
-
Date类的对象,用来表示任意的日期 和 时间(单位:毫秒)
-
构造方法
-
public Date();
创建一个Date对象,代表的是系统当前日期和时间
-
public Date(long time);
创建一个Date对象,代表自1970年1月1日0时0分0秒以来的
-
-
实例方法
-
public long getTime()
返回从1970年1月1日0时0分0秒走到此刻的总毫秒值
-
public void setTime(long time); 设置Date对象自1970年1月1日0时0分0秒以来的毫秒值
-
//需求:请计算出当前时间往后走1小时26分之后的时间是多少?
//1、获取Date对象
Date date = new Date();//空参构造
System.out.println("修改之前的date:" + date);
//2、获取当前date对象的毫秒值
long currentTime = date.getTime();
//3、当前时间 + 1小时26分
long l = currentTime + 1 * 60 * 60 * 1000 + 26 * 60 * 1000;
//4、把修改后的时间,赋值给date对象
date.setTime(l);
System.out.println("修改之后的date:" + date);
08-SimpleDateformat类
-
SimpleDateFormat
是一个以与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。 -
构造方法
public SimpleDateFormat(String pattern)
用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。
模式:
字母 日期或时间元素 表示 示例
y 年 Year 1996; 96
M 年中的月份 d 月份的天数
H 一天中的小时数(0-23)
m 小时中的分钟数
s 分钟中的秒数
-
日期格式化, 把Date对象转换为字符串
//创建对象 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //日期格式化, 把Date对象转换为字符串 String format = sdf.format(new Date()); System.out.println(format);
-
日期解析,返回Date日期对象,需要 throws 异常
public static void main(String[] args) throws ParseException { //创建对象 SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); //日期解析,返回Date日期对象 Date parse = sdf2.parse("1998年11月15日 20:30:40"); System.out.println(parse);//Sun Nov 15 20:30:40 CST 1998 }
-
需求:请计算出“2021年08月06日11点11分11秒”,往后走2天14小时的时间是多少?
public static void main(String[] args) throws ParseException { //需求:请计算出“2021年08月06日11点11分11秒”,往后走2天14小时的时间是多少? /* 分析 给的是 字符串 “2021年08月06日11点11分11秒”, 1、用SimpleDateFormat类的日期解析为Date对象 2、调用Date对象的getTime()获取毫秒值 3、计算出2天14小时的毫秒值 4、调用Date对象的setTime()设置毫秒值 5、用SimpleDateFormat类的日期格式化,把Date对象转为字符串,格式化输出 */ // 创建SimpleDateFormat对象 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); // 1、用SimpleDateFormat类的日期解析为Date对象 Date date = sdf.parse("2021年08月06日 11:11:11"); //2、调用Date对象的getTime()获取毫秒值 long currentTime = date.getTime(); //3、计算出2天14小时的毫秒值 long l = 2 * 24 * 60 * 60 *1000 + 14 *60 *60 *1000; //4、调用Date对象的setTime()设置毫秒值 date.setTime(currentTime + l); //5、用SimpleDateFormat类的日期格式化,把Date对象转为字符串,格式化输出 String s = sdf.format(date.getTime()); //输出结果 System.out.println(s);//2021年08月09日 01:11:11 }
08-Calendar获取年月日星期
-
public abstract class Calendar,它是抽象类
Calendar表示日历,包含很多与日期时间相关的字段信息(年、月、日、时、分、秒)
Calendar可以对每一个日历字段进行单独操作
-
获取Calendar的对象
public static Calendar getInstance(TimeZone zone)
使用指定时区和默认语言环境获得一个日历。返回的 Calendar 基于当前时间,使用了给定时区和默认语言环境
- Calendar c = Calendar.getInstance();
-
方法名 说明 public int get(int field) 取日期中的某个字段信息。 public void set(int field,int value) 修改日历的某个字段信息。 public void add(int field,int amount) 为某个字段增加/减少指定的值 public final Date getTime() 拿到此刻日期对象。 public long getTimeInMillis() 拿到此刻时间毫秒值
public static void main(String[] args) {
//获取Calendar的子类
Calendar c = Calendar.getInstance();
//c.set()设置年月日
// c.set(1998,10,15);
/* c.set(Calendar.YEAR,1998);
c.set(Calendar.MONTH,10);
c.set(Calendar.DAY_OF_MONTH,15);*/
//c.add()可进行日期加减操作
c.add(Calendar.YEAR,-5);//往后倒退5年
// c.get()获取年月日
//获取年
int year = c.get(Calendar.YEAR);
//获取月 注意 java程序中 ,月份是从 0 开始
int month = c.get(Calendar.MONTH) +1;
//获取日
int day = c.get(Calendar.DAY_OF_MONTH);
System.out.println(year + "年" + month + "月" + day +"日");
}
-
***注意:***calendar是可变日期对象,一旦修改后其对象本身表示的时间将产生变化
-
查表法获取Calendar中的星期
老外认为,一个星期中的第一天是星期日,星期六是一个星期中的最后一天
星期日 星期一 星期二 星期三 星期四 星期五 星期六 1 2 3 4 5 6 7
int day = c.get(Calendar.DAY_OF_WEEK);// 返回值是 3 ,则对应的是星期二
String[] weeks = {"","星期日","星期一","星期二","星期三","星期四","星期五","星期六"}; 0 1 2 3 4 5 6 7 int index = c.get(Calendar.DAY_OF_WEEK); System.out.println(weeks[index]);//星期二
day04-JDK8日期类-正则-Arrays
1、LocalDateTime对象的创建和获取【熟悉】
- JDK8 开始,java.time包提供了新的日期和时间API
-
LocalDate:年,月,日,周(无时间)
-
LocalTime: 时分秒
-
LocalDateTime:包含了日期及时间
-
Instant:代表的是时间戳
-
DateTimeFormatter:用于做时间的格式化和解析的
-
Duration:用于计算两个“时间” 间隔
-
Preiod:用于计算两个“日期间隔”
-
LocalDate、LocalTime、LocalDateTime
- 分别表示日期、时间、日期时间对象,他们的类的实例是不可变的对象
- 他们三者构建对象和API都是通用的
-
构建对象的方式如下:
public static Xxx now() 根据当前时间创建对象
举例:
LocalDateTime now = LocalDateTime.now(); LocalDate now1 = LocalDate.now(); LocalTime now2 = LocalTime.now();
public static Xxx of(...) 指定日期 / 时间创建对象
举例
LocalDateTime of = LocalDateTime.of(1998, 11, 15, 20, 30, 40); LocalDate of1 = LocalDate.of(2020, 2, 2); LocalTime now3 = LocalTime.of(23,8,8);
-
LocalDateTime可以转换为LocalDate和LocalTime
举例
LocalDateTime now = LocalDateTime.now(); LocalDate localDate = now.toLocalDate(); LocalTime localTime = now.toLocalTime();
-
-
获取
- getXxx
-
修改
- withXxx
-
向前减
- minusXxx
-
往后加
- plusXxx
2、Instant 时间戳【了解】
-
JDK8获取时间戳。可以通过Instant类由一个静态的工厂方法now()可以返回当前时间戳
Instant in = Instant.now(); //返回当前时间戳 Date date = Date.from(instant); //返回当前时间戳 //Date对象转为Instant对象 Instant instant = date.toInstant();
-
时间戳是包含日期和时间的,与java.util.Date很类似,事实上Instant就是类似JDK8以前的Date
-
设置时区
Zone时区 ZoneId.systemDefault() 可以获取当前系统的默认时区 Instant now = Instant.now(); ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
3、DateTimeFormatter对JDK8日期类进行解析或格式化
//日期对象
LocalDateTime localDateTime = LocalDateTime.now();
//日期格式化对象,返回字符串
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒 E a");
// 1.1格式化日期对象【日期格式化对象 . format (日期对象)】
System.out.println(dateTimeFormatter.format(localDateTime));//2022年07月08日 16时21分28秒 周五 下午
// 1.2 格式化日期对象【日期对象 . format (日期格式化对象)】
System.out.println(localDateTime.format(dateTimeFormatter));
//日期解析,返回日期对象
//解析格式
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
//返回解析对象
LocalDate localDateTime1 = LocalDate.parse("1998年11月15日", dateTimeFormatter2);
4、Period计算日期间隔,用于LocalDate之间的比较,【不能比较时分秒】
//当前本地 年月日
LocalDate today = LocalDate.now();
//假设生日 1998 - 11 - 15
LocalDate birthday = LocalDate.of(1998,11,15);
//Period 对象表示时间的间隔对象
Period period = Period.between(birthday,today);//第二个参数减第一个参数
//间隔多少年
int years = period.getYears();
//间隔多少月份
int months = period.getMonths();
//间隔多少天数
int days = period.getDays();
//总月份
long allMonths = period.toTotalMonths();
System.out.println("间隔 : " + years + "年" + months + "月" + days + "天");
System.out.println("总共间隔了" + allMonths + "月");
5、Duraation计算日期间隔,用于LocalDateTime、Instant之间的比较
-
注意:没有两个日期时间差的年数
//本地当前日期时间对象 LocalDateTime today = LocalDateTime.now(); //出生日期时间对象 LocalDateTime birthday = LocalDateTime.of(1998,11,15,0,0,0); //Duration表示日期时间间隔对象 Duration duration = Duration.between(birthday,today);//第二个参数减第一个参数 //两个时间差的天数 long days = duration.toDays(); //两个时间差的小时数 long hours = duration.toHours(); //两个时间差的分钟 long minutes = duration.toMinutes(); //两个时间差的秒数 long seconds = duration.toSeconds(); //两个时间差的毫秒数 long millis = duration.toMillis();
6、ChronoUnit可以用于在单个时间单位内测量一段时间,可以比较所有的时间单位【比较全面】
-
//本地当前日期时间对象 LocalDateTime today = LocalDateTime.now(); //出生日期时间对象 LocalDateTime birthday = LocalDateTime.of(1998,11,15,0,0,0); //相差的年数 long years = ChronoUnit.YEARS.between(birthday, today); //相差的月数 long months = ChronoUnit.MONTHS.between(birthday, today); //相差的天数 long days = ChronoUnit.DAYS.between(birthday, today); //相差的小时数 long hours = ChronoUnit.HOURS.between(birthday, today); //相差的分钟数 long minutes = ChronoUnit.MINUTES.between(birthday, today); //相差的秒数 long seconds = ChronoUnit.SECONDS.between(birthday, today); //相差的毫秒数 long millis = ChronoUnit.MILLIS.between(birthday, today);
6、正则表达式
-
正则表达式是专门用于对字符串做合法性校验的
-
String类的哪个方法可以与正则表达式进行匹配。
public boolean matches(String regex):
判断是否匹配正则表达式,匹配返回true,不匹配返回false
-
字符类【默认匹配一个字符】
-
[abc] 只能是 a,b或c
String regex = "[abc]"; String str = "d"; System.out.println(str.matches(regex));//false
-
[^abc] 除了 a , b ,c之外的任何字符
String regex = "[^abc]"; String str = "d"; System.out.println(str.matches(regex));//true
-
[a - zA - Z] a到z和A-Z的范围
String regex = "[a-zA-Z]"; String str = "6"; System.out.println(str.matches(regex));//false
-
[a - d[m - p]] a到d,或m到p,并集
String regex = "[a - d[m - p]]"; String str = "e"; System.out.println(str.matches(regex));//false
-
[a - z&&[ d e f ]] a-z 与 def 取交集 ,即只能是 d e f
String regex = "[a-z&&[def]]"; String str = "c"; System.out.println(str.matches(regex));//false
-
[a-z&&[^bc]] a-z,除了b和c
String regex = "[a-z&&[^bc]]"; String str = "b"; System.out.println(str.matches(regex));//false
-
[a-z&&[^m-p]] a-z,除了m-p
String regex = "[a-z&&[^m-p]]"; String str = "n"; System.out.println(str.matches(regex));//false
-
-
预定义的字符类【默认匹配一个字符】
-
. 任何字符 String regex = "\\."; //使用转义字符后,只是普通的点。 String str = "."; System.out.println(str.matches(regex));//true String regex2 = "."; // 没有转义,可以匹配任意字符 String str2 = "$"; System.out.println(str2.matches(regex2));//true
-
\d 一个数字
String regex = "\\d"; String str = "3"; System.out.println(str.matches(regex));//true
-
\D 非数字
String regex = "\\D"; String str = "6"; System.out.println(str.matches(regex));//false
-
\s 一个空白字符
String regex = "\\s"; //String str = " ";//空格 //String str = "\t";//制表符 //String str = "\n";//换行符 String str = "\r"; System.out.println(str.matches(regex));//true
举例:判断一串数字是否含有空白符
-
String regex = "[\\d&&[^\\s]]+"; String str = "134\t5 4"; System.out.println(str.matches(regex));//false
-
-
\w [a-zA-Z_0-9] 英文字母、数字、下划线
String regex = "\\w"; String str = "_"; System.out.println(str.matches(regex));//true
-
\W 就是对 \w取反 ===> 不是英文字母、数字、下划线的字符
String regex = "\\W"; String str = "\t"; System.out.println(str.matches(regex));//true
-
-
贪婪的量词【配合匹配多个字符】
-
X? X,一次或根本不
// a 出现一次或者 0 次 String regex = "a?"; // String str = "";//a 出现 0 次,根本没有 String str = "a";//a 出现一次 System.out.println(str.matches(regex));//true
-
X* X, 出现 0 次或者多次
// 前2个字符为a-zA-Z_0-9, 最后一个字符9要么出现很多次,要么不出现 String regex = "\\w{2}9*"; String str = "ab";//一次都没有出现 System.out.println(str.matches(regex));//true String str1 = "ab9";//出现一次 System.out.println(str1.matches(regex));//true String str2 = "ab99999";//出现5次 System.out.println(str2.matches(regex));//true
注意: 第三个字符可以没有出现,但是不能被其他字符占位
-
String str3 = "abT";// System.out.println(str3.matches(regex));//false
-
-
X+ , X出现一次或者多次
// 前三个字符为@qq,后面为 .com,要求出现一次或者多次 String regex = "@qq(.com)+"; String str = "@qq";//一次都没出现 System.out.println(str.matches(regex));//false String str1 = "@qq.com";//出现一次 System.out.println(str1.matches(regex));//true String str2 = "@qq.com.com";//出现2次 System.out.println(str2.matches(regex));//true
-
X{n} X 正好出现 n 次
// 前2个字符为a-z,后三个字符6要求出现三次 String regex = "[a-z]{2}6{3}"; String str = "ab666";//一次都没出现 System.out.println(str.matches(regex));//true
-
X{n ,} X 至少出现 n 次
// whw这三个字符至少出现三次 String regex = "(whw){3,}"; String str = "whwwhwwhw"; System.out.println(str.matches(regex));//true
-
X{n , m} X 至少出现 n 次,但不超过 m 次
// cn这两个字符至少出现一次,但不能超过两次 String regex = "(cn){1,2}"; String str = "cncncn"; System.out.println(str.matches(regex));//false
-
-
案例:提取字符串中的手机号码
String str = "电话1:15342337795;电话2:17876238497;电话3:19815637456"; String regex = "[1][\\d&&[^\\:]]\\d{9}"; //1、根据正则表达式,获取编译对象(正则对象) Pattern pattern = Pattern.compile(regex); //2、获取匹配器对象 Matcher matcher = pattern.matcher(str); /* matcher.find() 查找是否还有匹配的,返回boolean值 matcher.group() 查找到就把该字符串返回 */ while (matcher.find()){ String group = matcher.group(); System.out.println(group); }
7、Arrays数组工具类
-
Arrays数组工具类
Arrays是专门用于操作数组的工具类,可以对数组进行排序、查找等操作
-
常用方法:
方法名 说明 public static String toString(类型 [ ] a) 返回数组的内容(字符串形式) public static void sort(类型 [ ] a) 对数组进行默认升序排序 public static void sort(类型[ ] a, Comparator<? super T> c) 使用比较器对象自定义排序 public static int binarySearch(int [] a, int key) 二分搜索数组中的数据,数组必须有序,存在返回索引,不存在返负插入点 -1 -
使用比较器对象自定义排序
-
Integer[] arr ={11,58,6,35,88,9,16}; Arrays.sort(arr,new Comparator<Integer>(){ @Override public int compare(Integer o1, Integer o2) { /* 升序: o1 -o2 降序 o2 -o1 */ //int num = o1 -o2; int num = o2 -o1; return num; } }); System.out.println(Arrays.toString(arr));//[88, 58, 35, 16, 11, 9, 6]
-
如果直接return -1 会对数组进行翻转,相当于Collections.reverse(list);, return 0 或 1 不对数组进行操作
Integer[] arr ={44,11,33,22,55}; //Arrays.sort(arr,(o1, o2) -> -1); System.out.println(Arrays.toString(arr));//[55, 22, 33, 11, 44] //Arrays.sort(arr,(o1, o2) -> 0); System.out.println(Arrays.toString(arr));//[44, 11, 33, 22, 55] Arrays.sort(arr,(o1, o2) -> 1); System.out.println(Arrays.toString(arr));//[44, 11, 33, 22, 55]
-
Student[] array = new Student[5]; array[0] = new Student("Ken",18); array[1] = new Student("Jhon",18); array[2] = new Student("Mike",18); array[3] = new Student("Amiy",18); array[4] = new Student("Smith",18); Arrays.sort(array, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { //按照年龄进行排序 int num = o1.getAge() - o2.getAge(); //如果年龄相同,则比较姓名 if (num == 0){ num = o1.getName().compareTo(o2.getName()); } return num; } }); System.out.println(Arrays.toString(array));
7.1、binarySearch(int [] a, int key)
- binarySearch(int [] a, int key)方法的使用前提是数组元素有序。
1. 找到的情况下:返回元素在数组的索引
int[] arr = {11,22,33,44,55,66};
int index = binarySearch(arr, 55); ----> index = 4
2. 找不到的情况下,返回负的插入点减 1
int[] arr = {11,22,33,44,55,66};
int index = binarySearch(arr, 25); ----> index = -3
分析:
假如查找的元素在数组中,则该数组应该是: {11,22,25,33,44,55,66};
0 1 2 3 4 5 6
那么当前的插入点是 : 2
负的插入点减 1 -------> -2 - 1 = -3
int index = binarySearch(arr, -100); ----> index = -1
分析:
假如查找的元素在数组中,则该数组应该是: {100,11,22,33,44,55,66};
0 1 2 3 4 5 6
那么当前的插入点是 : 0
负的插入点减 1 -------> -0 - 1 = -1
- 案例-二分法实现数据动态插入
/*
* 集合、泛型,都不支持基本类型
* 用基本类型的包装类
*/
ArrayList<Integer> list = new ArrayList<>();
System.out.println("回车继续");
while(true) {
new Scanner(System.in).nextLine();
int n = new Random().nextInt(100);
//二分法查找,在list中找n所在的位置
//找不到,返回 -(插入点+1)
int index = Collections.binarySearch(list, n);
if(index < 0) {
index = (-index)-1;
}
list.add(index, n);
System.out.println(list.toString());
}
}
day05-Collection、数据结构
-
集合是存储数据的容器
-
集合只能存储引用数据类型,数组可以存储基本数据类型,也可以存储引用数据类型
01、单列集合collection接口
-
public interface Collection
-
Collection接口
-
接口 List implements Collection
实现类: ArrayList implements List
实现类: LinkedList implements List
-
接口 Set implements Collection
实现类: HashSet implements Set
子类: LinkedHashSet extends HashSet implements Set
实现类: TreeSet implements Set
注意:
- List 集合下的元素是有序的,有索引,元素可以重复
- Set 集合下的元素是无序的,没有索引,元素可以不能重复
- Collection接口API中常用的方法,其实现类都可以使用
-
public boolean add(E e) 把给定的对象添加到当前集合中
-
public void clear() 清空集合中所有的元素
-
public boolean remove(E e) 把给定的对象在当前集合中删除
-
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
contains:判断集合中,是否包含传入的对象 底层:使用equals()方法,来进行判断
-
public boolean isEmpty() 判断当前集合是否为空
-
public int size() 返回集合中元素的个数
-
public Object [ ] toArray() 把集合中的元素,存储到Object数组
-
<T> T[] toArray(T[] a) Integer[] arr = new Integer[0]; Integer[] array = list.toArray(arr); //底层会判断传入的数组的长度,会自动扩容,建议new Integer[list.size()]
-
JDK8以后, public boolean removeIf(参数),根据条件删除,参数是一个函数式编程接口
-
-
集合的遍历方式
-
遍历方式一
-
Collection 使用迭代器遍历
-
Collection 获取迭代器的方法
-
Iterator <T> iterator() // 返回集合中迭代器对象,该迭代器对象默认指向当前集合的 0 索引
-
boolean hasNext(); 询问当前位置是否有元素存在,存在返回true,不存在返回false
-
E next(); 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
-
举例:
ArrayList <String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("java"); Iterator it = list.iterator(); while (it.hasNext()){ System.out.println(it.next()); }
-
-
-
迭代器,如果已经遍历完毕,再想遍历的话,需要重新获取迭代器。
-
遍历方式二
-
增强 for,本质上是迭代器
增强for既能遍历集合、也能遍历数组
for(元素数据类型 变量名 : 数组/集合) { .... } Collection<String> list = new ArrayList<>(); for(String ele : list) { System.out.println(ele); }
-
-
并发修改异常(补充)
1.集合每次添加或者删除元素时,底层都会记录一次修改的次数modCount
2.在创建迭代器对象后使用expectedModCount记录已经被修改的次数
3.如果获取元素时发现expectedModCount和modCount值不一致,就认为集合被修改过,此时会触发 ConcurrentModificationException
while (it.hasNext()){ String s =(String) it.next(); if (s.equals("hello")){ //ConcurrentModificationException 并发修改异常 list.remove(s);//在使用迭代器或者增强for遍历集合,不允许集合本身对元素进行增删 } }
-
如何避免并发修改异常呢?
-
使用迭代器遍历集合,但是用迭代器自己的方法删除元素不会存在这个问题。
while (it.hasNext()){ String s =(String) it.next(); if (s.equals("hello")){ //使用迭代器删除 it.remove(); } }
-
使用JDK提供的removeIf(函数式接口)方法。
list.removeIf(new Predicate<String>() { @Override public boolean test(String s) { return s.equals("hello"); } });
list.removeIf(s -> s.equals("hello"));//使用Lambda表达式
-
新问题:迭代器中,删除的方法,但是普通的迭代器没有添加的方法
-
使用List集合中,特有的迭代器,ListIterator
-
ArrayList<String> list = new ArrayList<>(); list.add("java"); list.add("sql"); list.add("python"); list.add("c++"); ListIterator<String> it = list.listIterator(); while (it.hasNext()){ String s = it.next(); if ("sql".equals(s)){ it.add("PHP"); //it.set("HTML"); } } System.out.println(list);//[java, sql, PHP, python, c++]
-
-
-
-
当删除的元素是倒数第二个的时候,不会触发并发修改异常【源码的bug】
ArrayList<String> list = new ArrayList<>(); list.add("java"); list.add("sql"); list.add("python"); list.add("c++"); Iterator<String> it = list.iterator(); while (it.hasNext()){ String s = it.next(); if ("python".equals(s)){ list.remove(s); } } System.out.println(list);//[java, sql, c++]
-
-
使用forEach()遍历
-
default void forEach(Consumer<? super T> action):
-
list.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });
-
list.forEach(s -> System.out.println(s));
-
-
-
02、遍历总结
-
普通的for循环
能遍历数组,Collection中List集合,必须有索引
-
迭代器
Collection集合中的所有实现类和子类
-
增强for
Collection集合中的所有实现类和子类,和数组
※※※ 迭代器和增强for需要注意并发修改异常
-
forEach(函数式编程接口)
可以遍历所有的Collection
03、数据结构(特点)
数据结构指的是,数据组织的方式。不同的组织方式会有不同的特点
-
栈结构
先进后出
-
队列结构
先进先出
-
数组结构
数组是一种查询快,增删慢的模型
查询速度快
删除效率低
添加效率低
-
链表结构
链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。
链表特点:
查询效率低
增删效率相对较高
单链表:
从左找到右,是单方向的,所以称之为单向链表
双向链表:
查询速度比单向链表快,可以从前往后找,也可以从后往前找
-
二叉树、二叉查找树、平衡二叉树
每一个节点最多只有两个子节点
-
-
二叉查找树
-
二叉树查找树添节点
大的存右边,右子树
小的存左边, 左子树
一样的不存
-
二叉查找树可能存在的问题?
极端情况,左子树可能只有一个或者没有子节点,而右子树很高,导致查询的性能与单链表一样,查询速度变慢!
-
平衡二叉树
平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
平衡二叉树的要求:
任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
-
5.3.1、平衡二叉添加元素
-
平衡二叉树添加元素后可能导致不平衡
- 基本策略是进行左旋,或者右旋保证平衡
-
左旋:
- 将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点
-
右旋
- 将根节点的左侧往右拉,原先左子节点变成新的父节点,并把多余的右子节点出让,给已经降价的根节点当左子节点
-
平衡二叉树添加元素旋转的4中情况
-
左左
- 【左子树的左子树】添加新的元素导致不平衡
- 整体做一个右旋
- 【左子树的左子树】添加新的元素导致不平衡
-
左右
- 【左子树的右子树】插入新节点导致不平衡
-
先对左子树整体左一个左旋
-
-
再对整体做一个右旋
-
-
- 【左子树的右子树】插入新节点导致不平衡
-
右右
-
【右子树的右子树】插入新节点
-
整体做一个左旋
-
-
-
-
右左
- 【右子树的左子树】插入新节点
-
先对右子树整体做一个右旋
-
-
再对整体做一个左旋
-
-
- 【右子树的左子树】插入新节点
- 案例,现有一个平衡二叉树如下:
-
-
左左:【左子树的左子树】插入新节点 9
-
左右:【左子树的右子树】插入新节点 27
-
右右:【右子树的右子树】插入新节点 200
-
右左:【右子树的左子树】插入新节点 55 ,刚好平衡
-
-
红黑树(红黑树增删改查的性能都很好)
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。
每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
-
红黑规则
- 每一个节点或是红色的,或者是黑色的,根节点必须是黑色
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色
-
-
-
总结:
-
队列:先进先出,后进后出。
-
栈:后进先出,先进后出。
-
数组:内存连续区域,查询快,增删慢。
-
链表:元素是游离的,查询慢,首尾操作极快。
-
二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。
-
查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差
-
平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
-
红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
-
04、List
-
ArrayList和LinkedList是List的实现类,通用List的add、remove、set、get方法(增删改查)。
-
List系列集合通用方法
boolean add(E e); 在此集合的末尾添追加元素
void add(int index,E element); 在此集合中的指定位置插入指定的元素
E remove(int index); 删除指定索引处的元素,返回被删除的元素
E set(int index,E element); 修改指定索引处的元素,返回被修改的元素
E get(int index); 返回指定索引处的元素
1、ArrayList
-
ArrayList底层是什么结构?
- 数组结构:查询快、增删慢
/** * 往ArrayList集合中一次添加”孙悟空“、”猪八戒“、”沙和尚“、”小白龙“、”小白龙“、 * * 将最后一个元素修改为”唐僧“,再将最后一个元素置顶 */ ArrayList<String> list = new ArrayList<>(); list.add("孙悟空"); list.add("猪八戒"); list.add("沙和尚"); list.add("小白龙"); list.add("小白龙"); //1、将最后一个元素修改为”唐僧“ list.set(list.size() - 1, "唐僧"); //2、把最后一个元素插入到 0 索引处 list.add(0,list.get(list.size() - 1)); //3、把最后一个元素删除 list.remove(list.size() - 1);
-
ArrayList会自动创建一个长度为10的数组,当存满的时候,会再继续创建一个新的数组,新数组的长度是旧数组的 1.5 倍
把旧数组的内容拷贝到新数组中,旧数组会被回收。
2、LinkedList (双链表)
-
LinkedList的底层原理是一个双向链表,一个元素对应一个节点
原理:每次添加元素其实就是链表的一个节点,一次往链表的后面拼接,相对于List集合而言,多了一些针对头和尾操作的方法。
public void addFirst(E e); 在该列表开头插入指定的元素 public void addLast(E e); 将指定的元素追加到此列表的末尾
E getFirst(); E getLast(); E removeFirst(); E removeLast();
-
LinkedList list = new LinkedList();
-
list.get(1), 表面是拿着索引去找元素,底层还是去遍历一个一个节点的访问
-
底层原码
Node<E> node(int index) { // assert isElementIndex(index); //当前索引和长度的一半进行比较 if (index < (size >> 1)) { //当前索引离头结点近,就从头结点处开始遍历 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else {//当前索引离尾结点比较近,就从尾结点开始遍历 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
-
-
05、Set
-
哈希值(hashCode方法)
hashCode()方法的含义,获取一个哈希值(与地址值相关的整数)
在Object类中有一个hashCode()方法,任何一个对象都可以调用hashCode()方法获取哈希值
每一个对象都有不同的地址值,地址值不同那么哈希值就不同
如果子类复写了hashCode方法,哈希值就和属性值相关,如果属性值一样那么哈希值就一样
//Student类复写了hashCode()方法,如果两个对象的属性值一样,那么hashCode()就一样 System.out.println(stu1.hashCode());//24021582 System.out.println(stu2.hashCode());//24021582
1、HashSet
- HashSet底层原理
HashSet底层是:数组+链表组成的(哈希表结构)
-
特点:可以对元素进行去重
创建一个默认长度16的数组,数组名table
根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
判断当前位置是否为null
- 如果是null,说明这个位置没有元素,可以直接存入
- 如果不为null,说明这个位置有元素,需要进一步使用equals判断
如果equals判断不同,则以链表的形式插入元素;
- JDK 7新元素占老元素位置,指向老元素(头插法)
- JDK 8中新元素挂在老元素下面(尾插法)
- 如果equals判断相同,认为是同一个元素(不存)
去重原理:
1、当调用集合的添加方法时,首先会调用该对象的hashCode()方法,计算出一个哈希值
2、拿着这个哈希值,去数组中查找,是否有相同的
--没有相同的 : 直接存入
--有相同的 : 进行equals判断
3、调用该对象的equals方法,比较对象的内容
--false : 代表哈希值虽然相同,但是内容不同 --> 存入
--true : 代表哈希值相同,内容也相同 ---> 不存
效率:
--如果hashCode()方法的返回值是固定的,将会调用很多次equals()方法
-
JDK8版本后HashSet底层是:数组+链表+红黑树组成的(哈希表结构)
当链表的长度超过 8 的时候,就会把链表转为红黑树
2、LinkedHashSet
-
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
-
有序、不重复、无索引。
3、TreeSet
-
TreeSet可以对元素进行自动排序,底层基于红黑树实现
-
必须指定排序规则
-
如果没有指定排序规则,就会报错
ClassCastException: class com.itheima.d3_Set.Student cannot be cast to class java.lang.Comparable
第一种:让元素实现Comparable接口,默认排序
-
实现Comparable接口
public class Student implements Comparable<Student>
-
复写CompartTo方法
@Override public int compareTo(Student o) { //TreeSet集合底层会根据CompareTo方法的返回值是整数、负数、0来决定, // 将元素存储在二叉树的左边,还是右边,0就不存 /* this :将要添加到集合中的元素 o :集合中已有的元素 */ return this.getAge() - o.getAge();//按照年龄升序排序 }
- Test类测试
Student stu1 = new Student("张三",23); Student stu2 = new Student("张三",23);//重复,不会存到TreeSet集合中 Student stu3 = new Student("李四",24); Student stu4 = new Student("王五",25); TreeSet<Student> set = new TreeSet<>(); set.add(stu1); set.add(stu2); set.add(stu3); set.add(stu4); for (Student student : set) { System.out.println(student.getName() +"....." + student.getAge()); }
第二种:使用比较器Comparator接口,比较器排序
如果元素是自定义类型、或者元素本身具备模式排序规则,但是默认排序规则并不是自己想要的,就可以使用比较器自定义排序
- 创建TreeSet集合的时候,就给TreeSet构造方法中传入一个Comparator比较器
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o2.getAge() - o1.getAge(); } });
-
-
TreeSet集合的特点是怎么样的?
可排序、不重复、无索引
底层基于红黑树实现排序,增删改查性能较好
-
TreeSet集合指定排序规则有几种方式
默认排序规则Comparable(源码)
集合自定义Comparator比较器对象,重写比较规则
同时指定了自然排序,还指定了比较器排序,优先按照比较器排序
day06_Map、泛型、Collections类
01、泛型
-
泛型只在编译阶段有效,在运行阶段就会被擦除。
-
概念
- 什么是泛型?
泛型允许程序员在编写代码时使用一些以后才能确定的数据类型
定义泛型:<字母> 如:、
在创建对象的时候才指定明确的数据类型
ArrayList<String> list = new ArrayList<>();//list 集合只能存储String
ArrayList<Student> stu = new ArrayList<>();//stu集合只能存储学生对象
- 泛型的好处:
- 在编译时期起到限定数据类型的作用
- 将运行期的错误,提升到了编译期,避免后期进行强制类型转换
-
自定义泛型类
-
泛型类的格式:class 类名<泛型变量>{ }
自定义一个MyArrayList类,简单模拟ArrayList集合
1、类上的泛型是什么含义?
在类中有不确定的数据类型,使用表示
2、类上的泛型什么时候确定?
创建该类对象确定。
-
public class MyArrayList <T>{ private Object[] array = new Object[10]; private int index; public void add(T t){ array[index] = t; index++; } public T get(int index){ return (T)array[index]; } public T set(int index,T t){ Object obj = array[index]; array[index] = t; return (T)obj; } @Override public String toString() { return Arrays.toString(array); } }
-
-
自定义泛型方法
格式: public 返回值类型 方法名(T t){…}
//需求:写一个工具方法,可以往Collection系列的任意集合中添加任意类型的三元素,并把集合返回。 public static <T>Collection<T> addElement(Collection<T> coll,T t1,T t2,T t3){ coll.add(t1); coll.add(t2); coll.add(t3); return coll; }
-
自定义泛型接口
-
格式: public interface 接口名{}
泛型接口的作用:
可以让实现类选择当前功能需要操作的数据类型
public interface Dao <T>{ //添加一个 T 类型对象 void add(T t); //获取一个 T 类型对象 T get(String id); }
-
1、实现接口
// 实现泛型接口,指定具体的数据类型为 Student public class Demo3Dao implements Dao<Student> { @Override public void add(Student student) { } @Override public Student get(String id) { return null; } }
-
2、实现接口的时候,不指定具体类型,将泛型传递到自己的类当中
class A<T> implements Dao<T>{ @Override public void add(T t) { } @Override public T get(String id) { return null; } }
-
-
泛型通配符
-
泛型通配符的作用
-
泛型通配符一般用在方法的参数位置,限定参数的数据类型
- <?>表示任意类型
public static void main(String[] args) { //String 字符串类型 ArrayList<String> list1 = new ArrayList<>(); //Integer类型 ArrayList<Integer> list2 = new ArrayList<>(); method(list1); method(list2); } public static void method(ArrayList<?> list){ }
- <? extends 指定类型> 指定类型或者指定类型的子类型
public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); //method(list1);报错 method(list2);//class Integer extends Number, } public static void method(ArrayList<? extends Number> list){ }
- <? super 指定类型> 指定类型或者指定类型的父类
public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); //method(list1);报错 //method(list2);报错 ArrayList<Object> list3 = new ArrayList<>(); method(list3);//Number extends Object } public static void method(ArrayList<? super Number> list){ }
-
-
02、可变参数
-
什么是可变参数?
需求1:计算两个整数的和 public static void getSum1(int a ,int b){ return a + b; } 需求2:计算三个整数的和 public static void getSum2(int a ,int b, int c){ return a + b +c; } 需求:计算 n 个整数的和? 由于需要动态传入参数,参数的个数是不确定的,就就可以使用动态参数,接受不定的参数个数 public static void getSum2(int. . .arr){ int sum = 0; for(int i = 0; i < arr.length; i++){ sum += arr[i]; } return sum; }
-
可变参数(int . . . arr)本质上是一个数组
int[] array = {1,2,3}; int sum = getSum2(array);//可直接传入一个数组对象 public static void getSum2(int. . .arr){ int sum = 0; for(int i = 0; i < arr.length; i++){ sum += arr[i]; } return sum; }
-
-
当除了可变参数还有其他参数的时候,可变参数一定要放在参数列表中的最后一个
public static void getSum3(String str,int. . .arr){...}
03、Collections类
-
Collection是所有单列集合的跟接口,而Collections是对集合进行操作的工具类
-
Collections类的常用方法
- 给集合对象批量添加元素
public static <T> boolean addAll(Collection<? super T> coll, T . . . t);
ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"world","java","hello","abc");
- 打乱List集合元素的顺序
public static void shuffle(List<?> list);
ArrayList<String> list = new ArrayList<>(); Collections.shuffle(list);
- 将集合中元素按照默认规则排序
public static <T> void sort(List<?> list);
ArrayList<String> list = new ArrayList<>(); Collections.sort(list);
- 将集合中元素按照指定规则排序
public static <T> void sort(List<T> list, Comparator<? super T> c);
ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"world","java","hello","abc"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { /** * String 的compareTo()方法 * 按照字典顺序排序,如果字符串小于参数字符串,返回负数,反之则返回正数 */ return o1.compareTo(o2); } });
04、Map
- Map是双列集合,双列集合的元素是成对出现的,每一个元素都包含一个键和值
- Map的体系结构
- HashMap的键是无序的
- LinkedHashMap的键是有序的
- TreeMap的键是可以进行排序
-
键不能重复,值可以重复
-
Map集合常用API
-
Map<String,String> map = new HashMap<>();
-
V put(K key, V value) 设置键值对
map.put("贾乃亮","李小璐"); map.put("贾乃亮","李小龙");//新值会替换旧值,但是键不变,只是值改变了
-
V remove(Object key) 根据键删除键值对元素
map.remove("贾乃亮"); //返回被移除的键所对应的值
-
void clear() 移除所有键值对元素
map.clear()
-
boolean containsKey(Object obj) 判断集合是否包含指定键
boolean obj2 = map.containsKey("贾乃亮");
-
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean obj3 = map.containsValue("白百合");
-
boolean isEmpty() 判断集合是否为空
boolean bl = map.isEmpty();
-
int size() 集合的长度,也就是集合中键值对的个数
int size = map.size();
-
05、遍历Map集合
1、遍历方式一
-
键找值,需要用到的方法: map.keySet()、map.get()
HashMap<String,String> map = new HashMap<>(); map.put("贾乃亮", "李小璐"); map.put("王宝强","马蓉"); map.put("羽泉","白百合");
-
获取map集合中所有的键 map.keySet();
Set<String> key = map.keySet();//Set集合保证元素不重复,而map中的键是不重复的
-
通过键匹配值 String value = map.get(s);
for (String s : key) { String value = map.get(s); System.out.println(s + "....." + value); }
-
2、直接获取【键值对】
-
调用Map提供的entrySet()方法,获取所有的【键值对】组成的Set集合
将键值对封装到Map.Entry类
Set<Map.Entry<String, String>> entries = map.entrySet();
-
遍历Set集合,然后提取【键值对】中的键和值
for (Map.Entry<String, String> entry : entries) { //K getKey() 获得键 String key = entry.getKey(); //V getValue() 获得值 String value = entry.getValue(); System.out.println(key + "... " + value); }
-
Set<Map.entry<K,V>> entrySet() 获取所有键值对对象的集合
-
K getKey() 获得键
-
V getValue() 获得值
3、forEach()方法
- JDK8提供的一个新的方法 forEach,结合Lamdba表达式使用更加方便
default void forEach(BiConsumer<? super k,? super V> action); 结合Lambda遍历Map集合
//使用forEach遍历集合 map.forEach(new BiConsumer<String, String>() { @Override public void accept(String key, String value) { System.out.println(key + "..." + value); } });
//使用Lambda遍历集合 map.forEach((key, value) -> System.out.println(key + "..." + value));
06、HashMap
-
HashSet的底层就是HashMap,HashSet底层原理和HashMap是一模一样的【哈希表结构】
-
HashSet可以保证元素的唯一性,HashMap可以保证键的唯一性,通过重写,equals()和hashCode()方法,可以保证唯一性
07、LinkedHashMap
- LinkedHashSet的底层原理和LinkedHashMap 是一样的,比他们的父类多了一个双向链表,可以保证元素的存储顺序。
08、TreeMap
- TreeSet和TreeMap的底层原理是一样,都是红黑树
- TreeSet可以对元素进行排序
- TreeMap可以对键进行排序
- 同时,都要指定排序规则。
day07_不可变集合,Stream,异常体系
01、不可变集合
-
什么是不可变集合?
- 在创建集合时就已经添加好了元素,之后不能添加、不能删除、不能修改
-
获取不可变集合的方法
-
这些方法是JDK9之后才有的,List、Set、Map都可以获取不可变集合
-
方法名称 说明 static List of(E…elements) 创建一个具有指定元素的List集合对象 static Set of(E…elements) 创建一个具有指定元素的Set集合对象 static <K , V> Map<K,V> of(E…elements) 创建一个具有指定元素的Map集合对象 public static void main(String[] args) { List<String> list = List.of("张三", "李四", "王五"); //list.add("赵六"); //UnsupportedOperationException //list.remove(1);//UnsupportedOperationException //list.set(0,"小二");//UnsupportedOperationException System.out.println(list); Set<Integer> set = Set.of(100,200, 300, 400); System.out.println(set); Map<String, Integer> map = Map.of("张三", 20, "李四", 24, "王五", 25); System.out.println(map); }
-
02、Stream
1、概述
-
什么是Stream?
Stream流指的是流式编程,用于简化集合和数组的操作
- 需求:把存储姓名的集合中,提取姓氏是 “ 张 ”,名字是三个字的姓名
- 1、把姓名以“张” 开头的名字筛选出来,str . startsWith(" 张 ")
- 2、在1、的基础上,把姓名是三个字的提取出来,str.lenth() == 3
- 使用流式编程
- list.stream().filter(s - > s.startsWith(" 张 ")).filter(s -> s.length() == 3)
- 需求:把存储姓名的集合中,提取姓氏是 “ 张 ”,名字是三个字的姓名
-
Stream流式思想的核心
- 先得到集合或者数组的Stream流(传送带)
- 然后用Stream流提供方法对流中的数据进行操作(加工的环节)
-
Stream流的作用是什么?结合了什么技术?
- 简化集合、数组操作的API。结合了Lambda表达式
2、获取Stream流
数组、获取Stream流
public static<T> Stream<T> of(T... values) 获取当前数组/可变数据的Stream流
举例:
Stream<Integer> s1 = Stream.of(12, 54, 36, 55);
Integer[] arr = {1,2,3,4,5,6};//数组的声明需要使用引用类型 Stream<Integer> s1 = Stream.of(arr); //数组声明使用基本数据类型,错误示范: int[] arr = {1,2,3,4,5,6}; Stream<int[]> s1 = Stream.of(arr);//Stream<int[]> int[]类型是错误的泛型
集合、获取Stream流
default Stream<E> stream() 获取当前集合对象的Stream流
单列集合
List<String> list = List.of("张三", "李四", "王五"); //list获取Stream流 Stream<String> stream1 = list.stream();
双列集合转为单列集合,再获取Stream流
Map<String, Integer> map = Map.of("张三", 23, "李四", 24, "王五", 25); //获取键 Set<String> keys = map.keySet(); //键的Stream流 Stream<String> stream1 = keys.stream(); //获取值 Collection<Integer> values = map.values(); //值的Stream流 Stream<Integer> stream2 = values.stream(); //获取键值对 Set<Map.Entry<String, Integer>> entries = map.entrySet(); //键值对的Stream流 Stream<Map.Entry<String, Integer>> stream3 = entries.stream();
3、Stream流的常用API( 中间操作方法 )
Stream<T> filter(Predicate<? super T > predicate) 用于对流中的数据进行过滤
Stream<T> limit(long maxSize) 获取前几个元素
Stream<T> skip(long n) 跳过前几个元素
Stream <T> distinct() 去除流中重复的元素。依赖(hashCode和equals方法)
Static <T> Stream <T> concat(Stream s1,Stream s2) 合并s1和s2两个流为一个流
Stream<T> sorted() 对流中的元素进行排序,自己指定排序规则
Stream<T> mapToXxx() 把流中的元素转换为任意类型 比如:mapToDouble()
Stream<T> map() 把流中的元素转换为任意类型
- 举例:
List<String> list1 = List.of("张三", "张无忌", "小龙女", "周伯通", "杨过", "金轮法王", "一修大师"); List<String> list2 = List.of("李逵", "宋公明", "高俅", "林冲", "小龙女", "武松", "张无忌"); Stream<String> stream1 = list1.stream(); Stream<String> stream2 = list2.stream(); //合并两个Stream流 Stream<String> stream3 = Stream.concat(stream1, stream2); // 对流中的数据进行过滤 名字三个字、 去重、 截取前3个, 跳过第一个 stream3.filter(s -> s.length() == 3).distinct().limit(3).skip(1).forEach(s -> System.out.println(s));
4、Stream流的常见终结的操作方法
Optional<T> max(Comparator<? super T> comparator) 返回最大元本流根据提供的 Comparator。
/* stream().max()筛选出最大 返回值: Optional<Employee>是一个封装了Employee对象的对象 */ Optional<Employee> max1 = list1.stream().max((o1, o2) -> o1.getMoney() - o2.getMoney() >= 0 ? 1 : -1); /* max.get(); 返回值:返回被这个对象封装的Employee对象 */ Employee employee1 = max1.get();
void forEach(Consumer action) 对此流的每个元素指向遍历操作
long count() 返回此流中的元素个数
-
注意:
-
Stream流调用完方法之后其结果还是一个Stream流,可以继续调用Stream的方法,支持链式编程。
-
在Stream流中无法直接修改集合、数组中的数据,数组或是集合中的数据没有任何改变
-
终结操作方法,调用完成之后,就无法继续使用了,原因是不会返回Stream流了。
stream3.filter(s -> s.length() == 3).distinct().limit(3).skip(1).forEach(s -> System.out.println(s)); stream3.forEach(s -> System.out.println(s));//运行报错 IllegalStateException
终结和非终结方法的含义是什么?
- 终结方法后流不可以继续使用
- 非终结方法会返回新的Stream流,支持链式编程
-
-
案例
需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算, 要求如下: :每个员工信息至少包含了(姓名、性别、工资、奖金、处罚记录) :开发一部有4个员工、开发二部有5名员工 :分别筛选出2个部门的最高收入的员工信息,封装成优秀员工对象Topperformer 优秀员工包含:姓名和实际收入两个属性 ④:分别统计出2个部门的平均月收入,要求去掉最高收入和最低收入。 ⑤:统计2个开发部门整体的平均月收入,要求去掉最高收入和最低收入。
部分代码
//开发部门二的平均月收入,要求去掉最高收入和最低收入。 /* sorted(比较器) 指定排序规则 按照工资从小到大进行排序,返回值是 Stream<Employee> limit(list2.size() -1) 截取到倒数第二个 ,即去掉最后一个最高工资 skip(1) 跳过第一最低工资 mapToDouble 把流中的元素从员工类转换为一个DoubleStream类型 average() 求流中的平均值,返回值是一个OptionanlDouble getAsDouble(); 获取OptionanlDouble封装的Double值,返回基本数据类型double */ double avg2 = list2.stream().sorted((o1, o2) -> o1.getMoney() - o2.getMoney() >= 0 ? 1 : -1) .limit(list2.size() -1) .skip(1) .mapToDouble(value -> value.getMoney()) .average() .getAsDouble(); System.out.println("开发部门二的平均月收入" + avg2);
5、收集Stream流
-
Stream流的收集方法,可以把流中的元素再收集到集合中
R collect(Collector collector) 开始收集Stream流,指定收集器
-
Collectors工具类提供了具体的收集方式
public static <T> Collector toLlist() 把元素收集到List 集合中
-
举例
List<Integer> list1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); List<Integer> collect1 = list1.stream().filter(s -> s % 2 == 0).collect(Collectors.toList()); System.out.println(collect1);//[2, 4, 6, 8]
public static <T> Collector toSet() 把元素收集到Set集合中
-
举例
List<Integer> list1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); Set<Integer> collect1 = list1.stream().filter(s -> s % 2 == 1).collect(Collectors.toSet()); System.out.println(collect1);//[1, 3, 5, 7, 9]
public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map 集合中
-
举例
ArrayList<Employee> list1 = new ArrayList<>(); //开发部门一、 list1.add(new Employee("孙悟空","男",20000,5000,"大闹天宫")); list1.add(new Employee("猪八戒","男",15000,3000,"调戏良家少女")); list1.add(new Employee("沙和尚","男",16000,4000,"打碎琉璃盏")); list1.add(new Employee("小白龙","男",10000,2000,"吃了白马")); //把开发部中,员工的工资 > 15000的员工提取到一个新集合,只需要姓名和工资 Map<String, Double> map = list1.stream().filter(s -> s.getSalary() > 15000).collect(Collectors.toMap( //把员工的姓名当做map集合中的键 name -> name.getName(), //把员工的工资当做map集合中的值 salary -> salary.getSalary() )); System.out.println(map);//{沙和尚=16000.0, 孙悟空=20000.0}
- 把元素收集到数组中
public static <T> Collector toArray() 把元素收集到数组中
-
举例
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); //toArray()返回的是 Object的数组 Object[] objects = list.stream().filter(s -> s % 2 == 0).toArray();
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); /* new Integer[value] 把流中的元素返回到Integer数组中 value 表示剩余元素个数, 创建一个 Integer 数组,长度为value, */ Integer[] array = list.stream().filter(s -> s % 2 == 0).toArray(value -> new Integer[value]); System.out.println(Arrays.toString(array)); //[2, 4, 6, 8]
-
03、异常体系
-
什么是异常?
- 异常是程序在编译或者执行的过程可能出现的问题
- Java语言的设计者为了让开发者知道异常产生的原因,把异常设计成了类
-
学习异常的目的
-
避免异常的出现,同时处理可能出现的异常,让代码更稳健
-
异常的体系
常见的运行时异常
-
数组索引越界异常 ArrayIndexOutOfBoundsException
-
空指针异常 NullPointerException
-
数学操作异常 ArithmeticException 举例:【除数为 0 】
-
类型转换异常 ClassCastException 原因:在多态的情况下,父类 、接口转换为子类,类型转错了 【原本是什么类型,才可以转换为什么类型】
-
数字转换异常 NumberFormatException 原因:在调用方法时,参数的数据格式有问题 举例: int num = Integer.parseInt("12abc3");
-
-
编译时异常
- 不管代码真的有问题,在编译阶段就会报错,目的是提醒程序员不要出错
- 特点:
- 继承Exception的异常或者其子类
- 编译阶段报错,必须处理,否则代码不通过
-
如何产生异常
- 在写一个方法时,考虑到调用者可能传递非法数据,需要对调用者传递过来的数据进行合法性的校验,如果数据校验不合法就可以通过throw new产生一个异常对象,并抛给调用者,让调用者知道为什么错了。
-
注意:
-
如果方法中产生的是运行时异常:不需要在方法上声明
-
如果方法中产生的是编译时异常:需要在方法上用throws声明
-
-
遇到有异常的方法如何处理?
-
如果遇到的是运行时异常,一般不需要处理,直接改代码就行;如果改代码解决不了,也能使用try…catch处理
-
如果遇到编译时异常,可以继续使用throws声明,抛给下一个调用者处理,也可以使用try…catch捕获异常
-
-
自定义异常类
- 如果Java的API中提供的异常类,不足以描述你的问题;那么你就可以自定义异常类
- 只需要让类继承Exception或者RuntimeException即可
--------------------榆木脑袋,炼气期二层--------------------2024-03-26 0:13:45