Java™ 教程(类的更多方面)

类的更多方面

本节介绍依赖于使用对象引用的类的更多方面以及你在前面的对象部分中了解到的点运算符。java

从方法返回值

方法返回到调用它的代码。程序员

  • 完成方法中的全部语句。
  • 到达return语句。
  • 或抛出异常(稍后介绍)。

以先发生者为准。编程

你在方法声明中声明方法的返回类型,在方法体内,使用return语句返回值。segmentfault

声明为void的任何方法都不返回值,它不须要包含return语句,但它可能会这样作,在这种状况下,可使用return语句分支出控制流程块并退出该方法,而且能够像这样使用:数组

return;

若是你尝试从声明为void的方法返回值,则会出现编译器错误。app

任何未声明为void的方法都必须包含带有相应返回值的return语句,以下所示:编程语言

return returnValue;

返回值的数据类型必须与方法声明的返回类型匹配,你不能从声明为返回布尔值的方法返回一个整数值。函数

在对象部分中讨论的Rectangle类中的getArea()方法返回一个整数:this

// a method for computing the area of the rectangle
public int getArea() {
   return width * height;
}

此方法返回表达式width * height求值的整数。spa

getArea方法返回基本类型,方法还能够返回引用类型,例如,在一个操做Bicycle对象的程序中,咱们可能有这样的方法:

public Bicycle seeWhosFastest(Bicycle myBike, Bicycle yourBike,
                              Environment env) {
    Bicycle fastest;
    // code to calculate which bike is 
    // faster, given each bike's gear 
    // and cadence and given the 
    // environment (terrain and wind)
    return fastest;
}

返回类或接口

若是此部分让你感到困惑,请跳过它并在完成接口和继承课程后返回该部分。

当一个方法使用类名做为其返回类型时,例如whosFastest,返回对象的类型类必须是返回类型的子类或确切的类。假设你有一个类层次结构,其中ImaginaryNumberjava.lang.Number的子类,而java.lang.Number又是Object的子类,以下图所示。

classes-hierarchy.gif

如今假设你有一个声明为返回Number的方法:

public Number returnANumber() {
    ...
}

returnANumber方法能够返回ImaginaryNumber而不是ObjectImaginaryNumber是一个Number,由于它是Number的子类,可是,Object不必定是Number — 它能够是String或其余类型。

你能够重写方法并定义它以返回原始子类的方法,以下所示:

public ImaginaryNumber returnANumber() {
    ...
}

这种称为协变返回类型的技术意味着容许返回类型在与子类相同的方向上变化。

注意:你还可使用接口名称做为返回类型,在这种状况下,返回的对象必须实现指定的接口。

使用this关键字

在实例方法或构造函数中,这是对当前对象的引用 — 正在调用其方法或构造函数的对象,你可使用此方法从实例方法或构造函数中引用当前对象的任何成员。

将this与字段一块儿使用

使用this关键字的最多见缘由是由于字段被方法或构造函数参数遮蔽。

例如,Point类就是这样写的:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

但它多是这样写的:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

构造函数的每一个参数都会影响对象的一个​​字段 — 构造函数内部的x是构造函数的第一个参数的本地副本,要引用Point字段x,构造函数必须使用this.x

将this与构造函数一块儿使用

在构造函数中,你还可使用this关键字来调用同一个类中的另外一个构造函数,这样作称为显式构造函数调用,这是另外一个Rectangle类,其实现与对象部分中的实现不一样。

public class Rectangle {
    private int x, y;
    private int width, height;
        
    public Rectangle() {
        this(0, 0, 1, 1);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    ...
}

该类包含一组构造函数,每一个构造函数初始化一些或全部矩形的成员变量,构造函数为任何成员变量提供默认值,其初始值不是由参数提供的。例如,无参数构造函数在坐标0,0处建立1x1矩形。双参数构造函数调用四参数构造函数,传递宽度和高度,但始终使用0,0坐标,和以前同样,编译器根据参数的数量和类型肯定要调用的构造函数。

若是存在,则另外一个构造函数的调用必须是构造函数中的第一行。

控制对类成员的访问

访问级别修饰符肯定其余类是否可使用特定字段或调用特定方法,访问控制有两个级别:

  • 在顶级 — publicpackage-private(没有显式修饰符)。
  • 在成员级别 — publicprivateprotectedpackage-private(无显式修饰符)。

可使用修饰符public声明一个类,在这种状况下,该类对于全部类均可见,若是一个类没有修饰符(默认,也称为包私有),它只在本身的包中可见(包是相关类的命名组 — 你将在后面的课程中了解它们)。

在成员级别,你也可使用public修饰符或无修饰符(package-private),就像使用顶级类同样,而且具备相同的含义。对于成员,还有两个额外的访问修饰符:privateprotectedprivate修饰符指定只能在其本身的类中访问该成员,protected修饰符指定只能在其本身的包中访问该成员(与package-private同样),此外,还能够在另外一个包中经过其类的子类访问该成员。

下表显示了每一个修饰符容许的成员访问权限。

修饰符 子类 全部
public Y Y Y Y
protected Y Y Y N
无修饰符 Y Y N N
private Y N N N

第一个数据列指示类自己是否能够访问由访问级别定义的成员,如你所见,类始终能够访问本身的成员,第二列指示与该类相同的包中的类(无论父子关系)能够访问该成员,第三列指示在此包外声明的该类的子类是否能够访问该成员,第四列指示是否全部类均可以访问该成员。

访问级别以两种方式影响你,首先,当你使用来自其余源的类(例如Java平台中的类)时,访问级别将肯定你本身的类可使用的那些类的哪些成员,其次,当你编写一个类时,你须要肯定每一个成员变量和类中的每一个方法应具备的访问级别。

让咱们看一下类的集合,看看访问级别如何影响可见性,下图显示了此示例中的四个类以及它们之间的关系。

classes-access.gif

下表显示了Alpha类的成员对于可应用于它们的每一个访问修饰符的可见性。

修饰符 Alpha Beta Alphasub Gamma
public Y Y Y Y
protected Y Y Y N
无修饰符 Y Y N N
private Y N N N

选择访问级别的提示:

若是其余程序员使用你的类,你但愿确保不会发生滥用错误,访问级别能够帮助你执行此操做。

  • 使用对特定成员有意义的最严格的访问级别,除非你有充分的理由不使用private
  • 避免除常量以外的public字段(本教程中的许多示例都使用public字段,这可能有助于简明地说明一些要点,但不建议用于生产代码),public字段倾向于将你连接到特定实现,并限制你更改代码的灵活性。

了解类成员

在本节中,咱们将讨论使用static关键字建立属于类的字段和方法,而不是类的实例。

类变量

当从同一个类蓝图建立许多对象时,它们每一个都有本身不一样的实例变量副本,在Bicycle类的状况下,实例变量是cadencegearspeed,每一个Bicycle对象都有这些变量本身的值,这些变量存储在不一样的内存位置。

有时,你但愿拥有全部对象共有的变量,这是经过static修饰符完成的,在声明中具备static修饰符的字段称为静态字段或类变量,它们与类相关联,而不是与任何对象相关联,该类的每一个实例共享一个类变量,该变量位于内存中的一个固定位置,任何对象均可以更改类变量的值,但也能够在不建立类实例的状况下操做类变量。

例如,假设你要建立多个Bicycle对象并为每一个对象分配一个序列号,从第一个对象开始为1,此ID号对于每一个对象都是惟一的,所以是一个实例变量。同时,你须要一个字段来跟踪已建立的Bicycle对象的数量,以便你知道要分配给下一个对象的ID,这样的字段与任何单个对象无关,而与整个类有关,为此,你须要一个类变量numberOfBicycles,以下所示:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
        
    // add an instance variable for the object ID
    private int id;
    
    // add a class variable for the
    // number of Bicycle objects instantiated
    private static int numberOfBicycles = 0;
        ...
}

类变量由类名自己引用,如:

Bicycle.numberOfBicycles

这清楚地代表它们是类变量。

注意:你也可使用对象引用来引用静态字段 myBike.numberOfBicycles,但这是不鼓励的,由于它没有说明它们是类变量。

你可使用Bicycle构造函数来设置id实例变量并增长numberOfBicycles类变量:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
    private int id;
    private static int numberOfBicycles = 0;
        
    public Bicycle(int startCadence, int startSpeed, int startGear){
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;

        // increment number of Bicycles
        // and assign ID number
        id = ++numberOfBicycles;
    }

    // new method to return the ID instance variable
    public int getID() {
        return id;
    }
        ...
}

类方法

Java编程语言支持静态方法以及静态变量,静态方法在其声明中具备static修饰符,应该使用类名调用,而不须要建立类的实例,如:

ClassName.methodName(args)
注意:你也可使用对象引用来引用静态方法 instanceName.methodName(args),但这是不鼓励的,由于它没有说明它们是类方法。

静态方法的常见用途是访问静态字段,例如,咱们能够向Bicycle类添加一个静态方法来访问numberOfBicycles静态字段:

public static int getNumberOfBicycles() {
    return numberOfBicycles;
}

并不是全部实例和类变量和方法的组合都是容许的:

  • 实例方法能够直接访问实例变量和实例方法。
  • 实例方法能够直接访问类变量和类方法。
  • 类方法能够直接访问类变量和类方法。
  • 类方法不能直接访问实例变量或实例方法 — 它们必须使用对象引用,此外,类方法不能使用this关键字,由于没有要引用的实例。

常量

static修饰符与final修饰符结合使用,也用于定义常量,final修饰符表示此字段的值不能更改。

例如,如下变量声明定义了一个名为PI的常量,其值是pi的近似值(圆周长与直径之比):

static final double PI = 3.141592653589793;

以这种方式定义的常量不能从新分配,若是你的程序尝试这样作,则它是编译时错误,按照惯例,常量值的名称拼写为大写字母,若是名称由多个单词组成,则单词由下划线(_)分隔。

注意:若是将基本类型或字符串定义为常量而且该值在编译时已知,则编译器会将代码中的常量名称替换为其值,这称为编译时常量。若是外部世界中常量的值发生变化(例如,若是立法规定 pi实际上应该是 3.975),则须要从新编译使用此常量来获取当前值的任何类。

Bicycle类

在本节中进行了全部修改以后,Bicycle类如今是:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
        
    private int id;
    
    private static int numberOfBicycles = 0;

        
    public Bicycle(int startCadence,
                   int startSpeed,
                   int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;

        id = ++numberOfBicycles;
    }

    public int getID() {
        return id;
    }

    public static int getNumberOfBicycles() {
        return numberOfBicycles;
    }

    public int getCadence() {
        return cadence;
    }
        
    public void setCadence(int newValue) {
        cadence = newValue;
    }
        
    public int getGear(){
        return gear;
    }
        
    public void setGear(int newValue) {
        gear = newValue;
    }
        
    public int getSpeed() {
        return speed;
    }
        
    public void applyBrake(int decrement) {
        speed -= decrement;
    }
        
    public void speedUp(int increment) {
        speed += increment;
    }
}

初始化字段

如你所见,你一般能够在其声明中为字段提供初始值:

public class BedAndBreakfast {

    // initialize to 10
    public static int capacity = 10;

    // initialize to false
    private boolean full = false;
}

当初始化值可用而且初始化能够放在一行上时,这颇有效,然而,这种形式的初始化因为其简单性而具备局限性,若是初始化须要一些逻辑(例如,错误处理或for循环来填充复杂数组),则简单的赋值是不合适的。实例变量能够在构造函数中初始化,其中可使用错误处理或其余逻辑,为了为类变量提供相同的功能,Java编程语言包括静态初始化块。

注意:没有必要在类定义的开头声明字段,尽管这是最多见的作法,只有在使用它们以前才须要声明和初始化它们。

静态初始化块

静态初始化块是用大括号{}括起来的常规代码块,前面是static关键字,这是一个例子:

static {
    // whatever code is needed for initialization goes here
}

一个类能够有任意数量的静态初始化块,它们能够出如今类体中的任何位置,运行时系统保证按照它们在源代码中出现的顺序调用静态初始化块。

还有静态块的替代方法 — 你能够编写私有静态方法:

class Whatever {
    public static varType myVar = initializeClassVariable();
        
    private static varType initializeClassVariable() {

        // initialization code goes here
    }
}

私有静态方法的优势是,若是须要从新初始化类变量,能够在之后重用它们。

初始化实例成员

一般,你可使用代码在构造函数中初始化实例变量,使用构造函数初始化实例变量有两种选择:初始化块和final方法。

实例变量的初始化程序块看起来就像静态初始化程序块,但没有static关键字:

{
    // whatever code is needed for initialization goes here
}

Java编译器将初始化程序块复制到每一个构造函数中,所以,该方法可用于在多个构造函数之间共享代码块。

没法在子类中重写final方法,这在接口和继承的课程中讨论,如下是使用final方法初始化实例变量的示例:

class Whatever {
    private varType myVar = initializeInstanceVariable();
        
    protected final varType initializeInstanceVariable() {

        // initialization code goes here
    }
}

若是子类可能想要重用初始化方法,这尤为有用,该方法是final,由于在实例初始化期间调用非final方法可能会致使问题。

建立和使用类和对象的总结

类声明为类命名,并将类主体括在大括号之间,类名能够在前面加上修饰符,类主体包含类的字段、方法和构造函数,类使用字段来包含状态信息,并使用方法来实现行为,初始化类的新实例的构造函数使用类的名称,看起来像没有返回类型的方法。

你能够经过相同的方式控制对类和成员的访问:在声明中使用诸如public之类的访问修饰符。

你能够经过在成员声明中使用static关键字来指定类变量或类方法,未声明为static的成员隐式地是实例成员,类变量由类的全部实例共享,能够经过类名和实例引用来访问,类的实例获取每一个实例变量的本身的副本,必须经过实例引用访问它们。

你可使用new运算符和构造函数从类建立对象,new运算符返回对已建立对象的引用,你能够将引用分配给变量或直接使用它。

能够经过使用限定名称来引用可在其声明的类以外的代码访问的实例变量和方法,实例变量的限定名称以下所示:

objectReference.variableName

方法的限定名称以下所示:

objectReference.methodName(argumentList)

或者:

objectReference.methodName()

垃圾收集器自动清理未使用的对象,若是程序再也不包含对它的引用,则不使用该对象,你能够经过将包含引用的变量设置为null来显式删除引用。


上一篇: 对象

下一篇:嵌套类

相关文章
相关标签/搜索