JavaSE基础—数据类型和String
前言
本节内容主要讲的是对Java基础部分的数据类型和String类出现的典型的面试问题进行笔记存储。并且希望能够帮助到知识储备。
1、数据类型
基本的数据类型
基本的数据类型比较典型,这些写基本类型在我们生活中伴随左右。接下来来整理这些数据类型
类型名称 | 关键字 | 占用内存 | 取值范围 |
---|---|---|---|
字节型 | byte | 1 字节 | -128~127 |
短整型 | short | 2 字节 | -32768~32767 |
整型 | int | 4 字节 | -2147483648~2147483647 |
长整型 | long | 8 字节 | -9223372036854775808L~9223372036854775807L |
单精度浮点型 | float | 4 字节 | +/-3.4E+38F(6~7 个有效位) |
双精度浮点型 | double | 8 字节 | +/-1.8E+308 (15 个有效位) |
字符型 | char | 2 字节 | ISO 单一字符集 |
布尔型 | boolean | 1 字节 | true 或 false |
而且所有基本数据类型的大小(所占用的字节数)都是明确规定并且在各种不同的平台上保持不变。这一特性也是有助于提高 Java 程序的可移植性。
布尔类型
在基本数据类型其中,boolean比较特别,所以我单独的把这个内容提取出。在已知的了解中它只有两个值:true、false。并且占用字节较少。可以使用 1 bit 来存储,但是具体大小没有明确规定。
前面的表格好像写过是占1字节,我们分别说明一下:
- 我们首先在编译时,那是因为一般的boolean只有true和false,在编译后是使用0和1来表示的。此时这两个数只需要1byte即可存储,即计算机最小的存储单位。
- 在进行计算机处理数据时,因为我们的处理数据最小单位是1字节,所以我们会在这里将其以二进制数表达,即00000001,反之就是00000000。
比如在实际算法组中我们要对布尔类型进行一定的了解,才能够完成基本的数据处理的内容。比如我自己遇到的基于运算符的短路特性来完成算法实现:
public class TrueOrFalse {
static boolean f1(){
return false;
}
static boolean f2(){
return true;
}
static boolean f3(){
return true;
}
public static void main(String[] args) {
boolean result=TrueOrFalse.f1()||TrueOrFalse.f2()||TrueOrFalse.f3();
boolean result1=TrueOrFalse.f1()&&TrueOrFalse.f2()&&TrueOrFalse.f3();
System.out.println(result+" and "+result1);
}
}
这里的结果是"True and false"。比如在||运算符中,我们只要找到一个True,那么就形成“其他的结果微乎其微”的效果;&&同理就只找一个false就可以决定一个数据结果。并且在result1中,由于我们放置的f1在前面,此时就不需要判断f2和f3的内容。这种特性可以运用在算法中。
类型的包装与缓存值的概念
基本类型都会有对应的包装。比如我们遇到的String类型等等,除此之外还有Integer也比较有趣。
Integer x = 2; // 装箱 调用了 Integer.valueOf(2)
int y = x; // 拆箱 调用了 X.intValue()
但是我们或多或少感受到了不同的执行内容所产生的处理模式也不相同。那么接下来我们去看看这个问题:
new Integer(123) 与 Integer.valueOf(123)的区别是什么?
- new Integer(123) 每次都会新建一个对象;
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
比如如下例子:
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
首先我们在调用valueOf()方法时就是先判断是否在缓存值里,如果在就直接返回缓存值里的内容。
2、String类型
2.1 简述
除此之外,我们会经常遇到类似String str ="abc"和String str = new String(“abc”);之类的问题。但是前面基本提及过类似的内容,所以就直接介绍String类型本身。
String类型被声明为 final,因此它不可被继承。并且在Java8中内部使用 char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder
来标识使用了哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
无论是前者还是后者,都意味着value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
2.2 String类型不可变的好处
1. 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2. String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。比如如下
String str1="abc";
String str2="abc";
此时因为引用str1和str2都是指向常量池的一个对象"abc",如果String是可变类,那么就会引发后者对前者的修改。
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
2.3 String, StringBuffer and StringBuilder
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
2.4 String堆
字符串常量值保存着字符串的字面量。这些字面量可以通过String的intern()方法将字符串到String Pool中。
什么意思呢?
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4); // true
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等已经使用 equals() 方法进行确定,那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
关于Java对String字符串的处理,在Java7前是放在运行时常量池,但是其空间有限,所以会在大量使用字符的场景会出现报错——导致 OutOfMemoryError 错误。
2.5 new String(“abc”)
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
- “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
- 而使用 new 的方式会在堆中创建一个字符串对象。
3、运算
3.1 参数传递
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
以下代码中 Car car 的 car 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。
public class car {
String name;
car(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
值传递就是以一个值作为一个函数的实参,而引用传递传递的是一个变量。
class PassByValueExample {
public static void main(String[] args) {
Car car = new Car("A");
func(Car);
System.out.println(car.getName()); // B
}
private static void func(Car car) {
car.setName("B");
}
}
很明显这里就是承上启下的过程,因为我们改变了值的内容,以另一个指针改变了当前指针所涵盖的值,以致于在当前指针尝试输出就发生了变化。
3.2 转型
默认时,我们需要的转型是为了提高我们的精度,比如double不能向下转型成float,比如如下:
float f = 1.5;
正确做法应该是:
float f = 1.5f
此时的1.5f字面量才是float类型。
再来,比如一个整型int 1,因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。
比如如下:
short s1 = 1;
// s1 = s1 + 1;
s1 += 1;
s1++;
//使用 += 或者 ++ 运算符会执行隐式类型转换。
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
总结
今天的内容结合一些资料的内容和个人自身的理解,谢谢阅读。