大厂真题之京东-Java实习生,面试记录整理
1、 哪些情况下的对象会被垃圾回收机制处理掉?
利用可达性分析算法, 虚拟机会将一些对象定义为 GC Roots, 从 GC Roots 出发沿着引用链向下寻找, 如果某个对象不能通过 GC Roots 寻找到, 虚拟机就认为该对象可以被回收掉。
1.1 哪些对象可以被看做是 GC Roots 呢?
1) 虚拟机栈(栈帧中的本地变量表) 中引用的对象;
2) 方法区中的类静态属性引用的对象, 常量引用的对象;
3) 本地方法栈中 JNI(Native 方法) 引用的对象;
1.2 对象不可达, 一定会被垃圾收集器回收么?
即使不可达, 对象也不一定会被垃圾收集器回收, 1) 先判断对象是否有必要执行 finalize()方法, 对象必须重写 finalize()方法且没有被运行过。
2) 若有必要执行, 会把对象放到一个队列中, JVM
2.HashSet 是如何保证不重复的?
private transient HashMap<E,Object> map;
public HashSet(){
map = new HashMap<>();
}
public boolean add(E e){
return map.put(e, PRESENT)==null;
}
3、 utf-8 编码中的中文占几个字节;int 型几个字节?
utf-8 是一种变长编码技术, utf-8 编码中的中文占用的字节不确定, 可能 2 个、 3 个、 4 个, int 型占 4 个字节。
4、 静态代理和动态代理的区别, 什么场景使用?
代理是一种常用的设计模式, 目的是:
为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。
代理类和委托类都要实现相同的接口, 因为代理真正调用的是委托类的方法。
区别:
1) 静态代理:
由程序员创建或是由特定工具生成, 在代码编译时就确定了被代理的类是哪一个是静态代理。
静态代理通常只代理一个类;
2) 动态代理:
在代码运行期间, 运用反射机制动态创建生成。
动态代理代理的是一个接口下的多个实现类;
实现步骤:
a.实现 InvocationHandler 接口创建自己的调用处理器;
b.给 Proxy 类提供ClassLoader 和代理接口类型数组创建动态代理类;
c.利用反射机制得到动态代理类的构造函数;
d.利用动态代理类的构造函数创建动态代理类对象;
使用场景:
Retrofit 中直接调用接口的方法;
Spring 的 AOP 机制;
5、 Java 的异常体系
Java 中 Throwable 是所有异常和错误的超类, 两个直接子类是 Error(错误) 和 Exception(异常) :
1) Error 是程序无法处理的错误, 由 JVM 产生和抛出, 如 OOM、 ThreadDeath 等。
这些异常发生时, JVM 一般会选择终止程序。
2)Exception 是程序本身可以处理的异常, 又分为运行时异常(RuntimeException)(也叫 Checked Eception) 和 非 运 行 时 异 常 ( 不 检 查 异 常 Unchecked Exception) 。
运 行 时 异 常 有NullPointerException\IndexOutOfBoundsException 等, 这些异常一般是由程序逻辑错误引起的, 应尽可能避免。
非运行时异常有 IOException\SQLException\FileNotFoundException 以及由用户自定义的 Exception 异常等。
6.final finally finalize区别
-
final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。 -
finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。 -
finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用 System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断
-
强引用:只要引用存在,垃圾回收器永远不会回收
User user=new User();
-
软引用:非必须引用,内存溢出之前进行回收,可以通过以下代码实现
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; sf.get();//有时候会返回null
-
弱引用:第二次垃圾回收时回收,可以通过如下代码实现
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
//The value associated with this ThreadLocal.
Object value;
Entry(ThreadLocal<> k, Object v) {
super(k);
value = v;
}
}
//....
}
//.....
}
-
虚引用 :垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
7、 修改对象 A 的 equals 方法的签名, 那么使用 HashMap 存放这个对象实例的时候, 会调用哪个 equals 方法?
会调用对象的 equals 方法, 如果对象的 equals 方法没有被重写, equals 方法和==都是比较栈内局部变量表中指向堆内存地址值是否相等。
8、 Java 中实现多态的机制是什么?
多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时不确定, 在运行期间才确定, 一个引用变量到底会指向哪个类的实例。
这样就可以不用修改源程序, 就可以让引用变量绑定到各种不同的类实现上。
Java 实现多态有三个必要条件:
继承、 重定、 向上转型, 在多态中需要将子类的引用赋值给父类对象, 只有这样该引用才能够具备调用父类方法和子类的方法。
9、 如何将一个 Java 对象序列化到文件里?
ObjectOutputStream.writeObject()负责将指定的流写入, ObjectInputStream.readObject()从指定流读取序列化数据。
//写入
try {
ObjectOutputStream os = new ObjectOutputStream(new
FileOutputStream("D:/student.txt")); os.writeObject(studentList); os.close();
} catch(FileNotFoundException e) { e.printStackTrace();
} catch(IOException e) { e.printStackTrace();
}
10、 说说你对 Java 反射的理解
在运行状态中, 对任意一个类, 都能知道这个类的所有属性和方法, 对任意一个对象, 都能调用它的任意一个方法和属性。
这种能动态获取信息及动态调用对象方法的功能称为 java语言的反射机制。
反射的作用:
开发过程中, 经常会遇到某个类的某个成员变量、 方法或属性是私有的, 或只对系统应用开放, 这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方法。
1) 获取类的 Class 对象实例 Class clz = Class.forName("com.zhenai.api.Apple");
2) 根 据 Class 对 象 实 例 获 取 Constructor 对 象 Constructor appConstructor =clz.getConstructor();
3) 使 用 Constructor 对 象 的 newInstance 方 法 获 取 反 射 类 对 象 Object appleObj =appConstructor.newInstance();
4) 获取方法的 Method 对象 Method setPriceMethod = clz.getMethod("setPrice", int.class);
5) 利用 invoke 方法调用方法 setPriceMethod.invoke(appleObj, 14);
6) 通过 getFields()可以获取 Class 类的属性, 但无法获取私有属性, 而 getDeclaredFields()可以获取到包括私有属性在内的所有属性。
带有 Declared 修饰的方法可以反射到私有的方法,没有 Declared 修饰的只能用来反射公有的方法, 其他如 Annotation\Field\Constructor 也是如此。
11、Cloneable 接口实现原理
在开发过程中,拷贝实例是常见的一种操作,如果一个类中的字段较多,而我们又采用在客户端中逐字段复制的方 法进行拷贝操作的话,将不可避免的造成客户端代码繁杂冗长,而且也无法对类中的私有成员进行复制,而如果让需要 具备拷贝功能的类实现Cloneable接口,并重写clone()方法,就可以通过调用clone()方法的方式简洁地实现实例 拷贝功能
非检查异常( unckecked exception ):Error 和 RuntimeException 以及他们的子类。javac 在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用 try… catch…finally )这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误 ArithmeticException ,错误的强制类型转换错误 ClassCastException ,数组索引越界 ArrayIndexOutOfBoundsException ,使用了空对象NullPointerException 等等。
检查异常( checked exception ): 除了 Error 和 RuntimeException 的其它异常。javac 强制要求程序员为这样的异常做预备处理工作(使用 try…catch…finally 或者 throws )。在方法中要么用 try-catch 语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如 SQLException , IOException , ClassNotFoundException 等。
需要明确的是:检查和非检查是对于 javac 来说的,这样就很好理解和区分了。
12、 说一下泛型原理, 并举例说明
泛型就是将类型变成参数传入, 使得可以使用的类型多样化, 从而实现解耦。
Java 泛型是在Java1.5 以后出现的, 为保持对以前版本的兼容, 使用了擦除的方法实现泛型。
擦除是指在一定程度无视类型参数 T, 直接从 T 所在的类开始向上 T 的父类去擦除, 如调用泛型方法,传入类型参数 T 进入方法内部, 若没在声明时做类似 public T methodName(T extends Father t){}, Java 就进行了向上类型的擦除, 直接把参数 t 当做 Object 类来处理, 而不是传进去的 T。
即在有泛型的任何类和方法内部, 它都无法知道自己的泛型参数, 擦除和转型都是在边界上发生, 即传进去的参在进入类或方法时被擦除掉, 但传出来的时候又被转成了我们设置的 T。
在泛型类或方法内, 任何涉及到具体类型( 即擦除后的类型的子类) 操作都不能进行, 如new T(), 或者 T.play()(play 为某子类的方法而不是擦除后的类的方法)
13、数组在内存中如何分配
String[] computers = {"Dell", "Lenovo", "Apple", "Acer"}; //①
//只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为3
String[] names = new String[]{"多啦A梦", "大雄", "静香"}; //②
String[] cars = new String[4]; //③
14、 String 为什么要设计成不可变的?
1) 字符串常量池需要 String 不可变。
因为 String 设计成不可变, 当创建一个 String 对象时,若此字符串值已经存在于常量池中, 则不会创建一个新的对象, 而是引用已经存在的对象。
如果字符串变量允许必变, 会导致各种逻辑错误, 如改变一个对象会影响到另一个独立对象。
2) String 对象可以缓存 hashCode。
字符串的不可变性保证了 hash 码的唯一性, 因此可以缓存 String 的 hashCode, 这样不用每次去重新计算哈希码。
在进行字符串比较时, 可以直接比较 hashCode, 提高了比较性能;
3) 安全性。
String 被许多 java 类用来当作参数, 如 url 地址, 文件 path 路径, 反射机制所需的 Strign 参数等, 若 String 可变, 将会引起各种安全隐患。
更多文章,请关注公众号【程序员李木子】,有免费的电子书哦