本文主要参考:Javaguide ,在其基础上做了部分扩展
面向对象
面向对象 vs 面向过程
- 面向过程(POP):将解决问题的过程拆解为若干方法,通过方法的组合执行解决问题
- 面向对象(OOP):先抽象出一个对象,然后对象调用方法解决问题
对于简单的程序逻辑,优先使用POP;对于复杂的系统优先使用OOP,原因如下:
- OOP的封装性更好,安全性更高
- OOP的可维护性更好,各个模块之间解耦,系统迭代只需要修改小部分代码
- 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(); } }
|
子类无法访问父类的私有属性和私有方法
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(); } }
|
接口 & 抽象类
接口和抽象类都属于被复用的对象,两者均不能被实例化,但两者又有不同之处
- 抽象类可以有普通方法,但至少有一个抽象方法;接口只支持抽象方法(注:JDK9引入了私有、静态和默认方法)
- 接口的变量全部为静态常量,抽象类支持普通变量
- 抽象类目的是实现代码复用;而接口目的更偏向于规范类的行为,比如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()
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 { }
|
hashCode()和equals()需要同时重写,假设只重写其中一个,在一些集合,比如HashMap中,就会出现重复元素
String
Java中有三大类字符串类:String、StringBuilder和StringBuffer
| 区别 |
String |
StringBuilder |
StringBuffer |
| 可变性 |
不可变 |
可变 |
可变 |
| 线程安全性 |
安全 |
不安全 |
安全 |
| 性能 |
相对较差,每次都会生成新的对象 |
较好 |
较好 |
| 使用场景 |
任意场景操作少量数据 |
单线程操作大量数据 |
多线程操作大量数据 |
String不可变的原因
- String类被final修饰,不可继承
- 底层的char数组被private final修饰,并且没有对外提供修改的接口
- 双亲加载机制,即使自定义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);
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); } }
|
字符串常量池
池化技术的一种运用,是JVM为了避免字符串的频繁创建而单独在堆中开辟的缓存,主要是为了避免相同字符串的重复创建
1 2 3
| String aa = "ab"; String bb = "ab"; System.out.println(aa==bb);
|
虽然所有引用类型都有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; } }
|