Java基本数据类型和引用类型

3,278 阅读14分钟

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 :null

Process finished with exit code 0

引用类型

什么是引用类型?

引用其实就像一个对象的名字或者别名,一个对象在内存中会请求一块空间(堆区 heap)来保存数据,根据对象的大小,它可能占用的空间大小也不相等。访问对象的时候,我们也不会直接访问对象放在内存中的数据,而是通过引用去访问,引用也可以认为是一种数据结构,存放在栈 stack 中,它指示了对象在内存(堆区 heap)中的地址;

下面看下两个案例:

  • String a = "hello kingshion";
    
    String b = a;
    
    

    a 和 b 是两个不同的引用,但它们所指向的堆中的对象是同一个,所以它们的值是一样的;

    image-20201017113243570

  • String a = "hello";
    
    String b = "world";
    
    b = a;
    
    

    需要注意的是:String 对象的值本身是不可改变的,(底层为final修饰)那么是如何做到将 b = a ; 的呢?

    这种情况下是通过改变 它的引用 b 的值,使之指向了另一个 String 对象 a ;

    image-20201017113715455

    如图:一开始 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@1540e19d

Process 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
null

Process 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)

引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

与软引用、弱引用不同,虚引用必须和引用队列一起使用。

☀️ 学而不思则罔,思而不学则殆
👉 我是 江璇 ,一个不断努力的新人程序猿🐵
👊关注我,一起成长!一起进步!