Java面试笔记
第一章:Java基础知识
1.1 Java程序初始化顺序
Java程序初始化一般遵循以下三个原则(优先级依次递减)
-
静态对象(变量)优先于非静态对象初始化
- 静态对象初始化一次
- 非静态对象可能初始化多次
-
父类优先于子类初始化
-
按照成员变量定义顺序进行初始化
- 即使变量定义散布于方法定义之中,它们依然在任意方法(包括构造方法)被调用之前进行初始化
1.2 构造方法
构造方法具有如下特点:
-
构造方法名必须与类名相同,并且不能有返回值;
-
每个类可以有多个构造方法;
- 如果开发者不指定构造方法,那么构造方法将由编译器自动生成;
- 如果开发者指定了构造方法,那么编译器将不会再创建构造方法;
-
构造方法可以有0,1,或者1个以上的参数;
-
构造方法只能伴随着new关键字一起被调用,不能由开发者单独调用;
-
构造方法的主要功能是完成对象的初始化工作;
-
构造方法不能被继承,因此也不能被重写,但是可以被重载;
- 重写:函数名与形参列表、返回值类型都必须与原函数相同,发生在子类继承父类时;
- 重载:函数名相同,但是函数的形参个数或者类型不相同,发生在本类内部;
-
子类可以使用super关键字来显式地调用父类的构造方法;
- 如果父类中提供了无参构造方法,此时子类就不可以显式地调用父类的构造方法,此时编译器默认调用父类的无参构造方法;
- 当有父类存在时,实例化子类对象时会先执行父类的构造方法,然后才会执行子类的构造方法;
-
当父类和子类都没有构造方法的时候,编译器会给二者均生成默认无参构造方法,该方法只与类的修饰符保持一致(
public、private、protect) -
普通方法也可以与构造方法同名,互不影响,但是普通方法在定义时需要返回值类型。
1.3 Java中的clone方法
-
Java中没有指针;
-
每一个new语句返回的都是一个指针的引用;
-
Java中的值传递与引用传递:
- 在处理基本数据类型时采用值传递;
- 在处理所有非基本数据类型时采用引用传递;
-
当需要对象拷贝这一功能时就需要使用
clone()函数; -
对于
clone()函数的一般约定:- ★
x.clone() != x;即克隆对象与原对象不是同一个对象; - ★
x.clone().getClass() <span style="font-weight: bold;" class="mark"> x.getClass();即克隆的是同一类型的对象; -
x.clone().equals(x) </span> true;如果x.equals()方法定义恰当的话;
- ★
-
(可选)
clone()函数的基本运行流程- 继承Cloneable接口,该接口没有任何方法;
- 在类中重写
Object的clone()方法; - 在
clone()方法中调用super.clone()方法; - 把浅拷贝的引用指向原型对象的新克隆体;
-
浅拷贝与深拷贝
- 浅拷贝:被clone的对象拥有与原来对象中相同的值,但是对象的引用仍然指向原来的对象;
- 深拷贝:引用域所指向的对象也克隆一遍;
1.4 反射
-
定义:反射就是把java类中的各种成分映射成一个个的Java对象。
-
功能:反射能直接操作类私有属性。反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
- 注意:反射可以直接操纵
-
常用的反射方法:
-
获取类的构造方法
-
getConstructor(参数类型列表)//获取公开的构造方法 -
getConstructors()//获取所有的公开的构造方法 -
getDeclaredConstructors()//获取所有的构造方法,包括私有 -
getDeclaredConstructor(int.class,String.class)
-
-
获取类的成员变量的方法
-
getFields()//获取所有公开的成员变量,包括继承变量 -
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量 -
getField(变量名) -
getDeclaredField(变量名)
-
-
获取类的方法
-
getMethods()//获取所有可见的方法,包括继承的方法 -
getMethod(方法名,参数类型列表) -
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法 -
getDeclaredMethod(方法名,int.class,String.class)
-
-
1.5 Lambda表达式
-
简介:在Java8以前,Lambda表达式本质是一个匿名函数;现在是一种简化匿名内部类的代码写法;
-
基本格式(Java8以后):
-
(参数列表)->表达式语句 -
(参数列表)->{多个表达式语句;}
-
-
几个简单的例子
// 1. 不需要参数,返回值为 5 () -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y // 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s) -
注意事项(即Lambda表达式不具有大括号分割变量作用域的功能)
- 可以直接在 lambda 表达式中访问外层的局部变量;
- 隐性的具有
final的语义:lambda 表达式的局部变量可以不用声明为final,但是必须不可被后面的代码修改 - 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
1.6 Java多态的实现机制
-
多态的两种表现方式:重载(Overload)和重写(Override)
-
重载
- 发生在类内部或者子类中;
- 方法名必须相同;
- 方法的参数列表一定不同;
- 访问修饰符和返回值类型可以相同也可以不同;
-
重写
- 一般发生在父类和子类之间;
- 方法名、返回值类型、参数列表必须相同;
- 访问权限不能比父类中被重写的方法的访问权限更低;
- 同一个包中,子类可以重写父类所有方法,除了父类中的
private和final方法; - 构造方法不能被重写;
1.7 重载(Overload)和重写(Override)的区别与联系
-
重载
- 发生在类内部或者子类中;
- 方法名必须相同;
- 方法的参数列表一定不同;
- 访问修饰符和返回值类型可以相同也可以不同;
-
重写
- 一般发生在父类和子类之间;
- 方法名、返回值类型、参数列表必须相同;
- 访问权限不能比父类中被重写的方法的访问权限更低;
- 同一个包中,子类可以重写父类所有方法,除了父类中的
private和final方法; - 构造方法不能被重写;
1.8 抽象类(Abstract)和接口(Interface)的区别与联系
1.8.1 抽象类
-
定义:抽象类是类和类之间的共同特征,将这些共同特征进一步形成抽象类;
-
要求(了解):
- 被
abstract关键字定义的类就是抽象类,采用abstract关键字定义的方法就
是抽象方法 - 抽象类无法创建对象,但抽象类有构造函数可以供子类使用;
- 抽象方法不能被
final修饰,因为抽象方法就是被子类实现的; - 抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中;
-
final和abstract不能同时同时使用,这两个关键字是对立的; - 抽象类的子类可以是抽象类。也可以是非抽象类;
- 抽象方法表示没有实现的方法,没有方法体的方法;
- 被
1.8.2 接口
-
定义:接口是特殊的抽象类,类与类是继承extends,类与接口是实现implements,其实都是继承;
-
要求(了解):
- 支持多继承,且一个接口可以继承多个接口;
- 接口中的方法是抽象,所以不能有方法体;
- 定义抽象方法的时候可以省略修饰符
public abstract
1.8.3 总结(重点)

| 比较内容 | 抽象类 | 接口 |
|---|---|---|
| 构造方法 | 可以有 | 不可以有 |
| 方法 | 可以有抽象方法(抽象方法只能被abstract修饰,不可被private、static、synchronized和native修饰)和普通方法 |
只能有抽象方法,但1.8版本之后可以有默认方法。接口只有定义,不可有方法实现 |
| 实现 | extend |
implments |
| 类修饰符 | public、default、protected |
默认public |
| 变量 | 可以有常量也可以有变量 | 只能是静态常量默认有public、static 、final修饰,必须赋上初始值,并且不能被修改 |
| 多继承 | 只允许单继承 | 允许实现多个接口 |
| 静态方法 | 可以有 | 不可以 |
1.9 break、continue和return的区别与联系(包括goto)
break:立刻终止break语句所在的循环层的循环,外层循环不受影响;
continue:立刻结束该轮次的循环,进入循环的下一轮次。循环中本轮次,在continue后的语句本轮次不再执行;
return:return是一个跳转语句,用来表示从一个方法返回的数据结构,可以试用程序控制返回到调用它方法的地方。当main方法被执行时,main方法中的reuturn语句可以使程序执行返回到Java运行系统。
goto:goto是Java的保留字,但是Java并没有实现goto作为对应C++的功能,但是可以通过标识符:的形式定义标签,以便于Java使用break和continue对于多重循环的控制。
1.10 switch使用时有什么需要注意的
-
switch的在Java中属于分支语句的一类,同类的分支语句包括if、else等; -
语法格式
switch (表达式) { case 常量表达式或枚举常量: 语句; break; case 常量表达式或枚举常量: 语句; break; ...... default: 语句; break; } -
表达式所返回的类型有包含如下几种:
- 整型:
byte、short、int - 字符:
char - 字符串:
string - 枚举类型
- 整型:
-
注意事项
-
case后面跟的是要和表达式进行比较的值(被匹配的值); -
break表示中断,结束的意思,用来结束switch语句; -
default表示所有情况都不匹配的时候,就执行该处的内容;
-
1.11 volatile在Java中有什么作用
-
目的:为了线程安全;
-
线程安全
-
定义:在多线程情况下,对共享内存的使用不会因为不同线程的访问修改而发生不期望的情况;
-
三要素
- ✅可见性;
- ✅有序性;
- ❎原子性;
-
-
-
volatile三个作用-
解决多核CPU高速缓存导致的变量不同步(解决可见性问题);
-
CPU与内存之间存在多级缓存


-
不同处理器对于数据的修改逻辑不同从而发生变量不同步现象
如上述两图所示,X的变量同时被不同的处理器修改成各自的Y和Z
-
volatile基于内存屏障解决变量不同步- 当GPU写入数据时,若发现一个变量在其他变量中存有副本;
- 此时会发出信号通知其它CPU将该副本对应的缓存行置为无效状态;
- 当CPU读取变量副本,发现该缓存行无效时,它会重新从主内存中重新读取变量。
-
-
解决指令重排序问题(解决有序性问题);
- 一般情况下,指令会按照顺序从上到下、从左往右执行;
- 对于存在依赖关系的多个指令,譬如
int i=0;和i++;这两个指令,一定是先有变量i的定义,后对i进行操作; - 对于不存在依赖关系的多个指令,CPU在运行期间会对指令进行优化,即CPU会基于最优化的方式对不存在依赖关系的指令进行重排;
- 单线程下指令重排不会引发任何问题;
- 多线程下指令重排会引发很多错误,这会让线程“不安全”,因为这违反了线程安全规则之一的“有序性”;
- 被
volatile修饰过的变量将不再运行时被CPU进行指令优化重排,保证了有序性。
-
不保证操作的原子性;
- 原子性:一个或一组操作,要么连续执行不会被打断,要么都不执行,满足这样特征的一个或一组操作就体现出了原子性;
-
volatile不保证操作的原子性!
-
1.12 Java基本数据类型
-
Java提供了8中基本数据类型
-
注意要点
-
Java中不存在无符号的数;
-
Java中的数据的范围是固定的,不会随着硬件和操作系统的改变而发生改变;
-
Java中默认的浮点数类型是
double,因此,如果需要使用float关键字初始化浮点型变量时,需要使用类型转换符号,例如float f = 1.0f或者float f =(float)1.0 -
null-
null不是一个合法的Object实例,编译器并没有为其分配内存; -
null仅仅用于表明该引用目前目前没有指向任何对象; -
null将会对引用变量的值全部置为0; - 如果定义
String x = null,它表示定义了一个变量x,x中存放的是String的引用,此处为null
-
-
1.13 不可变类
-
定义
不可变类(Immutable Class)在创建实例后,在整个程序的运行期间内,都不允许修改其成员变量的值,但是刻印读取,类似于常量;
-
要点
- 所有的基本数据类型的封装类都是不可变类;
- 不可变类的值不可以被改变,但是可以修改其指向;
-
创建一个不可变类
- 所有成员变量均必须被private修饰;
- 类中不能定义能够休干成员变量的方法;
- 必须确保类中的所有方法均不能为子类所覆盖;
- 若类成员不是不可变量,那么在成员初始化或者使用get方法获取其成员变量时,需要使用clone方法来确保类的不可变性;
- 123321