Java 数据类型
变量就是申请内存来存储的值,当创建变量的时候,需要在内存中申请空间,内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来存储该类型的数据;
Java 的两大数据类型:
- 基本数据类型
- 引用数据类型
基本数据类型
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
byte
- byte 数据类型是8位的、有符号的、以二进制补码形式表示的整数;
- 最小值是128(-2^7);
- 最大值是 127(2^7-1);
- 默认值是 0;
- byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:byte a = 100,byte b = -50。
short:
- short 数据类型是 16 位、有符号的以二进制补码表示的整数
- 最小值是 -32768(-2^15);
- 最大值是 32767(2^15 - 1);
- Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是 0;
- 例子:short s = 1000,short r = -20000;
int
- int 数据类型是32位、有符号的以二进制补码表示的整数;
- 最小值是 -2,147,483,648(-2^31);
- 最大值是 2,147,483,647(2^31 - 1);
- 一般地整型变量默认为 int 类型;
- 默认值是 0 ;
- 例子:int a = 100000, int b = -200000;
float
- long 数据类型是 64 位、有符号的以二进制补码表示的整数;
- 最小值是 -9,223,372,036,854,775,808(-2^63);
- 最大值是 9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是 0L;
- 例子: long a = 100000L,Long b = -200000L。 "L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写;
double
- double 数据类型是双精度、64 位的浮点数;
- 浮点数的默认类型为double类型;
- double类型同样不能表示精确的值,如货币;
- 默认值是 0.0d;
- 例子:double d1 = 123.4;
boolean:
- boolean数据类型表示一位的信息;
- 只有两个取值:true 和 false;
- 这种类型只作为一种标志来记录 true/false 情况;
- 默认值是 false;
- 例子:boolean one = true。
char:
- char类型是一个单一的 16 位 Unicode 字符;
- 最小值是 \u0000(即为0);
- 最大值是 \uffff(即为65,535);
- char 数据类型可以储存任何字符;
- 例子:char letter = 'A' ;
实例
基本类型的包装类和最值
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
public static void main(String[] args) {
// byte
System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);
System.out.println("包装类:java.lang.Byte");
System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);
System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);
System.out.println();
// short
System.out.println("基本类型:short 二进制位数:" + Short.SIZE);
System.out.println("包装类:java.lang.Short");
System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE);
System.out.println();
// int
System.out.println("基本类型:int 二进制位数:" + Integer.SIZE);
System.out.println("包装类:java.lang.Integer");
System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);
System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);
System.out.println();
// long
System.out.println("基本类型:long 二进制位数:" + Long.SIZE);
System.out.println("包装类:java.lang.Long");
System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE);
System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE);
System.out.println();
// float
System.out.println("基本类型:float 二进制位数:" + Float.SIZE);
System.out.println("包装类:java.lang.Float");
System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE);
System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE);
System.out.println();
// double
System.out.println("基本类型:double 二进制位数:" + Double.SIZE);
System.out.println("包装类:java.lang.Double");
System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE);
System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE);
System.out.println();
// char
System.out.println("基本类型:char 二进制位数:" + Character.SIZE);
System.out.println("包装类:java.lang.Character");
// 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台
System.out.println("最小值:Character.MIN_VALUE=" + (int) Character.MIN_VALUE);
// 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台
System.out.println("最大值:Character.MAX_VALUE=" + (int) Character.MAX_VALUE);
}
}
运行结果如下:
基本类型:byte 二进制位数:8
包装类:java.lang.Byte 最小值:Byte.MIN_VALUE=-128 最大值:Byte.MAX_VALUE=127基本类型:short 二进制位数:16 包装类:java.lang.Short 最小值:Short.MIN_VALUE=-32768 最大值:Short.MAX_VALUE=32767
基本类型:int 二进制位数:32 包装类:java.lang.Integer 最小值:Integer.MIN_VALUE=-2147483648 最大值:Integer.MAX_VALUE=2147483647
基本类型:long 二进制位数:64 包装类:java.lang.Long 最小值:Long.MIN_VALUE=-9223372036854775808 最大值:Long.MAX_VALUE=9223372036854775807
基本类型:float 二进制位数:32 包装类:java.lang.Float 最小值:Float.MIN_VALUE=1.4E-45 最大值:Float.MAX_VALUE=3.4028235E38
基本类型:double 二进制位数:64 包装类:java.lang.Double 最小值:Double.MIN_VALUE=4.9E-324 最大值:Double.MAX_VALUE=1.7976931348623157E308
基本类型:char 二进制位数:16 包装类:java.lang.Character 最小值:Character.MIN_VALUE=0 最大值:Character.MAX_VALUE=65535
Process finished with exit code 0
类型的默认值
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
static boolean bool;
static byte by;
static char ch;
static double d;
static float f;
static int i;
static long l;
static short sh;
static String str;
public static void main(String[] args) {
System.out.println("Bool :" + bool);
System.out.println("Byte :" + by);
System.out.println("Character:" + ch);
System.out.println("Double :" + d);
System.out.println("Float :" + f);
System.out.println("Integer :" + i);
System.out.println("Long :" + l);
System.out.println("Short :" + sh);
System.out.println("String :" + str);
}
}
运行结果:
Bool :false
Byte :0
Character:
Double :0.0
Float :0.0
Integer :0
Long :0
Short :0
String :nullProcess finished with exit code 0
引用类型
什么是引用类型?
引用其实就像一个对象的名字或者别名,一个对象在内存中会请求一块空间(堆区 heap)来保存数据,根据对象的大小,它可能占用的空间大小也不相等。访问对象的时候,我们也不会直接访问对象放在内存中的数据,而是通过引用去访问,引用也可以认为是一种数据结构,存放在栈 stack 中,它指示了对象在内存(堆区 heap)中的地址;
下面看下两个案例:
-
String a = "hello kingshion"; String b = a;a 和 b 是两个不同的引用,但它们所指向的堆中的对象是同一个,所以它们的值是一样的;
-
String a = "hello"; String b = "world"; b = a;需要注意的是:String 对象的值本身是不可改变的,(底层为final修饰)那么是如何做到将
b = a ;的呢?这种情况下是通过改变 它的引用 b 的值,使之指向了另一个 String 对象 a ;
如图:一开始 a 指向堆区的 hello ,b 指向 堆区的 world ;然后通过改变引用,使 b 指向了 a 所指的 hello ;
引用类型的特征
- 对象、数组都是引用数据类型。
- 所有引用类型的默认值都是null。
- 一个引用变量可以用来引用任何与之兼容的类型。
- 例子:Object object = new Object ("Jinjx");
对象是如何传递的
基本类型是按值传递的
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
//交换两个变量的值
public static void Swap(int a,int b){
int c=a;
a=b;
b=c;
System.out.println("a: "+a);
System.out.println("b: "+b);
}
public static void main(String[] args){
int c=10;
int d=20;
Swap(c,d);
System.out.println("After Swap:");
System.out.println("c: "+c);
System.out.println("d: "+d);
}
}
运行结果:
a: 20 b: 10 After Swap: c: 10 d: 20
Process finished with exit code 0
发现传值后数值交换失败,主方法中打印出来的 c 和 d 仍然没有变化;具体原因如下:
在main 方法运行过程中Java 虚拟机会为 main 线程开辟一个栈空间,当 main 方法调用 swap 方法的时候新增一个栈帧,然后在该栈帧中会为swap 函数分配内存,创建属于自己的 a 、b 变量,不过当 swap 函数运行结束时,栈帧的内存被释放,内部的变量 a、b 也会跟着一起被释放;
虽然在打印的结果中能够看出形参的改变,但是实参并没有任何影响,实际上就是将参数的值做了一个拷贝传进 swap 函数的,那么在 swap 方法里面无论做什么改变都无法实际参数;
引用类型的都是引用传递
引用类型中有一种比较特殊的引用传递: String 类型的,考虑到 String
类型的底层是使用 final 修饰的(不可改变),以及 JVM 字符串常量池的存在;
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
public static void main(String[] args) {
String a = new String("hello"); //结果是hello world hello
swap(a);
System.out.println(a);
}
public static void swap(String a) {
a += " world";
System.out.println(a);
}
}
运行结果:
hello world
hello
新建一个字符对象,调用swap 方法后使用 引用传递,发现 a 不能在后面继续追加 world ,所以只能在常量池中新建一个字符常量,然后将 swap 中的 a 引用指向新建的常量;然而 main 方法中的 a 引用仍然是指向 hello 的;
一般常规的引用传递是这样的:
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
public static void main(String[] args) {
int a[] = {1, 2, 3, 3, 3};
change(a, 2);
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");
}
}
public static void change(int[] a, int i) {
a[i] = 10;
}
}
Java的四种引用类型
引用与对象
每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”。 在 Java 中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(reference)。
//创建一个引用,引用可以独立存在,并不一定需要与一个对象关联 String s;
通过将这个叫“引用”的标识符指向某个对象,之后便可以通过这个引用来实现操作对象了。
String str = new String("abc"); System.out.println(str.toString());
在 JDK1.2 之前,Java中的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。
Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。 在不同垃圾回收算法中,对引用的判断方式有所不同:
-
引用计数法:为每个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加1,当引用失效时,计数器就减1,当计数器为0时,则认为该对象可以被回收(目前在Java中已经弃用这种方式了)。
-
可达性分析算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。
四种引用类型
所以在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:
- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
这 4 种引用的强度依次减弱;
1. 强引用
Java中默认声明的就是强引用,比如:
Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
obj = null; //手动置null
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了;
2. 软引用
软引用是用来描述一些非必需但仍有用的对象,在内存足够的时候,软引用对象不会被回收,只有 在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等等;
在JDK1.2 以后,用 java.lang.ref.SoftReference 类来表示软引用;
下面以一个例子来进一步说明下强引用和软引用的区别:
在运行下面的Java代码之前需要先配置参数: -Xms2M -Xmx3M 将JVM 的初始内存设为2M,最大可用内存为 3M。
首先来测试一下强引用,在限制了 JVM 内存的前提下,下面的代码运行正常:
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
public static void main(String[] args) {
testStrongReference();
}
private static void testStrongReference() {
// 当 new byte为 1M 时,程序运行正常
byte[] buff = new byte[1024 * 1024 * 1];
}
}
但是如果将程序的
byte[] buff = new byte[1024 * 1024 * 1];
修改为:(替换为创建一个大小为 3M 的字节数组)
byte[] buff = new byte[1024 * 1024 * 3];
则内存不够使用,程序直接报错,强引用并不会被回收;
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
接下来我们看一下软引用会有什么不一样的地方,在下面的示例中连续创建10 个大小为1M的字节数组,并赋值给软引用,然后循环遍历这些对象并打印出来。
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
testSoftReference();
}
private static void testSoftReference() {
for (int i = 0; i < 10; i++) {
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> softReference = new SoftReference<byte[]>(buff);
list.add(softReference);
}
System.gc(); //主动通知垃圾回收
for (int i = 0; i < list.size(); i++) {
Object obj = ((SoftReference) list.get(i)).get();
System.out.println(obj);
}
}
}
打印结果:
null
null
null
null
null
null
null
null
null
[B@1540e19dProcess finished with exit code 0
能够发现无论循环创建多少个软引用对象,打印结果总是只有最后一个对象呗保留,其余的obj 都被置空回收了。
这就说明了在内存不足的情况下,软引用将会被自动回收。
值得注意的一点,即使 byte[] buff 数组引用指向对象,而且 buff 是一个强引用类型,但是 软引用类型的 softreference 指向的对象仍然被回收了,这是因为 Java 编译器发现在在之后的代码中,buff已经没有被使用了,所以自动进行了优化。
如果我们将上面的代码进行一些修改:
private static void testSoftReference() {
byte[] buff = null;
for (int i = 0; i < 10; i++) {
buff = new byte[1024 * 1024];
SoftReference<byte[]> softReference = new SoftReference<byte[]>(buff);
list.add(softReference);
}
System.gc(); //主动通知垃圾回收
for (int i = 0; i < list.size(); i++) {
Object obj = ((SoftReference) list.get(i)).get();
System.out.println(obj);
}
System.out.println("buff :"+buff.toString());
}
buff 会因为有强引用的存在,而导致无法被垃圾回收,从而出现 OOM 错误;
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
3. 弱引用
弱引用的引用强度比软引用的强度要更弱一些,无论内存时候足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收, 在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用;
我们以软引用同样的方式来测试一下弱引用:
/**
* @author jjxti
* @date 2020-09-03 19:40
*/
public class Main {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
testWeakReference();
}
private static void testWeakReference() {
for (int i = 0; i < 10; i++) {
byte[] buff = new byte[1024 * 1024];
WeakReference<byte[]> sr = new WeakReference<>(buff);
list.add(sr);
}
System.gc(); //主动通知垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((WeakReference) list.get(i)).get();
System.out.println(obj);
}
}
}
运行结果:
null
null
null
null
null
null
null
null
null
nullProcess finished with exit code 0
会发现所有被虚引用关联的对象都被垃圾回收了;
4. 虚引用
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就跟没有任何引用一样,它随时会被回收,在JDK1.2 之后,用 java.lang.ref.PhantomReference 类表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的get 方法仅仅是返回一个 null ,也就是说永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用对列一起使用。
package java.lang.ref;
/**
* @author Mark Reinhold
* @since 1.2
*/
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
引用队列(ReferenceQueue)
引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。
与软引用、弱引用不同,虚引用必须和引用队列一起使用。
☀️ 学而不思则罔,思而不学则殆
👉 我是 江璇 ,一个不断努力的新人程序猿🐵
👊关注我,一起成长!一起进步!