人 | 类 / class | class Person { } |
小明 | 实例 / ming | Person ming = new Person() |
定义class
在Java中,创建一个类,例如,给这个类命名为Person
,就是定义一个class
:
class Person {
public String name;
public int age;
}
定义了class,只是定义了对象模版,而要根据对象模版创建出真正的对象实例,必须用new操作符。
new操作符可以创建一个实例,然后,我们需要定义一个引用类型的变量来指向这个实例:
Person ming = new Person();
有了指向这个实例的变量,我们就可以通过这个变量来操作实例。访问实例变量可以用变量.字段
,例如:
ming.name = "Xiao Ming"; // 对字段name赋值
ming.age = 12; // 对字段age赋值
System.out.println(ming.name); // 访问字段name
Person hong = new Person();
hong.name = "Xiao Hong";
hong.age = 15;
直接操作field
,容易造成逻辑混乱。为了避免外部代码直接去访问field
,我们可以用private
修饰field
,拒绝外部访问:
class Person {
private String name;
private int age;
}
我们需要使用方法(method
)来让外部代码可以间接修改field
:
public class Main {
public static void main(String[] args) {
Person ming = new Person();
ming.setName("Xiao Ming"); // 设置name
ming.setAge(12); // 设置age
System.out.println(ming.getName() + ", " + ming.getAge());
}
}
class Person {
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
if (age < 0 || age > 100) {
throw new IllegalArgumentException("invalid age value");
}
this.age = age;
}
}
所以,一个类通过定义方法,就可以给外部代码暴露一些操作的接口,同时,内部自己保证逻辑一致性。
在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello
类中,定义多个hello()
方法:
class Hello {
public void hello() {
System.out.println("Hello, world!");
}
public void hello(String name) {
System.out.println("Hello, " + name + "!");
}
public void hello(String name, int age) {
if (age < 18) {
System.out.println("Hi, " + name + "!");
} else {
System.out.println("Hello, " + name + "!");
}
}
}
这种方法名相同,但各自的参数不同,称为方法重载(Overload
)。
继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让Student
从Person
继承时,Student
就获得了Person
的所有功能,我们只需要为Student
编写新增的功能。
Java使用extends
关键字来实现继承:
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
可见,通过继承,Student
只需要编写额外的功能,不再需要重复代码。
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
在OOP的术语中,我们把Person
称为超类(super class),父类(parent class),基类(base class),把Student
称为子类(subclass),扩展类(extended class)。
继承有个特点,就是子类无法访问父类的private
字段或者private
方法。
这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private
改为protected
。用protected
修饰的字段可以被子类访问。
super
关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName
。
静态/实例方法
在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中;
而非静态方法属于对象的具体实例,只有在类的对象创建时在对象的内存中才有这个方法的代码段
编译器只为整个类创建了一个静态变量的副本,也就是只分配一个内存空间,虽然可能有多个实例,但这些实例共享该内存
接口(Interface)
接口之间可以继承与扩展,一个类可以实现多个接口,一个接口可以有多种实现类
接口:确定ADT规约;
实际中更倾向于使用接口来定义变量
类:实现ADT
Java的接口中不能含有constructors,但是从Java 8开始接口中可以含有static工厂方法,可用其替代constructors
default
通过default方法,可以在接口中统一实现某些功能,无需在各个类中重复实现它。好处是以增量式为接口增加额外的功能而不破坏已经实现的类
重写(Overriding)
严格继承:子类只能添加新方法,无法重写超类中的方法
如果想要一个java中方法不能被重写,必须要加上前缀final
父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的。对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求
如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写
重写时,可以利用super()来复用父类型中函数的功能
抽象类(Abstract Class)
抽象方法:只有声明没有具体实现的方法。用关键词abstract来定义
抽象类:如果一个类含有至少一个抽象方法,则被称为抽象类
接口:一个只含有抽象方法的抽象类
如果某些操作是子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写
接口和抽象类都不能实例化!
多态、子类型、重载(Polymorphism, subtyping and overloading)
多态的三种类型
特殊多态(Ad hoc polymorphism):重载
参数化多态(Parametric polymorphism):泛型
子类型多态、包含多态(Subtyping):继承
特殊多态和重载(Overloading)
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
重载是一种静态多态,根据参数列表进行"最佳匹配",进行静态类型检查
重载的解析在编译阶段,与之相反,重写的方法是在运行阶段进行动态类型检查
参数列表必须不同
相同/不同的返回值类型
相同/不同的public/private/protected
可以声明新的异常
参数多态和泛型(Generic)
泛型擦除:运行时泛型类型消除(如:List<String>运行时是不知道String的),所以,不能使用泛型数组(如: Pair < String >[] foo = new Pair < String >[42]; 是错误的!不能被编译!)
如下是一个错误的实例:
List<Object> a; List<String> b; a = b;
通配符(Wildcards),只在使用泛型的时候出现,不能在定义中出现。 如:List< ? extends Animal >
?extends T 和 ?super T 分别表示T和它的所有子/父类
子类型多态、继承
重写时,子类的规约要强于父类的规约(更弱的前置条件,更强的后置条件)
子类的可见性要强于父类(即父类如果是public,子类不能为private)
子类不能比父类抛出更多的异常
(详情见LSP原则)
注:Java无法检测1,但是可以检测出2、3
子类型多态:不同类型的对象可以统一的处理而无需区分。
instanceof
instanceof()判断对象运行时的类型
注:其父类也会判为true,如 a instanceof Object 始终为true
getclass()获取当前类型
List<Object>不是List<String>的父类
List<String>是ArrayList<String>的父类
List<?> 是 List<String>的父类
注:重写equal()方法时,需要注意参数类型,必须也是Object类型