基本类型与对象
基本类型与对象说明
基本类型又叫内置数据类型,对象又叫引用数据类型。
基本类型共8种,其他的都是对象类型
java基本类型
基本类型共8种,需要牢记:
- byte数据类型是8位、有符号的,以二进制补码表示的整数
- short数据类型是16位、有符号的以二进制补码表示的整数
- int数据类型是32位、有符号的以二进制补码表示的整数
- long数据类型是64位、有符号的以二进制补码表示的整数
- float数据类型是单精度、32位、符合IEEE 754标准的浮点数
- double数据类型是双精度、64位、符合IEEE 754标准的浮点数
- boolean数据类型表示一位的信息
- char类型是一个单一的16位Unicode字符
注意:以上的位说的是b,实际说的时候一般都用B,所以也有说法byte字节类型占用1个字节(或者位置)
在计算机中,整数型使用二进制方式表示:而每一个整数型的第一个二进制都是作为正负符号。 0=正 1=负
所以 byte(8位){-2^7, 2^(7)-1},即10000000-0111111,-128到127,这个必须牢记
short{-2^15, 2^15-1},即-32768~32767
int{-2^31, 2^31-1}
long{-2^63, 2^63-1}
此处可以等后续源码、反码和补码学习后进一步加深理解
基本类型和对象类型详解
- 基本类型存储了实际的数值,而并非只是一个对象的引用,所以在为其赋值的时候,是直接将一个地方的内容复制到了另一个地方。例如,对基本数据类型使用a=b,那么b的内容就复制给a.若接着又修改了a,而b根本不会受这种修改的影响。
- 对一个对象赋值时,真正操作的是这个对象的引用。所以倘若“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方。这意味着假若对对象使用c=d,那么c和d都指向原本只有d指向的那个对象
- 在栈中可以直接分配内存的数据是基本数据类型;引用数据类型:是数据的引用在栈中,但是对象在堆中
equals和==的区别
- 对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址
- 对于equals方法,注意:equals方法不能作用于基本数据类型的变量(基本数据类型不是对象,并没有equals方法)
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;诸如String、Date、Integer等类对equals方法进行了重写,比较的是所指向的对象的内容。
示例代码
public static void main(String[] args) {
// TODO Auto-generated method stub
int n=3;
int m=3;
System.out.println(n==m); //基本类型值比较,true
String str = new String("hello");
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1==str2); //对象引用地址比较,false
str1 = str;
str2 = str;
System.out.println(str1==str2); //对象赋值后引用地址比较,true
System.out.println(str1.equals(str2));//String的equals方法重写了,比较的是值,true
StringBuffer sb = new StringBuffer("123");
StringBuffer test = new StringBuffer("123");
System.out.println(sb == test); //对象引用地址比较,false
System.out.println(sb.equals(test));//StringBuffer的equals方法没有重写,比较内存地址,false
}
String说明
String对象有很多独特的属性,是面试题里面经常考的部分
String不属于8种基本数据类型,String是一个对象,因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性
new String()和new String(“”)都是申明一个新的空字符串,是空串不是null
String str=”kvill”; String str=new String (“kvill”);的区别
在这里,我们先不谈堆和栈,只先简单引入常量池这个简单的概念。常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。参照如下代码:
public static void main(String[] args) {
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
//String直接定义常量,jvm虚拟机会把对应的值放在常量池中,只保留一份,所以s0和s1的内存地址同一份
//s2是编译时候可以明确的,所以s2的值也会作为"kvill"存在常量池中
System.out.println( s0==s1 ); //返回true
System.out.println( s0==s2 ); //返回true
}
用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间,参照如下代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
String s0 = "kvill"; //常量池
String s1 = new String("kvill"); //new String方式,是对象,在堆中
String s2 = "kv" + new String("ill");//new String("ill")是对象,编译期不能确定,所以s2是对象,在堆中
System.out.println(s0 == s1);//false
System.out.println(s0 == s2);//false
System.out.println(s1 == s2);//false
}
详细解释
字面量创建
- 如果字符串常量池中不存在字符串对象的引用,在堆中创建字符串对象,将字符串对象的引用保存在字符串常量池中;
- 如果字符串常量池中存在字符串对象的引用,直接返回字符串常量池中字符串对象的引用
new String()创建
- 在堆上创建未初始化的string对象
- 如果字符串常量池中不存在字符串对象的引用,在堆中创建字符串对象,将字符串对象的引用保存在字符串常量池中;
- 如果字符串常量池中存在字符串对象的引用,直接返回字符串常量池中字符串对象的引用
- string对象赋值
注意
- 在类加载阶段, JVM会在堆中创建对应这些 class 文件常量池中的字符串对象实例 ,并在字符串常量池中驻留其引用。具体在resolve阶段执行。这些常量全局共享。而resolve阶段是懒加载的,所以只会字面量进入Class的常量池,不会在堆上创建实例,更不会驻留字符串常量池;具体在ldc是才会进入。所以我们只需要注意字面量是否重复出现及是否调用intern()方法即可。
- jdk6的字符串常量池实现并不相同,并不能使用对堆对象的引用驻留来复用字符串常量。
String.intern()方法
存在于class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:
- 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
- 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
public static void main(String[] args) {
// TODO Auto-generated method stub
String s0= "kvill";
String s1=new String("kvill");
String s2=new String("kvill");
System.out.println( s0==s1 );
System.out.println( "**********" );
s1.intern(); //s1调用intern方法后,因为没有赋值,所以没有改变引用
s2=s2.intern(); //把常量池中"kvill"的引用赋给s2
System.out.println( s0==s1); //false
System.out.println( s0==s1.intern() ); //true
System.out.println( s0==s2 ); //true
}
注意:不是“将自己的地址注册到常量池中”了,而是在常量池中增加一个Unicode等于str的字符串并返回它的引用,如下代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() ); //false
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() ); //true
}
String的split方法在String很大的时候有性能瓶颈,所以如果把String按照某种分隔符拆分成String[]数组时,需要注意String的大小。
关于String是不可变的
String
真正不可变有下面几点原因:
- 保存字符串的数组被
final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。String
类被final
修饰导致其不能被继承,进而避免了子类破坏String
不可变。
String内部是private final char value[]; 因此String的实例一旦生成就不会再改变了,即修改String的值会产生临时变量。
所以代码中如果需要经常修改String的值,建议用StringBuffer 或者StringBuilder。