Java基础(中)

本文主要参考:Javaguide ,在其基础上做了部分扩展

面向对象

面向对象 vs 面向过程

  • 面向过程(POP):将解决问题的过程拆解为若干方法,通过方法的组合执行解决问题
  • 面向对象(OOP):先抽象出一个对象,然后对象调用方法解决问题

对于简单的程序逻辑,优先使用POP;对于复杂的系统优先使用OOP,原因如下:

  1. OOP的封装性更好,安全性更高
  2. OOP的可维护性更好,各个模块之间解耦,系统迭代只需要修改小部分代码
  3. OOP的扩展性更高,通过继承和多态,提升代码的复用度

POP一定比OOP性能高吗?

参考:https://github.com/Snailclimb/JavaGuide/issues/431

OOP需要实例化对象,开销比较大,消耗资源。但是,设计范式与系统性能并无强制依赖,POP也需要分配内存,有这种观念是因为大多数的OOP属于解释型语言,所以直观上造成了OOP的性能比较差,经典的反例就是C和C++,两者都属于编译型语言,前者是POP,后者OOP,两者的性能差异并不是很明显

构造方法

在创建实例时,本质上就是调用类的构造方法,构造方法不能被继承,无法重写,但是可以重载;

  • final不能修饰构造方法,因为构造方法本身不允许继承,无法重写
  • synchronized也不能修饰构造方法,synchronized基于对象监视器实现,需要锁住对象实例本身,在构造函数执行期间,对象实例还未创建完成,相当于一个先有鸡还是先有蛋的问题

面向对象三大特征

封装

封装是指把一个对象的属性或状态信息保存在对象内部,外部只能通过对象提供的接口进行访问,提高系统的安全性

private修饰的字段正常来说外部不能直接访问,但是一种情况例外:反射

继承

继承是指子类可以复用父类的代码,提升代码的复用性,子类只需要关注独立于父类之外的特定功能

Java只支持单继承,避免菱形继承问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 接口变量冲突
public interface T1 {
public static final int x = 5;
}

public interface T2 {
public static final int x = 1;
}

public class T3 implements T1, T2{
public static void main(String[] args) {
System.out.println(x); //编译报错
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 接口与类变量冲突
public class T1 {
public int x = 5;
}

public interface T2 {
public static final int x = 1;
}

public class T3 extends T1 implements T2{
public static void main(String[] args) {
System.out.println(x); //编译报错
}
}

子类可以继承静态方法,但无法重写,只是覆盖,但不属于重写,可以通过以下代码简单证明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class T1 {
public static void f() {
System.out.println("T1");
}
}

public class T2 extends T1{
public static void f() {
System.out.println("T2");
}

public static void main(String[] args) {
T1 t = new T2();
t.f(); // 输出T1,并未被重写,子类只是覆盖
}
}

子类无法访问父类的私有属性和私有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class T1 {
private void f() {
System.out.println("T1");
}
}

public class T2 extends T1{
public void f() {
super.f();
}

public static void main(String[] args) {
T2 t = new T2();
t.f(); // 编译失败
}
}

多态

多态分为编译时多态与运行时多态

  • 编译时多态发生在编译期,具体表现为方法重载,发生在同一个类的内部,重载方法的参数顺序、数量以及类型不同
  • 运行时多态发生在运行期,具体表现重写父类方法,父类指针指向子类实例,发生在具有继承关系的类中,方法的参数必须相同
区别点 重载 (Overloading) 重写 (Overriding)
发生范围 同一个类中 父类与子类之间(存在继承关系)
方法签名 方法名必须相同,但参数列表必须不同(参数的类型、个数或顺序至少有一项不同) 方法名、参数列表必须完全相同
返回类型 与返回值类型无关,可以任意修改 子类方法的返回类型必须与父类方法的返回类型相同,或者是其子类(协变返回类型)
访问修饰符 与访问修饰符无关,可以任意修改 子类方法的访问权限不能低于父类方法的访问权限(public > protected > default > private)
异常声明 可以抛出任意异常 子类方法抛出的异常不能比父类方法抛出的异常更宽泛
绑定时期 编译时绑定(静态绑定) 运行时绑定(动态绑定)

方法签名只和方法名和参数有关,与返回值和权限修饰符等无关,所以重载不要求返回值相同

一种情况是,方法支持变长参数且发生重载时,优先匹配非变长参数重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class T7 {
public void f() {
System.out.println("0");
}

public void f(String... args) {
System.out.println("1");
}

public static void main(String[] args) {
T7 t = new T7();
t.f(); //输出0
}
}

接口 & 抽象类

接口和抽象类都属于被复用的对象,两者均不能被实例化,但两者又有不同之处

  1. 抽象类可以有普通方法,但至少有一个抽象方法;接口只支持抽象方法(注:JDK9引入了私有、静态和默认方法)
  2. 接口的变量全部为静态常量,抽象类支持普通变量
  3. 抽象类目的是实现代码复用;而接口目的更偏向于规范类的行为,比如Serializable接口,内部无任何代码,它的作用仅仅是标记某个类可以被序列化

深拷贝 & 浅拷贝

  • 浅拷贝:外部对象不同,但内部引用指向相同的对象
  • 深拷贝:内部外部对象引用均不相同

Object

Object对象是Java体系中,所有类的父类,定义了许多通用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final native Class<?> getClass() //获取当前对象的class对象

public native int hashCode() // 获取对象哈希码,需要被重写

public boolean equals(Object obj) // 比较两个对象是否相等,需要被重写

protected native Object clone() throws CloneNotSupportedException // 返回当前对象拷贝

public String toString() // 默认返回哈希码

public final native void notify() // 唤醒一个线程

public final native void notifyAll() // 唤醒所有等待线程

public final native void wait(long timeout) throws InterruptedException //超时等待

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException // 阻塞等待

protected void finalize() throws Throwable { } // 类似C++中的析构函数,被回收的时候调用

hashCode()和equals()需要同时重写,假设只重写其中一个,在一些集合,比如HashMap中,就会出现重复元素

String

Java中有三大类字符串类:String、StringBuilder和StringBuffer

区别 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全性 安全 不安全 安全
性能 相对较差,每次都会生成新的对象 较好 较好
使用场景 任意场景操作少量数据 单线程操作大量数据 多线程操作大量数据

String不可变的原因

  1. String类被final修饰,不可继承
  2. 底层的char数组被private final修饰,并且没有对外提供修改的接口
  3. 双亲加载机制,即使自定义String类,加载的依然是JDK的String
1
2
3
4
5
6
7
8
9
public final class String implements java.io.Serializable,Comparable<String>, CharSequence {
@Stable
private final byte[] value;
}

abstract class AbstractStringBuilder implements Appendable, CharSequence {
byte[] value;

}

字符串拼接避免使用String+

直接使用String+会创建大量的临时String对象,占用大量内存空间,通过StringBuilder.append拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class T1 {
public static void main(String[] args) {
String s= "";
long startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
s += "1";
}
long endTime = System.currentTimeMillis();
System.out.println((endTime - startTime) / 1000.0); // 309秒

StringBuilder sb = new StringBuilder("");
startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
sb.append("1");
}
endTime = System.currentTimeMillis();
System.out.println((endTime - startTime) / 1000.0); // 0.011秒
}
}

字符串常量池

池化技术的一种运用,是JVM为了避免字符串的频繁创建而单独在堆中开辟的缓存,主要是为了避免相同字符串的重复创建

1
2
3
String aa = "ab"; // 常量池中不存在,创建
String bb = "ab"; // 常量池中存在相同字符串,直接返回其引用
System.out.println(aa==bb); // true

虽然所有引用类型都有toString方法,但是String与其他类之间并不能强制类型转换

1
2
3
4
5
6
7
8
public class T1 {
public static void main(String[] args) {
Integer x = 10;
String s1 = x.toString(); // 正常
String s2 = (String) x; // 编译错误
}
}


Java基础(中)
http://xuxiusheng.github.io/2025/12/29/Java基础(中)/
作者
Xuxiusheng
发布于
2025年12月29日
许可协议