Java后端面试

1.JAVA中的几种基本数据类型是什么,各自占用多少字节.

先了解2个单词:
1、bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。
2、byte --字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。

  • 1B=8bit 
  • 1Byte=8bit
  • 1KB=1024Byte(字节)=8*1024bit
  • 1MB=1024KB
  • 1GB=1024MB
  • 1TB=1024GB
  • 3、基本数据类型注意事项:

  • 未带有字符后缀标识的整数默认为int类型;未带有字符后缀标识的浮点数默认为double类型。
  • 如果一个整数的值超出了int类型能够表示的范围,则必须增加后缀“L”(不区分大小写,建议用大写,因为小写的L与阿拉伯数字1很容易混淆),表示为long型。
  • 带有“F”(不区分大小写)后缀的整数和浮点数都是float类型的;带有“D”(不区分大小写)后缀的整数和浮点数都是double类型的。
  • 编译器会在编译期对byte、short、int、long、float、double、char型变量的值进行检查,如果超出了它们的取值范围就会报错。
  • int型值可以赋给所有数值类型的变量;
  • long型值可以赋给long、float、double类型的变量;
  • float型值可以赋给float、double类型的变量;
  • double型值只能赋给double类型变量
  • 2.String 类能被继承吗?为什么?

    String 被声明为 final,因此它不可被继承

            内部使用 char 数组存储数据,该数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

    public final class String implements java.io.Serializable, Comparable, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    

    不可变的好处:

    1. 可以缓存 hash 值

            因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

    2. String Pool 的需要

            如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

    3. 安全性

            String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

    4. 线程安全

            String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

    Program Creek : Why String is immutable in Java?

    平常我们定义的String str=”a”;其实和String str=new String(“a”)还是有差异的。

    前者默认调用的是String.valueOf来返回String实例对象,至于调用哪个则取决于你的赋值,比如String num=1,调用的是
    public static String valueOf(int i) {
    return Integer.toString(i);
    }

  • 3.String ,StringBuffer,StringBuilder的区别?

    1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变
  • 2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步
  • 4.ArrayList和LinkedList有什么区别?

           ArrayList和Vector使用了基于动态数组的实现,可以认为ArrayList或者Vector封装了对内部数组的操作,比如向数组

    中添加,删除,插入新的元素或者数据的扩展和重定向。

           LinkedList使用了循环双向链表数据结构。与基于数组的ArrayList 相比,这是两种截然不同的实现技术,这也决

    定了它们将适用于完全不同的工作场景。

            LinkedList链表由一系列表项连接而成。 一个表项总是包含3个部分:元素内容,前驱表和后驱表,如图所示:
     

    Java后端面试_第1张图片

     

            在下图展示了一个包含3个元素的LinkedList 的各个表项间的连接关系。在JDK的实现中,无论LikedList 是否
    为空,链表内部都有一个header表项,它既表示链表的开始,也表示链表的结尾。表项header的后驱表项便是链表
    中第一个元素, 表项header的前驱表项便是链表中最后一个元素。

    Java后端面试_第2张图片

    ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下: 

  • 对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
  • 在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
  • LinkedList不支持高效的随机元素访问。
  • ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
  •         可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

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

  •  父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
  •  子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
  • 结论:对象初始化的顺序,先静态方法,再构造方法,每个又是先基类后子类

  •  
  •  父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
  •  父类构造方法
  •  子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
  •  子类构造方法
  •  
  • 6.有没有有顺序的Map实现类,如果有,他们是怎么保证有序的。

    reeMap和LinkedHashMap是有序的(TreeMap默认升序,LinkedHashMap则记录了插入顺序)。

7.抽象类和接口的区别 

1. 抽象类

        抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。

抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类

2. 接口

        接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

        从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。

        接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。

        接口的字段默认都是 static 和 final 的。

3. 比较

  • 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
  • 需要使用多重继承。

使用抽象类:

  • 需要在几个相关的类中共享代码。
  • 需要能控制继承来的成员的访问权限,而不是都为 public。
  • 需要继承非静态和非常量字段。

        在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

  1. 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
  2. 抽象类要被子类继承,接口要被类实现。
  3. 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
  4. 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  5. 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
  6. 抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
  7. 抽象类里可以没有抽象方法
  8. 如果一个类里有抽象方法,那么这个类只能是抽象类
  9. 抽象方法要被实现,所以不能是静态的,也不能是私有的。
  10. 接口可继承接口,并可多继承接口,但类只能单根继承

8.继承和聚合的区别在哪

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

聚合体现的是整体与部分、拥有的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期;比如计算机与CPU、公司与员工的关系等

9.IO模型有哪些 

IO是面向流的,NIO是面向缓冲区的

10.反射中,Class.forName和ClassLoader区别

1.Java类装载过程

Java后端面试_第3张图片

  • 装载:通过类的全限定名获取二进制字节流,将二进制字节流转换成方法区中的运行时数据结构,在内存中生成Java.lang.class对象; 
  • 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的; 
  •   校验:检查导入类或接口的二进制数据的正确性;(文件格式验证,元数据验证,字节码验证,符号引用验证) 
  •   准备:给类的静态变量分配并初始化存储空间; 
  •   解析:将常量池中的符号引用转成直接引用; 
  • 初始化:激活类的静态变量的初始化Java代码和静态Java代码块,并初始化程序员设置的变量值。

2. 分析 Class.forName()和ClassLoader.loadClass

        Class.forName(className)方法,内部实际调用的方法是  Class.forName(className,true,classloader);

第2个boolean参数表示类是否需要初始化,  Class.forName(className)默认是需要初始化。

一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。

ClassLoader.loadClass(className)方法,内部实际调用的方法是  ClassLoader.loadClass(className,false);

第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,

不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行

3.数据库链接为什么使用Class.forName(className)

JDBC  Driver源码如下,因此使用Class.forName(classname)才能在反射回去类的时候执行static块。

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

11.final的用途 

final可以修饰类、方法、成员变量

  • 当final修饰类的时候,说明该类不能被继承
  • 当final修饰方法的时候,说明该方法不能被重写
  • 当final修饰成员变量时,有两种情况:
    • 如果修饰的是基本类型,说明这个变量的所代表数值永不能变(不能重新赋值)!
    • 如果修饰的是引用类型,该变量所的引用不能变,但引用所代表的对象内容是可变的!

      12.写出三种单例模式实现 。

              单例模式:单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

              单例模式有三种:懒汉式单例,饿汉式单例,登记式单例,双重校验锁。

      单例模式的好处

    • 对于频繁使用的对象,可以省略new操作花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
    • 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
    • 1.懒汉式单例

      public class Singleton {
          private static Singleton singleton;
          private Singleton() {}  //此类不能被实例化
          public static synchronized Singleton getInstance() {
              if (singleton == null) {
                  singleton = new Singleton();
              }
              return singleton;
          }
      }

      优点:第一次调用才初始化,避免内存浪费。

      缺点:必须加锁synchronized 才能保证单例,(如果两个线程同时调用getInstance方法,会chuxia)但加锁会影响效率。

      2.饿汉式单例

      public class Singleton {
          private static final Singleton SINGLETON = new Singleton();
          private Singleton() {}  //此类不能被实例化
          public static Singleton getInstance() {
              return SINGLETON;
          }
      }

      优点:没有加锁,执行效率会提高。

      缺点:类加载时就初始化,浪费内存。

      3.登记式模式(holder)

      public class Singleton {
          private Singleton() {} //构造方法是私有的,从而避免外界利用构造方法直接创建任意多实例。
          public static Singleton getInstance() {
              return Holder.SINGLETON;
          }
          private static class Holder {
             private static final Singleton SINGLETON = new Singleton();
          }
      }

              内部类只有在外部类被调用才加载,产生SINGLETON实例;又不用加锁。此模式有上述两个模式的优点,屏蔽了它们的缺点,是最好的单例模式。

      4.双重校验锁-懒汉式(jdk1.5)

      public class Singleton {
          private volatile static Singleton singleton;
          private Singleton (){}
          public static Singleton getSingleton() {
      	if (singleton == null) {
      	    synchronized (Singleton.class) {
      		if (singleton == null) {
      		    singleton = new Singleton();
      		}
      	    }
      	}
      	return singleton;
          }
      }

              这样方式实现线程安全地创建实例,而又不会对性能造成太大影响。它只是第一次创建实例的时候同步,以后就不需要同步了。

              由于volatile关键字屏蔽了虚拟机中一些必要的代码优化,所以运行效率并不是很高,因此建议没有特别的需要不要使用。双重检验锁方式的单例不建议大量使用,根据情况决定。

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

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

  链表 中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储 数据元素 的 数据域,另一个是存储下一个结点地址的 指针。 
  如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表

内存存储区别

  • 数组从中分配空间, 对于程序员方便快速,但自由度小。
  • 链表从中分配空间, 自由度大但申请管理比较麻烦. 

逻辑结构区别

  • 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。 
  • 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项) 

总结

  1. 存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取; 
  2. 存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定; 
  3. 存储空间上,链表由于带有指针域,存储密度不如数组大; 
  4. 按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n); 
  5. 按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn); 
  6. 插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可; 
  7. 空间分配方面: 
  8. 数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败; 
  9. 链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;

 

14.error和exception的区别,CheckedException,RuntimeException的区别。

Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正。一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

Exception(异常)表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

  Exception又分为两类

    CheckedException:(编译时异常) 需要用try——catch显示的捕获,对于可恢复的异常使用CheckedException。

    UnCheckedException(RuntimeException):(运行时异常)不需要捕获,对于程序错误(不可恢复)的异常使用RuntimeException。

常见的RuntimeException异常

  • illegalArgumentException:此异常表明向方法传递了一个不合法或不正确的参数。
  • NullpointerException:空指针异常(我目前遇见的最多的)
  • IndexOutOfBoundsException:索引超出边界异常
  • illegalStateException:在不合理或不正确时间内唤醒一方法时出现的异常信息。即 Java 环境或 Java 应用不满足请求操作。  

常见的CheckedException异常

  • 我们在编写程序过程中try——catch捕获到的一场都是CheckedException。
  • io包中的IOExecption及其子类,都是CheckedException。

举个简单的例子(看别人的,觉得很形象,很好理解)

  Error和Exception就像是水池和水池里的水的区别

  “水池”,就是代码正常运行的外部环境,如果水池崩溃(系统崩溃),或者池水溢出(内存溢出)等,这些都是跟水池外部环境有关。这些就是java中的error

  “水池里的水”,就是正常运行的代码,水污染了、有杂质了,浑浊了,这些影响水质的因素就是Exception。

RuntimeExecption在java.lang包下。

常见的几种如下:

  1. ClassCastException(类转换异常)
  2. IndexOutOfBoundsException(下标越界异常)
  3. NullPointerException(空指针异常)
  4. ArrayStoreException(数据存储异常,操作数组时类型不一致)
  5. BufferOverflowException(还有IO操作的,缓冲溢出异常)
  6. IllegalArgumentException - 传递非法参数异常。
  7. ArithmeticException - 算术运算异常
  8. ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
  9. NegativeArraySizeException - 创建一个大小为负数的数组错误异常
  10. NumberFormatException - 数字格式异常
  11. SecurityException - 安全异常
  12. UnsupportedOperationException - 不支持的操作异常
  13. .Java中的HashSet内部是如何工作的。

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

            HashSet实现了Set接口。HashSet依赖的数据结构是哈希表因为实现的是Set接口,所以不允许有重复的值插入到HashSet中的对象不保证与插入的顺序保持一致。对象的插入是根据它的hashcode。HashSet中允许有NULL值。HashSet也实现了Searlizable和Cloneable两个接口

  15. .什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。

  16. 把对象转换为字节序列的过程称为对象的序列化

  17. 把字节序列恢复为对象的过程称为对象的反序列化。
  18.   当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

    对象的序列化主要有两种用途:
      1、 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
      2、在网络上传送对象的字节序列。

      在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

你可能感兴趣的:(Java后端面试)