四、多态
1.面向对象三大特征之三:多态
a.多态的概述、多态的形式
Ⅰ.什么是多态?
- 同类型的对象,执行同一个行为,会表现出不同的行为特征。(例如:猫和狗都是动物类型,执行的行为不一样,猫跑的行为是快,狗跑的行为是非常快。)
Ⅱ.多态的常见形式
父类类型 对象名称 = new 子类构造器;
// 接口也是一种父类——干爹
接口 对象名称 = new 实现类构造器;
Animal.java
/**
* 动物类:抽象类
*/
public abstract class Animal {
/**
* 跑:抽象方法
*/
public abstract void run();
}
Dog.java
public class Dog extends Animal {
/**
* 跑
*/
@Override
public void run() {
System.out.println("狗跑的很快!");
}
}
Turtle.java
public class Turtle extends Animal{
/**
* 跑
*/
@Override
public void run() {
System.out.println("乌龟跑的很慢!");
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 多态的形式
// 父类类型 对象名称 = new 子类构造器;
// Dog dog = new Dog();
Animal dog = new Dog();
dog.run();
// Turtle turtle = new Turtle();
Animal turtle = new Turtle();
turtle.run();
}
}
Ⅲ.多态中成员访问特点
- 方法调用:编译(编写代码)看左边,运行看右边。
- 变量调用:编译(编写代码)看左边,运行也看左边。(多态侧重行为多态/方法多态)
Animal.java
/**
* 动物类:抽象类
*/
public abstract class Animal {
public String name = "父类动物";
/**
* 跑:抽象方法
*/
public abstract void run();
}
Dog.java
public class Dog extends Animal {
public String name = "子类狗";
/**
* 跑
*/
@Override
public void run() {
System.out.println("狗跑的很快!");
}
}
Turtle.java
public class Turtle extends Animal{
public String name = "子类乌龟";
/**
* 跑
*/
@Override
public void run() {
System.out.println("乌龟跑的很慢!");
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 多态的形式
// 父类类型 对象名称 = new 子类构造器;
// Dog dog = new Dog();
Animal dog = new Dog();
// 方法调用:编译(编写代码)看左边,运行看右边。
dog.run();
// 变量调用:编译(编写代码)看左边,运行也看左边。(多态侧重行为多态/方法多态)
System.out.println(dog.name);
// Turtle turtle = new Turtle();
Animal turtle = new Turtle();
// 方法调用:编译(编写代码)看左边,运行看右边。
turtle.run();
// 变量调用:编译(编写代码)看左边,运行也看左边。(多态侧重行为多态/方法多态)
System.out.println(turtle.name);
}
}
Ⅳ.多态的前提
- 有继承/实现关系;有父类引用指向子类对象;有方法重写。
b.多态的好处
优势:
-
在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
Animal animal = new Dog(); animal.run(); /** * 比赛:所有动物可以参加比赛 */ public static void competition(Animal animal) { System.out.println("比赛开始!"); animal.run(); System.out.println("比赛结束!"); }
-
定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
多态下会产生的一个问题:
-
多态下不能使用子类的独有功能。
Dog.java
public class Dog extends Animal { public String name = "子类狗"; /** * 跑 */ @Override public void run() { System.out.println("狗跑的很快!"); } /** * 看门:狗类的独有功能 */ public void lookDoor() { System.out.println("狗在看门!"); } }
Test.java
public class Test { public static void main(String[] args) { Animal dog = new Dog(); dog.run(); // 多态下不能使用子类的独有功能 // dog.lookDoor(); } }
c.多态下引用数据类型的类型转换
自动类型转换(从子到父)
- 子类对象赋值给父类类型的变量指向。
强制类型转换(从父到子)
-
此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量。
-
作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
/** * 目标:学习多态形式下的类中转换机制 */ public class Test { public static void main(String[] args) { // 自动类型转换 Animal animal = new Dog(); animal.run(); // 强制类型转换 Animal animal1 = new Turtle(); animal1.run(); // 从父类类型到子类类型 必须强制类型转换 Turtle turtle = (Turtle) animal1; // 子类的独有功能 turtle.layEggs(); } }
-
注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException。
/** * 目标:学习多态形式下的类中转换机制 */ public class Test { public static void main(String[] args) { // 自动类型转换 Animal animal = new Dog(); animal.run(); // 强制类型转换 Animal animal1 = new Turtle(); animal1.run(); // 从父类类型到子类类型 必须强制类型转换 Turtle turtle = (Turtle) animal1; // 子类的独有功能 turtle.layEggs(); // 转型后的类型和对象真实类型不是同一种类型 // 强制类型转换 编译阶段不报错(注意:有继承或者实现关系编译阶段可以强制)但是运行可能出错 Dog dog = (Dog) animal1; } }
Java建议强制转换前,使用instanceof判断当前对象的真实类型,再进行强制转换。
/** * 目标:学习多态形式下的类中转换机制 */ public class Test1 { public static void main(String[] args) { // 自动类型转换 Animal animal = new Dog(); animal.run(); // 强制类型转换 Animal animal1 = new Turtle(); animal1.run(); // 变量名 instanceof 真实类型 // 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者其子类类型,是则返回true,反之返回false。 if (animal1 instanceof Turtle) { Turtle turtle = (Turtle) animal1; // 子类的独有功能 turtle.layEggs(); } else if (animal1 instanceof Dog) { Dog dog = (Dog) animal1; // 子类的独有功能 dog.lookDoor(); } competition(new Dog()); competition(new Turtle()); } /** * 比赛:所有动物可以参加比赛 */ public static void competition(Animal animal) { // 通用功能 animal.run(); if (animal instanceof Turtle) { Turtle turtle = (Turtle) animal; // 子类的独有功能 turtle.layEggs(); } else if (animal instanceof Dog) { Dog dog = (Dog) animal; // 子类的独有功能 dog.lookDoor(); } } }
总结:
- 引用数据类型的类型转换,有几种方式?
- 自动类型转换、强制类型转换。
- 强制类型转换能解决什么问题?强制类型转换需要注意什么?
- 可以转成真正的子类类型,从而调用子类独有功能。
- 有继承关系/实现关系的2个类型就可以进行强制转换,编译无问题。
- 运行时,如果强制转换后的类型不是对象真实类型则报错。
d.多态的综合案例
需求:
- 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
- 鼠标:被安装时可以完成接入、调用点击功能、拔出功能。
- 键盘:被安装时可以完成接入、调用打字功能、拔出功能。
分析:
- 定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。
- 提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。
- 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。
USB.java
/**
* USB接口
*/
public interface USB {
/**
* 接入USB设备
*/
void connect();
/**
* 断开USB设备
*/
void disconnect();
}
Keyboard.java
/**
* 键盘类
*/
public class Keyboard implements USB{
// 品牌
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Keyboard() {
}
public Keyboard(String brand) {
this.brand = brand;
}
/**
* 接入USB设备
*/
@Override
public void connect() {
System.out.println(brand + "成功连接电脑!");
}
/**
* 断开USB设备
*/
@Override
public void disconnect() {
System.out.println(brand + "成功断开电脑!");
}
/**
* 敲键盘
*/
public void pressKeyboard() {
System.out.println("敲键盘!");
}
}
Mouse.java
/**
* 鼠标类
*/
public class Mouse implements USB{
// 品牌
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Mouse() {
}
public Mouse(String brand) {
this.brand = brand;
}
/**
* 接入USB设备
*/
@Override
public void connect() {
System.out.println(brand + "成功连接电脑!");
}
/**
* 断开USB设备
*/
@Override
public void disconnect() {
System.out.println(brand + "成功断开电脑!");
}
/**
* 点击鼠标
*/
public void clickMouse() {
System.out.println("点击鼠标!");
}
}
Computer.java
public class Computer {
// 品牌
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Computer() {
}
public Computer(String brand) {
this.brand = brand;
}
/**
* 电脑开机
*/
public void start() {
System.out.println(brand + "电脑开机!");
}
/**
* 安装USB设备
*/
public void mountUSBDevice(USB usb) {
// 接入USB设备
usb.connect();
if (usb instanceof Keyboard) {
// 强制转换成键盘类
Keyboard keyboard = (Keyboard) usb;
// 敲键盘
keyboard.pressKeyboard();
} else if (usb instanceof Mouse) {
// 强制转换成鼠标类
Mouse mouse = (Mouse) usb;
// 点击鼠标
mouse.clickMouse();
}
// 断开USB设备
usb.disconnect();
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 创建电脑对象
Computer computer = new Computer("苹果");
// 开机
computer.start();
// 创建键盘对象
USB keyboard = new Keyboard("罗技");
// 创建鼠标对象
USB mouse = new Mouse("雷蛇");
// 安装键盘
computer.mountUSBDevice(keyboard);
// 安装鼠标
computer.mountUSBDevice(mouse);
}
}
2.内部类
a.内部类概述
内部类:
-
内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。
public class People { // 内部类 public class Heart { } }
内部类的使用场景、作用:
- 当一个事务的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事务提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
- 内部类通常可以方便访问外部类的成员,包括私有的成员。
- 内部类提供了更好的封装性,内部类本身就可以用private protected等修饰,封装性可以做更多控制。
内部类的分类:
- 静态内部类【了解】
- 成员内部类/非静态内部类【了解】
- 局部内部类【了解】
- 匿名内部类【重点】
b.内部类之一:静态内部类
什么是静态内部类?
- 有static修饰,属于外部类本身。
- 它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer {
// 静态成员内部类
public static class Inner {
}
}
静态内部类创建对象的格式:
格式:
外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
例:
Outer.Inner inner = new Outer.Inner();
Outer.java
/**
* 外部类
*/
public class Outer {
/**
* 静态成员内部类
*/
public static class Inner {
private String name;
private int age;
public static String schoolName;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getSchoolName() {
return schoolName;
}
public static void setSchoolName(String schoolName) {
Inner.schoolName = schoolName;
}
public Inner() {
}
public Inner(String name, int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name);
}
}
}
Test.java
public class Test {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.setName("花千骨");
inner.show();
}
}
静态内部类的访问拓展:
- 静态内部类中是否可以直接访问外部类的静态成员?
- 可以,外部类的静态成员只有一份,可以被共享访问。
- 静态内部类中是否可以直接访问外部类的实例成员?
- 不可以,外部类的实例成员必须用外部类对象访问。
总结:
- 静态内部类的使用场景、特点、访问总结。
- 如果一个类中包含了一个完整的成分,如汽车类中的发动机类。
- 特点、使用与普通类是一样的,类有的成分它都有,,只是位置在别人里面而已。
- 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。
- 注意:开发中实际上用的还是比较少。
c.内部类之二:成员内部类
什么是成员内部类?
- 无static修饰,属于外部类的对象。
- JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了。
public class Outer {
// 成员内部类
public class Inner {
}
}
成员内部类创建对象的格式:
格式:
外部类.内部类 对象名 = new 外部类构造器.new 内部类构造器();
例:
Outer.Inner in = new Outer().new Inner();
Outer.java
/**
* 外部类
*/
public class Outer {
/**
* 成员内部类:不能加static修饰
*/
public class Inner {
private String name;
private int age;
// JDK16开始支持静态成员
public static int demo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static int getDemo() {
return demo;
}
public static void setDemo(int demo) {
Inner.demo = demo;
}
public Inner() {
}
public Inner(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 静态方法
* 从JDK16开始支持
*/
public static void test() {
}
/**
* 普通实例方法
*/
public void show() {
System.out.println("姓名:" + name + "\t年龄:" + age);
}
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 创建内部类对象
Outer.Inner inner = new Outer().new Inner();
inner.setName("花千骨");
inner.setAge(20);
// 内部类实例方法
inner.show();
// 内部类静态方法
Outer.Inner.test();
}
}
成员内部类的访问拓展:
- 成员内部类中是否可以直接访问外部类的静态成员?
- 可以,外部类的静态成员只有一份,可以被共享访问。
- 成员内部类的实例方法中是否可以直接访问外部类的实例成员?
- 可以,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员。
总结:
- 成员内部类是什么样的、有什么特点?
- 无static修饰,属于外部类的对象。
- 可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员。
- 成员内部类如何创建对象?
- 外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();
案例:成员内部类
-
请观察如下代码,写出合适的代码对应其注释要求输出的结果。
class People { private int heartbeat = 150; public class Heart { private int heartbeat = 110; public void show() { int heartbeat = 78; // 78 System.out.println(?); // 110 System.out.println(?); // 150 System.out.println(?); } } }
注意:在成员内部类中访问所在外部类对象,格式:外部类名.this。
Test.java
public class Test2 {
public static void main(String[] args) {
People.Heart heart = new People().new Heart();
heart.show();
}
}
class People {
private int heartbeat = 150;
/**
* 成员内部类
*/
public class Heart {
private int heartbeat = 110;
public void show() {
// 局部变量
int heartbeat = 78;
// 78 局部变量
System.out.println(heartbeat);
// 110 成员内部类成员变量
System.out.println(this.heartbeat);
// 150
System.out.println(People.this.heartbeat);
}
}
}
d.内部类之三:局部内部类
局部内部类(鸡肋语法,了解即可)
- 局部内部类放在方法、代码块、构造器等执行体中。
- 局部内部类的类文件名为:外部类$N内部类.class。
Test.java
public class Test {
public static void main(String[] args) {
/**
* 局部内部类
*/
class Cat {
private String name;
public static int onlineNumber = 100;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat cat = new Cat();
cat.setName("小花");
System.out.println(cat.getName());
}
}
e.内部类之四:匿名内部类概述★
匿名内部类:
- 本质上是一个没有名字的局部内部类,定义在方法中、代码块中等。
- 作用:方便创建子类对象,最终目的为了简化代码编写。
匿名内部类格式:
new 类|抽象类名|接口名() {
重写方法;
};
Employee a = new Employee() {
public void work() {
}
};
a.work();
Test.java
public class Test {
public static void main(String[] args) {
// Animal tiger = new Tiger();
// tiger.run();
// 匿名内部类 不需要再定义子类去实现抽象方法
Animal tiger = new Animal() {
@Override
public void run() {
System.out.println("老虎跑得快!");
}
};
tiger.run();
}
}
abstract class Animal {
public abstract void run();
}
//class Tiger extends Animal {
//
// @Override
// public void run() {
// System.out.println("老虎跑得快!");
// }
//}
特点总结:
- 匿名内部类是一个没有名字的内部类。
- 匿名内部类写出来就会产生一个匿名内部类的对象。
- 匿名内部类的对象类型相当于是当前new的那个类型的子类类型。
总结:
-
匿名内部类的作用?
- 方便创建子类对象,最终目的是为了简化代码编写。
-
匿名内部类的格式?
Employee a = new Employee() { public void work() { } }; a.work();
-
匿名内部类的特点?
- 匿名内部类是一个没有名字的内部类。
- 匿名内部类写出来就会产生一个匿名内部类的对象。
- 匿名内部类的对象类型相当于是当前new的那个类型的子类类型。
f.匿名内部类常见使用形式
匿名内部类在开发中的使用形式了解:
-
某个学校需要让老师、学生、运动员一起参加游泳比赛
/** * 游泳接口 */ public interface Swimming { void swim(); }
/** * 测试类 */ public class JumppingDemo { public static void main(String[] args) { // 需求:goSwimming方法 } // 定义一个方法让所有角色进来一起比赛 public static void goSwimming(Swimming swimming) { swimming.swim(); } }
Test.java
/**
* 目标:掌握匿名内部类的使用形式(语法)
*/
public class Test {
public static void main(String[] args) {
// Swimming student = new Student();
// go(student);
// 学生参加游泳比赛
Swimming student = new Swimming() {
@Override
public void swim() {
System.out.println("学生自由泳!");
}
};
go(student);
// 老师参加游泳比赛
Swimming teacher = new Swimming() {
@Override
public void swim() {
System.out.println("老师蛙泳!");
}
};
go(teacher);
// 运动员参加游泳比赛
// 匿名内部类作为实参
go(new Swimming() {
@Override
public void swim() {
System.out.println("运动员蝶泳!");
}
});
}
/**
* 学生 老师 运动员可以一起参加游泳比赛
*/
public static void go(Swimming swimming) {
System.out.println("开始游泳!");
swimming.swim();
System.out.println("结束游泳!");
}
}
/**
* 游泳接口
*/
interface Swimming {
void swim();
}
//class Student implements Swimming {
//
// @Override
// public void swim() {
// System.out.println("学生自由泳!");
// }
//}
使用总结:匿名内部类可以作为方法的实际参数进行传输。
g.匿名内部类真实使用场景演示
匿名内部类真实使用场景:
- 给按钮绑定点击事件。
Test.java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* 目标:通过GUI变成 理解匿名内部类的真实使用场景
*/
public class Test {
public static void main(String[] args) {
// 1.创建窗口
JFrame jFrame = new JFrame("登录界面");
JPanel jPanel = new JPanel();
jFrame.add(jPanel);
// 2.创建一个按钮对象
JButton jButton = new JButton("登录");
// 匿名内部类的使用 绑定点击事件
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
JOptionPane.showMessageDialog(jFrame, "登录成功!");
}
});
// 匿名内部类简化
jButton.addActionListener(e -> JOptionPane.showMessageDialog(jFrame, "登录成功!"));
// 3.把按钮对象添加到窗口上展示
jFrame.add(jButton);
// 4.展示窗口
// 设置窗口大小
jFrame.setSize(400, 300);
// 设置居中显示
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
}
使用总结:
- 开发中不是我们主动去定义匿名内部类的,而不是别人需要我们写或者我们可以写的时候才会使用。匿名内部类的的代码可以实现代码进一步的简化。
3.常用API
什么是API?
- API(Application Programming Interface) 应用程序编程接口。
- 简单来说:就是Java帮我们写好的一些方法,我们可以直接调用。
a.Object
Object类的作用:
- 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类。
- Object类的方法是一切子类都可以直接使用的,所以我们要学习Object类的方法。
Object类的常用方法:
方法名 | 说明 |
---|---|
public String toString() | 默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址 。 |
public Boolean equals(Object o) | 默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false。 |
Ⅰ.toString()方法
方法名 | 说明 |
---|---|
public String toString() | 默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址 。 |
Student.java
public class Student {
private String name;
private char sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
public Student(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
}
Test.java
/**
* 目标:掌握Object类中toString方法的使用。
*/
public class Test {
public static void main(String[] args) {
Student student = new Student("白子画", '男', 1000);
String s = student.toString();
System.out.println(s);
System.out.println(student.toString());
// 直接输出对象变量 默认可以省略toString调用不写
System.out.println(student);
}
}
问题引出:
- 开发中直接输出对象,默认输出对象的地址其实是无意义的。
- 开发中输出对象变量,更多的时候是希望看到对象的内容数据而不是对象的地址信息。
toString存在的意义:
-
父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息。
package com.javase.apiobject; public class Student { private String name; private char sex; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Student() { } public Student(String name, char sex, int age) { this.name = name; this.sex = sex; this.age = age; } /** * 重写父类String * * @return 对象信息 */ @Override public String toString() { return "Student {name = " + name + ", sex=" + sex + ", age = " + age + "}"; } }
IDEA中重写toString()快捷方式:
- 鼠标右键 ->
Generate
->toString()
。 - 输入
toS
->public String toString() (generate via wizard)
->OK
。
- 鼠标右键 ->
总结:
- Object的toString方法的作用是什么》
- 默认是打印当前对象的地址。
- 让子类重写,以便返回子类对象的内容。
Ⅱ.equals()方法
方法名 | 说明 |
---|---|
public Boolean equals(Object o) | 默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false。 |
问题思考:
- 直接比较两个对象的地址是否相同完全可以用
==
替代equals。
equals存在的意义:
- 父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
package com.javase.apiobject;
import java.util.Objects;
import java.util.stream.Stream;
public class Student {
private String name;
private char sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
public Student(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
/**
* 重写父类String
*
* @return 对象信息
*/
@Override
public String toString() {
return "Student {name = " + name + ", sex=" + sex + ", age = " + age + "}";
}
/**
* 重写equals 定制相等规则
* 两个对象的内容一样就认为是相等的
*
* @param o 被比较者
* @return
*/
@Override
public boolean equals(Object o) {
// 判断o是不是学生类型
if (o instanceof Student) {
// o强制转化为学生类型
Student student2 = (Student) o;
// 判断2个对象的内容是否一样
// if (this.name.equals(student2.name) && this.sex == student2.sex && this.age == student2.age) {
// return true;
// } else {
// return false;
// }
return this.name.equals(student2.name) && this.sex == student2.sex && this.age == student2.age;
} else {
// 学生只能和学生比较 否则结果一定是false
return false;
}
}
}
IDEA中重写equal()快捷方式:
- 鼠标右键 ->
Generate
->equals() and hashCode()
。 - 输入
eq
->public boolean equals(Object obj) (generate via wizard)
->OK
。
总结:
- Object的equals方法的作用是什么?
- 默认是与另一个对象比较地址是否一样。
- 让子类重写,以便比较两个子类对象的内容是否相同
b.Objects
Objects概述:
- Objects类与Object还是继承关系,Objects类是从JDK1.7开始之后才有的。
官方在进行字符串比较时,没有对象自己的equals方法,而是选择了Objects的equals方法来比较两个对象。
@Override
public boolean equals(Object o) {
// 1.判断是否是同一个对象 如果是返回true
if (this == o) return true;
// 2.如果o是null或o不是学生类型返回false
if (o == null || getClass() != o.getClass()) return false;
// 3.说明o一定是学生类型而且不为null
Student student = (Student) o;
// Objects.equals进行了非空校验更加安全
return sex == student.sex && age == student.age && Objects.equals(name, student.name);
}
Objects的equals方法比较的结果是一样的,但是更安全。
Objects的常见方法:
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 比较两个对象,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较。 |
public static boolean isNull(Object obj) | 判断变量是否为null,为null返回true,反之返回false。 |
Ⅰ.equals()方法
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 比较两个对象,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较。 |
// Objects.equals(Object a, Object b) 源码
public static boolean equals(Object a, Object b) {
return a == b || a != null && a.equals(b);
}
Test.java
import java.util.Objects;
/**
* 目标:掌握Objects类的常用方法 重点:equals
*/
public class Test {
public static void main(String[] args) {
String string1 = null;
String string2 = new String("花千骨");
// 留下来隐患 可能出现空指针异常
// Cannot invoke "String.equals(Object)" because "string1" is null
//System.out.println(string1.equals(string2));
// 更安全 结果也是对的
// false
System.out.println(Objects.equals(string1, string2));
// ture
System.out.println(Objects.equals(null, null));
}
// Objects.equals(Object a, Object b) 源码
// public static boolean equals(Object a, Object b) {
// return a == b || a != null && a.equals(b);
// }
}
Ⅱ.isNull()方法
方法名 | 说明 |
---|---|
public static boolean isNull(Object obj) | 判断变量是否为null,为null返回true,反之返回false。 |
// 源码
public static boolean isNull(Object obj) {
return obj == null;
}
Test.java
import java.util.Objects;
/**
* 目标:掌握Objects类的常用方法 重点:equals
*/
public class Test {
public static void main(String[] args) {
String string1 = null;
String string2 = new String("花千骨");
// true
System.out.println(Objects.isNull(string1));
// false
System.out.println(Objects.isNull(string2));
}
}
总结:
- 对象进行内容比较的时候建议使用什么?为什么?
- 建议使用Objects提供的equals方法。
- 比较结果是一样的,但是更安全。
c.StringBuilder
StringBuilder概述:
- StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。
- 作用:提高字符串的操作效率,如拼接、修改等。
StringBuilder构造器:
名称 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容。 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象。 |
StringBuilder常用方法:
方法名称 | 说明 |
---|---|
public StringBuilder append(任意类型) | 添加数据并返回StringBuilder对象本身。 |
public StringBuilder reverse() | 将对象的内容反转。 |
public int length() | 返回对象内容长度。 |
public String toString() | 通过toString()就可以实现把StringBuilder转化为String。 |
Test.java
package com.javase.apistringbuilder;
/**
* 目标:学会使用StringBuilder操作字符串 最终还需要知道它性能好的原因
*/
public class Test {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append(1);
stringBuilder.append(1.1);
stringBuilder.append(true);
stringBuilder.append("abc");
System.out.println(stringBuilder);
StringBuilder stringBuilder1 = new StringBuilder();
// 支持链式编程
stringBuilder1.append("a").append("b").append("c");
System.out.println(stringBuilder1);
// 反转再拼接
stringBuilder1.reverse().append("111");
System.out.println(stringBuilder1);
// 注意:StringBuild只是拼接字符串的手段:效率好。
// 最终的结果还是要恢复成String类型
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append("123").append("456");
// 恢复成String
String rs = stringBuilder2.toString();
System.out.println(rs);
int a = 1;
System.out.println();
}
}
Ⅰ.String类拼接字符串原理图
String类拼接字符串原理图
Ⅱ.StringBuilder提高效率原理图
总结:
- 为什么拼接、反转字符串建议使用StringBuilder?
- String:内容是不可变的、拼接字符串性能差。
- StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
- 定义字符串使用String。
- 拼接、修改等操作字符串使用StringBuilder。
Ⅲ.案例:打印整型数组内容
需求:
- 设计一个方法用于输出任意整型数组的内容,要求输出成如下格式:“该数组内容为:[11, 22 ,33 ,44, 55]”
分析:
- 定义一个方法,要求该方法能够接收数组,并输出数组内容。(需要参数吗?需要返回值类型申明吗?)
- 定义一个静态初始化的数组,调用该方法,并传入该数组。
Test.java
public class Test {
public static void main(String[] args) {
int[] array1 = null;
System.out.println(toString(array1));
int[] array2 = {11, 22, 33, 44, 55};
System.out.println(toString(array2));
int[] array3 = {};
System.out.println(toString(array3));
}
public static String toString(int[] array) {
if (array != null) {
StringBuilder stringBuilder = new StringBuilder("[");
for (int i = 0; i < array.length; i++) {
stringBuilder.append(array[i]).append(i == array.length - 1 ? "" : ", ");
}
stringBuilder.append("]");
return stringBuilder.toString();
} else {
return null;
}
}
}
d.Math
Math类:
- 包含执行基本数字运算的方法,Math类没有提供公开的构造器。
- 如何使用类中的成员呢?看类的成员是否都是静态的,如果是,通过类名就可以直接调用。
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) |
Test.java
public class Test {
public static void main(String[] args) {
// 获取参数绝对值 10 10
System.out.println(Math.abs(10));
System.out.println(Math.abs(-10));
// 向上取整 5.0
System.out.println(Math.ceil(4.0000001));
// 向下取整 4.0
System.out.println(Math.floor(4.999999));
// 四舍五入 4 4
System.out.println(Math.round(4.499999));
System.out.println(Math.round(4.5000001));
// 获取两个int值中的较大值 100
System.out.println(Math.max(10, 100));
// 返回a的b次幂的值 8
System.out.println(Math.pow(2, 3));
// 返回值为double的随机值,范围[0.0, 1.0)
System.out.println(Math.random());
}
}
e.System
System类概述:
- System的功能是通用的,都是直接用类名调用即可,所以System不能被实例化。
System类的常用方法:
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的Java虚拟机,非零表示异常终止。 |
public static long currentTimeMillis(), | 返回当前系统的时间毫秒值形式。 |
public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组拷贝。 |
时间毫秒值:
- 计算机认为时间是有起点的,起始时间:1970年1月1日 00:00:00
- 时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总的毫秒数,应该是很大的。1s = 1000ms。
原因:
- 1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开的一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编程语言在老旧的PDP-7机器上开发出了Unix的一个版本。随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。
- 1970年1月1日 算C语言的生日。
Test.java
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
System.out.println("程序开始...");
// JVM终止
// System.exit(0);
// 计算机认为时间有起源:返回1978-01-01 00:00:00 走到此刻的总的时间毫秒值
long time = System.currentTimeMillis();
System.out.println(time);
// 进行时间的计算 进行性能分析
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
System.out.println("输出:" + i);
}
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - startTime) / 10000.0 + "s");
// 数组拷贝 arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数)
int[] array1 = {10, 20, 30, 40, 50, 60};
// [0, 0, 0, 0, 0, 0] 变为 [0, 0, 40, 50, 60, 0]
int[] array2 = new int[6];
System.arraycopy(array1, 3, array2, 3, 2);
// 把数组以字符串形式输出
System.out.println(Arrays.toString(array2));
System.out.println("程序结束!");
}
}
f.BigDecimal
BigDecimal作用:
- 用于解决浮点型运算精度失真的问题
public class Test {
public static void main(String[] args) {
System.out.println(0.09 + 0.01); // 0.09999999999999999
System.out.println(1.0 - 0.32); // 0.6799999999999999
System.out.println(1.015 * 100); // 101.49999999999999
System.out.println(1.301 / 100); // 0.013009999999999999
double c = 0.1 + 0.2; // 0.30000000000000004
System.out.println(c);
}
}
使用步骤:
-
创建对象BigDecimal封装浮点型数据(最好的方法是调用方法)
// 包装浮点数成BigDecimal对象 public static BigDecimal valueOf(double val) { }
方法名 | 说明 |
---|---|
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(double)的方式把double值转化为BigDecimal对象。
-
说明:BigDecimal(double)存在精度损失风险,在精度计算或值比较的场景中可能会导致业务逻辑异常。
// 例如:实际的存储值为0.100000001490116119384765625 BigDecimal g = new BigDecimal(0.1F);
-
正例:优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度堆尾数进行了截断。
// 正确方法 BigDecimal recommend1 = new BigDecimal("0.1"); BigDecimal recommend2 = BigDecimal.valueOf(0.1);
Test.java
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Test {
public static void main(String[] args) {
// 浮点型运算的时候 可能会出现数据失真
System.out.println("--------------------");
System.out.println(0.09 + 0.01); // 0.09999999999999999
System.out.println(1.0 - 0.32); // 0.6799999999999999
System.out.println(1.015 * 100); // 101.49999999999999
System.out.println(1.301 / 100); // 0.013009999999999999
double sum = 0.1 + 0.2; // 0.30000000000000004
System.out.println(sum);
System.out.println("--------------------");
// 包装浮点型数据成为大数据对象BigDecimal
System.out.println("--------------------");
double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c);
System.out.println("--------------------");
System.out.println("--------------------");
// 加法
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
BigDecimal cAdd = a1.add(b1);
System.out.println(cAdd);
// 减法
BigDecimal cSub = a1.subtract(b1);
System.out.println(cSub);
// 乘法
BigDecimal cMul = a1.multiply(b1);
System.out.println(cMul);
// 除法
BigDecimal cDiv = a1.divide(b1);
System.out.println(cDiv);
// 运用BigDecimal计算是手段 目的是结果
double rs = cDiv.doubleValue();
System.out.println(rs);
// 注意事项:BigDecimal是一定要精度运算
BigDecimal a2 = BigDecimal.valueOf(10.0);
BigDecimal b2 = BigDecimal.valueOf(3.0);
BigDecimal c2 = a2.divide(b2, 2, RoundingMode.HALF_UP);
System.out.println(c2);
System.out.println("--------------------");
}
}
总结:
- BigDecimal的作用是什么?
- 解决浮点型运算精度失真问题。
- BigDecimal的对象如何获取?
- BigDecimal b1 = BigDecimal.valueOf(0.1);
// 正确方法
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
Test.java
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Test {
public static void main(String[] args) {
// 浮点型运算的时候 可能会出现数据失真
System.out.println("--------------------");
System.out.println(0.09 + 0.01); // 0.09999999999999999
System.out.println(1.0 - 0.32); // 0.6799999999999999
System.out.println(1.015 * 100); // 101.49999999999999
System.out.println(1.301 / 100); // 0.013009999999999999
double sum = 0.1 + 0.2; // 0.30000000000000004
System.out.println(sum);
System.out.println("--------------------");
// 包装浮点型数据成为大数据对象BigDecimal
System.out.println("--------------------");
double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c);
System.out.println("--------------------");
System.out.println("--------------------");
// 加法
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
BigDecimal cAdd = a1.add(b1);
System.out.println(cAdd);
// 减法
BigDecimal cSub = a1.subtract(b1);
System.out.println(cSub);
// 乘法
BigDecimal cMul = a1.multiply(b1);
System.out.println(cMul);
// 除法
BigDecimal cDiv = a1.divide(b1);
System.out.println(cDiv);
// 运用BigDecimal计算是手段 目的是结果
double rs = cDiv.doubleValue();
System.out.println(rs);
// 注意事项:BigDecimal是一定要精度运算
BigDecimal a2 = BigDecimal.valueOf(10.0);
BigDecimal b2 = BigDecimal.valueOf(3.0);
BigDecimal c2 = a2.divide(b2, 2, RoundingMode.HALF_UP);
System.out.println(c2);
System.out.println("--------------------");
}
}
总结:
- BigDecimal的作用是什么?
- 解决浮点型运算精度失真问题。
- BigDecimal的对象如何获取?
- BigDecimal b1 = BigDecimal.valueOf(0.1);