java虚拟机

JVM(Java Virtual Machine ):Java虚拟机,简称JVM,是运行所有Java程序的假想计算机,是Java程序的运行环境,是Java 最具吸引力的特性之一。我们编写的Java代码,都运行在 JVM 之上。

跨平台:任何软件的运行,都必须要运行在操作系统之上,而我们用Java编写的软件可以运行在任何的操作系统上,这个特性称为Java语言的跨平台特性。该特性是由JVM实现的,我们编写的程序运行在JVM上,而JVM 运行在操作系统上。

Java的虚拟机本身不具备跨平台功能的,而是每个操作系统下都有不同版本的虚拟机

在这里插入图片描述

什么是java虚拟机?为什么java被称作是跨平台的编程语言?

JVM是Java Virtual Machine(Java虚拟机)的缩写,它是一个虚构出来的计算机,是用来运行java的字节码文件的。

java的跨平台实际上指的是字节码文件的跨平台,字节码文件是运行在jvm上的,与平台无关。
不同的操作系统有相对应的jvm,一套相同的字节码文件经过不同的jvm解释成相对应的机器码执行,这就是跨平台。

什么是机器码?

完全依附硬件而存在~并且不同硬件由于内嵌指令集不同,即使相同的0 1代码 意思也可能是不同的~换句话说,根本不存在跨平台性~比如~不同型号的CPU,你给他个指令10001101,他们可能会解析为不同的结果~

jre jdk

JRE (Java Runtime Environment) :是Java程序的运行时环境,包含 JVM 和运行时所需要的 核心类库 。
JDK (Java Development Kit):是Java程序开发工具包,包含 JRE 和开发人员使用的工具。

我们想要运行一个已有的Java程序,那么只需安装 JRE 即可。
我们想要开发一个全新的Java程序,那么必须安装 JDK 。

java程序开发步骤

在这里插入图片描述
编写、编译、运行。

Java源程序(.java)要先编译成与平台无关的字节码文件(.class),然后字节码文件再通过Java虚拟机解释成机器码运行。

字节码文件是经过编译器预处理过的一种文件,是JAVA的执行文件存在形式。

java基本数据类型

Java中的默认类型:整数类型是 int 、浮点类型是 double 。

虽然float占4个字节,但是采用科学技术法,取值范围远远大于8个字节的long

在这里插入图片描述

数据类型转换

Java程序中要求参与的计算的数据,必须要保证数据类型的一致性,如果数据类型不一致将发生类型的转换。

自动转换

当表达式两边数据类型不一致时,取值范围小的数据或者变量,可以直接赋值给取值范围大的变量

  1. 不需要进行代码的特殊处理
  2. byte,short,char数据,只要参加运算,就会自动转换成int类型
  3. 表达式最终的数据类型,一定是表达式中取值范围最大的类型
    byte、short、char -> int -> long -> float -> double
// 左边是long类型,右边默认是int类型,两边数据类型不一致
// 取值范围小的数据或者变量,可以直接赋值给取值范围大的变量

// int --> long,自动类型转换
// int4个字节,long8个字节,
// 自动类型转换只需要在前面补充4个字节的0,对值并没有影响
long num = 100;

double num = 100;

强制转换

浮点转换成整数,直接取消小数点,可能造成数据损失精度。

// 左边是byte类型,占一个字节,右边是int类型,占4个字节
// 从小到大的类型可以在前面添加缺少字节的数量的0
// 从大到小,需要强制类型转换,字节就会从左边开始被削减
int num = (int)6000000000L;

在这里插入图片描述

常量和变量的运算的注意事项

// 左边byte取值范围小,右边int取值范围大,理论说应该报错,但实际并没有
// 这么赋值的话,1代表着常量的角色
// 整数常量只要不超出对应的赋值变量的范围,可以直接赋值,相当于隐藏了一个强制类型转换



byte a = 1;

byte b = 2;

// 这个也不会报错,此时的 1 2 也属于常量,并且运算后的值也没有超出byte的范围
byte c = 1 + 2;

// a b 均为变量,值可能会超出byte的范围,所以这个不会自动强制类型转换,是报错的
byte d = a + b;

java运算符

// ++ - -
// 1. 单独使用a++,a- -,++a,- -a,是没有区别的,都是加1,或者减1
// 2. 和其他操作(赋值,打印)一起使用,
// 		加减在前,先加减,后赋值
//		加减在后,先赋值,后加减

int a = 1;
a++;
// 2
sout(a)

int b = 1;
int c = ++b;

// 2
sout(b);
// 2
sout(c)

int d = 1;
int e = d++;

// 2
sout(d)
// 1
sout(e)







// 复合赋值运算符
// +=,-=,*=,/=,%=

int a = 1;
int b =2;
// a = a + b;
int a += b;

// 复合赋值运算符,如果数据类型不一致,会发生强制类型转换(隐藏的)
// 相当于 byte s = (byte)200;






// 逻辑运算符的短路效果
// and,表达式左边为false,右边不计算
// or,表达式左边为true,右边不计算




// 三元运算符
int a = true ? 1 : 2;






// switch
// 1. case后面的常量值不能重复,case后不写break,匹配成功依然会对下一个case值进行匹配,这叫case穿透
// 2. switch后面表达式的数据类型:byte/short/char int String 枚举
// 3. default:所有值都不匹配时,执行这个
public static void main(String[] args) {
    int month = 3;
    switch (month) {
        case 12:
        case 1:
        case 2:
            System.out.println("冬季");
            break;
        case 3:
        case 4:
        case 5:
            System.out.println("春季");
            break;
        case 6:
        case 7:
        case 8:
            System.out.println("夏季");
            break;
        case 9:
        case 10:
        case 11:
            System.out.println("秋季");
            break;
        default:
            System.out.println("火星来的吧,滚...");
            break;

    }
    System.out.println("game...over...");
}

什么是引用

变量的实质是一小块内存单元。这一小块内存里存储着变量的值。

int a = 1; a就是变量的名,1就是变量的值。

A a =new A(); 而当变量指向一个对象时,这个变量就被称为引用变量

a就是引用变量,它指向了一个A对象,也可以说它引用了一个A对象。我们通过操纵这个a来操作A对象。 此时,变量a的值为它所引用对象的地址

  • 对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。

  • 对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)

java是值传递还是引用传递

java是值传递,没有引用传递

面试官:你好,你能说出下面个程序的执行结果吗?

public class Test {
 
	public static void main(String[] args) {
		String name = "Scott";
		int age = 5;
		User user = new User();
		user.setName(name);
		user.setAge(age);
		System.out.println("before change user = " + user);
		
		change(user, name, age);
		System.out.println("name = " + name);
		System.out.println("age = " + age);
		System.out.println("after change user = " + user);
	}
	
	public static void change(User user, String name, int age) {
		name = "Tom";
		age = 20;
		user.setName(name);
		user.setAge(age);
	}
	
	static class User {
		private String name;
		private int 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 "{name : " + name + ", age : " + age + "}";
		}
	}
}




// 执行结果
// before change user = {name : Scott, age : 5}
// name = Scott
// age = 5
// after change user = {name : Tom, age : 20}

面试官:恩,你的答案完全正确。age是一个基本类型变量,User、String都是引用类型变量,但是在调用change()方法之后,实参name和age都没有改变,而user对象改变了。那么可不可以说调用change()方法时,User对象是引用传递,age是值传递?可是String的传递方式又是什么呢?它的表现和age相同,但本身确是引用对象,这该如何解释呢?

:Java和C++不同,C++中有引用传递的函数调用方式,而Java中只有值传递

首先,值传递的官方解释是:

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

其次,引用传递的官方解释是:

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

一句话概括值传递和引用传递的区别:

值传递是传递实参副本,函数修改不会影响实参;引用传递是传递实参地址,函数修改会影响实参。

面试官:恩,那你可不可以结合上面的程序题,解释一下这句话呢?

:好的。针对于上面程序的应用场景,在调用change()方法的时候,user、name、age三个变量都是值传递

其中,user对象是将引用拷贝了一份,引用是对象的地址,change()中对user的修改,并没有影响到这个地址,而是修改了对象属性。产生混淆的关键在于人们看到对象本身被函数修改了,就错误的认为这是引用传递。但我们区分值传递还是引用传递的关键在于实参是否被函数所修改,对于user对象来说地址才是实参!但如果你在change()方法中修改user引用的地址,即新创建一个新的user对象的话,就会看到main方法中的user并没有任何改变,也就反向印证了它实际上是值传递。

name变量自然也是将name引用拷贝一份传递给change()方法,根据值传递的定义,函数对这个副本的修改不会影响到实际参数,又因为String的final特性,name = “Tom”; 实际上就是修改了name的地址,因此,实际参数不会受函数修改的影响。

age其本身也是将数值拷贝一份传入change(),所以任何修改都不会影响到实参。所以我们说,在Java中只有值传递这一种参数传递方式。

面试官:恩,你的理解已经非常到位了,那额外问一句,对于上面的三种类型User、String、int,当输出结果如何时,才可以认定是引用传递呢?

:对于User对象,上面的代码实际上并不能很好的验证这个问题,反而给人一种对象即是实参的假象。实际上,如果调用change()方法后,重新new 一个User对象后赋值给user,即改变了变量的地址。在这种情况下,main中如果可以成功打印出新对象的话,才可以认定是引用传递。

String类型的name也是一样,如果在change()中 name = “Tom”; main中如果可以打印出 Tom 的话才可以认为是引用传递。

包括age变量,尽管它是一个基本类型变量,但在内存中,依然有地址和数据的区分,如果在change()中的修改,main中可以看到的话,才可以说是引用传递。

Java 中基于各种数据类型分析 == 和 equals 的区别

  1. equals()的底层就是==,所以需要比较内容而不是地址值的话就需要覆盖重写equals()和hashcode()

Java 中的数据类型,可分为两类:

  1. 基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 它们之间的比较,应用双等号(==),比较的是它们的值。

  2. 复合数据类型(类)。
    当它们用双等号进行比较的时候,比较的是它们在内存中的存放地址,所以,除非是同一个 new 出来的对象,它们的比较后的结果为 true,否则比较后结果为 false。

也就是说

  • ==
    1. 如果比较的对象是基本数据类型,则比较值是否相等
    2. 如果比较的是引用数据类型,则比较的是对象的内存地址是否相等

因为Java只有值传递,所以对于==来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

  • equals()
    用来比较两个对象的内容是否相等

int和Integer

  1. int 是基本数据类型,Integer 是 int 的包装类,也叫做复合数据类型。
  2. Integer变量必须实例化后才能使用,而int变量不需要
  3. Integer的默认值是null,int的默认值是0;
    即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。

Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值

这条感觉和第二条是一个意思,

// 装箱
// 自动根据数值创建对应的 Integer对象,这就是装箱
// 动装箱时编译器就是通过调用valueOf()将原始类型值转换成对象
Integer i =10;

// 拆箱
// 将自动将包装器类型转换为基本数据类型
// 自动拆箱的时候编译器自动调用的是Integer的intValue()方法

Integer i = 10;  //装箱
int n = i;   //拆箱 
// Byte,Short,Integer,Long,Character,Boolean都实现了常量池技术,范围都和Integer一样
// float和double没有实现
public static void main(String[] args) {


            System.out.println("------------------一、int和Integer------------");


            System.out.println("--------------1、Integer对象使用new关键字生成-----------");

            Integer i = new Integer(100);
            Integer j = new Integer(100);
            System.out.println("i == j:" + (i == j));
            System.out.println("i.equals(j):" + (i.equals(j)));
            System.out.println("i.hashCode() == :" + i.hashCode());
            System.out.println("j.hashCode() == :" + j.hashCode());
            System.out.println("i,it's memory address:" + System.identityHashCode(i));
            System.out.println("j,it's memory address:" + System.identityHashCode(j));

            // false
            // true
            // 100
            // 100
            // 625576447
            // 1560911714

            // 正如上文提到的那样,复合数据类型使用双等号的时候是比较其在内存中的地址是否相同。
            // 一般而言,Object 的 hashCode()默认是返回内存地址的,
            //          在本例中直接输出对象的 hashCode 可以发现两者是一致的,那为什么==比较结果为 false呢?
            //          原因在于hashCode()可以重写,所以 hashCode()不能代表对象在内存的地址。
            //
            // System.identityHashCode(Object)方法可以得到对象的内存地址结果
            //          (严格意义上来讲,System.identityHashCode 的返回值和内存地址不相等的,
            //          该值是内存地址通过算法换算的一个整数值),不管该对象的类是否重写了 hashCode()方法。

            // Integer 类中关于 equals()方法和 hashCode()方法进行了重写,
            //          所以如果想比对内存地址的不同,需要使用System.identityHashCode(Object)方法。


            System.out.println("--------------2、表面上不是 new 关键字生成的 Integer 对象-----------");

            Integer a = 100;
            Integer b = 100;
            System.out.println("a == b:" + (a == b));
            System.out.println("a.equals(b):" + (a.equals(b)));
            System.out.println("a.hashCode():" + a.hashCode());
            System.out.println("b.hashCode():" + b.hashCode());
            System.out.println("a,it's memory address:" + System.identityHashCode(a));
            System.out.println("b,it's memory address:" + System.identityHashCode(b));

//            a == b:true
//            a.equals(b):true
//            a.hashCode():100
//            b.hashCode():100
//            a,it's memory address:939047783
//            b,it's memory address:939047783

            System.out.println("--------------2.1、这里就不得不提出另一种情况-----------");
            Integer ii = 128;
            Integer jj = 128;
            System.out.println("ii == jj:" + (ii == jj));
            System.out.println("ii.equals(jj):" + (ii.equals(jj)));
            System.out.println("ii.hashCode():" + ii.hashCode());
            System.out.println("jj.hashCode():" + jj.hashCode());
            System.out.println("ii,it's memory address:" + System.identityHashCode(ii));
            System.out.println("jj,it's memory address:" + System.identityHashCode(jj));


//            ii == jj:false
//            ii.equals(jj):true
//            ii.hashCode():128
//            jj.hashCode():128
//            ii,it's memory address:1237514926
//            jj,it's memory address:548246552

//            对于两个非 new 生成的 Integer 对象,进行比较时,如果两个变量的值在区间 -128 到 127 之间,
//                      则比较结果为 true,如果两个变量的值不在此区间,则比较结果为 false。
//                      通过打印出来的地址可以看出来,当不在指定区间范围时,实际上是两个不同的对象。
//
//             具体原因: Java 在编译 Integer i = 100 ;时,会翻译成为 Integer i = Integer.valueOf(100)。
//                      而 Java API 中对 Integer 类型的valueOf 的定义如下,对于-128 到 127 之间的数,会存储在缓存中,
//                      Integer i = 127 时,会直接从缓存中获取,下次再 Integer j = 127时,同样从缓存中取,而不会 new 个新对象。


            System.out.println("--------------3、两个 int 变量比较-----------");

            int c = 100;
            int d = 100;
            System.out.println("c == d:" + (c == d));
            System.out.println("c,it's memory address:" + System.identityHashCode(c));
            System.out.println("d,it's memory address:" + System.identityHashCode(d));

            //c == d:true
            //c,it's memory address:939047783
            //d,it's memory address:939047783

            // 对于这种简单数据类型,== 比较符就是比较它们的值大小


            System.out.println("--------------4、new 生成的 Integer 对象和 int 变量比较----------");


            Integer e = new Integer(100);
            int f = 100;
            System.out.println("e == f:" + (e == f));
            System.out.println("e.hashCode():" + e.hashCode());
            System.out.println("e,it's memory address:" + System.identityHashCode(e));
            System.out.println("f,it's memory address:" + System.identityHashCode(f));


            //e == f:true
            //e.hashCode():100
            //e,it's memory address:835648992
            //f,it's memory address:939047783

            // 基本数据类型 int 和它的包装类 Integer 比较时,Java 会自动拆包装为 int(将复合数据类型转化为基本数据类型),
            //          然后进行比较,实际上就变为两个 int 变量的比较。即使打印出来的地址不同,但是比较结果仍为 true,
            //          主要原因是因为不是通过比较内存地址进行判断的。


            System.out.println("--------------5、非 new 生成的 Integer 对象和 int 变量比较----------");

            int k = 100;
            Integer l = 100;
            System.out.println("k == l:" + (k == l));
            System.out.println("k,it's memory address:" + System.identityHashCode(k));
            System.out.println("l,it's memory address:" + System.identityHashCode(l));


            int m = 128;
            Integer n = 128;
            System.out.println("m == n:" + (m == n));
            System.out.println("m,it's memory address:" + System.identityHashCode(m));
            System.out.println("n,it's memory address:" + System.identityHashCode(n));


//            k == l:true
//            k,it's memory address:939047783
//            l,it's memory address:939047783
//            m == n:true
//            m,it's memory address:1134517053
//            n,it's memory address:492228202


            // 比较结果都为 true,因为同 4 一样,由于自动拆箱的特性,其实是进行值的比较,所以结果为 true。
            //          接着分析打印的内存地址,当在[-128,127]区间范围内时,Integer 数组(参考 Integer 类中的 IntegerCache)是存放在常量池中的,
            //          而 int 变量同样也是,所以值相等时,内存地址一致。


            System.out.println("--------------6、非 new 生成的 Integer 对象和 new Integer()生成的对象----------");


            Integer o = new Integer(100);
            Integer p = 100;
            System.out.print(o == p);

            // false
            // 因为非 new 生成的 Integer 变量指向的是 Java 常量池中的对象,而 new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同。


            System.out.println("--------------7、面试题----------");

            Integer i1 = 125;
            Integer i2 = 125;
            Integer i3 = 0;
            Integer i4 = new Integer(127);
            Integer i5 = new Integer(127);
            Integer i6 = new Integer(0);

            System.out.println("i1==i2:\t" + (i1 == i2));
            System.out.println("i1==i2+i3:\t" + (i1 == i2 + i3));
            System.out.println("i4==i5:\t" + (i4 == i5));
            System.out.println("i4==i5+i6:\t" + (i4 == i5 + i6));

            i3 = 5;
            Integer i7 = 130;
            System.out.println("i7==i2+i3:\t" + (i7 == i2 + i3));


//            i1==i2:	true
//            i1==i2+i3:	true
//            i4==i5:	false
//            i4==i5+i6:	true
//            i7==i2+i3:	true

            // 对于 i1 == i2 + i3 、 i4 == i5 + i6 和 i7 == i2 + i3 结果为 true,
            //          是因为,Java 的数学计算是在内存栈里操作的,Java 会对 i5、i6 进行拆箱操作,其实比较的是基本类型(127=127+0),
            //          他们的值相同,因此结果为 true。
            //          对 i2+i3 来说,结果是在内存栈中(同 int 基本类型一样),所以不管是与 i1 还是 i7 比较,返回结果都为 true。


    }

Double和double

// float与double一致
public static void main(String[] args) {


            System.out.println("------------------二、double 和 Double------------");


            System.out.println("--------------1、new 生成的两个 Double 对象比较-----------");

            Double i = new Double(100.0);
            Double j = new Double(100.0);

            System.out.println("i == j:" + (i == j));
            System.out.println("i.equals(j):" + (i.equals(j)));
            System.out.println("i.hashCode():" + i.hashCode());
            System.out.println("j.hashCode():" + j.hashCode());
            System.out.println("i,it's memory address:" + System.identityHashCode(i));
            System.out.println("j,it's memory address:" + System.identityHashCode(j));

            //i == j:false
            //i.equals(j):true
            //i.hashCode():1079574528
            //j.hashCode():1079574528
            //i,it's memory address:868693306
            //j,it's memory address:1746572565

            // Double 类源码可以发现,也重写了 equals 方法和 hashCode 方法




            System.out.println("--------------2、表面上非 new 生成的 Double 对象比较-----------");

            Double a = 100.0;
            Double b = 100.0;

            System.out.println("a == b:" + (a == b));
            System.out.println("a.equals(b):" + (a.equals(b)));
            System.out.println("a.hashCode():" + a.hashCode());
            System.out.println("b.hashCode():" + b.hashCode());
            System.out.println("a,it's memory address:" + System.identityHashCode(a));
            System.out.println("b,it's memory address:" + System.identityHashCode(b));

            //a == b:false
            //a.equals(b):true
            //a.hashCode():1079574528
            //b.hashCode():1079574528
            //a,it's memory address:989110044
            //b,it's memory address:424058530

            // 自动装箱,解析为 Double i = new Double(100.0);因此实际上还是两个不同的对象。


            System.out.println("--------------3、new 生成的 Double 对象和 double 变量比较-----------");
            Double c = 100.0;
            double d = 100.0;

            System.out.println("c == d:" + (c == d));
            System.out.println("c.equals(d):" + (c.equals(d)));
            System.out.println("c,it's memory address:" + System.identityHashCode(c));
            System.out.println("d,it's memory address:" + System.identityHashCode(d));

            //c == d:true
            //c.equals(d):true
            //c,it's memory address:321001045
            //d,it's memory address:791452441
            // 自动拆箱,转换为 double 变量进行值比较。

            
            
}

String

  • String中的equals方法是被重写过的,因为Object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。

  • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String对象。

public static void main(String[] args) {


            System.out.println("--------------1、引用指向常量池中 String 常量时比较-----------");

            String s1 = "abc";
            String s2 = "abc";

            System.out.println("s1 == s2:"+(s1 == s2));
            System.out.println("s1.equals(s2):"+s1.equals(s2));
            System.out.println(s1.hashCode());
            System.out.println(s2.hashCode());
            System.out.println("s1,it's memory address:" + System.identityHashCode(s1));
            System.out.println("s2,it's memory address:" + System.identityHashCode(s2));

            //s1 == s2:true
            //s1.equals(s2):true
            //96354
            //96354
            //s1,it's memory address:868693306
            //s2,it's memory address:868693306

            //首先说 s1 和 s2,在栈中开辟两块空间存放引用 s1 和 s2,在给 s1 赋值的时候去常量池中查找,
            //  第一次初始化的常量池为空的,所以是没有的,则在字符串常量池中开辟一块空间,存放 String 常量"abc",并把引用返回给 s1,
            //  当 s2 也是这样的过程,在常量池中找到了,所以 s1 和 s2 指向相同的引用,即 s1==s2 和 s1.equals(s2)都为 true。

            // 同时,String也重写了equals()和hashCode()





            System.out.println("--------------2、引用指向堆空间中 String 对象-----------");


            String s3 = new String("abc");
            String s4 = new String("abc");

            System.out.println("s3 == s4:"+(s3 == s4));
            System.out.println("s3.equals(s4):"+s3.equals(s4));
            System.out.println(s3.hashCode());
            System.out.println(s4.hashCode());
            System.out.println("s3,it's memory address:" + System.identityHashCode(s3));
            System.out.println("s4,it's memory address:" + System.identityHashCode(s4));

            //sb.toString()相当于生成一个新的String对象
            StringBuffer sb = new StringBuffer("abc");
            String s5 = sb.toString();
            System.out.println("s3 == s5:"+(s3 == s5));
            System.out.println("s5,it's memory address:" + System.identityHashCode(s5));

            //s3 == s4:false
            //s3.equals(s4):true
            //96354
            //96354
            //s3,it's memory address:1746572565
            //s4,it's memory address:989110044
            //s3 == s5:false
            //s5,it's memory address:424058530


            //首先在栈中开辟两块块空间存放引用 s1 和 s2,然后是创建两个对象,在创建对象的时候是在堆里面开辟了一个空间,两个对象自然地地址空间就不相同,
            //          这点从打印结果上就可以看出,所以在 s1==s2 是为 false。
            //          另外有时会使用到 StringBuffer 对象,再调用 toString() 方法,根据源码可知,该方法也是创建一个新的对象,所以 s1==s3结果为 false。

            // 只要是new,就是创建了一个新的对象



            System.out.println("--------------3、String 常量与 String 对象比较-----------");
            String s6 = "abc";
            String s7 = new String("abc");

            System.out.println("s6 == s7:"+(s6 == s7));
            System.out.println("s6.equals(s7):"+s6.equals(s7));
            System.out.println(s6.hashCode());
            System.out.println(s7.hashCode());
            System.out.println("s6,it's memory address:" + System.identityHashCode(s6));
            System.out.println("s7,it's memory address:" + System.identityHashCode(s7));

            String s8 = s7.intern();
            System.out.println("s6 == s8:"+(s6 == s8));
            System.out.println("s8,it's memory address:" + System.identityHashCode(s8));

            //s6 == s7:false
            //s6.equals(s7):true
            //96354
            //96354
            //s6,it's memory address:868693306
            //s7,it's memory address:321001045
            //s6 == s8:true
            //s8,it's memory address:868693306


            //s1 指向的是字符串常量池中的“abc”,s2 指向堆中的 String 对象“abc”,所以地址不相同,比较结果也就为 false。
            // 再者,String 类中的 intern()方法会从常量池中查找是否存在这样的值,如果存在则直接返回,不存在则往常量池中插入一个新的这样的值,然后返回。









            System.out.println("--------------4、String 常量做拼接操作后比较-----------");

            String q = "abc";
            //第一种情况
            String q2 = "ab";
            String q4 = q2 + "c";
            String q6 = new String("ab");
            String q7 = q6 + "c";
            System.out.println("q4,it's memory address:" + System.identityHashCode(q4));
            System.out.println("q7,it's memory address:" + System.identityHashCode(q7));
            System.out.println("q == q4:"+(q == q4));
            System.out.println("q == q7:"+(q == q7));


            //q4,it's memory address:791452441
            //q7,it's memory address:834600351
            //q == q4:false
            //q == q7:false
            // JVM 对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,
            // 而引用的值在程序编译期是无法确定的,即 s2+"c" 或 s6+"c"无法被编译器优化,
            // 只有在程序运行期来动态分配并将连接后的新地址赋给 s4 和 s7。所以上面程序的结果也就为 false。
            // 这段儿属实是有点不明白

Java中重写equals方法为什么要重写hashcode方法?

基本类型可以直接使用 == 比较
引用类型比较需要使用 Object.equals() 比较,但其底层依然是 == ,所以子类需要重写equals(),进而使其比较的是内容而不是地址值。

一般实体类中如果重写equals()时,也要重写hashCode();但只重写equales(),不重写hashCode()行不行?

不行,因为java规定如果两个对象内容相同hashCode一定相同,内容不同hashCode可能相同
所以,此时如果两个对象内容相同,只重写equals(),那么hashCode值就不一定相同了,就违反了java的规定。

因此,equals方法与hashCode方法根本就是配套使用的。对于任何一个对象,不论是使用继承自Object的equals方法还是重写equals方法。
hashCode方法实际上必须要完成的一件事情就是,为该equals方法认定为相同的对象返回相同的哈希值。如果只重写equals方法没有重写hashCode方法,就会导致equals认定相同的对象却拥有不同的哈希值。

使用hashCode是为了提高效率。在比较的时候,会先比较hashCode值,hashCode()返回值值是一个整数,效率高,如果hashCode值相等,再用equals来比较,不相等则直接返回,这已经能过滤一大批不相同的了。

String、StringBuffer、StringBuilder

Java 中操作字符串都有哪些类?它们之间有什么区别?

操作字符串的类有:String、StringBuffer、StringBuilder。

String特点:

  1. 字符串是常量,值在创建后不能更改,所以是线程安全的。
    但凡是感觉它要变化的时候,其实都是产生一个新的字符串。

  2. 因为String对象是不可变的,所以它们可以被共享。

  3. String类的底层采用的是字符数组(就有索引,0到长度-1)。

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的String对象,然后将指针指向新的 String 对象,
而 StringBuffer、StringBuilder可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而StringBuilder 是非线程安全的,
自然StringBuilder 的性能就高于StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用StringBuffer。

String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

String str2 = new String(“ABC”)创建了几个对象?

当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个字符串。然后再执行new操作,会在堆内存中创建一个存储“abc”的String对象,对象的引用赋值给str2。此过程创建了2个对象。

当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。

String s = "abc";
s += "d";
// abcd
// 内存中有"abc","abcd"两个对象,
// s开始指向"abc",被重新赋值后,又指向"abcd"
sout(s)


// 内存中只有一个abc对象被创建,同时被s和s2共享
String s = "abc";
String s2 = "abc";


String s = "abc";
// String底层是靠字符数组实现的
char[] data = {'a','b','c'}
String s = new String(data);

常用的构造方法

// 创建空的字符串对象,等价于""
String()

String(char[] value)

String(byte[] bytes)

静态方法

// 参数可以是基本类型
static String valueOf(Obj obj)

// 除Character之外,其他所有包装类都可以通过 parseXxx静态方法转换为对应的基本类型
static byte parseBytes();

常用的成员方法

判断功能

// 将此字符串与指定对象进行比较内容是否相同,区分大小写
boolean equals (Object anObject)
// 将此字符串与指定对象进行比较内容是否相同,忽略大小写
boolean equalsIgnoreCase (String anotherString)
// 当且仅当此字符串包含指定的str时,返回true
boolean contains(String str)
// 判断该字符串 是否以给定的字符串开头
boolean startsWith(String str)
// 判断该字符串 是否以给定的字符串结尾
boolean endsWith(String str)
// 判断该字符串的内容是否为空的字符串  "" 
boolean isEmpty() 

获取功能

// 获取该字符串的长度
int length()
// 返回字符串指定索引处的 char值
char charAt(int index)
// 从指定位置开始,到末尾结束,截取该字符串,返回新字符串
String substring(int start)
// 从指定位置开始,到指定位置结束,截取该字符串,返回新字符串,含beginIndex,不含endIndex
String substring(int start,int end)
// 获取给定的字符,在该字符串中第一次出现的位置
int indexOf(int ch )
// 获取给定的字符串,在该字符串中第一次出现的位置
int indexOf(String str)
// 从指定位置开始,获取给定的字符,在该字符串中第一次出现的位置
int indexOf(int ch,int fromIndex)
// 从指定位置开始,获取给定的字符串,在该字符串中第一次出现的位置
int indexOf(String str,int fromIndex)

转换功能

// 把该字符串 转换成 字节数组
byte[] getBytes() 
// 把该字符串 转换成 字符数组
char[] toCharArray()
// 把该字符串转换成 小写字符串
String toLowerCase()
// 把该字符串转换成 大写字符串
String toUpperCase() 
// 把该字符串与给定的字符串相连接,返回一个新的字符串	
String concat(String str)

替换功能

// 在该字符串中,将给定的旧字符,用新字符替换
String replace(char old,char new)
// 在该字符串中, 将给定的旧字符串,用新字符串替换
String replace(String old,String new)
// 去除字符串两端空格,中间的不会去除,返回一个新字符串
String trim()
// 将此字符串按照给定的regex(规则)拆分为字符串数组
String[] split(String regex) 
    注意:
        切割标准如果是句点.,要写成\\.
        切割标准如果是|,要写成\\|
	
	因为 . 是一个正则表达式,代表任意字符;|在正则表达式中表示或者。

StringBuffer/StringBuilder

String类,内容一旦确定,不可改变;效率低。

StringBuilder类,字符串缓冲区,代表可以改变的字符串序列
底层默认是长度为16的字符数组;拼接字符串效率高

字符数组会自动扩容,长度为原数组的 2n+2,扩容方式为把原有数组的内容,复制到新数组中

构造方法

// 创建一个字符串缓冲区,默认容量大小16
public StringBuffer()
// 创建一个字符串缓冲区,指定默认内容为 str 的内容,长度为 字符串str的长度 + 16
public StringBuffer(String str) 

成员方法

//在原有字符串缓冲区内容基础上,在末尾追加新数据
public StringBuffer append(String str) 
// 在原有字符串缓冲区内容基础上,在指定位置插入新数据
public StringBuffer insert(int offset,String str)
// 在原有字符串缓冲区内容基础上,删除指定位置上的字符
public StringBuffer deleteCharAt(int index)
// 在原有字符串缓冲区内容基础上,删除指定范围内的多个字符
public StringBuffer delete(int start,int end)
// 在原有字符串缓冲区内容基础上,将指定范围内的多个字符 用给定的字符串替换
public StringBuffer replace(int start,int end,String str)
// 将字符串缓冲区的内容 反转  "abc"----"cba"
public StringBuffer reverse()
// 从指定位置开始,到末尾结束,截取该字符串缓冲区,返回新字符串
public String substring(int start) 
// 从指定位置开始,到指定位置结束,截取该字符串缓冲区,返回新字符串
public String substring(int start,int end)

break/continue

  • break 用于完全结束一个循环,跳出循环体,执行循环后面的语句。
  • continue 跳过循环体中剩余的语句,直接进入下一次循环的执行。

介绍一下UUID

  • UUID 由 32 个十六进制数字组成,通常表示为 8-4-4-4-12 的格式。例如:550e8400-e29b-41d4-a716-446655440000。
  • 它包含了时间戳、时钟序列、节点标识符等信息,具体的结构取决于 UUID 的版本和变体。

部分知识引用自:
https://baijiahao.baidu.com/s?id=1614769568984126810&wfr=spider&for=pc
https://www.nowcoder.com/questionTerminal/a90230b35b5f4a7287f779ecdd88841d
https://blog.csdn.net/u010945925/article/details/9050917
https://blog.csdn.net/u014745069/article/details/86649062
https://blog.csdn.net/Ryice/article/details/87608851
https://zhuanlan.zhihu.com/p/107733987

在农村讲知识,是过不了日子的。

一位左右为难的一分钱都没有的母亲。

胡歌

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐