读书

Java经典面试题最详细版(面试必备)二

2022-07-07  本文已影响0人  管彤Java架构师

Java语言采用何种编码方案?有何特点?*
Java语言采用Unicode编码标准,它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

访问修饰符 **
在Java编程语言中有四种权限访问控制符,这四种访问权限的控制符能够控制类中成员的可见性。其中类有两种public、default。而方法和变量有 4 种:public、default、protected、private。

public : 对所有类可见。使用对象:类、接口、变量、方法
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
default : 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)


image.png

运算符 *
&&和&
&&和&都可以表示逻辑与,但他们是有区别的,共同点是他们两边的条件都成立的时候最终结果才是true;不同点是&&只要是第一个条件不成立为false,就不会再去判断第二个条件,最终结果直接为false,而&判断的是所有的条件。

||和|
||和|都表示逻辑或,共同点是只要两个判断条件其中有一个成立最终的结果就是true,区别是||只要满足第一个条件,后面的条件就不再判断,而|要对所有的条件进行判断。

关键字
static关键字 ***
static关键字的主要用途就是方便在没有创建对象时调用方法和变量和优化程序性能

1.static变量(静态变量)

用static修饰的变量被称为静态变量,也被称为类变量,可以直接通过类名来访问它。静态变量被所有的对象共享,在内存中只有一个副本,仅当在类初次加载时会被初始化,而非静态变量在创建对象的时候被初始化,并且存在多个副本,各个对象拥有的副本互不影响。

2.static方法(静态方法)

static方法不依赖于任何对象就可以进行访问,在static方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是在非静态成员方法中是可以访问静态成员方法/变量的。

public class Main {
public static String s1 = "s1";//静态变量
String s2 = "s2";
public void fun1(){
System.out.println(s1);
System.out.println(s2);
}

public static void fun2(){
    System.out.println(s1);
    System.out.println(s2);//此处报错,静态方法不能调用非静态变量
}

}
3.static代码块(静态代码块)

静态代码块的主要用途是可以用来优化程序的性能,因为它只会在类加载时加载一次,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。如果程序中有多个static块,在类初次被加载的时候,会按照static块的顺序来执行每个static块。

public class Main {
static {
System.out.println("hello,word");
}
public static void main(String[] args) {
Main m = new Main();
}
}
4.可以通过this访问静态成员变量吗?(可以)

this代表当前对象,可以访问静态变量,而静态方法中是不能访问非静态变量,也不能使用this引用。

5.初始化顺序

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。如果存在继承关系的话,初始化顺序为父类中的静态变量和静态代码块——子类中的静态变量和静态代码块——父类中的实例变量和普通代码块——父类的构造函数——子类的实例变量和普通代码块——子类的构造函数

final 关键字 ***
final关键字主要用于修饰类,变量,方法。

类:被final修饰的类不可以被继承
方法:被final修饰的方法不可以被重写
变量:被final修饰的变量是基本类型,变量的数值不能改变;被修饰的变量是引用类型,变量便不能在引用其他对象,但是变量所引用的对象本身是可以改变的。
public class Main {
int a = 1;
public static void main(String[] args) {
final int b = 1;
b = 2;//报错
final Main m = new Main();
m.a = 2;//不报错,可以改变引用类型变量所指向的对象
}
}
final finally finalize区别 ***
final主要用于修饰类,变量,方法
finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块
中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
finalize是一个属于Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,但Java语言规范并不保证inalize方法会被及时地执行、而且根本不会保证它们会被执行。
this关键字 **
重点掌握前三种即可
1.this关键字可用来引用当前类的实例变量。主要用于形参与成员名字重名,用this来区分。

public Person(String name, int age) {
this.name = name;
this.age = age;
}
2.this关键字可用于调用当前类方法。

public class Main {
public void fun1(){
System.out.println("hello,word");
}
public void fun2(){
this.fun1();//this可省略
}

public static void main(String[] args) {
    Main m = new Main();
    m.fun2();
}

}
3.this()可以用来调用当前类的构造函数。(注意:this()一定要放在构造函数的第一行,否则编译不通过)

class Person{
private String name;
private int age;

public Person() {
}

public Person(String name) {
    this.name = name;
}
public Person(String name, int age) {
    this(name);
    this.age = age;
}

}
4.this关键字可作为调用方法中的参数传递。

5.this关键字可作为参数在构造函数调用中传递。

6.this关键字可用于从方法返回当前类的实例。super

super关键字 **
1.super可以用来引用直接父类的实例变量。和this类似,主要用于区分父类和子类中相同的字段

2.super可以用来调用直接父类构造函数。(注意:super()一定要放在构造函数的第一行,否则编译不通过)

3.super可以用来调用直接父类方法。

public class Main {
public static void main(String[] args) {
Child child = new Child("Father","Child");
child.test();
}
}

class Father{
protected String name;

public Father(String name) {
    this.name = name;
}

public void Say(){
    System.out.println("hello,child");
}

}

class Child extends Father{
private String name;

public Child(String name1, String name2) {
    super(name1);      //调用直接父类构造函数
    this.name = name2;
}

public void test(){
    System.out.println(this.name);
    System.out.println(super.name);  //引用直接父类的实例变量
    super.Say();      //调用直接父类方法
}

}
this与super的区别 **
相同点:

super()和this()都必须在构造函数的第一行进行调用,否则就是错误的
this()和super()都指的是对象,所以,均不可以在static环境中使用。
不同点:

super()主要是对父类构造函数的调用,this()是对重载构造函数的调用
super()主要是在继承了父类的子类的构造函数中使用,是在不同类中的使用;this()主要是在同一类的不同构造函数中的使用
break ,continue ,return 的区别及作用 **
break结束当前的循环体
continue结束本次循环,进入下一次循环
return结束当前方法
面向对象和面向过程的区别 **
面向过程
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。

缺点:没有面向对象易维护、易复用、易扩展

面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

缺点:性能比面向过程低

面向对象三大特性(封装、继承、多态) ***
封装
封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。

继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

多态(重要)

多态是同一个行为具有多个不同表现形式或形态的能力。这句话不是很好理解,可以看这个解释,在Java语言中,多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

在Java中实现多态的三个必要条件:继承、重写、向上转型。继承和重写很好理解,向上转型是指在多态中需要将子类的引用赋给父类对象。

public class Main {
public static void main(String[] args) {
Person person = new Student(); //向上转型
person.run();
}
}

class Person {
public void run() {
System.out.println("Person");
}
}

class Student extends Person { //继承
@Override
public void run() { //重载
System.out.println("Student");
}
}
运行结果为

Student
面向对象五大基本原则是什么 **
单一职责原则(Single-Resposibility Principle)
一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。

开放封闭原则(Open-Closed principle)
软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。

里氏替换原则 (Liskov-Substituion Principle)
子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。

依赖倒置原则(Dependecy-Inversion Principle)
依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。

接口隔离原则(Interface-Segregation Principle)
使用多个小的专门的接口,而不要使用一个大的总接口。

抽象类和接口的对比 ***
在Java语言中,abstract class和interface是支持抽象类定义的两种机制。抽象类:用来捕捉子类的通用特性的。接口:抽象方法的集合。

相同点:

接口和抽象类都不能实例化
都包含抽象方法,其子类都必须覆写这些抽象方法
不同点:

image.png

在Java中定义一个不做事且没有参数的构造方法的作用 *
Java程序存在继承,在执行子类的构造方法时,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。如果父类只定义了有参数的构造函数,而子类的构造函数没有用super调用父类那个特定的构造函数,就会出错。

在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是 *
帮助子类做初始化工作。

一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么? *
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

构造方法有哪些特性? **
方法名称和类同名
不用定义返回值类型
不可以写retrun语句
构造方法可以被重载
变量 **
类变量:独立于方法之外的变量,用 static 修饰。
实例变量:独立于方法之外的变量,不过没有 static 修饰。
局部变量:类的方法中的变量。
成员变量:成员变量又称全局变量,可分为类变量和实例变量,有static修饰为类变量,没有static修饰为实例变量。


image.png

内部类 **

内部类包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类

重写与重载 ***

重载和重写的区别

构造器(constructor)是否可被重写(override)

构造器可以被重载,不能被重写

重载的方法能否根据返回类型进行区分?为什么?

不能,因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。

== 和 equals 的区别 ***

hashCode 与 equals(为什么重写equals方法后,hashCode方法也必须重写) ***

Java 中是值传递还是引用传递,还是两者共存 **

这是一个很容易搞混又很难解释清楚的问题,先说结论,Java中只有值传递

先看这样一段代码

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Main{
public static void main(String[] args) {
int a = 1;
printValue(a);
System.out.println("a:" + a);
}
public static void printValue(int b){
b = 2;
System.out.println("b:"+ b);
}
}</pre>

输出

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">b:2
a:1</pre>

可以看到将a的值传到printValue方法中,并将其值改为2。但方法调用结束后,a的值还是1,并未发生改变,所以这种情况下为值传递。

再看这段代码

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Main{
public static void main(String[] args) {
Preson p = new Preson();
p.name = "zhangsan";
printValue(p);
System.out.println("p.name: " + p.name);
}
public static void printValue(Preson q){
q.name = "lisi";
System.out.println("q.name: "+ q.name);
}
}
class Preson{
public String name;
}</pre>

输出结果

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">q.name: lisi
p.name: lisi</pre>

在将p传入printValue方法后,方法调用结束,pname属性竟然被改变了!所以得出结论,参数为基本类型为值传递,参数为引用类型为时为引用传递。这个结论是错误的,下面来看看判断是值传递还是值传递的关键是什么,先看定义

从定义中可以明显看出,区分是值传递还是引用传递主要是看向方法中传递的是实际参数的副本还是实际参数的地址。上面第一个例子很明显是值传递,其实第二个例子中向printValue方法中传递的是一个引用的副本,只是这个副本引用和原始的引用指向的同一个对象,所以副本引用修改过对象属性后,通过原始引用查看对象属性肯定也是被修改过的。换句话说,printValue方法中修改的是副本引用指向的对象的属性,不是引用本身,如果修改的是引用本身,那么原始引用肯定不受影响。看下面这个例子

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Main{
public static void main(String[] args) {
Preson p = new Preson();
p.name = "zhangsan";
printValue(p);
System.out.println("p.name: " + p.name);
}
public static void printValue(Preson q){
q = new Preson();
q.name = "lisi";
System.out.println("q.name: "+ q.name);
}
}
class Preson{
public String name;
}</pre>

输出结果

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">q.name: lisi
p.name: zhangsan</pre>

可以看到将p传入printValue方法后,printValue方法调用结束后,p的属性name没有改变,这是因为printValue方法中并没有改变副本引用q所指向的对象,而是改变了副本引用q本身,将副本引用q指向了另一个对象并对这个对象的属性进行修改,所以原始引用p所指向的对象不受影响。所以证明Java中只存在值传递。

IO流 *

Java IO流主要可以分为输入流和输出流。按照照操作单元划分,可以划分为字节流和字符流。按照流的角色划分为节点流和处理流。

Java I0流的40多个类都是从4个抽象类基类中派生出来的。

BIO,NIO,AIO 有什么区别? **

反射 ***

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java获取Class对象的三种方式

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class Person {
public String name = "zhangsan";
public Person() {
}
}</pre>

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Main{
public static void main(String[] args) throws ClassNotFoundException {
//方式1
Person p1 = new Person();
Class c1 = p1.getClass();
//方式2
Class c2 = Person.class;
//方式3可能会抛出ClassNotFoundException异常
Class c3 = Class.forName("com.company");
}
}</pre>

因为在一个类在 JVM 中只会有一个 Class 实例,所以对c1c2c3进行equals比较时返回的都是true

反射优缺点:

反射应用场景:

  1. Java的很多框架都用到了反射,例如Spring中的xml的配置模式等
  2. 动态代理设计模式也采用了反射机制

JAVA异常 ***

异常主要分为ErrorException两种

异常框图
[图片上传失败...(image-9eb7fd-1657097733640)]

除了以上的分类,异常还能分为非检查异常和检查异常

下面来看下try{}catch(){}finally{}return之间的“恩恩怨怨”,这里有些乱,面试时问的也不是很多,实在记不住就算啦。还是先看代码猜结果。

<pre class="java hljs language-java" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Main{
public static void main(String[] args) {
int a = test1();
System.out.println(a);
int b = test2();
System.out.println(b);
int c = test3();
System.out.println(c);
int d = test4();
System.out.println(d);
int e = test5();
System.out.println(e);
}
public static int test1(){
int a = 1;
try{
a = 2;
return a;
}catch(Exception e){
System.out.println("hello,test1");
a = 3;
}finally{
a = 4;
}
return a;
}
//输出 2

public static int test2(){
    int a = 1;
    try{
        a = 2;
        return a;
    }catch(Exception e){
        System.out.println("hello,test2");
        a = 3;
        return a;
    }finally{
        a = 4;
    }
}
//输出 2

public static int test3(){
    int a = 1;
    try{
        a = 2/0;
        return a;
    }catch(Exception e){
        System.out.println("hello,test3");
        a = 3;
    }finally{
        a = 4;
    }
    return a;
}
//输出 hello,test3 
// 4

public static int test4(){
    int a = 1;
    try{
        a = 2/0;
        return a;
    }catch(Exception e){
        System.out.println("hello,test4");
        a = 3;
        return a;
    }finally{
        a = 4;
    }
}
//输出 hello,test4
// 3

public static int test5(){
    int a = 1;
    try{
        a = 2/0;
        return a;
    }catch(Exception e){
        a = 3;
        return a;
    }finally{
        a = 4;
        return a;
    }
}

//输出 4

}</pre>

如果没有仔细的研究过,应该好多会猜错,下面总结下规律。

  1. 从前三个例子可以看出如果try{}中的代码没有异常,catch(){}代码块中的代码不会执行。所以如果try{}catch(){}都含有return时,无异常执行try{}中的return,存在异常执行catch(){}return
  2. 不管任何情况,就算try{}catch(){}中含有returnfinally{}中的代码一定会执行,那么为什么test1test2test3中的结果不是4呢,因为虽然finally是在return后面的表达式运算之后执行的,但此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
  3. 如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。这个不理解可以看看上面提到的Java的值传递的问题。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
  4. 如果finally{}中含有return,会导致程序提前退出,不在执行try{}catch(){}中的return。所以test5返回的值是4。

最后总计一下try{}catch(){}finally{}的执行顺序。

  1. 先执行try中的语句,包括return后面的表达式;
  2. 有异常时,执行catch中的语句,包括return后面的表达式,无异常跳过catch语句;
  3. 然后执行finally中的语句,如果finally里面有return语句,执行return语句,程序结束;
  4. finally{}中没有return时,无异常执行try中的return,如果有异常时则执行catch中的return。前两步执行的return只是确定返回的值,程序并未结束,finally{}执行之后,最后将前两步确定的return的返回值返回。

JAVA注解 **

面试问的不多,但是在使用框架开发时会经常使用,但东西太多了,这里只是简单介绍下概念。

Annotation注解可以看成是java中的一种标记记号,用来给java中的类,成员,方法,参数等任何程序元素添加一些额外的说明信息,同时不改变程序语义。注解可以分为三类:基本注解,元注解,自定义注解

JAVA泛型 ***

Java 泛型是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

JAVA序列化 **

深拷贝与浅拷贝 ***

重载clone方法

public class Main{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, CloneNotSupportedException {
Address s = new Address("天津");
Person p = new Person("张三", 23, s);
System.out.println("克隆前的地址:" + p.getAddress().getName());
Person cloneP = (Person) p.clone();
cloneP.getAddress().setName("北京");
System.out.println("克隆后的地址:" + cloneP.getAddress().getName());
}
}

class Address implements Cloneable {
private String city;
public Address(String name){
this.city = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return city;

}
public void setName(String name) {
    this.city = name;

}

}
class Person implements Cloneable{
private String name;
private int age;
private Address address;
public Person(String name, int age, Address address){
this.name = name;
this.age = age;
this.address = address;

}
@Override
public Object clone() throws CloneNotSupportedException {
      Person  person = (Person) super.clone();
      person.address = (Address)address.clone();
    return person;
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public int getAge() {
    return age;
}
public void setAge(int age) {
    this.age = age;
}
public Address getAddress() {
    return address;
}
public void setAddress(Address address) {
    this.address = address;
}

}
输出

克隆前的地址:天津
克隆后的地址:北京
其实就是Person类和Address类都要重写clone方法,这里面需要注意的一点是super.clone()为浅克隆,所以在在Person类中重写clone方法时,address对象需要调用address.clone()重新赋值,因为address类型为引用类型。

序列化

public class Main{
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address s = new Address("天津");
Person p = new Person("张三", 23, s);
System.out.println("克隆前的地址:" + p.getAddress().getName());
Person cloneP = (Person) p.deepClone();
cloneP.getAddress().setName("北京");
System.out.println("克隆后的地址:" + cloneP.getAddress().getName());
}
}

class Address implements Serializable{
private String city;
public Address(String name){
this.city = name;
}

public String getName() {
    return city;

}
public void setName(String name) {
    this.city = name;

}

}
class Person implements Serializable{
private String name;
private int age;
private Address address;
public Person(String name, int age, Address address){
this.name = name;
this.age = age;
this.address = address;

}

public Object deepClone() throws IOException, ClassNotFoundException {        
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);        
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}

public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public int getAge() {
    return age;
}
public void setAge(int age) {
    this.age = age;
}
public Address getAddress() {
    return address;
}
public void setAddress(Address address) {
    this.address = address;
}

}
输出

克隆前的地址:天津
克隆后的地址:北京
常见的Object方法 ***
这些方法都很重要,面试经常会问到,要结合其他知识将这些方法理解透彻
Object clone():创建与该对象的类相同的新对象
boolean equals(Object):比较两对象是否相等
void finalize():当垃圾回收器确定不存在对该对象的更多引用时,对象垃圾回收器调用该方法
Class getClass():返回一个对象运行时的实例类
int hashCode():返回该对象的散列码值
void notify():唤醒等待在该对象的监视器上的一个线程
void notifyAll():唤醒等待在该对象的监视器上的全部线程
String toString():返回该对象的字符串表示
void wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待

上一篇下一篇

猜你喜欢

热点阅读