Java 基础面试题总结
面向对象和面向过程的区别?
面向过程:面向过程的性能要比面向对象高。因为类的调用需要实例化,开销比较大,比较消耗资源。所以当性能是最重要的考虑因素时,比如:单片机、嵌入式开发等一般会采用面向过程开发。
面向对象:面向对象易维护、易复用、易扩展。因为面向对象有封装、继承、多态等特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
总结: Java 性能差的原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机器码。而面向过程语言大多是直接编译成机器码在电脑上执行。
Java 语言的特点
- 面向对象语言(封装、继承、多态)
- 平台无关性(通过 JVM 实现了平台无关性)
- 安全可靠、支持多线程、网络编程等
Java 与 C++ 的区别
- 都是面向对象语言,都支持封装、继承、多态的特性
- Java 不提供通过指针来直接访问内存
- Java 类是单继承,C++ 支持多继承;虽然 Java 不支持多继承,但是接口支持多继承
- Java 有自动管理内存的机制,不需要程序员手动释放内存
字符型常量和字符串常量的区别
- 写法上:字符常量是单引号引起的,字符串常量是双引号引起的若干个字符;
- 含义上:字符型常量相当于一个整数型(ASCII值),可以参与表达式运算;字符串常量表示一个内存地址值;
- 占用内存大小:字符型常量占用2字节,字符串常量占用若干个字节;
基本类型 | 字节 | 位 | 最小值 | 最大值 | 包装类型 |
---|---|---|---|---|---|
boolean | - | - | - | - | Boolean |
byte | 1 | 8 | -128 | 127 | Byte |
char | 2 | 16 | Character | ||
short | 2 | 16 | − 2 1 5 -2^15 −215 | 2 1 5 − 1 2^15 - 1 215−1 | Short |
int | 4 | 32 | − 2 3 1 -2^31 −231 | 2 3 1 − 1 2^31 - 1 231−1 | Integer |
long | 8 | 64 | − 2 6 3 -2^63 −263 | 2 6 3 − 1 2^63 - 1 263−1 | Long |
float | 4 | 32 | Float | ||
double | 8 | 64 | Double |
构造器 Constructor 是否可以被 Override?
构造器不能被重写,但是可以重载。
重载与重写的区别
重载:发生在同一个类中,方法名必须相同,参数类型、个数、顺序不同,方法返回值和访问修饰符可以不同;
重写:子类对父类允许访问的方法进行重新实现,方法名、参数列表必须相同,返回值类型小于等于父类,抛出异常值的类型小于等于父类,访问修饰符大于等于父类。
Java 面向对象语言编程的三大特性:封装、继承、多态
封装
把一个对象私有化,同时提供一些可以被外界访问属性的方法。
继承
继承是在原有类的基础上建立新的类,新的类中可以增加新功能,也可以使用父类的功能,通过继承,我们可以非常方便的复用以前的代码。
多态
是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才能确定。
Java 中有两种方式可以实现多态:继承(多个子类对父类同一个方法进行重写)和接口(多个类实现了同一接口并实现了同一方法)
String、StringBuilder 和 StringBuffer 的区别?为什么 String 类是不可变的?
String 类使用了 final 关键字来修饰类和用来保存字符串的字符数组,所以 String 是不可变的。
StringBuilder
和 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
类中也是使用字符数组保存字符串的,但是没有使用 final 关键字,所以这两种对象都是可变的。
线程安全: String 对象是不可变的,所以是线程安全的。StringBuffer
对方法加了同步锁,所以是线程安全的。StringBuilder
并没有加同步锁,所以是非线程安全的。
性能: String 对象每次进行改变的时候,都会生成一个新对象,然后将指针指向新对象。StringBuffer
和StringBuilder
每次都是对自身进行操作,而不是产生新的对象,所以要比 String 效率要高。相同情况下,StringBuilder
要比StringBuffer
的性能要高,但是要冒多线程不安全的风险。
总结:
- 操作少量数据:String
- 单线程操作大数量数据:
StringBuilder
- 多线程操作大量数据:
StringBuffer
在 Java 中顶一个不做事且没有参数的构造方法的作用
类初始化时需要调用构造函数。
Java程序在执行子类的构造方法之前,如果没有使用super()
来调用父类特定的构造方法,则会调用父类中的无参构造方法。因此,如果父类只定义了有参的构造方法,而子类中又没有使用super()
来调用指定的构造器,则编译时会发生错误,这时就需要在父类中加上无参的构造方法。
接口和抽象类的区别
- 接口中的方法都是
public
,所有方法在接口中不能实现(Java 8 开始接口方法可有有默认实现),而抽象类的方法修饰符可以使任意的。 - 接口中除了
static
、final
变量,不能有其他变量,而抽象类中则不一定; - 一个类可以实现多个接口,但是只能继承一个抽象类;接口本身可以通过
extends
扩展多个接口 - 从设计层面上讲,抽象是对类的抽象,是一种模板设计;而接口是对行为的抽象,是一种行为的规范。
成员变量和局部变量的区别
- 从语法上看:成员变量属于类,而局部变量是方法内的定义的变量或是方法参数;成员变量可以被
private
、public
、static
等修饰符修饰,而局部变量被访问控制修饰符和static
修饰;但是,成员变量和局部变量否可以被final
所修饰。 - 从存储位置上来看:如果成员变量使用
static
修饰,那么成员变量是属于类的,如果没有被static
所修饰,则成员变量是属于对象实例。对象实例存储在堆内存中,而局部变量存储在虚拟机栈中。 - 从生命周期上看:成员变量属于对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而消失。
- 成员变量如果没有赋初始值,自会自动赋默认值;而局部变量不会赋值。
创建对象用什么运算符?对象实体和对象引用有何不同?
new 运算符。
对象引用指向的对象实例。一个对象引用可以指向0个或1个对象实例,一个对象有 n 个引用指向它。
静态方法和实例方法有何不同?
- 调用方式:在外部调用静态方法时,可以使用
类名.方法名
的方式,也可以使用对象名.方法名
的方式,而实例只能通过后面这种方式;也就是说,调用静态方法无需创建对象。 - 访问权限:静态方法在访问本类的成员时, 值允许访问静态成员,不允许访问实例成员和方法;而实例方法则没此限制。
== 与 equals
==:如果是基本数据类的比较=比较的是值是否相等;如果是引用数据类型,则比较的是内存地址是否相等;
equals:比较的是两个对象的内容是否相等,一半有两种使用情况:
- 如果没有重写equals方法。则通过equals比较等价于==
- 如果重写equals,我们一般会用来比较两个对象的内容是否相等
为什么重写 equals 时必须重写 hashcode?
hashcode()
的作用是获取哈希码,哈希码的作用是确定该对象在哈希表中索引的位置。
equals 与 hash code 的相关规定:
- 如果两个对象相等(即equals结果返回true),则他们的 hashcode 值一定也是相等的;
- 如果两个对象的 hashcode 相等,但是它们不一定是相等的;
- 因此,equals 被覆盖过,咋 hashcode 方法也得覆盖;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return age;
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
Student student = (Student) obj;
if (age != student.age) {
return false;
}
return name.equals(student.name);
}
public static void main(String[] args) {
Student s1 = new Student("小明", 18);
Student s2 = new Student("小明", 18);
System.out.println(s1.equals(s2));
}
}
如果上面代码只是重写 equals 方法,没有重写 hashcode 方法,s1 与 s2 的内存地址肯定不同,所以 s1 != s2;
s1.equals(s2) 结果是true,根据哈希规则,equals 相等则 hash 值一定相等,就产生了矛盾。
为什么 Java 只有值传递?
按值传递表示接受的是调用者提供的值,而按应用传递表示接受的是调用者传递的变量内存地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值所对应的变量值。
final 关键字的使用
final 关键字主要使用在类、方法、变量。
- final 修饰类时,表明这个类不能被继承。final 类中的成员方法都会被隐式的转化为 final 方法
- final 修饰变量,如果是基本类型,则初始化后不能被修改,如果是引用类型,初始化后不能指向其他对象的应用;
- final 修饰方法,防止子类修改
Java 异常体系
异常的处理方式
- 通过try捕获异常并进行处理
- 不处理,抛给调用者(throw、throws)
throw 和 throws 的区别
- 位置不同:throws 位于函数上,后面跟异常类,可以跟多个;而 throw 用在函数内,后面跟异常对象;
- 功能不同:throws 用来申明异常,让调用者知道该功能可能抛出异常,可以给出预先处理的方法;throw 是抛出具体的问题,代码执行到 throw,功能也就结束了。