b1cat`s

Java面向对象

Word count: 2.4kReading time: 9 min
2020/02/13 1

JAVA面向对象

  • 在OOP中,class和instance是“模版”和“实例”的关系;
  • 定义class就是定义了一种数据类型,对应的instance是这种数据类型的实例;
  • class定义的field,在每个instance都会拥有各自的field,且互不干扰;
  • 通过new操作符创建新的instance,然后用变量指向它,即可通过变量来引用这个instance;
  • 访问实例字段的方法是变量名.字段名;
  • 指向instance的变量都是引用变量。

基础

01 方法、构造方法、方法重载

方法

为了更好的封装类,拒绝外部随意调用类内部字段导致混乱,可以用类内方法对字段进行操作修改。

  • 定义方法(修饰符有public方法,自然就有private方法。和private字段一样,private方法不允许外部调用)
1
2
3
4
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
  • this变量(它始终指向当前实例,优先级低于局部变量)
  • 可变参数(相当于数组类型, 类型...,String...name

构造方法

  • 默认构造方法
1
2
3
4
class Person {
public Person() { //new时 默认构造方法
}
}
  • 多构造方法(编译器通过构造方法的参数数量、位置和类型自动区分)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public Person(String name) {
this.name = name;
this.age = 12;
}

public Person() {
}
}

方法重载

这种方法名相同,但各自的参数不同,称为方法重载(Overload),方法重载的返回值类型通常都是相同的。

方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。

02 继承

  • 继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让Student从Person继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。
  • 在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),把Student称为子类(subclass),扩展类(extended class)。
  • 定义一个类时,若没有写extends,则默认继承自Java Object类
  • 继承有个特点,就是子类无法访问父类的private字段或者private方法,使用protected修饰符可以确保被子类访问
  • super(super关键字表示父类(超类))
    • 任何class的构造方法,第一行语句必须是调用父类的构造方法,如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
1
2
3
4
5
6
7
8
class Student extends Person {
protected int score;

public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
  • 向上转型
1
Person p1 = new Student();
  • 向下转型
    • 可以强制向下转型,最好借助instanceof判断;
1
2
3
4
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
  • 组合
    • 具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例
1
2
3
4
class Student extends Person {
protected Book book;
protected int score;
}

03 多态

  • 在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
    • Override和Overload不同的是,如果方法签名如果不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。
1
2
3
4
5
6
7
8
9
10
11
12
class Person {
public void run() {
System.out.println("Person.run");
}
}

class Student extends Person {
@Override //覆写run方法
public void run() {
System.out.println("Student.run");
}
}
  • 多态(真正执行的方法取决于运行时的实际类型的方法)
    image-20200213144905130
  • 之前一直没明白向上转型的应用场景在哪,在学习多态后才明白,是要先调用抽象的方法,再根据加载的不同子类执行不同的方法。
    • 实例:现在需要开发一个报税软件,满足不同人报税。可以先抽象出一个报税人,报税方法有着正常的报税比例,这个方法只传递报税人就行。报税人白领,正常工资报税;报税人科研人,享受国家津贴,报税较少。报税人作家,稿费报税。只需要覆写甲乙丙报税方法中的报税比例就可以了。流程中传递的报税人就是子类白领,科研人,作家向上转型的父类。报税执行的方法就是多态。
  • final 父类不允许子类对某个方法覆写,或不允许继承它这个类
    • 在大的项目中控制类的继承层次. 使子类数量不至于爆炸.在使用了多继承的类层次中这也是防止出现菱形继承层次结构的一个好办法. 要实现一个不能被继承的类有很多方法.

04 抽象类、接口

抽象类

因为多态特性里父类的方法只是为了子类对其进行覆写,同时方法内的执行无意义,可以将其直接声明一个抽象的方法,实现仅定义方法签名。

1
2
3
abstract class Person{            //含有抽象的方法必须声明为抽象类
public abstract void run();
}
  • 无法实例化一个抽象类。
  • 抽象类本身被设计只能用于被继承,抽象类可以强迫子类实现其定义的抽象方法,抽象方法相当于定义了“规范”。
  • 面向抽象编程
    • 尽量引用高层类型,无需关注子类的覆写方法如何实现的方式
1
2
Person s = new Student();
Person t = new Teacher();

接口

如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把抽象类改写成接口。

1
2
3
4
interface Person {
void run();
String getName();
}
  • interface是比抽象类更抽象的纯抽象接口,因为不能有字段。
    • 接口定义的所有方法默认都是public abstract
    • 当一个class需要实现接口时,需要使用implements关键字。
    • 可以实现多个接口
1
class Student implements Person, Hello{}
  • 接口继承(相当于拓展了接口的方法,使用extends)
    • 实现接口需要覆写所有接口的抽象方法
1
interface Person extends Hello{}
  • 继承关系
    • 合理设计interface和abstract class的继承关系,可以充分复用代码。一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。
  • default方法
    • 如何接口需要新增一个方法,那么所有实现该接口的子类均需要更改,使用default关键字修饰新增的方法无需更改子类
1
2
3
4
interface Person{
void run();
default String test();
}

05 静态字段、静态方法

静态字段

在class定义的字段,我们称为实例字段,实例字段的特点是,由于每个实例都有自己的字段,各个实例修改同名字段互不影响。

  • 还有一种字段,使用static关键字修饰的,称为静态字段。
    • 静态字段只有一个共享空间。
    • 无论哪个实例修改静态字段,所有市里的静态字段均被改了。
    • 推荐使用类.静态字段访问
1
2
Person.number = 99;
System.out.println(Person.number);

静态方法

  • 被static修饰的方式就是静态方法
    • 通过class.staticMethod()调用
    • 静态方法不属于实例,内部无法使用this变量,也无法访问实例字段
    • 接口默认为public static final类型

06 包

为了区分调用不同实现的相同类名,引入包的概念

  • 在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。
  • 包作用域(相同包下的类可以相互调用)
  • 使用import引入类 import src.b1cat.testimport *引入包内的所有类,不包括子包的类
  • 使用import static xxx可以导入一个类的静态字段和静态方法
  • 寻找类顺序:package->import->java.lang
  • 编译器默认自动import当前package的其他classimport java.lang.*

07 作用域

  • public、protected、private
  • 局部变量:在方法内部定义的变量

08 classpath、jar

classpath用于寻找加载class的路径,默认当前路径最好

  • java -classpath .:/usr/work/;/usr/share/class abc.xyz.Hello
    • 在当前路径/abc/xyz、/usr/work/abc/xyz、/usr/share/class/abc/xyz路径查找Hello类

jar可以将很多.class文件根据package层级关系打包成zip格式的压缩文件,方便管理

  • java -cp ./Hello.jar abc.xyz.Hello
  • 如果存在META-INF/MANIFEST.MF文件指定Main-Class,JVM直接读取MANIFEST.MF文件。直接java -jar hello.jar 运行
  • maven方便管理jar包

09 模块

为解决打包成jar包过程中依赖遗漏的问题,Java9以后引入了模块。同时为了模块化从Java9开始原有标准库rt.jar拆分成了好几十个模块,这些模块以.jmod拓展名标识,可以在$JAVA_HOME/jmods目录下找到
image-20200213144928649

编写模块

image-20200213144936399

  • bin目录存放编译后的class文件,src目录存放源码
  • 在src目录下新建一个module-info.java文件,用来描述模块依赖关系
1
2
3
4
module hello.world {
requires java.base; // 可不写,任何模块都会自动引入java.base
requires java.xml;
}
  • ①编译src目录下所有java文件存放至bin目录
    javac -d bin src/module-info.java src/b1cat/*.java
  • ②打包bin目录下所有的class文件为jar,指定Main-class参数,ja自己定位main所在类
    jar -create --file hello.jar --mian-class com.b1cat.Main -C bin .
  • ③创建模块
    jmod create --class-path hello.jar hello.jmod
CATALOG
  1. 1. JAVA面向对象
    1. 1.1. 基础