前言
本部分继续前一部分,将继续总结关于 static 关键字、this 关键字、包、访问权限、基本类型的类包装、反编译和文档生成器、JAR 文件的相关内容。
基本知识
五、static 关键字
前文已经提到,用 static 修饰的变量是类变量。
除构造方法外,其他的方法可以分为实例方法和类方法。方法声明中用 static 修饰的称作类方法或静态方法,不用 static 修饰的称作实例方法。
同一个类中的方法可以互相调用,实例方法可以调用该类中的实例方法或类方法;类方法只能调用该类的类方法,不能调用实例方法。
1.实例变量和类变量的区别
(1)已知不同的对象的实例变量将被分配不同的内存空间;而若类中有类变量,则所有对象的这个类变量都分配给相同的一处内存,改变其中一个对象的这个类变量会影响其他对象的这个类变量,即对象共享类变量。
(2)当 Java 程序执行时,若该类没有创建对象,类中的实例变量不会被分配内存;但是,类中的类变量在该类被加载到内存时,就分配了相应的内存空间。
(3)类变量是与类相关联的数据变量,改变了其中一个对象的这个类变量就同时改变了其他对象的这个类变量;实例变量仅是和相应的对象关联的变量,即不同对象的实例变量互不相同,即分配不同的内存空间,改变其中一个对象的实例变量不会影响其他对象的这个实例变量。
下面的例子中梯形对象共享一个下底。
class 梯形
{ float 上底,高;
static float 下底; //类变量
梯形(float 上底,float 高)
{ this.上底=上底;
this.高=高;
}
float 获取上底()
{ return 上底;
}
float 获取下底()
{ return 下底;
}
}
class Example4_7
{ public static void main(String[] args)
{ 梯形 laderOne,laderTwo; //梯形的字节码被加载到内存
梯形.下底=60; //通过类名操作类变量
laderOne=new 梯形(18.0f,20);
laderTwo=new 梯形(9.0f,10);
System.out.println("laderOne的上底:"+laderOne.获取上底());
System.out.println("laderTwo的下底:"+laderTwo.获取下底());
System.out.println("laderOne的上底:"+laderOne.获取上底());
System.out.println("laderTwo的下底:"+laderTwo.获取下底());
}
}
运行结果:
laderOne 的上底:18.0
laderOne 的下底:60.0
laderTwo 的上底:9.0
laderTwo 的下底:60.0
2.实例方法和类方法的区别
用 static 修饰的是类方法,不用 static 修饰的为实例方法。这两种方法在对象创建后都可以使用“.”运算符调用这些方法。
(1)当类的字节码文件被加载到内存时,类中的实例方法不会被分配入口地址,创建对象后才会分配;实例方法可以被类创建的任何对象调用执行。但注意方法的入口地址被所有的对象共享。当所有的对象都不存在时,方法的入口地址才被取消。
(2)在类被加载到内存时,类中的类方法就被分配了相应的入口地址,直到程序退出时才被取消。因此 Java 允许通过类名直接调用类方法。
(3)若一个方法不需要操作实例成员变量就可以实现某种功能,那么就可以考虑将这样的方法声明为类方法。这样做可以避免创建对象浪费内存。
下面例子中 A 类的 getContinueSum 方法是类方法,求1+2+3+……+100的和:
class A
{ int x,y,z;
static int getContinueSum(int start,int end)
{ int sum=0;
for(int i=start;i<=end;i++)
{ sum=sum+i;
}
return sum;
}
}
public class Example4_8
{ public static void main(String[] args)
{ int result=A.getContinueSum(0,100);
System.out.println(result);
}
}
六、this 关键字
this 是 Java 的一个关键字,表示某个对象。this 可以出现在实例方法和构造方法中,但不可以出现在类方法中。当 this 关键字出现在类的构造方法中时,代表使用该构造方法所创建的对象。
由于实例方法必须通过对象来调用,当 this 关键字出现在类的实例方法中时,代表正在调用该方法的当前对象。
1.实例方法可以操作类的成员变量,当实例成员变量在实例方法中出现时,默认格式如下:
this.成员变量
当 static 成员变量在实例方法中出现时,默认格式如下:
类名.成员变量;
如:
class A
{ int x;
static int y;
void f()
{ this.x=100;
A.y=200;
}
}
上述 A 类中的实例方法 f 中出现了 this,this 代表使用 f 的当前对象。所以,“this.x” 就表示当对象调用方法 f 时,将 100 赋给该对象的变量 x。因此当一个对象调用方法时,方法中的实例成员变量是指分配给该对象的实例成员变量,而 static 变量和其他对象共享。因此,通常情况下,可以省略实例成员变量名字前面的“this.”以及 static 变量前面的“类名.”。
如:
class A
{ int x;
static int y;
void f()
{ x=100;
y=200;
}
}
但当实例成员变量的名字和局部变量的名字相同时,成员变量前面的“this.”或“类名.”就不可以省略。
2.类的实例方法可以调用类的其他方法,对于实例方法调用的默认格式如下:
this.方法;
对于类方法调用的默认格式如下:
类名.方法;
如:
class B
{ void f()
{ this.g();
B.h();
}
void g()
{ System.out.println("ok");
}
static void h()
{ System.out.println("hello");
}
}
上述 B 类中的方法 f 出现了 this,this 代表使用方法 f 的当前对象。所以,方法 f 的方法体中的 this.g()就是当前对象调用方法 g,也就是说,在某个对象调用方法 f 的过程中,也调用了方法 g。由于这种逻辑关系非常明确,一个实例方法调用另一个方法时可以省略方法名字前面的“this.”或“类名”。
class B
{ void f()
{ g();
h();
}
void g()
{ System.out.println("ok");
}
static void h()
{ System.out.println("hello");
}
}
注意: this 不能出现在类方法中,这是因为类方法可以通过类名直接调用,此时可能还没有任何对象诞生。
七、包
包是 Java 语言中有效地管理类的一个机制。不同 Java 源文件中可能出现名字相同的类,如果想区别这些类,就需要使用包名。包名的目的是有效地区分名字相同的类,不同 Java 源文件中两个类相同时,它们可以通过隶属不同的包来相互区分。
1.包语句
通过关键字 package 声明包语句。package 语句作为 Java 源文件的第一条语句,指明该源文件定义的类所在的包,即为该源文件中声明的类指定包名。package 语句的一般格式如下:
package 包名;
如果源程序省略了 package 语句,源文件中所定义命名的类被隐含地认为是无名包的一部分。即源文件中定义命名的类在同一个包中,该包中的类没有包名。
包名可以是一个合法的标识符,也可以是若干个标识符加“.”分割而成,如:
package sunrise;
package sun.com.cn;
程序如果使用了包语句,如:
package tom.jiafei;
那么用户的目录结构必须包含有如下的结构:
……\tom\jiafei
如:
c:\1000\tom\jiafei
并且要将源文件保存在目录c:\1000\tom\jiafei中,然后编译源文件,编译源文件的语句如下:
c:\1000\tom\jiafei\javac 源文件
或
javac:\1000\tom\jiafei\源文件
包名的目的是有效地区分名字相同的类,在区分包名时用户可以根据用户的项目的范围来决定用户的包名。
注意:Java 语言不允许用户使用 Java 作为包名的第一部分,例如 java.jia 是非法的包名。
2.import 语句
(1)有包名的类
使用 import 语句可以引入包中的类。一个 Java 源程序中可以有多个 import 语句,它们必须写在 package 语句和源文件中类的定义之间。Java 为用户提供了 130 多个包,如:
java.applet //包含所有的实现 Java Applet 的类
java.awt //包含抽象窗口工具集中的图形、文本、窗口 GUI 类
java.awt.image //包含抽象窗口工具集中的图像处理类
java.lang //包含所有的基本语言类
java.io //包含所有的输入输出类
java.net //包含所有实现网络功能的类
java.until //包含有用的数据类型类
如果要引入一个包中的全部类,则可以用星号来代替,如:
import java.awt.*;
表示引入 java.awt 包中所有的类,而以下语句只是引入 java.until 包中的 Date 类。
import java.until.Date;
下面的例子中,使用 import 语句引入了 java.applet 包中所有的类
import java.applet.Applet;
import java.awt.*;
public class Example4_10 extends Applet
{ Button redbutton;
public void init()
{ redbutton=new Button("一个按钮");
add(redbutton);
}
}
注意:java.lang 包是 Java 语言的核心类库,它包含了运行 Java 程序必不可少的系统类,系统自动为程序引入 java.lang 包中的类,因此,不需要再使用 import 语句引入该包中的类。如果使用 import 语句引入了整个包中的类,那么可能会增加编译时间,但绝不会影响程序运行的性能,因为当程序执行时,只是将用户真正使用的类的字节码文件加载到内存。
Trangle.java
package tom.jiafei;
public class Trangle
{ double sideA,sideB,sideC;
boolean boo;
public Trangle(double a,double b,double c)
{ sideA=a;sideB=b;sideC=c;
if(a+b>c&&a+c>b&&c+b>a)
{ boo=true;
}
else
{ boo=false;
}
}
public void 计算面积()
{ if(boo)
{ double p=(sideA+sideB+sideC)/2.0;
double area=Math.sqrt(p*(p-sideA)*(p-sideB)*(p-sideC);
System.out.println("是一个三角形,面积是:"+area);
}
else
{ System.out.println("不是一个三角形,不能计算面积");
}
}
public void 修改三边()
{ sideA=a;sideB=b;sideC=c;
if(a+b>c&&a+c>b&&c+b>a)
{ boo=true;
}
else
{ boo=false;
}
}
}
Example4_11.java
import tom.jiafei.*; //引入包中的类
public class Example4_11
{ public static void main(String[] args)
{ Trangle trangle=new Trangle(12,-3,100);
trangle.计算面积();
trangle.修改三边(3,4,5);
trangle.计算面积();
}
}
(2)无包名的类
如果一个源文件要使用无名包中的类,要保证源文件和被使用的类在同一目录中。
下面的例中,A.java 省略了包语句,Example4_11.java 和 A.java 存放在同一目录中 D:\3000\中。首先编译 A.java,然后编译、运行 Example4_11.java。
A.java
public class A
{ public void hello()
{ System.out.println("你好");
}
}
Example4_12.java
public class Example4_12
{ public static void main(String[] args)
{ A a=new A();
a.hello();
}
}
(3)避免类名混淆
当用户在一个源文件中使用一个类时,只要不引起混淆,就可以省略该类的包名。但在某些特殊情况下就不能省略包名。
i): 区分无包名和有包名的类。
ii): 区分有包名的类。
3.静态导入
一个类的类变量和类方法不仅可以通过该类创建的对象访问,也可以直接通过类名访问。静态导入语句的语法与 import 语句类似。其功能是引入类中的类变量和类方法,语法格式如下:
import static 包名.类名.*;
或
import static 包名.类名.类变量名字;
import static 包名.类名.类方法名字;
静态导入只能导入有包名的类的类变量和类方法。
A.java
package tom.jiagei;
public class A
{ public static int MAX=100;
public static void f(int x)
{ for(int i=1;i<=x;i++)
System.out.println(i);
}
}
Example4_13.java
import static tom.jiafei.A.*; //静态导入
public class Example4_13
{ public static void main(String[] args) //直接使用 A 类的类变量
{ System.out.println(MAX);
f(100); //直接使用 A 类的类方法
}
}
八、访问权限
所谓访问权限就是指对象是否可以通过“.”运算符操作自己的变量,或通过“.”运算符使用类中的方法。访问限制修饰符有 private、protected 和 public,它们都是 Java 的关键字,用于修饰成员变量或方法。
一个类中的实例方法总是可以操作该类中的实例变量和类变量;
类方法总是可以操作该类中的类变量,与以上的访问限制符无关。
1.私有变量和私有方法
用 private 修饰的成员变量和方法称为私有变量和私有方法。如:
class Tom
{ private float weight; //weight是private的float型变量
private float f(float a,float b) //方法f是private方法
{ return a+b;
}
}
当在另外一个类中用类 Tom 创建了一个对象后,该对象不能访问自己的私有变量和私有方法。如:
class Jerry
{ void g()
{ Tom cat=new Tom();
cat.weight=23f; //非法
float sum=cat.f(3,4); //非法
}
}
在另一个类中不能通过类名 Tom 来操作这个私有类变量的情况:
(1)Tom 类中的某个成员是私有的类变量(静态成员变量);
(2)Tom 类中的某个方法是私有的类方法;
对于私有成员变量和方法,只有在本类中创建该类的对象时,这个对象才能访问自己的私有成员变量和类中的私有方法。如例 14:
class AAA
{ private int money;
private int getMoney();
{ return money;
}
public static void main(String[] args)
{ AAA exa=new AAA();
exa.money=3000;
int m=exa.getMoney();
System.out.println("money="+m);
}
}
如果将上述源文件修改如下:
class AAA
{ private int money;
private int getMoney();
{ return money;
}
}
public class E
{ public static void main(String[] args)
{ AAA exa=new AAA(); //对象exa不在AAA中
exa.money=3000; //非法
int m=exa.getMoney(); //非法
System.out.println("money="+m);
}
}
那么,以下语句都是非法的:
exa.money=3000;
int m=exa.getMoney();
2.共有变量和共有方法
用 public 修饰的成员变量和方法称为共有变量和共有方法。如:
class Tom
{ public float weight; //weight是public的float型变量
public float f(float a,float b) //方法f是public方法
{ return a+b:
}
}
当在另外一个类中用类 Tom 创建了一个对象后,该对象能访问自己的共有变量和共有方法。如:
class Jerry
{ void g()
{ Tom cat=new Tom();
cat.weight=23f; //合法
float sum=cat.f(3,4); //合法
}
}
如果 Tom 类中的某个成员是 public 类变量/方法,那么在另外一个类中,也可以通过类名 Tom 来操作 Tom 的这个成员变量/类方法。
3.友好变量和友好方法
不用上述三个修饰符修饰的成员变量和方法称为友好变量和友好方法。如
class Tom
{ float weight;
float f(float a,float b)
{ return a+b;
}
}
当在另外一个类中用类 Tom 创建了一个对象后,如果这个类与 Tom 类在同一个包中,那么该对象能访问自己的友好变量和友好方法。在任何一个与 Tom 同一个包中的类中,也可以通过 Tom 类的类名访问 Tom 类的类友好成员变量和类友好方法。
4.受保护的成员变量和方法
用 protected 修饰的成员变量和方法称为受保护的成员变量和受保护的方法。如:
class Jerry
{ protected float weight;
protected float f(float a,float b)
{ return a+b;
}
}
protected 方法将在后续的“子类”中与“友好”方法作区别。
5.public 类和友好类
类声明时,如果在关键字 class 前面加上 public 关键字,就称这样的一个类为一个 public 类,如:
public class A
{ ...
}
可以在任何另外一个类中,使用 public 类创建对象。如果一个类不用 public 修饰,如:
class A
{ ...
}
这样的类称作友好类,那么另外一个类中使用友好类创建对象时,要保证它们是在同一包中。
总结:不能用 protected 和 private 修饰类;访问限制修饰符访问权限从高到低的排列顺序是 public、protected、友好的、private。
九、基本类型的类包装
Java 的基本数据类型包括了 byte、int、short、long、float、double、char。Java 同时也提供了与基本数据类型相关的类,实现了对基本数据类型的封装。这些类在 java.long 包中,分别是 Byte、Integer、Short、Long、Float、Double 和 Character 类。
1.Double 类和 Float 类
上两种类实现了对 double 和 float 类 基本数据类型的类包装。
可以使用 Double 类的构造方法 Double ( double num ),创建一个 Double 类的对象;使用 Float 类的构造方法 Float ( float num),创建一个 Float 类的对象。Double/Float 对象调用 doubleValue()/floatValue() 方法 方法可以返回该对象含有的 double/flaot 型数据。
2.其他类
Byte、Integer、Short、Long 类可以创建对应类型的对象。其对应的对象分别调用各类型数据值方法可以返回这些对象含有的基本数据类型。
3.Character 类
Character 类实现了对 char 基本数据类型的类包装。
可以使用 Character 类的构造方法 Character ( char c ),创建一个 Character 类型的对象。Character 对象调用 charValue() 方法可以返回该对象含有的 char 型数据。
例:
public class Example4_16
{ public static void main(String[] args)
{ char a[] = {'a', 'b', 'c', 'D', 'E', 'F'};
for(int i=0;i<a.length;i++)
{ if(Character.isLowerCase(a[i])) //如果ch是小写字母方法,则返回true,否则返回false
{ a[i]=Character.toUpperCase(a[i]); //返回ch的大写形式
}
else if(Character.isUpperCase(a[i])) //如果ch是大写字母方法,则返回true,否则返回false
{ a[i]=Character.toLowerCase(a[i]); //返回ch的小写形式
}
}
for(int i=0;i<a.length;i++)
{ System.out.print(" "+a[i]);
}
}
}
其他该类方法可以百度搜索。
4.自动装箱与拆箱
十、反编译和文档生成器
1.使用 JDK 提供的反编译器 javap.exe,可以将字节码反编译为源码,查看源码类中的 public 方法名字和 public 成员变量的名字,如:
javap java.awt.Button
将列出 Button 中的 public 方法和 public 成员变量。下列命令:
javap -privae java.awt.Button
将列出 Button 中的全部方法和成员变量。
2.使用 JDK 提供的 javadoc.exe 可以制成源文件类结构的 html 格式文档。
使用 javadoc 时,也可以使用参数 -d 指定生成文档所在的目录,如:
javadoc -d F:\gxy\book Example.java
十一、JAR 文件
用户可以使用 jar.exe 把一些类的字节码文件压缩成一个 JAR 文件。
十二、总结
1.类是组成 Java 源文件的基本元素,一个源文件是由若干个类组成的;
2.类体可以有成员变量和方法两种重要的成员;
3.成员变量分为实例变量和类变量。类变量被该类的所有对象共享;不同对象的实例变量互不相同;
4.除构造方法外,其他方法分为实例方法和类方法。类方法不仅可以由该类的对象调用,也可以用类名调用;而实例方法必须由对象调用;