Java面向对象
大约 5 分钟
1. 三大特征
Java是面向对象语言(OOP),具有三大特征:封装、继承、多态
- 封装:利用抽象数据类型将数据和基于数据的操作封装在一起,可以向用户屏蔽内部实现细节。
- 继承:继承实现了IS-A关系,应遵循里氏原则(任何父类使用的地方都可以用子类替换)
- 多态:
- 编译时多态:方法重载
- 运行时多态:对象引用指向的类型在运行时才能确定,三个条件:继承、覆盖(重写)、向上转型
2. 继承
2.1 访问修饰符
Java中有三个访问修饰符private、protected和public,默认对包级可见
public | protected | default | private | |
---|---|---|---|---|
同类 | 可见 | 可见 | 可见 | 可见 |
同包 | 可见 | 可见 | 可见 | 不可见 |
父子类 | 可见 | 可见 | 不可见 | 不可见 |
不同包 | 可见 | 不可见 | 不可见 | 不可见 |
2.2 关键字
1. final
- 数据:声明为常量,确保第一次初始化后不能被改变
- 对于基本类型,final 使数值不变
- 对于引用类型,final 使引用不变(但自身的值可以修改)
- 方法:方法不能被子类重写(private隐式的被指定为final)
- 类:类不能被继承
2. static
- 静态变量:又称类变量,在内存中只存在一份,通过类名来访问
- 静态方法:在类加载的时候就存在,只能访问静态字段/方法,通过类名来访问
- 静态语句块:只在类初始化的时候运行一次
static{}
- 静态内部类:只能访问静态的字段/方法
- 静态导入:`import static com.xxx.xxx',使用静态变量时不用再指明ClassName
2.2 抽象和接口
1. 抽象
抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
2. 接口
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现,Java 8 之后添加了Default的方法。接口的成员(字段 + 方法)默认都是 public ,字段默认都是 static 和 final。
3. 区别
- 设计层面:抽象是IS-A关系,必须满足里氏替换原则;而接口是LIKE-A关系,并没有抽象要求的那么严格
- 使用上:一个类只能继承一个抽象类,但是可以实现多个接口
- 访问权限:接口的成员只能是public,而抽象类则多种
4. 如何使用
- 接口
- 不相关的类都实现一系列方法,比如Mybatis的Bean实现Serializable进行缓存优化
- 多重继承
- 抽象类
- 需要在几个类中共享代码
- 需要更丰富的访问权限控制
- 需要非static、final的字段
2.3 重写和重载
1. 重写Override
- 子类实现了一个与父类在方法声明上完全相同的一个方法
- 子类方法的访问权限必须大于等于父类方法
- 子类方法的返回类型必须是父类方法返回类型或为其子类型
使用 @Override 注解,可以让编译器帮忙检查重写
2. 重载Overload
- 方法与已存在的方法名称相同,但参数类型、个数、顺序至少有一个不同(返回值不同不算)
3. Object
Object是Java中所有类的父类,任何没有显式继承的类都会继承自Object,其内部包含以下通用方法
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 {}
1. equals()和==
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法
- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y); // false
equals的实现如下:
- 首先判断是否是同一对象
- 其次判断是否是同一类
- 最后将对象转换后,对比内部的一些细节
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqualExample that = (EqualExample) o;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
2. hashCode() 在重写了equals后,也需要对hashCode进行重写,确保内部的对比和哈希计算是相同的,比如上面内部对比了x和y,在hash中也要计算x和y
@Override
public int hashCode() {
return hash(x) + hash(y);
}
3. clone()
clone() 是 Object 的 protected 方法,而且不可能与我们自己的程序同包命,所以一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法
另外,这里也分为浅拷贝和深拷贝:
- 拷贝对象和原始对象的引用类型引用同一个对象
- 拷贝对象和原始对象的引用类型引用不同对象
默认支持的是浅拷贝,如果需要深拷贝的话,需要重写clone,并重新new一个对象赋值返回