第七章 复用类

1
2
3
4
5
6
7
8
9
10
11
12
//封装print函数,下方的代码直接调用此类
package chapter2;

public class Print {
public static void print(String string){
System.out.print(string);
}
public static void println(String string){
System.out.println(string);
}

}

组合 与 继承

组合

在新类中调用其他类的方法或属性,来构造新类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package chapter2;
import static chapter2.Print.*;
class Move {
void moveUp(){
println("moveUp");
}
void moveLeft(){
println("moveLeft");
}
}

public class Walk{
private Move move = new Move();
//在新类的方法中调用其他类的方法(组合)
void turn(){
println("Walk`s new method turn...");
move.moveUp();
move.moveLeft();
}
public static void main(String args[]){
//在新类中引用旧类
Walk walk = new Walk();
walk.turn();
}
}

输出结果

1
2
3
Walk`s new method turn...
moveUp
moveLeft

继承

通过继承,来调用基类的接口

一般将数据指定为private方法指定为public方便子类继承

将上面的代码修改下,利用继承的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package chapter2;
import static chapter2.Print.*;
class Move {
void moveUp(){
println("moveUp");
}
void moveLeft(){
println("moveLeft");
}
}

public class Walk extends Move{
//Move move = new Move();
void turn(){
println("Walk`s new method turn...");
moveUp();//原本为move.moveUp();
moveLeft();//原本为move.moveLeft();
}
public static void main(String args[]){
//在新类中引用旧类
Walk walk = new Walk();
walk.turn();
}
}

输出结果

1
2
3
Walk`s new method turn...
moveUp
moveLeft

再看一下复杂点的例子,来自菜鸟教程的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package chapter2;

class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象

a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();//会报错,Animal类没有bark方法
}
}

谈谈我的理解:
上面代码中,难理解的是Animal b = new Dog();这行代码

1
2
3
4
5
6
7
new Dog();//返回的是Dog对象的引用
Animal b;//建立一个Animal类的引用
/*
Animal类的引用可以指向Dog对象(Animal类的子类),是因为一个叫做`向上转型`的机制.
向上转型:子类可以当做基类来用.向上转型是安全的,基类有的功能,子类都有,把子类当做基类来用(类似于大材小用的那种感觉),所以Animal类型的引用可以操作(管理)其子类的对象,但是却不能处理基类中没有的方法或属性.
*/
b.bark();//会报错的原因在于,基类调用子类中特有的方法

继承与组合的区别

  • 继承类似于融合,子类拥有基类所有的接口(可继承的属性和方法)
  • 组合,类似于显式,有取舍的继承,并不会继承基类的全部接口

覆盖 与 重写

具体的可以看菜鸟教程里的介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package chapter2;
import static chapter2.Print.*;
class Animal{
public void profile(){
println("我是动物");
}
}
public class Fish extends Animal{
//重写
@Override
public void profile(){
println("我是鱼");
}
//重载
public void profile(String name){
println("我是鱼,我的名字是: "+name);
}
public static void main(String args[]){
Fish fish = new Fish();
Animal animal = new Animal();
fish.profile();
animal.profile();
fish.profile("fisher");
}
}

结果

1
2
3
我是鱼
我是动物
我是鱼,我的名字是: fisher

重写(override)

重写基类的方法,只修改方法内部的代码,不能将基类方法的权限降低,private<包访问权限<protected<public.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal{
protected void profile(){
println("我是动物");
}
void say(){
println("animal say");
}
}
public class Fish extends Animal{
//重写
@Override
public void profile(){ //protected->public
println("我是鱼");
}
@Override
protected void say(){ //包访问权限->protected
println("fish say");
}

那么为什么不能子类不能在重写时降低父类方法的权限?
因为基于向上转型原则,子类可能当做父类来用,如果降低子类的权限,就可能出错
即,如果基类方法被重写,并降低权限(可访问的数据变少,约束变多),就会使得向上转型变得不安全,子类不能做的,基类却可以做.
向上转型要求,对于父类和子类,子类可以比父类做的更多(功能更多),但是,父类能做的,子类一定要能做.这样就可以保证向上转型是安全的,

重载(overload)

要重载的类,方法名与基类相同,形参不同(形参个数,顺序不同).
可以修改访问权限,形参是区别重载的唯一

final关键字

final 数据

要确保每个final都有确定值

fianl 常量

有三种方式定义fianl 常量
在声明时,赋值

1
2
3
 class People{
final int a = 1;
}

在构造器中赋值

1
2
3
4
5
6
class People{
final int a;
public People() {
a = 1;
}
}

在构造代码块中赋值

1
2
3
4
5
6
class People{
final int a;
{
a = 1;//构造代码块中赋值
}
}

final 引用

对于基本数据类型,final确保数据恒定不变,对于引用,final确保引用恒定不变.即不能改变引用所指向的对象,但是可以利用引用对对象进行操作

final引用的赋值方式与fianl数据赋值方式相同,

1
2
3
4
5
6
7
8
9
10
public class fi {
final People people;
People man = new People();
People woman = new People();
{
people = man;
people = woman;//会报错
woman = man;//修改woman的引用对象,Java不会报错
}
}

final 参数

形参若为final类型,则不能修改该形参所引用的对象

final 方法

不可被重写(覆盖),但是可以被重载

对与private与final

  • final 与 private 一起使用是多次一举
    private代表方法不可被继承,也就没有重载和重写的可能
    看下面的例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package chapter2;
    import static chapter2.Print.*;
    class People{
    final int a=1;
    private void say(){
    println("Hello World");
    }
    }
    public class fi extends People{
    //@Override
    public void say(){
    println("这不是重写");
    }
    public void say(String name){
    println("这不是重载");
    }
    }

fi类中的两个方法,虽然名字与基类相同,但是却不是重载和重写,不信的话,可以将override的注释去掉,如果是重写的话,Java是不会报错的,但是实际上,如果把注释去掉,Java程序是无法运行的.
fi的两个方法只是名字与基类相同,但是两者却没有任何关系,因为对fi而言是看不到基类中的say方法的,所以在fi中的两个方法,Java是把它们当做fi类特有的方法.

继承与初始化

这个对我而言比较难理解,先看一下总体代码

总体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package chapter2;
import static chapter2.Print.*;
class Insect{
private int i = 9;
protected int j;
public Insect() {
// TODO Auto-generated constructor stub
println("Insect construct...");
println("i = "+i+", j = "+j);
j = 39;
}
//静态变量i1
private static int i1 = printInit("static i1 initialized");
//普通变量i2
int i2 = printInit(" i2 initialized");
//静态方法
static int printInit(String s){
println(s);
return 47;
}
}
public class Beetle extends Insect{
//普通变量k
private int k = printInit("Beetle.k initialized");
//
public Beetle() {
// TODO Auto-generated constructor stub
println("Beetle construct...");
println(" k = "+k+"\n"+" j = "+j);
}
//静态
private static int b1 = printInit("static Beetle.b1 initialized");
public static void main(String args[]){
println("Beetle main---");
//Beetle beetle = new Beetle();
//Insect insect = new Insect();
}
}

运行结果

1
2
3
static i1 initialized
static Beetle.b1 initialized
Beetle main---

main函数没有进行任何操作,也就是说:

  • java先找到.java文件中的public类,即先初始化Beetle类但是Beetle继承于Insect,之前先初始化Insect(这边的初始 化只针对静态变量而言)
  • 然后执行main函数代码

main函数生成Insect对象

1
2
3
4
5
public static void main(String args[]){
println("Beetle main---new Insect");

Insect insect = new Insect();
}

运行结果

1
2
3
4
5
6
static i1 initialized
static Beetle.b1 initialized
Beetle main---new Insect
i2 initialized
Insect construct...
i = 9, j = 0

由结果可以看出,java开始对Insect类进行完整的初始化:
静态变量->普通变量->构造函数

main函数生成Beetle对象

1
2
3
4
public static void main(String args[]){
println("Beetle main---new Beetle");
Beetle beetle = new Beetle();
}

运行结果

1
2
3
4
5
6
7
8
9
10
static i1 initialized
static Beetle.b1 initialized
Beetle main---new Beetle
i2 initialized
Insect construct...
i = 9, j = 0
Beetle.k initialized
Beetle construct...
k = 47
j = 39

从结果中可以看出,在main函数中新建Beetle对象的同时,也会对Insect类进行完整的初始化操作.

现在大致将java对Beetle.java文件的编译过程走一遍:
static数据是在java运行时就初始化,static方法是在调用时初始化
java在执行Beetle.java时,第一件事就是试图访问Beetle.main(){一个静态方法},通过extends知道,它有一个基类Insect,于是要先对基类进行处理(初始化static变量),这样子类的运行才能正常进行.注意,以上是刚进入访问main函数时的操作,也就是说无论是否新建一个Insect或则Beetle类,上面的流程都要走一遍.即先对所有可能要用到的类中的static变量进行初始化,至此就可以创建对象

然后开始执行main中代码,在创建Beetle对象时由于它继承自Insect故要先创建Insect对象,对象创建流程:静态变量->普通变量->构造函数,将InsectBeetle对象都创建完毕,才算把Beetle对象创建完毕.

本文标题:第七章 复用类

文章作者:定。

发布时间:2017年5月7日 - 16时05分

本文字数:7,665字

原始链接:http://cocofe.cn/2017/05/07/第七章 复用类/

许可协议: Attribution-NonCommercial 4.0

转载请保留以上信息。