文章目录
10 面向对象
1.对象的概念(属性,方法)
对象的特征,包含属性和行为:
- 属性也叫字段,成员变量,记录对象的数据, 如手机==>有颜色, 尺寸, 品牌, 有摄像头(类型可以是基本数据类型和引用类型)
- 行为也叫方法,描述该对象的动态特征,即它能做什么。如:手机==>手机内置有照相功能,上网功能等等, 你买了手机(对象)后,该手机(对象)所拥有的功能(对象的方法)你都能够调用
- 对象:用来描述客观事物的一个实体,由一组属性和方法构成
2. 类的概念
-
指一类相同的事物, 如学生类, 学生有姓名,学号等属性
-
类是抽象的概念,是对现实世界一组相同的对象的特征的描述,是一种模板
-
对象是具体的,是对类的具体的体现。
-
java中定义类的步骤
- 定义类名
-
定义类的属性
-
定义类的方法
-
定义类的步骤
类的命名规则: 帕斯卡规则,首字母大写,后续单词的首字母也是大写, 如 Student, StudentTest
首字母不能是数字类的属性名规则:驼峰规则
定义一个学生类
在这里我们并没有创建构造函数,系统会自动创建一个默认的构造函数
public class Student{ // 学生的属性1: 学号id 没初始化属性在创建对象后默认为0 public int id; // 学生的属性2: 姓名 没初始化在创建对象后默认为null public String name; // 学生能做的行为--方法 public void sayHi(){ System.out.printf("大家好,我叫%,我的学号是%d",id,name); } }
// 系统创建的默认构造函数
public Student() {
}
3. 对象的创建(实例化对象)
如: 捏泥人,我要捏一个学生出来, 我首先得捏出一个学生的模型, 捏出来模型后我才能在这个模型上给学生赋予
id, 姓名(通俗点说就像是给泥人上颜色,起名字等操作,这跟属性有关), 然后创建好泥人后, 我也可以让泥人
做它可以做的事情, 比如女娲捏造的泥人, 可以打篮球, 踢足球, 这两个方法我们都可以通过泥人这个对象
来调用它打篮球踢足球的方法
-
对象其实是自定义的数据类型, 例如Int 数据类型, 我们在定义的时候可以有100,200,300
-
而我们自定义的数据类型–对象(例如Student类)也是如此==>student1, student2, …
-
对象的数据类型是引用类型
-
创建Student对象
- student 是对象名(或者对象引用)-- 这个只是一个名称用来引用
- new Student() 创建的对象空间(数据)在堆内存里 才是真的对象
-
创建方式:
-
先声明再创建
Student student; student = new Student();
- new负责向jvm的堆空间申请内存空间,并创建一个对象
- Student()调用构造方法对对象完成实例化
- 直接创建
-
public class StudentTest {
public static void main(String[] args) {
// 创建对象,在入口程序中创建 -- 引用类型
// 创建Student对象
// student 是对象名(或者对象引用)-- 这个只是一个名称用来引用
// new Student() 创建的对象空间(数据) 才是真的对象
Student student = new Student();
// 给对象的属性赋值
student.id = 100;
student.name = "tom";
// 调用对象的方法
student.sayHi();
}
}
※※※对象在内存中的存在形式※※※
-
先加载Person类信息(属性和方法信息(Person.class),只会加载一次
-
在堆中分配空间,进行默认初始化(看数据类型默认值规则)
-
把地址赋给p,p就指向对象
4. 进行指定初始化,比如p.name =" jack" Ip.age =10
数组理解
public class Test {
public static void main(String[] args) {
int[] arr1 = {1, 3, 5, 7};
int[] arr2 = arr1;
arr2[0] = 100;
arr2 = null;
System.out.println("1===>"+Arrays.toString(arr1)); // 1===>[100, 3, 5, 7]
System.out.println("2===>"+Arrays.toString(arr2)); // 2===>null
}
}
4. 类中的方法(成员方法)
- 方法是用户描述类的某种行为或者功能
- 方法的定义(对象构建的方法为非静态方法)
- 定义方法名以及返回值类型(void表示无返回值)
- 编写方法体
- 方法的好处: 能实现代码的复用,在实现同一个功能时不用写大量重复的代码, 只需要调用方法即可
// 定义方法名以及返回值类型
public 返回值类型 方法名() {
// 方法的主体
}
示例1: 无参方法
public class Student{
// 学生的属性1: 学号id 没初始化属性在创建对象后默认为0
public int id;
// 学生的属性2: 姓名 没初始化在创建对象后默认为null
public String name;
// 学生能做的行为--方法
public void sayHi(){
System.out.printf("大家好,我叫%,我的学号是%d",id,name);
}
}
示例二:有参方法(即:需要传入参数才能调用方法)
public class Student{
// 学生的属性1: 学号id 没初始化属性在创建对象后默认为0
public int id;
// 学生的属性2: 姓名 没初始化在创建对象后默认为null
public String name;
// 学生能做的行为--方法
public void sayHi(){
System.out.printf("大家好,我叫%,我的学号是%d",id,name);
}
// 这里的a , b 称为形参
public void add(int a, int b) {
System.out.println(a + "+" + b + " = " + (a+b));
}
}
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
student.id = 100;
student.name = "tom";
student.sayHi();
student.add(10,20);// 需要传入参数,传入的参数叫做实参
}
}
示例3:有返回值方法
- 创建的方法有返回值,调用方法时必须定义对应类型的变量来接收方法返回的值
- return的返回值的类型,要跟方法定义时的返回值的类型保持一致。
- 修改add方法,变成有返回值的方法
- 调用带返回值的方法时,需要在方法的调用处,接受返回的参数
public class Student{
// 学生的属性1: 学号id 没初始化属性在创建对象后默认为0
public int id;
// 学生的属性2: 姓名 没初始化在创建对象后默认为null
public String name;
// 学生能做的行为--方法
public void sayHi(){
System.out.printf("大家好,我叫%,我的学号是%d",id,name);
}
// 这里的a , b 称为形参
/*public void add(int a, int b) {
System.out.println(a + "+" + b + " = " + (a+b));
}*/
// 此处将加法运算改成有返回值的方法,返回值类型为Int
public Int add(int a, int b) {
System.out.println("执行了带返回值的方法");
int c = a + b;
// return c : 表示该方法返回的值为c的值
return c;
}
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
student.id = 100;
student.name = "tom";
student.sayHi();
// 需要传入参数,传入的参数叫做实参
// 在调用add时应创建int型变量接受返回的值
int value = student.add(10,20);
//student.add(10,20); 如果只执行这个代码而不接受方法的返回值,则无法进行返回值的操作,只会执行了方法体的代码
System.out.println("提取到了add方法中的返回值,输出它!" + value);
}
}
5. 方法的参数传递方式
- java中,方法的参数传递,分为普通类型的参数传递(值类型),与引用类型的参数传递
1.值传递,传入的是变量中保存的值
- 实参传递给形参的数据是值, 内存上形参和实参是不同的, 是独立的, 修改形参的值不会对实参有影响, 如有一份文件, 我传给你你复印了一份, 无论你对你那一份复印纸怎么改变(撕碎,对折,删除)等都不会对我这一份原件里面的内容有影响
2. 引用传递,传递的是对象的引用(地址值)
- 操作的其实都是同一对象。
示例一:
public static void main(String[] args) {
int[] a = {1,2,3};
chang(a);
System.out.println(a[0]);
}
public static void chang(int[] x) {
x[0] = 100;
}
- 实参传递给形参的不是真实的数据是数据所在地址是引用的堆内存中的地址
- 形参和实参指向的是内存中的同一个数据, 共用一个引用地址
- 修改形参会对实参产生影响形参和实参事实上是同一个数据
示例二: **对象作为参数传递, 对象作为参数传递时,方法里,可以使用该对象公有的属性和方法。 **
// 水果类
public class Fruit{
public String name;
public void getName(){
System.out.println("我是一个" + name);
}
}
public class Juicer{
// 榨汁机的榨汁方法
public String juicing(Fruit fruit){
String juice = fruit.name + "汁";
return juice;
}
}
//测试类
// 参数传递
Fruit f = new Fruit();
f.name = "苹果";
// 调用榨汁机的方法
Juicer juicer = new Juicer();
String str = juicer.juicing(f);
3. 方法调用的内存图
方法重载上面有写
6. 构造函数
1. 构造函数的定义
-
对象通过new关键字初始化,系统背后到底是怎么被初始化出来的呢?
-
其实是通过一个叫作构造函数(构造方法)的方法初始化出来的。
-
通过IDEA中的out反编译类的字节码文件,可以看出,在源码中,多了一个与类名同名的方法,这个方法是系统自动 帮我们加入的,目的就是为了通过它创建出Student对象。
-
构造函数(构造器)是完成对对象的初始化,并不是创建对象
2.默认构造方法:
-
由系统默认提供,用于完成对新对象的初始化
-
无返回值
-
与类名相同
-
默认无参数
3. 构造函数的自定义
- 若需要定义一个狗狗类,我们希望创造出来的狗狗对象(比如金毛),出来的时候毛色就应该是黄色的,所 以可以这样定义:
- 我们可以在类中自定义构造函数,我们可以在这里给对象的属性做初始化。
public class Dog {
public String name;
public String color;
// 自定义构造函数 -- 写死为黄色,不推荐
public Dog() {
this.color = "黄色";
}
}
this解释
-
**this 代表的是当前要创建(new出来)的对象,哪个对象被调用,this.就表示哪个对象,在对象的内部,我们是通过this.属性名来来访问对象的属性。 用this. 作修饰,意思是区别我们使用的是普通的变量,还是类的属性。 **
-
**尤其是在一个方法中,当方法中的参数,和类的属性取名一样时,这样能更好的区分当前我们需要访问 的是属性还是方法中的参数: **
-
this关键字的用法:
-
this可以修饰属性
**总结:**当属性名字和形参发生重名的时候,或者属性名字和局部变量重名的时候,都会发生就近原则,所以如果我要是直接使用变量名字的话就指的是离的近的那个形参或者局部变量,这时候如果我想要表示属性的话,在前面要加上: this.修饰
-
this修饰方法:
**总结:**在同一个类中,方法可以互相调用,this.可以省略不写。 -
this可以修饰构造器:
**总结:**同一个类中的构造器可以相互用this调用,注意: this修饰构造器必须放在第一行
-
public class Dog{
public Dog(){
this.color = "黄色";
}
public String name;
public String color;
public void setName(String name){
// this访问属性,区别方法的参数
this.name = name;
}
public void printColor(){
String color = "黑色";
// 打印的结果是多少?
System.out.println(color);
//结果是黑色
}
}
- 当我们在访问对象的内部的方法时,我们也可以用this.方法名进行访问。
public class Calc{
public int numA;
public int numB;
public int sum(){
return this.numA + this.numB;
}
public double avg(){
//double avg = (double)(this.numA+this.numB)/2;
// 直接调用求和的方法, 这里当然也可以省略了this.
double avg = this.sum()/2.0;
return avg;
}
}
public class Dog{
public String name;
public String color;
public Dog(String color){
this.color = color;
}
public Dog(String color, String name) {
this(color);
this.name = name;
}
}
- 当我们在上面Dog类中提供了自定义的构造函数后,系统就不会再给我们提供默认无参的构造函数,这 点一定要记住。
4. 构造函数的重载
- 我们想进行自定义构造函数, 希望类的属性由我们自定义时(如狗狗的颜色,名字,品种)
- 那么我们就得自定义一个带参数的构造函数, 那么我们在创建狗狗 对象时,就可以使用这个自定义的带参数的构造函数,来创建对象了。
- 我们可以定义不同的构造函数来满足不同需求下的参数初始化
public class Dog{
public String name;
public String color;
public Dog(String color){
this.color = color;
}
public Dog(String name, String color){
this.name = name;
this.color = color;
}
}
// 创建Dog对象的同时,使用无参构造函数,赋予默认值
Dog dog = new Dog();
// 创建Dog对象的同时,给对象的color属性进行初始化
Dog dog = new Dog("黑色");
// 创建Dog对象的同时,给对象的name和color属性进行初始化
Dog dog = new Dog("小黑","黑色");
- 上述的三个方法,其实是一种方法的重载,我们传入不同的参数,系 统会自动帮我们匹配到对应的那个构造方法。
5. 构造函数的特点
- 与类名相同
- 没有返回值
- 用于创建对象
- 可以对类的对象的属性作初始化
- 可以构成重载 类中提供了自定义构造函数后,系统不会再提供默认无参构造函数。
6.构造函数的内存!!重点
在我们给对象的属性赋值初始化之前,Person.class会在堆内存里开辟一个空间存放对象的属性,初始化其属性值为其数据类型的默认值, 基本数据类型默认为其八个默认值 引用类型默认为null
7. 变量作用域
java中的变量有:
- 全局变量:也叫成员变量或者字段或者属性,作为类的属性定义的变量。 可以添加访问修饰符
- 局部变量:在方法中定义的变量。 不可以添加访问修饰符
- 块级变量:在代码块{}中定义的变量, 如for循环
作用域:
- 全局变量:作用域范围为整个对象
- 局部变量:作用域范围为从变量声明时,到方法结束时
- 块级变量:作用域范围为在当前块中,从变量的声明时,到块的结束
- 属性和局部变量可以重名,访问时遵循就近原则。
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
- 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变
量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡。即在一次方法调用过程中。
8. static关键字
- static是Java中的一个关键字,单词本身是静态的含义。一个类的成员包括变量、方法、构造方法、代码块和内部类,static可以修饰除了构造方法以外的所有成员。
- 使用static修饰的成员成为静态成员,是属于某个类的;而不使用static修饰的成员成为实例成员,是属于类的每个对象的。
- 调用类的静态变量,静态方法的时候类会被先加载,如果有静态代码块先执行该类的静态代码块再执行静态方法,静态变量
1 static变量
在类中,用static声明的成员变量为静态成员变量,也称为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:
- 为该类的公用变量,属于类,**被该类的所有实例共享,**在类被载入时被显式初始化。
- 对于该类的所有对象来说,static成员变量只有一份。被该类的所有对象共享!!
- 一般用“类名.类属性/方法”来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员变量.)
- 非静态方法可以直接访问静态变量
- 静态方法不能直接访问非静态变量(sno)·但是可以直接访问静态变量(waterMachine)
- 不管是非静态变量(实例变量)还是静态变量(类变量), 都是成员变量
总结: static变量和非static变量的区别:
- 份数不同:静态变量: 1份;非静态变量: 1个对象一份
- 存储位置不同: 静态变量:方法区;非静态变量: 堆中
- 内存分配空间的时间不同:静态变量:第一次加载类的时候;非静态变量:创建对
象的时候 - 生命周期不同: 静态变量和类的生命周期相同;非静态变量的生命周期和所属对象
相同 - 调用方式不同""
- 静态变量: 通过类名调用Student.classRoom
也可以通过对象名调用stu1.classRoom ="301”不推荐 - 非静态变量: 通过对象名调用stu1.name =“小张”;
- 静态变量: 通过类名调用Student.classRoom
- 在一个类中,类加载时所有的静态属性自上而下顺序加载
- main方法无论写在哪里都会作为最后一个静态属性被加载运行
- 如果在当前类的静态属性中,引用了其他的静态或非静态属性,会优先加载其他类的静态属性
2. static方法
-
static方法的作用
访问static变量和static方法
-
static方法的调用方式
- 通过类名调用 Student.showClassRoom(); 推荐该方式
- 通过对象名访问 stu1.showClassRoom();
-
不可以
- 静态方法中不可以访问非静态变量
- 静态方法中不可以访问非静态方法
- 静态方法中不可以访问this
- 理解:加载类的时候就加载静态变量和静态方法,此时可能还没有创建对象,所以非静态变量和非静态的方法还没有分配空间,无法访问
-
可以
-
非静态方法中可以访问静态变量
-
非静态方法中可以访问静态方法
-
理解:加载类的时候就已经加载静态变量和静态方法,创建对象后,非静态变量和非静态的方法才分配空间,此时静态变量和静态方法已经存在,可以访问
-
3. static代码块
-
总结1:局部代码块
- 位置:方法中
- 数量:多个
- 执行顺序:依次执行
- 局部代码块中定义的变量作用范围只限于当前代码块
-
总结2:(成员)代码块
- 位置:类中
- 数量:多个
- 执行顺序:依次执行
- 执行时间:每次创建对象的时候都执行;先执行代码块,再执行构造方法
- 作用:实际开发中很少用; 可以将各个构造方法中公共的代码提取到代码块;匿名内部类不能提供构造方法,此时初始化操作放到代码块中
-
总结3:static代码块
- 位置:类中
- 数量:多个
- 执行顺序:依次执行
- 执行时间:第一次加载类的时候执行,只执行一次
- 作用:给静态变量赋初始值。实际开发中使用比较多,一般用于执行一些全局性的初始化操作,比如创建工厂、加载数据库初始信息
9. 封装性
封装(encapsulation)是面向对象三大特征之一。对于程序合理的封装让外部调用更加方便,更加利于写作。同时,对于实现者来说也更加容易修正和改版代码。
1. 封装定义
-
我要看电视,只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。同理,汽车暴露离合、油门、制动和方向盘,驾驶员不需要懂原理和维修也可以驾驶。
-
需要让用户知道的才暴露出来,不需要让用户知道的全部隐藏起来,这就是封装。说的专业一点,封装就是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
-
我们程序设计要追求“高内聚,低耦合”。 高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
2.封装的优点
-
提高代码的安全性。
-
提高代码的复用性。
-
“高内聚”:封装细节,便于修改内部代码,提高可维护性。
-
“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
无封装代码示例
class Person {
String name;
int age;
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class Test {
public static void main(String[ ] args) {
Person p = new Person();
p.name = "小红";
p.age = -45;//年龄,姓名可以通过这种方式随意赋值,没有任何限制
System.out.println(p);
}
}
10.权限修饰符
Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。 Java中4种“访问控制符”分别为private、默认、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。
访问权限修饰符 | ||||
---|---|---|---|---|
修饰符 | 同一个类 | 同一个包中 | 子类 | 所有包的所有类 |
private | ***** | |||
默认 | ***** | ***** | ||
protected | ***** | ***** | ***** | |
public | ***** | ***** | ***** | ***** |
-
private 表示私有,只有自己类能访问
-
default表示没有修饰符修饰,只有同一个包的类能访问
-
protected表示可以被同一个包的类以及其他包中的子类访问
-
public表示可以被该项目的所有包中的所有类访问
- 封装后的类属性
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
setAge(age);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//在赋值之前先判断年龄是否合法
if (age > 130 || age < 0) {
this.age = 18;//不合法赋默认值18
} else {
this.age = age;//合法才能赋值给属性age
}
}
public int getAge() {
return age;
}
public class Test2 {
public static void main(String[ ] args) {
Person p1 = new Person();
//p1.name = "小红"; //编译错误
//p1.age = -45; //编译错误
p1.setName("小红");
p1.setAge(-45);
System.out.println(p1);
Person p2 = new Person("小白", 300);
System.out.println(p2);
}
}
1.类成员处理
-
一般使用private访问权限。
-
提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作(注意:boolean变量的get方法是is开头!)。
-
一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。
2.类处理
-
类只能使用public和默认来修饰
-
默认:当前包能访问
-
public:当前项目的所有包
-
public类要求类名和文件名相同,一个java文件中至多一个public类
11. 继承
继承(Inheritance)是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。
1. 继承及其作用
从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展。现实世界中的继承无处不在。比如:
哺乳动物继承了动物。意味着,动物的特性,哺乳动物都有;在我们编程中,如果新定义一个Student类,发现已经有Person类包含了我们需要的属性和方法,那么Student类只需要继承Person类即可拥有Person类的属性和方法。
- 继承实现代码
- 父类
public class Animal {//extends Object
private String color;//颜色
private int age;//年龄
public void eat(){
System.out.println("eating ..........");
}
public void sleep(){
System.out.println("sleeping............");
}
}
- 狗狗子类
- 此时狗狗子类也拥有父类的属性color, age和方法eat(), sleep(), 还拥有自己独有的方法fawn()
public class Dog extends Animal{
// 撒娇
public void fawn(){
// 省略狗狗撒娇的方法实现
}
}
- 猫子类
- 此时猫子类也拥有父类的属性color, age和方法eat(), sleep(), 还拥有自己独有的方法catchMouse()
public class Cat extends Animal{
// 抓老鼠
public void catchMouse(){
// 省略猫抓老鼠的方法实现
}
}
2. 继承应用
子类继承父类后,就可以使用父类所有的公有的资源了。
Dog dog = new Dog();
dog.color="黄色"; // name是继承父类的属性
dog.eat(); // eat()是继承父类的方法
不能被继承的父类成员:
- private成员
- 子类与父类不在同包,使用默认访问权限的成员
- 构造方法
3. super()方法
- super()方法只能在子类中使用, 如父类有带参数的构造函数的时候, 子类的构造函数就可以调用super()方法来初始化父类中共有的属性值
- super调用时只能放在第一行
- super指向该字类的父类, 类似于this指向与本次调用的对象,super相当于父类的对象, 可以调用父类所有的成员
- super“可以看做”是直接父类对象的引用。每一个子类对象都会有一个super引用指向其直接父类对象。
- 使用super调用普通方法,语句没有位置限制,可以在子类中随便调用
- 在一个类中,若是构造方法的第一行代码没有显式的调用super(…)或者this(…); 那么Java默认都会调用super(),含义是调用父类的无参数构造方法。这里的super()可以省略。
- 使用super:
- 调用成员变量 super.color
- 调用成员方法 super.introduce();
- 调用构造方法 super(color,age);
父类代码
public class Animal {
private String color;
private int age;
private String name;
public Animal() {
super();// 在无参构造器中会默认调用父类的无参构造器,只不过一般不写
}
public Animal(String color, int age) {
this.color = color;
this.age = age;
}
public Animal(String color) {
this.color = color;
}
public void eat() {
System.out.println("super动物都会吃");
}
public void sleep() {
System.out.println("super动物都会睡");
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal{" +
"color='" + color + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
狗狗类代码 注意构造方法里的super
public class Dog extends Animal {
public Dog() {
super();// 在无参构造器中会默认调用父类的无参构造器,只不过一般不写
}
public Dog(String color, int age, String name) {
super(color, age);
super.setName(name);
}
public void fawn() {
super.eat();
System.out.println("狗狗撒娇");
}
}
猫子类代码 注意构造方法里的super 以及重写了父类的toString()方法
public class Cat extends Animal {
private String character; // 猫咪性格
public Cat(String color, int age, String character) {
super(color,age);
this.character = character;
}
public void catchMouse() {
System.out.println("cat 抓老鼠");
}
public String getCharacter() {
return character;
}
public void setCharacter(String character) {
this.character = character;
}
@Override
public String toString() {
return "Cat{" +
"character='" + character + '\'' +
"color='" + this.getColor() + '\'' +
", age=" + this.getAge() +
", name='" + this.getName() + '\'' +
'}';
}
}
测试代码
import com.animal.Cat;
import com.animal.Dog;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("黑色",5,"大黑");
dog.fawn();
dog.sleep();
System.out.println(dog.toString());
System.out.println("================");
Cat cat = new Cat("花色猫", 1,"温顺");
System.out.println(cat.getName());
System.out.println(cat.toString());
}
}
输出结果 (此时的猫咪并没有因为狗狗类调用了super.setName(name)而继承到这个名字)因为在内存中,Cat的父类和Dog的父类实际上是两块内存
super动物都会吃
狗狗撒娇
super动物都会睡
Animal{color='黑色', age=5, name='大黑'}
===============================
null
Cat{character='温顺'color='花色猫', age=1, name='null'}
继承使用要点
-
父类也称作超类、基类。子类:派生类等。
-
Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
-
子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
Dog dog = new Animal(); // 错误的,因为子类不能继承父类的构造方法,在现实中逻辑也不允许,儿子怎么能创造父亲呢
-
如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
继承情况下构造方法的调用过程
-
继承条件下构造方法的执行顺序
-
构造方法的第一条语句默认是super(),含义是调用父类无参数的构造方法, 不然就像第二点一样指定有参的构造方法
-
构造方法的第一条语句可以显式的指定为父类的有参数构造方法:super(…)
-
构造方法的第一条语句可以显式的指定为当前类的构造方法:this(…)
-
-
注意事项
- 每个类最好要提供无参数的构造方法
-
构造方法的第一条语句可以是通过super或者this调用构造方法,须是第一条语句
- 构造方法中不能同时使用super和this调用构造方法,并不是说不能同时出现this和super
-
父类中有n个构造方法,子类中最好也有对应的n个构造方法
4.方法重写
-
上面动物类中有了toString()方法, 显然这个方法不能满足猫猫类的使用,所以在猫猫类中重写(Override)了toString()方法
-
在重写时,除了方法主体不一样,其他的必须全部跟父类的相应方法保 持一致,如果不一致,则会重写失败,成会一个子类的新的方法。 (基本都一致吧)
5. 方法重写和方法重载的区别
12. Object类 面试题
Object类是所有Java类的根基类,也就意味着所有的Java对象都拥有Object类的属性和方法。如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类。
- 面试题目:请你写出Object类的6个方法
以上方法是Object类的所有方法吗?不是 是Object类所有的public方法。 除此之外可能还有private、protected、默认的方法
13. == 和equals()方法的区别
- 基本数据类型的==比较的是数据是否相等
- 引用数据类型的==比较的是引用地址是否相等
- 引用数据类型的内容的比较要使用equals ()
- 但是因为Object 的 equals 方法默认就是比较两个对象的hashcode(理解为==比较的引用地址),是同一个对象的引用时返回 true , 否则返回 false。显然,这无法满足子类Dog判断两个Dog是否是一个Dog,要求color、age、nickName等所有属性都相同的要求,可根据要求重写equals方法。
- str.equals (“test”) 中 :str不能为空,不然出现NullPointerException异常
重写的方法
public boolean equals(Object obj) {
// 此处要进行强制类型转换成Animal
Animal other = (Animal) obj;
//如果参数是null、,直接返回false
if(obj == null){
return false;
}
//如果两个变量指向同一个空间,直接返回true
if(this == obj ){
return true;
}
if(this.color.equals(other.color) && this.age == other.age){
return true;
}else{
return false;
}
}
14. 组合(组合聚合复用原则)
继承和组合是复用代码的两种方式;
-
继承 is-a Dog is-a Animal Cat is-a Animal
-
组合 has-a Computer has-a cpu memery mainBoard。
面向对象的设计原则之一:组合聚合复用原则(优先使用组合,而不是继承)
-
除非两个类之间是“is-a”的关系,否则不要轻易的使用继承,不要单纯的为了实现代码的重用而使用继承,因为过多的使用继承会破坏代码的可维护性,当父类被修改时,会影响到所有继承自它的子类,从而增加程序的维护难度和成本。
-
不要仅仅为了实现多态而使用继承,如果类之间没有“is-a”的关系,可以通过实现接口与组合的方式来达到相同的目的。设计模式中的策略模式可以很好的说明这一点,采用接口与组合的方式比采用继承的方式具有更好的可扩展性
组合示例
- 定义CPU类及其字类
public class Cpu {
private String name;//intel amd
private double rate;//速度 频率
public void calc(){
System.out.println("cpu calc......");
}
public void ctrl(){//control
System.out.println("cpu control...");
}
}
public class AMDCpu extends Cpu {
public void calc() {
System.out.println("AMD calc");
}
}
public class IntelCpu extends Cpu {
public void calc() {
System.out.println("IntelCpu calc");
}
}
- 定义MailBoard 类和Memory 类
public class MailBoard {
public void connect(){
System.out.println(" mainBoard connecting .....");
}
}
public class Memory {
public void process(){
System.out.println("memory process.....");
}
}
- 使用组合定义Computer类
public class Computer{
private Cpu cpu = new Cpu();
private Memory memory = new Memory();
private MailBoard mailBoard = new MailBoard();
private void setCpu(Cpu cpu){
this.cpu = cpu;
}
public void computer(){
cpu.calc();
cpu.ctrl();
memory.process();
mailBoard.connect();
}
public static void main(String[] args) {
Computer computer = new Computer();
//computer.setCpu(new IntelCpu());
Cpu cpu = new AMDCpu(); // 多态
Cpu cpu2 = new IntelCpu();
Cpu cpu3 = new Cpu();
computer.setCpu(cpu);
computer.computer();
}
}
15. 多态(开闭原则)
1. 定义
-
**多态(polymorphism)是面向对象三大特征之一。**同一行为,通过不同的子类,可以体现出来的不同的形态。
-
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。
-
多态的要点:
- 多态是方法的多态,不是属性的多态(多态与属性无关)。
- 多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。
- 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了
- 下面代码中的d1不能调用Dog类中特有的方法(父类没有子类有), c1 不能调用Cat类中特有的方法
Animal d1 = new Dog();
在内存上实际是指向Dog的父类Animal, d1能访问Animal中的公共方法和变量, 访问子类重写的方法的时候优先访问子类访问的重写方法, 即有多态的原因- **使用父类做方法的形参,是多态使用最多的场合。**即使增加了新的子类,方法也无需改变,符合开闭原则。如后面增加再多的动物的toString()方法都无需改变,只需写一下新增动物的toString()方法
- 父类引用做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。另外即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则。
之前的示例中 实现toString()的多态
public class Test {
public static void main(String[] args) {
// 正确的 , 此时d1调用子类Dog重写Animal中的方法,即出现多态
Animal d1 = new Dog();
Animal C1 = new Cat();
// 此时调用toString()方法,Dog,Cat中toString方法是不一样的
showToString(d1);//出现多态
showToString(c1);//出现多态
// 假设Dog类中有一个特有的方法看门gatekeeper()
// 此时d1不能调用gatekeeper()
// d1.gatekeeper(); 错误的
d1.sleep();// 非多态,因为sleep是Dog继承Animal的不是重写的
}
// 不写这个方法直接调用也行,这么写方便看到传入实参是子类,形参是父类的效果
public static void showToString(Animal animal) {
animal.toString();
}
}
程序员示例
程序员父类
public class Programmer {
public String name ="proName";//姓名
//1.子类继承的
public void writeCode(){
System.out.println("writing code............");
}
//2.子类重写的
public void eat(){
System.out.println("eating with mouse.............");
}
}
中国程序员子类
public class Chinese extends Programmer{
public String name = "ChinName";
//1.从父类继承一个方法 writeCode()
//2.重写父类的一个方法 eat
public void eat() {
System.out.println(" Chinese eat rice with chopsticks.....");
}
//3.子类特有的方法
public void playShadowBoxing(){
System.out.println("Chinese play showdowBoxing every day......");
}
}
英国程序员子类
public class English extends Programmer {
//1.从父类继承一个方法 writeCode()
//2.重写父类的一个方法 eat
public void eat() {
System.out.println("English eating meat with knife.....");
}
//3.子类特有的方法
public void raceHorse(){
System.out.println("English like racing horse......");
}
}
意大利程序员子类
public class Italian extends Programmer {
//1.从父类继承一个方法 writeCode()
//2.重写父类的一个方法 eat
public void eat() {
System.out.println("Italian eating pizza with hand......");
}
//3.子类特有的方法
public void playFootBall(){
System.out.println("Italian like play football.....");
}
}
实现eat()多态
public class Test {
/*public static void showEat(Chinese ch){
ch.eat();
}
public static void showEat(Italian it){
it.eat();
}
public static void showEat(English en){
en.eat();
}*/
public static void showEat(Programmer pro){
pro.eat();
}
public static void main(String[] args) {
//Chinese ch = new Chinese();
Programmer ch = new Chinese();
showEat(ch);
English en = new English();
showEat(en);
Italian it = new Italian();
showEat(it);
Programmer pro = new Programmer();
showEat(pro);
}
}
public class Test2 {
public static void main(String[] args) {
Chinese ch = new Chinese();
ch.writeCode();//从父类继承的
ch.eat();//重写父类的,实现多态
ch.playShadowBoxing();//子类特有的方法
//编译时类型 // 运行时类型
Programmer ch2 = new Chinese();
ch2.writeCode(); //非多态
ch2.eat(); // 多态 调用的是子类重写的方法
//ch2.playShadowBoxing(); // 无法调用子类特有的方法
}
}
2.!!! 面向对象的设计原则之一:开闭原则OCP !!!
-
对扩展开放,对修改关闭
-
更通俗翻译:软件系统中的各种组件,如模块(Modules)、类(Classes)以及功能(Functions)等,应该在不修改现有代码的基础上,引入新功能
-
面向对象设计需要遵守的最高原则,也是最终目标
3. 向上转型
将子类对象赋给父类引用,称为向上转型(upcasting),自动进行类型转换。向上转型可以调用的子类继承的方法,但不能调用子类特有的方法。需要特别理解的是如果子类重写了父类的方法,向上转型后通过父类引用调用的却是真实子类重写的方法
public class Test {
public static void main(String[] args) {
//基本数据类型的自动转换
int n = 10;
System.out.println( n );
double d = n; // 左>右 自动转换
System.out.println(d);
//引用数据类型的自动转换
// 招聘程序员,来个英国籍程序员,满足要求,不需要特别声明
Programmer programmer = new Chinese(); // 自动转换 向上转型 左>右
// 不管是哪个国籍的,写到代码都是Java代码
programmer.writeCode();
// 中午休息了,大家都去食堂开始吃饭,哪个国家的人用哪个国家的吃饭方式
programmer.eat();
// 老板随便找一个程序员,说一起赛马吧,不可以;因为对方可能是中国或意大利程序员,不会赛马
//programmer.playShadowBoxing();
}
}
4. 向下转型
-
将父类的引用变量转换为子类类型,称为向下转型(downcasting)。向下转型后就可以调用子类特有的方法了。
-
需要进行强制转换Chinese ch = (Chinese)pro;
-
强制转换不是做手术,必须转换成真实子类型,否则ClassCastException;(类的类型转换异常)
-
向下转型之前肯定发生了向上转型
-
为了避免ClassCastException,向5下转型之前使用instanceof(实例对象)先判断一下
-
语法:
pro instanceof Italian 该对象是某个类或接口的实例对象
对象 instanceof 类或者接口
使用instancof的前提:左边的对象和右边的类型在继承树上有上下级关系,才能判断true, false
-
public class TestPoly2 {
public static void main(String[] args) {
//基本数据类型的强制转换
double d = 3.14;
System.out.println(d);
int n = (int)d; // 左 < 右 将数据的本身也发生了改变 做手术
// 若是向下转型数据溢出,会从每个数据范围的下界重新获取
System.out.println(n);
//引用数据类型的强制转换
Programmer programmer = new Chinese();
programmer.eat();//多态
//programmer.playShadowBoxing();
Chinese ch = (Chinese) programmer; // 左<右 不能做手术,必须转换成原来//的真实子类型
ch.playShadowBoxing();
// English en = (English)programmer; 做手术了,怎么能把中国人变成英国人
// en.raceHorse();
if(programmer instanceof Chinese){
Chinese ch = (Chinese) programmer;
ch.playShadowBoxing();
}else if(programmer instanceof English){
English en = (English) programmer;
en.raceHorse();
}else{
Italian it = (Italian) programmer;
it.playFootBall();
}
//java.lang.ClassCastException:
System.out.println(programmer instanceof Chinese); //false
System.out.println(programmer instanceof English); //true
System.out.println(programmer instanceof Programmer);//true
System.out.println(programmer instanceof Object);//true
//System.out.println(programmer instanceof String);
}
}
动态绑定
- 父类引用指向子类对象,调用对应的子类方法
- 实例方法的调用,只和引用所指向的对象有关
- 动态绑定三大前提
- 要有继承关系
- 要有方法重写
- 父类引用指向子类对象
Car c = new Bmw();
c.run();
c = new Benz();
c.run();
5.简单工厂模式==>返回值是父类类型
-
不仅可以使用父类作为形参, 而且可以使用父类做方法的返回类型 , 真实返回的对象可以是该类任何一个子类对象
-
它是解决大量对象创建问题的一个解决方案。将创建和使用分开,工厂负责创建,使用者直接调用即可。简单工厂模式的基本要求是:
- 定义一个static方法,通过类名直接调用
- 返回值类型是父类类型,返回的可以是其任意子类类型
- 传入一个字符串类型的参数,工厂根据参数创建对应的子类产品
-
下面设计的食物工厂
-
运用了方法重写
-
运用了多态
-
简单工厂模式
-
缺点: 仍然有代码复用,可用抽象,接口解决, 或者在新增子类的时候, 仍然需要继续添加代码
所以只是简化版的工厂模式
-
Food父类
public class Food {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Beef子类
public class Beef extends Food{
private String name;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
Egg子类
public class Egg extends Food{
private String name;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
Vegetables子类
public class Vegetables extends Food{
private String name;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
食物工厂类
public class FoodFactory {
public static Food getFood(String name) {
Food food = null;
if ("vegetables".equals(name)) {
food = new Vegetables();
food.setName("蔬菜");
return food;
} else if ("beef".equals(name)) {
food = new Beef();
food.setName("牛肉");
return food;
} else if ("egg".equals(name)) {
food = new Egg();
food.setName("炒蛋");
return food;
} else {
food = new Food();
food.setName("....");
return food;
}
}
}
学生类
public class Student {
public static void main(String[] args) {
Food food = FoodFactory.getFood("egg");
System.out.println("今天吃了" + food.getName());
}
}
优化代码
Food父类接口
public interface Food {
public String getName();
}
抽象食物类
public abstract class AbstractFood implements Food{
private String name;
// 重写方法接口Food的方法
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Beef子类
public class Beef extends AbstractFood {
}
Egg子类
public class Egg extends AbstractFood {
}
Vegetables子类
public class Vegetables extends AbstractFood{
}
食物工厂类
public class FoodFactory {
public static Food getFood(String name) {
Food food = null;
if ("vegetables".equals(name)) {
Vegetables vegetables = new Vegetables();
vegetables.setName("蔬菜");
food = vegetables;
} else if ("beef".equals(name)) {
Beef beef = new Beef();
beef.setName("牛肉");
food = beef;
} else if ("egg".equals(name)) {
Egg egg = new Egg();
egg.setName("炒蛋");
food = egg;
} else {
food = null;
}
return food;
}
}
学生类
public class Student {
public static void main(String[] args) {
Food food = FoodFactory.getFood("egg");
System.out.println("今天吃了" + food.getName());
food = FoodFactory.getFood("ffff");
System.out.println("今天吃了" + food);
// 输出
// 今天吃了炒蛋
// 今天吃了null
}
}
设计模式
-
有23中经典的设计模式,是一套被多数人知晓、经过分类编目的、反复使用的优秀代码设计经验的总结。每个设计模式均是特定环境下特定问题的处理方法。
-
面向对象设计原则是面向对象设计的基石,面向对象设计质量的依据和保障,设计模式是面向对象设计原则的经典应用。
-
简单工厂模式并不是23中经典模式的一种,是其中工厂方法模式的简化版
16.最终类 final关键字
- 在java中 使用final修饰的类,是最终类,这种类,不能再派生子类.
final关键字的作用
-
修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。被final修饰的变量也叫常量,常量名命名规则: 都是大写
final int MAX_SPEED = 120;
-
修饰方法:该方法不可被子类重写。但是可以被重载!
final void study(){}
-
修饰类: 修饰的类不能被继承。比如:Math 、String、System等。
public final class A {}
注意
-
final不能修饰构造方法
-
final修饰基本数据类型,值只能赋值一次,后续不能再赋值
-
final修饰引用数据类型,final Dog dog = new Dog(“亚亚”);,不能变化的引用变量的引用地址值,可以变化的是对象的属性(要想对象属性在一次赋值后也不能改变可在属性前加上final)
17. 抽象
1. 抽象的概念
- 通过abstract关键字声明的类,叫做抽象类
- 一个类被声明为抽象类后,不可以在外部被实例化
- 抽象类中,可以包含抽象方法和普通方法
- 一个类如果包含了抽象方法,则该类必须是一个抽象类
public abstract class 类名{
}
2. 为什么要抽象类
- 之前的Dog、Cat等类及其它们的父类Animal类, 我们在具体使用时,通常不会去特意实例化父类Animal, 而是去实例化具体的某个子类对象。而且有个动物叫动物吗? 所以下面的实例化通常就没有实际意义了:
Animal animal = new Animal();
- 所以我们可以把这种没有意义的实例化去掉,让Animal这个父类变为抽象的一种概念,在外部不能对其实例化。
public abstract class Animal{
//省略属性和方法
}
3.抽象方法
-
Animal类被抽象化以后,它里面的方法,eat(), speak(), run(), sleep() 这些方法,其实都在描述一种动物它应该所具备的一些行为特征,其实也是一种抽象的概念。把这些概念性的,但是又包 含具体方法实现的方法,派继承给我们的子类后,这些子类往往是用不上的,有时为了正确使用,不得 不去重写它们,而如果忘记重写它们,在调用时,又会调用到这些不太有意义的方法。
-
我们就可以使用抽象方法进行解决。
-
我们将抽象的父类中的那些对子类很重要的方法,但是又不需要父类去定义方法主体的方法,**定义成抽 象方法。 子类继承了这个父类后,就必须要实现这些方法后,才能使用。 **
-
从面向对象的角度来看,一个抽象的父类,其实是对它们子类的是一种行为上的约束,即,你是一只动 物,则,你必须要会eat(), 会sleep(), 会run(), 会speak(), 这样,才是一只合格的动物。
-
子类继承抽象父类后, 必须实现父类的抽象方法才能继承成功, 而且抽象父类中的抽象方法就无法直接被调用
public abstract class Animal{
// 定义抽象方法
public abstract void eat();
// 省略其他抽象方法
}
子类
// 实现抽象类
public class Dog extends Animal {
// 实现抽象方法
@Override
public void eat() {
System.out.println("狗狗吃骨头");
}
}
要点
抽象类的使用要点:
-
有抽象方法的类只能定义成抽象类
-
抽象类不能实例化,即不能用new来实例化抽象类。
-
抽象类必须有构造方法,创建子类对象的时候使用
-
一个抽象类至少0个抽象方法,至多(所有的方法都是抽象方法)个抽象方法
-
子类必须重写父类的抽象方法,不重写就提示编译错误;或者子类也定义为抽象类(这样子类也不能new)
-
override 重写 implements 实现
父类的方法是抽象的,需要子类实现;父类的方法不是抽象的,子类可以重写
18. 接口
1.定义
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
在Java当中,接口也是一个类、也包含方法,我们定义了一个接口类以后,就可以支持多个实现类,接口的调用方不用关心是谁写的实现类,只关心接口的方法参数、方法返回值就可以无缝的完成程序的对接。
类似于出现类中的抽象方法,子类必须实现抽象方法
声明格式
[访问修饰符] interface 接口名 [extends 父接口1,父接口2…] {
常量定义;
方法定义;
}
定义接口的详细说明:
-
**访问修饰符:**只能是public或默认。
-
**接口名:**和类名采用相同命名机制。
-
extends:接口可以多继承。
-
**常量:**接口中的属性只能是常量,总是:public static final 修饰。不写也是。
-
**方法:**接口中的方法只能是:public abstract。 省略的话,也是public abstract。
要点
-
子类通过implements来实现接口中的规范。
-
接口不能创建实例,但是可用于声明引用变量类型。
-
一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
-
JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
JDK1.8(含8)后,接口中包含普通的静态方法、默认方法。
2.总结
-
接口的组成
-
接口和数组、类、抽象类是同一个层次的概念
-
成员变量:接口中所有的变量都使用public static final修饰,都是全局静态常量
-
成员方法: 接口中所有的方法都使用public abstract修饰,都是全局抽象方法
-
构造方法:接口不能new,也没有构造方法
-
接口做方法的形参,实参可以是该接口的所有实现类
-
-
接口和多继承
-
C++ 多继承
好处 :可以从多个父类继承更多的功能
缺点:不安全 有两个父类Father1,Father2,都有一个方法giveMoney(),子类如果重写了,没有问题,如果 子类没有重写,调用giveMoney()是谁的 -
Java 单继承
好处:安全 缺点:功能受限
解决方案:既安全,功能又强大,采用接口。接口变相的使Java实现了C++的多继承,又没有C++多继承 的不安全性
public class Bird extends Animal implements Flyable,Sleepable
-
3. JDK1.8的接口新特征
JDK7及其之前
-
接口的变量都是public final static 全局静态常量,无变化
-
接口中都是抽象abstract方法,不能有static方法(因为abstract和static、final、private不能共存)
JDK1.8及其之后
- 接口中可以添加非抽象方法(static),实现类不能重写,只能通过接口名调用。
- 如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用
public interface Interface0 {
// 非抽象方法static
public static void test1() {
System.out.println("接口非抽象方法static测试");
}
public static void main(String[] args) {
Interface0.test1();
}
}
- 接口中可以添加非抽象方法(default默认方法),实现类可以继承或者重写,只能通过对象名调用
- 实现类可以直接使用default方法,可以重写default方法,但是必须去掉default。(default只能接口中使用)
public interface Interface0 {
// default
public default void test2() {
System.out.println("接口非抽象方法default测试");
}
public static void main(String[] args) {
Interface0.test1();
Book book = new Book(1);
book.test2();
}
}
- 上级接口中default方法的调用:MyInterface.super.method2()
4.面向接口编程
- 面向接口编程是面向对象编程的一部分。
- 接口就是规范,就是项目中最稳定的核心! 面向接口编程可以让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。
- 通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和和可维护性。
- 面向接口编程的概念比接口本身的概念要大得多。设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难!
抽象类和接口的区别和共同的
- 都可以定义抽象方法
- 都不能直接实例化对象
- 区别:
- 抽象类可以拥有成员变量和构造方法,是给子类继承的,接口没有
- 抽象类中可以拥有抽象方法和非抽象方法
- 接口在jdk1.8之前只能定义抽象方法,在jdk1.8之后可以定义静态方法和默认方法(default)
19.内部类(面试可能多)
-
内部类是一类特殊的类,指的是定义在一个类的内部的类。实际开发中,为了方便的使用外部类的相关属性和方法,这时候我们通常会定义一个内部类。
-
一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类(innerclasses)。
-
在Java中内部类主要分为成员非静态成员内部类、静态成员内部类、局部内部类、匿名内部类。
1.非静态成员内部类
- 作为类的成员存在,和成员变量、成员方法、构造方法、代码块并列。因为是类的成员,所以非静态成员内部类可以使用public、protected 、默认、private修饰,而外部类只能使用public、默认修饰。
public class OuterClass {
//成员变量
private String name;
private int num = 10;
//构造方法
public OuterClass() {
}
public OuterClass(String name, int num) {
this.name = name;
this.num = num;
}
//成员方法
public void methodOut(){
System.out.println("methodOut");
}
public void methodOut2(){
//外部类不可以直接访问内部的的成员变量和成员方法
//System.out.println(type);
//methodInner();
InnerClass ic = new InnerClass();
System.out.println(ic.num);//20
ic.methodInner();
}
//内部类
class InnerClass{
//成员变量
private String type;
private int num = 20;
//构造方法
public InnerClass() {
}
public InnerClass(String type, int num) {
this.type = type;
this.num = num;
}
//内部类的成员方法
public void methodInner(){
//内部类可以直接访问外部类的成员变量
System.out.println(name);
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
//内部类如何访问外部类的同名成员变量
System.out.println(OuterClass.this.num);//10
methodOut();
}
}
}
public class Test {
public static void main(String[] args) {
OuterClass oc = new OuterClass();
oc.methodOut();
oc.methodOut2();
//要创建 非静态成员内部类对象,必须先创建外部类的对象
//OuterClass.InnerClass ic = new OuterClass().new InnerClass();
OuterClass oc2 = new OuterClass();
OuterClass.InnerClass ic = oc2.new InnerClass();
}
}
总结
总结1:基本特征
-
内部类可以直接访问外部类的成员
-
外部类不能直接访问内部类的成员,需要先创建对象再通过对象名访问
-
内部类如何访问外部类的同名成员变量:OuterClass.this.num
-
必须先创建外部类的对象,才能创建内部类的对象。非静态成员内部类是属于某个外部类对象的
总结2:更多特征
-
非静态内部类不能有静态方法、静态属性和静态初始化块。
-
外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例
注意
- 内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后会出现Outer.class和Outer$Inner.class两个类的字节码文件。所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。
2.静态成员内部类
-
静态内部类只能够访问外部类的静态成员
-
静态内部类如何访问外部类的同名的成员变量:OuterClass.num
-
静态内部类属于整个外部类的。创建静态内部类的对象,不需先创建外部类的对象
-
外部类可以通过类名直接访问内部类的静态成员,访问非静态成员依旧需要先创建内部类对象。
3.局部内部类
- 它是定义在方法内部的,作用域只限于本方法,称为局部内部类。
- 局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法中被使用,出了该方法就会失效。
- 注意:局部内部类访问所在方法的局部变量,要求局部变量必须使用final修饰。JDK1.8中final可以省略,但是编译后仍旧会加final。
4.匿名内部类
-
匿名内部类就是内部类的简化写法,是一种特殊的局部内部类。
-
前提:存在一个类或者接口,这里的类可以是具体类也可以是抽象类。
-
本质是什么呢?是一个继承了该类或者实现了该接口的子类匿名对象。
-
适合那种只需要创建一次对象的类。比如:Java GUI编程、Android编程键盘监听操作等等。比如Java开发中的线程任务Runnble、外部比较器Comparator等。
语法
new 父类构造器(实参类表) \实现接口 () {
//匿名内部类类体!
}
问题:一个类实现Comparable接口,只能指定一种比较大小的规则,如果需要有更多的比较规则,怎么办?
**解决1: **内部比较器 public class Student implements Comparable{} 内部比较器只能有一个,一般采用最经常使用的比较规则 (无法解决多个规则的比较)
**解决2: **外部比较器 可指定多个 不需要Student实现该接口,而是定义专门的类。(规则很多个时要写的类太多)
**解决3: ** 匿名类
public interface Comparator {
int compare(Object obj1, Object obj2);
}
public class Test {
public static void main(String[] args) {
Book book1 = new Book("倚天屠龙记1","金庸1","清华大学出版社",35);
Book book2 = new Book("倚天屠龙记5","金庸5","清华大学出版社",35);
// 对于Compare接口,定义了一个运算规则,只使用一次,就构造了该内部类
// 类似于创建了一个实现类实现了接口,用完了该实现类就把它删了,而不用创建很多实现类来实现不同的运算规则
Compare compare = new Compare() {
@Override
public int compareTo(Object obj,Object o) {
Book book1 = (Book) obj;
Book book2 = (Book) o;
return (int) (book1.getPrice() - book2.getPrice());
}
};
int result = compare.compareTo(book1, book2);
System.out.println(result);
}
}
总结
-
匿名内部类可以实现一个接口,也可以继承一个类(可以是抽象类)。
-
匿名内部类只能实现一个接口,而不是多个
-
必须实现所有的方法,匿名内部类不能是抽象类
-
匿名内部类不可能有构造方法,因为类是匿名的
-
匿名内部类没有访问修饰符
-
如果想实现构造方法的一些初始化功能,可以通过代码块实现
-
如果要访问所在方法的局部变量,该变量需要使用final修饰。(JDK1.8可省略final)
内部类的作用和使用场景
内部类的作用:
-
内部类提供了更小的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
-
内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。 但外部类不能访问内部类的内部属性。
-
✳✳接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。✳
-
用匿名内部类实现回调功能。我们用通俗讲解就是说在Java中,通常就是编写一个接口,然后你来实现这个接口,然后把这个接口的一个对象作以参数的形式传到另一个程序方法中, 然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能
内部类的使用场合:
-
由于内部类提供了更好的封装特性,并且可以很方便的访问外部类的属性。所以,在只为外部类提供服务的情况下可以优先考虑使用内部类。
-
使用内部类间接实现多继承:每个内部类都能独立地继承一个类或者实现某些接口,所以无论外部类是否已经继承了某个类或者实现了某些接口,对于内部类没有任何影响。
UML关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASFwrwtL-1626089625031)(https://images0.cnblogs.com/blog/151517/201308/01161024-6fe577c238fd41aa8fdaeacbff7a451e.png)]
https://www.cnblogs.com/hoojo/p/uml_design.html
https://blog.csdn.net/ruren1/article/details/81584232
类之间的六种关系
- 类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合
1.依赖
-
只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了。
- 在该类的成员属性用到对方
- 在该类的方法的返回类型用到对方
- 在该类的方法接收的参数类型用到对方
- 在该类的方法中使用到对方
2.泛化关系(继承)
- 泛化关系实际上就是继承关系,是依赖关系的特例
- 如果A类继承了B类,我们就说A和B存在泛化关系
3.实现关系(接口与实现类)
- 实现关系实际上是A类实现B类,是依赖关系的特例
4.关联关系
- 关联关系实际上就是类与类之间的联系,他是依赖关系的特例
- 关联具有导航性:即双向关系和单向关系
- 关联具有多重性:1对1,1对多,多对1,多对多
5.聚合关系
- 聚合关系表示的是整体和部分的关系,整体和部分可以分开。聚合关系是关联关系的特例,所以他具有关联的导航性和多重性
- 例子:假定我们认为一台电脑中电脑和显示器可以分开,电脑和鼠标也可以分开
public class Computer{
private Mouse mouse; // 认为电脑和鼠标可以分开
private Monitor monitor;
public void setMouse(Mouse mouse){
this.mouse = mouse;
}
}
public class Mouse{
}
public class Monitor{
}
6.组合
- 组合也是整体与部分的关系,但是整体与部分不能分开。两者是共生共灭的关系
- 例子1:假定在上一个电脑案例中,如笔记本电脑,我们认为鼠标和显示器不能和电脑分开
public class Computer{
private Mouse mouse = new Mouse(); // 认为电脑和鼠标可以分开
private Monitor monitor = new Monitor();
public void setMouse(Mouse mouse){
this.mouse = mouse;
}
}
- 例子2:一个人必须有头部,而某些IDcard可有可无,则人与头是不可分开的,是组合关系‘人与某些IDcard是可分开的,是聚合关系
public class Person{
private IDcard card; // 聚合关系
private Head head = new Head(); // 组合关系
}
public class Head{
}
public class IDcard{
}
20.设计模式
1.工厂方法模式
- 简单工厂模式的问题: 当需要创建新的子类产品时,必须要去修改工厂类中具体的业务方 法。
- 工厂方法模式,由父类工厂提供了创建产品的标准(抽象方法),再由不同的子类工厂去实现。
- 工厂方法模式,将创建具体产品对象的动作,延迟到了子类工厂去创建。
简单工厂模式的代码
//运算类抽象类
public abstract class Operator {
public int numA;
public int numB;
//获取结果
public int getResult();
}
public class AddOperator extends Operator{
public int getResult(){
return numA+numB;
}
}
public class SubOperator extends Operator{
public int getResult(){
return numA-numB;
}
}
public class MulOperator extends Operator{
public int getResult(){
return numA*numB;
}
}
public class DivOperator extends Operator{
public int getResult(){
return numA/numB;
}
}
//简单工厂类
public class SimpleFactory {
// 这里包含一个static的知识点,可以先从普通方法进行
public Static Operator createOperator(String type) {
Operator opr = null;
switch(type){
case "+":
opr = new AddOperator();
break;
case "-":
opr = new SubOperator();
break;
case "*":
opr = new MulOperator();
break;
case "/":
opr = new DivOperator();
break;
}
return opr;
}
}
// 简易版main()
main(){
String type = "+";
Operator opr = SimpleFactory.createOperator(type);
opr.numA = 10;
opr.numB = 20;
int result = opr.getResult();
}
工厂方法模式代码
计算类
public abstract class Operator {
private double numA;
private double numB;
public abstract double getResult();
public double getNumA() {
return numA;
}
public void setNumA(double numA) {
this.numA = numA;
}
public double getNumB() {
return numB;
}
public void setNumB(double numB) {
this.numB = numB;
}
}
子类加法
public class AddOperator extends Operator {
@Override
public double getResult() {
return this.getNumA() + this.getNumB();
}
}
子类减法
public class SubOperator extends Operator {
@Override
public double getResult() {
return super.getNumA() - super.getNumB();
}
}
计算工厂
public abstract class OperatorFactory {
public abstract Operator cale();
}
计算工厂子类–加法计算工厂
public class AddOperatorFactory extends OperatorFactory {
@Override
public Operator cale() {
return new AddOperator();
}
}
计算工厂子类–减法计算工厂
public class SubOperatorFactory extends OperatorFactory {
@Override
public Operator cale() {
return new SubOperator();
}
}
测试类
public class Test {
public static void main(String[] args) {
// 确定的是加法
// 缺点: 在想用减法的时候仍然需要改代码改成new SubOperatorFactory();,下面有优化
OperatorFactory factory = new AddOperatorFactory();
Operator operator = factory.cale();
operator.setNumA(12);
operator.setNumB(10);
System.out.println(operator.getResult());// 结果为22.0
}
}
工厂方法模式的优化
- 使用配置文件+反射模式优化工厂模式
- 客户需求,保存在配置文件中,需求变更直接改配置文件即可
- 在工程目录下的resources创建data配置文件
- 在配置文件中写下顾客的需求,比如顾客想要加法,则在配置文件中填写加法类类的限定名
- 封装获取类对象限定名的方法
// 获取类对象的限定名
public String fetchClassName() {
try {
Properties properties = new Properties();
// 读取配置文件
InputStream inputStream =
TestProperties.class.getClassLoader().getResourceAsStream("data.properties");
properties.load(inputStream);
String content = properties.getProperty("className");
return content;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
改造优化Test类中的代码
import com.TestProperties;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
Test test = new Test();
// 获取要创建对象的限定名
String classname = test.fetchClassName();
// 工厂方法模式
System.out.println(classname);
try {
// Class.forName() 将类的.class文件加载到jvm中,并对类进行解释,执行类中的static块。
OperatorFactory factory = (OperatorFactory) Class.forName(classname).newInstance();
Operator operator = factory.cale();
operator.setNumA(12);
operator.setNumB(10);
System.out.println(operator.getResult());
}catch (Exception e){
e.printStackTrace();
return;
}
}
// 获取类对象的限定名
public String fetchClassName() {
try {
Properties properties = new Properties();
// 读取配置文件
InputStream inputStream =
TestProperties.class.getClassLoader().getResourceAsStream("data.properties");
properties.load(inputStream);
String content = properties.getProperty("className");
return content;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
抽象工厂模式
- 对于一批,多种类型的对象需要创建的场景时,工厂方法模式显然无法很好满足需求。这时候用抽象工厂模式就更好。
- 工厂方法模式是将多个产品抽象,使用一个工厂统一创建;抽象工厂模式就是把多个工厂也进一步抽象,将由创建工厂的工厂来创建
-
小明家新装修了,现在需要一批家电:电灯、电视、冰箱、空调、洗衣机。小明喜欢TCL品牌的,所以买的都是该品牌的。
后来,小明娶了老婆,老婆喜欢海尔品牌的,小明家有钱,任性,现在需要将所有的家电换成海尔品牌。
现用抽象工厂进行描述。
家电接口Appliance
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:09
*@desc 家电接口,给海尔家电和TCL家电来实现
*/
public interface Appliance {
// 根据顾客需求
void demand(Customer customer);
}
海尔冰箱类, 海尔空调类,海尔洗衣机类,海尔电视类
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc Haier空调
*/
public class HaierAirConditioner implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的空调");
}
}
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc Haier冰箱
*/
public class HaierRefrigerator implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的冰箱");
}
}
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc Haier电视
*/
public class HaierTV implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的电视");
}
}
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc Haier洗衣机
*/
public class HaierWashing implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的洗衣机"); }
}
TCL的四类
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc TCL空调
*/
public class TCLAirConditioner implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的空调"); }
}
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc TCL冰箱
*/
public class TCLRefrigerator implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的冰箱"); }
}
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc TCL电视
*/
public class TCLTV implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的电视"); }
}
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-30 22:45
*@desc TCL洗衣机
*/
public class TCLWashing implements Appliance {
@Override
public void demand(Customer customer) {
System.out.println("我是顾客"+customer.getName()+"想要的" + customer.getBrand() + "牌子的洗衣机");
}
}
家电工厂接口
package com.day5.homework.abstrack0;
import java.util.List;
/*
*@author JQ
*@create 2021-05-31 18:20
*@desc 家电工厂接口,给生产对应品牌家电工厂来实现
*/
public interface ApplianceFactory {
// 传入的是顾客所要买的家电类型
List<Appliance> getAppliance(Customer customer);
}
TCL家电生产工厂类
package com.day5.homework.abstrack0;
import java.util.ArrayList;
import java.util.List;
/*
*@author JQ
*@create 2021-05-31 18:18
*@desc TCL家电生产工厂,根据用户所要买的家电,分配给对应的专门生产某个家电的工厂生产
* 如顾客要电视则将专门给生产电视的工厂生产
*/
public class TCLFactory implements ApplianceFactory {
@Override
public List<Appliance> getAppliance(Customer customer) {
List<Appliance> appliances = new ArrayList<>();
for (String type : customer.getTypes()) {
Appliance appliance = null;
if ("冰箱".equals(type)) {
appliance = new TCLRefrigerator();
appliances.add(appliance);
} else if ("空调".equals(type)) {
appliance = new TCLAirConditioner();
appliances.add(appliance);
} else if ("电视".equals(type)) {
appliance = new TCLTV();
appliances.add(appliance);
} else if ("洗衣机".equals(type)) {
appliance = new TCLWashing();
appliances.add(appliance);
}
}
return appliances;
}
}
海尔家电生产工厂类
package com.day5.homework.abstrack0;
import java.util.ArrayList;
import java.util.List;
/*
*@author JQ
*@create 2021-05-31 18:18
*@desc 海尔家电生产工厂,根据用户所要买的家电,分配给对应的专门生产某个家电的工厂生产
* 如顾客要电视则将专门给生产电视的工厂生产
*/
public class HaierFactory implements ApplianceFactory {
@Override
public List<Appliance> getAppliance(Customer customer) {
List<Appliance> appliances = new ArrayList<>();
for (String type : customer.getTypes()) {
Appliance appliance = null;
if ("冰箱".equals(type)) {
appliance = new HaierRefrigerator();
appliances.add(appliance);
} else if ("空调".equals(type)) {
appliance = new HaierAirConditioner();
appliances.add(appliance);
} else if ("电视".equals(type)) {
appliance = new HaierTV();
appliances.add(appliance);
} else if ("洗衣机".equals(type)) {
appliance = new HaierWashing();
appliances.add(appliance);
}
}
return appliances;
}
}
家电品牌创造工厂
package com.day5.homework.abstrack0;
/*
*@author JQ
*@create 2021-05-31 18:38
*@desc 家电品牌创建工厂,根据用户的不同需求创建出TCL家电工厂和海尔家电工厂
*/
public class ApplianceBuilder {
public ApplianceFactory getBrand(Customer customer) {
if ("TCL".equals(customer.getBrand())) {
return new TCLFactory();
} else if ("Haier".equals(customer.getBrand())) {
return new HaierFactory();
} else {
return null;
}
}
}
顾客类
package com.day5.homework.abstrack0;
import java.util.List;
/*
*@author JQ
*@create 2021-05-30 22:40
*@desc
*/
public class Customer {
// 顾客名字
private String name;
// 顾客想买的牌子
private String brand;
// 顾客想买的家电类型集合
private List<String> types;
public List<String> getTypes() {
return types;
}
public void setTypes(List<String> types) {
this.types = types;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类
package com.day5.homework.abstrack0;
import java.util.ArrayList;
import java.util.List;
/*
*@author JQ
*@create 2021-05-31 18:42
*@desc
*/
public class Test {
public static void main(String[] args) {
List<String> types = new ArrayList<>();
ApplianceBuilder applianceBuilder = new ApplianceBuilder();
types.add("冰箱");
types.add("空调");
types.add("洗衣机");
types.add("电视");
System.out.println("==============小明============");
Customer customer1 = new Customer();
customer1.setName("小明");
customer1.setBrand("TCL");
customer1.setTypes(types);
ApplianceFactory applianceFactory = applianceBuilder.getBrand(customer1);
List<Appliance> appliances = applianceFactory.getAppliance(customer1);
for (Appliance appliance : appliances) {
appliance.demand(customer1);
}
System.out.println("============小明老婆===========");
Customer customer2 = new Customer();
customer2.setName("小明老婆");
customer2.setBrand("Haier");
customer2.setTypes(types);
//ApplianceBuilder applianceBuilder = new ApplianceBuilder();
applianceFactory = applianceBuilder.getBrand(customer2);
List<Appliance> appliancespro = applianceFactory.getAppliance(customer2);
for (Appliance appliance : appliancespro) {
appliance.demand(customer2);
}
}
}
// 结果:
==============小明============
我是顾客小明想要的TCL牌子的冰箱
我是顾客小明想要的TCL牌子的空调
我是顾客小明想要的TCL牌子的洗衣机
我是顾客小明想要的TCL牌子的电视
============小明老婆===========
我是顾客小明老婆想要的Haier牌子的冰箱
我是顾客小明老婆想要的Haier牌子的空调
我是顾客小明老婆想要的Haier牌子的洗衣机
我是顾客小明老婆想要的Haier牌子的电视
3.单例模式
- 只能创建一个实例,后续创建实例时也只能同一个实例
懒汉的单例模式(时间换空间)
public class SingleTon {
// 单例模式之懒汉模式
private static SingleTon singleTon = null;
public static SingleTon getInstance() {
if (singleTon == null) {
singleTon = new SingleTon();
}
return singleTon;
}
private SingleTon() {
}
}
饿汉的单例模式(空间换时间) 推荐!!
// 饿汉模式,在类加载的时候对象就被创建
// 程序员直接通过getInstance方法获取引用即可
public class SingleDog {
// 私有的静态的当前类的引用属性
// 当SingleDog类被加载时,所有的静态属性都被加载,新建的SingleDog对象的地址也会在
// 这个时候被SingleDog所保存
private static SingleDog singleDog = new SingleDog();
// 私有构造方法
private SingleDog() {
}
public static SingleDog getInstance() {
// 由于对象在方法调用之前,就已经成功创建,当前方法只需要返回当前引用对象即可,后续也都指向同一个实例对象
return singleDog;
}
}
21.异常
链接: 异常博客
1.定义
- 异常是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序。
- 异常不同与程序的编译错误、和逻辑错误,在程序在运行时发生的错误。
2.异常分类
异常主要分为:错误、一般性异常(受控异常)、运行时异常(非受控异常)
错误
- 如果应用程序出现了Error,那么将无法恢复,只能重新启动应用程序,最典型的Error的异常是:OutOfMemoryError
受控异常(编译异常)
- 这种异常属于一般性异常,出现了这种异常必须显示的处理,不显示处理java程序将无法编译通过。编译器强制普通异常必须try…catch处理,或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常。
非受控异常
-
非受控异常也即是运行时异常(RuntimeException),这种系统异常可以处理也可以不处理,所以编译器不强制用try…catch处理或用throws声明,所以系统异常也称为unchecked异常。
-
此种异常可以不用显示的处理,例如被0除异常,java没有要求我们一定要处理, 当出现这种异常时,肯定是程序员的问题,也就是说,健壮的程序一般不会出现这种系统异常。
常见异常类
IOException
:操作输入流和输出流时可能出现的异常ArithmetiException
:数字异常。如果把整数除以零,就会出现异常NullPointerException
:空指针异常ArrayIndexOutBoundsException
:下标越界异常,一般出现在数组中ClasscastException
:当类型转换失败时就会出现该异常IIIegalArgumentException
:非常参数异常,可以用来检查方法的参数是否合法
3.异常捕获
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7AqsFjno-1626089625035)(https://style.youkeda.com/img/ham/course/j2/ExceptionTest2.svg)]
- try中包含了可能产生异常的代码
- try后面是catch,catch可以有一个或多个,catch中是需要捕获的异常
- 当try中的代码出现异常时,出现异常下面的代码不会执行,马上会跳转到相应的catch语句块中,如果没有异常不会跳转到catch中
- finally表示,不管是出现异常,还是没有出现异常,finally里的代码都执行,finally和catch可以分开使用,但finally必须和try一块使用,如下格式使用finally也是正确的
try {
} finally {
}
private static void testException2() {
try {
//1、对可能产生异常的代码进行检视
//2、如果try代码块的某条语句产生了异常, 就立即跳转到catch子句执行, try代码块后面的代码不再执行
//3、try代码块可能会有多个受检异常需要预处理, 可以通过多个catch子句分别捕获
} catch (异常类型1 e1) {
//捕获异常类型1的异常, 进行处理
//在开发阶段, 一般的处理方式要么获得异常信息, 要么打印异常栈跟踪信息(e1.printStackTrace())
//在部署后, 如果有异常, 一般把异常信息打印到日志文件中, 如:logger.error(e1.getMessage());
} catch (异常类型2 e1) {
//捕获异常类型2的异常, 进行处理
//如果捕获的异常类型有继承关系, 应该先捕获子异常再捕获父异常
//如果没有继承关系, catch子句没有先后顺序
} finally {
//不管是否产生了异常, finally子句总是会执行
//一般情况下, 会在finally子句中释放系统资源
}
}
示例
private static void testException3() {
int i1 = 100;
int i2 = 0;
//try里是可能出现异常的代码
try {
//当出现被0除异常时,程序流程会执行到“catch(ArithmeticException arithmeticException)”语句,这里是运行是异常,编译可以通过
//被0除表达式以下的语句永远不会执行
int i3 = i1 / i2;
//永远不会执行
System.out.println(i3);
//采用catch可以拦截异常
//arithmeticException代表了一个ArithmeticException类型的局部变量
//采用arithmeticException主要是来接收java异常体系给我们new的ArithmeticException对象
//采用arithmeticException可以拿到更详细的异常信息
} catch (ArithmeticException arithmeticException) {
System.out.println("被0除了");
arithmeticException.printStackTrace();
}
}
4.异常抛出(thow和thows)
throws
- 用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就抛给谁,我自己不想处理。
- 用在方法声明后面,跟的是异常类名
- 可以跟多个异常类名,用逗号隔开
- 表示抛出异常,由该方法的调用者来处理
- throws表示出现异常的一种可能性,并不一定会发生这些异常
throw
- 是用来抛出一个具体的异常类型,自己处理用try,catch。
- 用在方法体内,跟的是异常对象名
- 只能抛出一个异常对象名
- 表示抛出异常,由方法体内的语句处理
- throw则是抛出了异常,执行throw则一定抛出了某种异常
3.一个例子
- 在某个方法里抛出了异常,必须要在方法签名上声明它,调用者调用的该方法,必须进行异常的捕获try…catch
public class ExceptionTest8 {
public static void main(String[] args) {
int num = 0;
try {
num = getInt(null);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println(num);
}
public static int getInt(String str) throws Exception {
if (str == null) {
throw new Exception("姓名不能为空");
}
return Integer.parseInt(str);
}
}
5.异常的信息
1.getMessage()
- 获取异常描述信息
使用异常对象的getMessage()方法,通常用于打印日志时
2.printStackTrace()
- 取得异常的堆栈信息
使用异常对象的printStackTrace()方法,比较适合于程序调试阶段
6.自定义异常
- 自定义异常通常继承于
Exception
或RuntimeException
,到底继承那个应该看具体情况来定。 - 自定义异常类可以有自己的变量和方法来传递错误代码或传递其它异常相关信息。
- 自定义空参数异常
public class ParamNullException extends Exception {
public ParamNullException() {
//必须调用父类默认的构造方法
super();
}
public ParamNullException(String msg) {
//手动调用父类的构造方法
super(msg);
}
}
- 测试
public class ExceptionTest {
public static void main(String[] args) {
String str = null;
try {
System.out.println(test(str));
} catch (ParamNullException e) {
System.out.println(e.getMessage());
}finally {
System.out.println("程序结束");
}
}
public static String test(String str) throws ParamNullException {
if (str == null) {
throw new ParamNullException("传入的参数不能为null");
}
return "恭喜你传参成功";
}
}
7.面试题(看博客)
1.谈谈你Java异常处理机制的理解?
Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为 java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。
Error: 表示应用程序本身无法克服和恢复的一种严重问题。
Exception: 表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常。
2.throw和throws的区别
-
throw
- throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
- throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。
- throw一般用于抛出自定义异常。
-
throws
- throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
- throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
- throws表示出现异常的一种可能性,并不一定会发生这种异常。
-
问题1:在什么情况下,catch后面的语句是不执行的
- 情况1:throw e;
- 情况2:发生的异常和catch中异常类型不匹配
- 情况3:return
-
问题2:不管什么情况下,希望某些语句都执行,怎么办
- finally语句
-
问题3:return和finally语句的执行顺序
- 执行return之前的语句----执行finally语句-----执行return
-
问题4:finally在实际开发中的使用场合
- IO流的管理,数据库连接的关闭 socket的关闭
-
问题5:唯一的例外
- System.exit(0); 终止当前正在运行的 Java 虚拟机。
22.常用类
1.包装类(封装类)
-
Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但我们在实际应用中经常需要将基本数据转化成对象,以便于操作。
-
为了解决这个不足,Java在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。8种基本数据类型,分别对应一个包装类。包装类均位于java.lang包
-
基本数据类型 包装类 byte Byte boolean Boolean short Short char Character int Integer long Long float Float double Double -
四种整型封装类和String都有常量池
-
但是四种整型封装类只有范围在-128~127之间的数,才支持常量池技术,超出范围后封装类的对象会直接存储在对空间中
int i1 = 1;
int i2 = 2;
int i3 = 3;
Integer a = 3;
Integer b = new Integer(i1) + new Integer(i2);
Integer c = new Integer(3);
System.out.println(a == b); // true 在进行+运算后,因为引用对象无法进行运算,所以运算后c与a对比会变成值对比
System.out.println(a == c); // false
System.out.println(b == c); // false
System.out.println(i3 == c); // true 会自动拆箱
为什么需要包装类?
-
某些方法的参数必须是对象,为了让基本数据类型的数据能作为参数,提供包装类。
-
包装类还可以提供更多的功能
-
其他特别重要的功能:比如可以实现字符串和基本数据类型之间的转换
注意
Ø 注意1:包装类的对象需要占用栈内存和堆内存,而基本数据类型的(局部)变量只占用栈内存;基本数据类型的变量占用空间少,更简单,更灵活,更高效。
Ø 注意2:作为成员变量,初始值不同。int 0;Integer null。
Ø 注意3:在这八个类中,除了Character和Boolean以外,其他的都是“数字型“,“数字型”都是java.lang.Number的子类。
自动拆箱和装箱
-
**自动装箱:**把基本数据类型直接转换为封装类型
-
拆箱: Integer i = 5;int j = i; 这样的过程就是自动拆箱。
-
在==比较的过程中,会隐式的自动将封装类转换为基本类型
-
用一句话总结自动装箱/拆箱:
-
自动装箱过程是通过调用包装类的valueOf()方法实现的,而自动拆箱过程是通过调用包装类的 xxxValue()方法实现的(xxx代表对应的基本数据类型,如intValue()、doubleValue()等)。
-
JDK1.5以后,增加了自动装箱与拆箱功能,如:
Integer i = 100; int j = new Integer(100);
-
自动装箱调用的是valueOf()方法,而不是new Integer()方法。
-
自动拆箱调用的xxxValue()方法。
包装类在自动装箱时为了提高效率,对于-128~127之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用equals方法。
2.String类
https://www.cnblogs.com/zhangyinhua/p/7689974.html
- 本质上是基本数据类型
- 具有不可变性,一旦定义就不可再改变,要改变的话只能在堆内存生成新的后重新指向
- String类是一个final类,意味着该类不能再有子类。
- 因为String太过常用,JAVA类库的设计者在实现时做了个小小的变化,即采用了享元模式,每当生成一个新内容的字符串时,他们都被添加到一个常量池中,当第二次再次生成同样内容的字符串实例时,就共享此对象,而不是创建一个新对象,但是这样的做法仅仅适合于通过=符号进行的初始化。
- 创建方式:
String a = "A"; // 直接赋值
String b = new String("B");//通过构造函数创建
String c = a + b; // 通过+号连接符进行字符串拼接
- 例子
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a==b); //true
System.out.println(a.equals(b)); //true
System.out.println(a==c); //false
System.out.println(a.equals(c)); //true
String a = "a";
String b = "b";
String c = "ab";
// 如果在拼接时,左右两边都是直接字符串,而不是字符串的引用,jvm在编译期间会将其直接拼接起来
String d = a + b;
String e = "a" + "b";
System.out.println(c == d); // false
System.out.println(c == e); // true
equals源码
- 比较的是内容
1.常用方法
方法摘要 | |
---|---|
char[] | str.toCharArray() 将字符串转换成对应的char数组并返回 |
char | charAt(int index) 返回指定索引处的 char 值。 |
int | compareTo( StringanotherString) 按字典顺序比较两个字符串。 |
String | concat( Stringstr) 将指定字符串连接到此字符串的结尾。 |
boolean | contains( CharSequences) 当且仅当此字符串包含指定的 char 值序列时,返回 true。 |
boolean | endsWith( Stringsuffix) 测试此字符串是否以指定的后缀结束。 |
boolean | equals( ObjectanObject) 将此字符串与指定的对象比较。 |
boolean | equalsIgnoreCase( StringanotherString) 将此 String 与另一个 String 比较,不考虑大小写。 |
int | hashCode() 返回此字符串的哈希码。 |
int | indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。 |
int | [indexOf](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#indexOf(int, int))(int ch, int fromIndex) 返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。 |
int | indexOf( Stringstr) 返回指定子字符串在此字符串中第一次出现处的索引。 |
int | [indexOf](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#indexOf(java.lang.String, int))( Stringstr, int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。 |
boolean | isEmpty() 当且仅当 length() 为 0 时返回 true 。 |
int | lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。 |
int | [lastIndexOf](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#lastIndexOf(int, int))(int ch, int fromIndex) 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。 |
int | lastIndexOf( Stringstr) 返回指定子字符串在此字符串中最右边出现处的索引。 |
int | [lastIndexOf](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#lastIndexOf(java.lang.String, int))( Stringstr, int fromIndex) 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。 |
int | length() 返回此字符串的长度。 |
String | [replace](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#replace(char, char))(char oldChar, char newChar) 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 |
String | [replace](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#replace(java.lang.CharSequence, java.lang.CharSequence))( CharSequencetarget, CharSequencereplacement) 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。 |
String | [replaceAll](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#replaceAll(java.lang.String, java.lang.String))( Stringregex, Stringreplacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 |
String[] | split( Stringregex) 根据给定正则表达式的匹配拆分此字符串。 |
boolean | startsWith( Stringprefix) 测试此字符串是否以指定的前缀开始。 |
boolean | [startsWith](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#startsWith(java.lang.String, int))( Stringprefix, int toffset) 测试此字符串从指定索引开始的子字符串是否以指定前缀开始。 |
String | substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。 |
String | [substring](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/String.html#substring(int, int))(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。[beginIndex, endIndex) |
String | toLowerCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 |
String | toString() 返回此对象本身(它已经是一个字符串!)。 |
String | toUpperCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 |
String | trim() 返回字符串的副本,忽略前导空白和尾部空白。 |
- 字符串格式化
- %s – 字符串
- %d – 整型
- %f – 浮点数
- %n – 换行
String str1 = "今天天气%s";
String str2 = String.format(str1, "暴雨");
System.out.println(str2);
System.out.printf(str1,"暴雨");
- 字符串和数字互转
- 注意字符串里只能是数字的组成
public static void main(String[] args) {
String text = "123";
// 转化字符串为数字
int a = Integer.parseInt(text);
System.out.println(a);
// 转化字符串为数字
a = Integer.parseInt("100");
System.out.println(a);
}
2.字符串内存图
3.String不可变的好处
-
可以实现多个变量引用堆内存中的同一个字符串实例,避免创建的开销。
-
程序中大量使用了String字符串,有可能是出于安全性考虑。
-
在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。
3. StringBuffer
- 可变字符串,
- StringBuffer本身的存储结构是char数组,每当值发生变化,会根据值的增长(超过了初始容量)对数组的长度进行扩容,将原有的元素复制到新数组中。
- *扩容规则:当容量到达上限时,将容量扩容为(当前容量上线+1)2
- 每一个StringBuffer的对象的初始容量是在初始化的时候构造函数内的字符串长度基础上 +16
- 构造方法
-
-
StringBuffer()
构造一个没有字符的字符串缓冲区,初始容量为16个字符。StringBuffer(CharSequence seq)
构造一个包含与指定的相同字符的字符串缓冲区CharSequence
。StringBuffer(int capacity)
构造一个没有字符的字符串缓冲区和指定的初始容量。StringBuffer(String str)
构造一个初始化为指定字符串内容的字符串缓冲区。
-
方法摘要
- append(),delete(),insert(),reverse(),capacity(),
方法摘要 | |
---|---|
StringBuilder | append(boolean b) 将 boolean 参数的字符串表示形式追加到序列。 |
int | capacity() 返回当前容量。 |
char | charAt(int index) 返回此序列中指定索引处的 char 值。 |
StringBuilder | [delete](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/StringBuilder.html#delete(int, int))(int start, int end) 移除此序列的子字符串中的字符。 [3,6) |
StringBuilder | deleteCharAt(int index) 移除此序列指定位置上的 char 。 |
void | ensureCapacity(int minimumCapacity) 确保容量至少等于指定的最小值。 |
int | indexOf( Stringstr) 返回第一次出现的指定子字符串在该字符串中的索引。 |
int | [indexOf](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/StringBuilder.html#indexOf(java.lang.String, int))( Stringstr, int fromIndex) 从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引。 |
StringBuilder | [insert](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/StringBuilder.html#insert(int, boolean))(int offset, boolean b) 将 boolean 参数的字符串表示形式插入此序列中。 |
int | lastIndexOf( Stringstr) 返回最右边出现的指定子字符串在此字符串中的索引。 |
int | [lastIndexOf](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/StringBuilder.html#lastIndexOf(java.lang.String, int))( Stringstr, int fromIndex) 返回最后一次出现的指定子字符串在此字符串中的索引。 |
int | length() 返回长度(字符数)。 |
StringBuilder | [replace](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/StringBuilder.html#replace(int, int, java.lang.String))(int start, int end, Stringstr) 使用给定 String 中的字符替换此序列的子字符串中的字符。 |
StringBuilder | reverse() 将此字符序列用其反转形式取代。 |
String | substring(int start) 返回一个新的 String ,它包含此字符序列当前所包含字符的子序列。 |
String | [substring](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK_API_1_6_zh_CN.CHM::/java/lang/StringBuilder.html#substring(int, int))(int start, int end) 返回一个新的 String ,它包含此序列当前所包含字符的子序列。 |
String | toString() 返回此序列中数据的字符串表示形式。 |
- StringBuffer类型转换为 String
StringBuffer sb = new StringBuffer("abc");
String str = sb.toString();
- String 类型转换为 StringBuffer
StringBuffer sb = new StringBuffer("abc");
String str = sb.toString();
StringBuffer sb2 = new StringBuffer(str);
4. StringBuilder
- StringBuilder 和 StringBuffer 代码几乎一样
- StringBuffer 是线程安全的,牺牲了性能
- StringBuilder 是线程不安全的,有较高的性能
- StringBuilder 的性能,读写效率比 StringBuffer高的多
性能差别:StringBuilder > StringBuffer >>>>>> String
System.out.println(System.currentTimeMillis());
long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 200000; i++) {
str += "a";
}
long end = System.currentTimeMillis();
System.out.println("+号耗费时间:" + (end - start) + "ms"); // 11069ms
long start1 = System.currentTimeMillis();
StringBuffer sb = new StringBuffer("");
for (int i = 0; i < 200000; i++) {
sb.append("a");
}
long end1 = System.currentTimeMillis();
System.out.println("append耗费时间:" + (end1 - start1) + "ms"); // 4ms
// 造成性能差异的主要原因在于+号连接符的底层原理过于麻烦
// String自己是不具备字符串拼接的能力的
// 先把String转换成StringBuilder,调用StringBuilder的append后再将StringBuilder转回String
5.Math类
Math
类包含执行基本数字运算的方法,如基本指数,对数,平方根和三角函数。
-
Math.abs()
-
Math.max()
-
Math.min()
-
-
static double
pow(double a, double b)
求a的b次幂
-
-
Math.random() 产生一个 0<=x<1 的double数
6.时间类
1.LocalDate(JDK1.8之后)
- 获取当前时间(只能获取年月日)
- LocalTime 获取时间时分秒,LocalDateTime都获取
import java.time.LocalDate;
public class DateTest {
public static void main(String[] args) {
// 得到当前的完整时间
LocalDate time = LocalDate.now();
// 打印出时间
System.out.println(time.toString());
}
}
2.DateTimeFormatter (JDK1.8之后)
- 转化日期格式
public class DateTest5 {
public static void main(String[] args) {
LocalDate time = LocalDate.now();
// 打印默认的时间数据
System.out.println(time.toString());
// 创建一个格式化方式
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy年MM月dd");
// 执行时间的格式化处理,得到期望格式的时间字符串
String timeStr = df.format(time);
// 打印时间
System.out.println(timeStr);
}
}
3.一些方法
- 获取当前时间所在的年 月 这个月中的天数 当前时间所在的星期数
public class DateTest {
public static void main(String[] args) {
LocalDate time = LocalDate.now();
// 得到当前时间所在年
int year = time.getYear();
System.out.println("当前年份 " + year);
// 得到当前时间所在月
int month = time.getMonth().getValue();
System.out.println("当前月份 " + month);
// 得到当前时间在这个月中的天数
int day = time.getDayOfMonth();
System.out.println("当前日 " + day);
// 得到当前时间所在星期数
int dayOfWeek = time.getDayOfWeek().getValue();
System.out.println("当前星期 " + dayOfWeek);
}
}
4.字符串转时间(parse)
import java.time.LocalDate;
public class DateTest8 {
public static void main(String[] args) {
// 定义一个时间字符串,日期是2019年1月1日
String date = "2019-01-01";
// 把字符串转化位 LocalDate 对象,并得到字符串匹配的日期
LocalDate date2 = LocalDate.parse(date);
// 打印出日期
System.out.println(date2.toString());
}
}
- 如果时间格式不是
yyyy-MM-dd
,就得借助DateTimeFormatter
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class DateTest81 {
public static void main(String[] args) {
// 定义一个时间字符串,日期是2019年1月1日
String date = "2019/01/01";
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy/MM/dd");
// 把字符串转化位 LocalDate 对象,并得到字符串匹配的日期
LocalDate date2 = LocalDate.parse(date,df);
// 打印出日期
System.out.println(date2.toString());
}
}
5.时间之间运算
import java.time.LocalDate;
public class DateTest {
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println("当前:" + now.toString());
System.out.println("加法运算");
System.out.println("加1天:" + now.plusDays(1));
System.out.println("加1周:" + now.plusWeeks(1));
System.out.println("加1月:" + now.plusMonths(1));
System.out.println("加1年:" + now.plusYears(1));
System.out.println("减法运算");
System.out.println("减1天:" + now.minusDays(1));
System.out.println("减1周:" + now.minusWeeks(1));
System.out.println("减1月:" + now.minusMonths(1));
System.out.println("减1年:" + now.minusYears(1));
}
}
两个时间的判断
import java.time.LocalDate;
public class DateTest11 {
public static void main(String[] args) {
LocalDate now = LocalDate.now();
// 可以对两个 LocalDate 进行比较,
// 可以判断一个日期是否在另一个日期之前或之后,
// 或者判断两个日期是否是同年同月同日。
boolean isBefore = now.minusDays(1).isBefore(LocalDate.now());
System.out.println("是否在当天之前:" + isBefore);
boolean isAfter = now.plusDays(1).isAfter(LocalDate.now());
System.out.println("是否在当天之后:" + isAfter);
// 判断是否是当天
boolean sameDate = now.isEqual(LocalDate.now());
System.out.println("是否在当天:" + sameDate);
}
}
日历类(Calendar)(老JDK里的date和SimpleDateFormat)
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class Test3 {
private static SimpleDateFormat sdf =
new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
public static void main(String[] args) {
// Date
// SimpleDateFormat 日期格式转换 字符串转日期,日期转字符串
// 获取当前时间
Date date = new Date();
System.out.println(date);
// 将时间转换成字符串
String timeStr = sdf.format(date);
System.out.println(timeStr);
// 将字符串转时间
// 手动抛异常
String dateStr = "2020年6月6日 10:40:45";
Date date1 = null;
try {
date1 = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date1.toString());
// Calender 日历类 负责对日期时间做设置和增删
//采用单例模式获取日历对象
Calendar c = Calendar.getInstance();
// 设置当前日历时间为date1
c.setTime(date1);
// 获取当前日历的时间
Date now = c.getTime();
// 将当前时间格式化输出
System.out.println("当前日期:\t" + sdf.format(now));
//在当前设置时间的基础上 往后翻一个月
c.add(Calendar.MONTH, 1);
System.out.println("下个月的今天:\t" + format(c.getTime()));
// 重置时间到当前时间
c.setTime(now);
// 修改日历的时间 单位为年
c.add(Calendar.YEAR, -1);
System.out.println("去年的今天:\t" + format(c.getTime()));
// 上个月的第三天
c.setTime(now);
c.add(Calendar.MONTH, -1);
c.set(Calendar.DATE, 3);
System.out.println("上个月的第三天:\t" + format(c.getTime()));
// 小例子
String timeStrs = "2022-02-22 00:00:00";
Date d = null;
try {
d = sdf.parse(timeStrs);
} catch (ParseException e) {
e.printStackTrace();
}
c.setTime(d);
c.set(Calendar.DATE, 1);
c.add(Calendar.DATE, -5);
System.out.println(c.getTime());
}
//format方法 返回的是按照我们指定格式 转换的日期 所对应的字符串
private static String format(Date time) {
return sdf.format(time);
}
}