JAVA面试基础篇及答案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_22067469/article/details/85808492

JAVA基础

1、JAVA中的几种基本数据类型是什么,各自占用多少字节。
基本类型 大小 最小值 最大值
byte 8 bit = 1字节 -128 127
short 16 bit = 2字节 -215 +215 -1
int 32 bit = 4字节 -231 +231 -1
long 64 bit = 8字节 -263 +263 -1
float 32 bit = 4字节 -3.40292347E+38 3.40292347E+38
double 64 bit = 8字节 -1.79769313486231570E+308 308-1.79769313486231570E+308
char 16 bit = 2字节 \u0000 unicode 0 u\ffff unicode 216 -1
boolean 1 bit false true
2、String类能被继承吗,为什么。

String被final所修饰,所以不能被继承。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence{}

1、final修饰的对象不能被修改。
2、final修饰的类不能被继承。
3、final修饰的方法不能被重写。

3、String,Stringbuffer,StringBuilder的区别。
  • 可变性
    String 类中使用 final 关键字字符数组保存字符串,private final char value[],所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value但是没有用 final 关键字修饰,所以这两种对象都是可变的。

    StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的。

    abstract class AbstractStringBuilder implements Appendable, CharSequence{
        char[] value;
        int count;
        AbstractStringBuilder() {
        }
        AbstractStringBuilder(int capacity) {
            value = new char[capacity];
        }
    }
    
  • 线程安全性
    String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

  • 性能
    每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

  • 总结
    1、操作少量的数据 = String
    2、单线程操作字符串缓冲区下操作大量数据 = StringBuilder
    3、多线程操作字符串缓冲区下操作大量数据 = StringBuffer

4、ArrayList和LinkedList有什么区别。

Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低)。
LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。

1、对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。

2、在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。

3、LinkedList不支持高效的随机元素访问。

4、ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。

5、总结:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了,在数据量不是很大的情况下,ArrayList是使用最多的集合。

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

类的实例化顺序:先静态再父子
父类静态数据->子类静态数据->父类字段->子类字段->父类构造函数->子类构造函数

6、用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们 内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
  • HashMap:基于哈希表的Map接口实现(哈希表对键进行散列,Map结构即映射表存放键值对)
  • LinkedHashMap:HashMap的基础上加上了链表数据结构
  • HashTable:哈希表
  • TreeMap:红黑树(自平衡的排序二叉树)
  • ConcurrentHashMap:Node数组+链表+红黑树(JDK1.8)

其中最频繁的是HashMap和ConcurrentHashMap,他们的主要区别是HashMap是非线程安全的。ConcurrentHashMap是线程安全的。

并发下可以使用ConcurrentHashMap和HashTable他们的主要区别是:

1、ConcurrentHashMap的hash计算公式:(key.hascode()^ (key.hascode()>>> 16)) & 0x7FFFFFFF,HashTable的hash计算公式:key.hascode()& 0x7FFFFFFF

2、HashTable存储方式都是链表+数组,数组里面放的是当前hash的第一个数据,链表里面放的是hash冲突的数据,ConcurrentHashMap是数组+链表+红黑树

3、默认容量都是16,负载因子是0.75。就是当hashmap填充了75%的busket是就会扩容,最小的可能性是(16*0.75),一般为原内存的2倍

4 、线程安全的保证:HashTable是在每个操作方法上面加了synchronized来达到线程安全,ConcurrentHashMap线程是使用CAS(compore and swap)来保证线程安全的。

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

1、在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。

2 、 Hashtable(同一把锁) 使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

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

ConcurrentHashMap的一些操作使用了synchronized锁,而不是ReentrantLock,虽然说jdk8的synchronized的性能进行了优化,但是我觉得还是使用ReentrantLock锁能更多的提高性能。

8、有没有有顺序的Map实现类,如果有,他们是怎么保证有序的。

TreeMap是基于红黑树对TreeMap中所有key进行排序,从而保证TreeMap中所有key-value对处于有序状态。TreeMap也有两种排序方式:

  • 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有key应该是同一个类的对象,否则将会抛出ClassCastException异常。
  • 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序。采用定制排序时不要求Map的key实现Comparable接口。
9、抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口 么。

1、接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
2、接口中的实例变量默认是 final 类型的,而抽象类中则不一定
3、一个类可以实现多个接口,但最多只能实现一个抽象类
4、一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5、接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

10、继承和聚合的区别在哪。

继承:指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性。

聚合:聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分。

11、IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

reactor模型:反应器模式(事件驱动模式):当一个主体发生改变时,所有的属体都得到通知,类似于观察者模式。

12、反射的原理,反射创建类实例的三种方式是什么。
原理

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

静态编译和动态编译
  • 静态编译:在编译时确定类型,绑定对象
  • 动态编译:运行时确定类型,绑定对象
反射机制优缺点
  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
  • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
反射创建类实例的三种方式

1、Class.forName(“com.A”)
2、new A().getClass()
3、A.class

13、反射中,Class.forName和ClassLoader区别 。

1、class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。

2、classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

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

动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理。

jdk动态代理的前提是目标类必须实现一个接口,代理对象跟目标类实现一个接口,从而避过虚拟机的校验。

cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。

15、动态代理与cglib实现的区别。

动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理。

jdk动态代理的前提是目标类必须实现一个接口,代理对象跟目标类实现一个接口,从而避过虚拟机的校验。

cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。

16、为什么CGlib方式可以对接口实现代理。

cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。而接口是可以被继承的。

17、final的用途。

1、被final修饰的类不可以被继承。

2、被final修饰的方法不可以被重写。

3、被final修饰的变量不可以被改变(final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的。)

总结:被final修饰的方法,JVM会尝试为之寻求内联,这对于提升Java的效率是非常重要的。因此,假如能确定方法不会被继承,那么尽量将方法定义为final的。

18、写出三种单例模式实现 。
饿汉式
public class EagerSingleton
{
    private static EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton()
    {
        
    }
    
    public static EagerSingleton getInstance()
    {
        return instance;
    }
}
懒汉式
public class LazySingleton
{
    private static LazySingleton instance = null;
    
    private LazySingleton()
    {
        
    }
    
    public static LazySingleton getInstance()
    {
        if (instance == null)
            instance = new LazySingleton();
        return instance;
    }
}
双检锁
public class DoubleCheckLockSingleton
{
    private static DoubleCheckLockSingleton instance = null;
    
    private DoubleCheckLockSingleton()
    {
        
    }
    
    public static DoubleCheckLockSingleton getInstance()
    {
        if (instance == null)
        {
            synchronized (DoubleCheckLockSingleton.class)
            {
                if (instance == null)
                    instance  = new DoubleCheckLockSingleton();
            }
        }
        return instance;
    }
}
单例模式的好处

1、控制资源的使用,通过线程同步来控制资源的并发访问

2、控制实例的产生,以达到节约资源的目的

3、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信

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

父类的equals不一定满足子类的equals需求。比如所有的对象都继承Object,默认使用的是Object的equals方法,在比较两个对象的时候,是看他们是否指向同一个地址。

但是我们的需求是对象的某个属性相同,就相等了,而默认的equals方法满足不了当前的需求,所以我们要重写equals方法。

如果重写了equals 方法就必须重写hashcode方法,否则就会降低map等集合的索引速度。

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

OO设计理念:封装、继承、多态

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。所以我们可以通过public、private、protected、default 来进行访问控制

关键字 本类 本包 子类 外部包
public
protected ×
default × ×
private × × ×
21、深拷贝和浅拷贝区别。

浅拷贝只拷贝指针,深拷贝就是拷贝他的值,重新生成的对像。

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

数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组。数组在列表的末尾追加元素时间复杂度就是O(1),如果在中间插入或者删除时间复杂度为O(n-i)

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

23、error和exception的区别,CheckedException,RuntimeException的区别。

Error(错误)表示系统级的错误和程序不必处理的异常,是java运行环境中的内部错误或者硬件问题。比如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由Java虚拟机抛出的。

Exception(违例)表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。
Exception又分为运行时异常,受检查异常。

RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止程序,因此,编译器不检查这些异常。

CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理), 所以称之为受检查异常。

23、请列出5个运行时异常。
  • NullPointerException(空指针)
  • IndexOutOfBoundsException(数组越界)
  • ClassCastException(类型转换)
  • ArrayStoreException(查找元素)
  • BufferOverflowException(内存溢出)
25、在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么。

不可以,双亲委派模式会保证父类加载器先加载类,就是BootStrap(启动类)加载器加载jdk里面的java.lang.String类,而自定义的java.lang.String类永远不会被加载到。

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

父类的equals不一定满足子类的equals需求。比如所有的对象都继承Object,默认使用的是Object的equals方法,在比较两个对象的时候,是看他们是否指向同一个地址。

但是我们的需求是对象的某个属性相同,就相等了,而默认的equals方法满足不了当前的需求,所以我们要重写equals方法。

如果重写了equals 方法就必须重写hashcode方法,否则就会降低map等集合的索引速度。

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

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

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

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。

hashCode()与equals()的相关规定
1、如果两个对象相等,则hashcode一定也是相同的
2、两个对象相等,对两个对象分别调用equals方法都返回true
3、两个对象有相同的hashcode值,它们也不一定是相等的
4、因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
5、hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

29、有没有可能2个不相等的对象有相同的hashcode。

有可能,最简单的方法,百分百实现的方式就是重写hascode(),两个对象有相同的hashcode值,它们也不一定是相等的。

30、Java中的HashSet内部是如何工作的。

HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)

HashSet实现Set接口,仅存储对象,调用add()方法向Set中添加元素,HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false。

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

序列化是一种用来处理对象流的机制 ,所谓对象流就是将对象的内容进行流化。

序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

32、java8的新特性。
================================================================================
感谢阅读,写得不好的地方请指教,能帮助到你是对我最好的回报,不卑不亢,加油。
请你记住比你优秀的一定比你努力,比你努力的人一定比你优秀。
================================================================================

猜你喜欢

转载自blog.csdn.net/qq_22067469/article/details/85808492
今日推荐