面向对象
每当提起 Java ,面向对象都是我们第一时间能想到与其关联的内容,我们在这里梳理一下面向对象的内容。
三大特性
面向对象的三大特性是老生常谈的话题,本节简单总结一下三大特性。
封装
概念:使对象尽可能隐藏内部细节,只保留一些对外的方法使之与外部发生联系。用户无需知道对象内部细节,但可以通过对象对外提供的方法来访问对象。
优点:
减少耦合:可以独立地进行开发、修改、测试。
便于开发人员理解这个类的功能。
提高系统的可复用性。
继承
概念:一个类可以通过关键字 extend 以继承父类的非 private 成员。解决共性抽取的问题。
特点:
一个类只能有一个直接父类(单继承),但是接口可以有多个父类(多继承)。
一个子类只有一个直接父类,但是一个父类可以拥有多个子类。
多态
多态具体分为编译时多态和运行时多态:
编译时多态主要是体现为方法的重载。Java虚拟机在编译时根据参数类型、个数、顺序能确定执行重载方法中的某一个。
运行时多态主要是体现为父类的引用指向子类的对象。需要在运行时,Java虚拟机才能确定引用指向的具体类型,执行具体类型中的方法。
运行时多态
运行时多态是面向抽象编程的基础,使用这个特性可以很方便地进行面向抽象类/接口开发,无需为具体的子类/实现类一一编写逻辑,因为子类重写了父类方法,运行时根据具体的类型执行父类中定义的方法。
运行时多态的条件:
继承,没有继承,就没有子类
重写,没有重写,多态就没有意义
向上转型
这里重点说说向上转型
向上转型的格式就是运行时多态的写法
父类 对象名 = new 子类();,意为创建一个子类对象,但把他看成父类来进行使用。这么做的好处是可以灵活地更换不同的子类;弊端是由于「编译看左,运行看右」,这个对象无法调用子类中特有的内容。
既然有向上转型,也就有向下转型。向下转型具体写法是
子类 对象名 = (子类) 原父类对象;,其实就是将父类对象「还原」成本来的子类对象。这么做的好处是可以使用子类的内容;弊端是必须要先确定子类类型,才能进行强转,否则会有 ClassCastException (可以借助
instanceof关键字进行判断)。
下面用一个例子来体现运行时多态的特点
public interface Animal {
void say();
}
public class Cat extends Animal {
@Override
public void say() {
System.out.println("miao miao miao~");
}
}
public class Dog extends Animal {
@Override
public void say() {
System.out.println("wang wang wang~");
}
}
public class Test() {
public static void main(String[] args) {
List<Animal> animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
for(Animal animal : animals) {
animal.say();
}
}
}
// ******输出******
miao miao miao~
wang wang wang~可以看到,即使开发时使用着接口的对象,但在运行时会根据具体的实现类型分别调用其重写的方法。这么做可以通用这一段遍历调用的逻辑,无需根据具体类型再开发相似的逻辑,非常的方便。
重写与重载
上文「多态」这一小节提到方法的重写、重载,本节进行两个概念的解释与对比。
相同点:
作用:都是体现了多态这一特性。
形式:重写方法与重载方法都与原方法的名称一样。
不同点:
存在:重写方法存在于继承体系中;重载方法存在于同一个类中。
形式上:重写方法与原方法的参数列表一模一样;重载方法与原方法的参数列表有区别(可能是参数类型、顺序、数量)。
另外方法重写需要遵循以下规则:
子类方法的访问权限必须大于等于父类方法访问权限
public > protected > 什么都不写 > private
子类方法的返回值必须小于等于父类方法的返回值范围(换言之就是返回值类型要相同或者是其子类)
子类方法只能声明小于等于父类方法声明的异常范围(可以抛出更少、更具体设置说不抛出)
前两点是里氏替换原则的内容,重写可以使用 @Override 注解,让编译器帮忙检查是否满足重写的规则
需要注意的是:两个方法名称一样,不管参数列表是否有区别,返回值都不允许相同。
抽象类与接口
抽象类特点
结构:抽象类允许定义抽象方法,但是也可以不定义。
实例化:抽象类不可以实例化,必须有一个子类继承它。
抽象方法:
被
abstract修饰没有方法体
必须定义在抽象类中
接口特点
接口是一种特殊的抽象类,一般在开发中更偏向使用接口,因为组合关系比继承关系耦合度更低。
Jdk8 中接口可以拥有的内容:
常量
抽象方法
默认方法,可以用于接口升级,避免增加接口导致大量实现类需要改动
静态方法
关键字
本小节总结一下面向对象中的一些关键字
super与this
分别用于在成员中调用父类的成员和本类中的其他成员
重点介绍一下用在构造方法中的情况
super(param) 调用父类构造方法
this(param) 调用本类其他构造方法
但是这两句必须是构造方法中第一句并且也是唯一一句,所以两者不能同时使用。
static
static 关键字有三个使用途径,当被 static 关键字修饰后,所有类对象共享这一份数据。
静态成员变量
静态成员方法
静态代码块,当第一次使用到本类时,静态代码块执行唯一的一次。一般用于一次性地为静态成员变量赋值。
final
用途
final 关键字可以用在四个地方
修饰类,这个类不能有任何的子类。
修饰方法,这个方法不允许在子类中被重写。
修饰局部变量,如果是基本数据类型,是指栈中的值不允许被改变;如果是引用类型,引用的地址指不允许被改变。
修饰成员变量,比修饰局部变量更加严格。
这个成员变量不允许有 setter 方法
被修饰后必须手动赋值或通过构造方法赋值,不会给默认值
值得一提的是,如果通过构造方法赋值的话,那么必须保证每一个构造方法中都能对这个成员变量赋值
关于提升性能
关于 final 关键字提升性能这个 topic ,众说纷纭,这里不做深究。本人认为提升性能应该从大方面出发,以大带小,比如合理设计表结构、合理设计连接池等,初学者暂时不合适在小地方深挖~
这里先说结论:使用 final 关键字更多地是一个编译时的提升,暂时没有发现对运行时有大幅度的提升,所以使用 final 关键字应该要出于清晰的设计和可读性的目的,而不是出于性能原因。
下面简单说说几个值得注意的地方
JVM 会缓存 final 变量,优化字符串的拼接,当字符串变量被 final 修饰时,在拼接的时候会认为这是一个常量,编译期就可以完成拼接。
final 的变量避免线程安全问题,多个线程可以共享,不需要额外的同步开销。