(2021.11.23 Tues)
限定符
-
public
,protected
,private
: 也叫access modifier,用于控制对类、方法和变量的访问 -
static
: 用于创建类方法和类变量 -
final
: 用于固定(finalise)类、方法和变量的实现,i.e., 常量之类 -
abstract
: 用于创建抽象类和方法(抽象基类) -
synchronized
和volatile
: 用于线程
限定符用于语句的最前面,如
public class RedButton extends javax.swing.Jbutton {
// ...
}
private boolean offline;
static final double WEEKS = 9.5;
public static void main(String[] arguments) {
// ...
}
控制对方法和变量的访问
public
, protected
, private
这些限定符决定了类中的哪些方法和变量对其他类是可见的。
通过访问控制,可以控制其他类如何使用您的类。类中的有些方法和变量只能在该类中使用。应对与该类进行交互的其他类隐藏他们。这被称为封装:对象控制外部世界对它的了解程度以及如何与它进行交互。
封装防止类中的变量被其他类读取或修改。只能通过调用该类的方法类使用这些变量。
Java提供四种级别的访问控制:公有、私有、受保护和默认(不适用访问控制限定符)。
1. 默认
声明变量和方法时不使用任何限定符
String version = "0.7a";
boolean processOrder() {
// ...
return true;
}
变量:同一个包中的其他任何类都可读取或修改它。
方法:同一个包中的其他任何类都可以调用它。
除此之外,其他任何类都不能以任何方式访问这些元素。
2. 私有
private
限定符:将方法或变量完全隐藏起来,不被其他任何类使用。这种变量或方法只在其所在的类中是可见的,被所属类中的方法使用或调用。任何私有变量或方法不能被子类继承。
发生下面两种情况,私有方法有用:
- 其他类没有理由使用该变量
- 其他类以不合适的方式修改该变量将带来严重后果
class Logger {
private String format;
// 通过一个getFormat存储器方法可使外部访问私有变量
public String getFormat() {
return this.format;
}
public void setFormat(String fmt) {
if ((fmt.equals('common')) || (fmt.equals('combined')) ) {
this.format = fmt;
}
}
}
3. 公有
(2021.11.23 Tues)
类中的方法或变量可供任何类使用,可声明其为公有public
。如果变量为公有,其他类可使用它。调用方法为
class_name.var_name
if (yard < 0 ) {
System.out.println('Touchdown!');
score = score + Football.TOUCHDOWN; // Football类中的公有变量TOUCHDOWN
}
应用程序的main()
方法必须是公有的,否则Java虚拟机(JVM)将不能调用它,以运行该程序。
由于类的继承性,所有的公有方法和变量都将被子类继承。
4. 保护
有的方法和变量仅供下面两种情况访问:
- 子类
- 同一个包中的其他类
这两种情况下可使用protected
限定符,如下
protected boolean outOfData = true;
对比保护访问控制和默认访问控制:被保护的变量可被子类使用,即使子类和超类位于不同的包中。
各类访问控制对比
可见性 | public | protected | default | private |
---|---|---|---|---|
同一个类中 | Y | Y | Y | Y |
同一个包中的任何类中 | Y | Y | Y | N |
包外的任何类 | Y | N | N | N |
同一包中的子类 | Y | Y | Y | N |
包外的子类 | Y | Y | N | N |
访问控制的继承
一个通用的规则,覆盖方法时,新方法的访问控制不能比原来的方法更严格,但可以更松。或者说,子类中,方法的可见性不能低于它覆盖的方法的可见性。一些继承的规则:
- 超类中被声明为公有的方法,在子类中必须也是公有的
- 超类中被声明为保护的方法,在子类中可以是公有或者保护,但不能为私有
- 对于没有访问控制的方法,即默认方法,子类中其访问控制可以更严格
- 超类中被声明为私有的方法不能被继承,因此上述规则不适用
存储器方法
对于声明为私有的变量,外部无法访问。可让其他类通过存储器方法来访问该私有变量。即在类中定义一个方法,该方法的内容仅仅是返回这个私有变量。一般读取方法的名称以get
开头,设置方法的名称以set
开头。如果访问的变量是boolean
变量,存储器方法以is
开头。如
private int zipCode;
public int getZipCode() {
return zipCode;
}
private boolean empty;
public boolean isEmpty() {
return empty;
}
静态变量和方法
限定符static
用于创建类方法和类变量。
访问类变量和类方法,可使用类名和变量(方法)名,并用句点将他们连接,如Colour.black
和Circle.pi
,也可以使用类的对象名,但对于类变量和类方法而言,使用类名更好,这样变量和方法的类型将更清晰。对于实例变量和实例方法,根本不能通过类名来引用。
下面这个例子,在构建函数中有自加操作,使得每次创建类时类变量都加1,创建n个实例,则最后一次创建之后类变量值为n。
public class InstanceCounter {
private static int numInstances = 0;
protected static int getCount() {
return numInstances;
}
private static void addInstance() {
numInstances++;
}
InstanceCounter() {
InstanceCounter.addInstance();
}
public static void main(String[] arguments) {
System.out.println("starting with " + InstanceCounter.getCount() + " objects");
for (int i = 0; i < 500; ++i) {
new InstanceCounter();
}
System.out.println('Created " + InstanceCounter.getCount() + " objects");
}
}
final类、方法和变量
(2021.11.24 Wed)
变量
关键字final
标注的变量被称为常量,它们的值不会被修改。变量的final
和static
限定符往往一起使用,这样变量就可以被声明为类变量。对于保持不变的值,没有理由让每个对象都存储这个值的一个拷贝,应将其声明为类变量。
publish static final int TOUCHDOWN = 6;
static final String TITLE = 'caption';
方法
被标记为final
的方法不能被子类覆盖。
public final void getSignature() {
//
}
将方法声明为final
最常见的原因是提高类的运行效率。通常,当JVM运行方法时,首先在当前类中查找该方法,接下来在其超类中查找,并一直沿着层次结构向上查找,直到找到该方法。提供了灵活性,简化了开发工作,但代价是速度降低(?)。如果方法是声明为final
时,编译器便可将其可执行字节码直接放到调用它的程序中,因为该方法不能被子类覆盖而发生变化。
注意,私有方法是final
的,无须显式的声明,因为不可能在子类中覆盖他们。
类
类限定符声明为final
的,该类将不能被继承。
public final class ChatServer {
//
}
创建final
类时,final
不能出现在关键字extends
后面。与final
方法一样,以降低灵活性为代价,提高了语言的速度。
在final
类中,所有方法都是final
的,无须声明,直接使用。
抽象类和方法
类的层次越高,抽象程度越高。位于类层次顶部的类只能定义所有类都有的行为和属性。
定义类层次的过程中,当您确定通用的行为和属性时,有时可能遇到一些永远都不会被实例化的类,这样的类用于定义其子类都有的行为和方法。这些类被称为抽象类,使用限定符abstract
来声明。
public abstract class xxx {
//
}
包
包是组织类的方式。包中可以包含任意多的类,这些类可能有关或者有继承关系。
程序小,使用的类少,就不需要用包。随着程序量增大,组织成包的好处就凸显。
包的出现应对了下面几种情况
- 包能将类组织成单元。在硬盘中,您通过文件夹来组织文件和应用程序,而包让用户能够将类编组,让每个程序只是用所需的类
- 包减少名称冲突带来的问题,随着Java类数量增长,类重名的可能性增加。当同一个程序中使用多组类时,可能发生名称冲突,导致错误。包能够引用所需的类,即使这个类与另一个包中的类同名
- 包可以大面积的保护类、变量和方法,而不是分别对每个类进行保护
- 包可以用于唯一的标识类
当使用import
指令或者通过全名来引用类时比如java.util.StringTokenizer
,都使用了包。
三种机制使用包中的类
-
仅类名:如果要使用的类位于包
java.lang
中,比如System
,Date
,可以通过类名来引用,java.lang
包中的类将自动导入到所有的程序中,无须事先使用import
声明。 -
全名:如果要使用的类位于其他包中,可以通过全名(包括包名)来引用,如
java.awt.Font
。
java.awt.Font text = new java.awt.Font();
- 导入:对于频繁使用的类,可以导入单个类或整个包。类或包被导入后,只需通过名称便可引用响应的类。
对于只使用一两次的类,使用全名可能更合适。如果需要使用某个类多次,应将其导入减少输入量,可使用import
关键字导入。
import声明
(2021.11.25 Thur)
import
声明,可用于导入
- 单个类
import java.util.ArrayList;
- 包中的所有类,用星号(*)代替类名
import java.awt.*;
- 导入类中的常量,常与
static
联合使用。import static
后面跟一个接口(类)的名称和星号。
import static java.lang.Math.*;
public class ShortConstants {
public static void main(String[] arguments) {
System.out.println('PI: ' + PI);
System.out.println("" + (PI * 3));
}
}
除了这种引用,还可以直接用<类名.变量名>的格式,如Math.PI
,Color.black
等。
创建自己的包
命名
约定1:Oracle推荐用户使用受自己控制的Internet域名来给包命名,并将域名的各部分颠倒过来。如个人域名是xxxx.org
,则所有包应以org.xxxx
开头,如org.xxxx.news
,org.xxxx.fin
等。这个规则可以确保其他Java人员不会提供同名的包,如果他们也这么做。
约定2:包名不使用大写字母,以便将其与类名区别开来。如内置类String
的全名是java.lang.String
,从中容易区分包名和类名。
创建文件夹结构
如包org.xxxx.news
,先创建文件夹org
,在其中再创建文件夹xxxx
,在其中创建文件夹news
。之后便可以在news
文件夹中创建所需要的类。
将类加入到包中
创建包的最后一个步骤是将类加入到包中。在类文件的import
语句和类声明之前添加一条package
语句
package org.xxxx.news;
package
声明必须在源代码文件的第一行,注释和空行除外。
包和类访问控制
前面提到的用于方法和变量的访问控制限定符,也可以控制对类的访问。
如没有指定限定符,则类的访问控制为默认级别,即可被同一个包中的其他任何类使用,但在包外不可见,也不可用。
通过包保护的类被隐藏其所在的包中,不能通过名称来导入或引用。
要让类在包外可见可导入,可在其定义中加入public
,使之称为公有。
注意,在import
语句中使用星号(*)时,导入的只有包中的公有类,私有类仍被隐藏,只能被包中的其他类使用。
接口
接口和抽象类和抽象方法一样,提供了其他类要实现的行为模板。他们对Java的单继承面向对象编程方法提供了有益的补充。
单继承存在的问题
多重继承,使得类可以继承多个超类,从而获得全部超类的行为和属性。使用多重继承后,方法调用以及如何组织类层次的问题将复杂的多,让人迷惑,带来了多义性。而Java没有多重继承,采用的是更简单的单继承。
Java接口是一种抽象行为,可以被混合到任何类中,从而给它添加超类不支持的行为。接口只包含抽象方法定义和常量,既没有实例变量,也没有方法实现。
在Java类库中,期望很多完全不同的类都实现某种行为时,都将实现和使用接口。后面将使用一个接口,java.lang.Comparable
。
接口和类
(*注:接口需要重新学习, 2021.11.27 Sat)
接口与类概念不同,但有很多相同的地方。接口也是在源文件中声明的,并被编译为.class
文件;多数情况下,可以使用类的地方,也可以使用接口。
这里的示例中,计划都可以将类名替换为接口名。我们常说的Java类,常常指的是“类或接口”。但是接口不能被实例化:new
只能创建非抽象类的实例。
实现和使用接口
在类中使用接口,定义关键字implements
public class AnimatedSign extends Sign
implements Runnable {
//
}
接口Runnable
扩展了Sign
的子类AnimatedSign
的行为。
实现多个接口
用逗号分开
public class AnimatedSign extends Sign
implements Runnable, Observer {
//
}
如果两个接口定义了相同的方法,可以采用下面三种方式来解决这个问题
- 如果两个方法的特征标相同,可以在类中实现一个方法。其定义能够满足两个接口
- 如果方法的参数列表不同,是一种简单的方法重载:实现两种方法特征标,分别满足各自的接口定义
- 如果方法的参数列表相同,但返回值不同,则无法创建一个能够满足两个接口的方法。
接口的其他用途
可以使用类的任何地方,都可使用接口代替。可以将变量的类型声明为接口
Iterator loop;
当变量的类型被声明为接口时,只能存储实现了该接口的对象。
创建和扩展接口
新接口
创建新接口,这样声明
interface Expandable {
//
}
声明和类定义相同, 只是使用的关键是是interface
而非class
,接口定义内是方法和变量。
接口内的方法定义是(默认)公有和抽象的,可以显式的声明这一点,如果没有包括这些限定符,将自动转换为公有和抽象。不能再接口内将方法声明为私有或保护的。
下面的Expandable
接口有两个方法,其中的一个expand()
被显式的声明为公有和抽象的,另一个contract()
被隐式的声明为公有和抽象的。
public interface Expandable {
public abstract void expand (); // explicitly public and abstract
void contract(); // effectively public and abstract
}
与类中的抽象方法一样,接口中的方法也没有方法体。接口只包含方法的特征标,不涉及任何实现。
接口中的方法
(2021.11.27 Sat)
接口中的方法默认是公有和抽象。考虑到可以在任何能够使用雷鸣的地方使用接口名,通过将方法参数定义为接口类型,可以创建通用参数,适用于可能使用接口的任何类。
来看接口Trackable
,定义了方法track()
和quitTracking()
,都不带任何参数。另一个方法beginTracking()
,接收一个参数,即Trackable
对象。在接口中将参数声明为Trackable
。
public interface Trackable {
public abstract Trackable beginTracking(Trackable self);
}
在类中实现该方法时,接受通用的Trackable
参数,并将它强制转换为相应的对象
public class Monitor implements Trackable {
public Trackable beginTracking(Trackable self) {
Monitor mon = (Monitor) self;
//
return mon;
}
}
扩展接口
和类相同,接口可以继承另一个接口,子接口将获得父接口中生命的所有方法定义和常量。扩展的关键字为extend
,像扩展类一样
interface PreciselyTrackable extends Trackable {
//
}
与类不同,接口层次中没有像Object
类那样的“根”接口。接口可独立存在,也可继承其他接口。
不同于类层次,接口层次可以多重继承。在定义的extends
部分,任意数量的接口用逗号隔开,新接口将包含其所有父类接口中的方法和常量。
接口管理中,管理方法名冲突的规则与使用多个接口的类相同。多个只有返回值不同的方法将导致编译错误。
接口与抽象类的对比
placeholder
Reference
1 R. Cadenhead著,袁国忠译,21天学通Java(第7版),中国工信出版集团,人民邮电出版社
网友评论