数据类型——面试考点
基本类型
8个基本类型
八个基本类型分别是定义整形数字的:byte(1bit)、short(2bit)、int(4bit)、long(8bit)。定义浮点型数字:float(4bit 6-7位小数)、doule(8bit 15位小数)。定义布尔值:boolean(1bit) 和字符型:char(2bit) 。
延伸知识点:Java支持引用数据类型和引用类型,Float和Long定义数字的时候,必须要在数字后面添加后缀 f / l 。
引用类型(对象)都继承Object类,都是按照java里面存储对象的内存模型来进行存储,对象本身存储在内存堆上,但是其引用存储在栈中。而基本变量不是对象,他没有引用的概念,定义基本变量都是直接存储在内存中的栈上,数据本身的值就存在栈空间里面,通过变量名调用。引用类包括类(String…)、接口(List…)、数组等。
switch的参数可以是哪些类型,不能是那些类型
在JDK5之前,switch只支持int类型作为参数类型,但是byte、short、char都可以自动转化为int。所有这四种基本类型对应的封装类通过自动拆箱,也可以作为参数。
在JDK6时,枚举(enum)也可以作为参数,底层是使用了枚举类的ordinal方法,返回的是枚举常量的序号,是int类型。
在JDK7之后,String也可以作为参数,底层使用了hashCode方法,返回的是哈希吗,也是int类型。
所以,现在可以往switch里传byte、short、char、enum、String类型。直接传long、float、double不行,但通过强转类型的方式可以转为int类型传入。boolean不能传,因为boolean是不能强转为int的。
基本类型包装类
概念:将基本数据类型数据组装成引用数据类型数据。
延伸知识点:Java编程的思想是一切皆对象,那为什么会有基本数据类型呢? 首先因为Java是强类型语言,在编译器编译的时候,得声名变量的类型,不然编译器就不知道这是什么类型,从而报错。其次因为基本类型是用的最频繁的一种类型,而且占用空间小,可以把他存储在栈中(栈是系统分配的、连续的空间,且单线程所有安全、快。),实现快速操作。而他们的封装类Integer等,必须创建实例,在堆中开辟内存(堆是存储对象的空间,是人为申请而非系统分配的、不连续的,所以相对较慢。他是多线程的,可以被所有线程访问。),既消耗资源,又消耗时间。
拆箱与装箱: 拆箱与装箱就是基本数据类型与包装类类型之间的相互转化。int的装箱是调用Integer的valueOf(int)
方法实现的,Integer的拆箱是通过调用Integer的intValue()
方法实现的。也就是说:自动装箱是调用包装类的valueOf
方法,拆箱是调用包装类的***value()
方法实现的。
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
此处API的解释为:
Integer类里面有一个IntegerCache的内部类:
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() {}
}
在IntegerCache类中创建了一个有static final
定义的cache数组,这个数组可以理解为一个缓存区
,由于它被final修饰,所以无法修改,其为常量。区间为-128到127
之间,装箱时在堆内存中存放的地方也是一样的。但是如果超过了这个缓存区,则对象的地址也会不一样。
引用类型
String类型的创建,每次创建多少对象。
1.String str = “hello”;
创建了一个或者0个对象。JVM在编译时会判断常量池(JDK6之前存储在方法区中、JDK7时被整合到堆内存中、JDK8之后储存在方法区中)中是否含有“hello”这个常量对象,如果有则str直接指向这个常量的引用,如果没有就会在常量池中创建这个对象。
2.String str = new String("hello");
创建了一个或者两个对象。如果常量池有“hello”常量对象,那么不用创建常量对象,直接将str指向它的引用。然后通过new正在堆内存中开辟一块空间来创建String对象。
3.String str =new String("a") + new String("b");
创建了6个或者5个或者4个对象。如果常量池有"a"或者"b"则不需要在常量池中创建他们,直接指向他们的引用。其次就是在堆内存中new开辟出来的两个String对象。然后通过StringBuilder对象调用append方法进行两次字符串的添加操作最后用StringBuilder的toString方法进行返回了一个堆中的字符串对象。StringBuilder 的 toString() 的调用,在字符串常量池中,没有生成"ab"。
4.String str = "a" + "b";
创建了一个或者0个对象。因为"a"+“b"在编译时,就已经编译成了"ab”,被放入常量池中。
String类可以被继承吗?
不可以被集成,因为String类有final
修饰,而final修饰的类是不能被继承的,实现细节不允许改变。
延伸知识点:final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。
final类不能被继承,没有子类。
final方法不能被子类重写,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
父类的private成员方法是不能被子类方法重写的,因此父类private类型的方法默认是final类型的。
所有说,父类的成员方法被private+final修饰,子类是可以写一个与父类方法名、参数、返回值相同的方法,这不是重写
String、StringBuffer、StringBuilder的区别、实现方式
String使用final关键字修饰可以知道String是不可变的类,String中字符数组的长度你定义多少,就是多少,不存在字符数组扩容一说。它内部是final修饰的char[] value,value一旦被赋值,就无法改变。并且被final修饰的引用一旦指向某个对象后,不可在指向其他对象,所有String是安全、不可变的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
}
StringBuffer对象则代表一个字符序列可变的字符串当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。完成后通过toString()方法将其转化为String对象。所以说StringBuffer对象是一个字符序列可变的字符串,它没有重新生成一个对象,而且在原来的对象中可以连接新的字符串。
StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是:StringBuffer上使用了synchronized
关键字加了同步锁证明它是线程安全的,而StringBuilder没有使用说明是线程不安全的StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。
String为什么被设计成长度不可变的
高效、安全。(线程安全、更容易构造、原子性)
final修饰StringBuffer后与StringBuffer还可以append吗?
StringBuffer 理解为缓冲区,不能通过赋值符号对其进行赋值,使用append进行值修改。没有创建新的对象。
对其加以final修饰,fianl 修饰引用对象,代表引用对象不可变,StringBuffer 实现append()没有产生新的对象,所以可以。
延伸概念:StringBuffer的appende()
方法
进入StringBuffer的append()源码
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
这里就可以看出和StringBuilder的区别,synchronized
修饰,线程安全,但效率会变慢一些。
父类AbstractStringBuilder的append()方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
可以看到如果append的值不为空,则调用ensureCapacityInternal(count + len);
传入的是count+拼接字符串的长度。进入ensureCapacityInternal()方法
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
Arrays.copyOf()
很常见,数组扩容。所以ensureCapacityInternal()其实就是复制了一个新的数组,将数组长度通过ensureCapacityInternal()传给Arrays.copyOf(),并重新赋值给value。 再看String的getChars()方法
/**
* Copies characters from this string into the destination character
* array.
* <p>
* The first character to be copied is at index {@code srcBegin};
* the last character to be copied is at index {@code srcEnd-1}
* (thus the total number of characters to be copied is
* {@code srcEnd-srcBegin}). The characters are copied into the
* subarray of {@code dst} starting at index {@code dstBegin}
* and ending at index:
* <blockquote><pre>
* dstBegin + (srcEnd-srcBegin) - 1
* </pre></blockquote>
*
* @param srcBegin index of the first character in the string
* to copy.
* @param srcEnd index after the last character in the string
* to copy.
* @param dst the destination array.
* @param dstBegin the start offset in the destination array.
* @exception IndexOutOfBoundsException If any of the following
* is true:
* <ul><li>{@code srcBegin} is negative.
* <li>{@code srcBegin} is greater than {@code srcEnd}
* <li>{@code srcEnd} is greater than the length of this
* string
* <li>{@code dstBegin} is negative
* <li>{@code dstBegin+(srcEnd-srcBegin)} is larger than
* {@code dst.length}</ul>
*/
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
最终调用的是System.arraycopy
的方法:将指定源数组中的数组从指定位置复制到目标数组的指定位置。
所以,append方法其实就是new了一个新数组,扩容,然后将需要添加的字符串复制到这个新数组去,(内存问题:StringBuffer在数据内容增大时,会为StringBuffer对象追加申请内存,申请数量为当前内存量的一倍,即StringBuffer总数为原内存量的2倍。)