一、Math类
1.定义
① Math类是定义在java.lang包(java的核心包)下的, 所以不需要导包。
② Math类定义时采用final关键词修饰,表示该类是最终类,不能被继承。
③ Math是一个帮助我们进行数学计算的工具类(私有化构造方法,所有方法都是静态的)。
2.属性
3.常用方法
方法全部用static修饰,是类方法,无需创建对象,直接使用 类名.方法名 调用方法
(1)abs方法
public static void main(String[] args) {
//abs 获取绝对值
System.out.println(Math.abs(88));//88
System.out.println(Math.abs(-88));//88
System.out.println(Math.abs(-2147483648));//-2147483648
System.out.println(Math.absExact(-2147483648));//异常
}
bug:
以int类型为例,取值范围为-2147483648 ~ 2147483647
如果没有正数与负数对应,那么传递负数结果有误
-2147483648没有正数与之对应,所以abs结果就会产生bug
解决办法:
采用Math中新的方法absExact(jdk15开始)
(2)ceil、floor和round方法
//ceil 向上取整(进一法,朝数轴的正方向进一)
System.out.println(Math.ceil(12.34));//13.0
System.out.println(Math.ceil(-12.34));//-12.0
//floor 向下取整(去尾法,朝数轴的负方向进一)
System.out.println(Math.floor(12.34));//12.0
System.out.println(Math.floor(-12.34));//-13.0
//round 四舍五入
System.out.println(Math.round(12.34));//12
System.out.println(Math.round(-12.34));//-12
(3)max和min方法
//max 获取两个整数的较大值
System.out.println(Math.max(20, 30));//30
//min 获取两个整数的较小值
System.out.println(Math.min(20, 30));//20
(4)pow、sqrt和cbrt方法
//pow 获取a的b次幂
System.out.println(Math.pow(2, 3));//8
/* 细节:
如果第二个参数是0~1之间的小数,操作就是开根号
建议:第二个参数一般传递>=1的正整数
*/
System.out.println(Math.pow(4, 0.5));//2.0
System.out.println(Math.pow(2, -2));//0.25
//sqrt 开根号
System.out.println(Math.sqrt(4));//2.0
//cbrt 开立方
System.out.println(Math.cbrt(8));//2.0
(5)random方法
//random 获取随机数(不常用,前端采用该方式)
/* 获取1~100之间的随机数:
Math.random() [0.0,1.0)
* 100 [0.0,100.0)
Math.floor [0,99]
+1 [1,100]
*/
System.out.println(Math.floor(Math.random() * 100) + 1);
二、System类
1.定义
System也是一个工具类,提供了一些与系统相关的方法
2.常用方法
(1)exit方法
//exit(状态码) 终止当前运行的java虚拟机
/* 状态码:
0:表示当前虚拟机是正常停止
非0(一般用1):表示当前虚拟机是正常停止
*/
System.exit(0);
(2)currentTimeMillis方法
时间原点:1970年1月1日00:00:00 --> C语言诞生时间
我国在东八区,有八小时时差,所以os中时间原点为1970年1月1日08:00:00
//currentTimeMillis 返回当前系统距离时间原点的时间毫秒值形式
System.out.println(System.currentTimeMillis());
用途:可以获取程序运行的总时间
(3)arraycopy方法
//arraycopy 数组拷贝
//参数:arraycopy(源数组,源数组起始索引,目标数组,目标数组起始索引,拷贝个数)
int[] arr1={1,2,3,4,5,6,7,8,9,10};
int[] arr2=new int[10];
System.arraycopy(arr1,6,arr2,2,3);//0 0 7 8 9 0 0 0 0 0
注意事项:
①如果源数组和目标数组都是基本数据类型,那么两者的类型必须保持一致,否则报错
public class Demo {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
double[] arr2 = new double[10];
System.arraycopy(arr1, 0, arr2, 0, 10);
}
}
运行结果:
② 在拷贝的时候需要考虑数组的长度,超出范围也会报错
public class Demo2 {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] arr2 = new int[5];
System.arraycopy(arr1, 0, arr2, 0, 10);
}
}
运行结果:
③ 如果源数组和目标数组都是引用数据类型,则子类类型可以复制给父类类型
public class Demo {
public static void main(String[] args) {
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
Student[] arr1 = {s1, s2, s3};
Person[] arr2 = new Student[3];
//把arr1中对象的地址值赋值给arr2中
System.arraycopy(arr1, 0, arr2, 0, 3);
//遍历数组arr2(需要强制转换)
for (int i = 0; i < arr2.length; i++) {
Student stu = (Student) arr2[i];
System.out.println(stu.getName() + "," + stu.getAge());
}
}
}
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person {
public Student() {
}
public Student(String name, int age) {
super(name, age);
}
}
三、Runtime类
1.定义
Runtime表示当前虚拟机的运行环境
2.常用方法
注意:方法并没有全被staitc修饰,因此Runtime并不是一个工具类。需要获取其对象,再调用其成员方法 。
(1)getRuntime方法
由于Runtime表示当前虚拟机的运行环境,所以只能有一个对象
通过源码可以看出,Runtime类私有化其构造方法,使得外界不能直接new其对象。
采用单例设计模式--饿汉式单例,确保外界只能有一个Runtime对象。
public class Demo {
public static void main(String[] args) {
//getRuntime 获取Runtime对象
Runtime r1=Runtime.getRuntime();
Runtime r2=Runtime.getRuntime();
System.out.println(r1==r2);//true
}
}
(2)exit方法
//exit 停止虚拟机
Runtime.getRuntime().exit(0);
question:Runtime的exit方法和System的exit方法有何不同?
通过查看System类中的exit方法,可以发现,底层调用的其实就是Runtime的exit方法,所以两者本质上是一样的。
(3)availableProcessors方法
//availableProcessors 获得CPU的线程数
System.out.println(Runtime.getRuntime().availableProcessors());//16
(4)maxMemory、totalMemory和freeMemory方法
//maxMemory JVM能从系统中获取的总内存大小(单位byte --> /1024 :kb --> /1024 :Mb)
System.out.println(Runtime.getRuntime().maxMemory() / 1024 / 1024);
//totalMemory JVM已经从系统中获取的总内存大小(单位byte)
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
//freeMemory JVM剩余内存大小(单位byte)
System.out.println(Runtime.getRuntime().freeMemory() / 1024 / 1024);
(5)exec方法
shutdown : 关机(加上参数才能执行)
-s : 默认在1分钟之后关机
-s -t 指定秒数 : 指定关机时间
-a : 取消关机操作
-r : 关机并重启
//exec 运行cmd命令
Runtime.getRuntime().exec("shutdown -s -t 3600");//在3600s后关机
运行后桌面右下角会弹出:
Runtime.getRuntime().exec("shutdown -a");//取消关机
运行后桌面右下角会弹出:
四、Object类
1.定义
Object类是java中的顶级父类,所有的类都直接或间接的继承于Object类。
2.方法
Object类中的方法可以被所有子类访问。
(1)Object类的构造方法
在Object类中,没有成员变量,所以就没有带参的构造方法,只有一个空参构造。
理解:
子类中的共性才会抽取到父类中,在java中,不可能有一个属性是所有类的共性,所以Object类中不可能有成员变量
question:针对于任意一个类的构造方法,在第一行都隐藏了一个 super(); 默认访问父类的无参构造,那为什么不能访问父类的带参构造呢?
原因:因为在顶级父类Object类中,只有无参构造方法。
(2)Object类的成员方法
一共有11个,先研究其中3个最常用的方法
① tostring方法
public class Demo {
public static void main(String[] args) {
//toString 返回对象的字符表现形式(包名.类名 + @ + 地址值)
Object obj=new Object();
String str1=obj.toString();
System.out.println(str1);//java.lang.Object@4eec7777
Student stu=new Student();
String str2=stu.toString();
System.out.println(str2);//com.mihoyo.objectdemo.Student@41629346
}
}
源码:
question1:为什么不用toString方法直接打印对象和调用toString方法的结果一样?
public class Demo {
public static void main(String[] args) {
Student stu=new Student();
String str2=stu.toString();
System.out.println(str2);//com.mihoyo.objectdemo.Student@41629346
System.out.println(stu);com.mihoyo.objectdemo.Student@41629346
}
}
System是一个类,out是该类中的一个静态变量,所以可以用 类名.变量名 调用该静态变量
out初始值为null,但后续会给它赋值。
简单理解为 System.out 可以获取打印的对象,再调用该对象的println方法,参数就是打印的内容。
继续查看println方法的源码,可以发现,底层也同样调用了object类中的toString方法,将对象变成字符串。
question2:由上可知,在默认情况下,因为Object类中的toString方法返回的是地址值,所以打印一个对象,输出的就是地址值。但输出地址值并不是我们想要的,我们想要的应该是输出对象内部的属性值,如何实现?
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + ", age=" + age + '}';
}
}
处理方案:重写父类Object类中的toString方法(见上)
结论:如果想要打印一个对象,想要看到其属性值,就重写toString方法。重写的方法中,把对象的属性值进行拼接。
② equals方法
public class Demo {
public static void main(String[] args) {
//equals 比较两个对象是否相等
Student s1=new Student();
Student s2=new Student();
boolean result= s1.equals(s2);
System.out.println(result);//false
}
}
question:为什么是false呢?
首先,因为是对象s1调用的equals方法,应该先去Student类中查找equals方法。
但Student类中我们并没有定义,就去其父类Object中找。
通过源码可以发现,Object类中的equals方法底层是用==来进行比较。
而==比较的是地址值,因为两个对象都是new出来的,在堆中的地址必然不一样,故答案为false。
question: 由上可知,在默认情况下,因为Object类中的equals方法底层使用==,所以比较两个对象,比较的就是地址值。但比较地址值并不是我们想要的,我们想要的应该是比较对象内部的属性值,如何实现?
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + ", age=" + age + '}';
}
//重写之后的equals方法比较的就是对象内部的属性值了
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
处理方案:重写父类Object类中的equals方法(见上)
结论:如果没有重回equals方法,默认使用Object类中的equals方法进行比较,比较的是对象的地址值。然而比较地址值意义并不大,使用需要重写,重写之后比较的就是对象内部的属性值了。
**************************************************************************************************************
Test:判断以下代码的结果
public class Test {
public static void main(String[] args) {
String s="abc";
StringBuilder sb=new StringBuilder("abc");
System.out.println(s.equals(sb));
System.out.println(sb.equals(s));
}
}
运行结果:
分析:
s.equals(sb) :因为equals方法是对象s调用的,而s是String类,所以查看String类中的equals方法
由于sb是StringBuilder类型,不是String类型,所以return结果直接为false
sb.equals(s) :因为equals方法是对象sb调用的,而s是StringBuilder类,所以查看StringBuilder类中的equals方法
通过查找发现, StringBuilder类中并没有重写equals方法。
那我们查看其父类AbstractStringBuilder中的equals方法。
通过查找发现,其直接父类AbstractStringBuilder中也没有重写equals方法。
而AbstractStringBuilder类默认继承于Object类,所以调用的事实上是Object类的equals方法,默认使用==比较两个对象的地址值。
由于对象s和对象sb的地址值不一样,所以结果为false。
③ clone方法
对象克隆: 把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制
通过查看源码可以发现,clone方法是被native修饰的,属于本地方法, 外界是不能够直接调用的。
简单地讲,一个Native Method就是一个java调用非java代码的接口。
在定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。该方法被调用时,是告知jvm去调用非java代码编写的实现体。
jvm能否去调用这个实现体,是根据是否实现了Cloneable这个接口来进行判断。
虽然这个接口并没有什么方法,但是必须实现该标志接口。如果不实现将会在运行期间抛出:CloneNotSupportedException异常。
通过查看文档,可以发现Object类本身也没有实现Cloneable接口,所以也不能在其对象上直接第调用,否则抛出异常。
question:那clone方法究竟怎么才能使用呢?
使用步骤:
Ⅰ.在需要调用clone方法的对象的类上,添加实现Cloneable接口
Ⅱ.重写clone方法,在自己的clone方法中调用父类的clone方法,将返回值类型强转成本类类型,将当前clone方法修饰符改成public
完整代码:
public class Student implements Cloneable{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + ", age=" + age + '}';
}
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
Ⅲ.在测试类中调用对象的clone方法(默认是浅克隆)
运行结果:
**************************************************************************************************************
浅克隆与深克隆
User类:
import java.util.Arrays;
public class User implements Cloneable{
private int id;
private String username;
private String password;
private String path;//游戏图片
private int[] data;//游戏进度
public User() {
}
public User(int id, String username, String password, String path, int[] data) {
this.id = id;
this.username = username;
this.password = password;
this.path = path;
this.data = data;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
@Override
public String toString() {
return "User{" + "id=" + id +
", username='" + username +
", password='" + password +
", path='" + path +
", data=" + Arrays.toString(data) +
'}';
}
@Override
public User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
Ⅰ. 浅克隆:不管对象内部的属性是基本数据类型 还是引用数据类型,都完全拷贝过来(Object类默认是浅克隆)
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
//1.创建一个对象
int[] data = {1, 2, 3, 4, 5};
User u1 = new User(1, "zhangsan", "1234qwer", "animal1", data);
//2.克隆对象,修改源对象的属性,查看克隆的对象是否发生变化
User u2 = u1.clone();
int[] arr = u1.getData();
arr[0] = 100;
//3.输出对象
System.out.println(u1);
System.out.println(u2);
}
}
运行结果:
可以看出,由于u2是浅克隆产生的,和u1的data指向的地址一样,所以当u1的data发生变化,u2也会同步改变
Ⅱ. 深克隆:基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的
虽然字符串也是引用数据类型,但是在对象的字符串类型属性的赋值中,不是通过new创建的,而是直接赋值的,因此就会去串池中找。
由于user1对象创建时,串池中创建了。user2对象再创建时,就会复用了。
字符串的创建方式https://mp.csdn.net/mp_blog/creation/editor/136662234如果需要深克隆,就需要重写clone方法或者使用第三方工具类
完整代码:
import java.util.Arrays;
public class User implements Cloneable {
private int id;
private String username;
private String password;
private String path;//游戏图片
private int[] data;//游戏进度
public User() {
}
public User(int id, String username, String password, String path, int[] data) {
this.id = id;
this.username = username;
this.password = password;
this.path = path;
this.data = data;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
@Override
public String toString() {
return "User{" + "id=" + id +
", username='" + username +
", password='" + password +
", path='" + path +
", data=" + Arrays.toString(data) +
'}';
}
/* 浅克隆
@Override
public User clone() throws CloneNotSupportedException {
return (User) super.clone();
}*/
//深克隆
public User clone() throws CloneNotSupportedException {
//1.先获取源对象的data数组
int[] data = this.data;
//2.创建新的数组,拷贝数据
int[] newdata = new int[data.length];
for (int i = 0; i < newdata.length; i++) {
newdata[i] = data[i];
}
//3.调用父类中的方法克隆对象
User u = (User) super.clone();
//4.由于Object中的clone默认是浅克隆,需要替换克隆的data属性
u.data = newdata;
return u;
}
}
此时运行相同的代码
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
//1.创建一个对象
int[] data = {1, 2, 3, 4, 5};
User u1 = new User(1, "zhangsan", "1234qwer", "animal1", data);
//2.克隆对象,修改源对象的属性,查看克隆的对象是否发生变化
User u2 = u1.clone();
int[] arr = u1.getData();
arr[0] = 100;
//3.输出对象
System.out.println(u1);
System.out.println(u2);
}
}
运行结果:
由于修改了重写的clone方法,使之成为深克隆。修改源对象u1的data属性,克隆后的u2对象的data属性并不会发生改变。
**************************************************************************************************************注:如果对象中存在多个引用数据类型,或者说存在的属性是二维数组,那么修改clone方法,就会涉及很多地址值,修改非常复杂,这时候就可以通过使用第三方工具类的方法
Ⅰ. 首先在本module下创建一个新的文件夹lib,引入jar包
Ⅱ. 右键jar包,点击Add as Library
Ⅲ. 编写代码
import com.google.gson.Gson;
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
//1.创建一个对象
int[] data = {1, 2, 3, 4, 5};
User u1 = new User(1, "zhangsan", "1234qwer", "animal1", data);
//2.克隆对象,修改源对象的属性,查看克隆的对象是否发生变化
Gson gson=new Gson();
//2.1将User对象变成一个字符串
String s=gson.toJson(u1);
//2.2再把字符串变回User类型的对象
User u2=gson.fromJson(s,User.class);
int[] arr = u1.getData();
arr[0] = 100;
//3.输出对象
System.out.println(u1);
System.out.println(u2);
}
}
运行结果:
从结果可以看出,只用了极少的代码,同样也实现了深克隆的功能。
五、Objects类
1.定义
Objects是一个对象工具类,提供了一些操作对象的方法。
2.常用方法
(1)equals方法
student类:
public class Student{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + ", age=" + age + '}';
}
//重写之后的equals方法比较的就是对象内部的属性值了
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
以上是重写了Object类的equals之后的代码,原则上是没问题的。
但当调用者为null时,结果会怎么样呢?
public class EqualsDemo {
public static void main(String[] args) {
//equals 比较两个对象是否相等
Student s1=null;
Student s2=new Student();
boolean result= s1.equals(s2);
System.out.println(result);
}
}
运行结果:
很明显,系统抛出了空指针异常,所以仅重写 Object类的equals方法是不够的,还需要进行非空判断。
如果自己写 if 判断是否为空,过于麻烦。
通过查看Objects的equals方法源码,可以发现
① 首先判断a和b的地址值是否一样, 相等返回true
② 再判断a是否为空, 为空返回false
③ 最后在调用equals方法判断对象a和b的值(没重写默认判断地址值,重写判断内部属性值)
import java.util.Objects;
public class EqualsDemo {
public static void main(String[] args) {
Student s1=null;
Student s2=new Student();
//Objects.equals 先做非空判断,再比较两个对象
boolean result= Objects.equals(s1,s2);
System.out.println(result);
}
}
运行结果:
结论:通过调用Objects类的equals方法,就可以避免 if 判断调用者是否为空了
(2)isNull和nonNull方法
通过查看源码,可以发现,本质上就是判断一个对象是否为空,两个方法返回的值是相反的。
import java.util.Objects;
public class Demo {
public static void main(String[] args) {
Student s1=null;
Student s2=new Student();
//Objects.isNull 判断对象是否为空
System.out.println(Objects.isNull(s1));//true
System.out.println(Objects.isNull(s2));//false
//Objects.nonNull 判断对象是否不为空
System.out.println(Objects.nonNull(s1));//false
System.out.println(Objects.nonNull(s2));//true
}
}
结论:调用该两个方法可以直接进行判断,不用写 if 来判断对象是否为空