2

类和对象

定义类

面向对象的程序设计过程中有两个重要概念:类(class)和对象(object,也被称为实例,instance),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。


[修饰符]    class    类名
{
    零个到多个构造器定义...
    零个到多个成员变量...
    零个到多个方法...
}

修饰符可以是public、final、abstract或者完全忽略。

如果从程序的可读性方面来看,Java类名必须由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。

构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例。如果程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器。一旦程序员为一个类提供了构造器,系统将不再为该类提供构造器。

定义成员变量

static修饰的成员不能访问没有static修饰的成员。

成员变量用于定义该类或该类的实例所包含的状态数据,方法则用于定义该类或该类的实例的行为特征或者功能实现。构造器用于构造该类的实例,Java语言通过new关键字来调用构造器,从而返回该类的实例。

[修饰符]    类型    成员变量名 [=默认值]

修饰符:public、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰成员变量。

类型:基本类型和引用类型

成员变量名:如果从程序的可读性方面来看,Java类名必须由一个或多个有意义的单词连缀而成的,第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。

默认值:定义成员变量还可以指定一个可选的默认值。

定义方法

[修饰符]    方法返回值类型    方法名(形参列表)
{
    //由零条到多条可执行性语句组成的方法体
}

修饰符:public、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰成员变量。

方法返回值类型:基本类型和引用类型如果声明了方法返回值类型,则方法体内必须与此处声明的类型匹配。如果一个方法没有返回值,则必须使用void来声明没有返回值。

形参列表:形参列表用于定义该方法可以接受的参数,形参列表由零组到多组“参数类型 形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开。

static

static是一个特殊的关键字,可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,因为通常把static修饰的成员变量和方法也称为类变量、类方法。不使用static修饰的普通方法、成员变量则属于该类的单个实例,而不属于该类。因为通常把不使用static修饰的成员变量和方法也称为实例变量、实例方法。

static修饰的成员变量和方法称为静态变量和静态方法,把不使用static修饰的成员变量和方法称为非静态变量和非静态方法。静态方法不能直接访问非静态成员。

有static修饰的成员属于类本身,没有static修饰的成员属于该类的实例。

构造器

构造器是一个特殊的方法,定义构造器的语法格式与定义方法的语法格式很像,定义构造器的语法格式如下:

[修饰符]    构造器名(形参列表)
{
    //由零条到多条可执行性语句组成的构造器执行体
}

修饰符:public、protected、private
构造器名:构造器名必须和类同名
形参列表:和定义方法形参列表的格式完全相同。
构造器既不能定义返回值类型,也不能使用void声明构造器没有返回值。

对象的产生和使用

static修饰的方法和成员变量,既可通过类来调用,也可通过实例来调用;没有使用static修饰的普通方法和成员变量,只可通过实例来调用。

对象、引用和指针

变量实例实际上是一个引用,它被存放在栈内存里,指向实际的类对象;而真正的类对象则存放在堆内存中。栈内存里的引用变量并未真正存储对象的成员变量,对象的成员变量数据实际存放在堆内存里;而引用变量只是指向该堆内存里的对象。

对象的this引用

this关键字总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认引用有两种情形。

  1. 构造器中引用该构造器正在初始化的对象。

  2. 在方法中引用调用该方法的对象。

对于static修饰的方法而言,则可以使用类来直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以,static修饰的方法中不能使用this引用。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员。

普通方法访问其他方法、成员变量时无须使用this前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。this引用也可以用于构造器中作为默认引用,由于构造器是直接使用new关键字来调用,而不是使用对象来调用的,所以this在构造器中代表该构造器正在初始化的对象。

方法详解

方法的所属性

Java语言里方法的所属性主要体现在如下几个方面:

  1. 方法不能独立完成,方法只能在类体里定义。

  2. 从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象。

  3. 永远不能独立执行方法,执行方法必须使用类或对象作为调用者。

同一个类的一个方法调用另一个方法时,如果被调用方法是普通方法,则默认使用this作为调用者;如果被调方法是静态方法,则默认方法类作为调用者。

方法的参数传递机制

声明方法时包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际传给形参的参数值也被称为实参。

形参个数可变的方法

Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。

public statci void test(int a, String ... books)

数组形式的形参可以处于形参列表的任意位置,但个数可变的形参只能处于形参列表的最后。也就是说一个方法中最多只能有一个长度可变的形参。

递归方法

一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无限循环控制。

希望遍历某个路径下的所有文件,但这个路径下文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个方法,该方法接受一个文件路径作为参数,该方法可遍历当前路径下的所有文件和文件路径——该方法中再次调用该方法本身来处理该路径下的所有文件路径。

方法重载

Java允许同一个类里定义多个同名方法,只有形参列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。

方法重载三个因素:

  1. 调用者,也就是方法的所属者,既可以是类,也可以是对象。

  2. 方法名,方法的标识。

  3. 形参列表,当调用方法时,系统将会根据传入的实参列表匹配。

两同一不同:同一个类中方法名相同,参数列表不同。方法返回值类型、修饰符等,与方法重载没有任何关系。

成员变量和局部变量

成员变量

clipboard.png

成员变量指的是类里定义的变量,也就是前面所介绍的field;局部变量指的是方法里定义的变量。

成员变量:一个类不能定义两个同名的成员变量。

  1. static修饰的类变量。类变量从该类的准备阶段起开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同;而实例变量则从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例变量的作用域与对应实例的生存范围相同。

  2. 成员变量无须显式初始化,只要为一个类定义了类变量或实例变量,系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化,成员变量默认初始化时的赋值规则与数组动态初始化时数组元素的赋值规则完全相同。

局部变量:一个方法里不能定义两个同名的方法局部变量,方法局部变量与形参也不能同名;同一个方法中不同代码块内的代码块局部变量可以同名;如果先定义代码块局部变量,后定义方法局部变量,前面定义的代码块局部变量与后面定义的方法局部变量也可以同名。

  1. 形参(方法签名中定义的变量) 作用域在整个方法内有效 必须显式初始化

  2. 方法局部变量(在方法内定义) 作用域从定义该变量的地方生效,到该方法结束时失效 必须显式初始化

  3. 代码块局部变量(在代码块内定义) 作用域从定义该变量的地方生效,到该代码块结束时失效 无须显式初始化

Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。

成员变量的初始化和内存中的运行机制

当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。

局部变量的初始化和内存中的运行机制

局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。

变量的使用规则

定义一个成员变量时,成员变量将被放置到堆内存中,成员变量的作用域将扩大到类存在范围或者对象存在范围,这种范围的扩大有两个害处。

  1. 增大了变量的生存时间,这将导致更大的内存开销。

  2. 扩大了变量的作用域,这不利于提供程序的内聚性。

    应该考虑使用成员变量的情况:

  3. 如果需要定义的变量是用于描述某个类或某个对象的固有信息。

  4. 如果在某个类中需要以一个变量来保存该类或者实例运行时的状态信息。

  5. 如果某个信息需要在某个类的多个方法之间进行共享,则这个信息应该使用成员变量来保存。

即使在程序中使用局部变量,也应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间就越短,程序运行性能就越好。因此,能用代码块局部变量的地方,就坚决不要使用方法局部变量。

隐藏和封装

理解封装

封装指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,二世通过该类所提供的方法来实现对内部信息的操作和访问。

对一个类或对象实现良好的封装,可以实现以下目的。
隐藏类的实现细节

  1. 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对成员变量的不合理访问。

  2. 可进行数据检查,从而有利于保证对象信息的完整性。

  3. 便于修改,提高代码的可维护性。

为了实现良好的封装,需要从两个方面考虑:

  1. 将对象的成员变量和实现细节隐藏起来,不允许外部直接访问。

  2. 把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作。

使用访问控制符

private → default → protected → public
访问控制级别由小到大

  • private(当前类访问权限):使用它来修饰成员变量就可以把成员变量隐藏在该类的内部。

  • default(包访问权限):defaulte访问控制的成员或外部类可以被相同包下的其他类访问。

  • protected(子类访问权限):成员既可以被同一包中的其他类访问,也可以被不同包中的子类访问。通常,如果使用protected来修饰一个方法,是希望其子类来重写这个方法。

  • public(公共访问权限):成员或外部类可以被所以类访问,不管访问类和被访问类是否处于同一包中,是否具有父子继承关系。

             private    default    protected    public
 同一个类中      √          √           √          √
 同一个包中                 √           √          √
 子类中                                 √          √
 全局范围内                                        √

外部类只能有两种访问控制级别:public和default,外部类不能用private和protected修饰,因为外部类没有处于任何类内部,也就没有其所在类的内部、所在类的子类的两个范围。

如果一个Java类的每个实例变量都被使用private修饰,并为每个实例变量都提供了public修饰setter和getter方法,那么这个类就是一个符号JavaBean规范的类。因此,JavaBean总是一个封装良好的类。

进行程序设计时,应尽量避免一个模块直接操作和访问另一个模块的数据,模块设计追求高内聚(尽可能把模块的内部数据、功能实现细节隐藏在模块内部独立完成,不允许外部直接干预)、低耦合(仅暴露少量的方法给外部使用)。

访问控制符的使用的基本原则:

  • 类里的绝大部分成员变量都应该使用private,只有一些static修饰的、类似全局变量的成员变量,才可能考虑使用public修饰。除此之外,有些方法只用于辅助实现该类的其他方法,这些方法被称为工具方法,工具方法也应该使用private修饰。

  • 如果某个类主要用作其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰这些方法。

  • 希望暴露出来给其他类自由调用的方法应该使用public修饰。因此,类的构造器通过public修饰,从而允许在其他地方创建该类的实例。因为外部类通常都希望被其他类自由使用,所以大部分外部类都使用public修饰。

package、import和import static

如果希望把一个类放到指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:

package packageName;

一旦在Java源文件中使用了这个package语句,就意味着该源文件里定义的所有类都属于这个包。位于包中的每个类的完整类名都应该是包名和类名的组合,如果其他人需要使用该包下的类,也应该使用包名加类名的组合。

同一个包中的类不必位于相同的目录下,例如有leePerson和LeePersonTest两个类,它们完全可以一个位于C盘下某个位置,一个位于D盘下某个位置,只要让CLASSPATH环境变量里包含这两个路径即可。虚拟机会自动搜索CLASSPATH下的子路径,把它们当成同一个包下的类来处理。也应该把Java源文件放在与包名一致的目录结构下。通常建议将源文件和class文件分开存放,以便管理。

clipboard.png

从可读性规范角度来看,包名应该全部是小写字母,而且应该由一个或多个有意义的单词连缀而成。

package语句必须作为源文件的第一条非注释性语句,一个源文件只能指定一个包,即只能包含一条package语句,该源文件中可以定义多个类,则这些类将全部位于该包下。如果没有显式指定package语言,则处于默认包下。

同一个包下的类可以自由访问,无须添加包前缀。

父包和子包之间确实表示了某种内在的逻辑关系。但父包和子包在用法上不存在任何关系,如果父包中的类需要使用子包中的类,则必须使用子包的全名,而不能省略父包部分。

//调用构造器时需要在构造器前添加包前缀
lee.sub.Apple a  = new lee.sub.Apple();

import关键字

import可以向某个Java文件中导入指定包层次下某个类或全部类,import语句应该出现在package语句之后(如果有)、类定义之前。一个Java源文件只能包含一个package语句,但可以包含多个import语句,多个import语句用于导入多个包层次下的类。java.lang包下的所有类默认导入。

//使用import语句导入单个类
import    package.subpackage...ClassName;
//使用import语句导入指定包下全部
import    package.subpackage.*

星号()只能代表类,不能代表包。因此使用import.lee.;语句时,它表明导入lee包下的所有类,而lee包下sub子包内的类则不会被导入。如需导入lee.sub.Apple类,则可以使用impor.lee.sub.*;语句来导入lee.sub包下的所有类。

一旦在Java源文件中使用import语句来导入指定类,在该源文件中使用这些类时就可以省略包前缀,不再需要使用类全名。

静态导入import static

import static package.subpackage..ClassName.fieldName|methodName;
field:静态成员变量;methodName:静态方法。
使用import可以省略写包名;使用import static则可以连类名都省略。

Java源文件的大致结构:

package语句                                                    //0个或1个,必须放在文件开始
import | import static 语句                                    //0个或多个,必须放在所有类定义之前
public classDefinition | interfaceDefinition | enumDefinition  //0个或1个public类、接口或枚举定义
classDefinition | interfaceDefinition | enumDefinition         //0个或多个普通类、接口或枚举定义

Java的常用包

Java.lang:核心类,如String、Math、System和thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。
java.util:Java的大量工具类/接口和集合框架类/接口,例如Arrays和List、Set等
java.net:一些Java网络编程相关的类/接口
java.io:一些Java输入/输出编程相关的类/接口
java.text:一些Java格式化相关的类
java.sql:Java进行JDBC数据库编程的想相关类/接口
java.awt:抽象窗口工具集的相关类/接口,这些类主要用于构建图形用户界面GUI程序
java.swing:Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序


布still
461 声望32 粉丝

数据挖掘、用户行为研究、用户画像