第5章 类与对象

        在本节需要了解面向过程与面向对象的区别,并理解面向对象的主要特点,掌握类与对象定义格式,并且理解引用数据类型的内存分配机制,掌握引用传递的分析方法,并且理解辣鸡的产生原因,掌握private关键字的使用,并且可以理解封装性的主要特点,掌握构造方法的定义要求、主要特点以及相关使用的限制,掌握简单Java类的开发原则,掌握static关键字的使用,并且可以深刻理解static定义成员属性与方法的定义。

面向对象(Object Oriented,OO)是现在最为流行的软件设计与开发方法,Java本身最大的特点就在于其属于面向对象的编程语言,在面向对象之中有两个最为核心的基本成员:类、对象。本章将为读者介绍面向对象程序的主要特点。

5.1 面向对象

面向对象是一种现在最为流行的程序设计方法,现在的程序开发几乎都是以面向对象为基础的。但是在面向对象设计之前,广泛采用的是面向过程,面向过程只是针对自己来解决问题,面向过程的操作是以程序的基本功能实现为主,实现之后就完成了,并不考虑系统地可维护性。面向对象,更多的是要进行子模块化的设计,每一个模块都需要独立存在,并且可以被重复利用,素以,面向对象的开发更像是一个具备标准的开发模式。

提示:关于面向过程与面向对象的区别

        考虑到读者暂时还没掌握面向对象的概念,所以本书先使用一些较为直白的方式帮助读者理解面向对象与面向过程的区别,例如,如果说现在要制造一把手枪,则可以有两种做法。
做法一(面向过程):将制作手枪所需要的材料准备好,由个人负责指定手枪的标准,例如,枪杆长度、扳机设置等,完全只是为一把手枪的规格服务,如果某个零件(例如扳机坏了)需要更换的时候,那么必须首先清楚这把手枪的制造规格,才可以进行生产,所以这种做法没有标准化鱼通用性。
做法二(面向对象):首先由一个技术人员,设计出手枪中各个零件的标准,并且不同的零件交给不同的制造部门,各个部门按照标准生产,最后统一由一个部门进行组装,这样即使一个零件坏掉了,也可以轻易进行维修,这样的设计更加具备通用性与标准模块化的设计要求。

面向对象的程序设计有三个主要特性:封装性、继承性和多态性。

1 封装性

封装是面向对象的方法所应遵循的一个重要原则。封装具有两个含义:一是指把对象的成员属性和行为看成一个密不可分的整体,将这两者封装在一个不可分割的独立单位中,另一层含义是指信息隐蔽,把不需要让外界知道的信息隐藏起来。有些对象的属性及行为允许外界用户知道或使用,但不允许梗概;而另一些属性活性物,则不允许外界知晓,或者只允许使用对象的功能,而尽可能的隐藏对象的功能实现细节。
        封装机制在程序设计中表现为,把描述对象属性的变量与实现对象的方法组合在一起,定义为一个程序结构,并保证外界不能任意梗概其内部的属性值,也不能任意调动其内部的功能方法。
        封装机制的另一个特点是,给封装在一个整体内的变量及方法规定了不同级别的可见性和权限。

2 继承性

继承是面向对象的方法中的重要概念,是提高软件开发效率的重要手段。
        首先继承拥有反映事物一般特性的类:其次在其基础上派生出反映特殊事物的类。如已有汽车的类、在该类中描述了汽车的普遍属性和行为,进一步再产生轿车的类,轿车的类是集成于起着,不禁拥有汽车的类的全部属性和行为,还增加轿车特有的属性和行为。
        在Java程序设计中,对于继承实现前一定要有一些已经存在的类(可以是自定义的类或者由类库提供的类)。用户开发的程序类需要继承这样一些已有的类。这样新定义的类结构可以继承已有类的结构(属性和方法)。被继承的类称为父类或超类,而经过继承产生的类成为子类或派生类。根据继承机制,派生类继承了超类的所有内容,并相应的增加了一些新的成员。
        面向对象程序设计中的继承机制,大大增加了程序代码的可重复利用性,提高了软件开发效率,降低了程序产生错误的可能性。
        若一个子类只允许继承一个父类,称为单继承;若允许继承多个父类,则称为多继承。目前Java不支持多继承。而Java通过接口(interface)的方式来弥补由于java不支持多继承而带来的子类不能享用多个父类的成员的缺憾。

3多态性

多态是面向对象程序设计的又一重要特征。多态是指允许程序中出现重名现象,Java语言中含有方法重载与对像多态两种形式的多态。
方法重载:在一个类中,允许多个方法使用同一个名字,当方法的参数不同,完成的功能也不同。
对象多态:子类对象可以与父类对象进行相互的转换,而且根据其使用子类的不同完成的功能也不同。
多态的特性使程序的抽象程度和简洁程度更高,有助于设计人员对程序分组协同开发。

5.2 类与对象

在面向对象中类和对象是最基本、最重要的组成单元,那么什么叫类呢,类实际上是表示一个客观世界某类群体的一些基本特征抽象,属于抽象的概念集合。而对象呢,就是表示一个个具体的、可以操作的事物,例如,张三同学、李四账户、王五的汽车,这些都是可以真实使用的事物,对标表示一个个独立的个体。
        例如,在线式生活中,人就可以表示一个类,因为人属于一个广义的概念,并不是一个具体个体描述。而某一个具体的人,例如,张三同学,就可以被成为对象,可以通过各种信息完整的描述这个具体的人,如这个人的姓名、年龄、性别等信息,那么这些信息在买你想对象的概念中被称为成员(或成员属性,实际上就是不同数据类型的变量,多一样被策划能够为成员变量),当然人是可以吃饭睡觉的,那么这些人的行为在类中就被称为方法。也就是说,如果要使用一个类,就一定有产生对象,每个对象之间是靠各个属性的不同来区分的,而每个对象所具备的操作就是类中规定好的方法。

5.2.1 类与对象的定义

类是由成员属性和方法组成,成员属性主要定义类的一个具体信息,实际上一个成员属性就是一个变量,而方法是一些操作的行为。但是在程序设计中,定义类也是要按照具体的语法要求来完成的,例如要定义类需要使用class关键字的定义,类的基础语法如下:
class 类名称
{
[访问修饰符] 数据类型 成员属性(变量);
public 返回值的数据类型 方法名称(参数类型 参数1,参数类型 参数2)
{
程序语句
}
}
根据给定的类定义结构可以发现,一个类结构的本质就是相关变量与方法的结合体,下面依据此格式定义一个类。

范例:类的定义

class Person   //String name
{
String name;  //成员属性:人的姓名
int age;   //成员属性:人的年龄
//定义一个信息获取的操作方法,此方法可以输出属性内容
public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}
}
本程序定义了一个Person类,里面有两个成员属性name(姓名,String型)、age(年龄,int型),而后有定义了一个tell()方法,该方法可以输出两个成员属性的内容。
提问:为什么Person类定义的tell()方法没有加上static
在第四章学习方法定义时要求方法前必须加上static,为什么在Person类定义的tell()方法前步迦static
回答:调用形式不同
在第四章讲解方法的时候是这样要求的:在主类中定义,并且由主方法调用的方法必须加上static,但是现在的情况有些改变,因为Person类的tell()方法将会由对象调用,与之前的调用形式不同,所以暂时没有加上。可以这样简单理解:如果是由对象调用的方法定义步迦static,如果不是由对象调用的方法才加上static。

一个类定义完成后并不能够被直接使用,因为类描述的知识一个广义的概念,而具体的操作必须有对象来执行,由于Java引用数据类型,所以对象的定义格式如下:
1 声明并实例化对象类对象 对象名称= new 类名称();
2分布定义 :类名称 对象=null;  度喜爱那个名称= new 类名称();
在Java中引用数据类型是需要进行内存分配的,所以在定义时必须要通过关键字new来分配相对应的内存空间才可以使用,此时该对象也被称为“实例化对象”,而一个实例化对象就可以采用以下的方式进行类结构的操作。
对象.成员属性:表示调用类之中的成员属性,可以为其赋值或者获取其保存内容。
对象.方法(),表示调用类之中的方法。
范例:通过实例化对象进行类操作
class Person{
String name;
int age;

public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}
}
public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person();
per.name="张三";
per.age=18;
per.tell();
}
}
本程序中通过关键字new取得了Person类的实例化对象,当获取到了实例化对象之后就可以为类中的属性赋值,并且实现类中方法的调用。

提示:关于类中成员属性默认值

在第2占中奖结果数据类型的默认值问题,且强调过,方法中定义的变量一定要进行初始化,但是在进行类结构定义时可以不为成员变量赋值,这样就会使用默认值进行初始化。

范例:观察类中成员属性默认值
public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person();
per.tell();
}
}
程序执行结果:
姓名null年龄0
本程序实例化Person类对象之后并没有为成员属性进行赋值,所以在调用tell()方法输出信息时,name内容为null(String类为引用数据类型),age内容为0(int型的默认值)

5.2.2 对象内存分析

Java中类属于引用数据类型,所有的引用数据类型在使用过程中都要通过关键字new开辟新的内存空间,当用户拥有了内存空间后才可以实现成员属性的信息保存,在引用数据类型操作中最为重要的内存有两块
【heap】堆内存:保存的是对象的具体信息(成员属性),在程序之中堆内存空间的开辟是通过new来完成的。
【stack】栈内存:保存的是一块堆内存的地址,即通过地址找到堆内存,而后找到独享内存,为了分析简化起见可以简单地理解为对象名称保存在占内存之中。

提示:关于方法信息的保存

类中所有的成员属性都是每个对象私有的,而类中的对象是所有对象共有的,方法的信息会保存在“全局方法区”这样的公共内存之中。。

程序中每当使用了关键字new都会为制定类型的对象进行堆内存空间的开辟,在堆内存中会保存相应的成员属性信息。这样当对象调用类中方法,会从对象对应的堆内存中获取相应的内容。

范例:类引用数据类型分析

public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person();
per.name="张三";
per.age=18;
per.tell();
}

}
本程序最为重要的内存操作为对象的实例化以及属性赋值操作,内存分配流程如下:
从上图可以看出,实例化对象一定需要对应的内存空间,而内存空间的开辟需要通过关键字new来完成。每一个对象在刚刚实例化之后,里面的所有成员属性的内容都是其对应数据类型的默认值,只有设置了成员属性内容之后,成员属性才可以替换为用户所设置的数据。

提示:关于后续内存的描述
所有的堆内存都会有相应的内存地址,同时栈内存会保存堆内存的地址数值。

在进行对象定义时除了在声明时实例化对象之外,也可以采用先定义对象,在通过关键字new实例化方式完成。

范例:对象实例化处理

public class JavaDemo
{
public static void main(String args[])
{
Person per=null; //声明对象
per=new Person(); //实例化对象
per.name="张三";
per.age=18;
per.tell();
}
}

注意:对象使用前必须进行实例化操作

引用数据类型在使用之前进行实例化才做,如果在开发志宏出现了以下代码,那么会在运行时产生异常;
范例:产生异常地代码
public class JavaDemo
{
public static void main(String args[])
{
Person per=null;
per.name="张三";
per.age=18;
pertell();
}
}

5.2.3 对象引用传递分析

类是一种引用数据类型。而引用数据类型的核心本质是在于堆内存和栈内存的分配与指向处理。在程序开发中,不同的栈内存可以指向同一块的堆内存空间(相当于为同一块堆内存设置不懂的对象名称),这样就信号成了对象的引用传递过程。

提示:引用传递的简单理解

首先所有的读者一定要清楚一件事情:程序来源于生活,只是对生活更抽象的理解。本着这个原则对于对象的引用传递可以换种简单的方式来理解。
        例如,现在有一位逍遥自在的小伙子叫“张麻蛋”,甾醇丽人都叫他的乳名“嘛类子”,江湖都叫他“马子哥”,有一天张麻蛋出去办事不小心撞断了对,导致了骨折,而此时“嘛类子”与“马子哥”一定回顾则,也就是说一个人有多个不同的名字(栈内存不同),但是不同的对象名称可移植性同一个实体(堆内存),这实际上就是引用传递的本质。

范例:引用传递

public class JavaDemo
{
public static void main(String args[])
{
Person per1=new person();
per1.name="张三";
per1.age=18;
Person per2=per1;
per2.age=80;
per1.tell();
}
}

本程序中重要的代码为Person per2=per1;该程序代码的核心意义在于,将per1对象堆内存的地址赋值给per2,这样就相当于两个不同的栈内存都指向了同一块堆内存空间。

在实际的项目开发中,引用传递使用最多的情况是结合方法来使用,既可以通过方法的参数接受引用对象,也可以通过方法返回一个引用对象。

范例:通过方法实现引用传递

public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person();
per.name="张三";
per.age=18;
change(per);
per.tell();
}
public static void change(Person temp)
{
temp.age=80;
}
}
本程序定义了change()方法,并且在方法上接收了Perosn类型的引用对象,遮阳挡通过change()方法的temp对象进行属性修改的时候会影响原始对象内容。

5.2.4 引用传递与垃圾产生分析

引用传递的本质意义在于,一块堆内存空间被不同的栈内存所引用,每一块栈内存都会保存有堆内存的地址信息,并且只允许保存一个堆内存地址的信息,即如果一块栈内存已经存在其他堆内存的引用,当需要改变引用指向时就需要丢弃已有的引用实体更换为新的引用实体。

范例:引用传递与垃圾产生

public class JavaDemo
{
public static void main(String args[])
{
Person per1=new Person();
Person per2=new Person();
per1.name="张三";
per1.age=18;
per2.name="李四";
per2.age=19;
per2=per1;
per2.age=80;
per1.tell();
}
}
本程序实例化了两个Person对象(per1和per2),并且分别为这两个对象进行赋值,但是由于发生了传递引用“per2=per1”,所以per2将丢弃原始的引用实体(产生垃圾),将引用指向per1的实体,遮阳挡执行per2.age=80语句时修改的就是per1对象的内容
通过上图可以发现本程序中的一个问题,per1和per2两个栈内存都各自保存一块堆内存空间指向,而每一块栈内存只能够保留一块堆内存空间的地址。所以当程序发生了引用传递(per2=per1)时,per2首先断开已有的堆内存连接,而后才能够指向新的堆内存(per1所指向的堆内存)。但是由于此时per2原本指向的堆内存空间没有任何栈内存对其进行引用,该内存空间就将成为垃圾空间,所有的垃圾空间将等待GC(Garbage Collection,垃圾收集器)不定期进行回收适当。

提示:虽然Java本身提供了自动垃圾收集机制,但在编码过程中需要尽量避免产生过多的垃圾。

5.3 成员属性封装

面向对象的第一大特性指的就是封装性,而封装性最重要的特点就是内部结构对外不可见。在之前的操作中可以发现所有类中的成员属性都可以通过实例化对象在类的外部进行调用,而这样的调用是不安全的,那么此时最稳妥的做法就是利用private实现成员属性的封装处理。而一旦使用了private封装之后,是不允许外部对象直接访问成员属性的,而此时的访问需要按照Java开发的标准setter()、getter()方法处理
setter(以private String name属性为例):public void setName(String n);
getter(以private String 属性为例):public String getName).
提示:关于完整的封装的定义
封装是第一个面向对象特征,实际上在一个类中不仅可以针对成员属性封装,还可以对方法、内部类实现封装。
严格爱说,封装是程序访问权限的处理操作,在Java中访问控制一共有四种:public、protected、default、private。

范例:使用private实现封装

class Person
{
private String name;
private int age;

public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}

public void setName(String tempName)
{
name=tempName;
}

public void setAge(int tempAge)
{
if(tempAge>0&&tempAge<250)
{
age=tempAge;
}
}
 

public String getName()
{
return name;
}

public int getAge()
{
return age;
}
}

public class JaveDemo
{
public static void main(String args[])
{
Person per=new Person();
per.setName("张三");
per.setAge(-10);
per.tell();
}
}
本程序利用了private关键字实现了Person类中成员属性的封装,这样对于name与age的使用就只能限定在Person类之中。如果外部操作要修改对象成员属性的内容则就必须按照要求通过setter()或getter()方法进行调用。

提问:为什么没有在程序中使用getter()方法
在本程序中定义Person类的时候存在了getName()和getAge()方法,但是发现在程序中并没有使用这两个方法。

回答:虽然在本程序中未被使用,但是确实是一个需要共同遵守的准则。
在类之中的属性定义setter()、getter()方法的目的是为了设置和取得属性的内容,也许某个操作暂时不需要用到getter()操作,大事后续可能会用到。从开发的角度上来讲,必须全部提供。
        对此给出重要的开发原则:在以后定义类的时候,类中所有的普通成员属性都需要通过privat额封装,封装之后的属性需要编写相应的setter()、getter()方法才可以被外部所使用。
        另外,在编写setter()或getter()方法的时候1,需要添加一些逻辑判断。

5.4 构造方法与匿名对象

构造方法是在类中定义的一种特殊方法,它在一个使用关键字new实例化新对象时默认调用,其主要功能是完成对象属性的初始化操作。

提示:关于构造方法的补充说明

在本章开篇就位读者讲解了对象实例化定义语法格式,在这样的格式之中就包含有构造方法的调用,现将此格式拆分:1类名称 2 对象名称= 3new 类名称();
类名称:对象所有的功能必须由类定义,也就是说本操作是告诉程序类所具有的功能。
对象名称:实例化对象的唯一标注,在程序之中利用此标注可以找到一个对象。
new:类属于引用数类型,所以对象的实例化一般需要用new来开辟内存。
类名称():一般只有在方法定义的时候才需要加上“()”,这个就表示调用构造方法。
另外只要定义了构造方法,name类中就会存在构造方法与普通方法两种方法,区别在于:构造方法是在实例化对象的时候使用,普通方法是在实例化对象后使用的。

在Java中,类中构造方法的定义要求如下
构造方法的名称和类名称保持一致。
构造方法不允许有返回值类型声明
由于对象实例化操作一定需要构造方法的存在,所以在此类中没有明确定义构造方法的话,就会自动生产一个无参数并且无返回值的构造方法,也就是说一个类中至少有一个构造方法。

提示:关于默认构造方法
在一个类中至少存在一个构造方法,在之前所编写的程序中没有生命构造方法,那么javac在命令编译程序时会自动为类追加一个无参且无返回值的构造方法。

范例:默认情况下会存在一个无参构造方法

class Person{
pub;ic Person()
{

}
}
正是由于这样的构造方法,所以当实例化对象new Person()才不会提示无参数构造方法的错误信息。

范例:定义构造方法为属性初始化

class Person{
private String nmae;
private int age;
public Person(String tempName,int tempAge)
{
name=tempAge;
age=tempAge;
}
public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}
}
public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person("张三",18);
per.tell();
}
}
本程序在Person类中定义了拥有两个参数的构造方法,并且利用这两个构造方法为类中的name和age属性初始化,这样就可以在Person类对象实例化的时候实现name和age的赋值操作。

提问:关于类中setter()方法的意义
在本程序中通过Person类的有参构造方法在类对象实例化的时候,就饿可以实现name和age属性内容的初始化,这样可以减少setter()方法的调用,已实现简化代码的目的,在这样的情况下继续提供setter()是否有意义
回答:setter()可以实现属性修改功能
setter()除了拥有初始化属性内容的功能之外,也可以试下你修改内容的功能。

构造方法虽然定义形式特殊,但是其本质依然属于方法,所以狗仔方法也可以进行重载。不过构造方法重载的时候只需要考虑参数的类型和个数即可,而方法名称一定要和类名称保持一致。

范例:构造方法重载

class Person{
private String name;
private int age;
public Person()
{
name="1";
age=-1;
}
public Person(String tempName)
{
name=tempName;
}
public Person(String tempName,int tempAge)
{
name=tempName;
age=tempAge;
}

public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}
}
public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person("张三");
per.tell();
}
}
 

本程序类中针对person类的构造方法进行了重载,分别定义了无参构造、单参构造、双参构造,这样在进行对象实例化的时候就可以通过不同的构造方法进行属性初始化内容的设置。

注意:编写顺序:在一个类中对构造函数重载时,所有的重载方法按照参数的个数由多到少活由少到多。

在对象实例化定义格式中,关键字new的主要功能是进行堆内存空间的开辟,而对象的名称是为了对该内存的引用,这样不仅方便使用堆内存同时防止其变为垃圾空间,也就是说对象真正的内容是在堆内存里面,而有了构造方法之后就可以再堆内存开辟的同时进行对象实例化处理,这样即使没有栈内存的只想,该对象也可以使用一次。

范例:使用匿名对象进行类操作

public class JavaDemo
{
public static void main(String args[])
{
new Person("张三",18).tell();
}
}
本程序直接利用new Person("张三",18)语句实例化了一个Person类匿名对象并且直接掉赢了tell()方法,由于没有栈内存的引用指向,所以该对象在使用一次之后就成为了垃圾空间。

提问:该对象该如何定义比较好
现在有了匿名对象和有名对象这两种类型的实例化对象
回答:首先匿名对象的最大特点是使用一次就丢掉了,好比一次性饭盒;而有名对象由于存在引用关系,可以进行反复操作。

在构造方法为属性初始化过程中,除了可以传递一些数据的参数之外,也可以接收引用数据类型。

范例:使用构造方法接收引用数据类型

class Message{
private String info;
public Message(String temoInfo)
{
info=tempInfo;
}
public String getInfo()
{
return info;
}
public void setInfo(String info)
{
this.info=info;
}



}

class Person
{
private String name;
private int age;
public Person(Message msg,int tempAge)
{
name=msg.getInfo();
age=tempAge;
}
public Mesage getMesage()
{
return new Message("姓名"+name+"年龄"+age);
}
public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}
}
public class JavaDemo
{
public static void main(String args[])
{
Person per=new Person(new Message("111"),12);
per.tell();
Mesage msg=per.getMessage();
System.out.println(msg.getInfo());
}
}

本程序实现了Perosn类与Message类两个类的相互引用,在Person类中需要在构造方法中接收Message类对象,并且将Message类重点info属性取出为name属性赋值。

5.5 this关键字

this描述的是本类结构中调用的关键字,在Java中this关键字可以描述3种结构的调用。
当前类中的属性:this.属性
当前类中的方法(普通方法、构造方法):this()、this.方法名称()
描述当前对象。

5.5.1 调用本类的属性

当通过setter()或者是构造方法为类中的成员设置内容时,为了可以清楚地描述出具体参数与成员之间的关系,往往会使用同样地名称,那么此时就需要通过this来描述类的成员属性。

范例:通过"this.成员属性"访问
class Person{
private String name;
private int age;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
public void tell()
{
System.out.println(name+age);
}


}
public classJavaDemo
{
public static void main(String args[])
{
Person per=new Person("张三",12);
per.tell();
}
}
在本程序提供的构造方法中所采用的参数名称与类名称属性完全相同,为了明确标记出操作的是本类成员属性,就需要通过关键字this来设置。

5.5.2 this调用本类方法

在现在所学习的Java内容中,一个类中会有两种方法的定义:普通方法、构造方法,这样在一个类中如果要想实现本类方法的调用,就可以通过this方法来实现。
调用本类普通方法:可以使用this.方法()调用,并且可以在构造方法与普通方法中使用。调用本类构造方法:可以使用this.方法()调用,并且可以在构造方法与普通方法中使用。
调用本来构造方法:调用本来其他构造方法使用this()形式,此语句只允许放在构造方法首行使用。

范例:使用this调用本类普通方法

class Person
{
private String name;
private int age;
public Person(String name,int age)
{
this.setName(name);
setAge(age);
}
public void tell()
{
System.out.println("姓名"+name+"年龄"+age);
}
}
public class JavaDemo
{
publci static void main(String args[])
{
Person per=new Person("张三",12);
per.tell();
}
}
本程序在构造方法中调用了本类中的普通方法,由于是在本类,所以是否使用this没有明确的要求,但是从标准型的角度上来讲还是采用this.方法()形式更加合理。
        当一个类中存在有若干个构造方法时可以使用this()形式实现构造方法之间的相互调用,但该语句只能放在构造方法的首行。

范例:使用this()实现本类构造方法的相互调用

class Person{
private String name;
private int age;
public Person()
{
System.out.println("一个新的Person类对象实例化了");
}
public Person(String name)
{
this();
this.name=name;
}
public Person(String name,int age)
{
this(name);
this.age=age;
}
public void tell()
{
System.out.println("姓名"+this.name+"年龄"+this.age);
}
}
public class JavaDemo
{
public static void mian(String args[])
{
Person per=new Person("张三",12);
per.tell();
}
}
本程序定义了3个构造方法,并且这三个构造方法之间可以进行相互调用,即双参构造调用单参,单参调用无参构造,这样不管调用那个构造方法都可以进行提示信息的输出。

注意:在使用this调用构造方法的时候要注意一下问题:

(1)所有类的构造方法是在对象实例化的时候默认调用的,而且是在调用普通方法之前调用,多以使用this()调用构造方法的操作,一定要放在构造方法的首行。

范例:错误的构造方法调用

class Person
{
private String name;
private int age;
public Person()
{
System.out.println("一个新的Person类对象被实例化");
}

public Person(String name)
{

this();
this.name=name;
}
public Person(String name,int age)
{
this.age=age;
this(name);//会出现错误
}

public String getInfo
{
this();
return "";
}
}

本程序在调用构造方法的时候一个是没有放在构造函数的首行,另外一个是放在了首行,而getInfo()方法不是构造方法,所以程序编译的时候出现了语法错误提示。
(2)如果一个类中存在多个构造方法的话,并且这些构造方法都是用了this()进行想回都阿勇,那么必须要保证一个构造方法没有调用其他构造,以作为程序的出口。

范例:错误的构造方法调用:
class Person{
private String name;
private int age;
public Person()
{
this("1",30);
System.out.println(""一个新的Person对象被实例化);
}
ppublic Person(String name)
{
this();

this.name=name;
}
public Person(String name,int age)
{
this(name);
this.age=age;
}
public String getInfo()
{
return ""+this.name+this.age;
}
}
该程序孙然渡河了构造方法调用时要放在构造方法首行这一要求,但是却出现了构造方法的递归调用,所以此操作依然不正确。

构造方法的相互调用最主要的目的是提升构造方法中执行代码的可重用性,为了更好地说明这个问题,下面通过一个构造方法的案例来说明。现在要求定义一个描述有员工信息的程序类,该类中提供有编号、姓名、部门、工资,在这个类中提供四个构造方法
无参构造:编号定义为1000,姓名定义为无名氏,其他内容为默认值。
单参构造:传递信号,姓名定义为新员工,部门定义为未定,工资0.0
三参构造:传递编号、姓名、部门,工资为2500.00。
四参构造:所有的属性全部进行传递

范例:利用构造方法相互调用实现代码重用

class Emp
{
private long empo;
private String ename;
private String dept;
private double salary;
public Emp()
{
this(1000,"1",null,0.0);
}
public Emp(long empno)
{
this(empno,"1","未定",0.0);
}
publc Emp(long empno,String ename,String dept)
{
this(empno,ename,dept,2500.00);
}
public Emp(long empno,String ename,String dept,double salary)
{
this.empno=empno;
this.ename=ename;
this.dept=dept;
this.salary=salary;
}
public String getInfo()
{
return ""+this.empno+this.ename+this.dept+this.salary;
}
}
public class JavaDemo
{
public static void main(String args[])
{
Emp emp=new Emp(1,"1","财务部",6500);
System.out.println(emp.getInfo());
}
}

5.5.3 this标识当前对象

一个类可以实例化若尬歌对象,这些对象都可以调用类中提供的方法,那么对于当前调用类中的方法的对象可以成为当前对象,而this就可以描述出这种当前对象的概念。

提示:对象输出问题
实际上所有的引用数据类型都是可以打印输出的,默认情况下数除以会出现一个对象的编码信息,这一点在下面的范例可以看见。

范例:观察当前对象

class Message{
public void printThis
{
System.out.println("【Message类】this="+this);
}
}
 

public class JavaDemo
{
public static void main(String args[])
{
Message msgA=new Message();
System.out.println("【主类】msgA="+msgA);
msgA.printThis();
Systemout.println("---------------------------------------");
Message msgB=new Message();
System.out.println("【主类】msgB="+msgB);
msgA.printThis();
}
}

本程序实例化了来帮个Message类的对象,并且分别调用了printThis()方法,通过执行的结果可以发现,类中的this会随着执行对象的不同而表示不同的势力。而对于之前所讲的this.属性这一操作,严格意义来说是调用当前对象的属性。
        为进一步理解this表示当前对象这一概念,下面通过一个简单的模拟程序来进行深入分析。本次实现一个消息发送的处理,在进行消息具体发送的操作之前应该先进行连接的创建,连接创建成功之后才可以进行消息内容的推送。

范例:实现消息发送逻辑

class Message
{
private Channel channel;//保存消息发送通道
private String title;        //消息标题
private String content;          //消息内容

//调用此构造实例化,此时的channel=主类ch

public Messgae(Channel channel,String title,String conbtent)
{
this.channel=channel;//保存消息通道
this.title=title;//设置title属性
this.content=content;//设置content属性
}
public void send()
{
//判断当前通道是否可用,那么此时的this.channel就是主类中的ch
if(this.channel.isConnect())
{
System.out.println("【消息发送】title"+this.title+"content"+this.content);
}else
{
System.out.println("【ERROR】没有课用的连接通道,无法进行消息发送");
}
}
}

class Channel{
private Message message;
//实例化channel类对象,调用构造方法,接收要发送信息的消息标题与信息内容

public Channel(String title,String content)
{
this.message=new Message(this,title,content);
this.message.send();
}

public boolen isConnect()
{
return true;
}
}

public class JavaDemo
{
public static void main(String args[])
{
Channel ch=new Channel("11","111");
}
}

本程序在Channel类的内部实例化了Mesage类对象,由于消息的发送需要通过通道来实现,所以将Channel的当前对象this传递到了Message类,并且利用Message.send()方法实现消息发送处理。

5.5.4综合案例:简单Java类

简单java类是指可以描述某一类信息的程序类,例如,描述个人信息、描述书信息、描述部门信息等,并且在这个类之中并没有特别复杂的逻辑操作,只作为一种信息保存的媒介存在。对于简单的Java类,其核心的开发结构如下:
类名称一定要有意义,可以明确的描述某一类事物。
类中的所有属性都必须使用private进行封装,封装后的属性必须提供setter()|getter()方法。
类中可以提供无数多种构造方法,但是必须保留无参构造方法。
类中不允许出现任何输出语句。所有的内容获取必须返回。
【可选】可以提供一个获取对象详细信息的方法,可以将此方法名称定义为getInfo()

提示:简单Java类的开发很重要

这里学习简单Java类不仅仅是对之前概念的总结,更是使之成为以后项目开发的重要组成部分。每一个读者都必须清楚地几下给出的开发要求,在随后的章节中也将对此概念进行进一步延伸和扩展。
同时,简单Java类也有许多名称,例如,POJO(Plain Ordinary JavaObject,普通Java对象),VO(Value Object,值独享)、PO(Persistent Object,持久化对象),TO(Transfer Object,传输对象),这些类定义结构相似,概念上只有些许区别。

范例:定义一个描述部门的简单Java类

class Dept
{
private long deptno;
private String dname;
private String loc;
public Dept(){}
public Dept(long deptno,String dname,String loc)
{
this.deptno=deptno;
this.dname=dname;
this.lob=loc;
}

public String getInfo()
{
return ""+this.deptno+this.dname+this.iloc;

}
}

public class JavaDemo
{
public static void main()
{
Dept dept=new Dept(10,"1","1");
System.out.println(dept.getInfo());
}
}

5.6 static关键字

static 是一个用于声明程序结构的关键字,此关键字可以用于全局属性和全局当方法的声明,主要特点是可以避免对象实例化,在没有实例化对象的时候直接进行此类结构的访问。

5.6.1 static属性

在一个类中,主要的组成就是属性和方法(分为构造方法与普通方法两种),而每一个对象都分别拥有各自的属性内容(不同对象的属性保存在不同的堆栈中)。如果想要类中的某个属性定义为公共属性(所有对象都可以使用的属性),则可以再声明属性前加上static关键字。

范例:定义static属性(现在假设定义的Person类描述的全部都是中国人的信息)

class Chinese
{
private String name;
private int age;
static String country="中国";
public Chinese(String name,int age)
{
this.name=name;
this.age=age;
}
public String getInfo()
{
return ""+this.name+this.age+this.country;
}
}

public class JavaDemo
{
public static void main(String args[])
{
Chinese perA=new Chinese("1",10);
Chinese perB=new Chinese("2",10);
Chinese perC=new Chinese("3",10);
perA.country="伟大的中国";
System.out.println(perA.getInfo());
System.out.println(perB.getInfo());
System.out.println(perC.getInfo());
}
}

本程序定义了一个描述中国人的泪chinese,类中定义了一个static类型的country属性,这样该属性就成为公共属性,该属性就会保存在全局数据区中,所有的对象都可以获得相同的对象内容,当有个对象改变了static属性内容后将会影响到其他所有对象:

提问:可以不讲country属性定义为static嘛
在本程序中,如果在Chinese类中定义时没有讲country属性设置为static,不是也可以实现相同的效果吗。
回答:使用static才表示公共

首先,在本程序中country是一个公共属性,这时使用static关键字的主要原因。此时在代码中不使用static定义country属性,则每个对象都会拥有该属性。

范例:不使用static定义country属性
class Chinese
{
private String name;
private int age;
String country="中国";
}

由于country属性没有使用static,所以在进行对象实例化操作时,内存关系如下:
static描述的时全局属性,对于全局属性除了乐意利用实例化对象调用外,最大的特点在于可以直接利用类名称并且在没有实例化对象产生的情况下进行调用。

范例:通过类名称直接调用static属性
public class JavaDemo
{
public static void main(String args[])
{
System.out.println("直接访问static属性"+Chinese.country);
Chinese.country="伟大的中国";
Chinese per=new Chinese("张三",10);
System.out.println(per.getInfo());
}
}

本程序在没有产生实例化对象的时候就直接利用了类名称输出和修改static属性的内容,通过本程序可以发现,static虽然定义在类中,但是不收实例化对象的使用限制。

注意:通过本程序,就应该清楚以下几点:
使用static定义的属性内容不在堆内存中保存,而是保存在全局数据区。
使用static定义的属性内容表示类属性,类属性可以由类名称直接进行调用(虽然可以通过实例化对象调用,但是在Java开发模式中)不提倡此类格式
static属性虽然定义在类中,但是其可以在没有实例化对象的时候进行调用(普通属性保存在堆内存中,而static属性保存在全局数据区之中)。
另外需要提醒读者的事,首要选择还是普通属性,而是否需要定义static属性是需要根据书籍的设计条件选择的。

5.6.2 static 定义方法

static除了可以进行属性定义之外,也可以进行方法的定义,一旦使用static定义了方法,那么此方法就可以再没有实例化对象的情况下调用。

范例:定义static方法

class Chinese
{
private String name;
private int age;
private static String country="中国";
public Chinese(String name,int age)
{
this.name=name;
this.age=age;
}

/**
*定义static方法,此方法可以在没有实例化对象的情况下调用
*利用此方法可以修改静态属性country的内容
*static 方法中不允许使用this关键字
*@param c要修改的新内容
*/

public static void setCountry(String c)
{
country=c;
}
public String getInfo()
{
return ""+this.name+this.age+Chinese.country;
}
}
public class JavaDemo
{
public static void main(String args[])
{
Chinese.setCountry("中华人名共和国");
Chinese per=new Chinese("张三",10);
System.out.println(per.getInfo());
}
}

本程序对静态属性country进行了封装处理,这样类的外部将无法直接进行此属性的调用,为了解决country属性的修改问题,所以设置了一个static方法setCountry().由于static定义的方法和属性均不受到实例化对象的限制,这样就可以直接利用类名称进行static方法调用。

注意:关于方法的调用问题

此时类中的普通方法实际上分为了两种:static方法和非static方法,而这两类方法之间的调用也存在着以下的限制。
static定义的方法不能调用非static的方法与属性。
非static定义的方法可以调用static的属性和方法。
以上两点,可以自行编写代码验证,之所以会存在这样的限制,主要原因如下。
使用static定义的属性和方法,可以再没有实例化对象的时候使用(如果没有实例化对象,也就没有了当前对象的this,所以static方法内部无法使用this关键字的原因就在此)
非static定义的属性和方法,必须实例化对象之后才可以进行调用。
在第4章讲解Java方法定义的格式时提出:如果一个方法在主类中定义,并且由主方法直接调用,那么前面必须有public static,即使用以下的格式
格式:在主类中定义,由主方法直接调用的普通方法定义格式。
public static 返回值类型 方法名称(参数列表)
{
return [返回值];
}

范例:观察代码
public class JavaDemo
{
public static void main(String args[])
{
print();
}

public static void print()
{
System.out.println("111");
}
}

按照之前所学习的概念来讲,此时范例表示的是一个static方法调用其他的static方法,但是如果这是print()方法的定义没有使用static呢,则必须使用实例化对象来调用非static方法,即所有的非static方法几乎都有一个特点:方法要由实例化对象调用

范例:实例化本质对象调用非static方法

public class JavaDemo
{
public static void main(String args[])
{
new JavaDemo().print();
}
public void print()
{
System.out.println("111");
}
}

5.6.3 static应用案例

static关键字最为重要的使用特点就是避免实例化对象的限制而直接进行属性或方法的调用,static属性还可以描述公共数据的特点,下面将通过磁特性讲解static属性的使用案例

范例:编写一个程序类,这个类可以实现实例化对象个数的统计,每创建一个新的实例化对象就可以实现一个统计操作。
class Book
{
private String title;
private static int count=0;
public Book(String title)
{
this.title=title;
count++;
System.out.println("第"+count+"本书创造出来");

}

}
public class JavaDemo
{
public static void main(String args[])
{
new Book("Java开发实战经典");
new Book("spring开发实战经典");
new Book("spring微架构开发实战经典");
}
}

在进行对象个数统计的时候,肯定需要一个公共的属性进行个数的保存,所以倴城定义了一个静态属性count,在通过构造方法调用对象实例化过程都会进行个数的累加处理。实际上对于此时的程序只需稍加修改就可以实现一个属性内容的自动命名的处理操作,现在假设Book类中有两个构造方法:无参构造和单参构造,但是要求不管调用哪一个构造函数都可以为title属性设置一个内容,这样既可以通过static属性进行自动命名处理。

范例:实现属性自动命名

class Book
{
private String title;
private static int count=0;
public Book()
{
this.title="11"+count++;
}
public Book(String title)
{
this.title=title;
}

public String getTitle()
{
return title;
}
}

public class JavaDemo
{
public static void main(String args[])
{
System.out.println(new Book("JAV").getTitle());
System.out.println(new Book().getTitle());
System.out.println(new Book("JAVe").getTitle());
System.out.println(new Book().getTitle());
}
}

本程序使用static属性的共享特点,定义了一个对象的计数指针,这样每当调用无参构造方法时都会保证自动设置一个title属性的内容。

提示:在多线程中存在此类操作。

5.7 代码块

代码块是在程序之中使用{}定义起来的一段程序,而根据代码块声明位置以及声明关键字的不同,代码块一共分为四种:普通代码块、静态代码块和同步代码块。

5.7.1 普通代码块

普通代码块是定义在方法中的代码块,利用这类代码块可以解决代码块在一个方法中过长所导致重复变量定义问题:

范例:观察一个程序

public class JavaDemo
{
public static void main(String args[])
{
if(true)
{
int x=10;
System.out.println("x="+x);
}
int x = 100 ;
System.out.println("x="+x);
}
}

本程序在if语句中定义了一个局部变量x,由于{}的作用,所以该变量不会与外部的变量x产生影响

提问:什么叫全局变量,什么叫局部变量;

回答:全局变量和局部变量是一种低昂对行的概念
全局变量和局部变量是针对定义代码的情况而定的,只是一种相对性的概念,例如,在以上的范例中,由于第一个变量x定义在了if语句之中(即定义在了一个{}之中),所以相对于第二个变量x其就成为局部变量,而现在如果有以下的程序代码

范例:说明代码

public class JDemo
{
private static int x = 100 ;
public static void main(String args[])
{
int x=100;
}
}

此程序中,相对于主方法中定义的变量x而言,在类中定义的变量x就成为了全局变量。所以这两个概念时相对而言的。

对于以上案例,如果将if语句取消了,实际上就变为了普通代码块,这样就可以保证两个x不会相互影响。

范例:定义普通代码块

public class JavaDemo
{
public static void main(String args[])
{

{
int x=100;
}
int x=10;
}
}

在本程序中直接使用一个{}定义了一个普通代码块,同样还将一个变量x定义在{}中,不会与全局变量相互影响。使用普通代码块可以将一个方法中的代码进行部分分隔。

5.7.2 构造代码块

将代码块定义在一个类中,这样就成为构造代码块。构造代码块的主要特点是在使用关键字new实例化新对象时调用。

范例:定义构造代码块

class Person
{
public Person()
{
System.out.println("Person类构造方法执行");

}

{
System.out.println("Person类构造块执行");
}
}

public class JavaDemo
{
public static void main(String args[])
{
new Person();
new Person();
}
}

每一次实例化新的对象都会调用构造块,并且构造代码块的执行有限元构造方法的执行

5.7.3 静态代码块

静态代码块也是定义在类中的,如果一个构造代码块上使用了static关键字进行定义的话,那么就表示静态代码块,静态代码块要考虑两种情况:情况1:在非主类中定义的静态代码块。
情况2:在主类中定义的静态代码块。

范例:在非主类中定义的静态代码块:
class Person
{
public Person()
{
System.out.println("构造方法Person类构造方法执行")
}

static
{
System.out.println("静态块Person执行")
}

{
System.out.println("构造块Person执行")
}
}

在本程序中实例化了多个Person类对象,静态代码块优先于构造代码块执行,并且无论实例化多少个对象,静态代码块中的代码只执行一次。

提示:利用静态代码块可以实现一些公共的初始化操作

在实际项目的开发中,由于静态代码块优先于所有程序代码执行,所以可以利用静态代码进行一些初始代码的执行:

范例:利用静态代码块执行初始化代码

class Message{
public static String getCountry()
{
return "lll";
}


}

class Person
{
private static String country;
static
{
country=Message.getCountry();
System.out.println(country);
}
}

pubic class JavaDemo
{
public static void main(String args[])
{
new Person();
}
}

在本程序中利用静态代码执行了部分程序代码,这些代码可以再类第一次使用的时候进行初始化操作,也是在实际开发中使用较多的一种代码结构。

范例:在主类中定义的静态代码块

public class JavaDemo
{
static 
{
System.out.println("111");
}

public static void main(String args[])
{
System.out.println("222")
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值