Java面试总纲

Posted Alex Leon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试总纲相关的知识,希望对你有一定的参考价值。

面试

文章目录


自我介绍


At first, Thank you so much for giving me this opportunity for this interview.

My name is XXX, and you can call me Alex Leon which is my English name.

I graduated from Shanghai Maritime University with bachelor’s degree at 2016, I have worked for two companies, have been engaged in Java development for about five years

I passed CET4 during my college years,and I got Java Software Development Special Skill Certificate at 2019 , which is issued by the (MIIT)Ministry of industry and information technology, Now I am studying and preparing for the exam of software designer.

I have good foundation and coding practice of Java, and also I know the skills of Groovy, mysql and Oracle, Im familiar with popular framework such as Spring, SpringBoot, SpringMVC, SpringCloud, Mybatis, and Grails, and I often use the tools like Kafka, RabbitMq, Redis and nginx

The latest project I participated is SPDB(Shanghai Pudong Development Bank) ecosystem marketing project, with framework springBoot, springCloud and grails. it is distributed and microserviced.

Im good at learning new technologies, I love coding, I love programming, and I always keep a good self-drive for learning.

CitiBank is a large and international company, on the other hand, I have similar project experience of bank, so I really hope to join Citibank

Thank you so much


各位面试官好, 我叫XXX,16年毕业于上海海事大学毕业, 毕业之后一直在上海发展,一共呆过两家公司,从事java开发工作5年左右

我的大概情况是: 参加工作前两年从事企业传统项目,主要是ERP\\CRM这些生产管理系统,用到的技术点主要是传统的单体框架,SSM框架,数据库是mysql。
后来这两年参与浦发银行生态圈项目,涉及分布式和微服务的架构

我近期参与的项目是浦发银行生态圈营销系统
主要功能是为浦发银行所有生态产品,比如手机银行app、浦惠到家app、浦慧app、甜橘app等,为这些产品提供制券和活动页面配置的管理端系统,以及这些h5活动页面的运行时服务支持
采用微服务分布式架构,开发语言采用的是groovy, 框架使用的是grails框架,包管理工具使用的是gradle, 同时集成了springCloud的相关组件

角色情况,前期作为初级开发工程师,主要以开发功能模块为主,近一年作为开发组长, 带领8个人的小团队,也参与到需求分析评估、项目流程管理,包括ci cd发布流程、以及代码审核的工作。

平时我喜欢看一些源码,会去github或者gitee逛一些开源项目, 也租了服务器购买和备案域名并搭建了一些个人项目,比如主页,在线简历,浏览器搜索页等

19年通过了工信部专项技能认证java开发工程师的考试, 21年去年我参加了国家软考软件设计师考试, 下午题目考了62发挥不错,上午题目就差5分就能通过
所以今年第一个目标就是换个工作,第二个目标就是能通过软考


基础


int类型的取值范围

-2^31 ~ 2^31 - 1

String底层为什么是final修饰的

  • 1.为了实现字符串池

只有字符串是不可变的,字符串池才有可能实现。不同的字符串变量可以指向池中的同一个字符串,节省heap空间。但如果字符串是可变的,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

  • 2.为了线程安全

只有字符串是不可变的,多线程才安全,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步,字符串本身就是线程安全的。

  • 3.为了实现String可以创建HashCode不可变性

只有字符串是不可变的,则在它创建的时候HashCode就可以被允许缓存,并且不会在每次调用 String 的 hashcode 方法时重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

  • 4.为了系统安全

而且String类中的很多方法的实现不是Java代码,而是调用操作系统的本地方法来完成的,如果String类不被final修饰,被继承重写方法的话,系统会很不安全。

虽然final修饰代表了不可变,但仅仅是引用地址不可变,并不代表了数组本身不会变

final关键字

final关键字可以修饰类,方法和变量:

  • 被final修饰的类不能被继承,即它不能拥有自己的子类;
  • 被final修饰的方法不能被重写;
  • final修饰的变量,无论是类变量、实例变量、参数变量(形参)还是局部变量,都需要进行初始化操作。

面向对象

重视对象思维,关注每个对象需要做什么,而不是关注过程和步骤

  1. 封装:

    明确标识出允许外部使用的所有成员函数和数据项

    内部细节对外部调用者透明,外部调用无需修改或者关心内部实现的细节

  2. 继承

    继承基类的方法,并作出自己的改变和扩展

    子类共性的方法或者属性(抽取出来)直接使用继承的父类的,不需要自己再定义,只需扩展自己个性化的

  3. 多态

    基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同,使得程序更易扩展

    多态有个条件就是继承,多态和继承是一脉相承的

    多态的条件:继承,方法重写,父类引用指向子类对象

使用引用变量调用的方法实际上是子类重写的方法,而不是父类的

弊端:多态调用方法不能是子类特有/独有的方法,因为能调用的方法必须是重写父类的方法,所以父类中没有的方法不能调用。

向上转型和向下转型

所以我们常说的向上转型,其实就是多态,即父类引用指向子类对象,此时调用方法实际上使用的是子类的实现, 而子类独有的方法是无法调用的

但如果将该变量强制类型转换成子类(向下转型)后,就可以使用子类特有的方法

Java自动类型转换

  1. 两种类型是彼此兼容的
  2. 转换的目的类型占得空间范围一定要大于转化的源类型

正向过程:由低字节向高字节自动转换

byte->short->int->long->float->double

逆向过程:使用强制转换,可能丢失精度。

int a=(int)3.14;

Java数据类型自动提升(注意以下讨论的是二元操作符)

Java定义了若干使用于表达式的类型提升规则:

  1. 所有的byte型. short型和char型运算后将被提升到int型(例外: final修饰的short, char变量相加后不会被自动提升。)
  2. 如果一个操作数是long形 计算结果就是long型;
  3. 如果一个操作数是float型,计算结果就是float型;
  4. 如果一个操作数是double型,计算结果就是double型;

另一种归纳方式(《Java核心技术卷I》P43):

  1. 如果两个操作数其中有一个是double类型,另一个操作就会转换为double类型。
  2. 否则,如果其中一个操作数是float类型,另一个将会转换为float类型。
  3. 否则,如果其中一个操作数是long类型,另一个会转换为long类型。
  4. 否则,两个操作数都转换为int类型。

抽象类和接口的区别

区别抽象类接口
默认的方法实现它可以有默认的方法实现接口完全是抽象的,所有方法都必须是抽象的,java1.8之后允许接口有默认实现
实现方式子类使用extends关键字来继承一个抽象类,如果子类不是抽象类的话,那么子类必须实现父类所有的抽象方法的具体实现实现类使用implements关键字来实现接口,它需要提供接口中所有声明的方法的具体实现
构造器抽象类可以有构造器接口不能有构造器
与正常Java类的区别除了你不能实例化抽象类之外,它几乎和正常的类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以是public、protected和default这些修饰符接口里的方法默认修饰符是public,也只能是public
普通变量抽象类可以对变量没有限制,和正常类一样接口中的变量必须是public static final的
多继承性由于java单继承局限,当继承了抽象类,就不能继承其他的类了一个类可以实现多个接口,并且对你继承另一个类时,没有限制
添加新的方法你可以往抽象类中添加新的正常方法,并且你不需要改变你现在的代码如果你往接口中添加新的方法,那么你必须在实现了该接口的类中实现接口的新方法

静态代码块,构造代码块和构造函数的执行顺序

静态代码块:最早执行,类被载入内存时执行,只执行一次。没有名字、参数和返回值,有关键字static。

构造代码块:执行时间比静态代码块晚,比构造函数早,和构造函数一样,只在对象初始化的时候运行。没有名字、参数和返回值。

构造函数:执行时间比构造代码块时间晚,也是在对象初始化的时候运行。没有返回值,构造函数名称和类名一致。

注意:静态代码块在类加载的时候就执行,所以的它优先级高于main()方法。

下面我们看一下有继承时的情况:

public class Parent 

    public Parent() 
        System.out.println("Parent的构造方法");
    

    static 
        System.out.println("Parent的静态代码块");
    

    
        System.out.println("Parent的构造代码块");
    


public class Son extends Parent 
    public Son() 
        System.out.println("Son的构造方法");
    

    static 
        System.out.println("Son的静态代码块");
    

    
        System.out.println("Son的构造代码块");
    

    public static void main(String[] args) 
        System.out.println("main方法");
        new Son();
    

Parent的静态代码块
        Son的静态代码块
        main方法
        Parent的构造代码块
        Parent的构造方法
        Son的构造代码块
        Son的构造方法

可以看出:父类始终先调用(继承先调用父类),并且这三者之间的相对顺序始终保持不变。

到此貌似没什么问题,但是请看如下变形:

public class B 
    public static B t1 = new B();
    public static B t2 = new B();

    
        System.out.println("构造代码块");
    

    public B() 
        System.out.println("构造函数");
    

    static 
        System.out.println("静态代码块");
    

    public static B t3 = new B();

    public static void main(String[] args) 
        new B();
    

构造代码块
        构造函数
        构造代码块
        构造函数
        静态代码块
        构造代码块
        构造函数
        构造代码块
        构造函数

因为b1、b2、b3用static修饰,与静态块处于同一优先级,同一优先级就按先后顺序来执行。

反射

在运行时动态获取调用或修改类信息,属性,方法。

Java中利用反射获取对象的方式有:

  • a)类名.class,不会加执行态代码块
Class<Object> c1 =Object.class
  • b)Class.forName(“包名.类名”) ,会执行静态代码块
Class<?> c2 = Class.forName("java.lang.Object");
  • c)类的实例对象.getClass(),会执行静态代码块
Class<?> c3 = new Object().getClass();
  • d)Class.forName(“包名.类名”, boolean,loader)
Class<?> c4 = Class.forName("com.java.oop.ClassA", false, ClassLoader.getSystemClassLoader());
  • e)类加载器.load(“包名+类名”) 不会执行静态代码块
ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> c5 = loader.loadClass("com.java.oop.ClassA");//不会执行静态代码块。

异常


集合

ArrayList动态数组扩容机制

在JDK1.8中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时(即添加第一个元素时),才真正分配容量,默认分配容量为10;

当容量不足时(容量为size,添加第size+1个元素时),先判断按照1.5倍(位运算)的比例扩容能否满足最低容量要求,若能,则以1.5倍扩容,否则以最低容量要求进行扩容。

执行add(E e)方法时,先判断ArrayList当前容量是否满足size+1的容量;在判断是否满足size+1的容量时,先判断ArrayList是否为空,若为空,则先初始化ArrayList初始容量为10,再判断初始容量是否满足最低容量要求;若不为空,则直接判断当前容量是否满足最低容量要求;若满足最低容量要求,则直接添加;若不满足,则先扩容,再添加。

ArrayList的最大容量为Integer.MAX_VALUE

ArrayList扩容的例子:ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。

假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 (如下图一);之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15(如下图二);当添加第16个数据时,继续扩容变为15 * 1.5 =22个。

HashMap的结构

HashMap是数组➕单向链表的数据结构

数组中保存的不是key value, 严格意义上讲保存的是一个Node实现了Map.Entry接口

我可以围绕它源码的三个主要方法来讲一下
put() get()和resize()

put()方法时, 先对key进行hashCode(), 得到的值再去与数组容量进行与操作,得到一个哈希值

这步操作是为了使得key的哈希值都在数组下标范围内,定位到数组下标的bucket

当这个bucket为空时,直接将这个node放进去,所以多线程下线程不安全,多条线程同时判断到bucket为空,同时放入node导致有些数据没了,解决办法有Collections.Sychronized,或使用concurrentHashMap

当这个bucket中已经有值,说明存在hash冲突,此时遍历链表对比key.equals()

如果链表中已有则覆盖oldValue,如果没有则在链表的尾部(尾插法)进行add,1.8之前是头插法,重新赋值第一个节点然后指向前一个节点,多线程情况下可能导致next节点永不为空从而造成死链

当链表长度大于8时,会转换成红黑树,利用红黑树的左旋右旋来提高效率,当小于6时又会转换成链表

get()方法时,先对key哈希,找到数组的bucket,然后遍历链表查询key.equals()是否存在

resize()方法,数组长度默认起始是16,默认负载因子为0.75f,所以当数组大小超过16*0.75=12时,会对数组进行双倍扩容

hashTable, hashMap, concerrentHashMap

hashtable中不能有null key或者value, hashmap中允许

Hashtable中使用了sycronize同步,效率较低,虽然多线程中相对安全,但也不常使用

因为可以使用Collections.sycronized去实现

或者直接使用concurrentHashMap, 它在hashMap的基础上外层多维护了一个segment

是分段进行加锁的,所以多线程时安全又提高了效率

concurrentHashMap中通过自旋锁和CAS确保不同线程获取到的是同一个segment对象

HashMap的遍历

  • 声明一个map
HashMap<String, String> map = new HashMap<>();
map.put("a","123");
map.put("b","456");
map.put("c","789");
  • 方法1:普通的foreach, 遍历的是key或者value
for (String val : map.values())
System.out.println("method1_foreach value:"+val);

for(String key : map.keySet())
System.out.println("method1_foreach key:"+ key + "; value:" + map.get(key));

  • 方法2:迭代器装载entry, 或者迭代器装载keySet, 可以在遍历中同时使用map(动态删除首选)
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext())
Map.Entry<String,String> entry = iterator.next();
System.out.println("method2_iterator: key:" + entry.getKey() + "; value:" + entry.getValue());

  • 方法3:entrySet foreach, 非常推荐
for (Map.Entry<String,String> entry : map.entrySet())
System.out.println("method3_entrySetForeach: key:" + entry.getKey() + "; value:"+entry.getValue());

  • 方法4:lamda表达式
map.forEach((key, value) ->
	System.out.println(key + ": " + value);
);
  • 方法5: stream API 单线程
map.entrySet().stream().forEach((entry) ->
	System.out.println(entry.getKey() + ": " + entry.getValue());
);

HashMap的动态删除

只能使用迭代器的方式(迭代器装载entrySet或者装载keySet),否则报异常ConcurrentModificationException

  • 迭代器装载keySet实现动态删除
Iterator<Integer> iter = map.keySet().iterator();
while(iter.hasNext()) 
	int key = iter.next();
	System.out.println(key + ": " + map.get(key));
	if(key == 2) 
		iter.remove();
	

  • 迭代器装载entrySet实现动态删除
Iterator<Map.Entry<Integer, String>> mapiter = map.entrySet().iterator();
while(mapiter.hasNext()) 
	@SuppressWarnings("unchecked")
	Map.Entry<Integer, String> entry = mapiter.next();
	System.out.println(entry.getKey() + ": " + entry.getValue());
	if(entry.getKey() == 2) 
		mapiter.remove();
	

理解和使用Java8中的时间API

hoohack


JVM

主要实现了Java的跨系统,不同系统由JVM编译处理成不同的机器码,所以不同的系统对应的JVM版本也不同

主要分为 类装载子系统、字节码执行引擎、运行时数据区

类装载子系统用于加载字节码

字节码执行引擎主要有三个作用

  • 执行字节码

  • 修改程序计数器

  • 创建和管理垃圾回收线程

    最重要的是运行时数据区,主要分为线程公有区和线程私有区

    线程公有区包含堆和方法区(元数据区)

  • 堆是存放对象的

  • 方法区用来存放常量、静态变量、类元信息

    线程私有区包含线程栈、本地方法栈、程序计数器

  • 当程序执行到native关键字修饰的本地方法的时候,会由本地方法栈分配空间

  • 程序计数器用于记录当前字节码执行到的位置,因为线程是交替获取cpu资源进行执行的,需要知道该从哪里执行

  • 线程栈中包含多个栈帧,每个线程分配一个线程栈,而线程中的方法又会分配不同的栈帧

    • 局部变量表:存放方法的局部变量
    • 操作数栈:为加减乘除等运算提供内存空间,变量先复制到操作数栈,运算完再将结果赋给相应变量. 所以i=i++还是等于原来的值, 是因为jvm先执行了压栈,将i压入操作数栈顶,然后执行自增操作,这个时候栈顶的i为1,本地变量i为2,但是又将栈顶的i弹出操作数栈并赋值给本地变量i了,所以本地变量i最终不变
    • 动态链接:我们调用一个方法的时候,那些方法名称包括括号实际上是"符号",比如math.compute(), "compute()"实际上是一种符号,动态链接就是根据这些东西去找到它的内存地址,从而找到对应的代码
    • 方法出口:一个方法执行完后,需要找到调用他的上一个方法中的位置好继续执行

当伊甸园区满了,会触发minor gc, minor gc会回收整个年轻代

幸存的对象会从伊甸园区 移动到 其中一个幸存区s0, 当再次触发minor gc, 幸存对象又会被挪到另一个空的幸存区s1, 然后s0会被清空

所以当一个对象如果一直幸存,它会在幸存区 s0 和 s1 之间反复横跳

每经历一次gc,对象的分代年龄会加1, 当加到15, 这个对象会被移动到老年代

如果幸存对象在幸存区放不下,gc后也会被直接放到老年代

当老年代放满之后,jvm会再开启一个垃圾回收线程,专门进行full gc, full gc会将年轻代和老年代都回收

当full gc之后还是没法腾出足够空间,就会内存溢出OOM, OutOfMemeryException

可达性分析

GCRoot根结点:线程栈的本地变量、静态变量、本地方法栈的变量。

将GCRoot作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余为标记的对象都是垃圾对象。

双亲委派模型

其实就是一种类加载器的层次关系

Java对象的创建过程

Java类的生命周期

当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。

一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况

加载

就是找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。

类的加载方式比较灵活,我们最常用的加载方式有两种,一种是根据类的全路径名找到相应的class文件,然后从class文件中读取文件内容;另一种是从jar文件中读取

连接

连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。

  • 验证:当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。

  • 准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的:

    • 基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
    • 引用类型的默认值为null。
    • 常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。
  • 解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。

那么什么是符号引用,什么又是直接引用呢?

我们来举个例子:我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:比如安徽省黄山市余暇村18号张三,通过这个信息我们就能找到这个人了。这里,123456790就好比是一个符号引用,而安徽省黄山市余暇村18号张三就是直接引用。在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法࿰

Android面试复习总纲

Java基础

Java底层

数据结构

基本算法

网络基础

Android基础

Android进阶

Android框架原理

以上是关于Java面试总纲的主要内容,如果未能解决你的问题,请参考以下文章

Java个人修炼总纲

剑谱总纲 | 大数据方向学习面试知识图谱

计算机网络面试大总结

java程序员面试的问题?

请问java程序员面试技巧

java程序员面试题大全含答案(2018--2019)