面向对象基础
创建Person类实例
Person ming = new Person();
在OOP中,class和instance是“模版”和“实例”的关系;
定义class就是定义了一种数据类型,对应的instance是这种数据类型的实例;
class定义的field,在每个instance都会拥有各自的field,且互不干扰;
通过new操作符创建新的instance,然后用变量指向它,即可通过变量来引用这个instance;
访问实例字段的方法是变量名.字段名;
指向instance的变量都是引用变量。
定义方法的语法
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
this变量
在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。
封装
封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
private关键字
继承
Java使用extends关键字来实现继承:
class Student entends Person{
}
没有明确写extends的类,编译器会自动加上extends Object
Java只允许单继承
- 子类拥有父类全部的属性和方法(包括Private,但是不能访问)
- protected关键字可以把字段和方法的访问权限控制在继承树内部(子类可以访问父类的Protected,包括子类的子类也可以访问)
super——表示父类(超类)
- 子类访问父类字段时,可以用super.fileName
- 如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。 - 所以最好写一个没有参数的空构造函数
- super和this的对比
继承中构造方法的关系
- 子类中所有的构造方法都会访问父类中空参数的构造方法
- 子类中每个构造方法的第一条语句默认都是super();
- 如果父类中没有构造方法
方法覆(重写)写(override)
参考我的上一篇博客——重载与重写
阻止继承
正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。
例如,定义一个Shape类:
public sealed class Shape permits Rect, Circle, Triangle {
…
}
向上转型
把子类类型安全地变为父类类型的赋值
例如
Person p = new Student();
向下转型
把父类类型强制转换为子类类型
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
如果测试上面的代码,可以发现:
Person类型p1实际指向Student实例,Person类型变量p2实际指向Person实例。在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException。
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
类的设计原则:高内聚低耦合
Java只能单继承,但允许多层继承
static关键字
- 修饰成员变量和方法
- 随着类的加载而加载,优先于对象存在,被类所有对象共享,可以通过类名调用
- 静态方法是没有this关键字的(好理解,this关键字指向当前对象)
- 静态方法只能访问静态成员变量和静态成员方法
- 内存图:存在于方法区的静态区
- 静态变量与成员变量的区别
静态变量属于类,成员变量属于对象
静态变量存储于方法区的静态区,成员变量存储于堆区
内存出现时间不同,静态伴随类,成员变量伴随对象
调用:静态可以通过类也可以通过对象,成员变量只能通过对象 - 推荐用类名.字段名访问静态变量,类名.方法名访问静态方法
静态方法可以理解为C中的函数
final关键字
- 修饰类,类不能被继承
- 修饰变量,变量就变成了“常量”,只能被赋值一次(可以在构造函数中初始化final字段)
- 修饰方法,方法不能被重写
多态
多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。(通过重写等方式)
多态前提和体现
- 有继承有重写
- 父类引用指向子类对象
重写(覆写)Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
- toString():把instance输出为String;
- equals():判断两个instance是否逻辑相等;
- hashCode():计算一个instance的哈希值。
必要的时候可以重写这几个方法,如下
class Person {
...
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}
// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
接口和抽象类
抽象类
抽象类特点
- 抽象类和抽象方法必须用abstract关键字修饰
格式:
abstract class 类名{}
publict abstract void eat(); - 抽象类不一定有抽象方法,有抽象方法的一定是抽象类
- 抽象类不能被实例化(new的时候编译报错)
- 抽象类的子类要么是抽象类,要么就要重写抽象类中所有的抽象方法
抽象类的成员特点
- 成员变量可以是常量也可以是变量
- 有构造方法,但是不能通过构造方法实例化,构造方法的作用是用于子类访问父类数据的初始化
- 抽象方法不能有具体实现,但是成员方法也可以有非抽象方法
abstrac关键字不能和private、final、static共存
容易理解,抽象类就是要让子类来继承实现的,抽象类不能实例化,上面三个关键字与这种理念冲突
面向抽象编程
抽象类Person,具体类Student
Person s = new Student();
s.run();
尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是:
上层代码只定义规范(例如:abstract class Person);
不需要子类就可以实现业务逻辑(正常编译);
具体的业务逻辑由不同的子类实现,调用者并不关心。
接口
用interface定义接口
格式:interfac 接口名{}
interface Person{
void run();
String getName();
}
interface抽象极了,不能有任何字段(成员变量等),接口定义的所有方法默认都是public abstract,所以不用写
接口特点
- 具体的class实现接口(用implements表示)
格式:clas 类名 implements 接口名{}
举个例子
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}
- 接口的子类
要么是抽象类,要么实现接口中所有的抽象方法 - 一个类可以实现多个interface
class Student implements Person, Hello { // 实现了两个interface
...
}
- 接口成员特点
成员变量只能是常量,默认修饰符public static final
没有构造方法
成员方法只能是抽象方法,默认public abstract
抽象类和接口的比较
default的介绍可以参考博文
链接: https://blog.csdn.net/qq_27093465/article/details/84565771.
- 一个interface可以继承自另一个interface。interface继承自interface使用extends,例如
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
继承关系
合理设计interface和abstract class的继承关系,可以充分复用代码。一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。可以参考Java的集合类定义的一组接口、抽象类以及具体子类的继承关系:
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象
List list = new ArrayList(); // 用List接口引用具体子类的实例
Collection coll = list; // 向上转型为Collection接口
Iterable it = coll; // 向上转型为Iterable接口