如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔空间
1.Java 语言具有哪些特点?
2.简述 Java 基本数据类型
3.简述自动装箱拆箱
对于 Java 基本数据类型,均对应⼀个包装类。
装箱就是自动将基本数据类型转换为包装器类型,如 int->Integer
拆箱就是自动将包装器类型转换为基本数据类型,如 Integer->int
4.简述 Java 访问修饰符
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.接口和抽象类的相同点和不同点
相同点:
不同点:
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 种使用方式:
14.为什么要把 String 设计为不可变
15.简述 String/StringBuffer 与 StringBuilder
16.判等运算符 == 与 equals 的区别
== 比较的是引⽤,equals 比较的是内容。
如果变量是基础数据类型,== 用于比较其对应值是否相等。如果变量指向的是对象,== 用于比较两个对象
是否指向同⼀块存储空间。
equals 是 Object 类提供的⽅法之⼀,每个 Java 类都继承自 Object 类,所以每个对象都具有 equals 这个
方法。Object 类中定义的 equals 方法内部是直接调用 == 比较对象的。但通过覆盖的方法可以让它不是比
较引用而是比较数据内容。
17.简述 Object 类常用方法
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 的区别
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 的区别是什么
23.简述泛型
泛型,即“参数化类型”,解决不确定对象具体类型的问题。在编译阶段有效。在泛型使用过程中,操作的数据类型被指定为⼀个参数,这种参数类型在类中称为泛型类、接口中称为泛型接口和方法中称为泛型方法。
24.简述泛型擦除
Java 编译器生成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程被称为泛型擦除。
25.序列化是什么
序列化是⼀种将对象转换成字节序列的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要的时候把该流读取出来重新构造成⼀个相同的对象。
26.简述 Java 序列化与反序列化的实现
27.面向对象和面向过程的区别
28.Java 程序从源代码到运行
一般有两步:
.java文件(源代码) 通过JDK中的javac编译成 .class文件(JVM可理解的Java字节码)
.class文件 通过JVM转换成 机器可执行的二进制机器码
我们需要格外注意的是 .class 到 机器码 这⼀步。在这⼀步 JVM 类加载器⾸先加载字节码⽂件,然后
通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被
调⽤的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编
译器完成第⼀次编译后,其会将字节码对应的机器码保存下来,下次可以直接使⽤。而我们知道,机器
码的运⾏效率肯定是⾼于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语⾔。
总结:Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,
Linux,macOS),目的是使⽤相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实
现是 Java 语⾔“⼀次编译,随处可以运行”的关键所在。
29.在 Java 中定义⼀个不做事且没有参数的构造方法的作用
Java 程序在执⾏⼦类的构造⽅法之前,如果没有⽤ super() 来调⽤⽗类特定的构造⽅法,则会调⽤
⽗类中“没有参数的构造⽅法”。因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中
⼜没有⽤ super() 来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误,因为 Java 程序在⽗类中找
不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法。
30.简述线程、程序、进程的基本概念,以及他们之间关系
线程与进程相似,但线程是⼀个比进程更小的执行单位。⼀个进程在其执行的过程中可以产⽣多个线
程。与进程不同的是同类的多个线程共享同⼀块内存空间和⼀组系统资源,所以系统在产⽣⼀个线程,
或是在各个线程之间作切换⼯作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的⽂件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程是程序的⼀次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行⼀个程序即是
⼀个进程从创建,运行到消亡的过程。简单来说,⼀个进程就是⼀个执行中的程序,它在计算机中⼀个
指令接着⼀个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输
入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划
分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独⽴的,而各线程则不⼀定,因为
同⼀进程中的线程极有可能会相互影响。从另⼀角度来说,进程属于操作系统的范畴,主要是同⼀段时
间内,可以同时执行⼀个以上的程序,而线程则是在同⼀程序内几乎同时执行⼀个以上的程序段。