面试总结-基础篇
01. JAVA中的几种基本数据类型是什么,各自占用多少字节?
JAVA的八种基本数据类型
- 整型
- byte 、 short 、 int 、 long
- 浮点型
- float 、 double
- 字符型
- char
- 布尔型
- boolean
数据类型 | 占用字节 | 默认值 | 封装类 |
---|---|---|---|
byte(字节型) | 1 | 0 | Byte |
short(短整形) | 2 | 0 | Short |
int(整形) | 4 | 0 | Integer |
long(长整形) | 8 | 0.0l | Long |
float(浮点型) | 4 | 0.0f | Float |
double(双精度浮点型) | 8 | 0 | Double |
char(字符型) | 2 | \u0000(空格) | Character |
boolean(布尔型) | Boolean |
注:
boolean在java规范里并没有规定大小;
网上说的大小有很多,比如1个bit(1/8个字节)、1个字节、4个字节;
我更倾向于4个字节,因为boolean类型会被编译为int类型,等于是说JVM里占用字节和int完全一样,int是4个字节,所以boolean也是4字节
02. String类能被继承吗,为什么?
不能 因为String被关键字final修饰。
03. final的用途
final是一种修饰符,是一种规定,可以用于修饰类、成员方法和成员变量
- final所修饰的类:不能被继承,不能有子类
- final所修饰的方法:不能被重写
- final所修饰的变量:是不可以修改的,是常量
04. String,Stringbuffer,StringBuilder的区别?
类型 | 值是否可变 | 线程安全 | 常用 | 描述 |
---|---|---|---|---|
String | 不可变 | String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 | ||
StringBuffer | 可变 | 线程安全 | 多线程操作字符串 | StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 |
StringBuilder | 可变 | 线程不安全 | 单线程操作字符串 | 可变类,速度更快 |
对于字符串拼接,我们实际使用中不用过于纠结是使用String还是StringBuffer与StringBuilder;现代JDK中编译时就会进行优化会将字符串拼接(+号拼接)优化为StringBuffer,甚至如"a"+“b"这样的凭借会直接编译优化为"ab”;因此还是优先考虑代码的可读性和美观,但是通过for循环拼接字符串时请使用StringBuffer或StringBuilder。
05. ArrayList和LinkedList有什么区别?
- ArrayList 查询快
- LinkedList 增删快
- 因为ArrayList底层是一个数组,所以查询快;LinkedList底层是一个链表,所以增删快。
如图所示,假设有一int类型类型的数组,每个int对象都有内存大小,占用4个字节。如果我们要查找第3个对象,可以通过(3-1)*4=8,故9到12字节就是我们要找的对象。是不是很快呢?而链表却不能做到这样的效率。如上图,我们要找到A3,必须先找到A2,要想找到A2,又必须先找到A1;这样的查找效率会大大降低。
如上图所示,数组的插入也相当的浪费效率;如果要在数组内的某一个位置进行插入,需要先将插入位置的前面复制一份,然后在新的数组后面添加新的元素,最后将旧的数组后半部分添加的新的数组后面;而在链表中插入就变得相当简单了,比如我要在A1和A2中插入A10,只需定位到A1的指针和A2的数据即可,将A1的指针指向A10的值,将A10的指针指向A2的值,A10就插入进了链表。
06. 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。
类的实例化顺序:
- 先静态: 先静态,后非静态
- 先父后子: 先父类同级,后子类同级
- 优先级: 字段 > 带参构造函数 > 无参构造函数 > 普通函数
- 同级: 从上到下依次执行
07. 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
常见的Map类有 HashMap 、 ConcurrentHashMap 、 HashTable 、 LinkedHashMap 和 TreeMap 。
并发下常使用 ConcurrentHashMap
类型 | 存储方式 | 是否允许空值 |
---|---|---|
HashMap | 根据键的HashCode值存储数据 | 最多只允许一条记录的键为 Null;允许多条记录的值为 Null |
HashTable | 同上 | 键或值都不允许为 Null |
LinkedHashMap | 同上 | 同 HashMap |
TreeMap | 同上 | 不允许键为Null;允许多条记录的值为 Null |
类型 | 默认容量 | 扩容机制 | 是否有序 |
---|---|---|---|
HashMap | 16(必须为2的整数次幂) | 容量变为原来的2倍 | 否 |
HashTable | 11(不要求底层数组的容量一定要为2的整数次幂) | 容量变为原来的2倍加1 | 否 |
LinkedHashMap | 容量没有限制 | - | 默认是数据插入顺序 |
TreeMap | 容量没有限制 | - | 默认是key升序 |
类型 | 线程安全 | 优缺点 |
---|---|---|
HashMap | 否 | 很快的访问速度,而且在Map中插入、删除和定位元素,HashMap 是最好的选择。 |
HashTable | 是 | 它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢 |
LinkedHashMap | 否 | LinkedHashMap是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。 |
TreeMap | 否 | TreeMap取出来的是排序后的键值对。但如果要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。 |
08. JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
弃用原因:
- 加入多个分段锁浪费内存空间。
- 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
- 为了提高 GC(垃圾回收) 的效率。
- 为了降低锁的粒度,提高性能。
JDK8 放弃了分段锁而是用了 Node数组+链表+红黑树 的数据结构来实现,并发控制使用 Synchronized 和 CAS 来操作,在没有hash冲突的情况下直接使用cas来操作,没有锁的性能开销,当hash冲突时,则直接用链表第一个object加锁,这里加的锁是synchronized,虽然效率不如 ReentrantLock, 但节约了空间,整个看起来就像是优化过且线程安全的HashMap。
09. ConcurrentHashMap(JDK1.8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?
- 减少内存开销
假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。 - 获得JVM的支持
可重入锁毕竟是API这个级别的,后续的性能优化空间很小。
synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。
10. 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的?
TreeMap和LinkedHashmap都是有序的
TreeMap默认是 key升序 ,也可以自定义排序规则:要实现Comparator接口
LinkedHashmap默认是数据 插入顺序
TreeMap 底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性
LinkedHashMap 底层存储结构是哈希表+链表,链表记录了添加数据的顺序
11. 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,可以实现多个接口么?
不同点:
- 抽象类要被子类继承,接口要被类实现。
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
共同点:
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
- 抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
其它:
- 抽象类里可以没有抽象方法
- 如果一个类里有抽象方法,那么这个类只能是抽象类
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
注意
- 类只能继承一个类,但是可以实现多个接口。
- 接口只能继承接口,但是可多继承接口。
12. 继承和聚合的区别在哪?
继承指的是一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识。
聚合体现的是整体与部分、拥有的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期;比如计算机与CPU、公司与员工的关系等;在java中表现在代码层面,只能从语义级别来区分。
扩展:
- 实现:指的是一个class类实现interface接口(可以是多个)的功能;实现是类与接口之间最常见的关系;在Java中此类关系通过关键字implements明确标识
- 依赖:可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A;比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖;表现在代码层面,为类B作为参数被类A在某个method方法中使用。
- 关联:体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,表现在代码层面,为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。
- 组合:组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束;比如你和你的大脑;表现在代码层面,和关联关系是一致的,只能从语义级别来区分。
13. 什么是IO模型?有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。
- 什么是IO模型?
- IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:NIO、BIO、AIO
- 介绍:
- NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理
- BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程并处理,如果这个连接不做任何事情会造成不必要的开销,当然可以通过线程池机制改善
- AIO(NIO2):异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
- 使用场景:
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
14. 反射的原理,反射创建类实例的三种方式是什么?
- 反射的原理:
- 我们都知道java类的执行需要编译、加载、连接、初始化四步。反射的原理就是加载时,jvm通过字节码class文件,生成相应的对象。
- 反射创建类实例的三种方式:
- 对象.getClass()
- 类.class
- Class.forName(“全路径名称”)
- 详细介绍请移步:什么是反射
15. 反射中,Class.forName和ClassLoader区别 。
- Java中Class.forName和classloader都可以用来对类进行加载。
- Class.forName除了将类的 .class文件 加载到jvm中之外,还会对类进行解释,执行类中的static块。
- 而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
- Class.forName(name,initialize,loader)带参数也可控制是否加载static块。
16. 描述动态代理的几种实现方式,分别说出相应的优缺点
常见的动态代理有 jdk动态代理 、 cglib动态代理
jdk动态代理是由java内部的反射机制来实现的,它是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。
cglib动态代理是通过继承来实现的,底层则是借助asm(Java 字节码操控框架)来实现的(采用字节码的方式,给A类创建一个子类B,子类B使用方法拦截的技术拦截所有父类的方法调用)。
优缺点:
- jdk动态代理有一定的局限性,只能基于接口。
- cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。但无法处理final的情况,通过继承。
17. jdk动态代理与cglib动态代理的区别
- jdk动态代理只能对实现了接口的类生成代理,而不能针对类 ;
- cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ; 因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。
18. 写出三种单例模式的实现
18.1 单例模式之饿汉式
在类加载时就初始化一个类对象,使用静态加载,但是在类加载时就实例化对象,单例对象较大的时候会影响系统加载速度
public class Singleton {
private Singleton() {
System.out.println("This is constructor.");
}
private static Singleton instance = new Singleton();
public static Singleton getInstance () {
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
18.2 延迟加载的singleton
只有在访问到单例对象的时候才去检查和实例化单例对象,满足延迟加载,但多线程访问时需要加线程同步,影响访问效率
public class LazySingleton {
private LazySingleton() {}
private static LazySingleton instance ;
public synchronized static LazySingleton getInstance () { //对于多线程访问的需加synchronized
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
18.3 使用静态内部类来作为singleton的容器
既能延迟加载,又能保证线程安全
public class StaticSingleton {
private StaticSingleton() {}
private static class SingletonHolder { //私有内部类在StaticSingleton 加载时,不初始化
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance () {
return SingletonHolder.instance;
}
}
19. 请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用。
- OO 即 Object Oriented 面向对象的设计模式。
- public:Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
- private:Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
- protected:介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
- default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。
- 作用域基本如下,越往下越小。
作用域 当前类 同一package 子孙类 其它package public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × ×
20. 深拷贝和浅拷贝区别
浅拷贝
基本类型拷贝值,引用类型只拷贝引用地址。
深拷贝
引用类型也是拷贝他的值,重新生成的对像,clone出来对象与原对象互不影响。
21. 如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣?
父类中重写hashcode和equals即可
优点:子类中我们不用再写。
缺点:有时候父类中equals和hashcode方法不满足我们的需求,需要重写。
22. 说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法?
- hashCode与equals方法都是Java Object对象中的方法,也就是说Java的一切对象都提供这两个方法。
- 当集合要添加新的元素时,首先会调用这个元素的hashCode方法,定位到它的物理位置;
如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
所以这里存在一个冲突解决的问题,这个时候就需要重写。
23. 有没有可能2个不相等的对象有相同的hashcode?
- hashCode是所有java对象的固有方法;
- 如果不重载的话,返回的实际上是 该对象在jvm堆上的内存地址 ,而不同对象的内存地址肯定不同,所以这个hashCode也就肯定不同了。
- 如果重载了的话,由于采用的算法的问题,有可能导致两个不同对象的hashCode相同。
24. 这样的a.hashcode() 有什么用,与a.equals(b)有什么关系。
- hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
- equals() 主要是用于比对两者的对象是否相同,相对于hashCode()是比较简单的。
- 两者主要在数据的存储方面有联系;
- equals()的时间复杂度为O(n),而hashCode()判断是否有相同元素的代价,只是一次哈希计算,时间复杂度为O(1),这极大地提高了数据的存储性能。
- 当hashcode有冲突时,容器就能判断:这个新加入的元素已经存在,需要另作处理:覆盖掉原来的元素(key)或舍弃。
- 此时就需要再进行一个equals()的比对,只有当equals()也返回true的时候,才会认为元素重复,舍弃存储
25. 数组和链表数据结构描述,各自的时间复杂度。
25.1 各自的特点
两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用
数组:
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素(插入时),在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。链表:
链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
25.2 数组和链表的区别
- 从逻辑结构角度来看:
- 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
- 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
- 数组元素在栈区,链表元素在堆区;
- 从内存存储角度来看:
- (静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
- 链表从堆中分配空间, 自由度大但申请管理比较麻烦。
- 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
- 数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
26. error和exception的区别,CheckedException,RuntimeException的区别。
首先Exception和Error都是继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
Exception 是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。
Error是java程序运行中不可预料的异常情况(正常情况下不大可能出现的情况),这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。【表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误 ,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。 Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。】
其中的Exception又分为检查性异常(checked)和非检查性异常(unchecked)(也叫RuntimeException)。
两个根本的区别在于:
- 检查性异常 必须在编写代码时,使用try catch捕获(比如:IOException异常)。
- 非检查性异常 在代码编写使,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。
27. 常见运行时异常 (RuntimeException)
- NullPointerException (空指针异常)
- IllegalArgumentException (传递非法参数异常)
- ClassCastException (类转换异常)
- IndexOutOfBoundsException (下标越界异常)
- ArrayIndexOutOfBoundsException (数组越界异常)
- ArrayStoreException (数据存储异常,操作数组时类型不一致)
- NumberFormatException (数字格式异常)
- BufferOverflowException (缓冲区溢出异常)
- AruthmeticException (算术异常)
28. 常见非运行时异常 (CheckedException)
- IOException (IO 操作异常)
- ClassNotFoundException (找不到指定 class 的异常)
- FileNotFoundException (文件不存在异常)
- SQLException (SQL语句异常)
- InterruptedException (中断异常-调用线程睡眠时候)
29. 常见错误 (Error)
- OutOfMemoryError (内存溢出错误)
- NoClassDefFoundError (找不到 class 定义错误)
- StackOverflowError (深递归导致栈被耗尽而抛出的错误)
30. 在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么?
不能
Java使用的是 双亲委托机制 :如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器(BootStrap),每一个层次的类加载器都是加此,因此所有的加载请求最终到达顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载请求时(指它的搜索范围没有找到所需的类),子类加载器才会尝试自己去加载。
因此,当你创建一个java.lang.String类时,启动类加载器首先加载的是java.lang.String本类,
加载你自己的java.lang.String类时,就会由虚拟机抛出的 java.lang.SecurityException:Prohibited package name:java.lang 异常。
31. 什么是向下转型和向上转型?
- 面向对象的转型只会发生在具有继承关系的父子类中(接口也是继承的一种)
- 向上转型:其核心目的在于参数的统一上,根本不需要强制类型转换。
向下转型:是为了操作子类定义的特殊功能,需要强制类型转换,可是现在存在的问题是:向下转型其实是一种非常不安全的操作,因为为编译的时候,程序不会报错,而在运行的时候会报错。
32. 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。
泛型是用来解决向下转型时所带来的安全隐患(ClassCastException)
泛型的核心意义在于:类在进行定义的时候可以使用一个标记,此标记就表示类中属性或者方法以及参数的类型,标记在使用的时候,才会去动态的设置类型。
33. Java中的HashSet内部是如何工作的。
- HashSet 的内部采用 HashMap来实现。
- 由于 Map 需要 key 和 value,所以HashSet中所有 key 的都有一个默认 value。
- 类似于 HashMap,HashSet 不允许重复的 key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象。
- HashSet 把存储的值作为 key。
34. 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决?
- 序列化:把对象转换为字节序列的过程称为对象的序列化。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
- 为什么序列化:序列化是为了将一个对象的状态(各个属性值)保存起来,然后在适当的时候再获得。
- 怎么序列化:实现Serializable接口即可
- 反序列化会遇到什么问题:随着版本的迭代,可能出现不认识旧数据的bug
- 解决办法:实现 Serializable 接口的时候,一定要给这个 serialVersionUID 赋值,赋值为 1L 即可。
35. HTTP GET和 HTTP POST 的区别是什么?
- GET参数通过URL传递,POST放在Request body中。
- GET请求在URL中传送的参数是有长度限制的,而POST没有。
- GET请求会被浏览器主动cache,而POST不会,除非手动设置。
- GET请求只能进行url编码,而POST支持多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET产生的URL地址可以被Bookmark,而POST不可以。
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
但是GET和POST本质上都是TCP链接,并无差别。只是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
36. java8的新特性
- Lambda表达式和函数式接口
- 接口的默认方法和静态方法
- 方法引用
- 重复注解
- 更好的类型推断
- Stream流
详解:JAVA8 新特性
致谢
- interview_internal_reference
- 继承、实现、依赖、关联、聚合、组合的联系与区别
- IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型
- 描述Java动态代理的几种实现方式,分别说出相应的优缺点。
- 动态代理的几种实现方式
- Java单例模式的三种实现方式
- Java深入理解深拷贝和浅拷贝区别
- 数组和链表数据结构描述,各自的时间复杂度
- Java异常error和exception的区别,CheckedException,RuntimeException的区别。
- Java中Error和Exception的异同以及运行时异常(Runtime exception)与检查型异常(checked exception)的区别
- JVM类加载器是否可以加载自定义的String
- JAVA基础面试试题及答案(阿里面试题-JAVA基础篇)