# 1.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字节

# 1.02 String类能被继承吗,为什么?

不能 因为String被关键字final修饰。

# 1.03 final的用途

final是一种修饰符,是一种规定,可以用于修饰类、成员方法和成员变量

  • final所修饰的类:不能被继承,不能有子类
  • final所修饰的方法:不能被重写
  • final所修饰的变量:是不可以修改的,是常量

# 1.04 String,Stringbuffer,StringBuilder的区别?

类型
值是否可变
线程安全
常用
描述
String 不可变 String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间
StringBuffer 可变 线程安全 多线程操作字符串 StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量
StringBuilder 可变 线程不安全 单线程操作字符串 可变类,速度更快

# 1.05 ArrayList和LinkedList有什么区别?

ArrayList与LinkedList的区别:

  • ArrayList 查询快
  • LinkedList 增删快
  • 因为ArrayList底层是一个数组,所以查询快;LinkedList底层是一个链表,所以增删快。

ArrayList与LinkedList

如图所示,假设有一int类型类型的数组,每个int对象都有内存大小,占用4个字节。如果我们要查找第3个对象,可以通过(3-1)*4=8,故9到12字节就是我们要找的对象。是不是很快呢?而链表却不能做到这样的效率。如上图,我们要找到A3,必须先找到A2,要想找到A2,又必须先找到A1;这样的查找效率会大大降低。

ArrayList与LinkedList插入图解

如上图所示,数组的插入也相当的浪费效率;如果要在数组内的某一个位置进行插入,需要先将插入位置的前面复制一份,然后在新的数组后面添加新的元素,最后将旧的数组后半部分添加的新的数组后面;而在链表中插入就变得相当简单了,比如我要在A1和A2中插入A10,只需定位到A1的指针和A2的数据即可,将A1的指针指向A10的值,将A10的指针指向A2的值,A10就插入进了链表。

# 1.06 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。

类的实例化顺序:

  1. 先静态: 先静态,后非静态
  2. 先父后子: 先父类同级,后子类同级
  3. 优先级: 字段 > 带参构造函数 > 无参构造函数 > 普通函数
  4. 同级: 从上到下依次执行

# 1.07 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。

常见的Map类有 HashMapConcurrentHashMapHashTableLinkedHashMapTreeMap

并发下常使用 ConcurrentHashMap

类型
存储方式
是否允许空值
HashMap 根据键的HashCode值存储数据 最多只允许一条记录的键为 Null;允许多条记录的值为 Null
HashTable 根据键的HashCode值存储数据 键或值都不允许为 Null
LinkedHashMap 根据键的HashCode值存储数据 同 HashMap
TreeMap 根据键的HashCode值存储数据 不允许键为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会更好。

# 1.08 JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。

# 弃用原因:

  • 加入多个分段锁浪费内存空间。
  • 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
  • 为了提高 GC(垃圾回收) 的效率。。
  • 为了降低锁的粒度,提高性能。

jdk8 放弃了分段锁而是用了Node锁,,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。

# 1.09 ConcurrentHashMap(JDK1.8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?

  • 减少内存开销 假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
  • 获得JVM的支持 可重入锁毕竟是API这个级别的,后续的性能优化空间很小。 synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。

# 1.10 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的?

TreeMap和LinkedHashmap都是有序的

TreeMap默认是 key升序 ,也可以自定义排序规则:要实现Comparator接口

LinkedHashmap默认是数据 插入顺序

TreeMap 底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性

LinkedHashMap 底层存储结构是哈希表+链表,链表记录了添加数据的顺序

# 1.11 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,可以实现多个接口么?

  • 不同点:

    • 抽象类要被子类继承,接口要被类实现。
    • 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
    • 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  • 共同点:

    • 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
    • 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
    • 抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
  • 其它:

    • 抽象类里可以没有抽象方法
    • 如果一个类里有抽象方法,那么这个类只能是抽象类
    • 抽象方法要被实现,所以不能是静态的,也不能是私有的。

TIP

  • 类只能继承一个类,但是可以实现多个接口。
  • 接口只能继承接口,但是可多继承接口。

# 1.12 继承和聚合的区别在哪?

  • 继承指的是一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识。

  • 聚合体现的是整体与部分、拥有的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期;比如计算机与CPU、公司与员工的关系等;在java中表现在代码层面,只能从语义级别来区分。

扩展:

  • 实现:指的是一个class类实现interface接口(可以是多个)的功能;实现是类与接口之间最常见的关系;在Java中此类关系通过关键字implements明确标识
  • 依赖:可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A;比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖;表现在代码层面,为类B作为参数被类A在某个method方法中使用。
  • 关联:体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,表现在代码层面,为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。
  • 组合:组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束;比如你和你的大脑;表现在代码层面,和关联关系是一致的,只能从语义级别来区分。

# 1.13 什么是IO模型?有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。

  • 什么是IO模型?
    • IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:NIO、BIO、AIO
  • 介绍:
    • JAVA NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理
    • JAVA BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程并处理,如果这个连接不做任何事情会造成不必要的开销,当然可以通过线程池机制改善
    • JAVA AIO(NIO2):异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
  • 使用场景:
    • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
    • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
    • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

# 1.14 反射的原理,反射创建类实例的三种方式是什么?

  • 反射的原理:
    • 我们都知道java类的执行需要编译、加载、连接、初始化四步。反射的原理就是加载时,jvm通过字节码class文件,生成相应的对象。
  • 反射创建类实例的三种方式:
    • 对象.getClass()
    • 类.class
    • Class.forName("全路径名称")
  • 详细介绍请移步:反射的那些事

# 1.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块。

# 1.16 描述动态代理的几种实现方式,分别说出相应的优缺点

常见的动态代理有 jdk动态代理cglib动态代理

  • jdk动态代理是由java内部的反射机制来实现的,它是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。

  • cglib动态代理是通过继承来实现的,底层则是借助asm(Java 字节码操控框架)来实现的(采用字节码的方式,给A类创建一个子类B,子类B使用方法拦截的技术拦截所有父类的方法调用)。

  • 优缺点:

    • jdk动态代理有一定的局限性,只能基于接口。
    • cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。但无法处理final的情况,通过继承。

# 1.17 jdk动态代理与cglib动态代理的区

  • jdk动态代理只能对实现了接口的类生成代理,而不能针对类 ;
  • cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ;
  • 因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。

# 1.18 写出三种单例模式的实现

# 1.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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 1.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;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 1.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;
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 1.19 请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用。

  • OO 即 Object Oriented 面向对象的设计模式。
  • public:Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
  • private:Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
  • protected:介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
  • default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。
  • 作用域基本如下,越往下越小。
    作用域 当前类 同一package 子孙类 其它package
    public
    protected ×
    default × ×
    private × × ×

# 1.20 深拷贝和浅拷贝区别。

  • 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
  • 简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象
  • 深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
  • 简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍

# 1.21 如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣?

  • 优点:子类中我们不用再写。
  • 缺点:有时候父类中equals和hashcode方法不满足我们的需求,需要重写。

# 1.22 说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。

  • hashCode与equals方法都是Java Object对象中的方法,也就是说Java的一切对象都提供这两个方法。
  • 当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了; 如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 所以这里存在一个冲突解决的问题,这个时候就需要重写。

# 1.23 有没有可能2个不相等的对象有相同的hashcode。

  • hashCode是所有java对象的固有方法;
  • 如果不重载的话,返回的实际上是 该对象在jvm的堆上的内存地址 ,而不同对象的内存地址肯定不同,所以这个hashCode也就肯定不同了。
  • 如果重载了的话,由于采用的算法的问题,有可能导致两个不同对象的hashCode相同。

# 1.24 这样的a.hashcode() 有什么用,与a.equals(b)有什么关系。

  • hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
  • equals() 主要是用于比对两者的对象时候相同,相对于hashCode()是比较简单的。
  • 两者主要在数据的存储方面有联系;
  • equals()的时间复杂度为O(n),而hashCode()判断是否有相同元素的代价,只是一次哈希计算,时间复杂度为O(1),这极大地提高了数据的存储性能。
  • 当hashcode有冲突时,容器就能判断:这个新加入的元素已经存在,需要另作处理:覆盖掉原来的元素(key)或舍弃。
  • 此时就需要再进行一个equals()的比对,只有当equals()也返回true的时候,才会认为元素重复,舍弃存储

# 1.25 数组和链表数据结构描述,各自的时间复杂度。

# 1.25.1 各自的特点:

  • 两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用

  • 数组: 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。

  • 链表: 链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。

# 1.25.2 数组和链表的区别:

  1. 从逻辑结构角度来看:
    • 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
    • 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
  2. 数组元素在栈区,链表元素在堆区;
  3. 从内存存储角度来看:
    • (静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
    • 链表从堆中分配空间, 自由度大但申请管理比较麻烦。
    • 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
    • 数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。

# 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),这种异常是在代码编写或者使用过程中通过规范可以避免发生的,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。

# 1.27 常见运行时异常 (RuntimeException)

  • NullPointerException (空指针异常)
  • IllegalArgumentException (传递非法参数异常)
  • ClassCastException (类转换异常)
  • IndexOutOfBoundsException (下标越界异常)
  • ArrayIndexOutOfBoundsException (数组越界异常)
  • ArrayStoreException (数据存储异常,操作数组时类型不一致)
  • NumberFormatException (数字格式异常)
  • BufferOverflowException (缓冲区溢出异常)
  • AruthmeticException (算术异常)

# 1.28 常见非运行时异常 (CheckedException)

  • IOException (IO 操作异常)
  • ClassNotFoundException (找不到指定 class 的异常)
  • FileNotFoundException (文件不存在异常)
  • SQLException (SQL语句异常)
  • InterruptedException (中断异常-调用线程睡眠时候)

# 1.29 常见错误 (Error)

  • OutOfMemoryError (内存溢出错误)
  • NoClassDefFoundError (找不到 class 定义错误)
  • StackOverflowError (深递归导致栈被耗尽而抛出的错误)

# 1.30 在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么?

不能

JVM的四种类加载器

Java使用的是 双亲委托机制 :如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是加此,因此所有的加载请求最终到达顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载请求时(指它的搜索范围没有找到所需的类),子类加载器才会尝试自己去加载。

因此,当你创建一个java.lang.String类时,启动类加载器首先加载的是java.lang.String本类,加载你自己的java.lang.String类时,就会由虚拟机抛出的 java.lang.SecurityException:Prohibited package name:java.lang 异常。

# 1.31 什么是向下转型和向上转型?

  • 面向对象的转型只会发生在具有继承关系的父子类中(接口也是继承的一种)
  • 向上转型:其核心目的在于参数的统一上,根本不需要强制类型转换。 向下转型:是为了操作子类定义的特殊功能,需要强制类型转换,可是现在存在的问题是:向下转型其实是一种非常不安全的操作,因为为编译的时候,程序不会报错,而在运行的时候会报错。

# 1.32 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。

  • 泛型是用来解决向下转型时所带来的安全隐患

  • 泛型的核心意义在于:类在进行定义的时候可以使用一个标记,此标记就表示类中属性或者方法以及参数的类型,标记在使用的时候,才会去动态的设置类型。

# 1.33 Java中的HashSet内部是如何工作的。

  • HashSet 的内部采用 HashMap来实现。
  • 由于 Map 需要 key 和 value,所以HashSet中所有 key 的都有一个默认 value。
  • 类似于 HashMap,HashSet 不允许重复的 key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象。
  • HashSet 把存储的值作为 key。

# 1.34 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决?

  • 序列化:把对象转换为字节序列的过程称为对象的序列化。
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
  • 为什么序列化:序列化是为了将一个对象的状态(各个属性值)保存起来,然后在适当的时候再获得。
  • 怎么序列化:实现Serializable接口即可
  • 反序列化会遇到什么问题:随着版本的迭代,可能出现不认识旧数据的bug
  • 解决办法:实现 Serializable 接口的时候,一定要给这个 serialVersionUID 赋值,赋值为 1L 即可。

# 1.35 java8的新特性

  • Lambda表达式和函数式接口
  • 接口的默认方法和静态方法
  • 方法引用
  • 重复注解
  • 更好的类型推断

详解:JAVA8十大新特性 (opens new window)

# 参考链接

  1. interview_internal_reference (opens new window)
  2. 继承、实现、依赖、关联、聚合、组合的联系与区别 (opens new window)
  3. IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型 (opens new window)
  4. 描述Java动态代理的几种实现方式,分别说出相应的优缺点。 (opens new window)
  5. 动态代理的几种实现方式 (opens new window)
  6. Java单例模式的三种实现方式 (opens new window)
  7. Java深入理解深拷贝和浅拷贝区别 (opens new window)
  8. 数组和链表数据结构描述,各自的时间复杂度 (opens new window)
  9. Java异常error和exception的区别,CheckedException,RuntimeException的区别。 (opens new window)
  10. Java中Error和Exception的异同以及运行时异常(Runtime exception)与检查型异常(checked exception)的区别 (opens new window)
  11. JVM类加载器是否可以加载自定义的String (opens new window)