1 面向对象概述
面向过程:站在程序员的角度按照“自顶向下,逐步求精”的模块化思想去解决问题,适合解决小规模的问题。
面向对象:按照“由下而上”的思想,站在用户的角度先考虑系统由什么物体(对象)组成,对象之间时如何交互的。它比较适合解决大规模的问题。
对象:万物皆对象,对象是由静态的属性和动态的方法组成。
类:一组具有相同属性和行为对象的抽象事物。
消息:向某个对象发送请求。(如:手机来电)
方法:对象对收到请求的响应。(如:手机收到来电会震动提示)
抽象:从一个具体的对象中提取提取一组数据,去除非本质和特性的属性,保留本质的、共性的。
比如学生,有很多的属性(学号、姓名、性别、身高、体重、血型、专业、班级、成绩),如果我们要做一个学生成绩管理系统,我们应该关注的,除了学生的基本信息(姓名、性别等),还要关注和学习相关的属性(学号、专业、班级、成绩),不用关注他的血型、体重、身高等跟成绩无关的属性。而如果我们要做的是一个学生健康管理系统,那么除了基本属性,身高、体重就是学生的本质属性。
封装:给对象一个边界,将内部信息尽量的隐藏,只保留对外的操作接口。比如:显示器内部的元器件是看不到的,也不允许随意摆弄,而是留下电源、信号接口以及调节亮度的按钮。封装使得对象更加的安全,容易。
继承:继承是允许后代直接使用前辈直接拥有的。比如,长得像父母就是继承了父母的部分基因。如果自然界中没有继承,世界就依然是混沌状态,所有生物的出现都是从初始状态,也就没有了进化。继承是一种代码重用的机制,使得代码更省略、更可靠、更高效。
比如:一个拥有卡车设计和制造能力的企业,要设计消防车,显然是比没有经验的企业更加高效和可靠,因为他继承了之前的设计和制造能力,只是需要在此基础上稍加改动即可。
多态:同样的消息发送给不同的对象,不同的对象做出的响应可能有所不同。比如:跑步运动员和游泳运动员,都有“出发”的这个方法,当收到裁判员传来的消息时,都开始执行“出发”,但是他们所做出来的动作是不相同的。
面向对象程序设计方法实际上是用我们习惯的人的思维方式去思考和设计,其实是返璞归真,理解起来也不会很难。
2.类与对象
2.1 类和对象的定义与使用
类的定义:
Class 类名{
数据成员-静态属性
成员函数-动态行为
}
对象的定义:
类名 对象名
与int a的含义差不多
类定义的“{}”就是类的“外壳”,提供了封装,private声明的成员外面是不能访问的,而public可以。
引用:一个储存了某个对象地址的变量,它储存的不是对象本身,而是对象的地址。
Point point=new Point();的含义是:首先实例化一个对象(new Point()),然后定义一个引用point指向这个实例。
point=point1
不是讲实例point赋值给point1,而只是改变了引用的关系,即point也指向了point1指向的实例
2.2 构造函数和垃圾回收
为什么需要有构造函数?
构造函数是完成对象的初始化,这个当然可以定义一个成员方法予以实现。但是这个方法必须显式调用,否则就可能因未初始化出现错误。
我们希望这样的方法能够被自动调用,能被自动调用,那么它的名字一定是独一无二的,什么样的名字才是独一无二的呢?——以类名作为方法名。这就是构造函数。
构造函数,是在对象实例化时自动被系统调用,该函数名必须独一无二。对于一个类来说,就是将一个类名作为函数名。
构造函数不需要程序员去定义返回值,他是系统自动决定的,void也不行。
构造函数无法、也无需指定指定返回值,它是自动被调用的。
函数重载
多个函数使用同一个函数名,系统根据参数自动调用相应的函数。
函数重载解决了标识符统一的问题。
拷贝构造函数
用一个已经存在的本类的对象去复制新的对象
垃圾回收:所谓的“垃圾”是指之前系统分配的内存单元,但是现在没用了,
再具体的讲,就是没有引用指向它。对于“垃圾”来讲,程序员不需要自己去回收,而是系统提供的垃圾回收机制自动回收,当程序员觉得有必要时,也可以强制让系统回收。
2.3 访问权限控制和封装
方法的访问控制(四种访问权限):
封装的要领,是站在用户的角度去思考怎么去描述一个类,它应该有什么属性和行为,而不是去考虑如何实现。
2.4 静态
静态成员修饰的成员与一般的成员有何区别呢?
一般的成员是每个对象有一份数据,而静态成员是属于类的,即所有对象所共享;
一般成员的访问方式:对象名.成员名,而静态成员的访问方式:类名.成员名。
package 静态;
public class java1809Student {
privateint count;//班级人数
private String name;
private int age;
public java1809Student(String name, int age){
this.name = name;
this.age = age;
count++;
}
publicvoid printClaswInfo(){
System.out.printf("班级人数%d",count);
}
}
package 静态;
public class Test {
publicstatic void main(String[] args) {
java1809Student s1=new java1809Student("王二小",25);
java1809Student s2=new java1809Student("王三小",21);
java1809Student s3=new java1809Student("王四小",23);
s1.printClaswInfo();
}
}
执行上述代码,创建了三个学生,本来预期输出班级信息(人数)应该为3人,结果输出结果为1?为什么?
姓名可以是每个学生一份,但班级人数不应该时个体的属性,而是整个集体的属性,怎么做到这一点?
this是“这个”、“当前”的意思,每一个成员方法都隐含有一个参数(this)。
比如
public void print(){
System.out.println("学号:"+sno);
System.out.println("姓名:"+name);
System.out.println("性别:"+(sex==MALE?"男":"女"));
System.out.println("学历:"+(level==UNDERGRADUATION?"本科":"专科"));
}
中的
System.out.println("学号:"+sno);
严格来讲,应该写成
System.out.println("学号:"+this.sno);
对于一般的成员变量,实在构造函数中初始化,那么静态成员变量在哪初始化呢?是否也是在构造函数中初始化呢?
不能!因为每次实例化对象时都要执行构造函数(可能时多次),但静态成员是整个类共享,只需要初始化一个,所以不能再构造函数中初始化。那么它应该在哪里初始化呢?-静态代码块
static {
className="java1908班";
classroom="五号";
count=0;
}
静态代码块在加载类的时候执行 而且只会执行一次,
静态常量,静态成员变量,静态成员方法,静态代码块
静态方法没有this 为什么?
因为this是属于当前对象的,而静态方法是属于整个类共享的。
静态方法只能访问静态成员
非静态方法age不能被来自一个静态的方法访问。
一般的成员都是通过构造函数去完成初始化(实例化对象时),而静态成员则是在加载类的时候(也就是实例化之前)初始化
2.5 内部类
内部类是指在一个类的内部定义的类
内部类可以自由访问外部类的所有成员
内部类可以作为成员类(在外部类中定义,和成员方法平行)和方法内部类(定义在外部类的成员方法里面)。
内部类的实例化:
Computer.Cpu cpu=new Computer().new Cpu(); System.out.println(cpu.getY());
当一个类离开另一个类而无法单独存在或者说没有意义的情况下,使用内部类是最合适的(如:CPU离开计算机类,单独存在就没有意义了)
面对对象设计
(1)类的设计:考虑应该由哪些属性去描述这个类,这个类应该提供什么方法?
(2)方法(函数)设计:函数名、参数和返回值类型
如果需要返回多个结果,函数返回值如何设计
(1)可以将多个返回结果融在一个结果中,用特定的值表示其中的一个结果。比如查找函数需要返回是否找到以及找到的话下标是多少两个结果。可以将这两个结果并在一个整型返回值中,用-1表示没找到,找到的话就返回下标。这样设计的理由是下标不可能为-1,所以-1可以表示特定的含义。
(2)将多个返回值封装成一个类对象
(3)可以考虑使用输出型参数,在Java中就只能使用引用类型了。
3 继承与多态
3.1 继承的基本概念
什么是继承?
继承就是可以直接使用前辈的属性和方法。
为什么需要继承?
自然界中如果没有继承,那一切都是出于混沌状态。在软件开发中,我们可以借鉴自然界的机制,已经有的东西我们希望能够直接拿来用(复用)而不用重复做。
案例:定义人和员工类
定义人
publicclass Person {
private Stringid;//身份证号码
private String name;//姓名
private String sex;//性别
public Person(String id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
publicvoid print(){
System.out.println(id);
System.out.println(name);
System.out.println(sex);
}
}
定义员工类
publicclass Employee {
private Stringid;
private Stringname;
private Stringsex;
privateint salary;//工资
public Employee(String id, String name, String sex, int salary) {
this.id = id;
this.name = name;
this.sex = sex;
this.salary = salary;
}
publicvoid print(){
System.out.println(id);
System.out.println(name);
System.out.println(sex);
System.out.println(salary);
}
}
测试程序
publicclass Test {
publicstatic void main(String[] args) {
Person p = new Person("3320078427849803","zhang","男");
Employee e = new Employee("413739879472078","wang","女",3600);
p.print();
e.print();
}
}
这样做存在的问题:
(1)重复劳动:人有姓名、性别等属性,员工中又重复定义了,将来可能在学生、教师等类中还要重复。
(2)信息不一致:比如人的性别用0-1表示,而员工类中可能用“男/女”来表示,导致使用上的混乱。
那么能否在员工类中使用已有的人类的姓名、性别等呢——继承。
3.2 类的继承
如何使用继承呢?
把上面的员工类改写一下——继承自Person类
publicclass Employee extendsPerson {
privateint salary;//工资
public Employee(String id, String name, String sex, int salary) {
super(id, name, sex);
this.salary = salary;
}
@Override
publicvoid print() {
super.print();
System.out.println(salary);
}
}
使用继承就很好地解决了上面提出的两个问题。
extends是“拓展、延伸”的含义,public class Employee extends Person 就是声明一个叫做Employee的类,它是在Person类的基础上进行拓展,专业术语称之为继承自Person类。
super是“超”的含义,Person类相对于Employee类就是超类,通俗一点的说法是父类。
子类继承了父类的属性和方法,当然也可以在此基础上进行拓展——加和改。在这个案例中加了“薪水”属性,改了“打印”方法。这里有一个专业术语——重写(override)。
继承不仅仅解决了刚才的两个问题,还使得类有层次结构,逻辑上也更加合理。比如“员工”也是“人”。
3.3 访问权限控制(补充)
在之前已经讲过private和public
private:私有的,外部是不可访问的
public:公有的,外部可以访问
protected:保护的,外部访问如果是来自子类是可以的,其它的拒绝。
default:自学
也就是外部分为两种:子类和非子类,对于子类而言,protected等同于public,而对于非子类protected又等同于private。
比如在Employee类中,如果name是私有的,那么这个访问就是被拒绝的,而如果是保护的则可以。
publicvoid print() {
super.print();
System.out.println(name);
System.out.println(salary);
}
即使是保护的成员,对于非子类的外部访问也是拒绝的,比如在测试类中就是不可以。
publicclass Test {
publicstatic void main(String[] args) {
Person p = new Person("3320078427849803","zhang","男");
Employee e = new Employee("413739879472078","wang","女",3600);
p.print();
e.print();
System.out.println(e.name);
}
}
3.3 多态
多态就是将同一个消息发送给不同对象时,它们所做的响应可能是不同的。比如说动物都有“叫”的方法,但是狗是“汪汪”,而猫是“喵喵”,当它们接收到被打的消息时,所做的响应是不同的。
案例:打动物
定义一个动物类,实现“叫”的方法
publicclass Animal {
publicvoid shout(){
System.out.println("吼吼……");
}
}
定义一个狗类,继承自动物类,重写“叫”的方法
publicclass Dog extends Animal {
@Override
publicvoid shout() {
System.out.println("汪汪……");
}
}
定义一个猫类
publicclass Cat extends Animal {
@Override
publicvoid shout() {
System.out.println("喵喵……");
}
}
测试程序
publicclass Test {
public staticvoid main(String[] args) {
Dog d = new Dog();
Cat c = new Cat();
hit(d);
hit(c);
}
privatestatic void hit(Cat c) {
c.shout();
}
privatestatic void hit(Dog d) {
d.shout();
}
}
在测试程序中定义了两个“打”的方法,这种做法是比较笨拙的,有几种动物就需要定义几个方法,这个可能没完没了。有没有一个一劳永逸的方法?
不要传递Dog或者Cat等对象,而是传递一个Animal类对象就可以解决上面的问题。测试程序修改为
publicclass Test {
publicstatic void main(String[] args) {
Dog d = new Dog();
Cat c = new Cat();
hit(d);
hit(c);
}
privatestatic void hit(Animal a) {
a.shout();
}
}
这里涉及两个知识点:
(1)赋值兼容性规则
hit方法形参是Animal类型,而实参是Dog或者Cat,所以类型不一致,为什么还可以呢?
所谓赋值兼容性规则,指的是凡是需要用到父类引用的地方,都可以使用它的子类引用去代替。比如某人请求派车接他,结果对方派了个三轮车去接了,虽然他可能不满意,但是也没有办法,因为对方是按照他的要求做的。
(2)多态
形参一定是父类的引用,而实参可以是它的任何一个子类的引用。
Final 修饰类的时候不能被继承 ,修饰方法的时候不能被重写。
4 抽象类与接口
4.1 抽象类
在上一章中动物多态性的案例中,我们提到了动物有“叫”的方法
publicclass Animal {
publicvoid shout(){
System.out.println("吼吼……");
}
}
实际上,我们能确定的是动物是有“叫”的方法,但是具体怎么叫是无法描述的!
那么在Java中如何来描述和解决这样的问题呢——抽象方法
抽象方法只有声明,没有实现。Abstract
如果类中含有抽象方法,那么它就是抽象类。
抽象类不能实例化对象,下面的代码是错误的
Animal a1 = new Animal();
既然不能实例化对象,那么抽象类还有什么用呢?
抽象类是去完成上层规划,不去考虑具体实现细节,这个可以交由后代去实现。
4.2 接口
抽象类指的是其中含有抽象方法,而所谓接口,指的是其中所有的方法都是抽象方法。
为什么使用接口?
如果各个形状类自行其是,比如三角形面积返回值类型是float,矩形返回double,而圆形的方法名使用拼音。那么对于使用者必然造成困扰——太乱了!
而使用接口就起到了统一规划的目的,他的实现者(继承者)必须要实现它规划好的方法,不能更改。这样就增加了类的一致性,便于使用和维护。
4.3 匿名内部类
在上面的案例中,如果还需要显示一个正方形的信息,和之前的圆形以及矩形类似,需要以下步骤:
(1)定义一个类并实现形状接口
public class Rectangle implements Shape
(2)定义一个对象
Rectangle r = new Rectangle(p,3,5);
(3)使用该对象
show(r);
如果这个对象我们仅仅就使用一次,上面的步骤就过于麻烦了,可以考虑简化——匿名内部类
show(new Shape(){
@Override
publicdouble length() {
return0;
}
@Override
publicdouble area() {
return0;
}
});
5 泛型
5.1 泛型的引入
在之前的堆栈中,我们只能存储int数据,如果需要存储float数据,就必须再定义一个堆栈。我们发现代码基本上是一样的,只是将其中的部分int改成了float,但是我们不得不重新写一遍。
如果以后还要存储double、String……等类型数据怎么办?有没有一劳永逸的办法只写一遍呢?——泛型。
publicclass FloatStack {
private float[]data;//数据存储区
private int capacity;//堆栈容量
private int size;//堆栈当前实际大小
private int top;//栈顶位置
public FloatStack(int capacity){
this.capacity = capacity;
data= new float[capacity];
size= 0;
top =-1;
}
publicboolean isFull(){
return size == capacity;
}
publicboolean isEmpty(){
return size == 0;
}
publicboolean push(floatd){
if(isFull()){
returnfalse;
}
top++;
size++;
data[top] = d;
returntrue;
}
public Float pop(){
if(isEmpty()){
returnnull;
}
floatd = data[top];
top--;
size--;
return d;
}
}
5.2 泛型的定义和使用
使用泛型定义一个通用性堆栈。也就是把刚才的特定类型改为“某种”类型,即泛指某种类型(这就是泛型的由来)。就这么一点小小的改动带来的确实本质性的变化——一劳永逸,体现了通用。
publicclass CommonStack<T> {
private T[]data;//数据存储区
private int capacity;//堆栈容量
private int size;//堆栈当前实际大小
private int top;//栈顶位置
public CommonStack(int capacity){
this.capacity = capacity;
data= (T[])new Object[capacity];
size= 0;
top =-1;
}
publicboolean isFull(){
return size == capacity;
}
publicboolean isEmpty(){
return size == 0;
}
publicboolean push(Td){
if(isFull()){
returnfalse;
}
top++;
size++;
data[top] = d;
returntrue;
}
public T pop(){
if(isEmpty()){
returnnull;
}
Td = data[top];
top--;
size--;
return d;
}
}
使用泛型的时候需要指定具体类型
CommonStack<Float> s2 = new CommonStack<Float>(5);
这个叫类型参数化,也就是将类型作为参数。在此之前我们说的参数都是数据,只不过数据是有类型的。普通参数用“()”,类型参数用“<>”。
6 综合案例——五子棋
6.1 系统分析
面向对象分析方法,站在用户的角度回答两个问题:
(1)系统中应该有什么对象?
a.棋盘
b.棋局:多个棋子构成的某一时刻的状态
c.游戏:系统总控对象,接收用户的操作指令,并按照用户的操作指令控制棋盘和棋局。
(2)每个对象都应该有什么属性和方法?
棋盘
| ||
属性 | rows | 行数 |
cols | 列数 | |
size | 每一个格子的尺寸 | |
margin | 棋盘离窗口的边距 | |
方法 | init | 初始化 |
convert | 将物理坐标(像素)转换为逻辑坐标(行号,列号) | |
getWidth | 获取棋盘的宽度(这个暂时可能考虑不到,在后续过程中才会考虑到这一点,设计也不一定就是一步到位) | |
getHeight | 获取棋盘的高度 |
b.棋局
棋局 | ||
属性 | chesses | 当前棋局状态 方案1:描述每一个棋子的状态 棋子:{行号,列号,颜色},颜色用0、1表示 对应的棋局:[{1,3,1},{2,1,1},{2,2,0}] 方案2:描述每一个落点的状态 落点:0-无子;1-有黑子;-1-有白子 对应的棋局: [ [0,0,0,0,0,0] [0,0,0,1,0,0] [0,1,-1,0,0,0] [0,0,0,0,0,0] [0,0,0,0,0,0] [0,0,0,0,0,0] ] |
turn | 当前轮到谁落子(1-黑方;-1-白方) | |
方法 | init | 初始化 |
exist | 判断某个位置是否有棋子 | |
win | 判断输赢,即是否连成了五个及以上的同颜色棋子 | |
add | 落子 |
c.游戏
游戏 | ||
属性 | board | 棋盘 |
chess | 棋局 | |
|
| |
|
| |
方法 | init | 初始化 |
drawBoard | 绘制棋盘 | |
drawChess | 绘制棋子 | |
handle | 接收并处理用户的落子指令 |
6.2系统设计
a. 棋盘(Board)
考虑到棋盘的属性在游戏过程中是不变的,所以可以将其属性和方法设计成静态的,那么初始化的方法就应该由静态代码块来完成。
转换:Point convert(int x,int y)
增加一个坐标类Point,描述行号和列号
b. 棋局(Chess)
定义常量(0-无子,1-黑子,-1-白子)
初始化: void init(),也可以使用构造函数
判断是否某个落点有棋子:boolean exist(Pointp)
判断输赢:int isWin(Point p)
落子:void add(Point p)
c. 游戏
初始化 void init(),也可以使用构造函数
处理落子指令 void handle(intx, int y)
绘制棋盘void drawBoard()
绘制棋子void drawChess(Pointp,int turn)
浮点型不能直接比较 ,要
集合的三个特点:
1. 集合是用于存储对象的容器
2. 集合的长度是可变的
3. 集合不可以存储基础数据类型值
package 核心编程.集合框架.添加;
import java.util.ArrayList;
import java.util.Collection;
public class Test {
public staticvoid main(String[] args) {
Collection c=new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);
c.add(5);
c.add(6);
c.add(7);
c.add(8);
c.add(9);
c.add(10);
c.add(11);
System.out.println(c.toString());
//集合的添加方法
}
}
package 核心编程.集合框架.添加;
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
public staticvoid main(String[] args) {
Collection c=new ArrayList();
Collection d=new ArrayList();
c.add(1);
c.add(2);
c.add(3);
d.add(5);
d.add(6);
d.add(7);
c.addAll(d);//添加方法
System.out.println(c.toString());;
}
}
删除的三个方法
Remove removeall clear;
判断
判断数组中有没有该数据 contains
package 核心编程.判断;
import java.util.ArrayList;
import java.util.Collection;
public class Test {
public staticvoid main(String[] args) {
Collection c=new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);
System.out.println(c.contains(3));
System.out.println(c.contains(5));
System.out.println(c);// 有-true 没有 -false
}
}
containsAll 判断该集合 是否包含另一个集合的所有数据 有true 没有 false
package 核心编程.判断;
import java.util.ArrayList;
import java.util.Collection;
public class demo {
public staticvoid main(String[] args) {
Collection c = new ArrayList();
Collection d = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);
d.add(1);
d.add(5);
System.out.println(d.isEmpty());
System.out.println(c.containsAll(d));
System.out.println(c);
}
}
isEmpty 判断该集合是否为空
| add | |
| addAll | |
| clear | |
| contains | |
| containsAll | |
| equals | |
| hashCode | |
| isEmpty | |
iterator | ||
| remove | |
| removeAll | |
| retainAll | |
| size | |
| toArray | |
| toArray |
Collection
List:有顺序,元素都有下标,元素内容可以重复
Set:无序的,元素不能重复,不保证存入和取出顺序一致。
Vector:内部是数组数据结构
Arraylist:查询效率高,增删效率低,内部是数组数据结构。
Linkedlist:增删效率高,而查询效率低,内部是链表结构。