内容预览
- 面向对象的思想
- java类的定义格式
- 类的成员变量
- 对象创建的内存图解
- 面向对象的封装性
- 面向对象的继承性
一、面向对象思想
面向对象思想
面向对象思想:万物皆对象。解决某个问题时,我们第一时间想到的应该是某个对象是否具有解决这个问题的功能,如果有就使用这个功能,如果没有,我们就创建一个对象定义这个功能。这就是面向对象的思想。
类和对象
类:类是一个抽象的概念,是对象的模板。把一类事物的共性抽取出来就形成了类。如:男人和女人都具有姓名、年龄的共性,把姓名和年龄抽取出来,就形成了Person类。
对象:实际生活中处处皆对象,如出租车、地铁、笔记本等都是对象。对象是实际存在的事物,也是类的实例化。
面向过程与面向对象的区别
-
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
-
面向对象是把构成问题的事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
这样说也过于抽象,可以拿生活中的实例来理解面向过程与面向对象,例如五子棋
①面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
②如果是面向对象的设计思想来解决问题。整个五子棋可以分为1、黑白双方玩家,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
对象的组成
对象是由属性和方法组成的。类是对象的模板,对象是类的实例化。
-
属性:用来描述具体某个对象的特征,属性是静态的。比如小明身高180M,体重70KG,这里身高、体重都是属性。
-
方法:用来描述某个对象的行为,行为是动态的。比如小明会跑,会说话,跑、说话这些行为就是对象的方法。
对象的特点
对象的特点为封装、继承和多态。后面会有单独的笔记进行总结。
面向过程与面向对象的优缺点
网上发现一篇文章写的特别好,采用蛋炒饭与盖浇饭的例子来说明二者的区别:
用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
蛋炒饭制作的细节可以不用深究,但最后一步肯定是把米饭和鸡蛋混在一起炒匀。而盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
盖浇饭的好处就是"菜"“饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,“饭” 和"菜"的耦合度比较低。蛋炒饭将"蛋”“饭"搅和在一起,想换"蛋”"饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
看了这篇文章,简单的总结一下:
面向过程
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
二、Java类的定义格式
/*
① public 为修饰符,限定访问Person类的权限
② class 关键字在声明类时必须出现
*/
public class Person {
// 声明成员变量
private int id;
private int age;
// 定义成员方法
public void setId(int id) { this.id = id; }
public void setAge(int age) { this.age = age; }
public int getId() { return id; }
public int getAge() { return age; }
}
JAVA里面用class关键字定义一个类,后面加上自定义的类名即可。
如这里定义的person类,使用class person定义了一个person类,然后在person这个类的类体里面定义person这个类应该具有的成员变量(即属性)和方法,如这里定义的int id
和int age
这个两个成员变量,或者叫属性。
id表示人的身份证号码,人应该具有这个属性,age表示人的年龄,这也是人应该具有的。这样就在person这个类里面定义了两个人应该有的属性,
接下来就是定义方法了,这里定义了三个方法,分别是getAge()
、setAge(int i)
和getId()
,分别用来获取人的年龄,设置人的年龄,获取人的id,getAge()
方法获取了人的年龄后,将获取到的值返回,所以使用了return age
语句, getId()
方法也使用了return id
语句用于返回获取到的id的值。
三、对象的组成:成员变量
成员变量概述
在JAVA里面的任何变量首先应该要声明,然后再赋值,然后再使用。
成员变量和局部变量有一个重要区别:成员变量在类里面声明时如果不进行初始化,那么JAVA会默认给它初始化,而局部变量JAVA不会默认给它初始化,所以在方法里面声明一个局部变量如果不给它初始化时就会出错。
成员变量默认初始化大多数都是0,boolean类型的为false,引用类型的为null。如果不记得JAVA对成员变量默认的初始化是多少的话,那就这样做,定义一个成员变量,不给它初始化,然后直接打印这个成员变量,打印出来的结果就是JAVA默认的初始化的值。
成员变量与局部变量的区别
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 类中方法外 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间 或 静态域内 | 栈空间 |
对象的组成:构造方法
在面向对象里面有一个特殊的方法,叫构造方法。
构造方法是用来创建一个新的对象的,与new组合在一起用,使用new+构造方法
创建一个新的对象。你new一个东西的时候,实际上你调用的是一个构造方法,构造方法就是把自己构造成一个新的对象,所以叫构造方法,构造一个新对象用的方法叫构造方法。
构造方法的格式:构造方法与类名同名且没有返回值。
public class Person {
// 声明成员变量
private int id;
private int age;
// 定义无参构造方法 。如果不手动创建构造函数,系统会自动为该类添加一个无参构造方法。
public Person(){ }
// 定义有参构造方法
public Person(int id, int age) {
this.id = id;
this.age = age;
}
}
构造方法写好后就和new组合在一起使用,new的作用是构建一个新对象,创造一个新对象,所以new的时候实际当中调用的是构造方法。只有调用了这个构造方法才能构造出一个新的对象。例如:
public static void main(String[] args) {
Person tom = new Person(1, 25); // 调用person这个构造方法创建一个新的对象,并给这个对象的成员变量赋初始值
Person liming = new Person(); // 使用无参构造方法创建对象
}
注意:
-
声明一个类,若没有在类中指定其构造方法(构造函数)时,编译器会为这个类自动添加形如类名( ){ }的构造函数。(系统自动添加一个空的无参构造函数)
// 定义一个Person类,没有指定构造函数 public class Person { // 声明成员变量 private int id; private int age; // 定义成员方法 public void setId(int id) { this.id = id; } public void setAge(int age) { this.age = age; } public int getId() { return id; } public int getAge() { return age; } } public class PersonTest { public static void main(String[] args) { // 定义一个person01对象,调用系统自动添加的构造函数创建对象 Person person01 = new Person(); // 调用Person类中的方法,为person01对象赋值 person01.setAge(19); person01.setId(110); // 调用Person类中的toString方法,打印输出Person类的实例对象的值 System.out.println(person01.toString()); } }
-
一旦给这个类里面指定了构造方法,那么系统就不会再给这个类添加构造方法了。
-
父类的构造器不可被子类继承
JavaBean
JavaBean的标准:
① 类是公共的
② 有一个无参的公共的无参构造器
③ 属性私有,且有对应的get/set方法
四、对象创建的内存图解
public static void main(String[] args) {
Person tom = new Person(1, 25); // 调用person这个构造方法创建一个新的对象,并给这个对象的成员变量赋初始值
}
当方法调用完成之后,栈里面为它分配的空间全部都要消失,即把这个方法调用时分配给它的内存空间释放出来,所以这个构造方法person调用完成之后,栈内存里面分配的两小块内存_id和_age自动消失了。这样就把它们所占的空间让了出来,让其他的方法去占用。而new出来的对象则永远留在了堆内存里面。
五、对象的特点:封装性
封装的概念
概念:把功能的实现细节隐藏起来,对外只公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅对外暴露少量的方法用于使用。
实现封装的工具:权限修饰符
权限修饰符修饰类的成员
Java权限修饰符public
、protected
、(缺省)
、private
置于类的成员定义前,用来限定对象对该类成员的访问权限。
修饰符 | 本类 | 同一个包的类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
(缺省) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
权限修饰符修饰类
对于class的权限修饰只可以用public和default(缺省)。
- public类可以在任意地方被访问。
- default类只可以被同一个包内部的类访问。
六、对象的特点:继承性
为什么要有继承
多个类中存在相同属性和行为时,将这些内容抽取到单独的一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
- 此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。
- 类继承语法规则:
class Subclass extends SuperClass{ }
继承的作用
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
关于继承的规则
- 子类不能直接访问父类中私有的(private)的成员变量和方法。
- Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
继承后成员变量的特点
子类和父类中的成员变量重名:
- 调用的使用:子类自己有,就使用自己的,子类没有,使用父类
public class Fu{
String s = "父类";
}
public class Zi extends Fu{
String s = "子类";
}
public static void main(String[] args) {
//创建对象,子类对象
Zi zi = new Zi();
//子类对象,调用成员s
System.out.println(zi.s);
}
-
易错点:当成员方法的形参、子类的成员变量、父类的成员变量重名时,如果没有在要调用的成员方法中使用this关键字、super关键字,默认使用形参(就近原则)。
// 父类 public class Person { String name = "父类成员变量的值"; } // 子类 public class Student extends Person { String name = "子类成员变量的值"; public void print(String name){ name = "成员方法的值"; System.out.println(name); } } // 测试类 public class StudentTest { public static void main(String[] args) { Student student = new Student(); student.print(""); // 输出结果:成员方法的值 } }
继承后成员方法的特点
方法重写override:子类父类出现了一模一样的方法,称为子类重写了父类的方法.又称为覆盖或者复写.
调用子类重写的方法,假如子类没有重写,调用父类的方法
public class Fu {
public void print(){
System.out.println("父类方法print");
}
}
public class Zi extends Fu {
/**
* 要重写父类的方法
* 直接复制
*/
public void print(){
System.out.println("子类方法print::重写");
}
}
public static void main(String[] args) {
//创建子类对象
Zi zi = new Zi();
zi.print();
}
方法重写
1、定义:
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
2、要求
① 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
②子类方法抛出的异常不能大于父类被重写方法的异常
③子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
④子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
3、注意
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的。
4、重写方法举例
// 父类
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "
" +"age: "+ age;
}
}
// 子类
public class Student extends Person {
public String school;
//重写方法
public String getInfo() {
return "Name: "+ name + "
age: "+ age
+ "
school: "+ school;
}
// main方法
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
如何判断编译题中的重写是否正确
① 先判断父类、子类中是否发生重写了(可能会发生父子类的重载)
② 判断返回值类型、访问权限修饰符是否正确
class Super {
int show(int a,int b){return 0;}
}
class Demo15 extends Super {
private int show(int a,long b){return 0;}
}
public static void main(String[] args) {
Super s = new Demo15();
System.out.println(s.show(1,1));
}
}
// 输出结果:0
/**
编译可以通过,父子类中发生了重载而不是重写
*/
class Super {
public int get() {
return 4;
}
}
class Demo15 extends Super {
public long get() {
return 5;
}
public static void main(String[] args) {
Super s = new Demo15();
System.out.println(s.get());
}
}
/**
编译不通过,父子类方法重写了,但是子类返回值类型错误
*/