• 冒险村物语
  • 英雄无敌3塔防
  • 驾考家园手游

java基础面试题

2022-03-22

一、前置知识

1.原码反码补码

1.正数的原码、反码、补码都一样
2.负数的反码 = 它的原码符号位不变,其他位取反(取反的意思:0 换成 1 、 1 换成 0 )
3.负数的补码 = 它的反码 +1
4.0的反码、补码都是0

2.进制转换

任意进制到十进制的转换


十进制到任意进制转换

十进制到二进制的转换

十进制到十六进制的转换

3.位运算

二、基础语法

1.重载和重写的区别


在编译阶段根据静态类型可以确定重载方法
重写方法是在运行期间动态分派的

2.面向对象的三大特性

封装

继承

继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法。


Java中类只支持单继承,不支持多继承

Java中类支持多层继承

多态


3.创建字符串对象的区别对比

4.== 和 equals 区别是什么、String 中的 equals 方法是如何重写的、为什么要重写 equals 方法、为什么要重写 hashCode 方法

== 和 equals 区别是什么

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; }

内部实现分为三个步骤:先比较引用是否相同(是否为同一对象),再判断类型是否一致(是否为同一类型),最后比较内容是否一致。

为什么要重写equals方法

1.举例说明不重写会出现什么问题

public static void main(String[] args) { Student stu1 = new Student("张三", 20); Student stu2 = new Student("张三", 20); System.out.println(stu1.equals(stu2)); }

不重写结果会返回false
2.说明出现问题的原因

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

equals方法默认继承的Object类,默认比较两个对象的内存地址
3.说明重写后解决了问题

public class Student { private String name; private Integer stuId; public Student(String name, Integer stuId) { this.name = name; this.stuId = stuId; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Student)) { return false; } Student student = (Student) o; return Objects.equals(name, student.name) && Objects.equals(stuId, student.stuId); } }

重写后返回true

为什么要重写hashCode方法

1.举例说明不重写会出现什么问题

public static void main(String[] args) { Student stu1 = new Student("张三", 20); Student stu2 = new Student("张三", 20); Map map = new HashMap(); map.put(stu1, 1); map.put(stu2, 2); System.out.println(map); }

利用hashmap举例,上面已经重写了equals方法,理论上key相同的话,应该数据会被覆盖,但实际上打印了两个值,如下

2.说明出现问题的原因
因为在HashMap中,是根据key的hashcode值确定数组下标位置的,如果数组下标位置相同,先比较key的hashcode值,如果相同,再用equals方法对比,如果相等,则覆盖。
而如果不重写hashcode方法,默认比较的是物理地址,则stu1和stu2的hashcode值不同

3.说明重写后解决了问题

@Override public int hashCode() { return Objects.hash(name, stuId); }

重写后两个对象的hashcode值相同,可以覆盖,结果打印一个值

5.String s1 = new String(“abc”)在内存中创建了几个对象。

1.说出结果
创建 1 个或者 2 个对象
2.解释结果
new String 会先去常量池中判断有没有此字符串,如果有则只在堆上创建一个字符串并且指向常量池中的字符串,如果常量池中没有此字符串,则会创建 2 个对象,先在常量池中新建此字符串,然后把此引用返回给堆上的对象

7.描述一下值传递和引用传递的区别

1.解释概念
值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,在函数内对参数进行修改,不会影响到实际参数。

引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数内,在函数内对参数所进行的修改,将影响到实际参数。

2.java参数传递特点
如果是基础类型,那么在方法传递的时候复制的是(栈中)基础类型的引用和值,如果是引用类型复制的是(栈中)引用地址。

3.结论
在Java中本质上只有值传递,也就说Java的传参只会传递它的副本,并不会传递参数本身
详解

9.请描述一下static关键字和final关键字的用法

static关键字

1.总体说明static关键字的应用范围
static 关键字可用于变量、方法、代码块和内部类,表示某个特定的成员只属于某个类本身,而不是该类的某个对象。

2.分条说明用法
2.1静态变量
1)由于静态变量属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问。
2)不需要初始化类就可以访问静态变量。

2.2静态方法
1)Java 中的静态方法在编译时解析,因为静态方法不能被重写(方法重写发生在运行时阶段,为了多态)。
2)抽象方法不能是静态的。
3)静态方法不能使用 this 和 super 关键字。
4)成员方法可以直接访问其他成员方法和成员变量。
5)成员方法也可以直接方法静态方法和静态变量。
6)静态方法可以访问所有其他静态方法和静态变量。
7)静态方法无法直接访问成员方法和成员变量。

2.3静态代码块
1)一个类可以有多个静态代码块。
2)静态代码块的解析和执行顺序和它在类中的位置保持一致。

2.4静态内部类
1)静态内部类不能访问外部类的所有成员变量。
2)静态内部类可以访问外部类的所有静态变量,包括私有静态变量。
3)外部类不能声明为 static。

final关键字

1.从使用上说
1.1final变量
final变量有成员变量或者是本地变量(方法内的局部变量),在类成员中final经常和static一起使用,作为类常量使用。其中类常量必须在声明时初始化,final成员常量可以在构造函数初始化。
1.2final方法
final方法表示该方法不能被子类的方法重写
1.3final类
final类不能被继承,final类中的方法默认也会是final类型的,java中的String类和Integer类都是final类型的。
2.使用细节
1)final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。final变量一旦被初始化后不能再次赋值。
2)本地变量必须在声明时赋值。 因为没有初始化的过程
3)在匿名类中所有变量都必须是final变量。
4)final方法不能被重写, final类不能被继承
5)接口中声明的所有变量本身是final的。类似于匿名类
6)final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
7)final方法在编译阶段绑定,称为静态绑定(static binding)。
8)将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
3.final好处(为什么使用final)
1)提高了性能,JVM在常量池中会缓存final变量
2)final变量在多线程中并发安全,无需额外的同步开销
3)final方法是静态编译的,提高了调用速度
4)final类创建的对象是只可读的,在多线程可以安全共享

10.接口和抽象类的区别是什么

1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
2.1抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。
2.2抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
模板式设计:
大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。
辐射式设计:
比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新

11.Integer缓存池

1.缓冲范围-128-127
2.面试题

1.Integer i1 = new Integer(127); Integer i2 = new Integer(127); System.out.println(i1 == i2); //False,i1和i2都是通过new分配的内存空间,所以指向两个不同的内存空间 System.out.println(i1.equals(i2));//True 2.Integer i3 = new Integer(128); Integer i4 = new Integer(128); System.out.println(i3 == i4); //False,i3和i4都是通过new分配的内存空间,所以指向两个不同的内存空间 System.out.println(i3.equals(i4));//True 3.Integer i5 = 127; Integer i6 = 127; System.out.println(i5 == i6); //True System.out.println(i5.equals(i6));//True 4.Integer i7 = 128; Integer i8 = 128; System.out.println(i7 == i8); //False System.out.println(i7.equals(i8));//True

3.底层源码和原理

private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k = 127; } private IntegerCache() {} }

1)内部使用的cache数组作为缓存数据的机制;
2)使用了low、high分别作为缓存的数值的最小值和最大值;
3)low实际上是一个固定的值-128;
4)high是可以设置的:

12.为什么Java是解释性语言

1.编译性语言和解释性语言概念
编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。

解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束!
2.java为什么是解释性语言
虽然java也需要编译,编译成.class文件,但是并不是机器可以识别的语言,而是字节码,最终还是需要 jvm的解释,才能在各个平台执行,这同时也是java跨平台的原因。

13.权限修饰符

14.代码块

概述

在Java中,使用 { } 括起来的代码被称为代码块

分类

局部代码块

public class Test { /* 局部代码块 位置:方法中定义 作用:限定变量的生命周期,及早释放,提高内存利用率 */ public static void main(String[] args) { { int a = 10; System.out.println(a); } // System.out.println(a); } }

构造代码块

public class Test { /* 构造代码块: 位置:类中方法外定义 特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行 作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性 */ public static void main(String[] args) { Student stu1 = new Student(); Student stu2 = new Student(10); } } class Student { { System.out.println("好好学习"); } public Student(){ System.out.println("空参数构造方法"); } public Student(int a){ System.out.println("带参数构造方法..........."); } }

静态代码块

public class Test { /* 静态代码块: 位置:类中方法外定义 特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次 作用:在类加载的时候做一些数据初始化的操作 */ public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(10); } } class Person { static { System.out.println("我是静态代码块, 我执行了"); } public Person(){ System.out.println("我是Person类的空参数构造方法"); } public Person(int a){ System.out.println("我是Person类的带...........参数构造方法"); } }

15.内部类

概念

在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类

定义格式

/* 格式: class 外部类名{ 修饰符 class 内部类名{ } } */ class Outer { public class Inner { } }

访问特点

/* 内部类访问特点: 内部类可以直接访问外部类的成员,包括私有 外部类要访问内部类的成员,必须创建对象 */ public class Outer { private int num = 10; public class Inner { public void show() { System.out.println(num); } } public void method() { Inner i = new Inner(); i.show(); } }

成员内部类

class Outer { private int num = 10; private class Inner { public void show() { System.out.println(num); } } public void method() { Inner i = new Inner(); i.show(); } } public class InnerDemo { public static void main(String[] args) { //Outer.Inner oi = new Outer().new Inner(); //oi.show(); Outer o = new Outer(); o.method(); } }

class Outer { static class Inner { public void show(){ System.out.println("inner..show"); } public static void method(){ System.out.println("inner..method"); } } } public class Test3Innerclass { /* 静态成员内部类演示 */ public static void main(String[] args) { // 外部类名.内部类名 对象名 = new 外部类名.内部类名(); Outer.Inner oi = new Outer.Inner(); oi.show(); Outer.Inner.method(); } }

局部内部类

class Outer { private int num = 10; public void method() { int num2 = 20; class Inner { public void show() { System.out.println(num); System.out.println(num2); } } Inner i = new Inner(); i.show(); } } public class OuterDemo { public static void main(String[] args) { Outer o = new Outer(); o.method(); } }

匿名内部类

概述


匿名内部类在开发中的使用

当发现某个方法需要,接口或抽象类的子类对象,我们就可以传递一个匿名内部类过去,来简化传统的代码

/* 游泳接口 */ interface Swimming { void swim(); } public class TestSwimming { public static void main(String[] args) { goSwimming(new Swimming() { @Override public void swim() { System.out.println("铁汁, 我们去游泳吧"); } }); } /** * 使用接口的方法 */ public static void goSwimming(Swimming swimming){ /* Swimming swim = new Swimming() { @Override public void swim() { System.out.println("铁汁, 我们去游泳吧"); } } */ swimming.swim(); } }

16.Lambda表达式

函数式编程思想

Lambda表达式的使用前提

Lambda表达式和匿名内部类的区别

17.java中的异常

概述

编译时异常和运行时异常的区别

18.对象序列化和反序列化

19.反射

概述

获取Class类对象的三种方式

20.xml

概述

作用

作为配置文件的优势


常见的解析工具
DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java

xml解析

xml解析就是从xml中获取到数据

21.注解

元注解

元注解就是描述注解的注解

二、String相关

1.String,String Builder,String Buffer 的区别

2.为什么String的是不可变的?

因为存储数据的char数组是使用final进行修饰的,所以不可变。

3.为什么String Buffer是线程安全的?

这是因为在StringBuffer类内,常用的方法都使用了synchronized 进行同步所以是线程安全的,然而StringBuilder并没有。这也就是运行速度StringBuilder > StringBuffer的原因了。

三、反射

1.什么是反射

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

三、java中的集合类

1.HashMap相关、HashMap和 Hashtable的区别、HashMap和HashSet 区别、HashMap底层实现、HashMap的长度为什么是 2 的幂次方、HashMap多线程操作导致死循环问题、HashMap的线程安全实现有哪些、ConcurrentHashMap 的底层实现。

HashMap简介

HashMap采用key/value存储结构,每个key对应唯一的value,查询和修改的速度都很快,能达到O(1)的平均时间复杂度。它是非线程安全的,且不保证元素存储的顺序。key值和vaule都可以为空。

不保证元素存储的顺序

Map mp = new HashMap(); for (int i=0; i System.out.println(entry.getKey() + "-" + entry.getValue()); }

结果

key1-value1 key2-value2 key0-value0 key5-value5 key6-value6 key3-value3 key4-value4 key9-value9 key7-value7 key8-value8

如果想要有序,采用LinkedHashMap

HashMap存储结构

jdk1.7中采用数组+链表

1.7中hashmap有一个内部类Entry,每添加一个新的键值对就将它封装到一个Entry对象中,然后放进容器中。

jdk1.8采用数组+链表+红黑树

数组元素和链表节点采用内部类Node类实现,与jdk1.7的Entry类对比,仅仅只是换了名字。而红黑树采用TreeNode类实现

1)引入红黑树的原因
加快查询速度
数组的查询效率为O(1),链表的查询效率是O(k),红黑树的查询效率是O(log k),k为桶(数组的一个元素又称作桶)中的元素个数,所以当元素数量非常多的时候,转化为红黑树能极大地提高效率。

2)转换阈值
数组长度大于等于64,且链表长度大于等于8,将链表转换成红黑树。
当红黑树中节点数量小于等于6,则将红黑树还原回链表

3)为什么转换阈值是8和6
根据泊松分布,hash碰撞发生8次的概率非常低,此时链表性能已经非常差,后序可能还会继续发生hash碰撞。转换成红黑树主要出于性能考虑。
而红黑树转链表的阈值为6,主要是因为,如果也将该阈值设置于8,那么当hash碰撞在8时,会反生链表和红黑树的不停相互激荡转换,白白浪费资源。

4)为什么采用红黑树而不是平衡二叉树
对于插入删除等操作,红黑树效率更高。原因是平衡二叉树对于平衡性的要求更高,在调整平衡的过程中消耗更大。
详解

5)补充:红黑树的介绍
是一种特殊的二叉查找树

HashMap解决hash冲突的方法

1)hashmap采用的方法
链地址法
hashmap中的hash算法本质上分为三步:取key的hashCode值、高位运算、取模运算

方法一: //jdk1.8 & jdk1.7 static final int hash(Object key) { int h; // h = key.hashCode() 为第一步 取hashCode值 // h ^ (h >>> 16) 为第二步 高位参与运算 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 方法二: //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 static int indexFor(int h, int length) { //第三步 取模运算 return h & (length-1); }

jdk1.8中优化了高位运算的算法,(h = key.hashCode()) ^ (h >>> 16)主要考虑性能优化,使得数组较小的时候,也能保证考虑到高低位都参与到Hash的计算中,同时不会有太大的开销。

第三步取模运算采用h & (table.length -1)。因为HashMap底层数组的长度总是2的n次方。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。

2)解决hash冲突的主要方法
(1)开放定址法(2)链地址法(3)再哈希法(4)公共溢出区域法

HashMap主要参数

//默认的初始容量为16 static final int DEFAULT_INITIAL_CAPACITY = 1

人气推荐

知识阅读

精彩推荐

  • 游戏
  • 软件
查看更多>>