为什么字符串是常量,在创建后不能被修改
因为String是被final修饰的,它的属性value是一个final修饰的char数组。
final修饰的。这就意味着value在构造器中被初始化后,无法再被修改了。
String是线程安全的吗?
String是不可变类,所以是线程安全的
☆String类被final修饰,不代表String对象是被final修饰
String的值在创建之后不能更改
String的值是不可以改变的,注意是值。因为value是一个final修饰的char数组,final修饰的。
这就意味着value在构造器中被初始化后,无法再被修改了。
事实上String对象内容的改变是通过内存地址的改变来完成的。
public class TestString {
public static void main(String[] args) {
String a = "hello";
System.out.println(a);
//相当于 new String("hello world") 此时引用a指向的内存地址发生了变化
//并不是值发生了变化
//a指向了1个新的对象
a+= " world";
System.out.println(a);
// 注意String类是被final修饰 不代表String对象是被final修饰的
// final String age = "1";
// age = age + "2"; 报错
}
}
Hello的内容并没有发生变化。只是a的指向发生了变化。
为什么会发生变化?
因为相当于 a+= " world"; 相当于 new String(“hello world”) ;
此时a指向了新创建的字符串对象。
Stirng面试题
String newS1 = new String("hello");
String newS2 = new String("hello");
String ss1 = "hello";
String ss2 = "hello";
System.out.println("ss1==ss2是"+(ss1==ss2));
System.out.println("ss1==newS1是"+(ss1==newS1));
System.out.println("newS1==newS2是"+(newS1==newS2));
true
false
false
String newS1 = new String(“hello”)首先会在堆中创建一块内存, 内存地址返回给栈中newS1。
然后因为"hello"是常量,JVM会去方法区中的字符串常量池查看是否有"hello"字符串的对象,没有的话就分配一个空间来存放"hello",并将其空间地址存入堆中new出来的对象中;
在新建newS2对象时先去方法区的字符串常量池中寻找"hello"字符串对象,发现已经有了,就直接将其内存地址存入堆中new出来的对象中,同理堆中new出来的对象的内存地址返回给newS2
注意 newS1 和 newS2 使用 == 比较的是内存地址值
对于String ss1=“hello"这样创建的对象,JVM会直接检查字符串常量池是否已有"hello"字符串对象,如没有,就分配一个内存存放"hello”,如有了,则直接将字符串常量池中的地址返回给栈中的ss1而不经过堆中new的对象
所以ss1和ss2指向的是同一个对象。
这个时候有人就会有疑问了,既然ss1和ss2指向的是同一个对象,那我修过ss1的值岂不是也同时改变了ss2的值?
显然,改变ss1的值并不会影响ss2以及其它对象的值,原因其实Java的String是一个不可变的类,假设我们对ss1重新赋值时其实是在字符串常量池中重新分配了一块内存然后令ss1指向新对象
我们可以尝试用反射来强行修改字符串常量池中对象的值,来看看会怎样
String内部是由一个char[] value和int hash值构成,但是Java,下面用反射来改变其内部的value值
package 反射修改String;
import java.lang.reflect.Field;
public class 反射修改String {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String newS1 = new String("hello");
String newS2 = new String("hello");
String ss1 = "hello";
String ss2 = "hello";
System.out.println("ss1==ss2是" + (ss1 == ss2));
System.out.println("ss1==newS1是" + (ss1 == newS1));
System.out.println("newS1==newS2是" + (newS1 == newS2));
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取ss1对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(ss1);
//把value所引用的数组中的第5个字符为'e'
value[4] = 'e';
System.out.println("ss1=" + ss1);
System.out.println("ss2=" + ss2);
System.out.println("newS1=" + newS1);
System.out.println("newS2=" + newS2);
System.out.println("ss1==ss2是" + (ss1 == ss2));
System.out.println("ss1==newS1是" + (ss1 == newS1));
System.out.println("newS1==newS2是" + (newS1 == newS2));
}
}
ss1==ss2是true
ss1==newS1是false
newS1==newS2是false
ss1=helle
ss2=helle
newS1=helle
newS2=helle
ss1==ss2是true
ss1==newS1是false
newS1==newS2是false
结果所有最终指向字符串常量池为"hello"的对象最终都会变成"helle"。
其内存原理如下:
用反射强行把方法区中的"hello"中的’o’改成了’e’,所以最终指向同一个字符串常量池中的对象的值都会跟着改变
拆箱与装箱
//自动装箱 把基本类型转换为包装类类型
//相当于Integer.valueOf(99);
Integer total = 99;
//自动拆箱 把包装类类型转换为基本类型
//相当于total.intValue();
int totalprim = total;
Integer缓冲池
Integer的数据直接赋值
如:Integer a = 99;
相当于Integer.valueOf(99)。
如果在-128到127之间,会直接从缓冲池里获取数据。
package com.itheima.zkDemo;
public class TestInteger {
public static void main(String[] args) {
Integer a = 1;
Integer b = 1;
System.out.println(a == b);
Integer a1 = new Integer(1);
Integer b1 = new Integer(1);
System.out.println(a1 == b1);
Integer a2 = Integer.valueOf(1);
Integer b2 = Integer.valueOf(1);
System.out.println(a2 == b2);
}
}
//true
//false
//true
package com.itheima.zkDemo;
public class TestInteger2 {
public static void main(String[] args) {
Integer a = 129;
Integer b = 129;
System.out.println(a == b);
Integer a1 = new Integer(129);
Integer b1 = new Integer(129);
System.out.println(a1 == b1);
Integer a2 = Integer.valueOf(129);
Integer b2 = Integer.valueOf(129);
System.out.println(a2 == b2);
}
}
//false
//false
//false
基本类型回收时机
我们要看你这个int a处于什么地方,如果是在方法内部中被执行到,也就是说是局部变量,就会在栈内存中分配内存,由于是int型,所以就是4字节,也就是32bit,long类型的才会是64bit。
而你说的表示地址我们称为堆内存。创建的对象以及对象中的方法和数据是存储在堆内存中的。JVM会在栈内存中建立对象的引用,然后当执行到new语句时,在堆内存中创建对象,这时就将这个对象的类型以及这块区域的内存地址值赋给引用,然后进行对象中数据的初始化。
也就是说,对象的引用存储在栈内存中,存放的是类型以及hash值,如Integer@0xff67。而对象里的内容实际上是存储在堆内存中的。
如果你这句int a只是在某个对象内作为成员变量,那么根本不会涉及到栈内存。a就存储在它所在的那个对象的堆内存中。明白了么?
对象数组
**数组既可以存储基本数据类型,也可以存储引用类型。**它存储引用类型的时候的数组就叫对象数组。
集合只能存储引用类型不能存储基本类型,具体表现为泛型为基本类型时编译期就会报错。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u44czF0r-1669712210994)(assets/1629514797676.png)]
排序算法
冒泡排序算法
相邻元素两两比较,大的往后放,第一次完毕,最大值出现在了最大索引处
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4w0nfsX-1669712210994)(…/1.java%E5%9F%BA%E7%A1%80/assets/1615802790029.png)]
N个元素外层需要比较N-1次。内层每次需要比较N-1-外层层数。
假设4个元素,外层需要比较3次,内层每次需要比较3-外层层数
package digitalwallet;
public class CompareSort {
public static void main(String[] args) {
//int[] values = {80, 10, 20, 5, 8, 4, 9, 19, 90};
int[] values = {80, 10, 20, 90};
//冒泡排序代码
bubbleSort(values);
printArray(values);
}
public static void bubbleSort(int[] arr){
boolean over = true;
//假设有4个元素 需要两两比较 x<3 取值范围是 0 1 2 外层需要比较3 次
for (int x = 0; x < arr.length - 1 && over; x++) {
over = false;
// y < 4 - 1 -x x取值范围是 0 1 2
// 即 y < 3 y < 2 y < 1 内层别需要比较 3 2 1
for (int y = 0; y < arr.length - 1 - x; y++) {
if (arr[y] > arr[y + 1]) {
int temp = arr[y];
arr[y] = arr[y + 1];
arr[y + 1] = temp;
over = true;
}
}
}
}
// 遍历功能
public static void printArray(int[] arr) {
System.out.print("[");
for (int x = 0; x < arr.length; x++) {
if (x == arr.length - 1) {
System.out.print(arr[x]);
} else {
System.out.print(arr[x] + ", ");
}
}
System.out.println("]");
}
}
package digitalwallet;
import sun.awt.SunHints;
public class CompareAndSort {
public static void main(String[] args) {
int[] values = {80, 10, 20, 90};
for (int i = 0; i < values.length-1; i++) {
for (int j = 0; j < values.length-1-i; j++) {
int prev = values[j];
int next = values[j+1];
if(prev>next){
int temp = prev;
//注意这里不能使用prev 和 next 因为不是引用类型
values[j] = values[j+1];
values[j+1] =temp;
}
}
}
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
}
}
选择排序算法
从0索引开始,依次和后面元素比较,小的往前放,第一次完毕,最小值出现在了最小索引处
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSKxeiTa-1669712210995)(…/1.java%E5%9F%BA%E7%A1%80/assets/1615806876790.png)]
N个元素外层需要比较N-1次。内层每次需要比较N-1-外层层数。
假设4个元素,外层需要比较3次,内层每次需要比较4-外层层数-1 次,第一个元素分别和第二三四个元素比较共比较3次
public class ChooseCompare {
public static void main(String[] args) {
int[] values = {80, 10, 20, 90};
//外层需要比较3次
for (int i = 0; i < values.length-1; i++) {
//内层需要比较 下标为0 的要和 下标为1的比较 和 下标为1 2 3的比较 总共 比较3次
for (int j = i+1; j < values.length; j++) {
if (values[i]>values[j]){
int temp = values[j];
values[j] =values[i];
values[i] = temp;
}
}
}
for (int value : values) {
System.out.println(value);
}
}
}
===