Java基础(一)之数据类型——全面,浅显易懂

前言

有空就想借着写博客的同时把 Java 相关的个人所学的基础知识做一个梳理整合。内容尽量做到全面,浅显易懂吧,这样既方便自己以后查阅复习,也分享出来给刚刚入门的程序员们,希望可以给大家一些参考,让大家对Java有一个基本的认识,更好的学习Java。

Java基础知识点归纳

数据类型

数据类型,Java的数据类型主要分为两大:

  • 内置数据类型
  • 引用数据类型

基本数据类型(内置数据类型)

内置数据类型总共有八种:

  • byte/8
  • char/16
  • short/16
  • int/32
  • float/32
  • long/64
  • double/64
  • boolean/~

这应该没什么好说的,基本类型就是这八种。不同的数据类型储存不同类型的变量。

基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。

装箱与拆箱

在 Java 5 之前如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);

在 Java 5 之后就提供了自动装箱的特性,上面的代码就变成了这样:

int i = 10;

这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

Integer x = 10;     // 装箱  
int y = x;         // 拆箱

简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

装箱过程是通过调用包装器的valueOf方法实现的,那么new Integer() 与 Integer.valueOf() 之间有什么区别呢?

  • new Integer(123) 每次都会新建一个对象;
  • Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
		Integer x = new Integer(10);
        Integer y = new Integer(10);
        System.out.println(x == y);    // false
        Integer z = Integer.valueOf(10);
        Integer k = Integer.valueOf(10);
        System.out.println(z == k);   // true

从上面代码打印出来的情况,我们可以很明显的看到两者的区别。valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

在 Java 8 中,Integer 缓存池的大小默认为 -128~127。

 /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。

		Integer m = 101;
        Integer n = 101;
        System.out.println(m == n); // true
        Integer x = 127;
        Integer y = 127;
        System.out.println(x == y); // true
        Integer a = 128;
        Integer b = 128;
        System.out.println(a == b); // false

基本类型对应的缓冲池如下:

  • boolean values true and false
  • all byte values
  • short values between -128 and 127
  • int values between -128 and 127
  • char in the range \u0000 to \u007F

在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。

数据转换

Java的变量类型为布尔型boolean;字符型char;整型byte、short、int、long;浮点型float、double。

其中四种整型变量和两种浮点型变量分别对应于不同的精度和范围。

简单数据类型之间的转换又可以分为:

  • 低级到高级的自动类型转换
  • 高级到低级的强制类型转换
  • 包装类过渡类型能够转换

类型由低级到高级分别为(byte,short,char)–>int–>long–>float–>double

byte b;
int i=b;
long l=b;
float f=b;
double d=b;

上面的语句在 Java 里面可以直接通过,但是将double型变量赋值给float变量,不加强转的话会报错。

引用数据类型(String)

String 被声明为 final,因此它不可被继承。

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

public static void main(String[] args) {
        //定义字符串变量
        //String:引用类型,但是一个不可变的字符序列,当被声明那一刻起,就已经决定它的不能给改变
        String str = "abc" ;//String str = new String("abc") ;
        appendStr(str) ;
        System.out.println(str);    /// abc
    }
    public static void appendStr(String str){
        str += "def" ;
    }

String 不可变有几大好处:

  1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。所以 String 的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。如果 String 是可变的,那么变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。String 自己便是线程安全的。

  1. 本地安全性

类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。

String, StringBuffer 跟 StringBuilder

  1. 可变性
  • String 不可变
  • StringBuffer 和 StringBuilder 可变
  1. 线程安全
  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

三者使用情况:

  • 如果字符串不会更改,请使用String类,因为String对象是不可变的。
  • 如果字符串可以更改(例如:字符串构造中的大量逻辑和操作)并且只能从单个线程访问,则使用 StringBuilder 就足够了。
  • 如果字符串可以更改,并且将从多个线程访问,使用 StringBuffer 因为 StringBuffer 是同步的,因此具有线程安全性。

在大部分情况下 StringBuffer > String,StringBuilder > StringBuffer。

字符串常量池

我们带着以下三个问题,去理解字符串常量池:

  • 字符串常量池的设计意图是什么?
  • 字符串常量池在哪里?
  • 如何操作字符串常量池?

字符串常量池的设计思想

  • 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
  • JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
    • 为字符串开辟一个字符串常量池,类似于缓存区
    • 创建字符串常量时,首先坚持字符串常量池是否存在该字符串
    • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
  • 实现的基础
    • 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
    • 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收

即:“享元模式”,顾名思义 - - - > 共享元素模式。

一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素。

看下列代码:

String a = "aaa" ;
String b = "aaa" ;
System.out.println(a == b );  //true

字符串常量池在哪里

在分析字符串常量池的位置时,首先了解一下堆、栈、方法区:

    • 存储的是对象,每个对象都包含一个与之对应的class

    • JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

    • 对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定

    • 每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)

    • 每个栈中的数据(原始类型和对象引用)都是私有的

    • 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)

    • 数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失

  • 方法区

    • 静态区,跟堆一样,被所有的线程共享

    • 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量

答案就是:在Java 7之前,String Pool被放在运行时常量池中,它属于永久代。而在Java 7,String Pool被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致OutOfMemoryError错误。

问题:String str = new String(“abc”) 创建多少个对象?

  • 1.在常量池中查找是否有"abc"对象

    • 有则返回对应的引用实例

    • 没有则创建对应的实例对象

  • 2.在堆中 new 一个 String(“abc”) 对象

  • 3.将对象地址赋值给str,创建一个引用

答案:如果常量池中没有"abc"字面量则创建两个对象,否则创建一个对象,以及创建一个引用。

操作字符串常量池的方式

下面代码中,s1和s2采用new String()的方式新建了两个不同字符串,而s3和s4是通过s1.intern()方法取得一个字符串引用。intern()首先把s1引用的字符串放到String Pool中,然后返回这个字符串引用。因此s3和s4引用的是同一个字符串。

public static void main(String[] args) {
        String s1 =  new  String("aaa");
        String s2 =  new  String("aaa");
        System.out.println(s1 == s2);// false
        String s3=s1.intern();
        String s4=s1.intern();
        System.out.println(s3==s4); // true
    }

当一个字符串调用intern()方法时,如果String Pool中已经存在一个字符串和该字符串值相等(使用equals()方法进行确定),那么就会返回String Pool中字符串的引用;否则,就会在String Pool中添加一个新的字符串,并返回这个新字符串的引用。

如果是采用"bbb"的形式创建字符串,会自动地将字符串放入

public static void main(String[] args) {
        String s5="bbb";
        String s6="bbb";
        System.out.println(s5==s6); // true
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值