本文主要是介绍200多道 java常见面试题以及答案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
慢慢更新中☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺
Java面试题以及答案
一、 Java基础
1. JDK、JRE、JVM的有什么区别?
先看一下这三个都是什么意思:
JDK(Java Development kit): 指的是Java开发工具集。JDK是整个Java得核心,包括了Java基础类库、Java运行环境(jre)和Java开发工具。
JRE(Java Runtime Environment):指得是Java运行时的环境。Java程序运行的时候必须要JRE的支持,如果只安装JRE的话,那么这个系统可以跑任何的Java程序,但是不可以开发Java程序。
JVM(Java Virtual Machine):指得是Java虚拟机,它是Java可跨平台运行的核心,所有的Java程序首先被编译成.class文件,它能够将class文件中的字节码指令进行识别并调用操作系统上的API完成工作,从而与操作系统进行交互,只有JVM还不能将class执行,因为在解释class的时候Jvm需要调用解释所需要的类库lib,而Jre包含了lib类库,Jvm屏蔽了具体操作系统的相关信息,使得Java程序只需要生成在java虚拟机上运行的目标代码(字节码),就可以不加修改的在多个平台运行。
用一张图宏观的理解一下:
JDK包含JRE包含JVM…
2.==和qeuals的区别是什么?
在= = 和equals 对比区别之前,应先了解一下Jvm内存分配的问题。
在Jvm中内存分为两种:堆内存和栈内存。这俩的区别在于,当我们创建一个对象时(new Object),就会调用他的构造函数来开辟一个空间,将数据对象存在堆内存中,与此同时在栈内存中也生成了对应的引用,当我们在后续代码中调用的时候用的也都是栈内存中的引用,还需注意的一点,基本数据类型是存储在栈内存中。
在这里有可以引申出什么是基本数据类型和引用类型,Java有8大基本类型,其中分为了4类,分类是:整型包括(byte、short、int、long),泛型包括(float、double),字节型包括(char),布尔型包括(boolean),其中bate占用一个字节,short和char占用两个字节,int和float占用四个字节,double和long占用八个字节,boolean只有true和false,这八种数据变量中直接存储值。其中八大基本类型也有对应的包装类,提供了很多方法,且不进行初始化时默认为空,且属于引用类型;引用类型主要是一些类、接口、数组,引用类型变量中存储的是地址、对应的地址存储数据;
= = 和equals的区别到底是什么那?
= = 对比的是基本类型中‘值’的内容,equals对比的是两个对象的‘内存地址’的内容,值得注意的是equals不能作用于对比基本类型的变量,如果equals方法没有重写的话,则对比的是引用类型的变量所指向的对象的地址;
总结来说:== 在基本数据类型:值内容, 引用类型时:equals 重写:值内容 , equals不重写:地址
下面附上几组代码对比,更容易了解 = = 和equals的不同之处。
Integer aaa = new Integer(5);Integer bbb = new Integer(5);int a = 10;int b = 10;String str1 = new String("justice");String str2 = new String("justice");String str3;str3 = str1;System.out.println(aaa == bbb);System.out.println(aaa.equals(bbb));System.out.println(a == b);System.out.println(str1 == str2);System.out.println(str1.equals(str2));System.out.println(str1 == str3);System.out.println(str1.equals(str3));执行下来依次结果为:false,true,true,false,true,true,true;
分析问题原因结果:aaa和bbb 都是包装类型,而且new了不同的对象,变量存储地址,所以在= =的时候对比的是内存地址而不是值故为false,equals为true;a和b是基本类型,= =对比值内容相同就可以故为true;str1和str2都是String类型,属于引用类型,变量存储地址,所以= =为false,equals为true;创建str3的时候,str3指向了str1,所以str1和str3的指向同一个地址,存储的数据自然相同,所以均为true;
3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
不对!
两个对象的equals相等,那么他们的hashCode必须相等,反之则不一定;
两个对象的 = = 相等,那么他们的hashCode一定相等,反之不一定成立;
hashCode的常规协定:
1、在Java应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致的返回相同的整数,前提是对象进行equals比较时所用到的信息没有修改过。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2、两个对象的equals()相等,那么对这两个对象的每个对象调用hashCode()都必须生成相同的整数结果。
3、两个对象的equals()不相等,那么对这两个对象中的任一对象上调用hashCode()不要求一定生成相同结果。但是为不相等的两个对象生成不用的整数结果可以提高哈希表的性能。
看段代码:
String str1 = "精彩"; String str2 = "笔记"; System. out. println(String. format("str1:%d | str2:%d", str1. hashCode(),str2. hashCode()));System. out. println(str1. equals(str2));结果:str1:1179395 | str2:1179395 false
很显然“精彩”和“笔记”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。
4.final 在 java 中有什么作用?
在Java中final关键字可以用来修饰类、修饰方法、修饰变量(包括成员量(全局变量)和内部变量);
在修饰类的时候:表示该类是不可以被继承的;
在修饰方法的时候:表示该方法是不可以被重写的;
在修饰变量的时候:表示变量只能赋值一次,以后该值是不能修改的(常量)
被final修饰的变量是不能够改变的.但是这里的不能够改变,对于不同的数据类型是有不同的意义的。当final修饰基础类型数据的时候,这个值初始化后将不能改变,当final修饰的引用类型数据的时候后,也就是在修饰对象的时候,引用类型初始化后会永远的指向一个内存地址,不可修改。但是该内存中保存的对象信息是可以进行修改的。看一张图:
如上图,变量a在初始化后永远指向003这块内存,而这块内存在初始化后的值永远是100。
在看一张final修饰引用类型数据的图:
如上图,变量p指向了0003这块内存0003内存中保存的是对象p的句柄(p对象数据的内存地址),这个句柄是永远不可修改的,但是p对象的数据是可以修改的;
代码示例
public static void main(String[] args) {final Person p = new Person(20, "炭烧生蚝");p.setAge(18); //可以修改p对象的数据System.out.println(p.getAge()); //输出18Person pp = new Person(30, "蚝生烧炭");p = pp; //这行代码会报错, 不能通过编译, 因为p经final修饰永远指向上面定义的p对象, 不能指向pp对象. }
final修饰变量的本质:final修饰的变量会指向一块固定的内存,这块固定的内存中的值是不可以改变的。引用类型变量所指向的对象之所以可以改变,是因为引用类型的变量不是直接指向对象数据的,而是指向被引用的对象的。所以被final修饰的引用类型的变量永远指向一个内存地址。不可以修改,但是对象的数据是可以修改的。
5.java 中的 Math.round(-1.5) 等于多少?
Math.round(-1.5)的值是 -1;
先来了解一下Math.round()的修饰原则,+0.5向下取整。也有说四舍六入五成双先来看一下正数还是比较符合规则System.out.println(Math.round(11.6)); →结果:12System.out.println(Math.round(11.5)); →结果:12System.out.println(Math.round(11.4)); →结果:11但是负数就很奇葩,负数的时候小数小于等于5 就按照四舍五成双,小数大于5的话就是绝对值+0.5,向下取整;System.out.println(Math.round(-11.6)); →结果:12System.out.println(Math.round(-11.5)); →结果:11System.out.println(Math.round(-11.4)); →结果:11
6.String 属于基础的数据类型吗?
String类并不是基本数据类,而是一个类(class),是C++、java等编程语言中的字符串。
String类是不可变的,对String类的任何改变,都是返回一个新的String类对象。 String 对象是 System.Char 对象的有序集合,用于表示字符串。String 对象的值是该有序集合的内容,并且该值是不可变的。
7.java 中操作字符串都有哪些类?它们之间有什么区别?
有String、StringBuffer、StringBuilder
三个相同点:都是字符缓冲区、可变的字符序列、具有相同的构造和方法
区别1内存:String是不可变对象,每次操作String都会产生一个新的对象,然后指针指向新的String对象
区别2线程:String是不可变类是线程安全的、StringBuffer是线程安全的,同步锁,多线程仍可以保证数据安全;StringBuilder线程不安全,多线程无法保证数据安全;
区别3效率:StringBuilder>StringBuffer>String
不频繁增改字符,就用String;否则用StringBuffer或StringBuilder
8.String str="i"与 String str=new String(“i”)一样吗?
不一样;
因为String str =“i”;是声明了一个常量,Java虚拟机会将其放到常量池中,而String str = new String(“i”); 是创建了一个对象,在内存中开辟了一块新的空间,会存放在堆内存中。
在Java虚拟机常量池中,在你创建一个新的String str2 = "i"对象时,如果值i存在的话,会把值的地址直接给到 str2上不会创建一个新的对象。如果在你new一个新的对象的时候会在堆内存中创建一个新的对象分配一个新的地址。引申一个问题:String s3=new String(“Hello”)会创建几个对象;首先来说Jvm会先去常量池中寻找 Hello , 如果没有则会在常量池中先创建一个字符常量,放到字符常量池中,如有则跳过。当遇到new的时候,还会在堆内存中创建一个新的对象,存储Hello 。所以可能会创建一个也可能会创建两个,这没有固定的答案;
9.如何将字符串反转?
第一种:利用 StringBuffer 或 StringBuilder 的 reverse 成员方法:
String str = "ABCD";System.out.println(new StringBuilder(str).reverse().toString());System.out.println(new StringBuffer(str).reverse().toString());
第二种:利用 String 的 toCharArray 方法先将字符串转化为 char 类型数组,然后将各个字符进行重新拼接:
String str = "ABCD";char[] chars = str.toCharArray();String str2 = "";for (int i = chars.length - 1; i >= 0; i--) {str2 += chars[i];}System.out.println(str2);
第三种:利用 String 的 CharAt 方法取出字符串中的各个字符:
String str = "ABCD";String str2 = "";int len = str.length();for (int i = 0; i < len; i++) {str2 = str.charAt(i) + str2;}System.out.println(str2);
10.String 类的常用方法都有那些?
equals:字符串是否相同equalsIgnoreCase:忽略大小写后字符串是否相同compareTo:根据字符串中每个字符的Unicode编码进行比较compareToIgnoreCase:根据字符串中每个字符的Unicode编码进行忽略大小写比较indexOf:目标字符或字符串在源字符串中位置下标lastIndexOf:目标字符或字符串在源字符串中最后一次出现的位置下标valueOf:其他类型转字符串charAt:获取指定下标位置的字符codePointAt:指定下标的字符的Unicode编码concat:追加字符串到当前字符串isEmpty:字符串长度是否为0contains:是否包含目标字符串startsWith:是否以目标字符串开头endsWith:是否以目标字符串结束format:格式化字符串getBytes:获取字符串的字节数组getChars:获取字符串的指定长度字符数组toCharArray:获取字符串的字符数组join:以某字符串,连接某字符串数组length:字符串字符数matches:字符串是否匹配正则表达式replace:字符串替换replaceAll:带正则字符串替换replaceFirst:替换第一个出现的目标字符串split:以某正则表达式分割字符串substring:截取字符串toLowerCase:字符串转小写toUpperCase:字符串转大写trim:去字符串首尾空格
11.抽象类必须要有抽象方法吗?
不一定, 抽象类必须有关键字abstract来修饰。抽象类可以不包含抽象方法,但是有抽象方法的类一定是抽象类
12.抽象类能使用 final 修饰吗?
不能,因为抽象类的主要目的就是子类集成然后实现抽象类中的内部方法,使用final修饰的类都是不可以被继承的。
13.普通类、抽象类和接口的区别?
1、普通类可以实例化,接口不可以实例化因为没有构造方法,抽象类如果想要实例化,抽象类必须指向实现所有抽象方法的子类对象(抽象类可以直接实例化,直接重写自己的抽象方法),接口必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承、接口需要被子类实现。
3、接口只能做方法的声明,抽象类可以做方法的声明也可以做方法的实现。
4、接口里定义的变量只能是公共的静态常量,抽象类中定义的变量就是普通变量。
5、抽象类里面的抽象方法必须全部被子类实现,如果子类不能全部实现抽象方法,那么该子类只能是抽象类。一个实现接口的时候,如果不能全部实现接口方法,那么该类只能是抽象类。
6、抽象方法只能声明不能实现。接口是设计的结果、抽象类是重构的结果。
7、抽象类中可以没有抽象方法。如果一个类中有抽象方法,那么该类是抽象类。
8、抽象方法要被实现,所以不能是静态的,也不能是私有的。
9、接口可以继承接口、并可以多继承接口,但类只能单继承。
10、接口中的常量:有固定的修饰符public static final ;不能用private和protected修饰/本质上都是static的而且是final类型的,不管加不加static修饰
11、接口中的抽象方法:有固定修饰符-public abstract
13、接口细节:
若接口中方法或变量没有写public,static,final / public,abstract ,会自动补齐 。
接口中的成员都是共有的。
接口与接口之间是继承关系,而且可以多继承。
接口不能被实例化。
一个类可以实现多个接口。
在java开发中,我们经常把常用的变量,定义在接口中,作为全局变量使用,访问形式:接口名.变量名。
一个接口不能继承其它的类,但是可以继承别的接口。
一个重要的原则:当一个类实现了一个接口,要求该类把这个接口的所有方法全部实现。
注意:
① 抽象类和接口都是用来抽象具体的对象的,但是接口的抽象级别更高。
② 抽象类可以有具体的方法和属性,接口只能有抽象方法和静态常量。
③ 抽象类主要用来抽象级别,接口主要用来抽象功能。
④ 抽象类中,且不包含任何的实现,派生类必须覆盖它们。接口中所有方法都必须是未实现的。
⑤ 接口方法,访问权限必须是公共的 public。
⑥ 接口内只能有公共方法,不能存在成员变量。
⑦ 接口内只能包含未被实现的方法,也叫抽象方法,但是不能用 abstract 关键字。
⑧ 抽象类的访问速度比接口要快,接口是稍微有点慢,因为它需要时间去寻找在类中实现的方法。
⑨ 抽象类,除了不能被实例化外,与普通 java 类没有任何区别。
⑩ 抽象类可以有 main 方法,接口没有 main 方法。
⑪ 抽象类可以用构造器,接口没有。
⑫ 抽象方法可以有 public、protected 和 default 这些修饰符,接口只能使用默认 public。
⑬ 抽象类,添加新方法可以提供默认的实现,不需要改变原有代码。接口添加新方法,子类必须实现。
⑭ 抽象类的子类用 extends 关键字继承,接口用 implements 来实现。
什么时候用抽象类和接口那?
1.若果你拥有一些方法并且想让他们中的一些有默认实现,那就用抽象类。
2. 如果你想实现多重继承,那么必须使用接口。由于 java 不支持多继承,子类不能继承多个父类,但是可以实现多个接口,因此你可以使用接口来实现它。
3. 如果基本基本功能在不断变化,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么所有实现类都需要改变。
14.java 中 IO 流分为几种?
按照流的流向分为:输入流和输出流
按照操作单元划分为:字节流和字符流
按照流的角色分为:节点流和处理流
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式分类结构图:
按操作对象分类结构图:
15.BIO、NIO、AIO 有什么区别?
BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。
16.Files的常用方法都有哪些?
isExecutable:文件是否可以执行isSameFile:是否同一个文件或目录isReadable:是否可读isDirectory:是否为目录isHidden:是否隐藏isWritable:是否可写isRegularFile:是否为普通文件getPosixFilePermissions:获取POSIX文件权限,windows系统调用此方法会报错setPosixFilePermissions:设置POSIX文件权限getOwner:获取文件所属人setOwner:设置文件所属人createFile:创建文件newInputStream:打开新的输入流newOutputStream:打开新的输出流createDirectory:创建目录,当父目录不存在会报错createDirectories:创建目录,当父目录不存在会自动创建createTempFile:创建临时文件newBufferedReader:打开或创建一个带缓存的字符输入流probeContentType:探测文件的内容类型list:目录中的文件、文件夹列表find:查找文件size:文件字节数copy:文件复制lines:读出文件中的所有行move:移动文件位置exists:文件是否存在walk:遍历所有目录和文件write:向一个文件写入字节delete:删除文件getFileStore:返回文件存储区newByteChannel:打开或创建文件,返回一个字节通道来访问文件readAllLines:从一个文件读取所有行字符串setAttribute:设置文件属性的值getAttribute:获取文件属性的值newBufferedWriter:打开或创建一个带缓存的字符输出流readAllBytes:从一个文件中读取所有字节createTempDirectory:在特殊的目录中创建临时目录deleteIfExists:如果文件存在删除文件notExists:判断文件不存在getLastModifiedTime:获取文件最后修改时间属性setLastModifiedTime:更新文件最后修改时间属性newDirectoryStream:打开目录,返回可迭代该目录下的目录流walkFileTree:遍历文件树,可用来递归删除文件等操作
二、 容器
17.java容器都有哪些?
Java中容器主要分为两大类:Collection和Map
18.Collection 和 Collections 有什么区别?
Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法,Collection 接口在Java类库中有很多具体的提现,Collectionj接口的意义是为各种具体的集合提供了最大化的统一操作方式。List,Set,Queue接口都继承Collection。直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。
Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全等),大多数方法时用来处理线性表的。此类不需要实例化,就像一个工具,服务于java的Collection框架。
19.List、Set、Map 之间的区别是什么?
首先看一下数组和集合的区别:数组的大小是固定的,并且数组内只能有相同数据类型的元素。集合的大小是不固定的,在不知道有多少种数据类型的情况下用集合。
List:元素具有可重复性,有序性。
Set:元素具有无序性,唯一性;
Map:采用<key,value>存储元素,key唯一,value可重复;
20. HashMap 和 Hashtable 有什么区别?
线程安全上:Hashtable是线程安全的,HashMap是线程不安全的;因为Hashtable的元素get和put都是用synchronized 修饰的,HashMap没有;
性能上:因为Hashtable是线程安全的,每个方法都会堵塞其他线程,性能比较差,HashMap性能就更好。
为空上:Hashtable不允许键或值为空,HashMap则都可以为空;具体原因:
public synchronized V put(K key, V value) {// Make sure the value is not nullif (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;int hash = key.hashCode();...}HashMap源码:static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
可以看出Hashtable为空会抛出异常,HashMap做了为空的处理;
实现方式不同:Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
容量扩容上:Hashtable的初始容量为16 HashMap 初始容量为11;两者的负载因子默认都是:0.75。当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
迭代器上:HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
21. 如何决定使用 HashMap 还是 TreeMap?
TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键(key)。
HashMap<K,V>的Key值实现散列hashCode(),分布是散列的、均匀的,不支持排序;数据结构主要是桶(数组),链表或红黑树。适用于在Map中插入、删除和定位元素。
如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候会使用HashMap。
22. 说一下 HashMap 的实现原理?
HashMap 基于 Hash 算法实现的,通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
23. 说一下 HashSet 的实现原理?
①是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
②当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
③HashSet的其他操作都是基于HashMap的。
这篇关于200多道 java常见面试题以及答案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!