Java面经整理(1)

一)Java中支持多继承吗,为什么?

答案:在JAVA中是不支持多继承的,原因是多继承会存在菱形继承的问题

菱形继承:

1)菱形继承也被称之为是钻石继承,是一种在JAVA面向对象编程的时候遇到的一个可能出现的继承问题;

2)假设JAVA支持多继承,那么就有可能一个类D继承两个不同的类C和类B,而这两个类最终又继承一个父类A,形成一个钻石或者是菱形的继承关系,这样一来D类就继承了B C A三个类此时就会出现一定的问题:

3)下面这种情况就会发生歧义或者是二义性问题,因为如果A,B,C类都存在着相同的方法,但是D类此时有没有重写这个方法,那么我此时D d=new D(),d.run(),那么在调用这个方法的时候,编译器无法确定应该调用哪一个父类的方法,因为D类此时直接或者间接继承了三个相同的方法,此时就会导致编译错误

Java面经整理(1)_第1张图片

二)String底层是怎么实现的?

Java的String类在lang包里面,这个包不需要手动进行导入,是由JAVA程序自动进行导入

1)String底层的实现是基于数组(字节数组或者是字符数组)和字符串常量池来进行实现则,利用不可变final来修饰

2)为什么要把char[]数组变成byte[]数组,就是为了更方便地精细化进行管理,一个char等于两个byte,如果一个汉字要占用三个字节,那么在JDK9之前只能创建大小为2个char,但是多出来一个byte是不会使用的,但是程序没有办法表示三个byte,所以我就只能创建4个byte,这本身就不够精细化,占用内存空间比较大,这个时候再JDK9之后就可以创建大小为3的字节数组来表示一个汉字了

三)为什么String要设计成不可变对象?

1)方便实现字符串常量池,字符串常量池在程序运行的时候可以节省很多内存空间,因为不同的字符串变量指向相同的字面量的时候,都指向字符串常量池中的同一个对象,既节省了空间,又提升了速度

2)不可变对象是线程安全的,String的不可变性保证了字符串对象的值不会被修改,这也就意味着多线程环境下,多个线程可以共享字符串对象而不需要担心它的值被修改,不会存在多个线程同时修改同一个变量的问题;

3)不可变对象更方便的hashcode,作为Key时可以更方便的保存到HashMap里面,因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算,更方便的进行缓存

四)String是如何设计成不可变对象的? 

String是不可变对象,代表的是字符串中的内容是不可以改变的

1)String类本身被final修饰,表示该类不可被继承

2)String中的char[] val数组被final修饰表明这个val引用不可以指向其它字符数组,但是是可以通过value引用将这个数组中的内容进行修改

Java面经整理(1)_第2张图片

3)字符串真正不能被修改的原因是,存储字符串的value是被private进行修饰的,只能在String类中被使用,但是String中没有公开提供访问String中的value的公开方法,拿不到value引用,在类外不可以访问私有的成员变量,所以String初始化之后外界没有有效的手段去改变它

4)所有涉及到修改字符串内容的操作都是创建一个新对象,所以说改变的是新对象

网上有些人说字符串不可变内部是因为内部保存字符的数组是被final所修饰的,final修饰这个类表明这个类是不可以被继承的,final修饰引用表示此引用变量不可以指向其他对象,但是引用对象里面的内容是可以进行修改的

5)在String类的实现中,所有涉及到修改字符串内容的操作都是创建一个新的对象,我们修改的是新对象,是因为 Java 作者在 String 的所有方法里面,都很小心地避免去修改了 char 数组中的数据涉及到对 char 数组中数据进行修改的操作全部都会重新创建一个 String对象

6(就假设拿下面这个代码来说:要避免直接修改String对象,因为String类是不能够直接修改的,所有的修改都会直接创建一个新对象,效率十分低下

Java面经整理(1)_第3张图片

在我们的这个代码进行循环拼接的过程中,尤其是我们从反汇编的语法中可以看到,这种方式不推荐使用,可以看到每一次循环都需要重新创建一个StringBuilder对象,效率非常低

每一次进行字符串拼接的过程中,每一次循环都new了一个StringBuilder对象,调用 append方法进行拼接,每一次循环都会new一个StringBuilder对象,最后调用toString()转化成String对象,这频繁的new对象效率就会变得非常低,注意:这里面的init方法就表示构造方法,每一次循环都创建两个对象,效率非常低

每一次字符串的拼接,会被优化成创建StringBuilder对象,最后调用ToString,new对象花费时间,消耗内存

Java面经整理(1)_第4张图片

Java面经整理(1)_第5张图片

        String str = "hello";
        for (int i = 0; i < 100; i++) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(str);
            stringBuilder.append(i);
            str = stringBuilder.toString();
        }
        System.out.println(str);
 StringBuilder stringBuilder=new StringBuilder("abc");
 System.out.println(stringBuilder);

在这里面为什么要打印abc呢?因为在StringBuilder类中重写了ToString方法:在里面会重新new一个String对象

  public String toString() {
        return new String(value, 0, count);
    }
五)字符串常量池底层的实现:String底层其实是一个StringTable的哈希表
先看第一部分:

首先在字符串常量池里面,所有被存储的字符串都是使用哈希表来进行实现的,哈希里面存储节点的值存在着三个信息:

Java面经整理(1)_第6张图片

下面是字符串常量池是如何存储对象的:

Java面经整理(1)_第7张图片

首先程序会根据字符串存储的哈希值%字符串常量池中数组的长度来得到要存储在数组的那一个下标

1)首先,当我们执行String str1="abc"的时候,首先会创建一个字符串对象地址是0X3344,里面有一个val引用和int hash,这个val引用里面又会存放着一个地址0X1122,通过这个val引用就可以找到最终数组中指向的内容

2)然后在创建哈希表中的一个节点,里面包含着三个部分(哈希值,字符串的引用,下一个结点的地址),这时候节点中的引用指向0X3344,未来字符串常量池会将这个节点的地址存放到哈希表里面

3)因为String str1="abc",所以str1直接指向字符串常量池中的0X3344,也就是字符串在字符串常量池中的地址

Java面经整理(1)_第8张图片

1)然后再去执行String str2=new String("abc")的时候,首先会在堆里面创建一个String对象

Java面经整理(1)_第9张图片

2)虽然他们的str1和str2的里面存放的new String()的地址不同不一样,但是两个字符串对象中的指向的value数组的地址是相同的,最终都是字符串常量池中的val数组的地址,最终这个char数组本质上还是存放于字符串常量池中的;

String str1="abc"

String str2=new String("abc")

一个字符串对象有两个组成部分:val引用和hash

1)先创建一个字符串对象地址是0X3344里面的里面有一个val引用指向了char数组(0X1122数组也是有地址的)&#

你可能感兴趣的:(java,开发语言)