文章目录
1.继承
1.1 继承
- 面向对象编程语言和过的最突出特点就是
变量类型的继承
- 更符合大自然规律:父亲有的,儿子就有
- 首先看个例子
// Father
public class Father
{
public void f1()
{
System.out.println("hi");
}
}
// Son
public class Son extends Father
{
public static void main(String[] a)
{
Son s = new Son();
s.f1(); //Son没有定义f1,而是通过父类继承的
}
}
1.2 继承
- 面向过程编语言没有继承,导致出现很多类型重复定义
- 物以类聚,世间万皆对象,对象也可分成若干类别。
- 类别内的对象属性和方法都具有一定共同点。
- 将共同点提取出来,即形成了 父类 /基类 /超类
- Parent class / Base class / Super class
- 而其他类 则自动成为 子类/派生类
- Child class / Derived class
1.3 继承
// Man
public class Man {
int height;
int weight;
public void eat(){};
public void plough(){}; //耕田
}
// Woman
public class Woman {
int height;
int weight;
public void eat(){};
public void weave(){}; //织布
}
提取共同点:
// Human
public class Human {
int height;
int weight;
public void eat(){};
}
继承:
public class Man extends Human{
public void plough(){}; //耕田
}
public class Woman extends Human {
public void weave(){}; //织布
}
从多个类别(对象)中提前共性,形成了符类。其他类继承符类,成为子类,也拥有这些共性
1.4 继承
- Man extends Human表示Man继承于human
- Human是父类,Man是子类
- 子类继承父类所有的属性和方法(但不能直接访问private成员)
- 根据信息隐藏原则:子类会继承符类所有的方法。可以直接使用
- 子类也会继承父类的符类的所有的属性和方法(但不能直接访问private成员)
1.5 继承
Base :
public class Base {
private int num = 10;
public int getNum()
{
return this.num;
}
}
Derived : 1
public class Derived extends Base{
public static void main(String[] args) {
Derived foo = new Derived();
System.out.println(foo.getNum());
}
}
// 输出结果:10
子类可以通过调用父类的方法来访问父类的私有的成员属性
Derived : 2
public class Derived extends Base{
private int num = 20;
public int getNum() {
return this.num;
}
public static void main(String[] args) {
Derived foo = new Derived();
System.out.println(foo.getNum());
}
}
// 输出结果:20
在同样方法名和参数情况下,本类的方法会比父类的方法优先级高
1.6 继承
-
单根继承原则:每个类都只能继承一个类(Java继承和C++一个最大的区别)
- Java:若一个方法在当前类中没有定义,那此方法就来自它的父类
- C++支持一个类继承多个类(需要去寻找某方法对应的中间父类)
- 在Java语言的设计中,针对C++中方法指代不清的问题进行改进,特意强调了单根继承的原则
-
如果不写extends,Java类都默认继承java.lang.Object类
- class Human{…}
- 等价于 class Human extends Object{…}
- 等价于 class Human extends java.lang.Object{…}
-
class Human extends java.lang.Object
-
Java所有类从java.lang.Object开始,构建出一个类型继承树
-
Object类里面默认就有clone、equals、finalize、getClass、hashCode、toString等方法
- class A{}也是继承于Object类,所以类A也有clone(),equals()等方法
1.7 继承
- 每个Java类都必须有构造函数
- 如果没有显示定义构造函数,Java编译器自动为该类产生一个空的无线参构造函数。如果以及有了显示的有参构造函数,编译器就不会越俎代庖了
- 每个子类的构造函数的第一句话,都默认调用父类的无参数构造函数super(),除非子类的构造函数第一句话是super,而且super语句必须放在第一条,不会出现连续两条super语句。
- 查看A.java和B.java程序
// A
public class A {
public A(){
System.out.println("111111");
}
public A(int a){
System.out.println("222222");
}
}
// B
public class B extends A{
public B(){
//super();编译器自动增加super()
System.out.println("333333");
}
public B(int a){
super(a); //编译器不会自动增加super();
System.out.println("444444");
}
public static void main(String[] a){
B obj1 = new B();
System.out.println("==============");
B obj2 = new B(10);
}
}
输出结果
111111
333333
==============
222222
444444
如果构造函数的第一句话不是super,编译器会自动增加一句super();如果构造函数第一句是程序员自己写的super语句,编译器就不会自动增加了
1.8 总结
- 子类继承父们所有的东西 但不能直接访问 private 成员 )
- Java所有类都继承自java.lang.Object类
- JavaJavaJava 所有的 类都是单根继承
- 子类构造函数 默认第一句话都会去调用父的子类构造函数
2.抽象类和接口
2.1 抽象类
- 类:属性 (0 或多个)+ 方法 (0 或多个 )
最简单的类:class A{} - 一个完整 (健康 )的类:所有方法都实现有实现 (方法体 )
完整的方法:方法名(参数){ 方法体 } - 类可以没有方法,但是有方法就肯定要有实现,这才是一 个完整的类
- 一个完整的类才可以被实例化,被new 出来
- 如果一个类 暂时有方法未实现,需要被定义为抽象类
若方法只有方法名字,形参列表,没有方法体,那所在的类就被定义为抽象类
2.2 抽象类
- 抽象类关键字abstract声明
- 抽象类的组成
- (optional)成员变量,个数不限
- (optional)具体方法,方法有实现,个数不限
- (optional)抽象方法,加abstract关键字,个数不限
public abstract class Shape {
//
int area;
// 抽象帆帆
public abstract void calArea();
}
当图形未知时,无法给出calArea的具体实现,因此此方法被定义为abstract。
calArea()是一个不完整/不健康的方法,因为它没有方法体。
类A被定义为抽象的:abstract class A{};因此 A a=
new A();是不成立的。
如果有一个abstract的方法,那么类也必须是 abstract的
2.3 抽象类
- 抽象类也是类。一个类继承于抽象类,就不能继承于其他的(抽象)类
- 子类可以继承于抽象类,但是一定要实现父类们所有abstract的方法。如果不能完全实现,那么子类也必须被定义为抽象类
- 只有实现父类(们)的所有抽象方法,才变成完整类
public abstract class Shape {
//面积
int area;
//计算面积方法
public abstract void calArea();
}
//继承自Shape抽象类
public class Rectangle extends Shape{
int width; //宽
int length; //长
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
public void calArea() {
System.out.println(this.length * this.width);
}
public static void main(String[] args) {
Rectangle rect = new Rectangle(10,5);
rect.calArea();
}
}
Rectangle继承于Shape,那么Rectangle就必须实现calArea方法
2.4 接口
- 如果类的所有方法都没实现,那么这个就算是接口interface
public interface Animal {
public void eat();
public void move();
}
- 接口不算类,或者说是“特殊”的类
interface是一种特殊的类 - 类只可以继承 (extends) 一个类,但是可以实现 (implements) 多 个接口,继承和实现可以同时。
1.被继承的类可以时抽象类,也可以是普通类。
2.继承抽象类,必须实现abstract的方法;实现多个接口,必须实现接口中所定义的所有方法
3.接口设计时为了弥补单根继承的不足:单根继承下,一个类只能遵循另一个类的标准,不是很灵活
4.接口里所有方法都是空的
5.一个类的方法,只会在当前类或者父类中定义,肯定不会再所实现的父类接口中定义
例如:类C继承于类A,同事也实现接口B。C中的f1()方法只会在A或者C中实现
2.5 接口
- 接口可以继承 (多个 )接口,没有实现的方法将会叠加
- 类实现接口,就必须实现所有未实现的方法。如果没有全部实现,那么只能成为一个抽象类。
抽象类的方法可以为空(没有方法体),正常类的方法不能为空(必须有方法体)。 - 接口里可以定义变量,但是一般是常量,详细乐意参考final节
- 例子Animal/Cat
Animal 接口:
//
public interface Animal {
public void eat();
public void move();
}
接口Animal存储上是.java文件,编译后也是.class文件。 、
Cat类:
// Cat 实现了 Animal接口
public class Cat implements Animal{
public void eat() {
System.out.println("Cat: I can eat");
}
public void move(){
System.out.println("Cat: I can move");
}
}
- 例子Animal/ClimbTree/LandAnimal/Rabbit
ClimbTree 接口:
public interface ClimbTree {
public void climb();
}
LandAnimal 抽象类:
// LandAnimal 实现了 Animal 的move方法
public abstract class LandAnimal implements Animal {
public abstract void eat() ;
public void move() {
System.out.println("I can walk by feet");
}
}
Rabbit 类:继承LandAnimal抽象类、实现ClimbTree接口
extends必须写在implements 前面
public class Rabbit extends LandAnimal implements ClimbTree {
public void climb() {
System.out.println("Rabbit: I can climb");
}
public void eat() {
System.out.println("Rabbit: I can eat");
}
}
- 例子Animal/ClimbTree/CatFamily/Tiger
CatFamily 接口:继承Animal接口以及ClimbTree接口
public interface CatFamily extends Animal, ClimbTree{
// CatFamily包含以下3个方法
//eat()
//move()
//climb()
}
Tiger 类:实现CatFamily 接口
不全部实现所有方法,需改成抽象类
public class Tiger implements CatFamily {
//必须实现CatFamily中的三个方法
public void eat() {
System.out.println("Tiger: I can eat");
}
public void move() {
System.out.println("Tiger: I can move");
}
public void climb() {
System.out.println("Tiger: I can climb");
}
}
继承多个接口,相当于将多个接口为实现的方法都“承载”过来。
2.6 总结
- 抽象类和接口相同点:两者都不能 被实例化,不能new操作
- 抽象类和接口不同点:
抽象类abstract,接口interface
抽象类可以有部分方法实现,接口所有方法不能有实现
一个类只能继承(extends)一个(抽象)类,实现(implements)多个接口
- 接口可以继承(extends)多个接口
- 抽象类有构造函数,接口没有构造函数
- 抽象类可以有main,可以独立运行;接口没有main函数,不能被运行
- 抽象类方法可以有private/protected,接口方法都是public
3.转型、多态和锲约设计
3.1 类转型
- 变量支持相互转化,比如int a=(int)3.5;
- 类型可以相互转型,但是只限制于有继承关系的类
子类可以转换成父类,而父类不可以转为子类
- 子类继承父类所有的财产,子类可以变成父类(从大变小,即
向上转型
);从父类直接变成子类(从小到大,即向下转型
)则不允许
Human obj1 = new Man();//OK,Man extends Human
Man obj2 = new Human ();//illegal,Man is a derived class Human
3.2 类转型
- 父类转为子类有一种情况例外:
就是这个父类本身就是从子类转化过来的
Human obj1 = new Man();//OK,Man extends Human
Man obj2 = (Man)obj1 ;//Ok,because obj1 is born form Man class
obj1本身就是Human类型。obj1本身起源就是来自Man。所以可以强制转换成Man类型
3.3 多态
- 类型转换,带来的作用就是多态
- 子类继承父类的所有方法,但子类可以重写定义一个名字、参数和父类一样的方法,这种行为就是重写(覆写、覆盖、overwrite/override、not overload(重载))
- 重载:函数名一样,形参不一样
- 重写:子类的方法,替换掉父类的方法(同名同参数)
- 子类的方法的优先级高于父类的
Man 类:重写了Human的eat和plough方法
public class Man extends Human {
public void eat() {
System.out.println("I can eat more");
}
public void plough() { }
public static void main(String[] a) {
Man obj1 = new Man();
obj1.eat(); // call Man.eat()
Human obj2 = (Human) obj1;
obj2.eat(); // call Man.eat()
Man obj3 = (Man) obj2;
obj3.eat(); // call Man.eat()
}
}
输出结果:
I can eat more
I can eat more
I can eat more
obj2转型之前是obj1,而obj1是Man类型,所以obj2本质上是Man类型
obj2本身也是脱胎于obj1
3.4 多态
- 多态的作用
- 以统一的接口来操纵某一类中不同的对象的动态行为
- 对象之间的解耦
- 参看AnimalTest.java
// Animal 接口
public interface Animal {
public void eat();
public void move();
}
// Cat
public class Cat implements Animal{
public void eat() {
System.out.println("Cat: I can eat");
}
public void move(){
System.out.println("Cat: I can move");
}
}
// Dog
public class Dog implements Animal{
public void eat() {
System.out.println("Dog: I can eat");
}
public void move() {
System.out.println("Dog: I can move");
}
}
// AnimalTest
public class AnimalTest {
public static void haveLunch(Animal a) {
a.eat();
}
public static void main(String[] args) {
Animal[] as = new Animal[4];
as[0] = new Cat();
as[1] = new Dog();
as[2] = new Cat();
as[3] = new Dog();
for(int i=0;i<as.length;i++) {
as[i].move(); //调用每个元素的自身的move方法
}
for(int i=0;i<as.length;i++) {
haveLunch(as[i]);
}
haveLunch(new Cat()); //Animal a = new Cat(); haveLunch(a);
haveLunch(new Dog());
haveLunch(
new Animal()
{
public void eat() {
System.out.println("I can eat from an anonymous class");
}
public void move() {
System.out.println("I can move from an anonymous class");
}
});
}
}
3.5 契约设计
- Java编程设计遵循契约精神
- 契约:规定规范了对象应该包含的行为方法
- 接口定义了方法的名称、参数和返回值,规范了派生类的行为
- 基于接口,利用转型和多态,不影响真正方法的调用,成功地将调用类和被调用类解耦(decoupling)
3.6 契约设计
- 被调用类(haveLunch只和Animal有联系)
public static void haveLunch(Animal a) {
a.eat();
}
Animal是一个接口,里面所有的方法都是空的,没有方法体
- 调用类
haveLunch(new Cat()); //隐形转型:Animal a = new Cat(); haveLunch(a);
haveLunch(new Dog());
haveLunch(
// 接口是不能new的,实际上在这里重新new了下接口
// new接口的同时,必须将接口中所有的方法实现补足补全
// 这段话中间就隐藏地定义了一个匿名类
// 匿名类只在这句话里面用一次就结束了
new Animal(){
public void eat() {
System.out.println("I can eat from an anonymous class");
}
public void move() {
System.out.println("I can move from an anonymous class");
}
});
只需要传进来一个实现Animal接口地对象,就可以运行haveLunch方法。
这2个类就彻底地完成解耦
3.7总结
- 类转型:子类可以转父类,父类不可以转子类(除非父类对象本身就是子类)
- 多态:子类转型为父类后,调用普通方法,依旧是子类本身的方法
- 契约设计:类不会直接使用另外一个类,而实采用接口形式,外部可以“空投”这个接口下地任意子类对象
在类相互调用中,Java普遍采用接口地契约设计,使对象之间或者说,程序之间的耦合度减轻