👉 在线笔记:https://du1in9.github.io/javase.github.io/
Day 1 面向对象高级
第一部分
1.1 static 修饰成员的特点
public class Student {
String name;
int age;
static String school;
}
/*
static 关键字 : 修饰符, 可以修饰成员变量, 成员方法
特点:
1. 被类的所有对象所共享
2. 多了一种调用方式, 可以通过类名进行调用 (推荐使用类名调用)
3. 随着类的加载而加载, 优先于对象存在
*/
Student.school = "传智专修学院";
Student stu1 = new Student();
stu1.name = "张三";
stu1.age = 23;
System.out.println(stu1.name + "---" + stu1.age + "---" + Student.school); // 张三---23---传智专修学院
Student stu2 = new Student();
System.out.println(stu2.name + "---" + stu2.age + "---" + Student.school); // null---0---传智专修学院
1.2 static 修饰成员变量内存图解
1.3 static 修饰成员方法
/*
1. 成员方法什么时候加入 static
- 常用于制作工具类
2. 工具类: 不是描述事物的, 而是帮我们完成一些事情 (打工)
3. 如果发现一个类中, 所有的方法, 全都是 static 所修饰
- 私有该类的构造方法
- 目的: 为了不让其他类, 再创建对象
*/
int[] arr = {11,22,33};
System.out.println(ArrayTools.getMax(arr));
System.out.println(ArrayTools.getMin(arr));
public class ArrayTools {
private ArrayTools(){}
public static int getMax(int[] arr) {...}
public static int getMin(int[] arr) {...}
}
- static 方法中, 只能访问静态成员 (直接访问)
- static 中不允许使用 this 关键字
重新认识 main 方法:
1.4 继承介绍和使用
继承:让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员
Coder c = new Coder();
c.name = "张三";
c.age = 23;
c.salary = 12000;
class Employee {
String name;
int age;
double salary;
}
class Coder extends Employee {
}
class Manager extends Employee {
}
Coder c = new Coder();
c.setName("张三");
c.setAge(23);
c.setSalary(12000);
class Employee {
private String name;
private int age;
private double salary;
// ...(JavaBean)
}
class Coder extends Employee {}
class Manager extends Employee {}
什么时候使用继承?
1.5 继承中的成员变量和成员方法
思考:子父类中,如果出现了重名的成员变量,使用的时候会优先使用? 答:就近原则
/*
子父类中, 出现了方法声明一模一样的方法 (方法名, 参数, 返回值)
在创建子类对象, 调用方法的时候, 会优先使用子类的方法逻辑
这虽然是就近原则的现象, 但其实是子类的方法, 对父类的方法, 进行了重写操作
*/
Zi z = new Zi();
z.method();
z.show(); // Zi...show
class Fu {
int num = 10;
public void show(){
System.out.println("Fu...show");
}
}
class Zi extends Fu {
int num = 20;
public void method(){
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
System.out.println(super.num); // 10
}
public void show(){ // 方法重写
System.out.println("Zi...show");
}
}
1.6 方法重写
/*
方法重载(Overload) : 在同一个类中, 方法名相同, 参数不同, 与返回值无关
参数不同: 类型不同, 个数不同, 顺序不同
方法重写(Override) : 在子父类当中, 出现了方法声明一模一样的方法 (方法名, 参数, 返回值)
目标1: 能够独立识别出, 方法是不是重写的方法
- 注解: @Override
目标2: 方法重写的使用场景.
- 当子类需要父类的方法, 但是觉得父类的方法逻辑不好 (修改 | 增强) 就可以对父类的方法进行重写
*/
Son s = new Son();
s.love();
class Father {
public void love() {
System.out.println("送花");
}
}
class Son extends Father {
@Override
public void love() {
System.out.println("送酱肉包");
}
}
注意事项:
- 父类中私有方法不能被重写
- 子类重写父类方法时,访问权限必须大于等于父类
1.7 权限修饰符 & 继承的特点
package com.itheima.a;
public class Fu {
// 所以实际开发中,几乎不用protected,主要用: private, public
protected void show(){
System.out.println("protected...fu...show");
}
}
package com.itheima.b;
import com.itheima.a.Fu;
// 不同包下的子类
public class Zi extends Fu {
public void method(){
super.show();
}
}
package com.itheima.b;
// 不同包下的无关类
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
// z.show(); 报错
z.method();
}
}
Java只支持单继承,不支持多继承,但支持多层继承
C c = new C();
c.methodC(); // C...
c.methodB(); // B...
c.methodA(); // A...
class A {
public void methodA() {
System.out.println("A...");
}
}
class B extends A {
public void methodB() {
System.out.println("B...");
}
}
class C extends B {
public void methodC() {
System.out.println("C...");
}
}
Q:为什么不支持多继承?
A:因为多继承会引起方法引用的冲突;而多层继承会方法重写,所以不会冲突
1.8 继承中构造方法的访问特点
/*
除了Object类, 在所有构造方法的第一行代码, 都默认隐藏了一句话 super();
通过这句代码, 访问父类的空参数构造方法
细节: Java当中所有的类, 都直接或者间接的继承到了 Object 类
*/
Zi z1 = new Zi();
Zi z2 = new Zi(10);
class Fu {
public Fu() {
System.out.println("Fu类的空参构造方法");
}
public Fu(int num) {
System.out.println("Fu类的带参构造方法...");
}
}
class Zi extends Fu {
public Zi() {
System.out.println("Zi类的空参构造方法");
}
public Zi(int num) {
System.out.println("Zi类的带参构造方法...");
}
}
Student stu = new Student("李四", 20, 100);
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// ...(JavaBean)
}
public class Student extends Person {
private double score;
public Student() {
}
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
1.9 继承内存图
1.10 super 的省略规则
/*
super 调用父类成员的省略规则 :
super.父类成员变量 | super.父类成员方法()
-> 被调用的变量和方法, 在子类中不存在, super.可以直接省略的
*/
Zi z = new Zi();
z.method();
class Fu {
int num = 10;
public void show(){
System.out.println("Fu...show");
}
}
class Zi extends Fu {
public void method(){
System.out.println(num);
show();
}
}
1.11 this 和 super
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.本类成员变量; | this.本类成员方法(); | this(); this(…);本类构造方法 |
super | super.父类成员变量; | super.父类成员方法(); | super(); super(…);父类构造方法 |
// 开闭原则 : 对功能扩展做开放, 对修改代码做关闭
A a1 = new A(1, 2, 3);
A a2 = new A(4, 5, 6);
A a3 = new A(1,2,3,4);
/*
项目Version1.0版本 : 有3个成员变量
项目Version1.1版本 : 新增一个成员变量
*/
class A {
int a;
int b;
int c;
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) {
// super; 注意:this() 和 super() 都在争夺构造方法第一行的位置,所以二者不能共存.
this(a, b, c);
this.d = d;
}
}
1.12 final 关键字
final 关键字是最终的意思,可以修饰(方法,类,变量)
final 修饰的特点
- 修饰方法:表明该方法是最终方法,不能被重写
- 修饰类:表明该类是最终类,不能被继承
- 修饰变量:表明该变量是常量,不能再次被赋值
- 变量是基本类型:final 修饰指的是基本类型的数据值不能发生改变
- 变量是引用类型:final 修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
- 成员变量如果被 final 修饰,需要在构造方法结束之前完成赋值
/*
final修饰变量的命名规范 :
如果变量名是一个单词, 所有字母大写 max MAX
如果变量名是多个单词, 所有字母大写, 中间使用下划线分割 maxValue MAX_VALUE
*/
final int[] arr = {11,22,33};
// arr = new int[3];
arr[0] = 100;
class Student {
final int num = 20; // 修饰变量
public final void method() { // 修饰方法
}
}
final class Fu extends Object { // 修饰类
public void show() {
}
public void print() {
}
}
第二部分
2.1 package 包和抽象类介绍
什么是包?
- 包本质来说就是文件夹, 用来管理类文件的
- 建包的语法格式:package 公司域名倒写.技术名称。报名建议全部英文小写,且具备意义
- 建包语句必须在第一行,一般IDEA工具会帮助创建
导包
- 相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!导包格式:import 包名.类名;
- 假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问。
package com.itheima.a;
public class Student {
public void eat(){
System.out.println("学生吃饭");
}
}
package com.itheima.b;
public class Student {
public void sleep(){
System.out.println("学生睡觉");
}
}
package com.itheima.c;
import com.itheima.a.Student;
Student stu1 = new Student();
com.itheima.b.Student stu2 = new com.itheima.b.Student();
抽象方法:将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法。
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类
abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
2.2 抽象类的注意事项
- 抽象类不能实例化
- 抽象类存在构造方法
- 抽象类中可以存在普通方法
- 抽象类的子类:要么重写抽象类中的所有抽象方法;要么是抽象类
Zi z = new Zi();
z.method();
abstract class Fu {
public Fu(){ // 构造方法
}
public abstract void show();
public void method(){ // 存在普通方法
System.out.println("method...");
}
}
class Zi extends Fu {
public Zi(){
}
@Override
public void show() { // 重写所有抽象方法
}
}
abstract 关键字的冲突
- final:被 abstract 修饰的方法,强制要求子类重写,被 final 修饰的方法子类不能重写
- private:被 abstract 修饰的方法,强制要求子类重写,被 private 修饰的方法子类不能重写
- static:被 static 修饰的方法可以类名调用,类名调用抽象方法没有意义
2.3 接口介绍以及定义和实现
接口:体现的思想是对规则的声明;Java中的接口更多体现的是对行为的抽象。
- 接口用关键字 interface 来定义
public interface 接口名 {}
- 接口不能实例化
- 接口和类之间是实现关系,通过 implements 关键字表示
public class 类名 implements 接口名 {}
- 接口的子类(实现类):要么重写接口中的所有抽象方法;要么是抽象类(不推荐)
/*
接口: 体现的思想就是声明 [规则]
- 左边是接口引用, 右边一定是实现类对象 ★
思路: 如果发现一个类, 所有的组成, 都是抽象方法
- 没有成员变量, 没有普通方法
这种类, 我们通常会设计为Java中的接口, 因为现在这个类存在的唯一价值, 就只是声明规则了
*/
InterImpl ii = new InterImpl();
ii.method();
ii.show();
interface Inter {
public abstract void show();
public abstract void method();
}
class InterImpl implements Inter {
@Override
public void show() {
System.out.println("show...");
}
@Override
public void method() {
System.out.println("method...");
}
}
2.4 接口中的成员特点
-
成员变量:只能是常量,默认修饰符:public static final
-
构造方法:没有
-
成员方法:只能是抽象方法,默认修饰符:public abstract
关于接口中的方法,JDK8 和 JDK9 中有一些新特性
package com.itheima.a;
public interface Inter {
static final int num = 10;
}
import com.itheima.a.Inter;
System.out.println(Inter.num); // 10
interface MyInter {
int NUM = 10; // public static final
void method(); // public abstract
}
2.5 类和接口之间的关系
-
类和类的关系
继承关系,只能单继承,但是可以多层继承 -
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口 -
接口和接口的关系
继承关系,可以单继承,也可以多继承
class Fu {
public void show() {
System.out.println("Fu..show");
}
}
interface A {
void show(); // 抽象方法
}
interface B { // 没有构造方法
void show();
}
class Zi extends Fu implements A, B { // 在继承一个类的同时实现多个接口
}
interface InterC extends InterA, InterB { // 接口和接口之间多继承
void show();
}
class InterCImpl implements InterC { // 干爹InterA,InterB,InterC; 亲爹Object
@Override
public void show() { // 重写方法
}
}
2.6 抽象类和接口的对比
- 成员变量:抽象类 : 可以定义变量, 也可以定义常量;接口 : 只能定义常量
- 成员方法:抽象类 : 可以是定义具体方法, 也可以定义抽象方法;接口 : 只能定义抽象方法
- 构造方法:抽象类 : 有;接口 : 没有
- 抽象类 : 对事物做抽象 (描述事物)
- 接口 : 对行为抽象 (制定规则)
2.7 多态引入
多态介绍:同一个行为具有多个不同表现形式或形态的能力
public interface OrderService { // 同一个行为
void create();
void findOne();
}
public class OrderServiceImpl implements OrderService { // 表现形式1
@Override
public void create() {System.out.println("国内业务 --- 创建订单");}
@Override
public void findOne() {System.out.println("国内业务 --- 查询单个订单");}
}
public class OverseasServiceImpl implements OrderService { // 表现形式2
@Override
public void create() {System.out.println("国外业务 --- 创建订单");}
@Override
public void findOne() {System.out.println("国外业务 --- 查询单个订单");}
}
OrderService order = null;
switch (choice) {
case 1:
order = new OrderServiceImpl();
break;
case 2:
order = new OverseasServiceImpl();
break;
}
order.create();
order.findOne();
2.8 多态的前提
- 有继承 / 实现关系
- 有方法重写
- 有父类引用指向子类对象
/*
1. 对象多态 好处: 方法的形参定义为父类类型, 这个方法就可以接收到该父类的任意子类对象了
2. 行为多态 好处: 同一个方法, 具有多种不同表现形式, 或形态的能力
*/
useAnimal(new Dog()); // 狗吃肉
useAnimal(new Cat()); // 猫吃鱼
// 有父类引用指向子类对象
public static void useAnimal(Animal a) { // Animal a = new Dog();
a.eat(); // Animal a = new Cat();
}
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal { // 有继承关系
@Override
public void eat() {System.out.println("狗吃肉");} // 有方法重写
}
class Cat extends Animal {
@Override
public void eat() {System.out.println("猫吃鱼");}
}
2.9 多态的成员访问特点
/*
多态的成员访问特点:
1. 成员变量 : 编译看左边(父类), 运行看左边(父类)
2. 成员方法 : 编译看左边(父类), 运行看右边(子类)
在编译的时候, 会检查父类中有没有这个方法
没有 : 编译出错
有 : 编译通过, 但是运行的时候, 一定会执行子类的方法逻辑
原因: 担心你调用的方法, 在父类中是一个抽象方法
--------------------------------------------------------------------------
多态创建对象, 调用静态成员 :
静态的成员, 推荐类名进行调用
细节: 静态的成员, 可以使用对象名调用, 但这是一种假象
- 生成字节码文件后, 会自动将对象名调用, 改成类名调用
*/
Fu f = new Zi();
System.out.println(f.num); // 10 编译看左边(父类), 运行看左边(父类)
f.show(); // Zi...show 编译看左边(父类), 运行看右边(子类)
f.print(); // Fu...print 对象名调用, 实际上是类名调用
Fu.print(); // Fu...print 类名调用
Inter i = new InterImpl();
i.method();
class Fu {
int num = 10;
public void show() {
System.out.println("Fu...show");
}
public static void print(){
System.out.println("Fu...print");
}
}
class Zi extends Fu {
int num = 20;
public void show() {
System.out.println("Zi...show");
}
public static void print(){
System.out.println("Zi...print");
}
}
-
多态的好处 :提高了程序的扩展性
对象多态 : 将方法的形参定义为父类类型, 这个方法可以接收该父类的任意子类对象
行为多态 : 同一个行为, 具有多个不同表现形式或形态的能力
-
多态的弊端:不能使用子类的特有成员
2.10 多态的转型
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
public class OverseasServiceImpl implements OrderService {
public void check() {System.out.println("IP地址检测");}
@Override
public void create() {System.out.println("国外业务 --- 创建订单");}
@Override
public void findOne() {System.out.println("国外业务 --- 查询单个订单");}
}
OrderService order = null;
switch (choice) {
case 1:
orderService = new OrderServiceImpl();
break;
case 2:
orderService = new OverseasServiceImpl();
break;
}
/*
ClassCastException : 类型转换异常
在引用数据类型的强转中, [实际类型]和[目标类型]不匹配, 就会出现此异常
instanceof : 判断左边的引用, 是否是右边的数据类型
*/
if(order instanceof OverseasServiceImpl){
OverseasServiceImpl osi = (OverseasServiceImpl) order;
osi.check();
}
orderService.create();
orderService.findOne();
第三部分
3.1 JDK8 和 JDK9 新特性
-
JDK8的新特性:接口中可以定义有方法体的方法。
默认注意事项:
- 默认方法不是抽象方法,所以不强制被重写 (但是可以被重写,重写的时候去掉default关键字)
- public可以省略,default不能省略
- 如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
静态注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
AInterImpl a = new AInterImpl();
a.method();
A.function(); // 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
interface A {
default void method(){
System.out.println("A...method");
}
// public可以省略,static不能省略
static void function(){ // 接口中可以定义有方法体的静态方法
System.out.println("A...static...function");
}
}
interface Inter {
void show();
// public可以省略,default不能省略
default void method(){ // 接口中可以定义有方法体的方法
System.out.println("Inter...method");
}
}
class AInterImpl extends Object implements Inter, A {
@Override
public void show() { // 实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
System.out.println("AInterImpl...show");
}
@Override
public void method() {
A.super.method(); //
Inter.super.method();
}
}
class BInterImpl implements Inter {
@Override
public void show() {
System.out.println("BInterImpl...show");
}
}
- JDK9的新特性:接口中可以定义私有方法。
interface Inter {
void show();
void print();
public static void start(){
System.out.println("start方法执行...");
log();
}
public static void end(){
System.out.println("end方法执行...");
log();
}
private static void log(){ // 接口中可以定义私有方法
System.out.println("日志记录");
}
}
3.2 代码块
/*
代码块 : 使用 { } 括起来的代码被称为代码块
分类 :
1. 局部代码块(不常用)
位置: 方法中的一对大括号
作用: 限定变量的生命周期, 提早的释放内存
2. 构造代码块(不常用)
位置: 类中方法外的一对大括号
特点: 在创建对象, 执行构造方法的时候, 就会执行构造代码块 (优先于构造方法执行)
作用: 将多个构造方法中, 重复的代码, 抽取到构造代码块中, 从而提升代码的复用性
3. 静态代码块
位置: 类中方法外的一对大括号, 需要加入static关键字
特点: 随着类的加载而执行, 因为类只加载一次, 所以也就只执行一次
作用: 对数据进行初始化
*/
class Student {
static String school;
static {
school = "黑马程序员";
}
{
System.out.println("好好学习");
}
public Student(){
System.out.println("空参构造方法...");
}
}
3.3 内部类
-
成员内部类 (不常用)
/* 内部类: 在类中定义的类 创建对象格式: 外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象(); */ Outer.Inner oi = new Outer().new Inner(); System.out.println(oi.num); // 10 oi.show(); // show... MyOuter.MyInner mm = new MyOuter().new MyInner(); mm.show(); // method... class Outer { private void method(){ System.out.println("method..."); Inner i = new Inner(); System.out.println(i.num); // 1. 外部类中, 访问内部类成员 : 需要创建对象访问 } class Inner { int num = 10; public void show(){ System.out.println("show..."); method(); // 2. 内部类中, 访问外部类成员 : 直接访问, 包括私有 } } }
class MyOuter { int num = 10; class MyInner { int num = 20; public void show(){ int num = 30; System.out.println(num); // 30 System.out.println(this.num); // 20 System.out.println(MyOuter.this.num); // 10 } } }
-
静态内部类 (不常用)
/* 静态内部类 : static 修饰的成员内部类 创建对象格式 : 外部类名.内部类名 对象名 = new 外部类名.内部类对象(); 注意事项 : 静态只能访问静态 */ OuterClass.InnerClass.show(); class OuterClass { int num1 = 10; static class InnerClass { public static void show(){ System.out.println("show..."); OuterClass o = new OuterClass(); System.out.println(o.num1); } } }
-
局部内部类 (不常用)
// 局部内部类: 放在方法、代码块、构造器等执行体中 A a = new A(); a.show(); class A { public void show(){ class B { public void method(){ System.out.println("method..."); } } B b = new B(); b.method(); } }
-
匿名内部类 (掌握)
useInter(new InterImpl()); // 方法的形参是接口类型, 我们该传入的是该接口的实现类对象 public static void useInter(Inter i){ i.show(); } interface Inter { void show(); } class InterImpl implements Inter { @Override public void show() { System.out.println("InterImpl...show..."); } }
/* 匿名内部类 : 概述 : 匿名内部类本质上是一个特殊的局部内部类(定义在方法内部) 前提 : 需要存在一个接口或类 格式 : new 类名(){} : 代表继承这个类 new 接口名(){} : 代表实现这个接口 结论 : 可以让代码变得更加简洁, 在定义类的时候对其进行实例化 */ useInter(new Inter(){ @Override public void show() { System.out.println("匿名内部类...show..."); } }); interface Inter { void show(); }
匿名内部类可以作为方法的实际参数进行传输
3.4 Lambda 表达式介绍
/*
Lambda表达式 : JDK8开始后的一种新语法形式
作用 : 简化匿名内部类的代码写法。
格式 : () -> {}
() : 匿名内部类被重写方法的形参列表
{} : 被重写方法的方法体代码
*/
useInterB(new InterB() {
@Override
public void show() {
System.out.println("匿名内部类, 重写后的show方法...");
}
});
useInterB( () -> {
System.out.println("Lambda 表达式, 重写后的show方法...");
} );
public static void useInterB(InterB b) {
b.show();
}
// Lambda表达式, 只允许操作函数式编程接口 : 有, 且仅有一个抽象方法的接口
@FunctionalInterface
interface InterB {
void show();
}
3.5 Lambda 表达式省略规则
useShowHandler(new ShowHandler() {
@Override
public void show() {
System.out.println("我是匿名内部类, 重写后的show方法...");
}
});
useShowHandler(() -> System.out.println("Lambda表达式, 重写后的show方法...")); // 省略{}与;
useStringHandler(new StringHandler() {
@Override
public void printMessage(String msg) {
System.out.println("匿名内部类打印:" + msg);
}
});
useStringHandler(msg -> System.out.println("Lambda打印:" + msg)); // 省略类型, 省略(), 省略{}与;
useRandomNumHandler(new RandomNumHandler() {
@Override
public int getNumber() {
return new Random().nextInt(100) + 1;
}
});
useRandomNumHandler(() -> new Random().nextInt(100) + 1); // 省略类型, 省略return, 省略{}与;
useCalculator((int a, int b) -> {
return a + b;
});
useCalculator((a, b) -> a - b); // 省略类型, 省略return, 省略{}与;
快捷键:alt + enter + enter
Lambda 表达式和匿名内部类的区别
- 使用限制不同
匿名内部类 : 可以操作类, 接口
Lambda表达式 : 只能操作函数式接口 - 实现原理不同
匿名内部类:编译之后,产生一个单独的.class字节码文件
Lambda表达式:编译之后,没有一个单独的.class字节码文件
3.6 窗体
// 创建窗体对象
JFrame frame = new JFrame();
// 设置窗体大小
frame.setSize(500, 800);
// 修改窗体的关闭模式
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
frame.setTitle("这是一个窗体标题");
// 设置窗体可见
frame.setVisible(true);
3.7 组件
// 0. 如果取消了窗体的默认布局, 就需要手动指定组件的摆放位置了
frame.setLayout(null);
// 1. JButton创建按钮
JButton btn = new JButton("点我啊~");
btn.setBounds(50,50, 100,100);
frame.getContentPane().add(btn);
// 3. JLabel展示文本
JLabel jl = new JLabel("听君一席话");
jl.setBounds(50,50,100,100);
frame.getContentPane().add(jl);
// 4. JLabel展示图片
JLabel png = new JLabel(new ImageIcon("C:\\Develop\\image\\3.png"));
png.setBounds(50,150,100,100);
frame.getContentPane().add(png);
注意:如果多个组件摆放在同一个位置,后添加的组件,会被压在底部。
3.8 事件监听
// 动作事件 : ActionListener
// 1. 鼠标点击 2. 空格按键
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("我被点了!!!");
}
});
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
// fn ctrl esc 上下左右不能用
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == 37) {
System.out.println("左移动业务代码");
}else if(keyCode == 38){
System.out.println("上移动业务代码");
}else if(keyCode == 39){
System.out.println("右移动业务代码");
}else if(keyCode == 40){
System.out.println("下移动业务代码");
}
}
@Override
public void keyReleased(KeyEvent e) {
}
});
3.9 适配器设计模式
- 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 - 适配器设计模式:解决接口与接口实现类之间的矛盾问题
interface 和尚 {
void 打坐();
void 念经();
void 撞钟();
void 习武();
}
abstract class 和尚Adapter implements 和尚 {
@Override
public void 打坐() {}
@Override
public void 念经() {}
@Override
public void 撞钟() {}
@Override
public void 习武() {}
}
class 鲁智深 extends 和尚Adapter {
@Override
public void 习武() {System.out.println("鲁智深只习武");}
}
3.10 模板设计模式
模板设计模式:把抽象类整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法
让使用模板的类(继承抽象类的类)去重写抽象方法实现需求
小结:模板设计模式的优势,模板已经定义了通用结构,使用者只需要关心自己需要实现的功能即可
public abstract class CompositionTemplate {
public final void write() {
System.out.println("我的爸爸");
body();
System.out.println("啊~ 这就是我的爸爸~");
}
public abstract void body();
}
public class Tom extends CompositionTemplate {
@Override
public void body() {
System.out.println("我的爸爸是一个争强好色的人, 每天晚上都忙到很晚, 才陆陆续续的回家...");
}
}
Tom t = new Tom();
t.write();
Day 2 石头迷阵游戏
1. 构建游戏窗体
// 创建窗体对象
JFrame frame = new JFrame();
// 设置窗体大小
frame.setSize(514, 595);
// 设置窗体关闭模式
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
frame.setTitle("石头迷阵单机版V1.0");
// 设置窗体置顶
frame.setAlwaysOnTop(true);
// 设置窗体居中
frame.setLocationRelativeTo(null);
// 取消默认布局
setLayout(null);
// 设置窗体可见
frame.setVisible(true);
2. 绘制界面
int[][] data = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
JLabel jl = new JLabel(new ImageIcon("C:\\Develop\\image\\" + data[i][j] + ".png"));
jl.setBounds(50 + 100 * j, 90 + 100 * i, 100, 100);
frame.getContentPane().add(jl);
}
}
JLabel bk = new JLabel(new ImageIcon("C:\\Develop\\image\\background.png"));
bk.setBounds(26, 30, 450, 484);
frame.getContentPane().add(bk);
3. 使用继承重构代码
- 定义 MainFrame 类继承 JFrame
- 将代码抽取到一个单独的方法 initFrame()
- 将绘制界面的代码, 抽取为一个单独的方法 paintView()
- 在构造方法中, 调用 initFrame() 和 paintView(), 最后调用 setVisible(true);
public class mainFrame extends JFrame {
int[][] data = {...};
public mainFrame(){
initFrame();
paintView();
setVisible(true);
}
public void initFrame(){...}
public void paintView(){...}
}
4. 打乱石头方块
private void initnumber() {
Random r = new Random();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int x = r.nextInt(4);
int y = r.nextInt(4);
int temp = data[i][j];
data[i][j] = data[x][y];
data[x][y] = temp;
}
}
}
5. 移动业务准备
-
键盘监听
public class mainFrame extends JFrame implements KeyListener { public mainFrame(){ // 窗体对象.addKeyListener(KeyListener实现类对象); this.addKeyListener(this); // this : 1) 窗体对象 2) KeyListener实现类对象 } @Override public void keyPressed(KeyEvent e) { int keycode = e.getKeyCode(); move(keycode); } private static void move(int keycode) { if (keycode == 37){ System.out.println("左"); } else if (keycode == 38) { System.out.println("上"); } else if (keycode == 39) { System.out.println("右"); } else if (keycode == 40){ System.out.println("下"); } } @Override public void keyReleased(KeyEvent e) {} @Override public void keyTyped(KeyEvent e) {} }
-
确定0号元素所在位置
int row; // 0号元素的行数 int col; // 0号元素的列数 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(data[i][j] == 0){ row = i; col = j; } } } // System.out.println(row + ", " + col);
6. 移动业务
-
基本实现
public void paintView(){ super.getContentPane().removeAll(); ... super.getContentPane().repaint(); } public void keyPressed(KeyEvent e) { int keycode = e.getKeyCode(); move(keycode); paintView(); // 每按一次, 重绘 } private void move(int keycode) { if (keycode == 37){ int temp = data[row][col]; data[row][col] = data[row][col+1]; data[row][col+1] = temp; col++; } else if (keycode == 38) {...} else if (keycode == 39) {...} else if (keycode == 40) {...} else if (keycode == 90) { // 按下 z,开启作弊 data = new int[][]{ {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 0}}; } }
-
错误解决
private void move(int keycode) { if (keycode == 37){ if(col == 3){ // 在边界, 无法交换 return; } int temp = data[row][col]; data[row][col] = data[row][col+1]; data[row][col+1] = temp; col++; } else if (keycode == 38) {...} else if (keycode == 39) {...} else if (keycode == 40) {...} else if (keycode == 90) {...} }
7. 判断游戏胜利
int[][] win = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
public boolean victory(){
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if(data[i][j] != win[i][j]){
return false;
}
}
}
return true;
}
public void paintView(){
super.getContentPane().removeAll();
if(victory()){
JLabel v = new JLabel(new ImageIcon("C:\\Develop\\image\\win.png"));
v.setBounds(124, 230, 266, 88);
getContentPane().add(v);
}
...
}
private void move(int keycode) {
if(victory()){
return;
}
...
}
8. 统计步数
int cnt; // 统计步数
public void paintView() {
...
JLabel scoreLabel = new JLabel("步数为:" + cnt);
scoreLabel.setBounds(50,20,100,20);
getContentPane().add(scoreLabel);
...
}
private void move(int keycode) {
...
if (keycode == 37){... cnt++;}
else if (keycode == 38){... cnt++;}
else if (keycode == 39){... cnt++;}
else if (keycode == 40){... cnt++;}
...
}
9. 重新游戏
public void paintView() {
...
JButton btn = new JButton("重新游戏");
btn.setBounds(350,20,100,20);
getContentPane().add(btn);
btn.setFocusable(false);
btn.addActionListener(e -> {
cnt = 0;
initnumber();
paintView();
});
...
}
Day 3 常用 API
第一部分
1.1 Object类 - toString方法
方法名 | 说明 |
---|---|
public String toString() | 默认是返回当前对象在堆内存中的地址信息:类的全类名@十六进制哈希值 |
toString存在的意义:父类 toString() 方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息
/*
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
-------------------------------------------------------
细节: 使用打印语句, 打印对象名的时候, println方法, 源码层面, 会自动调用该对象的toString方法.
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
*/
Student stu = new Student("张三",23);
System.out.println(stu);
public class Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
1.2 Object类 - equals方法
方法名 | 说明 |
---|---|
public boolean equals(Object o) | 默认是比较当前对象与另一个对象的地址是否相同相同返回true,不同返回false |
equals存在的意义:父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
/*
public boolean equals(Object obj) {
return (this == obj);
}
*/
Student stu1 = new Student("张三",23);
Student stu2 = new Student("张三",23);
System.out.println(stu1.equals(stu2));
public class Student {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (o instanceof Student){
Student stu = (Student) o;
return this.age == stu.age && this.name.equals(stu.name);
} else{
return false;
}
}
}
1.3 equals 与 Objects 源码理解
快捷方式:右键 → generate → equals()
@Override
public boolean equals(Object o) {
// this : stu1 o : stu2
if (this == o) {
// 1. 两个对象做地址值的比较, 如果地址相同, 里面的内容肯定相同, 直接返回为true
return true;
}
// 代码要是能够走到这里, 代表地址肯定不相同
// 2. 代码要是能够走到这里, 代表stu1肯定不是null, 如果stu2是null, 就直接返回false
// 3. 如果字节码不相同, 就意味着类型不相同, 直接返回false
if (o == null || this.getClass() != o.getClass()) {
return false;
}
// 4. 代码要是能够走到这里, 代表字节码相同, 类型肯定相同.
Student student = (Student) o;
return this.age == student.age && Objects.equals(this.name, student.name);
}
方法名 | 说明 |
---|---|
public static boolean equals (Object a, Object b) | 比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较 |
public static boolean isNull (Object obj) | 判断变量是否为 null |
/*
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
-----------------------------------------------------------------------------------------------
a == b : 如果地址相同, 就会返回为true
: 如果地址不相同, 就会返回false
-----------------------------------------------------------------------------------------------
假设 a 是 null 值, null != null : false
&& : 左边为false, 右边不执行, 避免 a.equals(b) 空指针异常
-----------------------------------------------------------------------------------------------
假设 a 不是 null 值, stu1 != null : true
&& : 左边为true, 右边执行, a.equals(b) 就不会出现空指针异常
*/
Student stu1 = null;
Student stu2 = new Student("张三",23);
// 问题: Objects.equals方法, 和 stu1.equals方法, 有什么区别?
// 细节: Objects.equals方法, 内部依赖于我们自己所编写的equals
// 好处: Objects.equals方法, 内部带有非null判断
System.out.println(Objects.equals(stu1, stu2));
System.out.println("看看我执行了吗?");
System.out.println(Objects.isNull(stu1));
System.out.println(Objects.isNull(stu2));
1.4 Math 类
方法名 | 说明 |
---|---|
public static int abs (int a) | 获取参数绝对值 |
public static double ceil (double a) | 向上取整 |
public static double floor (double a) | 向下取整 |
public static int round (float a) | 四舍五入 |
public static int max (int a,int b) | 获取两个int值中的较大值 |
public static double pow (double a,double b) | 返回a的b次幂的值 |
public static double random () | 返回值为double的随机值,范围[0.0,1.0) |
System.out.println(Math.abs(-12.3)); // 12.3
System.out.println(Math.ceil(12.2)); // 13
System.out.println(Math.floor(12.2)); // 12
System.out.println(Math.round(3.4)); // 3
System.out.println(Math.round(3.6)); // 4
System.out.println(Math.max(10, 20)); // 20
System.out.println(Math.min(10, 20)); // 10
System.out.println(Math.pow(2, 3)); // 8
System.out.println(Math.random()); // [0.0,1.0)
1.5 System 类
方法名 | 说明 |
---|---|
public static void exit (int status) | 终止当前运行的 Java 虚拟机,非零表示异常终止 |
public static long currentTimeMillis () | 返回当前系统的时间毫秒值形式 |
public static void arraycopy (数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组拷贝 |
int[] arr = {11,22,33,44,55};
int[] destArr = new int[3];
System.arraycopy(arr, 2, destArr, 0, 3);
for (int i = 0; i < destArr.length; i++) { // 33 44 55
System.out.println(destArr[i]);
}
1.6 BigDecimal 类
方法名 | 说明 |
---|---|
public BigDecimal add (BigDecimal b) | 加法 |
public BigDecimal subtract (BigDecimal b) | 减法 |
public BigDecimal multiply (BigDecimal b) | 乘法 |
public BigDecimal divide (BigDecimal b) | 除法 |
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) | 除法 |
/*
BigDecimal类 : 解决小数运算中, 出现的不精确问题
BigDecimal创建对象 :
public BigDecimal(double val) : 不推荐, 无法保证小数运算的精确
public BigDecimal(String val)
public static BigDecimal valueOf(double val)
注意: 如果使用BigDecimal运算, 出现了除不尽的情况, 就会出现异常
*/
BigDecimal bd1 = BigDecimal.valueOf(10.1);
BigDecimal bd2 = BigDecimal.valueOf(3.2);
System.out.println(bd1.divide(bd2, 3, RoundingMode.HALF_UP)); // 3.156
System.out.println(bd1.divide(bd2, 3, RoundingMode.UP)); // 3.157
System.out.println(bd1.divide(bd2, 3, RoundingMode.DOWN)); // 3.156
BigDecimal a = bd1.add(bd2);
BigDecimal s = bd1.subtract(bd2);
double A = a.doubleValue(); // 13.3
double S = s.doubleValue(); // 6.9
System.out.println(A * S); // 91.77000000000001
1.7 包装类
方法名 | 说明 |
---|---|
public static String toBinaryString (int i) | 得到二进制 |
public static String toOctalString (int i) | 得到八进制 |
public static String toHexString (int i) | 得到十六进制 |
public static int parseInt (String s) | 将字符串类型的整数转成int类型的整数 |
String s = "10,50,30,20,40";
// 1. 根据逗号做切割
String[] sArr = s.split(",");
// 2. 准备一个整数数组, 准备存储转换后的数字
int[] nums = new int[sArr.length];
// 3. 遍历字符串数组
for (int i = 0; i < sArr.length; i++) {
// 4. 将数字字符串转换为整数, 并存入数组
nums[i] = Integer.parseInt(sArr[i]);
}
1.8 包装类面试题
/*
自动装箱的时候, 如果装箱的数据范围, 是-128~127, ==号比较的结果就是true, 反之都是false
-----------------------------------------------------------------------------------
自动装箱的原理 : 自动帮我们调用了 Integer.valueOf(127);
public static Integer valueOf(int i) {
if (i >= -128 && i <= 127) {
return IntegerCache.cache[255];
}
return new Integer(i);
}
如果装箱的数据, 不在 -128 ~ 127 之间, 会重新创建新的对象
如果装箱的数据, 在 -128 ~ 127 之间, 不会创建新的对象, 而是从底层的数组中, 取出一个提前创建好的Integer对象, 返回
- Integer类中, 底层存在一个长度为256个大小的数组, Integer[] cache
在数组中, 存储了256个Integer对象, 分别是 -128 ~ 127
*/
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true
Long i11 = 129L;
Long i22 = 129L;
System.out.println(i11 == i22); // false
System.out.println(i11.equals(i22)); // true
第二部分
2.1 Arrays 数组工具
/*
Arrays类常用方法 :
-------------------------------------------------------------------------------------------
public static String toString (类型[] a) : 将数组元素拼接为带有格式的字符串
public static boolean equals (类型[] a, 类型[] b) : 比较两个数组内容是否相同
public static int binarySearch (int[] a, int key) : 查找元素在数组中的索引 (保证数组的元素是排好序的)
- 如果查找的元素, 在数组中不存在: 返回 (-(插入点) - 1)
public static void sort (类型[] a) : 对数组进行默认升序排序
TODO: 后面学完了红黑树, 回头对这个方法做补充
-------------------------------------------------------------------------------------------
*/
int[] arr1 = {11, 22, 33, 44, 55};
int[] arr2 = {11, 22, 33, 44, 66};
// 1.将数组元素拼接为带有格式的字符串
System.out.println(Arrays.toString(arr1));
// 2.比较两个数组内容是否相同
System.out.println(Arrays.equals(arr1, arr2));
// 3.查找元素在数组中的索引
System.out.println(Arrays.binarySearch(arr1, 33));
// 4.对数组进行默认升序排序
Arrays.sort(nums);
2.2 冒泡排序
冒泡排序: 相邻的两个数进行比较, 如果第一个比第二个大, 就交换他们两个
for (int i = 0; i < arr.length - 1; i++) {
// -1: 避免索引越界, -i: 提升效率
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
2.3 选择排序
选择排序: 从 0 索引开始,拿着每一个索引上的元素跟后面的元素依次比较
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i] > arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
2.4 二分查找
private static int binarySearch(int[] arr, int num) {
int min = 0;
int max = arr.length - 1;
int mid;
while (min <= max) {
mid = (min + max) / 2;
if (num > arr[mid]) {
min = mid + 1;
} else if (num < arr[mid]) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}
2.5 正则表达式
正则表达式 : 本质来说就是一个字符串, 可以指定一些规则, 来校验其他字符串
/*
[] : 单个字符
1. 字符类 :
---------------------------------------------------------------------------
[abc] 只能是a, b, 或c
[^abc] 除了a, b, c之外的任何字符
[a-zA-Z] a到z A到Z,包括(范围)
[a-d[m-p]] a到d,或m通过p:([a-dm-p]联合)
[a-z&&[def]] d, e, 或f(交集)
[a-z&&[^bc]] a到z,除了b和c:([ad-z]减法)
[a-z&&[^m-p]] a到z,除了m到p:([a-lq-z]减法)
---------------------------------------------------------------------------
2. 预定义字符类:
---------------------------------------------------------------------------
. 任何字符
\d 一个数字: [0-9]
\D 非数字: [^0-9]
\s 一个空白字符: [ \t\n\x0B\f\r]
\S 非空白字符: [^\s]
\w [a-zA-Z_0-9] 英文、数字、下划线
\W [^\w] 一个非单词字符
\ : 转义字符
System.out.println("\t"); \ ---> t ---> 解密成功 ---> tab键
---------------------------------------------------------------------------
3. 数量:
---------------------------------------------------------------------------
X? X, 一次或0次
X* X, 零次或多次 (任意次数)
X+ X, 一次或多次
X {n} X,正好n次
X {n, } X,至少n次
X {n,m} X,至少n但不超过m次
---------------------------------------------------------------------------
*/
/*
1. QQ号正则
不能以0开头
全部都是数字
5~12位
2. 手机号正则
必须是1开头
第二位: 3 4 5 6 7 8 9
全都是数字, 必须是11位
3. 邮箱正则
zhangSan@itcast.cn
zhangsan@163.com
123456@qq.com
zhangsan@sina.com
zhangsan@itcast.qq.com
zhangsan@xxx.edu
zhangsan@xxx.org
*/
String qqRegex = "[1-9]\\d{4,11}";
String telRegex = "[1][3-9]\\d{9}";
String emailRegex = "\\w+[@][\\w&&[^_]]+(\\.[a-z]{2,3})+";
2.6 使用正则做爬取
/*
String类中与正则有关的常见方法 :
public String replaceAll(String regex,String newStr) : 按照正则表达式匹配的内容进行替换
*/
String s = "先帝1创业2未半而中道3崩殂4,今5天下三分6,益州疲弊7,此8诚危急存亡之秋也。然9侍卫之臣不懈于内,忠志之士忘身10于外者,盖追先帝之殊遇11,欲报之于陛下也。诚宜12开张圣听13,以光14先帝遗德,恢弘15志士之气,不宜妄自菲薄16,引喻失义17,以塞忠谏之路也18。\n 宫中府中,俱为一体19;陟罚臧否20,不宜异同:若有作奸犯科21及为忠善者22,宜付有司23论其刑赏24,以昭陛下平明之理25;不宜偏私26,使内外异法也27。\n 侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯28,是以先帝简拔以遗陛下29:愚以为宫中之事,事无大小,悉以咨之30,然后施行,必能裨补阙漏31,有所广益32";
s = s.replaceAll("\\d", "");
System.out.println(s);
/*
需求:请把下面文本中的电话,邮箱,座机号码,热线都爬取出来。
*/
String data = "来黑马程序员学习Java," +
"电话:18666668888,18699997777或者联系" +
"邮箱:boniu@itcast.cn 邮箱:bozai@itcast.cn 邮箱2:dlei0009@163.com" +
"座机电话:01036517895,010-98951256 " +
"热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
String regex = "[1][3-9]\\d{9}|\\w+[@][\\w&&[^_]]+(\\.[a-z]{2,3})+|[0]\\d{2,3}-?\\d{7,8}|400-?\\d{3}-?\\d{4}";
// 1. 将正则表达式封装为 Pattern 对象
Pattern pattern = Pattern.compile(regex);
// 2. 获取匹配器对象
Matcher matcher = pattern.matcher(data);
while (matcher.find()) {
System.out.println(matcher.group());
}
Day 4 时间类
1. 时间类 jdk7 (了解)
Date 类:代表的是日期和时间
构造器 | 说明 |
---|---|
public Date () | 创建一个Date对象,代表的是系统当前此刻日期时间。 |
public Date (long time) | 把时间毫秒值转换成Date日期对象。 |
常见方法 | 说明 |
---|---|
public long getTime () | 返回从1970年1月1日 00:00:00走到此刻的总的毫秒数 |
public void setTime (long time) | 设置日期对象的时间为当前时间毫秒值对应的时间 |
SimpleDateFormat 类:用于日期格式化
构造器 | 说明 |
---|---|
public SimpleDateFormat () | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat (String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
格式化方法 | 说明 |
---|---|
public final String format (Date date) | 将日期格式化成日期/时间字符串 |
public final Date parse (String source) | 将字符串解析为日期类型 |
// 需求: 计算你来到了这个世界多少天
// 1. 键盘录入用户的生日 (日期字符串)
Scanner sc = new Scanner(System.in);
String birthday = sc.nextLine();
// 2. 创建SimpleDateFormat对象, 指定模式, 用于将日期字符串解析为Date日期对象 (birthdayDate)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
// 生日那一天的日期对象
Date birthdayDate = simpleDateFormat.parse(birthday);
// 3. 创建Date日期对象, 封装此刻的时间 (todayDate)
Date today = new Date();
// 4. 计算用户活了多少毫秒
long time = today.getTime() - birthdayDate.getTime();
// 5. 将毫秒值, 转换为天的单位
System.out.println(time / 1000 / 60 / 60 / 24);
3. 日历类 jdk7 (了解)
创建对象 : Calendar是一个抽象类,不能直接创建对象
方法名 | 说明 |
---|---|
public static Calendar getInstance () | 获取当前时间的日历对象 |
public int get (int field) | 取日历中的某个字段信息 |
public void set (int field,int value) | 修改日历的某个字段信息 |
public void add (int field,int amount) | 为某个字段增加/减少指定的值 |
/*
get方法的参数 : Calendar类中的静态常量
Calendar.YEAR : 获取年份信息
Calendar.MONTH : 月份是0~11, 记得做+1操作
Calendar.DAY_OF_MONTH : 获取日
Calendar.DAY_OF_WEEK : 获取星期, 获取星期的时候, 需要提前设计一个数组
Calendar.DAY_OF_YEAR : 获取一年中的第几天
*/
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
System.out.println(year); // 2024
// 注意Calendar类的月份是 0~11, 想要获取常规的月份, 需要对结果 + 1操作
int month = c.get(Calendar.MONTH);
System.out.println(month + 1); // 5
int day = c.get(Calendar.DAY_OF_MONTH);
System.out.println(day); // 8
char[] weeks = {' ', '日', '一', '二', '三', '四', '五', '六'};
int weekIndex = c.get(Calendar.DAY_OF_WEEK);
System.out.println(weeks[weekIndex]); // 三
int dayOfYear = c.get(Calendar.DAY_OF_YEAR);
System.out.println(dayOfYear); // 129
c.set(Calendar.YEAR, 2022);
c.set(2008,8,8);
System.out.println(c.get(Calendar.YEAR)); // 2008
c.add(Calendar.YEAR, -1);
System.out.println(c.get(Calendar.YEAR)); // 2023
// 需求 : 键盘录入一个日期字符串, 程序输出这个日期是一年中的第多少天
// 1. 使用SimpleDateFormat, 将日期字符串转换为日期对象
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
Date date = dateFormat.parse(dateContent);
// 2. 将日期对象, 转换为Calendar对象
Calendar c = Calendar.getInstance();
c.setTime(date);
// 3. 调用get方法, 获取一年中的第几天
int dayOfYear = c.get(Calendar.DAY_OF_YEAR);
System.out.println("是这一年中的第:" + dayOfYear + "天");
4. 日历类 jdk8
// JDK8版本之前 :
Date time = new Date();
System.out.println("修改前获取时间: " + time); // Wed May 08 19:46:19 CST 2024
time.setTime(1000);
System.out.println("修改后获取时间:" + time); // Thu Jan 01 08:00:01 CST 1970
// JDK8版本之后 :
LocalDateTime now = LocalDateTime.now();
System.out.println("修改前获取时间: " + now); // 2024-05-08T19:46:19.938524500
LocalDateTime afterTime = now.withYear(2008);
System.out.println("修改后获取时间: " + afterTime); // 2008-05-08T19:46:19.938524500
System.out.println("修改后获取时间: " + now); // 2024-05-08T19:46:19.938524500
- 日历类
LocalDateTime nowDateTime = LocalDateTime.now();
System.out.println(nowDateTime); // 2024-05-08T20:01:23.230769400
// LocalDate nowDate = LocalDate.now();
System.out.println(nowDateTime.getYear()); // 2024
System.out.println(nowDateTime.getMonthValue()); // 5
System.out.println(nowDateTime.getDayOfMonth()); // 8
// LocalTime nowTime = LocalTime.now();
System.out.println(nowDateTime.getHour()); // 20
System.out.println(nowDateTime.getMinute()); // 1
System.out.println(nowDateTime.getSecond()); // 23
System.out.println(nowDateTime.getNano()); // 230769400
System.out.println(nowDateTime.getDayOfYear()); // 129
System.out.println(nowDateTime.getDayOfWeek().getValue()); // 3
方法名 | 说明 |
---|---|
withHour、withMinute、withSecond、withNano | 修改时间,返回新时间对象 |
plusHours、plusMinutes、plusSeconds、plusNanos | 把某个信息加多少,返回新时间对象 |
minusHours、minusMinutes、minusSeconds、minusNanos | 把某个信息减多少,返回新时间对象 |
equals isBefore isAfter | 判断2个时间对象,是否相等,在前还是在后 |
LocalDateTime nowTime = LocalDateTime.now(); // 2024-05-08T20:14:37.504370800
System.out.println(nowTime);
System.out.println(nowTime.minusHours(1)); // 2024-05-08T19:14:37.504370800
System.out.println(nowTime.plusMinutes(1)); // 2024-05-08T20:15:37.504370800
System.out.println(nowTime.withYear(2008)); // 2008-05-08T20:14:37.504370800
LocalDate myDate = LocalDate.of(2008, 8, 8);
LocalDate nowDate = LocalDate.now();
System.out.println(myDate + "是否在" + nowDate + "之后? " + myDate.isAfter(nowDate)); // false
System.out.println(myDate.equals(nowDate)); // false
- 日期格式化类
方法名 | 说明 |
---|---|
static DateTimeFormatter ofPattern(格式) | 获取格式对象 |
String format(时间对象) | 按照指定方式格式化 |
LocalDateTime now = LocalDateTime.now();
System.out.println("格式化之前:" + now); // 2024-05-08
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy年M月d日");
String result = f.format(now);
System.out.println("格式化之后:" + result); // 2024年5月8日
LocalDate parse = LocalDate.parse(result, f);
System.out.println("解析之后:" + parse); // 2024-05-08
5. 时间类 jdk8
- ZoneId 类
方法名 | 说明 |
---|---|
static Set<String> getAvailableZoneIds() | 获取Java中支持的所有时区 |
static ZoneId systemDefault() | 获取系统默认时区 |
static ZoneId of(String zoneId) | 获取一个指定时区 |
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set); // [Asia/Aden, ...]
System.out.println(set.size()); // 601
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId); // Asia/Shanghai
ZoneId of = ZoneId.of("Africa/Nairobi");
ZonedDateTime zonedDateTime = Instant.now().atZone(of);
System.out.println(zonedDateTime); // 2024-05-08T15:49:32.345860+03:00[Africa/Nairobi]
- Instant 时间戳:用于表示时间的对象, 类似之前所学习的 Date
Instant now = Instant.now();
System.out.println("当前的时间戳是:" + now); // 2024-05-08T13:03:03.112082700Z
ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime); // 2024-05-08T21:03:03.120086300+08:00[Asia/Shanghai]
方法名 | 说明 |
---|---|
static ZonedDateTime now() | 获取当前时间的ZonedDateTime对象 |
static ZonedDateTime ofXxxx(...) | 获取指定时间的ZonedDateTime对象 |
ZonedDateTime withXxx(时间) | 修改时间系列的方法 |
ZonedDateTime minusXxx(时间) | 减少时间系列的方法 |
ZonedDateTime plusXxx(时间) | 增加时间系列的方法 |
Instant now = Instant.now();
System.out.println("当前时间为(世界标准时间):" + now); // 2024-05-08T13:09:24.140062400Z
Instant instant1 = Instant.ofEpochMilli(1000);
System.out.println(instant1); // 1970-01-01T00:00:01Z
ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.systemDefault());
System.out.println("带时区的时间:" + zonedDateTime); // 2024-05-08T21:09:24.148064200+08:00[Asia/Shanghai]
System.out.println(now.isAfter(instant1)); // true
System.out.println("减1000毫秒:" + now.minusMillis(1000)); // 2024-05-08T13:09:23.140062400Z
System.out.println("加5秒钟:" + now.plusSeconds(5)); // 2024-05-08T13:09:29.140062400Z
- ZoneDataTime 带时区的时间对象
方法名 | 说明 |
---|---|
static ZonedDateTime now() | 获取当前时间的ZonedDateTime对象 |
static ZonedDateTime ofXxxx(...) | 获取指定时间的ZonedDateTime对象 |
ZonedDateTime withXxx(时间) | 修改时间系列的方法 |
ZonedDateTime minusXxx(时间) | 减少时间系列的方法 |
ZonedDateTime plusXxx(时间) | 增加时间系列的方法 |
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now); // 2024-05-08T21:01:00.648070900+08:00[Asia/Shanghai]
ZonedDateTime of = ZonedDateTime.of(2008, 8, 8, 8, 8, 8, 8, ZoneId.systemDefault());
System.out.println(of); // 2008-08-08T08:08:08.000000008+08:00[Asia/Shanghai]
System.out.println(now.withDayOfMonth(9)); // 2024-05-09T21:01:00.648070900+08:00[Asia/Shanghai]
System.out.println(now.minusYears(1)); // 2023-05-08T21:01:00.648070900+08:00[Asia/Shanghai]
System.out.println(now.plusMonths(1)); // 2024-06-08T21:01:00.648070900+08:00[Asia/Shanghai]
6. 工具类
- Duration:用于计算两个“时间”间隔(秒,纳秒)
LocalDateTime today = LocalDateTime.now();
System.out.println(today); // 2024-05-08T21:23:11.526684300
LocalDateTime otherDate = LocalDateTime.of(2023, 2, 4, 0, 0, 0);
System.out.println(otherDate); // 2023-02-04T00:00
Duration duration = Duration.between(otherDate, today);
System.out.println(duration.toNanos()); // 39734591526684300
- Period:用于计算两个“日期”间隔(年、月、日)
LocalDate today = LocalDate.now();
System.out.println(today); // 2024-05-08
LocalDate otherDate = LocalDate.of(2024, 2, 4);
System.out.println(otherDate); // 2024-02-04
Period period = Period.between(otherDate, today);
System.out.println(period.getMonths()); // 3
- ChronoUnit:用于计算两个“日期”间隔
LocalDateTime today = LocalDateTime.now();
System.out.println(today); // 2024-05-08T21:28:20.166517700
LocalDateTime birthDate = LocalDateTime.of(2024, 2, 4, 0, 0, 0);
System.out.println(birthDate); // 2024-02-04T00:00
System.out.println(ChronoUnit.DAYS.between(birthDate, today)); // 94
System.out.println(ChronoUnit.HALF_DAYS.between(birthDate, today)); // 189
- 小案例
// 需求 : 键盘录入用户生日, 计算出用户的实际年龄.
Scanner sc = new Scanner(System.in);
String birthday = sc.nextLine();
// 1. 将键盘录入的日期字符串, 解析为日期对象
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy年M月d日");
LocalDate birthdayDate = LocalDate.parse(birthday, f);
// 2. 获取今天的日期对象
LocalDate now = LocalDate.now();
// 3. 计算时间间隔
long result = ChronoUnit.YEARS.between(birthdayDate, now);
System.out.println(result);
参考链接:https://www.bilibili.com/video/BV1Fv4y1q7ZH?p=1&vd_source=ed621eaa6bcf9bf6acb7d0527c30489a
网友评论