1.补充:final修饰的数据存储在哪里?
数据存储在内存的不同区域,主要分别是:
堆区heap——凡是new出来的都在堆中,
栈区stack——存储引用、压入方法
静态区static storage
常量区constant storage
被final修饰的变量会变成常量,所以是在常量区。常量区位于堆区
2.Object类
概述:类层次结构的根类,所有类都直接或者间接的继承自该类
Object类中的常用方法:
hashCode()方法:
public int hashCode():
(1)返回该对象的哈希码值,默认情况下,该方法会根据对象的地址来计算
(2)不通用对象的hashCode()一般来说不同,但是,同一个对象的hashCode()肯定相同
(3)不是对象的实际地址值,可以理解为逻辑地址值
getClass()方法:
public final Class getClass()
(1)返回此对象的运行时类
(2)可以通过Class类中的一个方法,获取对象的真实类的全名称.
public String getName()——暂时学这一个,以后再详解
toString()方法 :
public String toString()
(1)返回该对象的字符串表示
(2)它的值等于:getClass().getName()+"@"+Integer.toHexString(hashCode())
左边:类名 中间:@ 右边:hashCode的十六进制表现形式
(3)由于默认情况下这么写对我们来说没有意义,一般建议重写该方法
可以自动生成;如果直接打印对象的引用,会默认调用该对象的toString()方法
equals()方法
(1)指示其他某个对象是否与此都对象"相等",返回值是boolean类型
(2)默认情况下比较的是对象的引用是否相同
(3)Object中的equals方法是比较对象的地址值的,由于比较对象的引用没有意义,因为开发中我们通常比较的是对象中的属性值,我们认为相同属性是同一个对象, 一般建议重写该方法
补充:
==和equals的区别:
相同点:都可以做比较,返回值都是boolean类型
不同点:①==号是比较运算符,既可以比较基本数据类型,也可以比较引用数据类型.基本数据类型比较的是值,引用数据类型比较的是地址值
②equals方法在没重写前,比较的是地址值,底层依赖的是==号,但是比较地址值是没有意义的,我们需要重写该方法,比较对象中的属性值
3.Scanner类
A: Scanner的概述
B: Scanner的构造方法原理
Scanner(InputStream source)
System类下有一个静态的字段:
public static final InputStream in;标准的输入流,对应着键盘录入
C:一般方法
hasNextXxx() 判断是否还有下一个输入项,其中xxx可以是Int,Double等。如果需要判断是否包含下一个字符串,则可以省略Xxx
nextxxx() 获取下一个输入项。Xxx的含义和上个方法中的Xxx相同,默认情况下, Scanner使用空格,回车等作为分隔符
两个常用的方法:
public int nextInt() : 获取一个int类型的值
public String nextLine() : 获取一个 String类型的值
4.String类——字符串
字符串字面值"abc"也可以看成是一个字符串对象
字符串是常量,一旦被赋值,就不能被改变
常见构造方法:
public String():空构造
public String(byte[] bytes):把字节数组转成字符串
public String(byte[] bytes,int index,int length):把字节数组的一部分转成字符串
public String(char[] value):把字符数组转成字符串
public String(char[] value, int index, int count):把字符数组的一部分转成字符串
public String(String original):把字符串常量值转成字符串
对于字符串要铭记:String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
补充:
字符串常量池:字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多.JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
几个例子:
(1)public void test1(){
String str1="123";
String str2="123";
System.out.println(str1==str2);
}
上述结果为:true。
当执行String str1="123"时,JVM首先会去字符串池中查找是否存在"123"这个对象,如果不存在,则在字符串池中创建"123"这个对象,然后将池中"123"这个对象的引用地址返回给字符串常量str1,这样str1会指向池中"123"这个字符串对象;如果存在,则不创建任何对象,直接将池中"123"这个对象的地址返回,赋给字符串常量。当创建字符串对象str2时,字符串池中已经存在"123"这个对象,直接把对象"123"的引用地址返回给str2,这样str2指向了池中"123"这个对象,也就是说str1和str2指向了同一个对象,因此结果输出:true。
(2)public void test2(){
String str3=new String("123");
String str4=new String("123");
System.out.println(str3==str4);}
上述结果为:false。
采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"123"这个字符串对象,如果有,则不在池中再去创建"123"这个对象了,直接在堆中创建一个"123"字符串对象,然后将堆中的这个"123"对象的地址返回赋给引用str3,这样,str3就指向了堆中创建的这个"123"字符串对象;如果没有,则首先在字符串池中创建一个"123"字符串对象,然后再在堆中创建一个"123"字符串对象,然后将堆中这个"123"字符串对象的地址返回赋给str3引用,这样,str3指向了堆中创建的这个"123"字符串对象。当执行String str4=new String("123")时, 因为采用new关键字创建对象时,每次new出来的都是一个新的对象,也即是说引用str3和str4指向的是两个不同的对象,因此输出:false。
(3)public void test3(){
String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println(s0==s1);
System.out.println(s0==s2);
}
上述结果为:true、true。因为例子中的s0和s1中的"helloworld”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而"hello”和"world”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的一个引用。所以我们得出s0==s1==s2。)
(4)“ String s1=new String(“abc”)”语句创建了几个对象?
该语句首先String s1是声明,new String(“abc”)先在常量池中查找,若没有则创建“abc”,而后通过new在堆内存中创建对象,把“abc”拷贝赋值。String 定义为初始化一个新创建的 String 对象,表示一个与该参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的一个副本。故创建常量池和堆内存中两个对象,两个对象的地址值不一样。
(5)public void test5(){
String str1="abc";
String str2="def";
String str3=str1+str2;
System.out.println(str3=="abcdef"); //false}
上述结果为:false。
因为str3指向堆中的"abcdef"对象,而"abcdef"是字符串池中的对象,所以结果为false。JVM对String str="abc"对象放在常量池中是在编译时做的,而String str3=str1+str2是在运行时刻才能知道的。new对象也是在运行时才做的。而这段代码总共创建了5个对象,字符串池中两个、堆中三个。+运算符会在堆中建立来两个String对象,这两个对象的值分别是"abc"和"def",也就是说从字符串池中复制这两个值,然后在堆中创建两个对象,然后再建立对象str3,然后将"abcdef"的堆地址赋给str3。
步骤:
1)栈中开辟一块中间存放引用str1,str1指向池中String常量"abc"。
2)栈中开辟一块中间存放引用str2,str2指向池中String常量"def"。
3)栈中开辟一块中间存放引用str3。
4)str1 + str2通过StringBuilder的最后一步toString()方法还原一个新的String对象"abcdef",因此堆中开辟一块空间存放此对象。
5)引用str3指向堆中(str1 + str2)所还原的新String对象。
6)str3指向的对象在堆中,而常量"abcdef"在池中,输出为false。
(6)比较字符串常量的“+”和字符串引用的“+”的区别
public void test8(){
String test="javalanguagespecification";
String str="java";
String str1="language";
String str2="specification";
System.out.println(test == "java" + "language" + "specification");
System.out.println(test == str + str1 + str2);
}
上述结果为:true、false。
分析:为什么出现上面的结果呢?这是因为,字符串字面量拼接操作是在Java编译器编译期间就执行了,也就是说编译器编译时,直接把"java"、"language"和"specification"这三个字面量进行"+"操作得到一个"javalanguagespecification" 常量,并且直接将这个常量放入字符串池中,这样做实际上是一种优化,将3个字面量合成一个,避免了创建多余的字符串对象。而字符串引用的"+"运算是在Java运行期间执行的,即str + str2 + str3在程序执行期间才会进行计算,它会在堆内存中重新创建一个拼接后的字符串对象。总结来说就是:字面量"+"拼接是在编译期间进行的,拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接运算实在运行时进行的,新创建的字符串存放在堆中。
对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。
5.String类的判断功能
boolean equals(Object obj):比较字符串的内容是否相同,区分大小写
boolean equalsIgnoreCase( String str):比较字符串的内容是否相同,忽略大小写
boolean contains( String str):判断大字符串中是否包含小字符串
boolean startsWith( String str):判断字符串是否以某个指定的字符串开头
boolean endsWith( String str):判断字符串是否以某个指定的字符串结尾
boolean isEmpty():判断字符串是否为空
补充:
""和null的区别
""是字符串常量,同时也是一个 string类的对象,既然是对象当然可以调用 string类中的方法null是空常量,不能调用任何的方法,否则会出现空指针异常,null常量可以给任薏的引用数据类型赋值