JavaSE面试要点一:数据类型(8大基本类型、switch的传参类型、包装类、String、StringBuffer、StringBuilder、append的相关知识)

基本类型

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倍。)

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值