Java面试指南(基础篇)
文章目录
- 前言
- 01 Java语言的特点
- 02 JVM、JRE及JDK的关系
- 03 Java和C++的区别
- 04 基本数据类型
- 05 类型转换
- 06 自动装箱与拆箱
- 07 String的不可变性
- 08 字符常量和字符串常量的区别
- 09 字符串常量池
- 10 String 类的常用方法
- 11 String和StringBuffer、StringBuilder的区别
- 12 switch 是否能作用在 byte,long 和String 上
- 13 Java语言采用何种编码方案?有何特点?
- 14 访问修饰符
- 15 &,&&和|,||的区别
- 16 static关键字
- 17 final 关键字
- 18 final finally finalize区别
- 19 this关键字
- 20 super关键字
- 21 this与super的区别
- 22 break ,continue ,return 的区别
- 23 面向对象和面向过程的区别
- 24 面向对象三大特性(封装、继承、多态)
- 25 面向对象五大基本原则
- 26 抽象类和接口的对比
- 27 构造方法
- 28 构造方法有哪些特性?
- 29 变量
- 30 内部类
- 31 重写与重载
- 32 == 和 equals 的区别
- 33 hashCode 与 equals
- 34 Java值传递
- 35 IO流
- 36 BIO,NIO,AIO 有什么区别?
- 37 反射
- 38 Java异常
- 39 Java注解
- 40 Java泛型
- 41 JAVA序列化
- 42 深拷贝与浅拷贝
- 43 常见的Object方法
前言
“凌云西岸古嘉州,江水潺潺绕郭流” —张定陶 《嘉定舟中作》
01 Java语言的特点
1.Java是一种面向对象的语言
2.Java通过Java虚拟机实现了平台无关性,一次编译,到处运行
3.支持多线程
4.支持网络编程
5.具有较高的安全性和可靠性
02 JVM、JRE及JDK的关系
1.JDK(Java Development Kit):Java开发工具包是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。
2.JRE(Java Runtime Environment):Java运行环境是运行Java程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
3.JVM(Java Virtual Machine):Java虚拟机是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序 。
简单来说就是JDK是Java的开发工具,JRE是Java程序运行所需的环境,JVM是Java虚拟机,它们之间的关系是JDK包含JRE和JVM,JRE包含JVM。
03 Java和C++的区别
1.Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台
2.Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针
3.Java 支持自动垃圾回收,而 C++ 需要手动回收
4.Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承
5.Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操 作符重载,而 C++ 可以
6.Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto
04 基本数据类型
注意:
- String不是Java的基本数据类型
- int包装类型为Integer,char包装类型为Character,其他的为首字母大写
- \u0000为一个空格
基本类型 | 包装类型 | 占用字节 | 取值范围 | 默认值 |
---|---|---|---|---|
byte | Byte | 1 | -2^7 ~ 2^7-1 | 0 |
short | Short | 2 | -2^15 ~ 2^15-1 | 0 |
int | Integer | 4 | -2^31 ~ 2^31-1 | 0 |
long | Long | 8 | -2^63 ~ 2^63-1 | 0L |
float | Float | 4 | 3.4e-45 ~ 1.4e38 | 0.0F |
double | Double | 8 | 4.9e-324 ~ 1.8e308 | 0.0D |
char | Character | 2 | 0 ~ 2^16 - 1 | \u0000 |
boolean | Boolean | 1 | true/flase | false |
05 类型转换
- 隐式类型转换:也叫自动类型转换,是从存储范围小的类型到存储范围大的类型
- 显示类型转换:也叫强制类型转换,是从存储范围大的类型到存储范围小的类型,该类类型转换很可能存在精度的损失
代码示例:
// 1.会报错,因为1是int类型,s+1会自动转换为int型,将int型直接复制给short型会报错
short s = 1;
s = s + 1;
// 2.修改
short s = 1;
s = (short)(s + 1);
06 自动装箱与拆箱
- 自动装箱:将基本类型转换为包装类型
- 自动拆箱:将包装类型转换为基本类型
代码示例:
// 1.自动装箱,将int类型的10可以直接赋值给Integer类型的i
Integer i = 10;
// 2.自动拆箱,将Integer类型的i可以直接赋值给int类型的n
int n = i; //拆箱
07 String的不可变性
在 Java 8 中,String 内部使用 char 数组存储数据。并且被声明为final,因此它不可被继承
// String源码
public final class String implements java.io.Serializable, Comparable, CharSequence {
private final char value[];
}
为什么String要设计成不可变的呢(不可变性的好处):
1. 可以缓存 hash 值( )
因为 String 的hash值经常被使用,例如String 用做 HashMap 的 key。不可变的特性可以使得 hash值也不可变, 因此只需要进行一次计算。
2. 常量池优化
String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
3. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
08 字符常量和字符串常量的区别
1.形式上:字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符
2.含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
3.占内存大小:字符常量占两个字节,字符串常量占若干个字节(至少一个字符结束标志)
09 字符串常量池
字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用
10 String 类的常用方法
面试时一般不会问,但面试或笔试写字符串相关的算法题经常会涉及到,还是得背一背(以下大致 是按使用频率优先级排序)
length()
:返回字符串长度charAt()
:返回指定索引处的字符substring()
:截取字符串trim()
:去除字符串两端空白split()
:分割字符串,返回一个分割后的字符串数组。replace()
:字符串替换。indexOf()
:返回指定字符的索引。toLowerCase()
:将字符串转成小写字母。toUpperCase()
:将字符串转成大写字符。
11 String和StringBuffer、StringBuilder的区别
1. 可变性
String 不可变, StringBuilder 和 StringBuffer 是可变的
2. 线程安全性
String 由于是不可变的,所以线程安全。 StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。 StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
3. 性能
StringBuilder
> StringButter
> String
为了方便记忆,总结如下
区别 | 是否可变 | 是否安全 | 性能 |
---|---|---|---|
String | 不可变 | 安全 | 低 |
StringBuilder | 可变 | 不安全 | 高 |
StringBuffer | 可变 | 安全 | 较高 |
12 switch 是否能作用在 byte,long 和String 上
1.switch 可以作用于char byte short int 及它们对应的包装类型
2.switch 不可作用于long double float boolean 及他们的包装类型
3.在JDK1.5之后可以作用于枚举类型
4.在JDK1.7之后可作用于String 类型
13 Java语言采用何种编码方案?有何特点?
Java语言采用Unicode编码标准,它为每个字符制订了一个唯一的数值,因此在任何的语言,平台, 程序都可以放心的使用。
14 访问修饰符
在Java编程语言中有四种权限访问控制符,这四种访问权限的控制符能够控制类中成员的可见性。其中类有两种public 、 default 。而方法和变量有 4 种: public 、 default 、 protected 、private。
- public : 对所有类可见。使用对象:类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
- default : 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
修饰符 | 当前类 | 同包内 | 子类(同包) | 其他包 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
default | Y | Y | Y | N |
private | Y | N | N | N |
15 &,&&和|,||的区别
1. &&和&
&&和&都可以表示逻辑与,但他们是有区别的,共同点是他们两边的条件都成立的时候最终结果才是true;不同点是&&只要是第一个条件不成立为false,就不会再去判断第二个条件,最终结果直接为false,而&判断的是所有的条件。
2. ||和|
||和|都表示逻辑或,共同点是只要两个判断条件其中有一个成立最终的结果就是true,区别是||只要满足第一个条件,后面的条件就不再判断,而|要对所有的条件进行判断。
16 static关键字
static关键字的主要用途 就是方便在没有创建对象时调用方法和变量和优化程序性能
1.static变量(静态变量)
用static修饰的变量被称为静态变量,也被称为类变量,可以直接通过类名来访问它。静态变量被所有的对象共享,在内存中只有一个副本,仅当在类初次加载时会被初始化,而非静态变量在创建对象的时候被初始化,并且存在多个副本,各个对象拥有的副本互不影响。
2.static方法(静态方法)
static方法不依赖于任何对象就可以进行访问,在static方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是在非静态成员方法中是可以访问静态成员方法/变量的。
package com.syh;
public class Esther {
// 1.静态变量
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);
// 2.静态方法中不能直接访问非静态变量,代码报错
System.out.println(s2);
}
}
3.static代码块(静态代码块)
静态代码块的主要用途是可以用来优化程序的性能,因为它只会在类加载时加载一次,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。如果程序中有多个static块,在类初次被加载的时候,会按照static块的顺序来执行每个static块。
package com.syh;
public class Esther {
static {
System.out.println("hello,word");
}
public static void main(String[] args) {
Esther esther = new Esther();
}
}
4.可以通过this访问静态成员变量吗?(可以)
this代表当前对象,可以访问静态变量,而静态方法中是不能访问非静态变量,也不能使用this引用。
5.初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。如果存在继承关系的话,初始化顺序为 父类中的静态变量和静态代码块——子类中的静态变量和静态代码块——父类中的实例变量和普通代码块——父类的构造函数——子类的实例变量和普通代码块——子类的构造函数
17 final 关键字
final关键字主要用于修饰类,变量,方法。
- 类:被final修饰的类不可以被继承
- 方法:被final修饰的方法不可以被重写
- 变量:被final修饰的变量是基本类型,变量的数值不能改变;被修饰的变量是引用类型,变量便不能在引用其他对象,但是变量所引用的对象本身是可以改变的。
package com.syh;
public class Esther {
int a = 1;
public static void main(String[] args) {
final int b = 1;
// 1.报错,final变量不能被修改
b = 2;
final Esther esther = new Esther();
// 2.不报错,可以改变引用类型变量所指向的对象
esther.a = 2;
}
}
18 final finally finalize区别
1.final主要用于修饰类,变量,方法
2.finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
3.finalize是一个属于Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾,但Java语言规范并不保证inalize方法会被及时地执行、而且根本不会保证它们会被执行。
19 this关键字
重点掌握前三种即可
- 1.this关键字可用来引用当前类的实例变量。主要用于形参与成员名字重名,用this来区分。
package com.syh;
public class Esther {
private String name;
private int age;
/**
* 构造方法
* @param name
* @param age
*/
public Esther(String name, int age){
this.name = name;
this.age = age;
}
}
- 2.this关键字可用于调用当前类方法。
package com.syh;
public class Esther {
public void fun1() {
System.out.println("hello,word");
}
public void fun2() {
// this可以省略
this.fun1();
}
public static void main(String[] args) {
Esther esther = new Esther();
esther.fun2();
}
}
- 3.this()可以用来调用当前类的构造函数。(注意:this()一定要放在构造函数的第一行,否则编译不通过)
package com.syh;
public class Esther {
private String name;
private int age;
public Esther() {
}
public Esther(String name) {
this.name = name;
}
public Esther(String name, int age) {
this(name);
this.age = age;
}
}
- 4.this关键字可作为调用方法中的参数传递。
- 5.this关键字可作为参数在构造函数调用中传递。
- 6.this关键字可用于从方法返回当前类的实例。super
20 super关键字
- super可以用来引用直接父类的实例变量。和this类似,主要用于区分父类和子类中相同的字段
- super可以用来调用直接父类构造函数。(注意:super()一定要放在构造函数的第一行,否则编译不通过)
- super可以用来调用直接父类方法。
package com.syh;
/**
* 父类
*/
public class Father {
protected String name;
public Father(String name) {
this.name = name;
}
public void Say() {
System.out.println("hello,child");
}
}
package com.syh;
/**
* 子类
*/
public class Child extends Father {
private String name;
public Child(String name1, String name2) {
// 1. 调用父类构造方法
super(name1);
this.name = name2;
}
public void test() {
System.out.println(this.name);
// 2.引用直接父类的实例变量
System.out.println(super.name);
// 3.调用直接父类的方法
super.Say();
}
}
package com.syh;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Child child = new Child("Father", "Child");
child.test();
}
}
21 this与super的区别
1. 相同点:
- super()和this()都必须在构造函数的第一行进行调用,否则就是错误的
- this()和super()都指的是对象,所以,均不可以在static环境中使用。
2. 不同点:
-
super()主要是对父类构造函数的调用,this()是对重载构造函数的调用
-
super()主要是在继承了父类的子类的构造函数中使用,是在不同类中的使用;this()主要是在同一类的不同构造函数中的使用
22 break ,continue ,return 的区别
1.break结束当前的循环体
2.continue结束本次循环,进入下一次循环
3.return结束当前方法
23 面向对象和面向过程的区别
1. 面向过程
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。
- 缺点:没有面向对象易维护、易复用、易扩展
2. 面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
24 面向对象三大特性(封装、继承、多态)
1. 封装
封装就是隐藏对象的属性和实现细节,仅对外公开接口 ,控制在程序中属性的读和修改的访问级别。
2. 继承
继承就是子类继承父类的特征和行为 ,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
3. 多态(重要)
多态是同一个行为具有多个不同表现形式或形态的能力。这句话不是很好理解,可以看这个解释,在Java语言中,多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
**在Java中实现多态的三个必要条件:**继承、重写、向上转型。继承和重写很好理解,向上转型是指在多态中需要将子类的引用赋给父类对象。
package com.syh;
public class Esther {
public static void main(String[] args) {
// 1.向上转型
Person person = new Student();
person.run();
}
}
class Person {
public void run() {
System.out.println("Person");
}
}
// 2.继承
class Student extends Person {
// 3.重载
@Override
public void run() {
System.out.println("Student");
}
}
25 面向对象五大基本原则
1.单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。 单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
2.开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
3.里氏替换原则 (Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类
4.依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
5.接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口。
26 抽象类和接口的对比
在Java语言中,abstract class和interface是支持抽象类定义的两种机制。抽象类:用来捕捉子类的通用特性的。接口:抽象方法的集合。
1. 相同点:
- 接口和抽象类都不能实例化
- 都包含抽象方法,其子类都必须覆写这些抽象方法
2. 不同点:
类型 | 抽象类 | 接口 |
---|---|---|
定义 | abstract class | Interface |
实现 | extends(需要提供抽象类中所有声明的方法的实现) | implements(需要提供接口中所有声明的方法的实现) |
继承 | 抽象类可以继承一个类和实现多个接口;子类只可以继承一个抽象类 | 接口只可以继承接口(一个或多个);子类可以实现多个接口 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
27 构造方法
1. 构造方法的作用
Java程序存在继承,在执行子类的构造方法时,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”,如果父类只定义了有参数的构造函数,而子类的构造函数没有用super调用父类那个特定的构造函数,就会出错。
2. 为什么在调用子类构造方法之前会先调用父类的无参构造方法
帮助子类做初始化工作。
3. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
28 构造方法有哪些特性?
- 方法名称和类同名
- 不用定义返回值类型
- 不可以写retrun语句
- 构造方法可以被重载
29 变量
- **静态变量:**也叫类变量,独立于方法之外的变量,用static修饰。
- **实例变量:**独立于方法之外的变量,不过没有 static 修饰。
- **局部变量:**类的方法中的变量。
- **成员变量:**成员变量又称全局变量,可分为静态变量和实例变量,有static修饰为静态变量,没有static修饰为实例变量。
类别 | 静态变量 | 实例变量 | 局部变量 |
---|---|---|---|
定义位置 | 类中,方法外 | 类中,方法外 | 方法中 |
初始值 | 有默认初始值 | 有默认初始值 | 无默认 初始值 |
存储位置 | 方法区 | 堆 | 栈 |
生命周期 | 类何时被加载和卸载 | 实例何时被创建及销毁 | 方法何时被调用及结束调用 |
30 内部类
内部类包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类
- 成员内部类
1.成员内部类定义为位于另一个类的内部,成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)
2.当成员内部类拥有和外部类同名的成员变量或者方法时,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:外部类.this.成员变量
3.在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
4.成员内部类是依附外部类而存在的,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
package com.syh;
public class Outter {
private double a = 0;
public static int b = 1;
public Outter() {
}
public Outter(double a) {
this.a = a;
Inner inner = new Inner();
inner.fun();
}
// 1.成员内部类
class Inner {
int b = 2;
public void fun() {
System.out.println(a);
// 2.访问内部类的成员变量b
System.out.println(b);
// 3.访问外部类的成员变量b
System.out.println(Outter.this.b);
}
}
}
package com.syh;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
// 4.创建内部类对象
Outter.Inner inner = outter.new Inner();
}
}
- 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
package com.syh;
public class Outter {
private int outter_a = 1;
private static int static_b = 2;
public void test1() {
int inner_c = 3;
// 1.局部内部类
class Inner {
private void fun() {
System.out.println(outter_a);
System.out.println(static_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void test2() {
int inner_d = 3;
class Inner {
private void fun() {
// 2.编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
System.out.println(outter_a);
System.out.println(static_b);
System.out.println(inner_d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
- 匿名内部类
匿名内部类只没有名字的内部类,在日常开发中使用较多。使用匿名内部类的前提条件:必须继承一个父类或实现一个接口。
package com.syh;
interface Person {
public void fun();
}
class Demo {
public static void main(String[] args) {
new Person() {
public void fun() {
System.out.println("hello,word");
}
}.fun();
}
}
- 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
package com.syh;
public class Outter {
int a = 1;
static int b = 2;
public Outter() {
}
static class Inner {
public Inner() {
// 1.报错,静态内部类不能访问非静态变量
System.out.println(a);
System.out.println(b);
}
}
}
package com.syh;
public class Main {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
内部类的优点:
- 1.内部类不为同一包的其他类所见,具有很好的封装性;
- 2.匿名内部类可以很方便的定义回调。
- 3.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
- 4.内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
31 重写与重载
1.重载:
发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
2.重写:
发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写
3.构造器是否可被重写:
构造器可以被重载,不能被重写
4.重载的方法能否根据返回类型进行区分?
不能,因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。
32 == 和 equals 的区别
1.==
对于基本数据类型,==比较的是值;
对于引用数据类型,==比较的是内存地址
。
2.eauals
对于没有重写equals方法的类,equals方法和==作用类似;
对于重写过equals方法的类,equals比较的是值。
33 hashCode 与 equals
1. equals
因为String类重写了equals()方法,所以比较的是值
2. hashcode
hashCode方法返回对象的散列码,返回值是int类型的散列码。散列码的作用是确定该对象在哈希表中的索引位置。
关于hashCode有一些约定:
- 两个对象相等,则hashCode一定相同。
- 两个对象有相同的hashCode值,它们不一定相等。
- hashCode()方法默认是对堆上的对象产生独特值,如果没有重写hashCode()方法,则该类的两个对象的hashCode值肯定不同
3. 为什么重写equals方法后,hashCode方法也必须重写
根据规定,两个对象相等,hashCode值也许相同,所以重写equals方法后,hashCode方法也必须重写,重写之后可以提高集合效率。
34 Java值传递
这是一个很容易搞混又很难解释清楚的问题,先说结论,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。
- 值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
- 引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
1. 传递基本类型参数
package com.syh;
public class Esther {
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
输出结果:
a = 20
b = 10
num1 = 10
num2 = 20
解析:在 swap() 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本身。
2. 传递引用类型参数1
package com.syh;
public class Esther {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
}
输出结果:
1
0
解析:看了这个案例很多人肯定觉得 Java 对引用类型的参数采用的是引用传递。实际上,并不是的,这里传递的还是值,不过,这个值是实参的地址罢了!
3. 传递引用类型参数2
package com.syh;
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Person{name = " + name + "}";
}
public static void main(String[] args) {
Person xiaoZhang = new Person("小张");
Person xiaoLi = new Person("小李");
swap(xiaoZhang, xiaoLi);
System.out.println("xiaoZhang:" + xiaoZhang.getName());
System.out.println("xiaoLi:" + xiaoLi.getName());
}
public static void swap(Person person1, Person person2) {
Person temp = person1;
person1 = person2;
person2 = temp;
System.out.println("person1:" + person1.getName());
System.out.println("person2:" + person2.getName());
}
}
输出结果:
person1:小李
person2:小张
xiaoZhang:小张
xiaoLi:小李
怎么回事???两个引用类型的形参互换并没有影响实参啊!
解析:swap方法的参数 person1和 person2只是拷贝的实参 xiaoZhang和 xiaoLi的地址。因此, person1和 person2的互换只是拷贝的两个地址的互换罢了,并不会影响到实参 xiaoZhang和 xiaoLi 。
35 IO流
Java IO流主要可以分为输入流和输出流。按照照操作单元划分,可以划分为字节流和字符流。按照流的角色划分为节点流和处理流。
Java IO流的 40 多个类都是从 4 个抽象类基类中派生出来的。
- **InputStream:**字节输入流
- **Reader:**字符输入流
- **OutputStream:**字节输出流
- **Writer:**字符输出流
36 BIO,NIO,AIO 有什么区别?
1.BIO (Blocking I/O): 服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io
2.NIO (New I/O): 服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持
3.AIO (Asynchronous I/O): 服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。
这些概念看着比较枯燥,可以从这个经典的烧开水的例子去理解
- BIO :来到厨房,开始烧水,并坐在水壶面前一直等着水烧开。
- NIO :来到厨房,开始烧水,但是我们不一直坐在水壶前面等,而是做些其他事,然后每隔几分钟到厨房看一下水有没有烧开。
- AIO :来到厨房,开始烧水,我们不一直坐在水壶前面等,而是在水壶上面装个开关,水烧开之后它会通知我。
37 反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
1. Java获取Class对象的三种方式
package com.syh;
public class Person {
public String name = "zhangsan";
public Person() {
}
}
package com.syh;
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.syh.Person");
}
}
注意:因为在一个类在 JVM 中只会有一个 Class 实例,所以对c1、c2、c3进行equals比较时返回的都是true。
2. 反射优缺点:
- **优点:**运行期类型的判断,动态加载类,提高代码灵活度。
- **缺点:**性能比直接的java代码要慢很多。
3. 反射应用场景:
- Java的很多框架都用到了反射,例如Spring中的xml的配置模式等
- 动态代理设计模式也采用了反射机制
38 Java异常
1.主要分为Error和Exception两种
- **Error:**Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理。
- **Exception:**Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
2. 异常框图
3. try catch finally
- try用于捕获异常
- catch用于处理异常
- finally中是总会执行的代码
39 Java注解
面试问的不多,但是在使用框架开发时会经常使用,但东西太多了,这里只是简单介绍下概念。
Annotation注解可以看成是java中的一种标记记号,用来给java中的类,成员,方法,参数等任何程序元素添加一些额外的说明信息,同时不改变程序语义。注解可以分为三类:基本注解,元注解,自定义注解
1. 标准注解
- @Deprecated:该注解用来说明程序中的某个元素(类,方法,成员变量等)已经不再使用,如果使用的话的编译器会给出警告。
- @SuppressWarnings(value=“”):用来抑制各种可能出现的警告。
- @Override:用来说明子类方法覆盖了父类的方法,保护覆盖方法的正确使用
2. 元注解
- @Target(value=“ ”):该注解是用来限制注解的使用范围的,即该注解可以用于哪些程序元素。
- @Retention(value=“ ”):用于说明注解的生存周期
- @Documnent:用来说明指定被修饰的注解可以被javadoc.exe工具提取进入文档中,所有使用了该注解进行标注的类在生成API文档时都在包含该注解的说明。
- @Inherited:用来说明使用了该注解的父类,其子类会自动继承该注解。
- @Repeatable:java1.8新出的元注解,如果需要在给程序元素使用相同类型的注解,则需将该注解标注上。
3. 自定义注解
用@Interface来声明注解。
40 Java泛型
Java 泛型是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
- 泛型擦除
Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。看下面代码
package com.syh;
import java.util.ArrayList;
public class Person {
public static void main(String[] args) {
// 泛型擦除
ArrayList arrayList1 = new ArrayList<>();
ArrayList arrayList2 = new ArrayList<>();
System.out.println(arrayList1.getClass() == arrayList2.getClass()); // true
}
}
41 JAVA序列化
**1. 序列化:**将对象写入到IO流中
**2. 反序列化:**从IO流中恢复对象
**3. 序列化的意义:**将Java对象转换成字节序列,这些字节序列更加便于通过网络传输或存储在磁盘上,在需要时可以通过反序列化恢复成原来的对象。
4. 实现方式:
- 实现 Serializable 接口
- 实现 Externalizable 接口
5. 序列化的注意事项
- 对象的类名、实例变量会被序列化;方法、类变量、transient实例变量都不会被序列化。
- 某个变量不被序列化,可以使用transient修饰。
- 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
- 反序列化时必须有序列化对象的class文件。
42 深拷贝与浅拷贝
**深拷贝:**对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,两个引用指向两个对象,但对象内容相同。
**浅拷贝:**对基本数据类型进行值传递,对引用数据类型复制一个引用指向原始引用的对象,就是复制的引用和原始引用指向同一个对象。
43 常见的Object方法
这些方法都很重要,面试经常会问到,要结合其他知识将这些方法理解透彻
- Object clone():创建与该对象的类相同的新对象
- boolean equals(Object):比较两对象是否相等
- void finalize():当垃圾回收器确定不存在对该对象的更多引用时,对象垃圾回收器调用该方法
- Class getClass():返回一个对象运行时的实例类
- int hashCode():返回该对象的散列码值
- void notify():唤醒等待在该对象的监视器上的一个线程
- void notifyAll():唤醒等待在该对象的监视器上的全部线程
- String toString():返回该对象的字符串表示
- void wait():在其他线程调用此对象的 notify() 方法或 notifyAll()方法前,导致当前线程等待