JavaSE学习笔记-08

包装类 Wrapper

八大包装类继承体系图

 

 虚线表示实现接口,实线表示继承父类。。。。。

包装类和基本数据类型的转换

基本数据类型不是对象,然而包装类是对象。。。。。

演示装箱和拆箱:

jdk5以前必须是手动:

 jdk5之后可以自动装箱和拆箱:

 底层还是运用到的是valueof这个方法:

底层对这个inValue方法进行包装,debug追进去之后可以发现。 

 其他的包装类转换和这种类似,自己试一试

包装类练习:

 经典面试题

 三元运算符是一个整体,因此最高精度是double类型,因此当输出1的时候,会发生精度的提升,因此输出的是 1.0

 

这个if else是分别进行的,不是同一个整体。所以精度不会互相影响。。。

包装类转换为String类型

 方式1解释:一开始我们使用自动装箱,把100装进去,自动转换为Integer类型(但还是底层实现了valueOf方法)Integer.valueOf(100)。

之后我们加上一个空字符串,在字符串常量池合成一个新的字符串"100",但是原来的Integer类对象并没有发生任何的改变 还是 100这个对象。

 

valueOf源码:

 String类型转换为包装类类型:

 总结:

 包装类常用方法

 面试题

Integer m=1和Integer n=1两者很明显是自动装箱。那么我们知道,他们的本质还是调用包装类Integer的valueOf这个方法

阅读valueOf方法源码可知:范围为-128到127之间的时候,直接返回这个值。不在这个范围的时候,我们是返回这个new出的对象

结果:

 

 1表示对 2表示错

2  2  1  2  2  1  1

记录:只要有基本数据类型,判断的就是值是否相同。一旦出现了一个基本数据类型,如本题出现了int i12=127,那么直接就是比较对应的值是否相等。。。。

 注意一点就可以:Integer i=127表示的意思是自动装箱:底层调用Integer.valueOf(127)

如果值是在  -128到127之间返回的就是这个值。但是不在这个范围的时候,我们返回的就是通过这个值new出的Integer对象,自行查看valueOf源码。

 解释一下示例五:一个是127,一个是new出来的对象,怎么可能返回true?肯定是false啊

String类(重点)

 

 这里的不可以修改的意思是:不可以修改指向的地址,但是指向的字符串的内容还是可以进行修改的

如图:value原来指向一个字符串,一旦被final修饰之后,不可以再指向其他地址的字符串,但是可以修改原先指向地址处字符串的内容

 如图演示:被final修饰的value字符数组,不可以改变指向。只可以改变字符数组的内容。

 不可以修改value的地址。但是可以修改 value数组的内容。

两种创建String对象的区别:

 

对于方式一:我们是直接用引用s(如图)指向指向这个常量池中的字符串的

对于方式二:

我们在堆空间中进行维护了value属性,用这个属性是指向常量池中的字符串。这个引用s2(如图)指向这个value属性的

 对于维护这个value属性,我们看源码:

JVM内存图:

 分析:

第一种方式:直接赋值 String s="hsp";

在栈空间先创建一个局部变量对象引用s 存放地址,之后再在字符串常量池上进行查询,如果常量池中原本就存在这个常量字符串,那么就直接指向这个字符串,并且保存这个地址。如果不存在,我们再自己进行创建并且保存地址,注意是栈空间上的引用s直接指向常量池

 第二种方式:调用构造器 String s2=new String("hsp");

先在栈空间中,创建一个实例对象引用s2,之后再在堆空间进行new一个对象空间,维护一个属性为value。并且把这个堆空间中的对象地址存放到栈空间的对象引用s2中。那么即是让s2指向堆空间中维护的value属性。之后发现参数为一个字符串,因此我们再从字符串常量池中进行寻找,如果存在,直接指向,并且把这个地址给到堆空间中维护的value属性进行存放。如果不存在,我们再自己进行创建一个常量字符串存到常量池中,并且把这个常量字符串的地址返回给堆空间维护的

value属性进行存放。这样形成了s2指向堆空间的value属性,value属性指向常量池中字符串。

 String练习题

 对于==:我们知道既可以比较基本数据类型,也可以比较引用数据类型。我们知道String是引用数据类型,因此我们比较的是地址,两个对象new两次创建出的地址肯定不一样,所以第二个为false

对于equals:我们知道,它只可以比较引用数据类型,并且重写了String这个引用类型的equals方法,这里省略解释,肯定是true。【如果忘记可以查看深入学习Java-03中的解释】

 

  intern方法:

对于intern这个方法,我们记住一点就行:无论如何,我们最终返回的就是字符串常量池中常量字符串的地址

 

 解释:

equals只可以比较引用数据类型,并且已经重写了String类型的equals方法,

1.为true

== 既可比较引用类型也可以比较基本数据类型,比较引用类型时,比较的就是地址。

我们通过作图可知:

a中存放的地址:是直接指向字符串常量池中常量字符串

b中存放的地址:在堆空间中对象的地址

因此 a==b

2.返回false

对于intern这个方法,我们记住一点就行:无论如何,我们最终返回的就是字符串常量池中常量字符串的地址

上面我们分析过 a和b存放的地址,所以

a==b.intern() 

3.返回true

4.返回false

1.False

2.True

3.True

4.False

 

1.T  equals只可以比较引用类型 并且equals方法被重写了,所以比较的实际上是两个字符串的内容,所以为true

2.T  这个同理3  只会创建一份,因此地址肯定是一样的

3.T 对于String这个引用类型,==比较的就是地址,那么我们知道一个相同的字符串常量在常量池中只会创建一份,因此肯定是返回true

4. F

字符串对象的特性(重点)

 

我们要明白常量字符串在常量池中的创建规则:如果发现创建的字符串中在常量池中存在,那么更改指向即可。倘若不存在,我们再进行创建这个字符串常量存放到常量池中。 

本题:先在字符串常量池中创建一个字符串常量对象“hello”

然后执行第二句,在常量池中寻找看是否存在 haha 这个字符串,发现不存在,那么再创建一个字符串存放到常量池中,并且改变s1的指向,指向haha。

 经典面试题:

哈哈哈哈哈

答案是一个对象。因为编译器底层会进行优化,

 

对于这个String a="hello";创建a对象,在内存中分布图:类似下图:a在栈区,对象hello在常量池中

  这个我们要debug一下:

追进去:

添加一点特别重要的事情就是:

我们这里String  c=a+b;不一定非要 a和b都是变量,只要a和b不全是字符串常量,那么在编译的过程中肯定要进行接下来的过程。

过程:

先创建一个StringBuilder对象,之后通过StringBuilder的append方法把a对应常量字符串追加一下,同理接着把b对应的常量字符串也追加到后面。之后再在堆空间上创建一个String类型的对象

 1.调用StringBuilder

2.追加hello

3. 追加abc

 4.调用底层的toString方法,把 helloabc 返回给你

 总结:

 

我们知道了debug的这个过程之后,我们就可以画出内存图。

值得我们注意的就是 String c=a+b;的艰难创建过程。。。但是最终还是调用底层的toString方法

我们知道这个toString方法还是new一个对象返回的,

所以是new一个对象在堆空间开辟空间的,地址返回给c,c是指向堆空间中new出来的String类型的对象。堆空间中的对象中存放着常量池中字符串的地址。。。。

我们要明白: 

拓展:

c最终指向的是堆空间的地址,然而d指向的字符串常量池的地址,肯定是false啊

  String e="hello"+"abc"; 编译器会有一个优化机制,直接返回常量池中的helloabc这个字符串的地址返回给 e 所以d==e 返回true

 

 我们对于s1+s2 是经历了许多次debug可以知道,

但是对于intern方法明确一点就行,他一定是返回字符串常量池中字符串的地址的。

 难题:

 结果是:

 

 分析:

从main方法中开始分析,一开始new出来一个Test1类型的对象存到堆空间中

重点是要分析出来:之后我们要在这个Test1new出来的对象空间中再进行new对象:String str=new String("hsp"); new出来一个String类型的对象。

同理,我们分配一个str作为对象实例引用指向,指向这个对象空间中存放的属性value,这个属性value指向字符串常量池中常量字符串:“hsp” 的地址。

同时一个重点就是:字符数组其实也是一个对象,是位于堆空间的。数组名作为一个地址相当于对象实例的引用,指向这个字符数组的内容。

下一步,我们调用方法change,根据前面的知识我们可以知道,每一次调用一个方法都会在栈空间开辟新的空间。

传过去的参数是 str的地址和ch这个字符数组的地址,,,

指向是一样的,

但是这个str和这个ch,这生成的线也是独立存在的和前面的线不一样。【毕竟是参数,都是存在于栈空间上的局部变量类型】

执行方法change:

str="java" :我们知道str指向的空间values,这个values空间指向的字符串常量池中没有 "java" 这个字符串。所以根据前面所学知识可知,我们会重新在常量池中写一个java,并且让str指向这个java

ch[0]='h'就是通过改变ch指向的数组内容

等该方法执行完之后,该方法在栈空间开辟的空间就会销毁。新开辟的str和ch这两个局部变量参数也会随之销毁,对应指向的线也随之消失。

但是之前在new Test1这个对象中 new出来的对象 new String("hsp") 是不会改变的。ch这个数组对象同样存在完好。

最后输出:hsphava

 String类常用方法:

 

 

我们要注意第4个方法:

我们返回字符串对象中第一次出现的索引,注意是第一次

 注意:

返回的是0,第一次出现we这个字符串的下标位置。。。。

 5.获取该字符在字符串中最后一次出现的索引,如果找不到,返回-1。

 6.

 name.substring(0,5)表示的意思是从下标为0处的字符截取到下标为5的字符的前一个即是 hello

我们也可以把0到5这个区间想象为一个左闭右开的区间即是从下标为0截取下标为4处 [0,5)==[0,4]

 

表示的意思是从下标为2开始截取到下标为5的前一个字符 即是llo 

同上 想象为左闭右开的区间 [2,5)==[2,4]

 

 4.把字符串中的字符全部进行替换,如图:把林黛玉全部替换为薛宝钗

当然也可以直接写成s2,只要是字符串类型就可以

 但是注意的是:

s1是没有发生任何的变化的,只是s1.replace()这个结果是变化的。我们前面能实现功能是因为,我们进行了  s1=s1.replace();这种s1的覆盖方式。。。。。。

 看一下图中代码:s1是没有任何变化的,只是s1.replace()是不断的变化的

 5.

 当我们想要以 \\进行转义的时候,我们要使用转义字符:\ 。

分析一下 \\\\  :

第一个 \ 表示一个转义字符 它的意思是第二个\ 就是一个 \

第三个 \ 也表示是一个转义字符 它的意思是第四个 \ 就是一个 \  啊

 

 对于第7个方法,我们通过查看源码可知规则:

当我们从最短的字符串中可以比较出不同的时候,我们返回的就是前一个和后一个,第一个开始不同的字母的ASCII码的差值。

当我们不可以从最短字符串比较出来不同的时候,我们用的是第一个字符串的长度减去第二个字符串的长度。如图情况,结果为-1

 

结果为3

第二种情况就是,我们在比较完最短的字符串的时候,可以分辨出两个字符串有不同的字符 。

第三种情况就是,在把最短的字符串比较完之后,发现和较长的字符串前面都相同,那么只能用长度进行相减得结果。

 8.

 StringBuffer

 

 

注意第三点:

在数组中存放字符串的内容,数组存放在堆空间中,所以我们把字符串内容存放到堆空间中 。

 

然而对于String类型,我们每一次增加一个字符。不是说直接在字符串后面进行追加,

如果常量池中原来有,那么更换地址指向。

如果没有改变后字符串,那么在常量池中创建一个这个改变后的字符串,返回一个新的地址。

原来的字符串也不会删除,但是地址随着每一次的字符增加,每一次都要进行改变的。

 
 我们知道这个字符数组是存放于堆空间的,当我们增加内容的时候,不是说每一次都更新地址。当我们这一块数组的空间不够用的时候,我们才会进行开辟一个二倍(或者增加规定大小)的空间在堆空间中,并且把原来的字符串赋值到这个新开辟的空间。

这样就比较高效,不用说每增加一个字符就进行一次地址的更新。。。。

 构造器的使用

 指定大小

 

String类型转换为StringBuffer类型:

 

 把StringBuffer转换为String:

 StringBuffer方法(先掌握常用的六种方法)

 删除,索引下标范围是左闭右开的

 我们这里还依旧是左闭右开的范围区间,所以把赵敏替换为周芷若

 

重点:

 

StringBuffer练习

 我们找源码:

1.

2.从append进去

 3.发现还是调用的是父类的append方法 ,那么再进去append

4.当str==null的时候,调用的是这个appendNull方法,再追进去

这个表示的空对象(null)转换成一个空字符的字符数组 {'n' 'u' 'l' 'l' }

分析:由上面的源码可知,当传入的字符串为null的时候,我们调用的是父类的appendNull方法

那么最后转换为一个字符数组之后,我们把这个数组进行返回(看看源码)

——接上面的图片

 分析:

对于第一句:我们可以知道,sb返回回来的就是一个字符数组: {'n' 'u' 'l' 'l' },那么打印出来的就是null

对于第二句:我们就要看StringBuffer的构造器源码了:

 传进来的str是null,是空指针,所以null.length()会导致空指针异常。

一旦发生异常下面的代码就不会再执行

 

 StringBuilder

 

1.StringBuilder不是线程安全的,所以多线程的情况下是不建议使用的。但是在单线程的情况下,我们是建议使用StringBuilder的,因为StringBuilder在大多数情况下,它是要比StringBuffer速度要块的。 

 2.它俩拥有的方法差不多

 

2.串行化:对象可以进行网络传输,也可以保存到文件中。 

 第一条和第三条的源码依据:

 第四条源码依据:

 第五条源码依据:

如图:StringBuffer的方法是有互斥处理的,是适用于多线程的安全问题的。

 但是StringBuilder则不一样,它是没有互斥处理的。

 重点:String StringBuffer  StringBuilder的区别与选择场景

 对于String:是不可变字符序列,效率低,但是复用率高。

如何解释一下复用率高呢?

我们在字符串常量池中,当我们修改字符内容的时候,不可以直接在后面追加字符。而是重新开辟一块新的空间去存放这个新的字符串内容,并且原来的字符串依然在常量池中保存一份。因此我们有了许多的字符串在常量池中保存。

举个例子:常量池中只有 leo这个字符串

但是我们想要leomessi这个字符串,我们不可以直接追加到eo后面一个messi

我们必须重新创建一个leomessi存到空间中

此时 空间中就包含了 leo和leomessi的字符串

假设说下一次我们再要找leomessi这个字符串,我们就可以复用上一次保存的leomessi

因为常量池中字符串,不断的改变过程中,改变前的字符串是不会改变的,依然是保存着的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值