Android面试题(12):new String创建了几个对象

知乎_Accelerator: 怎样理解 java 注解和运用注解编程?
知乎_胖胖: java用这样的方式生成字符串:String str = “Hello”,到底有没有在堆中创建对象?
Emanuel’s Notes_Java中几种常量池的区分

一些答案篇幅不长问题也放在一起写吧,new String的问题在最后。

1. 修改对象A的equals方法的签名,那么使用HashMap存放这个对象实例的时候,会调用哪个equals方法?
这个问题有点奇葩,方法签名无法修改的,修改了之后就不算重写了。例如equals的方法签名是:

equals(Object)

修改方法签名就像是这样:

equals(Object, String)
equals(String)
exxxxx(Object)

方法签名由方法名和参数类型组成,如果方法名相同,参数类型或参数个数不同,那么算是重载。而方法签名和Object的equals不一样,那么就不算重写了equals方法,HashMap依然会调用Object的的equals方法。

而题目如果是问,对象A重写了equals方法,那么HashMap会调用对象A的equals方法。

2. Java中实现多态的机制是什么?
这问题由陷阱,多态并不单单指的是继承关系的多态,方法重载也算是java中多态的一种体现。
所以答案是:重写(或者回答继承、实现、向上转型),重载。

3. 如何将一个Java对象序列化到文件里?
实现序列化接口,使用ObjectOutputStream将对象写入到FileOutputStream。

4. 说说你对Java反射的理解
反射可以解析一个类的结构,并获取该类对象的属性,调用该类对象的方法。

  • 1、 使得java程序更加灵活,可以在程序运行后动态的修改程序的逻辑。例如可以通过加载一个class文件来达到运行时修改程序逻辑的目的。
  • 2、自审,例如将一个对象转成json字符串时,就需要知道对象的字段名和属性值。通过反射可以获取到这些信息。

总的来说,反射增加了java程序的灵活性,扩展性,但是损失了一定的性能和安全性。例如上面说的加载class文件,这就需要java程序预留后台,这是需要考虑安全性的地方。

5. 说说你对java注解的理解
注解(Annotation),也叫元数据(Metadata)。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

简单来说:
注解是就是在类、字段、方法、参数等地方打个标记,本身没有任何作用,供外部程序解析使用。

例如,文档注解,可以通过javad命令生成文档。
例如,@override、@deprecated等注解,可以让IDE或编译器做语法检查。
例如,我们可以自定义注解,然后通过反射的方式获取注解携带的数据,做一些逻辑操作(赋值等)。

6. 说说你对依赖注入的理解
可以直接看我的另一篇文章,写的很详细:使用Dagger2前你必须了解的一些设计原则

先说说依赖倒置,依赖倒置是实现解耦的一种重要手段。
例如:高层模块调用低层模块,所以依赖低层模块,当低层发生变动时,高层也受到了影响,需要修该大量代码。至于什么是高层什么是低层,我们可以想象一个包含关系。例如汽车包含发动机,那么汽车是高层模块,发动机是低层模块。

我们可以通过抽象的方式(接口)解耦,让高层依赖于接口,那么当低层模块改变时,不影响高层。比如,我们让汽车依赖于一个发动机接口,而发动机都按这个接口的标准设计,到时候更换发动机,汽车就不需要重新设计结构了。

现在再来说说依赖注入,依赖注入是不在类中实例化其他依赖的类,而是先把依赖的类实例化了,然后以参数的方式传入构造函数中,让高层模块和低层模块进一步解耦。
例如,我们想在汽车类中使用发动机对象,我们可能直接new了一个发动机对象出来,但是如果发动机还依赖其它对象,比如油呢,我们也要把油给new出来。这就导致汽车不仅依赖于发动机,还依赖了油。

7. 说下泛型的原理,并举例说明
泛型实在源码级别的东西, 能够进行编译期间类型检查,不需要我们频繁的判断类型,提高了代码的健壮性。

而泛型的原理实际上只是用于编译器校验,实际运行时是泛型会被擦除。例如你可以通过反射的方式往List插入一个String类型,所以你不能通过泛型进行类型转换的。

8. Object类的equals和hashCode方法重写,为什么?
重写equals是给用户自定义两个对象是否相等的逻辑,而不是单纯通过==号比较地址值。

而hashCode则是为了提高某些数据结构的性能而存在的,例如HashMap,将key的hashCode作为索引,直接比较hashCode可以提高性能。

而equals在HashMap中和hashCode也息息相关,因为你重写了equals方法,那么你认为相同的两个对象在hashMap中实际上是不想等的,因为它们的hashCode不想等,所以我们重写equals的时候也必须重写hashCode防止出现这种矛盾出现。

9. java中你对String的了解

JVM的东西我不太了解,并且根据JVM版本不同,内部实现可能也不同。所以,下面的内容可以在面试中谈谈,但不一定是正确的,但是我们需要一个合理的解释去尝试理解。

String是不可变的对象,内部由char数组实现,而String的实际上是保存在堆中的,而不是我们常说的常量池。

String str = new String("123");这行代码“创建”了一个或者两个对象,这个怎么理解呢?

首先我们来说说常量池,一般常量池有三种:
- 字节码常量池:存在于class字节码中。通过javap -v反编译可以class文件即可查看,保存着一些字面量 ,也就是符号引用。
- 运行时常量池:每个类在加载后都会被分配一个单独的常量池,称为运行时常量池,是每个类私有的。
- 字符串常量池:整个JVM中只有一个,当中保存的并不是字符串实例,字符串实例会保存在堆中,而字符串常量池则是保存着该实例的引用。因为一直被字符串常量池引用的原因,所以该实例是不会被GC回收的。

首先来说说String str = new String("123");这行代码。
在编译成字节码之后,字节码常量池如下:

 #2 = String             #24            // 123
 #3 = Class              #25            // java/lang/String
#18 = Utf8               str
#24 = Utf8               123
#25 = Utf8               java/lang/String

#2代表的就是“123”这个字符串所对应的符号引用,而#3代表String类,#18则是str变量。
123java/lang/String实际上都是符号引用,单纯的字面量。

当程序运行,JVM加载类时,有个解析阶段,会将部分符号引用解析成直接引用。
当解析123这个符号时,会在堆中创建“123”这个字符串实例(如果堆中没有的话),然后将它的引用保存到字符串常量池中。

当类加载完后,执行到String str = new String("123");这行代码时:

 0: new           #2                  // class java/lang/String
 3: dup
 4: ldc           #3                  // String 123
 6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
 9: astore_1
10: return

创建一个String对象 –> 将堆中的“123”实例压入栈中 –> 调用String的构造进行初始化。

所以,看这几行命令,创建了多少个对象?
有多少个new就是多少个对象,所以,只有1个。这行代码只创建了一个对象,那就是str引用的String对象。
这里,ldc指令只是把先前在类加载过程中已经创建好的一个String对象(”xyz”)的一个引用压到操作数栈顶而已,并不新创建String对象。

所以,严谨来说,String str = new String("123");这行代码的作用只是创建一个新String实例。
不过非要说两个也可以,因为“123”在编译时也会创建一个实例,但是这里再明确说一次,“123”这个实例并不是保存在所谓的常量池中,而是堆中,常量池(StringTable)只是保存着它的引用。

10. String为什么要设计成不可变的?
最后再说说为什么String要设计成不可变。

之前关于基本数据类型的文章也说过,java优化了这些常用的数据类型,可以不通过new的方式定义,并直接保存在栈中,通过包装类的方式转换成对象使用。

而字符串作为最常使用的类型之一,也有这方面的优化,也是可以不通过new的方式创建实例,并且使用字符串常量池的方式优化字符串的创建。

String s1 = "abcd";  
String s2 = "abcd";  

例如上面这两行代码,实际上只创建了一个实例,它们指向堆中的同一个实例。

String s1= "ab" + "cd";  
String s2= "abc" + "d";  

而这两行代码的作用和前面也是一样的,编译器会优化这个过程,所以也是只创建了一个实例。

虽然String是一个引用类型,但是因为可以通过字面值创建实例和不可变的原因,我们可以像基本数据类型一样使用它。

如果字符串是可变的,那么就不能使用字符串常量池了。因为你改变其中一个字符串,整个程序中引用这个字符串的变量都受到了影响,将对安全性产生非常大的影响。

猜你喜欢

转载自blog.csdn.net/u010386612/article/details/80150031