包装类缓存问题

在 JDK 5 以后,几种包装类对象在内部实现中通过使用相同的对象引用实现了缓存和重用。

例如:Integer类型对于-128-127之间的数字是在缓冲区取的,所以对于在这个范围内的数值用双等号(==)比较是一致的,因为对应的内存地址是相同的。但对于不在这区间的数字是在堆中 new 出来的,所以地址空间不一样,也就不相等

  • Byte、Short、Integer、Long 缓存范围:[-128,127]
  • Character 缓存范围:[0,127]
  • Boolean 直接返回 True Or False

注意:浮点数类型的包装类 Float 和 Double 并没有实现常量池技术


Boolean部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
// 一开始就定义 TRUE FALSE 两个常量
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

// 很少使用此构造函数, 非必须时推荐使用静态工厂
public Boolean(boolean value) {
this.value = value;
}

// valueOf 方法能产生更好的时间和空间性能
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

character部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 此方法通常优先于构造函数, 原因也是产生更好的时间和空间性能
public static Character valueOf(char c) {
if (c <= 127) {
return CharacterCache.cache[(int)c];
}
return new Character(c);
}

// 具体的逻辑如下:
private static class CharacterCache {
private CharacterCache(){}

static final Character cache[] = new Character[127 + 1];

static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}

Byte部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}

private static class ByteCache {
private ByteCache(){}

static final Byte cache[] = new Byte[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}

Short部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
//缓存范围
if (sAsInt >= -128 && sAsInt <= 127) {
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}

private static class ShortCache {
private ShortCache(){}

static final Short cache[] = new Short[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}

Integer部分源码

valueOf方法源码

1
2
3
4
5
6
// 该方法缓存的值总是在-128到127之间,并可能缓存该范围之外的其他值
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

IntegerCache源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    /**
* JLS协议要求缓存在-128到127之间(包含边界值)
* 程序第一次使用Integer的时候
* JVM 的启动参数 -XX:AutoBoxCacheMax=size 修改最大值
* 可以通过系统属性来获得:-Djava.lang.Integer.IntegerCache.high=<size>
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
//使用数组来缓存常量池
static final Integer cache[];
static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//如果有配置JVM启动参数来修改最大值(-XX:AutoBoxCacheMax=<size>)
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
//将设置的最大值与127进行比较,谁大用谁
i = Math.max(i, 127);
//再比较,获取最小值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 如果该值配置错误则忽略该参数配置的值,使用默认范围-128~127
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
// 缓存通过for循环来实现,创建范围内的整数对象并存储到cache数组中
// 程序第一次使用Integer的时候需要一定的额外时间来初始化该缓存
for(int k = 0; k < cache.length; k++){
cache[k] = new Integer(j++);
}
//无论如何,缓存的最大值肯定是大于等于127
assert IntegerCache.high >= 127;
}
//私有的构造方法,因为所有的属性均属于类常量
private IntegerCache() {}
}

总结:

1、引用类型的==是比较对应的引用地址

2、Integer中使用的默认缓存是-128到127。但是可以在JVM启动的时候进行设置。

3、Integer b=Integer.valueOf(8);和Integer b=8;是一样的效果

4、Integer.valueOf()方式,比较是否在缓存范围之内,在就直接从缓存中获取,不在new一个Integer对象

5、每次使用new来创建Integer对象,是用不到Integer Cache缓存的

6、Integer中的使用缓存的方式也可以理解为享元模式


面向对象的特征有哪些方面?

四大基本特征:封装,继承,多态,抽象

封装

将对象封装成一个高度自治和相对封闭的个体,对象的状态由这个对象自己的行来读取和改变,即:类的属性私有化,对外提供改属性的获取或改变的方法

优势:

  • 通过隐藏对象的属性来保护对象内部的状态
  • 提高了代码的可用性和可维护性,因为对象的行为可以被单独地改变或扩展
  • 禁止对象之间的不良交互提高模块化

抽象

抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处

对比:抽象关注对象的行为,而封装关注对象行为的细节


继承

子类继承父类,拥有父类的基本属性和方法,通过重写父类方法实现代码复用,也可以对自身方法进行扩展,达到功能的增强

image-20220922190111098

多态

多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作

多态可以分为 编译时多态 和 运行时多态

编译时多态:方法的重载编译时多态

运行时多态:子类继承父类,类实现接口

多态存在的三个必要条件

  • 要有继承
  • 要有重写
  • 父类引用指向子类对象

多态的作用:消除类型之间的耦合关系


成员变量(全局变量)和局部变量的区别?

定义的位置

  • 局部变量定义在函数中,语句内
  • 成员变量定义在类中

内存中的位置

  • 局部变量存储在栈内存中
  • 成员变量存储在堆内存中

初始化值

  • 局部变量没有默认初始化值,必须赋值才可以使用
  • 成员变量有默认初始化值

生命周期

  • 局部变量一旦作用区域结束,就立刻释放
  • 成员变量也称为实例变量,随着对象创建而创建,随着对象回收被回收

HashSet和TreeSet的区别是什么?

HashSet

  • 不能保证元素的排列顺序,顺序有可能发生变化
  • 集合元素可以是null,但只能放入一个null
  • HashSet底层是采用HashMap实现的
  • HashSet底层是哈希表实现的

TreeSet

  • TreeSet中的数据是排好序的,不允许放入null值。
  • TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key
  • TreeSet的底层实现是采用二叉树(红-黑树)的数据结构

用什么方法来区分Set里的元素是否重复?

Set 里的元素是不能重复的,用iterator()方法来区分重复与否,equals()是判读两个Set是否相等。

equals()和 ==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值


对Error和Exception的理解

首先两者继承自Throwable类,Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。 Error 是指程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM出现的问题

异常分为 运行时异常其他异常

运行时异常(不受检异常):空指针异常、数组索引越界异常

其他异常(受检异常):例如 IOException 使用 try catch finally 进行处理