目录
8、String、StringBuffer、StringBuilder 的区别?
9、String s = new String("xyz") 会创建几个对象?
13、final、finally、finalize 的区别?
23、try、catch、finally三个语句块应注意的问题?
1、什么是面向对象?
面向对象是一种思想,世间万物都可以看做一个对象,Java 是一个支持并发、基于类和面向对象的计算机编程语言。
面向对象软件开发具有以下优点:
- 代码开发模块化,更易维护和修改。
- 代码复用性强。
- 增强代码的可靠性和灵活性。
- 增加代码的可读性。
2、面向对象的特征?
封装、继承、多态、抽象。
- 封装
给对象提供了隐藏内部特性和行为的能力。其实也就是将对象的属性和方法变为私有,对象提供一些能被其它对象访问的方法来改变它的属性,例如set/get方法,提高了代码的可用性和可维护性。在 Java 当中,有 4 种修饰符: default
、public
、private
和 protected
。
- 继承
给对象提供了从基类获取字段和方法的能力。从而提供了代码的重用行,同时为实现多态性作准备。
- 多态
父类的引用指向子类。方法的重写,重载与动态链接构成多态性。例如接口和接口的多个实现类。
当创建对象时,若左右类不同的时候,只能调用左边类的方法,但真正执行的是右边类对应的方法。如果进行了强制类型转换(一般是向上转型),执行的还是右边类对应的方法。
- 抽象
是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。Java 支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。
3、面向对象和面向过程的区别?
- 面向过程
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。比如,单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展。
- 面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
- 缺点:性能比面向过程低。
4、重载和重写的区别?
- 重写
override
方法名、参数、返回值相同。
子类方法不能缩小父类方法的访问权限。
子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
存在于父类和子类之间。
方法被定义为 final
不能被重写。
- 重载
overload
参数类型、个数、顺序至少有一个不相同。
不能重载只有返回值不同的方法。
存在于同类中。
5、JDK、JRE、JVM 分别是什么关系?
简单说,就是 JDK 包含 JRE 包含 JVM,JRE 包含 JVM。
- JDK 即为 Java 开发工具包,包含编写 Java 程序所必须的编译、运行等开发工具以及 JRE。
- JRE 即为 Java 运行环境,提供了运行 Java 应用程序所必须的软件环境,包含有 Java 虚拟机(JVM)和丰富的系统类库。系统类库即为 Java 提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。
- JVM 即为 Java 虚拟机,提供了字节码文件(
.class
)的运行环境支持。
6、Java 中的几种基本数据类型?
Java 支持的数据类型包括基本数据类型和引用类型。
- 基本数据类型
- 整数值型:
byte
、short
、int
、long
- 字符型:
char
- 浮点类型:
float
、double
- 布尔型:
boolean
- 整数值型:
整数型:默认 int
型,小数默认是 double
型。Float 和 Long 类型的必须加后缀。比如:float f = 100f
。
byte取值范围 -128至127。
- 引用类型
- 引用类型包括类、接口、数组等。
- 特别注意,String 是引用类型不是基本类型。
引用类型声明的变量是指该变量在栈中实际存储的是一个引用地址,实体在堆中。
7、Java是值传递和引用传递?
在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。
只是在传递过程中分以下情况:
- 如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
- 如果是对引用数据类型进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
8、String、StringBuffer、StringBuilder 的区别?
Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它们可以储存和操作字符串。
- String ,是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer/StringBuilder 类,表示的字符串对象可以直接进行修改,区别在StringBuffer是线程不安全的。
9、String s = new String("xyz")
会创建几个对象?
声明:s不是对象,s是指针引用。String类型是final的,是不可改变的,对String类型的变量进行操作,只会改变变量的引用地址,而不会改变原String对象。在 jvm 的工作过程中,会创建一片的内存空间存放 string 对象以及final修饰的变量,我们把这片内存空间叫做常量池。
判断 :
if("xyz"在常量池中存在){
只会在堆中创建一个new String("xyz") ;一个对象
} else {
会现在常量池中创建一个“xyz”,然后在堆中创建一个new String("xyz");两个对象
}
10、Integer的缓存策略
public class JavaIntegerCache {
public static void main(String[] args) {
Integer integer1=3;
Integer integer2=3;
if(integer1==integer2){
System.out.println("integer1==integer2");//执行
}else{
System.out.println("integer1!=integer2");
}<br>
Integer integer3=300;
Integer integer4=300;
if(integer3==integer4){
System.out.println("integer3==integer4");
}else{
System.out.println("integer3!=integer4");//执行
}
}
}
在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。在创建新的 Integer 对象之前会先在 IntegerCache.cache 中查找。有一个专门的 IntegerCache 类来负责 Integer 的缓存。这种 Integer 缓存策略仅在自动装箱(autoboxing)的时候有用,使用构造器创建的 Integer 对象不能被缓存。
Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。
11、equals 与 == 的区别?
- 对于==
如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等。
如果作用于引用类型的变量,则比较的是所指向的对象的地址。
- 对于equals方法
注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址。诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
12、equals方法和hashcode的关系?
HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的(后半句说的用hashcode来代表对象就是在hash表中的位置)。
为什么hashcode就查找的更快?比如:我们有一个能存放1000个数这样大的内存中,在其中要存放1000个不一样的数字,用最笨的方法,就是存一个数字,就遍历一遍,看有没有相同得数,当存了900个数字,开始存901个数字的时候,就需要跟900个数字进行对比,这样就很麻烦,很是消耗时间,用hashcode来记录对象的位置,来看一下。hash表中有1、2、3、4、5、6、7、8个位置,存第一个数,hashcode为1,该数就放在hash表中1的位置,存到100个数字,hash表中8个位置会有很多数字了,1中可能有20个数字,存101个数字时,他先查hashcode值对应的位置,假设为1,那么就有20个数字和他的hashcode相同,他只需要跟这20个数字相比较(equals),如果每一个相同,那么就放在1这个位置,这样比较的次数就少了很多,实际上hash表中有很多位置,这里只是举例只有8个,所以比较的次数会让你觉得也挺多的,实际上,如果hash表很大,那么比较的次数就很少很少了。
通过前面这个例子,大概可以知道,先通过hashcode来比较,如果hashcode相等,那么就用equals方法来比较两个对象是否相等。用个例子说明:上面说的hash表中的8个位置,就好比8个桶,每个桶里能装很多的对象,对象A通过hash函数算法得到将它放到1号桶中,当然肯定有别的对象也会放到1号桶中,如果对象B也通过算法分到了1号桶,那么它如何识别桶中其他对象是否和它一样呢,这时候就需要equals方法来进行筛选了。
如果两个对象equals相等,那么这两个对象的HashCode一定也相同;如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置。
所以,如果对象的equals方法被重写,那么对象传的HashCode方法也尽量重写。
13、final、finally、finalize 的区别?
- final
final
,是修饰符关键字。主要用在三个地方:变量、方法、类。
被声明为 final
的类,不能派生出新的子类。因此一个类不能既被声明为 abstract
的,又被声明为 final
的;被声明为 final
的方法只能使用,不能重写;被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
- finally
finally,是修饰符关键字。
在异常处理时提供 finally
块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch
子句就会执行,然后控制就会进入 finally
块(如果有的话)。
- finalize
finalize
,是方法名。
#finalize()
方法,是在 Object 类中定义的,因此所有的类都继承了它。在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。
14、抽象类和接口有什么区别?
从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
- Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法。
- 类可以实现很多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
- 抽象类可以在不提供接口方法实现的情况下实现接口。
- Java 接口中声明的变量默认都是
final
的。抽象类可以包含非final
的变量。 - Java 接口中的成员函数默认是
public
的。抽象类的成员函数可以是private
,protected
或者是public
。 - 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含
#main(String[] args)
方法的话是可以被调用的。
15、类的实例化顺序?
初始化顺序如下:
- 父类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
- 子类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
- 父类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
- 执行父类的构造方法。
- 子类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
- 执行子类的构造方法。
推荐博客:深入理解Java对象的创建过程:类的初始化与实例化
17、成员变量与局部变量的区别有那些?
- 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;
- 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存
- 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
- 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。
18、什么是内部类?
内部类是指在一个外部类的内部再定义一个类。内部类提供了更好的封装,除了该外围类,其他类都不能访问。内部类主要有以下几类:成员内部类、局部内部类、静态内部类、匿名内部类。
典型的情况是,内部类继承自某个类或实现某个接口,内部类的代码操作创建其的外围类的对象。所以你可以认为内部类提供了某种进入其外围类的窗口。使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。
内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。
- 成员内部类
作为外部类的一个成员存在,与外部类的属性、方法并列。
创建成员内部类,必须使用此外围类的一个对象来创建其内部类的一个对象:Outer.Inner outin = out.new Inner()。因为成员内部类的对象会悄悄地链接到创建它的外围类的对象。如果你用的是静态内部类,那就不需要对其外围类对象的引用。
注意事项:
- 成员内部类中,不能定义静态成员(包括属性和方法);
- 成员内部类中,可以访问外部类的所有成员;
- 内部类和外部类的同名实例变量可以共存;
- 在内部类中访问内部类自己的变量直接用变量名,也可以用this.变量名;
- 如果内部类中没有与外部类同名的变量,则可以直接用变量名访问外部类变量;
- 如果内部类中有与外部类同名的变量,则可以用外部类名.this.变量名
- 局部内部类
在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,和此外围类所有的成员。访问局部内部类必须先有外部类对象,用外部对象调用局部内部类所在的方法。
注意事项:
- 成员内部类中,不能定义静态成员;
- 成员内部类中,可以访问当前代码块内的常量,和此外围类所有的成员;
- 内部类和外部类的同名实例变量可以共存;
- 在内部类中访问内部类自己的变量直接用变量名,也可以用this.变量名;
- 如果内部类中没有与外部类同名的变量,则可以直接用变量名访问外部类变量;
- 如果内部类中有与外部类同名的变量,则可以用外部类名.this.变量名;
- 可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的;
- 静态内部类(嵌套类)
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象。
- 不能从嵌套类的对象中访问非静态的外围类对象。
生成一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:Outer.Inner in = new Outer.Inner(); 而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类(正常情况下,你不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,因为它是static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则)
注意事项:
- 静态内部类可以用public,protected,private修饰;
- 静态内部类中可以定义静态或者非静态的成员;
- 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
- 匿名内部类
简单地说:匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:
- 只用到类的一个实例。
- 类在定义后马上用到。
- 类非常小(SUN推荐是在4行代码以下)
- 给类命名并不会导致你的代码更容易被理解。
在使用匿名内部类时,要记住以下几个原则:
- 匿名内部类不能有构造方法。
- 匿名内部类不能定义任何静态成员、方法和类。
- 匿名内部类不能是public,protected,private,static。
- 只能创建匿名内部类的一个实例。
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
匿名内部类主要用在回调函数中,在Java中,通常就是编写另外一个类或类库的人规定一个接口,然后你来实现这个接口,然后把这个接口的一个对象作为参数传给别人的程序,别人的程序必要时就会通过那个接口来调用你编写的函数,执行后续的一些方法,。
public class CallBack {
public static void main(String[] args) {
CallBack callBack = new CallBack();
callBack.toDoSomethings(100, new CallBackInterface() {
public void execute() {
System.out.println("我的请求处理成功了");
}
});
}
public void toDoSomethings(int a, CallBackInterface callBackInterface) {
long start = System.currentTimeMillis();
if (a > 100) {
callBackInterface.execute();
} else {
System.out.println("a < 100 不需要执行回调方法");
}
long end = System.currentTimeMillis();
System.out.println("该接口回调时间 : " + (end - start));
}
}
public interface CallBackInterface {
void execute();
}
19、什么是 Java IO ?
20、什么是java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。反序列化的过程,则是和序列化相反的过程。另外,我们不能将序列化局限在 Java 对象转换成二进制数组,例如说,我们将一个 Java 对象,转换成 JSON 字符串,或者 XML 字符串,这也可以理解为是序列化。
- 序列化
- 将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,
implements Serializable
只是为了标注该对象是可被序列化的。 - 对于不想进行序列化的变量,使用
transient
关键字修饰。transient
只能修饰变量,不能修饰类和方法。 - 然后,使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
- 接着,使用 ObjectOutputStream 对象的
#writeObject(Object obj)
方法,就可以将参数为obj
的对象写出(即保存其状态)。
- 将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,
- 反序列化
- 要恢复的话则用输入流。
21、如何实现对象克隆?
一般来说,有两种方式:
- 1、实现 Cloneable 接口,并重写 Object 类中的
#clone()
方法。可以实现浅克隆,也可以实现深克隆。 - 2、实现 Serializable 接口,通过对象的序列化和反序列化实现克隆。可以实现真正的深克隆。
实际场景下,我们使用的克隆比较少,更多是对象之间的属性克隆。例如说,将 DO 的属性复制到 DTO 中,又或者将 DTO 的属性复制到 VO 中。此时,我们一般使用 Bean复制的几种框架(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier),其实都是相当于克隆中的浅克隆。
- spring包和Apache中的 BeanUtils 采用反射实现;
- cglib包中的 Beancopier 采用动态字节码实现;
22、java异常体系?
Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。下面将详细讲述这些异常之间的区别与联系:
- Error与Exception
- Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
- Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
- 运行时异常和非运行时异常
- 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
- 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
23、try、catch、finally三个语句块应注意的问题?
- try、catch、finally三个语句块均不能单独使用,三者可以组成 try...catch...finally、try...catch、try...finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
- try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
- 多个catch块时候,最多只会匹配其中一个异常类且只会执行该catch块代码,而不会再执行其它的catch块,且匹配catch语句的顺序为从上到下,也可能所有的catch都没执行。
- 先Catch子类异常再Catch父类异常。
24、throw、throws关键字?
- throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。
- throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出.
25、Throwable 类常用方法?
#getMessage()
方法:返回异常发生时的详细信息。#getCause()
方法:获得导致当前 Throwable 异常的 Throwable 异常。#getStackTrace()
方法:获得 Throwable 对象封装的异常信息。#printStackTrace()
方法:在控制台上打印。
26、说说反射的用途及实现?
Java 反射机制主要提供了以下功能:
- 在运行时构造一个类的对象。
- 判断一个类所具有的成员变量和方法。
- 调用一个对象的方法。
- 生成动态代理。
反射的应用很多,很多框架都有用到:
- Spring 框架的 IoC 基于反射创建对象和设置依赖属性。
- Spring MVC 的请求调用对应方法,也是通过反射。
- JDBC 的
Class#forName(String className)
方法,也是使用反射。
27、java自定义注解?
28、Java 对象创建的方式?
- 使用
new
关键字创建对象。 - 使用 Class 类的 newInstance 方法(反射机制)。
- 使用 Constructor 类的 newInstance 方法(反射机制)。
- 使用 clone 方法创建对象。
- 使用(反)序列化机制创建对象。