什么是面向对象?
面向对象是一种思想,世间万物都可以看做一个对象,这里只讨论面向对象编程(OOP),Java 是一个支持并发、基于类和面向对象的计算机编程语言.
请说说面向对象的特征?
封装、继承、多态、抽象。
重载和重写的区别?
- 1)重写 override
方法名、参数、返回值相同。
子类方法不能缩小父类方法的访问权限。
子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
存在于父类和子类之间。
方法被定义为 final 不能被重写。 - 2)重载 overload
参数类型、个数、顺序至少有一个不相同。
不能重载只有返回值不同的方法名。
存在于父类和子类、同类中。
JDK、JRE、JVM 分别是什么关系?
- 1.JDK
JDK 即为 Java 开发工具包,包含编写 Java 程序所必须的编译、运行等开发工具以及 JRE。开发工具如:
用于编译 Java 程序的 javac 命令。
用于启动 JVM 运行 Java 程序的 Java 命令。
用于生成文档的 Javadoc 命令。
用于打包的 jar 命令等等。
简单说,就是 JDK 包含 JRE 包含 JVM。 - 2.JRE
JRE 即为 Java 运行环境,提供了运行 Java 应用程序所必须的软件环境,包含有 Java 虚拟机(JVM)和丰富的系统类库。系统类库即为 Java 提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。
简单说,就是 JRE 包含 JVM。 - 3.JVM
JVM 即为 Java 虚拟机,提供了字节码文件(.class)的运行环境支持。
Java 中的几种基本数据类型是什么?各自占用多少字节?
Java 支持的数据类型包括基本数据类型和引用类型。
基本数据类型如下:
整数值型:byte (8,-128~127)、short (16,-32768~32767,)、int(32,-2147483648-2147483647)、long(64,19位)
字符型:char
浮点类型:float(-3.4×10^-38 ~ 3.4×10^38,有效数位 7)、double
布尔型:boolean
整数型:默认 int 型,小数默认是 double 型。Float 和 Long 类型的必须加后缀。比如:float f = 100f 。
引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。
引用类型包括类、接口、数组等。
特别注意,String 是引用类型不是基本类型。
什么是值传递和引用传递?
- 值传递,是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
- 引用传递,一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。
一般认为,Java 内的传递都是值传递,Java 中实例对象的传递是引用传递。
是否可以在 static 环境中访问非 static 变量?
static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对static变量进行初始化。
如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
char 型变量中能不能存一个中文汉字?为什么?
- 在 C 语言中,char 类型占 1 个字节,而汉字占 2 个字节,所以不能存储。
- 在 Java 语言中,char 类型占 2 个字节,而且 Java 默认采用 Unicode 编码,一个 Unicode 码是 16 位,所以一个 Unicode 码占两个字节,Java 中无论汉字还是英文字母,都是用 Unicode 编码来表示的。所以,在 Java 中,char 类型变量可以存储一个中文汉字。
String、StringBuffer、StringBuilder 的区别?
Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它们可以储存和操作字符串。
- String ,是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer/StringBuilder 类,表示的字符串对象可以直接进行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 要高。
对于三者使用的总结
- 操作少量的数据 = String 。
- 单线程操作字符串缓冲区下操作大量数据 = StringBuilder 。
- 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
String s = new String("xyz") 会创建几个对象?
首先,在 String 池内找,如果找到 "xyz" 字符串,不创建 "xyz" 对应的 String 对象,否则创建一个对象。然后,遇到 new 关键字,在内存上创建 String 对象,并将其返回给 s ,又一个对象。
所以,总共是 1 个或者 2 个对象。
String 为什么是不可变的?
简单的来说,String 类中使用 final 关键字字符数组保存字符串。代码如下:
// String.java
private final char[] value;
所以 String 对象是不可变的。
而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[] value ,但是没有用 final 关键字修饰。所以这两种对象都是可变的。
StringTokenizer 是什么?
StringTokenizer ,是一个用来分割字符串的工具类。
示例代码如下:
StringTokenizer st = new StringTokenizer(”Hello World”);
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
输出如下:
Hello
World
什么是自动拆装箱?
自动装箱和拆箱,就是基本类型和引用类型之间的转换。
为什么要转换?开发中不能直接地向集合( Collection )中放入原始类型值,因为集合只接收对象。
int 和 Integer 有什么区别?
int 是基本数据类型。
Integer 是其包装类,注意是一个类。
Integer 的缓存策略
在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。适用于整数区间 -128 到 +127,Integer 缓存策略仅在自动装箱的时候有用,使用构造器创建的 Integer 对象不能被缓存,Java 编译器把原始类型自动转换为封装类的过程称为自动装箱,这相当于调用 valueOf那个方法在创建新的 Integer 对象之前会先在 IntegerCache.cache 中查找。有一个专门的 Java 类来负责 Integer 的缓存。Byte,Short,Long 有固定范围: -128 到 127,对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。
equals 与 == 的区别?
- 值类型(int,char,long,boolean 等)用 == 判断相等性。
- 对象引用
== 判断引用所指的对象是否是同一个。
equals 方法,是 Object 的成员函数,有些类会覆盖(override) 这个方法,用于判断对象的等价性
如何在父类中为子类自动完成所有的 hashCode 和 equals 实现?这么做有何优劣?
父类的 equals ,一般情况下是无法满足子类的 equals 的需求。
比如所有的对象都继承 Object ,默认使用的是 Object 的 equals 方法,在比较两个对象的时候,是看他们是否指向同一个地址。但是我们的需求是对象的某个属性相同,就相等了,而默认的 equals 方法满足不了当前的需求,所以我们要重写 equals 方法。如果重写了 equals 方法,就必须重写 hashCode 方法,否则就会降低 Map 等集合的索引速度。
这样的 a.hashCode() 有什么用,与 a.equals(b) 有什么关系?
- equals 方法,用于比较对象的内容是否相等。当覆盖了 equals 方法时,比较对象是否相等将通过覆盖后的 equals 方法进行比较(判断对象的内容是否相等)。
- hashCode 方法,大多在集合中用到。将对象放入到集合中时,首先判断要放入对象的 hashCode 值与集合中的任意一个元素的 hashCode 值是否相等,如果不相等直接将该对象放入集合中。如果 hashCode 值相等,然后再通过 equals 方法判断要放入对象与集合中的任意一个对象是否相等,如果 equals 判断不相等,直接将该元素放入到集合中,否则不放入。
有没有可能 2 个不相等的对象有相同的 hashCode?
可能会发生,这个被称为哈希碰撞。当然,相等的对象,即我们重写了 equals 方法,一定也要重写 hashCode 方法,否则将出现我们在 HashMap 中,相等的对象作为 key ,将找不到对应的 value 。
所以说,equals 和 hashCode 的关系会是:
equals 不相等,hashCode 可能相等。
equals 相等,请重写 hashCode 方法,保证 hashCode 相等。
final、finally、finalize 的区别?
1)final 是修饰符关键字。
如果一个类被声明为 final ,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract 的,又被声明为 final 的。
将变量或方法声明为 final ,可以保证它们在使用中不被改变。被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能使用,不能重写。2)finally
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
在以下 4 种特殊情况下,finally块不会被执行:
1.在 finally 语句块中发生了异常。
2.在前面的代码中用了 System.exit() 退出程序。
3.程序所在的线程死亡。
4.关闭 CPU 。3)finalize
finalize ,是方法名。
Java 允许使用 #finalize() 方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。
1.它是在 Object 类中定义的,因此所有的类都继承了它。
2.子类覆盖 finalize() 方法,以整理系统资源或者执行其他清理工作。
3.finalize() 方法,是在垃圾收集器删除对象之前对这个对象调用的。
抽象类和接口有什么区别?
从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
- Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法。
- 类可以实现很多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
- 抽象类可以在不提供接口方法实现的情况下实现接口。
- Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。
- Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public 。
- 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 #main(String[] args) 方法的话是可以被调用的。
继承和组合的区别在哪?
- 继承:指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。在 Java 中,此类关系通过关键字 extends 明确标识,在设计时一般没有争议性。
- 组合:组合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即 has-a 的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。
因为组合能带来比继承更好的灵活性,所以有句话叫做“组合优于继承”。
讲讲类的实例化顺序?
初始化顺序如下:
父类静态变量
父类静态代码块
子类静态变量、
子类静态代码块
父类非静态变量(父类实例成员变量)
父类构造函数
子类非静态变量(子类实例成员变量)
子类构造函数
什么是内部类?
简单的说,就是在一个类、接口或者方法的内部创建另一个类。这样理解的话,创建内部类的方法就很明确了。当然,详细的可以看看 《Java 内部类总结(吐血之作)》 文章。
内部类的作用是什么?
内部类提供了更好的封装,除了该外围类,其他类都不能访问。Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?
可以继承其他类或实现其他接口,在 Java 集合的流式操作中,我们常常这么干。内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
什么是 Java IO ?
Java IO 相关的类,在 java.io
包下,具体操作分成面向字节(Byte)和面向字符(Character)两种方式。如下图所示:
什么是 Java 序列化
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
- 可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
- 序列化是为了解决在对对象流进行读写操作时所引发的问题。
反序列化的过程,则是和序列化相反的过程。
另外,我们不能将序列化局限在 Java 对象转换成二进制数组,例如说,我们将一个 Java 对象,转换成 JSON 字符串,或者 XML 字符串,这也可以理解为是序列化。
如何实现 Java 序列化?
1.将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。
2.使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
3.使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。
反序列化
要恢复的话则用输入流。
Java 序列话中,如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,使用 transient 关键字修饰。
当对象被序列化时,阻止实例中那些用此关键字修饰的的变量序列化。
当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
transient 只能修饰变量,不能修饰类和方法。
如何实现对象克隆?
一般来说,有两种方式:
1、实现 Cloneable 接口,并重写 Object 类中的 #clone() 方法。可以实现浅克隆,也可以实现深克隆。
2、实现 Serializable 接口,通过对象的序列化和反序列化实现克隆。可以实现真正的深克隆。
- 浅度克隆: 浅度克隆对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于非基本数据类型的属性,仅仅复制一份引用给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。
- 深度克隆: 在浅度克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类,也实现克隆,这样对于非基本数据类型的属性,复制的不是一份引用,即新产生的对象和原始对象中的非基本数据类型的属性指向的不是同一个对象
error 和 exception 有什么区别?CheckedException 和 RuntimeException 有什么区别?
Java 的异常体系,基于共同的祖先 java.lang.Throwable 类。如下图所示:
- Error(错误),表示系统级的错误和程序不必处理的异常,是 Java 运行环境中的内部错误或者硬件问题。例如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由 Java 虚拟机抛出的。
- Exception(异常),表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。Exception 又分为运行时异常和受检查异常。
1.RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止逻辑,因此,编译器不检查这些异常。
2.CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理),所以称之为受检查异常。
异常的使用的注意地方?
1.不要将异常处理用于正常的控制流(设计良好的 API 不应该强迫它的调用者为了正常的控制流而使用异常)。
2.对可以恢复的情况使用受检异常,对编程错误使用运行时异常。
3.避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)。
4.优先使用标准的异常。
5.每个方法抛出的异常都要有文档。
6.保持异常的原子性
7.不要在 catch 中忽略掉捕获到的异常。
Throwable 类常用方法?
getMessage() 方法:返回异常发生时的详细信息。
getCause() 方法:获得导致当前 Throwable 异常的 Throwable 异常。
getStackTrace() 方法:获得 Throwable 对象封装的异常信息。
printStackTrace() 方法:在控制台上打印。
请列出 5 个运行时异常?
NullPointerException
IndexOutOfBoundsException
ClassCastException
ArrayStoreException
BufferOverflowException
throw 与 throws 的区别 ?
throw ,用于在程序中显式地抛出一个异常。
throws ,用于指出在该方法中没有处理的异常。每个方法必须显式指明哪些异常没有处理,以便该方法的调用者可以预防可能发生的异常。最后,多个异常用逗号分隔。
异常处理中 finally 语句块的重要性?
不管程序是否发生了异常, finally 语句块都会被执行,甚至当没有catch 声明但抛出了一个异常时, finally 语句块也会被执行。
finally 语句块通常用于释放资源, 如 I/O 缓冲区, 数据库连接等等。
异常被处理后异常对象会发生什么?
异常对象会在下次 GC 执行时被回收。
说说反射的用途及实现?
Java 反射机制主要提供了以下功能:
1.在运行时构造一个类的对象。
2.判断一个类所具有的成员变量和方法。
3.调用一个对象的方法。
4.生成动态代理。
反射的应用很多,很多框架都有用到:
1.Spring 框架的 IoC 基于反射创建对象和设置依赖属性。
2.Spring MVC 的请求调用对应方法,也是通过反射。
3.JDBC 的 Class#forName(String className) 方法,也是使用反射。
反射中,Class.forName 和 ClassLoader 区别?
这两者,都可用来对类进行加载。差别在于:
- Class#forName(...) 方法,除了将类的 .class 文件加载到JVM 中之外,还会对类进行解释,执行类中的 static 块。
- ClassLoader 只干一件事情,就是将 .class 文件加载到 JVM 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。
什么时候用断言(assert)?
断言,在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。
一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。
断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为 false ,那么系统会报告一个AssertionError 错误。
实际场景下,我们会在 Spring 的源码中看到,它自己封装了 Assert 类,实现更方便的断言功能,并且,在生产环境下也启用。
另外,在单元测试中,也会使用自己封装的断言类,判断执行结果的正确与错误。
Java 对象创建的方式?
1.使用 new 关键字创建对象。
2.使用 Class 类的 newInstance 方法(反射机制)。
3.使用 Constructor 类的 newInstance 方法(反射机制)。
4.使用 clone 方法创建对象。
5.使用(反)序列化机制创建对象。
参考与推荐如下文章:
- 《2018 年最新 Java 面试题及答案整理》
- 《32 道常见的 Java 基础面试题》
- 《Java 总结》
- 《Java 基础知识》
- Java 面试宝典