Java 面试题
1. 面向对象的特征有哪些?
面向对象有以下几个特征:
- 抽象:将对象的本质特征描绘出来,忽略非本质的特征。对象有什么属性,什么行为,忽略行为是怎么执行的。
- 继承:基于已有的特征属性、行为,创建出新的类。提供特征的类叫父类(基类、超类),继承特征的类叫子类(衍生类),子类会从父类中继承已有的特征。
- 封装:将特定的特征和行为捆绑包装起来。让外部调用时只需要关注有什么特征,能实现什么行为,而不知道它到底是怎么实现的。
- 多态:对象有多重形态。猫属于动物,猫是一种形态,动物也是一种形态。在代码中多态通常体现在继承父类和实现接口中。创建一个父类对象,赋值子类实例,该对象即是父类也是子类。
2. 访问修饰符public,private,protected,以及不写(默认)时的区别?
- private:私有的访问级别,只能该类内访问。
- default:默认的访问级别,只能该类,同包类访问。
- protected:保护的访问级别,只能该类,同包类,该类子类访问。
- public:公共的访问级别,其他类都可以访问。
3. String 是最基本的数据类型吗?
不是。Java中的基本数据类型只有8个:byte, short, int, long, float, double,char, boolean。除此之外都是引用类型。
4. float f=3.4; 是否正确?
不正确。3.4是double类型,赋值给float会出现精度丢失,不能直接转换为float。若需要复制给float,可通过类型强转换实现。例如:float f = (float)3.4; 或 float f = 3.4F;
5. short s1=1; s1=s1+1; 有错吗? short s1=1; s1+=1; 有错吗?
s1 是 short 类型,s1+1 会转换成 int 类型,赋值给 s1 需要进行类型转换成 short ,不然会报错。而s1+=1自动会转换成short类型,不会报错。
6. Java有没有goto?
Java 中有 goto,但无法使用。goto和const在Java中属于保留的关键字,无法使用。
7. int 和 Integer 有什么区别?
int是基础数据类型,Integer是包装数据类型,是一个类。
原始数据类型有:byte, short, int, long, float, double, char, boolean。
包装类型有:Byte, Short, Integer, Long, Float, Double, Char, Boolean。
Java5开始引入了自动装箱/拆箱机制,两种类型可相互转换。
Integer a = new Integer(3);
Integer b = 3; // 3 自动转为 Integer 类型
int c = 3;
// (a==b) 为false,两个引用并不是同一对象
// (a==c) 为true,a 自动转为 int 类型再和 c 比较
Integer对 象值在 -128 到 127 之间,则不会new信的Integer对象,而是直接引用常量池中的Integer对象。
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
// (f1==f2) 为 true
// (f3==f4) 为 false
8. &和&&的区别?
& 和 && 都是需要两遍都为 true 则返回 true。
不一样的地方是当 && 左边为false,则右边直接返回false,不会进行运算。而 & 则两遍都会进行运算后才会返回结果。
| 和 || 也是如此。
9. 解释内存中的栈(stack),堆(heap)和方法区(method area)的用法
通常我们定义一个基本数据类型的变量,一个对象的引用,还有函数调用的现场保存都是用JVM中的栈空间。
//TODO
10. Math.round(11.5)等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)等于12,Math.round(11.5)等于11。
四舍五入的原理是在参数上+0.5然后进行下取整。
11. switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在String上?
在 Java5之前,switch(expr)只能作用在 byte, short, char, int。从 Java5开始,引入了枚举类型,expr也可以是enum类型;从Java7开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
12. 用最有效率的方法计算2乘以8?
2<<3
左移3位相当于成乘以2的3次方。右移3位相当于除以2的3次方。
13. 数组有没有length()方法?String有没有length()方法?
数组没有length方法,只有length变量。String有length()方法。
14. 如何跳出当前的多重嵌套循环?
方式一:通过break标签跳出多重循环; Java定义的标签一定要紧跟在循环前同时标签名后是冒号。
public static void main(String[] args) {
// 标签
endLoop:
for (int i = 0; i < 10; i++) {
// System.out.println(i);
for (int j = 0; j < 10; j++) {
// System.out.println(i + j);
if(j==3){
break endLoop;
}
}
}
}
方式二:通过循环条件限制循环
public static void main(String[] args) {
boolean endLoop = false;
for (int i = 0; i < 10 && !endLoop; i++) {
// System.out.println(i);
for (int j = 0; j < 10 && !endLoop; j++) {
// System.out.println(i + j);
if(j==3){
endLoop = true;
}
}
}
}
方式三:抛出异常跳出中断循环
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
// System.out.println(i);
for (int j = 0; j < 10; j++) {
// System.out.println(i + j);
if(j == 3){
throw new Exception();
}
}
}
}
15. 构造器(constructor)是否可以被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。
16. 两个对象值相同(x.equals(y)==true),可有不同的 hash code 吗?
如果两个对象相同(equals方法返回true),那么它们的hashCode值一定相同。
如果两个对象的hashCode相同,他们并不一定相同。
17. 是否可以继承 String 类?
String 类是 final 类,不可以被继承。
18. 当一个对象被当做参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
属于值传递。
Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用(地址值)。
19. String 和 StringBuilder、StringBuffer的区别
String 是只读字符串,意味着String引用的字符串内容是不能被改变的。
StringBuffer 和 StringBuilder 类表示的字符串对象是可以直接进行修改的。
StringBuilder 是Java 5中引入的,和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 要高。
20. 重载(Overload)和重写(Override)得区别
方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。
重载发生在一个类中,同样的方法如果有不同的参数列表(参数类型不同 或 参数个数不同 或 都不同)则视为重载。
重写发生在子类与父类之间,子类重写父类中的方法要求名字相同,参数类型和个数相同,返回类型相同或其子孙类。
21. JVM加载 class 文件的原理机制
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的。Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
// TODO
22. char 类型变量中能不能存储一个中文汉字,为什么?
char类型可以存储一个中文汉字。
在Java中,char类型用于表达单个 Unicode 字符。 Unicode 编码范围包括了世界上所有的语言文字,因此可以使用 char 来存储汉字。
23. 抽象类(abstract class)和接口(interface)有什么区别?
抽象类和接口都不能实例化,但可以定义抽象类和接口类型的引用。
抽象类里的抽象方法需要子类继承并全部实现,否则子类只能继续定义为抽象类。
接口里未实现的方法需要该接口的实现类来全部实现。
抽象类中可定义构造器,可定义具体的实现方法,而接口中不能定义构造器,也不能定义具体的实现方法。
抽象类中的成员可以定义成员变量,而接口中定义的成员变量实际上都是常量。
有抽象方法的类必须被声明为抽象类,而抽象类并不一定必须有抽象方法。
24. 静态嵌套类(Static Nested Class)和内部类(Inner Class)的区别?
Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖外部类实例被实例化。
而通常的内部类需要在外部类实例化后才能实例化。
25. Java中会存在内存泄漏吗?
理论上Java因为有垃圾回收机制(GC),不会存在内存泄漏问题(这也是Java被广泛适用于服务器端变成的一个重要原因)。
然后再实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄漏的发生。
例如Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收期是不会回收这些对象的。这些对象中可能存在无用的垃圾对象,如果不及时关闭或清空则可能导致内存泄漏。
26. 抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被 synchronized 修饰?
都不能。
抽象方法需要子类重写,而静态的方法是无法被重写的。
本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的。
synchronized 和方法的实现细节有关,抽象方法不没有实现细节。
27. 静态变量和实例变量的区别
静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个实例对象。
一个类不管创建多少个实例对象,静态变量在内存中有且仅有一个拷贝。
静态变量可实现让多个实例对象共享内存。
实例变量必须依存于某一个实例对象,需要先创建实例对象后,通过该对象才能访问到它。
28. 是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?
不可以。
静态方法只能访问静态成员。静态方法无须实例化,即可通过类访问。
非静态方法需要归属的类被实例化对象后,通过实例对象才可以进行访问。
29. 如何实现对象克隆?
- 实现 Cloneable 接口并重写Object类中的clone()方法;
- 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
基于序列化和反序列化实现的克隆,通过泛型限定,可监察处要克隆的对象是否支持序列化。这项检查是编译器完成的,在编译时暴露出问题,比在运行时才暴露问题好。
30. GC是什么?为什么要有GC?
// TODO
31. String s = new String("wxy"); 创建了几个字符串对象?
两个对象。一个是静态区的 "wxy",一个是用于new创建在堆上的对象。
32. 接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?
接口可以继承接口,而且支持多重继承。
抽象类可以实现接口。
抽象类可继承具体类也可以继承抽象类。
33. 一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?
可以。但一个源文件中最多只能有一个公开类(public class)。而且文件名必须和公开类的类名完全一致。
34. Anonymous Inner Class(匿名内部类)是否可以继承其他类?是否可以实现接口?
可以继承其他类或实现其他接口。
35. 内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
在Java中,内部类(或嵌套类)确实可以引用其包含类(外部类)的成员,包括私有成员。内部类自动持有对其外部类实例的引用,因此它可以访问外部类的所有成员,无论这些成员的访问权限如何。
但是,有一些细节和限制需要注意:
静态上下文中的访问限制:
- 如果内部类是静态的(静态内部类),那么它不能直接访问外部类的非静态成员。静态内部类不持有外部类实例的引用,因此它只能访问外部类的静态成员。
局部内部类的限制:
- 定义在方法内部的局部内部类只能访问其所在方法的final变量或有效final变量(即那些虽然没有被声明为final,但实际上没有被修改过的变量)。从Java 8开始,这个限制有所放宽,允许访问“有效final”的局部变量,这些变量在初始化后没有被修改过。
匿名内部类的限制:
- 匿名内部类的限制与局部内部类相似,因为它们都是在某个作用域内定义的,所以它们只能访问该作用域内可见的final或有效final变量。
潜在的内存泄漏:
- 由于非静态内部类持有外部类的引用,如果内部类的实例比外部类的实例存在的时间更长,那么外部类的实例将不会被垃圾收集器回收,这可能导致内存泄漏。因此,在设计内部类时需要注意其生命周期管理。
命名冲突:
- 如果外部类和内部类有同名的成员(字段、方法等),那么在内部类中可以通过外部类的类名加上.来明确引用外部类的成员,以避免命名冲突。但是,通常建议避免这种命名冲突以提高代码的可读性。
总的来说,Java内部类可以引用其外部类的成员,但需要根据内部类的类型(静态或实例)以及定义的位置(方法内部或类级别)来注意访问限制和潜在的内存管理问题。
36. Java 中的 final 关键字有哪些用法?
- 修饰类,表示该类不能被继承。
- 修饰方法;表示方法不能被重写。
- 修饰变量,表示变量只能一次复制,复制以后值不能被修改(常量)。
37. 指出下面程序的运行结果
class A {
static {
System.out.print("1");
}
public A() {
System.out.print("2");
}
}
class B extends A {
static {
System.out.print("a");
}
public B() {
System.out.print("b");
}
}
public class Demo {
public static void main(String[] args) {
A ab = new B();
ab = new B();
}
}
执行结果:1a2b2b。
创建对象时构造器的调用顺序是:先初始化父类及其静态成员,初始化子类及其静态成员,调用父类构造器并初始化非静态成员,最后调用自身构造器并初始化非静态成员。静态成员只初始化一次。
38. 如何将字符串转换为基本数据类型?如何将基本类型转换成字符串?
基础类型对应的包装类中,提供了名为 parseXXX(String) 或 valueOf(String) 得转换方法,可将字符串转换为基础数据类型。
基础数据类型通过拼接字符串可获得其所对应的字符串。通过 String.valueOf()方法也可以返回相应的字符串。
39. 如何实现字符串的替换及反转?
通过 String 类的 replace() 方法来实现字符串的替换。这个方法接受两个参数,第一个参数是需要被替换的字符串,第二个参数是替换成新的字符串。
实现字符串反转有多种方法:
方法一:通过 StringBuilder 的 reverse() 方法实现字符串反转。
public String reverseString(String input) {
StringBuilder sb = new StringBuilder(input);
return sb.reverse().toString();
}
方法二:使用字符数组实现
public String reverseString(String input) {
char[] charArray = input.toCharArray();
int left = 0;
int right = charArray.length - 1;
while (left < right) {
char temp = charArray[left];
charArray[left] = charArray[right];
charArray[right] = temp;
left++;
right--;
}
return new String(charArray);
}
方法三:使用递归实现
public String reverseString(String input) {
if (input == null || input.length() <= 1) {
return input;
}
return reverseString(input.substring(1)) + input.charAt(0);
}
方法四:使用java8 Stream API 实现
import java.util.stream.Collectors;
public String reverseString(String input) {
// 将字符串转换为字符流
List<Character> characters = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.toList());
// 反转字符列表
Collections.reverse(characters);
// 将反转后的字符列表转换回字符串
return characters.stream().map(Object::toString)
.collect(Collectors.joining());
}
使用 StringBuilder 反转字符串更高效。
40. 怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?
String s1 = "你好";
String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");
41. 日期和时间
41.1 如何获得年月日时分秒?
创建 java.util.Calendar 或 java.time.LocalDateTime 类的实例,通过其实例的get方法可获得年月日时分秒对应内容。
public class DateTimeTest {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));
// Java 8
LocalDateTime dt = LocalDateTime.now();
System.out.println(dt.getYear());
System.out.println(dt.getMonthValue()); // 1 - 12
System.out.println(dt.getDayOfMonth());
System.out.println(dt.getHour());
System.out.println(dt.getMinute());
System.out.println(dt.getSecond());
}
}
41.2 如何获得从1970年1月1日0时0分0秒到现在的毫秒数?
// 方法1
Calendar.getInstance().getTimeInMillis();
// 方法2
System.currentTimeMillis();
//方法3 Java 8
Clock.systemDefaultZone().millis();
41.3 如何获得某月的最后一天?
Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH);
41.4 如何格式化日期?
利用 java.text.DataFormat 的子类(例如 SimpleDateFormat 类)中的format(Date)方法可将日期格式化。
import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatExample {
public static void main(String[] args) {
// 创建一个Date对象
Date date = new Date();
// 创建一个SimpleDateFormat对象,并指定日期格式
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 使用SimpleDateFormat对象来格式化日期
String formattedDate = formatter.format(date);
// 输出格式化后的日期
System.out.println("当前日期和时间是: " + formattedDate);
}
}
java8中可以使用 java.time.format.DateTimeFormatter 来格式化时间日期。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeFormatterExample {
public static void main(String[] args) {
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
// 创建一个DateTimeFormatter对象,并指定日期时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 使用DateTimeFormatter对象来格式化日期时间
String formattedDateTime = now.format(formatter);
// 输出格式化后的日期时间
System.out.println("当前日期和时间是: " + formattedDateTime);
// 如果你只想格式化日期部分,可以使用LocalDate
LocalDate today = LocalDate.now();
// 创建一个只用于日期格式化的DateTimeFormatter对象
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 使用DateTimeFormatter对象来格式化日期
String formattedDate = today.format(dateFormatter);
// 输出格式化后的日期
System.out.println("今天的日期是: " + formattedDate);
}
}
42. 打印昨天的当前时刻
import java.util.Calendar;
class YesterdayCurrent {
public static void main(String[] args){
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}
在Java 8中,可以用下面的代码实现相同的功能。
import java.time.LocalDateTime;
class YesterdayCurrent {
public static void main(String[] args) {
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);
System.out.println(yesterday);
}
}
43. 比较一下 Java 和 JavaScript
Java 是原 Sun Microsystems 公司推出的面向对象的程序设计语言,特别适合互联网应用程序开发。Java 的前身是 Oak 语言。
JavaScript 是 Netscape 公司的产品,为了扩展 Netscape 公司的产品,为了扩展 Netscape 浏览器的功能二开发的一种可以嵌入 Web 页面中运行的基于对象和事件驱动的解释性语言。 JavaScript 的前身是 LiveScript。
Java 和 JavaScript 都是编程语言,它们都是面向对象的编程语言,都可以用于开发 Web 应用程序,都具有跨平台的能力。
它们不同的地方是:
- Java 是一种静态类型的编程语言,而 JavaScript 是一种动态类型的编程语言。这意味着在 Java 中,变量的类型需要再编译时确定,而在 JavaScript 中,类型是在运行时确定的。
- Java 是一种编译型语言,需要将源代码编译成字节码才能运行。而 JavaScript 是一种解释型语言,不需要编译,直接在浏览器中运行。
- Java 的主要用于开发后端应用程序,如服务器应用程序和 Android 应用程序。而 JavaScript 主要用于开发前端应用程序,如网页交互和动态效果。
- Java 有严格的语法和编码规范,对错误和异常处理有较高的要求。而 JavaScript 的语法相对灵活,容错性较高。Java 用了强类型变量检查,变量在编译之前必须作声明。而JavaScript 中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript 的解释器在运行时检查推断其数据类型。
- Java 和 JavaScript 在线程并发处理方面有较大的区别。
- 并发模型:
- Java 使用基于线程的并发模型,即每个线程有自己的执行上下文,并且可以通过锁机制来实现线程间的同步与互斥。
- JavaScript 使用基于事件循环的并发模型,即所有任务都被放入一个队列中,通过时间循环来处理任务的执行。
- 多线程支持:
- Java 原生支持多线程,可以通过 Thread 类或者实现 Runnable 接口来创建线程。
- JavaScript 是单线程的,只有一个主线程来执行所有的任务。但是 JavaScript 可以通过 Web Worker 创建多个子线程来处理一些计算密集型的任务。
- 同步与异步:
- Java 中可以通过使用 synchronized 关键字来实现线程同步,保证线程的安全性。
- JavaScript 使用回调函数、Promise、asynce/await 等方式来处理异步任务,避免了阻塞主线程。
- 内存模型:
- Java 中每个线程都有自己的栈空间,共享栈空间。
- JavaScript 中所有的变量都是共享的,包括全局变量和函数内部的局部变量。
- 锁机制:
- Java 中可以使用synchronized 关键字来实现线程间的同步与互斥。
- JavaScript 中没有原生的锁机制,但可以使用互斥量(Mutex)来实现类似的功能。
- 并发模型:
44. 什么时候用断言(assert)
断言通常用于检查程序中的变量假设是否成立,它是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true,如果不是则会抛出 AssertionError 异常。
我们可以使用断言验证方法的参数是否满足预期条件,验证方法的返回值是否满足预期条件,验证程序中的假设情况是否成立。
断言默认是禁用的,可以使用 -ea 参数启用断言。例如: java -ea MyApp。在生产环境中,通常不建议使用断言,因为它会影响性能。
断言检查通常在开发和测试时开启,为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。
断言使用关键字 assert 来表示。断言语句格式为 “ assert condition : message; ”。其中 condition 是一个布尔表达式,message 是一个可选的字符串,用于在断言失败时提供额外的信息。
45. Error 和 Exception 有什么区别?
Error 是指严重的问题,程序无法自身恢复并继续执行,通常是由于系统级故障或资源耗尽导致的。例如 OutOfMemoryError、StackOverflowError等。
Exception 是指程序运行过程中可能出现的错误或异常情况,可以被程序捕获并运行相应的处理。Exception 又分为两种类型:受检异常(Checked Exception)和非受检异常(Unchecked Exception)。
- 受检异常:指在编译阶段必须捕获或声明抛出的异常。对于受检异常,程序必须显示处理或在方法名中声明抛出,否则无法通过编译。例如:IOException、SQLException等。
- 非受检异常:指在运行时可能出现的异常。对于非受检异常,程序可以选择捕获和处理,但没有强制要求。例如:NullPointerException、ArrayIndexOutOfBoundsException等。
46. try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?
会执行。会在返回给调用者之前执行。
47. Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?
使用try来捕捉异常,使用catch来处理捕捉的异常,使用throw来向上抛出异常,使用throws来声明方法可能抛出的异常,finally表示无论有无发生异常最终都会执行。
