您的当前位置:首页>全部文章>文章详情

大厂真题之京东-Java实习生,面试记录整理

发表于:2024-01-21 14:33:35浏览:205次TAG: #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 是如何保证不重复的?

向 HashSet 中 add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合 equles 方法比较。HashSet 中的 add ()方法会使用 HashMap 的 add ()方法。
以下是 HashSet 部分源码:
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet(){
map = new HashMap<>();
}
public boolean add(E e){
return map.put(e, PRESENT)==null;
}
HashMap 的 key 是唯一的,由上面的代码可以看出 HashSet 添加进去的值就是作为 HashMap 的key。所以不会重复( HashMap 比较key是否相等是先比较 hashcode 在比较 equals )。

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(),回收垃圾,一个对象是否可回收的最后判断
对象的四种引用
  • 强引用:只要引用存在,垃圾回收器永远不会回收

Object obj = new Object();
User user=new User();
可直接通过obj取得对应的对象 如 obj.equels(new Object()); 而这样 obj 对象对后面 new Object 的一个强引用,只有当 obj 这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。
  • 软引用:非必须引用,内存溢出之前进行回收,可以通过以下代码实现

 

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
  • 弱引用:第二次垃圾回收时回收,可以通过如下代码实现
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued 方法返回对象是否被垃圾回收器标记。
ThreadLocal 中有使用到弱引用 :
public class ThreadLocal<T> {
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;
}
}
//....
}
//.....
}

 

  • 虚引用 :垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。虚引用主要用于检测对象是否已经从内存中删除。

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接口是Java开发中常用的一个接口, 它的作用是使一个类的实例能够将自身拷贝到另一个新的实例中,注意,这里所说的“拷贝”拷的是对象实例,而不是类的定义,进一步说,拷贝的是一个类的实例中各字段的值
在开发过程中,拷贝实例是常见的一种操作,如果一个类中的字段较多,而我们又采用在客户端中逐字段复制的方 法进行拷贝操作的话,将不可避免的造成客户端代码繁杂冗长,而且也无法对类中的私有成员进行复制,而如果让需要 具备拷贝功能的类实现Cloneable接口,并重写clone()方法,就可以通过调用clone()方法的方式简洁地实现实例 拷贝功能
深拷贝(深复制)和浅拷贝(浅复制)是两个比较通用的概念,尤其在C++语言中,若不弄懂,则会在delete的时候出问题,但是我们在这幸好用的是Java。虽然Java自动管理对象的回收,但对于深拷贝(深复制)和浅拷贝(浅复制),我们还是要给予足够的重视,因为有时这两个概念往往会给我们带来不小的困惑。
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象 A1 中包含对 B1 的引用, B1 中包含对 C1 的引用。浅拷贝 A1 得到 A2 , A2 中依然包含对 B1 的引用, B1 中依然包含对 C1 的引用。深拷贝则是对浅拷贝的递归,深拷贝 A1 得到 A2 , A2 中包含对 B2 ( B1 的 copy )的引用, B2 中包含对 C2 ( C1 的 copy )的引用。若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝。
异常分类以及处理机制

Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。
Throwable又派生出Error类和Exception类。
错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。

总体上我们根据 Javac 对异常的处理要求,将异常类分为二类。

非检查异常( 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、数组在内存中如何分配

对于 Java 数组的初始化,有以下两种方式,这也是面试中经常考到的经典题目:静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度,如:
//只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为4
String[] computers = {"Dell", "Lenovo", "Apple", "Acer"}; //①
//只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为3
String[] names = new String[]{"多啦A梦", "大雄", "静香"}; //②
动态初始化:初始化时由程序员显示的指定数组的长度,由系统为数据每个元素分配初始值,如:
//只是指定了数组的长度,并没有显示的为数组指定初始值,但是系统会默认给数组数组元素分配初始值为null
String[] cars = new String[4]; //③
因为 Java 数组变量是引用类型的变量,所以上述几行初始化语句执行后,三个数组在内存中的分配情况如下图所示:

由上图可知,静态初始化方式,程序员虽然没有指定数组长度,但是系统已经自动帮我们给分配了,而动态初始化方式,程序员虽然没有显示的指定初始化值,但是因为 Java 数组是引用类型的变量,所以系统也为每个元素分配了初始化值 null ,当然不同类型的初始化值也是不一样的,假设是基本类型int类型,那么为系统分配的初始化值也是对应的默认值0。

 

14、 String 为什么要设计成不可变的?

1) 字符串常量池需要 String 不可变。

因为 String 设计成不可变, 当创建一个 String 对象时,若此字符串值已经存在于常量池中, 则不会创建一个新的对象, 而是引用已经存在的对象。

如果字符串变量允许必变, 会导致各种逻辑错误, 如改变一个对象会影响到另一个独立对象。

2) String 对象可以缓存 hashCode。

字符串的不可变性保证了 hash 码的唯一性, 因此可以缓存 String 的 hashCode, 这样不用每次去重新计算哈希码。

在进行字符串比较时, 可以直接比较 hashCode, 提高了比较性能;

3) 安全性。

String 被许多 java 类用来当作参数, 如 url 地址, 文件 path 路径, 反射机制所需的 Strign 参数等, 若 String 可变, 将会引起各种安全隐患。

更多文章,请关注公众号【程序员李木子】,有免费的电子书哦