面向对象思想与应用
面向对象(Object Oriented)是软件开发方法,一种编程范式。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。面向过程类的程序更关注于程序执行的流程,而面向对象的程序更关注程序中数据的应用。
面向对象中所涉及到的对象概念实际上是对任何事物的抽象概述,例如对人类进行抽象概述那就会出现相貌、肤色、性格等一系列通过数据进行描述的内容,并且会存在进食、工作、睡眠等动作流程类的内容。Java可以通过类的方式对人类特征进行描述,并且自由的创建人类的具体实例来描述一个人的具体信息。
相比基本类型,对象类型能描述更多的信息,例如在此前练习中应用到的String类型就是对象类型,对于字符串可以有编码、长度、字符内容等信息,也可以存在大写化、小写化、裁剪等功能。
引用与地址的概念:
基本类型值通常保存在变量中,变量将直接承接值的内容。对象类型因为占用的空间较大,并不会和基本类型放在同一存储空间中,并且因为对象类型较大,也不会直接将内容放到变量里。
通过创建对象类型的实例,将在内存中开辟空间来存储这个实例的信息,并且将实例所占空间的地址抛出。接收此地址的变量也就不再叫做变量,而是叫做引用。引用中存放的是实例在空间中的地址,通过引用可以调取和操作实例中的数据。
类与实例的关系:
内存中的实例要通过类来创建,类中会对一类事物进行抽象的描述,在创建对象后会对类的实际内容进行定义。也就是说类是对象的模板,对象是类的具体实现。
类的概述
例如对狗类进行类描述,那狗类同时具有的名字、年龄、毛色类的值描述就可以定义为类中的变量,而狗类的奔跑和进食类似的动作流程描述就可以使用方法来概括。
类中的变量在创建对象后将作为整个对象中的值所用,也称为属性。类中的方法与普通声明方法的方式类似,但不可以添加static关键字,未来在学习static关键字后就会直到原因了:
public class Dog{
//此类事物拥有的信息用属性来记录
String name;
int age;
String color;
//此类事物拥有的能力用方法来记录
public void run(){
System.out.print("run!");
}
public void eat(){
System.out.print("eat!");
}
}
类中的变量也称为属性,但在创建对象后将存在于实例中。那运行中类中的变量也就称为实例变量,这与局部变量不同。
类中只能存在属性和方法两类成员,在未来学习中还会存在代码块。类的属性一般用来描述事物可以用值来描述的特征,而方法通常用来描述事物会处理的流程和动作。
局部变量与实例变量:
局部变量表示定义在方法或者流程代码块中的变量,生命周期都在创建时到所在代码块结束。而对象中的属性可以在整个对象的任意位置使用,与顺序无关。
对象操作
对象的创建
对象通过new关键字来创建,创建后所生成的引用要放到与对象所对应的引用类型中存储:
new Dog();//这一步将创建一个Dog类的对象
Dog d = new Dog();//创建一个Dog类的对象,然后将地址存放到Dog类型的引用d中
其中new关键字后面的类名和括号将表示创建于哪一个类,并采用哪一种构造方式,构造方法的知识将在未来学到,所以初学对象通常都是用默认的构造方式,也就是以上的写法。
对象的调用
对象是由类创建而来,那类中存在的属性和方法也就在对象中存在。例如上文中所创建的d变量所接收的Dog类型对象,其中就存在名称、年龄、毛色的值,并且具有奔跑和进食的方法。
通过引用就可以操作对象中的实例变量,也能够调用实例中的方法:
对象属性的操作:
//假设已经声明了上文中的Dog类
Dog d = new Dog(); //创建Dog类的对象,赋值给Dog类型的引用d
d.name = "二狗"; //向d所指引的对象地址中的内存中的name属性进行赋值
d.age = 8;
d.color = "骚红";
String name = d.name; //取出d引用所指引的内存下的对象中的name属性的值,赋值给name变量
对象方法的调用:
//假设已经声明了上文中的Dog类
Dog d = new Dog(); //创建Dog类的对象,赋值给Dog类型的引用d
d.run(); //找到d引用地址下的对象中的run方法然后执行!
对象的隔离
new关键字的意义就是创建一个空间,并通过类的定义构建这块空间里具有的内容。每次采用new关键字都会产生新的空间,并且通过引用的控制操作不同地址下的内容:
//借用上文中的Dog类
Dog d1 = new Dog();
Dog d2 = new Dog();
通过不同地址的引用可以操作不同内存中的实例内容,并且不会干扰到其他对象空间的内容:
d1.name = "ergou"; //向d1地址下的对象的name属性赋值
d2.name = "gousheng"; //向d2地址下的对象的name属性赋值
可以打印各引用所指对象内存中的数据,查看是否产生了干扰:
System.out.print(d1.name); //输出d1这个地址中的对象的name属性值
System.out.print(d2.name); //输出d2这个地址中的对象的name属性值
引用也不是不可复制的,判断一下下文中运行打印结果:
Dog d1 = new Dog();
Dog d2 = new Dog();
d2 = d1;
d1.name = "ergou";
d2.name = "gousheng";
System.out.print(d1.name);
System.out.print(d2.name);
上文中d1和d2在开始都创建了自己的空间,并且获得了空间的地址。但d1把自己的地址放到了d2中,那在经历这一步后d1和d2中就存在了同样的对象地址,也就指向了同一个对象。那接下来对d1或者d2的操作都是同一个空间,最终空间的属性name被赋值了一次,又被覆盖了一次。输出的结果应该是两次相同的最后一次赋值。
构造函数
创建对象时,要指定初始化的方法,也就是在new关键字后的类名和括号。实际上这是对一个方法的调用,这个方法的名称就叫类名,没有任何参数。
这种方法在类型并没有声明,是因为每个类中都存在默认的一个与类名相同且没有参数的方法,此方法如果表现出来如下:
public class Dog{
public Dog(){}
}
构造函数的意义就是在创建对象之初对对象进行初始化的设置,每个类中默认的构造函数并没有做任何事情。
构造函数当然也可以自己创建,也可以重载,但书写任何一个构造函数之后,原本默认的构造函数就不再生效。构造函数有以下特点:
- 构造函数没有返回值(连void都不写)。
- 构造函数的方法名必须与类名相同。
- 每个类中都默认存在一个“公开,无参”的构造函数。
- 构造函数是可以重载的!但是,手动创建的构造函数将覆盖默认存在的构造函数。一般情况下,我们会重载构造函数,但是会顺手把无参的构造函数声明出来。
- 构造函数不能使用static修饰。
- 构造方法只会在创建对象的时候调用,不能手动的调用。
现在再去看创建对象时的语句,指明了创建哪个类的对象,并且采用哪个构造函数:
//这一句话指定了两个要点:创建哪个类的对象(Dog),调用哪个构造方法(无参构造)
new Dog();
重载构造函数
可以在类中书写自定义的构造函数,从而在创建时向对象的属性中赋值:
class Dog{
int age;
String name;
String color;
public Dog(int nianling,String xingming,String yanse){
age = nianling;
name = xingming;
color = yanse;
}
}
那在创建Dog的对象的时候,就不能采用默认的构造函数了,因为默认的函数已经在创建了自己的构造函数之后消失了,那创建对象并调用的语句如下:
public class Index{
public static void main(String[] args){
Dog d = new Dog(8,"二狗","骚红");
System.out.print(d.color);
}
}
自定义的构造函数可以创建多个,但在创建了自定义的构造函数之后通常都会将默认的构造函数声明出来,即使没有任何内容也没有调用的位置。因为开发人员创建对象的时候还是更习惯采用默认构造函数。
this关键字
this关键字一般写到类的方法中,在创建对象中,this表示当前对象所处的空间。
指定实例内属性
在类的方法中,允许存在重名的实例变量和局部变量:
public class Dog{
String name;//实例属性
public void myName(String name){//局部变量
System.out.print(name);
}
}
那在使用变量的时候,将采用最近的变量,如上文采用的就是局部变量。
因为所指向的是当前对象地址,所以可以在方法中局部变量与实例变量重名的时候指定实例变量。
所以在自定义的构造函数中,可以如下写法:
class Dog{
int age;
String name;
String color;
public Dog(int age,String name,String color){
this.age = age;
this.name = name;
this.color = color;
}
}
可以在方法参数名和属性名同名的情况下,实现对构造方法赋值语句的编写。
指定实例内方法
和属性一样,this还可以调用当前对象中的方法:
public class Demo{
public static void main(String[] args){
Dog d = new Dog(); //创建了一个Dog类的实例
d.run(); //调用Dog实例的run方法
}
}
class Dog{
String name;
public void run(){
System.out.println("我要跑步!");
this.eat(); //调用当前实例下的(this)eat方法
//eat(); //还可以这样写
System.out.println("我继续跑步!");
}
public void eat(){
System.out.println("我开始吃东西!");
}
}
指定构造函数
this是可以指定当前实例的构造函数的,因为构造函数的调用只能在创建对象的时候使用,所以this对构造函数的调用必须在构造函数中进行。
- this所调用的构造函数不能是身处的构造函数。
- this在进行构造函数调用时,必须处在构造函数的第一行。
- this在调用构造函数时,采用
this(参数表)
的语法。
public class Demo{
public static void main(String[] args){
Dog d = new Dog();
System.out.print(d.name);
}
}
class Dog{
String name;
public Dog(){
this("旺财");
}
public Dog(String name){
this.name = name;
}
}
对象作为属性
在对某一种较为复杂的事物进行描述的时候,它的某个特征可能并不能被任何基本类型描述,而是需要另一种事物来进行描述。例如描述一台电脑时,其中有一个组成部分叫做鼠标,它既不是一个数字也不是一个字符串,而是由品牌、颜色、类型等其他基础数据组成,那就可以将鼠标设计成一个对象,然后将其在作为电脑对象的一个属性来使用。
以下代码将描述一台电脑的信息,电脑将具有键盘和鼠标两个组成部分,以及其他电脑的信息。
首先创建鼠标类,用于描述鼠标类的事物:
class ShuBiao{//描述一个鼠标的类
//鼠标所具有的信息
String pinPai;
double jiaGe;
//鼠标所具有的功能
public void dianJi(){
System.out.println(pinPai+"牌子的鼠标的点击");
}
public void huaDong(){
System.out.println(pinPai+"牌子的鼠标的滑动");
}
}
同理创建键盘类:
class JianPan{//描述一块键盘的类
//鼠标所具有的信息
String pinPai;
double jiaGe;
//键盘所具有的功能
public void daZi(){
System.out.println(pinPai+"牌子的键盘的打字");
}
}
在描述电脑的时候就可以将鼠标和键盘作为属性,当然此时他们应在同一包下,否则要考虑导包:
class DianNao{//描述一台电脑的类
//电脑所具有的信息
String pinPai;
double jiaGe;
ShuBiao shubiao; //此处为鼠标对象的引用
JianPan jianpan; //此处为键盘对象的引用
//电脑所具有的功能:此功能将使用到此电脑的鼠标和键盘
public void bianCheng(){
shubiao.huaDong();
jianpan.daZi();
}
}
进行测试时,就可以将鼠标和键盘实例化,并赋值到电脑实例中:
public class TestPC{//这是一个供测试用的主函数所在的公开类
public static void main(String[] args){
DianNao dn = new DianNao(); //首先创建一个电脑的实例
//创建鼠标的实例,并赋予其相应的信息
ShuBiao s = new ShuBiao();
s.pinPai = "雷蛇";
s.jiaGe = 123.0;
dn.shubiao = s; //将此鼠标放到dn电脑上
//创建键盘的实例,并赋予其相应的信息
JianPan j = new JianPan();
j.pinPai = "罗技";
j.jiaGe = 200.0;
dn.jianpan = j; //将此键盘放到dn电脑上
dn.bianCheng(); //使用电脑的编程方法(此方法将使用到鼠标和键盘中的功能)
//查看dn电脑的鼠标的牌子是什么
String p = dn.shubiao.pinPai;
System.out.print(p);
}
}
对象构建过程
在执行new语句之后,将执行三步后将对象完全构建完成:
- 给所有属性在内存中分配空间,赋值为默认值。数字的默认值为0,Boolean类型为false,char类型为数字类型的0,所有对象类型都是null。
- 给属性赋值为初始值,初始值也就是在属性声明时直接赋值。
- 调用构造函数,构造函数中还可能对属性第三次赋值。
第一步是在内存中自动完成的,所以并无法监测。但第二步和第三步可以借助一个辅助类,来查看执行顺序:
public class Test{
public Test(String msg){
System.out.println(msg);
}
}
然后声明需要创建对象的类,并应用辅助类:
public class Dog{
Test test = new Test("in Test");//属性初始化
public Dog(){
System.out.println("in Dog");
}
}
在创建Dog类对象的时候,可以观察控制台输入内容,来判断属性的初始化与构造函数的启动的先后顺序。
值传递与引用传递
上文中也说到,引用中的地址也不是不可以复制的,可以将当前引用中的地址赋值给其他引用,那因为两个引用指向了同一块空间,所以相互之间能够获取到对方的操作。
但基本类型变量中存储的就是值的内容,在复制给其他变量的时候就是将值复制了一份出去,在其他变量进行修改的时候并不会修改到原来变量中的内容。
以下习题表现了值传递和引用传递中的区别:
public class Index{
public static void main(String[] args){
Year y = new Year();
//测试变量的传递
int a = 10;
int a2 = y.add(a);
int a3 = y.add(a);
int a4 = y.add(a);
System.out.println(a);//10
//测试引用的传递
Dog d = new Dog();
d.setAge(10);
Dog d2 = y.add(d);
Dog d3 = y.add(d);
Dog d4 = y.add(d);
System.out.println(d); //13
System.out.println(d2); //13
System.out.println(d3); //13
System.out.println(d4); //13
}
}
class Year{
public Dog add(Dog d){
int newAge = d.getAge()+1;
d.setAge(newAge);
return d;
}
public int add(int a){
a+=1;
return a;
}
}
class Dog{
private int age;
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}