前边十几p是跟着视频做一个石块迷阵,没什么能发出来的,所以跳过,温习的话得回去看原视频
p146 常用api Object类 toString方法
Object类:
所有的类都直接或者间接的继承了Object类,所以Object类的所有方法是一切子类都可以直接使用的。
toString()方法:
返回该对象的字符串表示
使用打印语句打印对象名的时候,println方法在源码层会自动调用对象的toString方法,也就是打印对象名就是打印对象名.toString()
从这里可以看出来,在没有重写toString方法的情况下,打印对象名打印出来的所谓“内存地址”的东西的本质是真正的内存地址配合这里的哈希算法算出来的哈希值转化为16进制之后的结果。
但是这段东西对程序员本身来说是没有意义的,所以实际运用中一般会对这个方法进行重写。
在开发工具idea中,在类中右键空白区域->Generator->toString()可以由系统快速重写toString方法,并且会由用户选择将哪些变量展示出来,并将选择的变量用一个比较整洁的格式用字符串展示出来。以前装过的快速包装JavaBean的ptg插件也会自动重写toString方法。
p147 equals方法
equals()方法:
传入一个对象,指示这个传入的对象是否与调用该方法的对象”相等“
实质上Object类的equals方法比较的是对象的内存地址,所以在创建类的时候一般会重写equals方法,例如以前用过的String类和Arrays类就重写了equals方法,所以达成了不是比较地址而是比较具体内容的效果。
重写思路:
-
调用equals方法的对象(也就是发起比较的对象)可以在方法中用this代替,因为调用方法调用的是这个对象的类的成员方法;
-
equals方法传入的Object obj也就是用来被比较的对象,所以在重写的方法中obj就代表被比较的对象,但是输入的对象都会被判定为Object类,所以需要用强制转换将接收的对象向下转换回原本的类型,再去比较内部的各个成员变量(判断this.变量名 == (类名)obj.变量名);
-
由于Object类是所有类的直接或者间接父类,所以一个类的equals方法里甚至可以传入一个跟他完全没有关系的类的对象,而如果这个发起对比的对象的类中涉及到了上一条里提到过的强制转换,就会出现类型转换错误导致程序出错(只有有继承关系的类之间才能互相转换)。所以需要在重写的equals方法里再添加一个判断,使用instanceof方法判断传入的这个对象是不是这个equals对象可以拿来比较的类(一般都是判断传入的这个类是不是重写equals方法的这个类本身),不是的话就做出对应的动作然后返回false。
-
实际上idea开发工具在类空白区右键generator可以选择生成equals方法和hashCode方法,它和我们自己在上面编写的equals的方法存在的区别会在下一节进行讲解。
经过这个过程就可以明白,Object类中的equals方法存在的意义就是为了被子类重写,以便让子类自己来定制比较内容的规则。
p148 阅读idea生成的equals方法
创建一个学生类包含String类变量name和int类对象age,由idea生成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); }
第一个if语句用于判断两者地址是否相同(地址相同一定为同一对象,自己和自己一定相同),两者不是同一地址的对象时才能够向下继续执行到第二个if语句;此外,如果调用方法的对象为空,会直接导致空指针异常,代码报错。
第二个if语句的第一个判断输入的内容是不是一个空对象(null)。逻辑层面上,如果第一个对象不为空、第二个对象为空则二者一定不相同;
第二个判断本类和输入的类的字节码是否相同(实际也就是判断二者的类型是否相同),如果字节码不同则内容不同;
通过两个语句运行到下面的代码部分就代表比较的和被比较的两个类是都不为空的、类型相同的、两个不同的对象,随后对输入的对象强制转换(对象本身确实是相同的,但是这里作为临时变量接收被比较的对象的这个对象 o 是Object类而不是原来的类,所以要强制转换),转换完成后挨个比较两个对象里的属性,如果属性都相同就代表这两个对象是“相等的”。
注:在最后一行可以看到调用了Objects.equals方法,这个方法的作用是比较括号内两个对象是否相等。在底层代码中也会经过一系列类似于这里整段代码的严谨的判断之后再比较具体内容。
那么我们用编译器编写的equals方法和Objects里的equals方法有什么区别?
经过测试,Objects里的equals方法实际上还是依赖于我们自己给类编写的equals方法,但是他的好处在于增加了一个对 发起比较的类 的是否为空的检测,前面提到过空对象发起equals方法会报错导致空指针异常,但是使用Objects.equals方法就算遇到空的类发起比较也不会引发空指针异常,这样程序就不会报错而可以继续往下走。
Objects.equals的方法体如下:
return a == b || a != null && a.equals(b);
如果a为空、b为空,a==b为true,双或符号||左侧为true则右侧不执行,整体为true;a与b地址相等同理;
如果a为空、b不为空,a==b为false,接着看右侧,a!=null为false,双与符号&&左侧为false则右侧不执行整体为false,双或符号左侧为false右侧为false,整体为false;
如果a不为空、b为空,a==b为false,看右边,a!=null为true,才会调用a.equals(b)方法,最后返回false,得出整体为false;
如果a、b都不为空、地址不同且内容相等,则a==b为false,a!=null为true,a.equals(b)为true,得出整体为true。
由此可见,这一行代码规避掉了所有a对象为空时调用类中equals方法的情况,所以不会出现空对象调用equals时出现的空指针异常。
Objects还有一个方法是isNull()方法,传入一个对象,效果为判断这个对象是否为空。
p149 Math类、System类
Math类
归属于java.lang包,不需要导入就可用。
Math类没有构造方法,并且由api文档可知这个类的所有成员方法都是静态(static)方法,所以这个类就是前面提到过的工具类,不需要(实际上也无法)创建对象。
常用成员方法
记得运用的时候要在方法名前面添加类名点(Math.方法名),并且这些方法如果没有单独说明,即为对除了short以外的所有表达数字的数据类型(也就是int,long,double,float)都可用。
abs(a); 返回参数a的绝对值
ceil(a); 将小数a向上取整,返回结果(只对float和double这两个表示小数的类型有用,只返回double类型结果,向下取整也一样)
floor(a); 将小数a向下取整,返回结果
round(a); 将小数a四舍五入,输入的数据为float则返回int类型,输入的数据为double则返回long类型。
max(a,b); 返回a与b中更大的一个数,返回值的类型为a与b其中范围最大的一个(范围从小到大为int,long,float,double)
min(a,b);返回a与b中更小的一个数,返回值的类型为a与b其中范围最大的一个
pow(double a,double b); 返回a的b次幂,返回值为double类型
random(); 返回一个[0.0,1.0)的double类型的数,与Random类中的random方法不同,这里的random方法无法通过传入参数来限制取值范围,取值范围固定为0.0到1.0且包括0.0,不包括1.0(但是可以无限接近1.0)的小数(返回值固定为double类型)
System类
System类的功能是静态,也就是这个类也是个工具类。
常用成员方法
exit(int status); 终止当前运行的Java虚拟机,参数不为0则代表这次终止是异常终止
currentTimeMillis(); 返回当前系统的时间毫秒值形式,返回值类型为long,实际返回结果出来是一串数字,实际意义是从1970年1月1日0时0分0秒到现在所经历的毫秒数,可以用来测试程序运行时间等等。
arraycopy(Object src,int srcPos,Object dest,int destPos,int length);
将一个数组 从某一个位置开始的 若干份数据 拷贝到 另一个数组的 某一个位置开始 的若干个位置。接受的五个参数分别是数据源数组(从哪个数组拷贝),起始索引(从数组的哪里开始拷贝),目的地数组(拷贝到哪个数组),起始索引(拷贝到目的地数组的哪个索引开始),拷贝数据的个数。length值设置不当会导致数组越界。
计算机的时间原点
前面提到过的1970年1月1日0时0分0秒是计算机的时间原点,也是c语言的生日(实际换算到中国的时差是8时而不是0时)。
p150 BigDecimal类
概述
这个类用于解决小数运算中出现的不精确问题。
例如:double num1 = 0.1;
double num2 = 0.2;
(num1 + num2).sout
按照常理来说输出结果应该是0.3,但是实际测试一次发现他的输出结果为0.30000000000000004,很明显是不正确的。这个就是小数运算中产生的不精确现象。
BigDecimal类创建对象
有三种创建该类的对象的方法:
BigDecimal(double val); 直接把小数传进构造方法(不要用,这种方法并不会解决不精确的问题,只是做无用功)
BigDecimal(String val); 把小数用字符串的形式传进构造方法
BigDecimal.valueOf(double val); 用这个类的valueOf方法把小数传给类来创建对象。
常用成员方法
add(BigDecimal augend); 一个BigDecimal类调用add方法传入另一个BigDecimal,返回BigDecimal类的对象,它的含义为二者相加的和。
subtract(BigDecimal augend); 跟add差不多,但是是减法而不是加法,其余都一样
multiply(BigDecimal augend); 乘法
divide(BigDecimal augend); 除法,但是遇到除不尽的情况会报错
divide(BigDecimal augend,int scale,int roundingMode);除法,相对上一个方法,多出的一个int类型参数scale代表除法得出的结果取小数点后多少位,另一个int类型参数roundingMode代表结果末尾的舍入模式,一般情况选用RoundingMode.HALF_UP,也就是四舍五入(需要import java.math.RoundingMode)对源代码解读之后可以发现四舍五入的舍入模式的int类型数值为4,进一法(UP)为0,直接舍去(DOWN)为1。
doublevalue(BigDecimal augend); 用double类型返回这个BigDecimal类对象代表的实际数值。
p151 包装类
什么是包装类?
就是将基本数据类型包装成类,变成引用数据类型。
包装类的好处?
变成类就可以创建对象,然后这个对象就可以调用这个类的方法来解决问题了。
以前提到过,除了int类型包装类是Integer,char类型包装类是Character,其余的基本数据类型把首字母大写就是它的包装类。
怎么包装?
首先先介绍两个概念:
手动装箱:指手动调用方法把基本数据类型的数据包装成包装类;
手动拆箱:手动的把包装类拆成基本数据类型的数据。
以int类型和Integer为例(其余的可以根据这个例子配合api文档来推演,大差不差)
两种手动包装方法:
Integer(int value); 把要包装的数据直接传给构造方法;(无论是编译器还是课上的老师都说不推荐使用已过时,不过没说为什么,能不用就不用)
Integer.valueOf(int value); 把要包装的数据用valueOf方法传进对象里。
两种拆包方法:
对象.intValue(); 返回这个包装类的基本类型数据,这里是Integer类所以返回的是int类型。
自动拆装箱:
从JDK5开始出现。
自动装箱:可以把基本数据类型直接赋值给包装类的变量
自动拆箱:可以将包装类的变量直接赋值给基本数据类型变量
自动装箱的原理:自动调用了valueOf方法
结论:基本数据和对应的包装类可以直接做运算了
Integer包装类常用方法
toBinaryString(int i); 把传入的数据转化成二进制的字符串
toOctalString(int i);把传入的数据转化为八进制的字符串
toHexString(int i); 把传入的数据转化为十六进制的字符串
parseInt(String s); 把传入的数字字符串转化为int类型数字,如果传入的不是数字字符串就会出现NumberFormatException异常(数字格式化异常)。
这四个方法都是静态修饰的,所以可以直接用类名调用,也就是Integer.方法名 的形式。
以Integer包装类的方法为例子,其他包装类的常用方法都大差不差,逻辑相同或者相似、只有返回值类型、参数类型、方法名有略微不同。
练习:
已知字符串String s = "10,50,30,20,40"
把这个字符串转换为整数然后存入数组,随后求出最大值并打印。
个人思路:用截取(substring方法)挑出字符串的每个数字,但是只能挑出特定位数的,如果有一个数字不是两位数就会全乱套;然后把这些数给到数组,再用一般方法求数组最大的一个然后打印
老师思路:用切割(split方法)直接把字符串切成数字组成的字符串类型数组,然后用parseInt方法把每个数字字符串都转化成int类型存进int类型数组,然后求数组里的最大的一个并打印。
p152 包装类面试题
上面的判断结果为true,下面的判断结果为false。
先说结论:
直接把int类型数值赋值给包装类会使用自动装箱,自动装箱的时候如果装的数据范围是-128到127,那么这个装箱之后的对象是系统中 已有的 一个包装类 的数组 里面具体含义对应 的一个已有对象,否则会new一个新的包装类对象
细化原因:
阅读源代码可以发现,valueOf方法里写死了在-128到127这个范围内和范围外使用的是不同的return语句,在范围外的话会new一个Integer类对象然后返回,所以下面i3和i4虽然都是129,但是实际上是new出来的两个新的对象,地址不同,==运算结果就是false;相反,上面的情况在-128到127这个范围内会返回一个已有的数组里含义对应的一个对象,i1和i2都会指向一个系统中已有的包装类数组中含义对应的同一个已有的包装类对象,所以==运算的结果是true。
再说简单点就是,在-128到127范围里自动装箱返回的是系统里已有的对象,范围外自动装箱会new一个新对象。
反思1:为什么会有这么一个数组?
为了在实际运用中需要频繁的用到这个范围里的数的自动装箱的时候可以节省空间,不然用一次自动装箱他就new一个对象,用的越多占的空间越大。而且所有数值的包装类都有这么一个用来节省空间的数组。
反思2:如何避免题目中这种明明含义相同但是比对结果不同的情况?
用equals方法就行了。
本章学习目标:
- 掌握Object类中toString方法,equals方法的作用
- 能够根据帮助文档,使用Math,System类中的方法
- 能够使用BigDecimal类解决小数运算的精度损失问题
- 清楚自动拆装箱的原理,并能够给讲清楚最后那道包装类的面试题