文章目录

  • 2020年JAVA基础面试题
  • 第1题,静态变量和实例变量的区别
  • 第2题,equals与==的区别
  • 第3题,抽象类与接口的区别
  • 第4题,java容器有哪些
  • 第5题,list ,Map,Set的区别
  • 第6题,list ,Map,Set的子类有哪些
  • 第7题,实现多线程的方式有几种
  • 第8题,线程池有几种类型
  • 第9题,自定义线程池的参数有哪些
  • 总结
  • 参考资料


2020年JAVA基础面试题

今年(2020年)因为疫情的原因倒闭的公司很多,招java工程师的公司大量锐减,但是培训机构越来越多,java初级工程师也越来越多,java的面试越来越难了,让我们再总结一下java的基础面试题吧,要不然到时候连基础面都过不了。

第1题,静态变量和实例变量的区别

答:静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。在Java开发中,上下文类和工具类中通常会有大量的静态成员。

第2题,equals与==的区别

对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
== 解读

基本类型:比较的是值是否相同;
引用类型:比较的是引用是否相同;
代码示例:

String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

代码解读:因为 x 和 y 指向的是同一个引用,所以== 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。
equals 解读

equals 本质上就是==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。
首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:

class Cat {
    public Cat(String name) {
        this.name = name;
    }
 
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
 
Cat c1 = new Cat("王磊");
Cat c2 = new Cat("王磊");
System.out.println(c1.equals(c2)); // false

输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源码如下:

public boolean equals(Object obj) {
    return (this == obj);
}

原来 equals 本质上就是 ==。

那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:

String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true

同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。
总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

所以,面试的时候,你可以把总结的部分说出来。

第3题,抽象类与接口的区别

比较点

抽象类

接口

默认方法

抽象类可以有默认的方法实现

java 8之前,接口中不存在方法的实现

实现方式

子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现

子类使用implements来实现接口,需要提供接口中所有声明的实现.

构造器

抽象类中可以有构造器

接口中不能

和正常类区别

抽象类不能被实例化

接口则是完全不同的类型

访问修饰符

抽象方法可以有public,protected和default等修饰

接口默认是public,不能使用其他修饰符

多继承

一个子类只能存在一个父类

一个子类可以存在多个接口

添加新方法

抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码

如果往接口中添加新方法,则子类中需要实现该方法

所以,可以这样回答

  1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
  2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。
  3. 一个类可以实现多个接口,但只能继承一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。
  4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符
  5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。

其中,第三条是重点,要记得回答。一个类可以实现多个接口,但只能继承一个抽象类。

要注意分清继承实现的区别。还有就是接口跟接口之间是继承的关系。
所以,这道题还可以这样问

问:接口是否可继承(extends)接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)?
答:接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类,但前提是具体类必须有明确的构造函数。

第4题,java容器有哪些

Java 容器分为 Collection 和 Map 两大类,其下又有很多子类,如下所示是Collection和Map的继承体系:

java 静态变量 map存储 用户信息 失效_面试


Collection大类

List

Java 的 List 是非常常用的数据类型。List 是有序的 Collection。Java List 一共三个实现类:分别是 ArrayList、Vector 和 LinkedList。

  • ArrayList:ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数
    组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数
    组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进
    行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
  • Vector:Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一
    个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。
  • LinkList:LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

Set

Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等性本质是对象 hashCode 值(java 是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode 方法和 equals 方法。

  • HashSet:基于哈希表实现,存入数据是按照哈希值,所以并不是按照存入的顺序排序,为保证存入的唯一性,存入元素哈希值相同时,会使用 equals 方法比较,如果比较出不同就放入同一个哈希桶里。
  • TreeSet:基于红黑树实现,支持有序性操作,每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
  • LinkHashSet( HashSet+LinkedHashMap ):继承于 HashSet、又是基于 LinkedHashMap 来实现的, 具有 HashSet 的查找效率 。

Queue
队列

LinkedBlockingQueue
BlockingQueue
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue

Map大类

  1. HashMap: 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快
    的访问速度,但遍历顺序却是不确定的。 HashMap 非线程安全,底层实现为数组+链表+红黑树。
  2. ConcurrentHashMap:支持并发操作的HashMap,在JDK1.7和1.8实现线程安全的方式不同。
  3. HashTable:Hashtable 是遗留类,不建议使用,很多映射的常用功能与 HashMap 类似,通过 synchronized 把整个(table)表锁住来实现线程安全的,效率十分低下。
  4. TreeMap: 红黑树实现,可排序,需要对一个有序的key集合进行遍历时建议使用。
  5. LinkHashMap: 是 HashMap 的一个子类, 增加了一条双向链表, 从而可以保存记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

第5题,list ,Map,Set的区别

List 接口存储一组不唯一,有序(插入顺序)的对象

Set 接口存储一组唯一,无序的对象

Map接口存储一组键值对象,提供key到value的映射

第6题,list ,Map,Set的子类有哪些

  • Collection
  • List
  • ArrayList
  • LinkedList
  • Vector
  • Stack
  • Set
  • HashSet
  • LinkedHashSet
  • TreeSet
  • Map
  • HashMap
  • LinkedHashMap
  • TreeMap
  • ConcurrentHashMap
  • Hashtable

第7题,实现多线程的方式有几种

JAVA多线程实现方式
主要有三种:

  • 继承Thread类
  • 实现Runnable接口
  • 使用ExecutorService、Callable、Future实现有返回结果的多线程。

其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。

第8题,线程池有几种类型

1、newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:

工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统OOM。

2、newFixedThreadPool

创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

3、newSingleThreadExecutor

创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4、newScheduleThreadPool

创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

5、newSingleThreadScheduledExecutor

创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行并且可定时或者延迟执行线程活动。

线程程的学习建议看阳哥出的——尚硅谷互联网大厂高频重点面试题(第2季)
下面三个重点
Executors.newFixedThreadPool(int)—— 执行一个长期的任务,性能好很多
Executors.newSingleThreadExecutor()—— 一个任务一个线程执行的任务场景
Executors.newCachedThreadPool()—— 适用:执行很多短期异步的小程序或者负载较轻的服务器

第9题,自定义线程池的参数有哪些

可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池。
常见的构造函数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

创建一个新的 ThreadPoolExecutor与给定的初始参数。

参数

意义

corePoolSize

线程池常驻核心线程数

maximumPoolSize

能够容纳的最大线程数

keepAliveTime

空闲线程存活时间

unit

存活时间单位

workQueue

存放提交但未执行任务的队列

threadFactory

创建线程的工厂类

handler

等待队列满后的拒绝策略

理解:线程池的创建参数,就像一个银行

corePoolSize就像银行的“当值窗口“,比如今天有2位柜员在受理客户请求(任务)。如果超过2个客户,那么新的客户就会在等候区(等待队列workQueue)等待。当等候区也满了,这个时候就要开启“加班窗口”,让其它3位柜员来加班,此时达到最大窗口maximumPoolSize,为5个。如果开启了所有窗口,等候区依然满员,此时就应该启动”拒绝策略handler,告诉不断涌入的客户,叫他们不要进入,已经爆满了。由于不再涌入新客户,办完事的客户增多,窗口开始空闲,这个时候就通过keepAlivetTime将多余的3个”加班窗口“取消,恢复到2个”当值窗口“。

思考为什么要自定义线程池

总结

面试时要注意引导面试官往自己熟悉的方面,不然就会一直不断深挖问一下。