Java基础5—包装类

本文详细讲解了Java中的包装类,包括装箱、拆箱的基本用法,以及包装类的共同特点如重写Object方法、实现Comparable接口。深入剖析了String类的内部原理和编码转换,探讨了StringBuilder和StringBuffer的线程安全与性能差异。同时,介绍了随机数生成和洗牌算法,并给出了一些编程习题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Java有8种基本类型,每种基本类型都有一个对应的包装类。包装类是什么呢?

包装类是一个类,内部有一个实例变量,保存对应的基本类型的值,这个类一般还有一些静态方法、静态变量和实例方法,以方便对数据进行操作

基本类型包装类
booleanBoolean
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter

1. 基本用法

将基本类型转换为包装类的过程,一般称为“装箱”,而将包装类型转换为基本类型的过程,则称为“拆箱”。

Integer a = Integer.valueOf(1);		// 装箱
int b = a.intValue();		// 拆箱

// 构造
Integer a = new Integer(1);

从字节码中,我们发现装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。

因此,

  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

1.1 包装类的共同点

  1. 重写Object方法

  2. 实现Comparable接口

  3. 包装类和String方法

  4. 常用常量

  5. 共同的父类Number,通过父类中定义的各种方法,包装类实例可以返回任意的基本数值类型。

  6. 不可变性,包装类都是不可变类。所谓不可变是指实例对象一旦创建,就没有办法修改了。

BigDecimal

《阿里巴巴Java开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false

我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 BigDecimal(String) 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。


2. 剖析String

2.1 内部原理

String类内部用一个字符数组表示字符串,实例变量定义为:

private final char value[];

String中的大部分方法内部也都是操作的这个字符数组。

2.2 编码转换

2.3 常量字符串

String a = "11";	// a指向常量字符串 "11"的位置
String b = "11";	
a == b;	// true,两者指向同一个位置

String c = new String("11");
String d = new String("11");	
c == d;	// false,两者指向不同的对象

字符串的创建

  • String s = “a” + “b" + "c"创建了多少个对象?

创建了1个,因为在编译时JVM会合并 “a”、“d”、"c"成为一个对象,放入常量池,然后让s指向它

  • String s = new String(“a” + " b") 创建了多少个对象?

String s = new String(“a” + " b") 创建了多少个对象?

创建了两个,一个是字符串常量,一个是String对象


3. 剖析StringBuilder

3.1 基本用法

StringBuilder sb = new StringBuilder();

sb.append("2");
sb.append("3");

String a = ab.toString();

3.2 内部原理

还是用字符数组,不同于String,此数组不是final的,他保存一个实例变量count表示数组已使用的字符个数。

  • 在构造时调用父类构造生成特定长度的char数组,
  • append方法会直接复制字符到字符数组,数组长度不够时进行扩容。
  • toString方法会生成一个String对象,保存字符数组的内容
  • insert方法会在确保有足够长度后,将原数组插入位后字符后移,然后插入。

3.3 String的+和+=

String的+法一般会生成StringBuilder对象,通过append方法实现String的加减

3.4 String,StringBuilder和StringBuffer

可变性:

String类中使用final字符数组保存字符串,因此String不可变

private final char value[];

而其他两者都继承自AbstractStringBuilder类,此类中字符数组未用final修饰。

线程安全性:

String对象不可变,可以理解为常量,线程安全。

StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,因此是线程安全的。

StringBuilder并没有对方法加同步锁,所以是非线程安全的。

性能:

String类型每次操作时都会生成新的String对象,并改变指针指向新的String对象。

StringBuffer每次都会对Stringbuffer对象本身进行操作,而不是生成新的对象并改变引用。

StringBuilder性能比StringBuffer高10%~15%,但线程不安全。


4. 随机

4.1 Math.random

double a = Math.random();	// 无参构造
double b = Math.random(seed);	// 有参构造
Math.Random(seed1) == Math.random(seed1);

种子相同,产生的随机数序列就是相同的。

4.2 随机的基本原理

public Random(long seed) {
    if (getClass() == Random.class)
        this.seed = new AtomicLong(initialScramble(seed));
    else {
        // subclass might have overriden setSeed
        this.seed = new AtomicLong();
        setSeed(seed);
    }
}

synchronized public void setSeed(long seed) {
	this.seed.set(initialScramble(seed));
	haveNextNextGaussian = false;
}

private static long initialScramble(long seed) {
	return (seed ^ multiplier) & mask;
}

4.3 洗牌

一种常见的随机场景是洗牌,就是将一个数组或序列随机重新排列

       
	int[] arr = {1,2,3,4,5,6,7,8,9};
    shuffle(arr);
    System.out.println(Arrays.toString(arr));
    
    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];    
        arr[j] = tmp;
    }
    
    private static void shuffle(int [] arr){
        Random rnd = new Random();
        
        for(int i = arr.length; i> 1; i--){
            swap(arr, i-1, rnd.nextInt(i));
        }
    }

4.4 抢红包算法

public class RandomRedPacket {
    
    private int leftMoney;
    private int leftNum;
    private Random rnd;
    
    public RandomRedPacket(int totle, int num){
        this.leftMoney = totle;
        this.leftNum = num;
        this.rnd = new Random();
    }
    
    public synchronized int nextMoney(){
        if(this.leftNum <= 0){
            throw new IllegalStateException("抢光了");
        }
        if(this.leftNum == 1){
            return this.leftMoney;
        }
        
        double max = this.leftMoney / this.leftNum * 2d;
        int money = (int) (rnd.nextDouble()*max);
        money = Math.max(1,money);
        this.leftMoney -= money;
        this.leftNum--;
        return money;
    }
}

// test
@Test
public void test_1(){

    RandomRedPacket redPacket = new RandomRedPacket(100, 5);
    for(int i =0; i < 5; ++i){
        System.out.println(redPacket.nextMoney());
    }
}

5. 习题

  • 无论如何,Integer与new Integer不会相等。不会经历拆箱过程,
  • 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false
    java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存
  • 两个都是new出来的,都为false
  • int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值