1、解释下什么是⾯向对象?⾯向对象和⾯向过程的区别?
⾯向对象是⼀种基于⾯向过程的编程思想,是向现实世界模型的⾃然延伸,这是⼀种“万物皆对象”的编程思想。由 执⾏者变为指ഀ者,在现实⽣活中的任何物体都可以归为⼀类事物,⽽每⼀个个体都是⼀类事物的实例。⾯向对象 的编程是以对象为中⼼,以消息为驱动。
区别:
(1)编程思路不同:⾯向过程以实现功能的函数开发为主,⽽⾯向对象要⾸先抽象出类、属性及其⽅法,然后通 过实例化类、执⾏⽅法来完成功能。
(2)封装性:都具有封装性,但是⾯向过程是封装的是功能,⽽⾯向对象封装的是数据和功能。
(3)⾯向对象具有继承性和多态性,⽽⾯向过程没有继承性和多态性,所以⾯向对象优势很明显
2、⾯向对象的三⼤特性?分别解释下?
(1)封装:通常认为封装是把数据和操作数据的⽅法封装起来,对数据的访问只能通过已定义的接⼝。
(2)继 承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为⽗类(超类/基类),得到继承信息 的被称为⼦类(派⽣类)。
(3)多态:分为编译时多态(⽅法᯿载)和运⾏时多态(⽅法᯿写)。要实现多态需 要做两件事:⼀是⼦类继承⽗类并᯿写⽗类中的⽅法,⼆是⽤⽗类型引⽤⼦类型对象,这样同样的引⽤调⽤同样的 ⽅法就会根据⼦类对象的不同⽽表现出不同的⾏为。
⼏点补充
(1)⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅法⼦类是⽆法 访问,只是拥有。因为在⼀个⼦类被创建的时候,⾸先会在内存中创建⼀个⽗类对象,然后在⽗类对象外部放上⼦ 类独有的属性,两者合起来形成⼀个⼦类的对象;
(2)⼦类可以拥有⾃⼰属性和⽅法;
(3)⼦类可以⽤⾃⼰的 ⽅式实现⽗类的⽅法。(重写)
3、JDK、JRE、JVM 三者之间的关系?
JDK(Java Development Kit):是 Java 开发⼯具包,是整个 Java 的核⼼,包括了 Java 运⾏环境 JRE、Java ⼯具 和 Java 基础类库。
JRE( Java Runtime Environment):是 Java 的运⾏环境,包含 JVM 标准实现及 Java 核⼼类库。
JVM(Java Virtual Machine):是 Java 虚拟机,是整个 Java 实现跨平台的最核⼼的部分,能够运⾏以 Java 语⾔ 写作的软件程序。所有的 Java 程序会⾸先被编译为 .class 的类⽂件,这种类⽂件可以在虚拟机上执⾏。
4、重载和重写的区别?
(1)重载:编译时多态、同⼀个类中同名的⽅法具有不同的参数列表、不能根据返回类型进⾏区分【因为:函数 调⽤时不能指定类型信息,编译器不知道你要调哪个函数】;
(2)重写(⼜名覆盖):运⾏时多态、⼦类与⽗类之间、⼦类᯿写⽗类的⽅法具有相同的返回类型、更好的访问 权限。
5、Java 中是否可以重写⼀个 private 或者 static ⽅法?
Java 中 static ⽅法不能被覆盖,因为⽅法覆盖是基于运⾏时动态绑定的,⽽ static ⽅法是编译时静态绑定的。 static ⽅法跟类的任何实例都不相关,所以概念上不适⽤。
Java 中也不可以覆盖 private 的⽅法,因为 private 修饰的变量和⽅法只能在当前类中使⽤, 如果是其他的类继承 当前类是不能访问到 private 变量或⽅法的,当然也不能覆盖。
静态⽅法补充:静态的⽅法可以被继承,但是不能重写。如果⽗类和⼦类中存在同样名称和参数的静态⽅ 法,那么该⼦类的⽅法会把原来继承过来的⽗类的⽅法隐藏,⽽不是重写。通俗的讲就是⽗类的⽅法和⼦类的⽅法是两个没有关系的⽅法,具体调⽤哪⼀个⽅法是看是哪个对象的引⽤;这种⽗⼦类⽅法也不在存在多态的性质。
6、构造器是否可以被重写?
在讲继承的时候我们就知道⽗类的私有属性和构造⽅法并不能被继承,所以 Constructor 也就不能被 Override(重写),但是可以 Overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。
7、构造⽅法有哪些特性?
(1)名字与类名相同;
(2)没有返回值,但不能⽤ void 声明构造函数;
(3)成类的对象时⾃动执⾏,⽆需调⽤。
8、在 Java 中定义⼀个不做事且没有参数的构造⽅法有什么作⽤?
Java 程序在执⾏⼦类的构造⽅法之前,如果没有⽤ super() 来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参 数的构造⽅法”。
因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤ super() 来调⽤⽗类中特定的构造 ⽅法,则编译时将发⽣错误,因为 Java 程序在⽗类中找不到没有参数的构造⽅法可供执⾏。解决办法是:在⽗类 ⾥加上⼀个不做事且没有参数的构造⽅法。
9、Java 中创建对象的⼏种⽅式?
1、使⽤ new 关键字;
2、使⽤ Class 类的 newInstance ⽅法,该⽅法调⽤⽆参的构造器创建对象(反射): Class.forName.newInstance();
3、使⽤ clone() ⽅法;
4、反序列化,⽐如调⽤ ObjectInputStream 类的 readObject() ⽅法。
10、抽象类和接⼝有什么区别?
(1)抽象类中可以定义构造函数,接⼝不能定义构造函数
(2)抽象类中可以有抽象⽅法和具体⽅法,⽽接⼝中只能有抽象⽅法(public abstract)
(3)抽象类中的成员权限可以是 public、默认、protected(抽象类中抽象⽅法就是为了᯿写,所以不能被 private 修饰),⽽接⼝中的成员只可以是 public(⽅法默认:public abstrat、成员变量默认:public static final);
(4)抽象类中可以包含静态⽅法,⽽接⼝中不可以包含静态⽅法;
JDK 8 中的改变:
1、在 JDK1.8中,允许在接⼝中包含带有具体实现的⽅法,使⽤ default 修饰,这类⽅法就是默认⽅法。
2、抽象类中可以包含静态⽅法,在 JDK1.8 之前接⼝中不能包含静态⽅法,JDK1.8 以后可以包含。之前不能包含 是因为,接⼝不可以实现⽅法,只可以定义⽅法,所以不能使⽤静态⽅法(因为静态⽅法必须实现)。现在可以包 含了,只能直接⽤接⼝调⽤静态⽅法。JDK1.8 仍然不可以包含静态代码块。
11、静态变量和实例变量的区别?
静态变量:是被 static 修饰的变量,也称为类变量,它属于类,因此不管创建多少个对象,静态变量在内存中有且 仅有⼀个拷⻉;静态变量可以实现让多个对象共享内存。
实例变量:属于某⼀实例,需要先创建对象,然后通过对象才能访问到它。
12、short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1; 呢?有没有错误?
对于 short s1 = 1; s1 = s1 + 1; 来说,在 s1 + 1 运算时会⾃动提升表达式的类型为 int ,那么将 int 型值赋值给 short 型变量,s1 会出现类型转换错误。
对于 short s1 = 1; s1 += 1; 来说,+= 是 Java 语⾔规定的运算符,Java 编译器会对它进⾏特殊处理,因此可以正确 编译。
13、Integer 和 int 的区别?
(1)int 是 Java 的⼋种基本数据类型之⼀,⽽ Integer 是 Java 为 int 类型提供的封装类;
(2)int 型变量的默认值是 0,Integer 变量的默认值是 null,这⼀点说明 Integer 可以区分出未赋值和值为 0 的 区分;
(3)Integer 变量必须实例化后才可以使⽤,⽽ int 不需要。
Integer 和 int 的⽐较延伸:
1、由于 Integer 变量实际上是对⼀个 Integer 对象的引⽤,所以两个通过 new ⽣成的 Integer 变量永远是不相等 的,因为其内存地址是不同的;
2、Integer 变量和 int 变量⽐较时,只要两个变量的值是相等的,则结果为 true。因为包装类 Integer 和基本数据 类型 int 类型进⾏⽐较时,Java 会⾃动拆包装类为 int,然后进⾏⽐较,实际上就是两个 int 型变量在进⾏⽐较;
3、⾮ new ⽣成的 Integer 变量和 new Integer() ⽣成的变量进⾏⽐较时,结果为 false。因为⾮ new ⽣成的 Integer 变量指向的是 Java 常量池中的对象,⽽ new Integer() ⽣成的变量指向堆中新建的对象,两者在内存中的 地址不同;
4、对于两个⾮ new ⽣成的 Integer 对象进⾏⽐较时,如果两个变量的值在区间 [-128, 127] 之间,则⽐较结果为 true,否则为 false。Java 在编译 Integer i = 100 时,会编译成 Integer i = Integer.valueOf(100),⽽ Integer 类型 的 valueOf 的源码如下所示:
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ?
Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
从上⾯的代码中可以看出:Java 对于 [-128, 127] 之间的数会进⾏缓存,⽐如:Integer i = 127,会将 127 进⾏缓 存,下次再写 Integer j = 127 的时候,就会直接从缓存中取出,⽽对于这个区间之外的数就需要 new 了。
包装类的缓存:
Boolean:全部缓存
Byte:全部缓存
Character:<= 127 缓存
Short:-128 — 127 缓存
Long:-128 — 127 缓存
Integer:-128 — 127 缓存
Float:没有缓存
Doulbe:没有缓存
14、装箱和拆箱
⾃动装箱是 Java 编译器在基本数据类型和对应得包装类之间做的⼀个转化。⽐如:把 int 转化成 Integer,double 转化成 Double 等等。反之就是⾃动拆箱。
原始类型:boolean、char、byte、short、int、long、float、double
封装类型:Boolean、Character、Byte、Short、Integer、Long、Float、Double
15、switch 语句能否作⽤在 byte 上,能否作⽤在 long 上,能否作⽤在 String 上?
在 switch(expr 1) 中,expr1 只能是⼀个整数表达式或者枚举常量。⽽整数表达式可以是 int 基本数据类型或者 Integer 包装类型。由于,byte、short、char 都可以隐式转换为 int,所以,这些类型以及这些类型的包装类型也 都是可以的。⽽ long 和 String 类型都不符合 switch 的语法规定,并且不能被隐式的转换为 int 类型,所以,它们 不能作⽤于 switch 语句中。不过,需要注意的是在 JDK1.7 版本之后 switch 就可以作⽤在 String 上了。
16、Object 的常⽤⽅法有哪些?
clone ⽅法:⽤于创建并返回当前对象的⼀份拷⻉;
getClass ⽅法:⽤于返回当前运⾏时对象的 Class;
toString ⽅法:返回对象的字符串表示形式;
finalize ⽅法:实例被垃圾回收器回收时触发的⽅法;
equals ⽅法:⽤于⽐较两个对象的内存地址是否相等,⼀般需要重写;
hashCode ⽅法:⽤于返回对象的哈希值;
notify ⽅法:唤醒⼀个在此对象监视器上等待的线程。如果有多个线程在等待只会唤醒⼀个
notifyAll ⽅法:作⽤跟 notify() ⼀样,只不过会唤醒在此对象监视器上等待的所有线程,⽽不是⼀个线程
wait ⽅法:让当前对象等待
17、final、finally、finalize 的区别?
final:⽤于声明属性、⽅法和类,分别表示属性不可变、⽅法不可覆盖、被其修饰的类不可继承;
finally:异常处理语句结构的⼀部分,表示总是执⾏;
finallize:Object类的⼀个⽅法,在垃圾回收时会调⽤被回收对象的finalize
18、== 和 equals 的区别?
==:如果⽐较的对象是基本数据类型,则⽐较的是数值是否相等;如果⽐较的是引⽤数据类型,则⽐较的是对象的 地址值是否相等。
equals ⽅法:⽤来⽐较两个对象的内容是否相等。注意:equals ⽅法不能⽤于⽐较基本数据类型的变量。如果没 有对 equals ⽅法进⾏重写,则⽐较的是引⽤类型的变量所指向的对象的地址(很多类重写了 equals ⽅法,⽐如 String、Integer 等把它变成了值⽐较,所以⼀般情况下 equals ⽐较的是值是否相等)。
19、两个对象的 hashCode() 相同,则 equals() 也⼀定为 true 吗?
两个对象的 hashCode() 相同,equals() 不⼀定为 true。因为在散列表中,hashCode() 相等即两个键值对的哈希 值相等,然⽽哈希值相等,并不⼀定能得出键值对相等【散列冲突】。
20、为什么重写 equals() 就⼀定要重写 hashCode() ⽅法?
这个问题应该是有个前提,就是你需要⽤到 HashMap、HashSet 等 Java 集合,⽤不到哈希表的话,其实仅仅重写 equals() ⽅法也可以。⽽⼯作中的场景是常常⽤到 Java 集合,所以 Java 官⽅建议重写 equals() 就⼀定要重写 hashCode() ⽅法。
对于对象集合的判重如果⼀个集合含有 10000 个对象实例,仅仅使⽤ equals() ⽅法的话,那么对于⼀个对象判 ᯿就需要⽐较 10000 次,随着集合规模的增⼤,时间开销是很⼤的。但是同时使⽤哈希表的话,就能快速定位到 对象的⼤概存储位置,并且在定位到⼤概存储位置后,后续⽐较过程中,如果两个对象的 hashCode 不相同,也不 再需要调⽤ equals() ⽅法,从⽽⼤⼤减少了 equals() ⽐较次数。
所以从程序实现原理上来讲的话,既需要 equals() ⽅法,也需要 hashCode() ⽅法。那么既然重写了 equals(),那 么也要重写 hashCode() ⽅法,以保证两者之间的配合关系。
hashCode()与equals()的相关规定:
1、如果两个对象相等,则 hashCode ⼀定也是相同的;
2、两个对象相等,对两个对象分别调⽤ equals ⽅法都返回 true;
3、两个对象有相同的 hashCode 值,它们也不⼀定是相等的;
4、因此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖;
5、hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode(),则该 class 的两个对象⽆论 如何都不会相等(即使这两个对象指向相同的数据)。
21、& 和 && 的区别?
Java 中 && 和 & 都是表示与的逻辑运算符,都表示逻辑运输符 and,当两边的表达式都为 true 的时候,整个运算 结果才为 true,否则为 false。
&&:有短路功能,当第⼀个表达式的值为 false 的时候,则不再计算第⼆个表达式;
&:不管第⼀个表达式结果是否为 true,第⼆个都会执⾏。除此之外,& 还可以⽤作位运算符:当 & 两边的表达式 不是 Boolean 类型的时候,& 表示按位操作。
22、Java 中的参数传递时传值呢?还是传引⽤?
Java 的参数是以值传递的形式传⼊⽅法中,⽽不是引⽤传递。
当传递⽅法参数类型为基本数据类型(数字以及布尔值)时,⼀个⽅法是不可能修改⼀个基本数据类型的参数。
当传递⽅法参数类型为引⽤数据类型时,⼀个⽅法将修改⼀个引⽤数据类型的参数所指向对象的值。即使 Java 函 数在传递引⽤数据类型时,也只是拷⻉了引⽤的值罢了,之所以能修改引⽤数据是因为它们同时指向了⼀个对象, 但这仍然是按值调⽤⽽不是引⽤调⽤。
23、Java 中的 Math.round(-1.5) 等于多少?
等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。
24、两个⼆进制数的异或结果是什么?
两个⼆进制数异或结果是这两个⼆进制数差的绝对值。表达式如下:a^b = |a-b|。
两个⼆进制 a 与 b 异或,即 a 和 b 两个数按位进⾏运算。如果对应的位相同,则为 0(相当于对应的算术相减), 如果不同即为 1(相当于对应的算术相加)。由于⼆进制每个位只有两种状态,要么是 0,要么是 1,则按位异或 操作可表达为按位相减取值相对值,再按位累加。
26、如何实现对象的克隆?
(1)实现 Cloneable 接⼝并᯿写 Object 类中的 clone() ⽅法;
(2)实现 Serializable 接⼝,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。
27、深克隆和浅克隆的区别?
(1)浅克隆:拷⻉对象和原始对象的引⽤类型引⽤同⼀个对象。浅克隆只是复制了对象的引⽤地址,两个对象指 向同⼀个内存地址,所以修改其中任意的值,另⼀个值都会随之变化,这就是浅克隆。
(2)深克隆:拷⻉对象和原始对象的引⽤类型引⽤不同对象。深拷⻉是将对象及值复制过来,两个对象修改其中 任意的值另⼀个值不会改变,这就是深拷⻉(例:JSON.parse() 和 JSON.stringify(),但是此⽅法⽆法复制函数类 型)。
补充:
深克隆的实现就是在引⽤类型所在的类实现 Cloneable 接⼝,并使⽤ public 访问修饰符重写 clone ⽅法。
Java 中定义的 clone 没有深浅之分,都是统⼀的调⽤ Object 的 clone ⽅法。为什么会有深克隆的概念?是由于我 们在实现的过程中刻意的嵌套了 clone ⽅法的调⽤。也就是说深克隆就是在需要克隆的对象类型的类中᯿新实现克 隆⽅法 clone()。
28、什么是 Java 的序列化,如何实现 Java 的序列化?
对象序列化是⼀个⽤于将对象状态转换为字节流的过程,可以将其保存到磁盘⽂件中或通过⽹络发送到任何其他程 序。从字节流创建对象的相反的过程称为反序列化。⽽创建的字节流是与平台⽆关的,在⼀个平台上序列化的对象 可以在不同的平台上反序列化。序列化是为了解决在对象流进⾏读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现 Serializable 接⼝,该接⼝没有需要实现的⽅法,只是⽤于标注该对象是 可被序列化的,然后使⽤⼀个输出流(如:FileOutputStream)来构造⼀个 ObjectOutputStream 对象,接着使 ⽤ ObjectOutputStream 对象的 writeObject(Object obj) ⽅法可以将参数为 obj 的对象写出,要恢复的话则使⽤ 输⼊流。
29、什么情况下需要序列化?
(1)当你想把的内存中的对象状态保存到⼀个⽂件中或者数据库中时候;
(2)当你想⽤套接字在⽹络上传送对象的时候;
(3)当你想通过 RMI 传输对象的时候