Java八股系列——Java基础
Posted _瞳孔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java八股系列——Java基础相关的知识,希望对你有一定的参考价值。
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔空间
1.Java 语⾔具有哪些特点?
- Java 为纯⾯向对象的语⾔。它能够直接反应现实⽣活中的对象。
- 具有平台⽆关性。Java 利⽤ Java 虚拟机运⾏字节码,⽆论是在 Windows、Linux 还是 MacOS 等其它平台对 Java 程序进⾏编译,编译后的程序可在其它平台运⾏。
- Java 为解释型语⾔,编译器把 Java 代码编译成平台⽆关的中间代码,然后在 JVM 上解释运⾏,具有很好的可移植性。
- Java 提供了很多内置类库。如对多线程⽀持,对⽹络通信⽀持,最重要的⼀点是提供了垃圾回收器。
- Java 具有较好的安全性和健壮性。Java 提供了异常处理和垃圾回收机制,去除了 C++中难以理解的指针特性
2.简述 Java 基本数据类型
- byte: 占⽤ 1 个字节,取值范围-128 ~ 127
- short: 占⽤ 2 个字节,取值范围-215 ~ 215-1
- int:占⽤ 4 个字节,取值范围-231 ~ 231-1
- long:占⽤ 8 个字节
- float:占⽤ 4 个字节
- double:占⽤ 8 个字节
- char: 占⽤ 2 个字节
- boolean:占⽤⼤⼩根据实现虚拟机不同有所差异
3.简述⾃动装箱拆箱
对于 Java 基本数据类型,均对应⼀个包装类。
装箱就是⾃动将基本数据类型转换为包装器类型,如 int->Integer
拆箱就是⾃动将包装器类型转换为基本数据类型,如 Integer->int
4.简述 Java 访问修饰符
- default: 默认访问修饰符,在同⼀包内可⻅
- private: 在同⼀类内可⻅,不能修饰类
- protected : 对同⼀包内的类和所有⼦类可⻅,不能修饰类
- public: 对所有类可⻅
5.构造⽅法、成员变量以及静态成员变量三者的初始化顺序
先后顺序:静态成员变量、成员变量、构造⽅法。
详细的先后顺序:
⽗类静态变量
⽗类静态代码块
⼦类静态变量
⼦类静态代码块
⽗类⾮静态变量
⽗类⾮静态代码块
⽗类构造函数
⼦类⾮静态变量
⼦类⾮静态代码块
⼦类构造函数
6.Java 代码块执⾏顺序
- ⽗类静态代码块(只执⾏⼀次)
- ⼦类静态代码块(只执⾏⼀次)
- ⽗类构造代码块
- ⽗类构造函数
- ⼦类构造代码块
- ⼦类构造函数
- 普通代码块
代码块说明:
普通代码块:就是在方法后面使用""括起来的代码片段,不能单独执行,必须调下其方法名才可以执行。
静态代码块:在类中使用static修饰,并使用""括起来的代码片段,用于静态变量的初始化或对象创建前的环境初始化。
同步代码块:使用synchronize关键字修饰,并使用""括起来的代码片段。它表示在同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。
构造代码块:在类中没与任何的前缀或后缀,并使用""括起来的代码片段。
7.⾯向对象的三⼤特性
- 封装:将客观事物抽象成类,每个类可以把⾃身数据和⽅法只让可信的类或对象操作,对不可信的进⾏信息隐藏。
- 继承:对象的⼀个新类可以从现有的类中派⽣,派⽣类可以从它的基类那继承⽅法和实例变量,且派⽣类可以修改或新增新的⽅法使之更适合特殊的需求。
- 多态:允许不同类的对象对同⼀消息作出响应。不同对象调⽤相同⽅法即使参数也相同,最终表现⾏为是不⼀样的。
8.为什么 Java 语⾔不⽀持多重继承
原因一:为了程序的结构能够更加清晰从⽽便于维护。
假设 Java 语⾔⽀持多重继承,类 C 继承⾃类 A 和类 B,如果类 A 和 B 都有⾃定义的成员⽅法 f() ,
那么当代码中调⽤类 C 的 f() 会产⽣⼆义性。
Java 语⾔通过实现多个接⼝间接⽀持多重继承。接⼝由于只包含⽅法定义,不能有⽅法的实现,类 C 继承
接口 A 与接口 B 时即使它们都有⽅法 f() ,也不能直接调⽤⽅法,需实现具体的 f() ⽅法才能调⽤,不会产⽣⼆义性。
// TODO:类型转换?
原因二:多重继承会使类型转换、构造⽅法的调⽤顺序变得复杂,会影响到性能。
9.简述 Java 的多态
Java 多态可以分为编译时多态和运⾏时多态。
编译时多态主要指⽅法的重载,即通过参数列表的不同来区分不同的⽅法。
运⾏时多态主要指继承⽗类和实现接⼝时,可使⽤⽗类引⽤指向⼦类对象。
运⾏时多态的实现:主要依靠⽅法表,⽅法表中最先存放的是 Object 类的⽅法,接下来是该类的⽗类的⽅
法,最后是该类本身的⽅法。如果⼦类改写了⽗类的⽅法,那么⼦类和⽗类的那些同名⽅法共享⼀个⽅法表
项,都被认作是⽗类的⽅法。因此可以实现运⾏时多态。
运行时多态实现的前提
1、运用了继承或者实现
2、实现了方法的重写
3、使用了父类引用指向子类对象
多态的作用
在实际开发的过程中,使用父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,
可以实现使用一个方法对多种子类对象进行同一个操作,提高了程序的扩展性,也简化了代码的开发
(不再需要为了对多种子类对象实现同一个功能而编写多个函数)。
10.Java 提供的多态机制
Java 提供了两种⽤于多态的机制,分别是重载与覆盖。
重载:重载是指同⼀个类中有多个同名的⽅法,但这些⽅法有不同的参数,在编译期间就可以确定调⽤哪个⽅法。
覆盖:覆盖是指派⽣类重写基类的⽅法,使⽤基类指向其⼦类的实例对象,或接⼝的引⽤变量指向其实现类
的实例对象,在程序调⽤的运⾏期根据引⽤变量所指的具体实例对象调⽤正在运⾏的那个对象的⽅法,即需
要到运⾏期才能确定调⽤哪个⽅法。
11.接⼝和抽象类的相同点和不同点
相同点:
- 都不能被实例化。
- 接⼝的实现类或抽象类的⼦类需实现接⼝或抽象类中相应的⽅法才能被实例化。
不同点:
- 接⼝只能有⽅法定义,不能有⽅法的实现,⽽抽象类可以有⽅法的定义与实现。
- 实现接⼝的关键字为 implements,继承抽象类的关键字为 extends。⼀个类可以实现多个接⼝,只能继承⼀个抽象类。
- 当⼦类和⽗类之间存在逻辑上的层次结构,推荐使⽤抽象类,有利于功能的累积。当功能不需要,希望⽀持差别较⼤的两个或更多对象间的特定交互⾏为,推荐使⽤接⼝。使⽤接⼝能降低软件系统的耦合度,便于日后维护或添加删除⽅法。
12.简述内部类及其作⽤
内部类是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员,并且依附于外部类而存在的。
内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。
内部类的共性:
1: 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,
但是前面冠以外部类的类名和$符号。
2: 内部类不能用普通的方式访问。
3: 内部类声明成静态的,就不能随便的访问外部类的成员变量了,
此时内部类只能访问外部类的静态成员变量。
4: 外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问
为什么需要内部类
1: 内部类方法可以访问该类定义所在的作用域的数据,包括私有的数据
2: 内部类可以对同一个包中的其他类隐藏起来,一般的非内部类,
是不允许有 private 与protected权限的,但内部类可以
3: 可以实现多重继承
4: 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
成员内部类:
作为成员对象的内部类。可以访问 private 及以上外部类的属性和⽅法。
外部类想要访问内部类属性或⽅法时,必须要创建⼀个内部类对象,
然后通过该对象访问内部类的属性或⽅法。外部类也可访问 private 修饰的内部类属性。
public class OutClass
private String name = " "; // 成员属性
public class InnerClass // 成员内部类
private String name = ""; // 成员内部类中的属性可以和外部类的属性同名
private static Integer age = 18; // 此处报编译错误,成员内部类中的属性不可以用static修饰
public static void print() // 内部类中的方法也不能用static修饰
局部内部类:
存在于⽅法中的内部类.
不能使用访问控制修饰符和 static 修饰符修饰。
局部内部类只在当前方法中有效。
局部内部类中不能定义 static 成员。
局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符和 static 修饰符修饰。
在局部内部类中可以访问外部类的所有成员。
在局部内部类中只可以访问当前方法中 final 类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 <OuterClassName>.this.<MemberName> 的形式访问外部类中的成员。
public class Test
int a = 0;
int d = 0;
public void method()
int b = 0;
final int c = 0;
final int d = 10;
class Inner
int a2 = a; // 访问外部类中的成员
// int b2 = b; // 编译出错
int c2 = c; // 访问方法中的成员
int d2 = d; // 访问方法中的成员
int d3 = Test.this.d; //访问外部类中的成员
Inner i = new Inner();
System.out.println(i.d2); // 输出10
System.out.println(i.d3); // 输出0
public static void main(String[] args)
Test t = new Test();
t.method();
匿名内部类:
匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。
使用匿名类可使代码更加简洁、紧凑,模块化程度更高。
匿名类和局部内部类一样,可以访问外部类的所有成员。
如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数。
匿名类中允许使用非静态代码块进行成员初始化操作。
匿名类的非静态代码块会在父类的构造方法之后被执行。
public class Out
void show()
System.out.println("调用 Out 类的 show() 方法");
public class TestAnonymousInterClass
// 在这个方法中构造一个匿名内部类
private void show()
Out anonyInter = new Out()
// 获取匿名内部类的实例
void show()
System.out.println("调用匿名类中的 show() 方法");
;
anonyInter.show();
public static void main(String[] args)
TestAnonymousInterClass test = new TestAnonymousInterClass();
test.show(); // 调用匿名类中的 show() 方法
静态内部类:
静态内部类是指使用 static 修饰的内部类
在创建静态内部类的实例时,不需要创建外部类的实例。
静态内部类中可以定义静态成员和实例成员。
外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。
静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
public class Outer
static class Inner
int a = 0; // 实例变量a
static int b = 0; // 静态变量 b
class OtherClass
Outer.Inner oi = new Outer.Inner();
int a2 = oi.a; // 访问实例成员
int b2 = Outer.Inner.b; // 访问静态成员
13.Java 语⾔中关键字 static 的作⽤是什么
static 的主要作⽤有两个:
- 为某种特定数据类型或对象分配与创建对象个数⽆关的单⼀的存储空间。
- 使得某个⽅法或属性与类⽽不是对象关联在⼀起,即在不创建对象的情况下可通过类直接调⽤⽅法或使⽤类的属性。
具体⽽⾔ static ⼜可分为 4 种使⽤⽅式:
- 修饰成员变量。⽤ static 关键字修饰的静态变量在内存中只有⼀个副本。只要静态变量所在的类被加载,这个静态变量就会被分配空间,可以使⽤“类.静态变量”和“对象.静态变量”的⽅法使⽤。
- 修饰成员⽅法。static 修饰的⽅法⽆需创建对象就可以被调⽤。static ⽅法中不能使⽤ this 和 super 关键字,不能调⽤⾮ static ⽅法,只能访问所属类的静态成员变量和静态成员⽅法。
- 修饰代码块。JVM 在加载类的时候会执⾏ static 代码块。static 代码块常⽤于初始化静态变量。static代码块只会被执⾏⼀次。
- 修饰内部类。static 内部类可以不依赖外部类实例对象⽽被实例化。静态内部类不能与外部类有相同的名字,不能访问普通成员变量,只能访问外部类中的静态成员和静态成员⽅法。
14.为什么要把 String 设计为不可变
- 节省空间:字符串常量池(String pool)是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串已经存在于常量池中,则不会创建新的对象,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。
- 多线程安全:多线程中,可变对象的值很可能被其他线程改变,造成不可预期的结果。而不可变的 String 可以自由在多个线程之间共享,不需要同步处理。
- hashcode 缓存的需要:因为字符串不可变,所以在它创建的时候 hashcode 就被缓存了,不需要重新计算。这就使得字符串很适合作为 HashMap 中的 key,效率大大提高。
15.简述 String/StringBuffer 与 StringBuilder
- String 类采⽤利⽤ final 修饰的字符数组进⾏字符串保存,因此不可变。如果对 String 类型对象修改,需要新建对象,将⽼字符和新增加的字符⼀并存进去。
- StringBuilder,采⽤⽆ final 修饰的字符数组进⾏保存,因此可变。但线程不安全。相较于StringBuffer有速度优势,因此多数情况下建议使用 StringBuilder类。
- StringBuffer,采⽤⽆ final 修饰的字符数组进⾏保存,因此可变。所有公开方法都是 synchronized 修饰的,所以线程安全。在应用程序要求线程安全的情况下必须使用 StringBuffer 类。
16.判等运算符 == 与 equals 的区别
== ⽐较的是引⽤,equals ⽐较的是内容。
如果变量是基础数据类型,== ⽤于⽐较其对应值是否相等。如果变量指向的是对象,== ⽤于⽐较两个对象
是否指向同⼀块存储空间。
equals 是 Object 类提供的⽅法之⼀,每个 Java 类都继承⾃ Object 类,所以每个对象都具有 equals 这个
⽅法。Object 类中定义的 equals ⽅法内部是直接调⽤ == ⽐较对象的。但通过覆盖的⽅法可以让它不是⽐
较引⽤⽽是⽐较数据内容。
17.简述 Object 类常⽤⽅法
- hashCode:通过对象计算出的散列码。⽤于 map 型或 equals ⽅法。需要保证同⼀个对象多次调⽤该⽅法,总返回相同的整型值。
- equals:判断两个对象是否⼀致。需保证 equals ⽅法相同对应的对象 hashCode 也相同。
- toString: ⽤字符串表示该对象
- clone:深拷⻉⼀个对象
18.深拷贝与浅拷贝
首先可以了解一下引用拷贝与对象拷贝
引用拷贝:创建一个指向对象的引用变量的拷贝。
Test1 test = new Test1();
Test1 cloneTest = test;
System.out.println(test.hashCode()); // 1360875712
System.out.println(cloneTest.hashCode()); // 1360875712
对象拷贝:创建对象本身的一个副本。
// 需要实现Cloneable接口,否则会报错
public class Test1 implements Cloneable
@Override
protected Test1 clone() throws CloneNotSupportedException
Test1 clone = null;
try
clone = (Test1) super.clone();
catch(CloneNotSupportedException e)
throw new RuntimeException(e); // won't happen
return clone;
public static void main(String[] args) throws CloneNotSupportedException
Test1 test = new Test1();
Test1 cloneTest = (Test1)test.clone();
// 输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象
// 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。
System.out.println(test.hashCode()); // 1360875712
System.out.println(cloneTest.hashCode()); // 1625635731
深拷贝和浅拷贝都是对象拷贝
浅拷贝:
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。
"里面的对象“会在原来的对象和它的副本之间共享。
简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象
public class Test1
public static void main(String[] args) throws CloneNotSupportedException
Teacher teacher = new Teacher();
teacher.setName("老师");
Student student1 = new Student();
student1.setName("学生1");
student1.setTeacher(teacher);
Student student2 = (Student) student1.clone();
System.out.println(student2.getName()); // 学生1
System.out.println(student2.getTeacher().getName()); // 老师
// 修改老师的信息
teacher.setName("老师hhh");
System.out.println(student1.getTeacher().getName()); // 老师hhh
System.out.println(student2.getTeacher().getName()); // 老师hhh
class Teacher implements Cloneable
private String name;
public String getName()
return name;
public void setName(String name)
this.name = name;
class Student implements Cloneable
private String name;
private Teacher teacher;
public String getName()
return name;
public void setName(String name)
this.name = name;
public Teacher getTeacher()
return teacher;
public void setTeacher(Teacher teacher)
this.teacher = teacher;
@Override
public Object clone() throws CloneNotSupportedException
return super.clone();
深拷贝:
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
public class Test1
public static void main(String[] args) throws CloneNotSupportedException
Teacher teacher = new Teacher();
teacher.setName("老师");
Student student1 = new Student();
student1.setName("学生1");
student1.setTeacher(teacher);
Student student2 = (Student) student1.clone();
System.out.println(student2.getName()); // 学生1
System.out.println(student2.getTeacher().getName()); // 老师
// 修改老师的信息
teacher.setName("老师hhh");
System.out.println(student1.getTeacher().getName()); // 老师hhh
System.out.println(student2.getTeacher().getName()); // 老师
class Teacher implements Cloneable
private String name;
public String getName()
return name;
public void setName(String name)
this.name = name;
@Override
public Object clone() throws CloneNotSupportedException
return super.clone();
class Student implements Cloneable
private String name;
private Teacher teacher;
public String getName()
return name;
public void setName(String name)
this.name = name;
public Teacher getTeacher()
return teacher;
public void setTeacher(Teacher teacher)
this.teacher = teacher;
@Override
public Object clone() throws CloneNotSupportedException
// 改为深复制:
Student student = (Student) super.clone();
// 本来是浅复制,现在将Teacher对象复制一份并重新set进来
student.setTeacher((Teacher) student.getTeacher().clone());
return student;
19.简述Java异常的分类
Java 异常分为两种, 这两个类均继承 Throwable:
Error(程序⽆法处理的错误)
Exception(程序本身可以处理的异常)
Error 常⻅的有 StackOverFlowError、OutOfMemoryError 等等。
Exception 可分为运⾏时异常和⾮运⾏时异常。
对于运⾏时异常,可以利⽤ try catch 的⽅式进⾏处理,也可以不处理。
对于⾮运⾏时异常,必须处理,不处理的话程序⽆法通过编译。
20.简述 throw 与 throws 的区别
- throw ⼀般是⽤在⽅法体的内部,由开发者定义当程序语句出现问题后主动抛出⼀个异常。
- throws ⼀般⽤于⽅法声明上,代表该⽅法可能会抛出的异常列表。
21.出现在 Java 程序中的 finally 代码块是否⼀定会执⾏
当遇到下⾯情况不会执⾏。
当程序在进⼊ try 语句块之前就出现异常时会直接结束。
当程序在 try 块中强制退出时,如使⽤ System.exit(0),也不会执⾏ finally 块中的代码。
其它情况下,在 try/catch/finally 语句执⾏的时候,try 块先执⾏,当有异常发⽣,catch 和 finally 进⾏处理
后程序就结束了,当没有异常发⽣,在执⾏完 finally 中的代码后,后⾯代码会继续执⾏。
值得注意的是,当 try/catch 语句块中有 return 时,finally 语句块中的代码会在 return 之前执⾏。
如果 try/catch/finally 块中都有 return 语句,finally 块中的 return 语句会覆盖 try/catch 模块中的 return 语句。
22.final、finally 和 finalize 的区别是什么
- final ⽤于声明属性、⽅法和类,分别表示属性不可变、⽅法不可覆盖、类不可继承。
- finally 作为异常处理的⼀部分,只能在 try/catch 语句中使⽤,finally 附带⼀个语句块⽤来表示这个语句最终⼀定被执⾏,经常被⽤在需要释放资源的情况下。
- finalize 是 Object 类的⼀个⽅法,在垃圾收集器执⾏的时候会调⽤被回收对象的 finalize()⽅法。当垃圾回收器准备好释放对象占⽤空间时,⾸先会调⽤ finalize()⽅法,并在下⼀次垃圾回收动作发⽣时真正回收对象占⽤的内存。
23.简述泛型
泛型,即“参数化类型”,解决不确定对象具体类型的问题。在编译阶段有效。在泛型使⽤过程中,操作的数据类型被指定为⼀个参数,这种参数类型在类中称为泛型类、接⼝中称为泛型接⼝和⽅法中称为泛型⽅法。
24.简述泛型擦除
Java 编译器⽣成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程被称为泛型擦除。
25.序列化是什么
序列化是⼀种将对象转换成字节序列的过程,⽤于解决在对对象流进⾏读写操作时所引发的问题。序列化可以将对象的状态写在流⾥进⾏⽹络传输,或者保存到⽂件、数据库等系统⾥,并在需要的时候把该流读取出来重新构造成⼀个相同的对象。
26.简述 Java 序列化与反序列化的实现
- 序列化:将 java 对象转化为字节序列,由此可以通过⽹络对象进⾏传输。
- 反序列化:将字节序列转化为 java 对象。
- 具体实现:实现 Serializable 接⼝,或实现 Externalizable 接⼝中的 writeExternal()与 readExternal()⽅法。
27.面向对象和面向过程的区别
- 面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等⼀般采用面向过程开发。但是,面向过程没有面向对象易维护、易复⽤、易扩展。
- 面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低
- 注:Java 性能差的主要原因并不是因为它是面向对象语⾔,而是 Java 是半编译语⾔,最终的执行代码并不是可以直接被 CPU 执行的⼆进制机械码。而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它⼀些面向过程的脚本语⾔性能也并不⼀定比 Java 好。
28.Java 程序从源代码到运行
一般有两步:
.java文件(源代码) 通过JDK中的javac编译成 .class文件(JVM可理解的Java字节码)
.class文件 通过JVM转换成 机器可执行的二进制机器码
我们需要格外注意的是 .class 到 机器码 这⼀步。在这⼀步 JVM 类加载器⾸先加载字节码⽂件,然后
通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被
调⽤的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编
译器完成第⼀次编译后,其会将字节码对应的机器码保存下来,下次可以直接使⽤。而我们知道,机器
码的运⾏效率肯定是⾼于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语⾔。
总结:Java 虚拟机(JVM以上是关于Java八股系列——Java基础的主要内容,如果未能解决你的问题,请参考以下文章