Java基础常见题
如何理解Java的面向对象?
Java的面向对象就是把现实世界的东西抽象成代码里的'对象',通过对象的交互来解决问题。
Java是一种纯粹的面向对象语言,它对类、对象、继承、封装、多态等概念有很好的支持。与面向过程编程(POP)相比,OOP更适用于复杂系统的设计与实现:
- 面向过程:以函数为中心,将问题分解为一系列步骤,数据与操作分离,适合简单、线性的任务
- 面向对象:以对象为中心,通过对象间的交互解决问题,数据与操作封装在对象中,适合复杂系统
面向对象的四大特性:
- 封装:封装是将对象的属性和方法结合成一个独立的单位,并尽可能隐藏对象的内部细节,只对外暴露有限的信息。在Java中,我们使用访问修饰符(private、protected、public)来控制访问权限,通过getter/setter方法提供安全的数据访问。
- 继承:继承允许一个类(子类)继承另一个类(父类)的属性和方法,实现代码复用和层次关系。Java使用extends关键字实现单继承(一个子类只能有一个直接父类),但可以通过实现多个接口来达到类似多重继承的效果。
- 多态:多态是指允许不同类的对象对同一消息作出响应。在Java中,多态主要通过方法重写(Override)和接口实现来实现。多态分为:
- 编译时多态:方法重载(Overload)
- 运行时多态:方法重写(Override)
- 抽象:抽象是将复杂现实世界的简单化过程,通过抽象可以忽略一个对象或系统的某些具体细节,而只关注其关键特性。在Java中,抽象可以通过抽象类和接口来实现。
除了四大特性,还可以提到 SOLID 设计原则,这是构建健壮、可维护系统的关键:
- 单一职责原则(SRP):一个类应该只有一个引起它变化的原因
- 开闭原则(OCP):软件实体应对扩展开放,对修改关闭
- 里氏替换原则(LSP):子类必须能够替换其父类
- 接口隔离原则(ISP):客户端不应该依赖它不需要的接口
- 赖倒置原则(DIP):要依赖于抽象,不要依赖于具体
Java中"==" 和 equals 有什么
"==" 是 Java 中一种操作符,它有两种比较方式
- 对于基本数据类型来说," ==" 判断的是两边的值是否相等
- 对于引用类型来说, "=="判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域。
而equals方法 是 Object 类定义的一个方法。源码如下:
java
public boolean equals(Object obj) {
return (this == obj);
}
可以看到,在Object中其实equals方法与使用"=="是等价的。但是Java的开发人员赋予equals方法的意义是比较两个对象的值是否相等。因此就需要我们在有需要的时候去根据自身需要来重写equals方法。重写equals方法的同时,需要一并重写hashCode方法。
为什么重写 equals 方法必须重写 hashcode 方法
1.保持一致性 背景:equals 方法用于比较两个对象是否相等,而 hashCode 方法用于计算对象的哈希码。在集合框架中,如 HashMap 和 HashSet,这两个方法的正确实现至关重要。 问题:如果两个对象通过 equals 方法被认为是相等的,但它们的 hashCode 方法返回不同的值,那么在使用哈希表时就会出现问题。例如,在 HashMap 中,键的哈希码用于确定键值对存储的位置。如果两个相等的键有不同的哈希码,它们可能会被存储在不同的位置,导致无法正确地找到对应的值。 解决方案:为了保持一致性,如果两个对象通过 equals 方法被认为是相等的,那么它们的 hashCode 方法必须返回相同的值。这样可以确保在哈希表中,相等的键能够被正确地映射到相同的位置。
2.保证集合的正确性 背景:在集合框架中,HashSet 和 HashMap 等集合类依赖于 hashCode 方法来高效地存储和检索元素。 问题:如果 equals 方法被重写,但 hashCode 方法没有被重写,那么在 HashSet 中,两个相等的对象可能会被视为不同的元素,导致集合中出现重复的元素。在 HashMap 中,两个相等的键可能会被视为不同的键,导致无法正确地存储和检索值。 解决方案:重写 hashCode 方法可以确保在集合中,相等的对象能够被正确地识别和处理。这样可以保证集合的正确性和一致性。
3.避免性能问题 背景:在哈希表中,哈希码的计算用于快速定位元素。如果 hashCode 方法没有被正确重写,可能会导致哈希表的性能下降。 问题:如果 hashCode 方法没有被重写,那么默认的 hashCode 方法会返回对象的内存地址。这可能导致哈希表中的元素分布不均匀,从而导致性能问题。 解决方案:重写 hashCode 方法可以确保哈希表中的元素分布均匀,从而提高哈希表的性能。
4.符合 Java 规范 背景:Java 规范要求,如果两个对象通过 equals 方法被认为是相等的,那么它们的 hashCode 方法必须返回相同的值。 问题:如果不遵守这一规范,可能会导致不可预测的行为和错误。 解决方案:为了符合 Java 规范,确保程序的正确性和可维护性,必须在重写 equals 方法的同时重写 hashCode 方法。 示例 假设有一个 Person 类,重写了 equals 方法,但没有重写 hashCode 方法:
java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
// 没有重写 hashCode 方法
}
在 HashSet 中使用 Person 对象时,可能会出现重复元素:
java
HashSet<Person> set = new HashSet<>();
set.add(new Person("Alice", 30));
set.add(new Person("Alice", 30));
预期结果:set 中只有一个元素
实际结果:set 中可能有两个元素,因为 hashCode 方法没有被重写 如果重写了 hashCode 方法:
java
@Override
public int hashCode() {
return Objects.hash(name, age);
}
那么在 HashSet 中,两个相等的 Person 对象会被正确地识别为同一个元素,从而避免重复。
内部类可以引用外部类的成员吗?有没有什么限制?
内部类如果不是static修饰的,那么它可以通过"this."访问创建它的外部类对象的所有属性。这是因为非静态内部类默认持有外部类的引用。怎么理解呢?其实是因为编译器在编译非静态内部类的时候给非静态内部类的构造方法加了一个外部类的参数,所以非静态内部类可以通过这个参数访问到外部类程成员和方法。可以通过javap -p来反汇编字节或者通过反射调用非静态内部类的构造方法来验证。 另外,非静态内部类无法直接实例化,而是需要先实例化外部类后再实例化非静态内部类,如果是通过反射调用非静态内部类的构造方法,则必须传入外部类的对象才可成功实例化非静态内部类。
内部类如果是sattic修饰的,则这个内部类和普通的类其实并没有差别,它只可以访问外部类对象的所有static属性。 一般普通类只有public或package的访问修饰,而内部类可以实现static,protected,private等访问修饰。 当从外部类继承的时候,内部类是不会被覆盖的,它们是完全独立的实体,每个都在自己的命名空间内,如果从内部类中明确地继承,就可以覆盖原来内部类的方法。
finally中的代码一定会执行吗?try里有return,finally还执行么
在Java官方文档中对finally的描述如下:
The finally block always executes when the try block exits.
If the try clause executes a return, the compiled code does the following:
Saves the return value (if any) in a local variable.
Executes a jsr to the code for the finally clause.
Upon return from the finally clause, returns the value saved in the local variable.
大致意思是:finally代码块中的内容一定会得到执行。
try中存在return的情况下,会把try中return的值存到栈帧的局部变量表中,然后去执行finally语句块,最后再从局部变量表中取回return的值返回。另外,当try和finally里都有return时,会忽略try的return,而使用finally的return。
特殊情况 在正常情况下,finally中的代码一定会得到执行,但是如果我们将执行try-catch-finally 代码块的线程设置为守护线程,或者在fianlly之前调用System.exit结束当前虚拟机,那么finally则不会得到执行:
java
try {
System.exit(0);
} catch (Exception e) {
} finally {
}
Thread t1 = new Thread() {
@Override
public void run() {
//try-catch-finally
}
};
t1.setDaemon(true);//设置为守护进程
t1.start();
int和Integer有什么区别?
Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。 Java 为每个原始类型提供了包装类型:
- 原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
遇到一个面试题,也是和自动装箱和拆箱有点关系的,代码如下所示:
java
public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,如果看看valueOf的源代码就知道发生了什么。
java
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是Integer的内部类,其代码如下所示:
java
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1= =f2的结果是true,而f3==f4的结果是false。