本节学习目标:
- 了解并掌握包的使用方式;
- 了解并掌握final关键字的使用方式;
- 了解并掌握内部类的类型与使用方式。
为了更好地组织和管理类,Java提供了类包(Package)机制,与C++的命名空间(Namespace)功能类似,Java的包用于确定类的唯一标识。
1.1 包的创建在Java中包的设计要与系统中文件夹结构相对应,如一个包名为pers.dyj123,那么该包中的类就应位于pers文件夹下的dyj123子文件夹中。
没有定义包名的类将会被归纳为默认包中。在实际开发时应当为所有类都指定包名。在类中定义包名使用package关键字:
package pers.dyj123;
public class MyClass {
public static int sum(int x, int y) {
return x + y;
}
}
package语句必须放在源码的第一行。它必须是源码中的第一行非注释代码,包名会成为类名的一部分。
比如上面例子中的类的完整类名(全限定类名)就为pers.dyj123.MyClass,类名(非限定类名,也叫短类名)为MyClass。
如果要多次使用同一个类,那么就需要写很多次这个类的全限定类名:
public class Test {
public static void main(String[] args) {
int x = pers.dyj123.MyClass.sum(10, 20);
int y = pers.dyj123.MyClass.sum(20, 30);
int z = pers.dyj123.MyClass.sum(30, 40);
}
}
如果全限定类名很长的话会导致代码臃肿,可读性变差,影响美观。
为了解决这个问题,Java提供了import关键字,可以导入全限定类名,这样在代码中只使用类名即可指代全限定类名:
import pers.dyj123.MyClass;
public class Test {
public static void main(String[] args) {
int x = MyClass.sum(10, 20);
int y = MyClass.sum(20, 30);
int z = MyClass.sum(30, 40);
}
}
如果要使用同一个包下的很多类时,可以使用通配符(*)来导入一个包下的所有类(不包括这个包的子包中的类):
import pers.dyj123.*;
public class Test {
public static void main(String[] args) {
int x = MyClass.sum(10, 20);
int y = MyClass.sum(20, 30);
int z = MyClass.sum(30, 40);
}
}
如果导入一个类仅使用这个类中的静态变量、静态常量或静态方法时,
import关键字可以和static关键字联合使用(JDK1.5新特性),用于导入类中的静态属性(需要指定导入的静态属性名或方法名):
import static pers.dyj123.MyClass.sum;
public class Test {
public static void main(String[] args) {
int x = sum(10, 20);
int y = sum(20, 30);
int z = sum(30, 40);
}
}
在编写代码时,Java默认会导入java.lang包下的类(不包括其子包下的类),使用这些类时不必使用import关键字导入,直接使用类名即可。
1.3 包名的命名规范- 包名命名规范:
- 包名命名的一般规则:<域名>.<公司名或个人名>.<项目名或工程名>.<模块名>...(如com.sun.net.httpserver.HttpContext);
- 域名:如org,com,net等,还有一些特殊的域名:
- indi:个体项目,指个人发起,但非自己独自完成的项目,可公开或私有项目,版权主要属于发起者;
- pers:个人项目,指个人发起,独自完成,可分享的项目,版权主要属于个人;
- priv:私有项目,指个人发起,独自完成,非公开的私人使用的项目,版权属于个人;
- onem:与indi相同,推荐使用indi;
- team:团队项目,指由团队发起,并由该团队开发的项目,版权属于该团队所有。
- 项目名或工程名,模块名及其之后的包名统一为小写字母,单词与单词之间紧挨。
final这个单词本身意义为“最终的”,“最后的”。当它被用作Java关键字final时,它的意义常常为“最终的”,“不可变”的。
final关键字可以修饰常量,方法和类:
- final关键字修饰的变量是一个常量;
- final关键字修饰的方法不能被子类重写;
- final关键字修饰的类不能被继承。
final关键字可用于变量的声明,当变量被final修饰时,一旦该变量被初始化,就不可以再改变该变量的值。
通常情况下,被final关键字修饰的变量被称为常量(Constant),为了规范,被final修饰的常量名需大写,单词与单词之间使用下划线连接。
常量是不可变的值,比如数学中的圆周率PI(π),它就是个常量,可以使用以下语句定义:
final double PI = 3.1415;
常量在声明时就必须进行初始化(赋值操作),final关键字除了可以修饰基本数据类型常量,也可以修饰引用数据类型常量。
基本数据类型常量被初始化后,就不可以更改它的值,引用数据类型常量被初始化后,就不可以使它指向另一个对象。
在Java中定义全局常量,通常使用public static final修饰,全局常量必须在声明时被初始化(赋值)。
在方法中,final关键字可以修饰方法的参数,被final关键字修饰的参数与常量相同,使用时无法更改它的值,无法更改它指向的地址:
public static int sum(final int x, final int y) {
return x + y;
}
2.2 final 方法
final关键字也可以修饰方法本身,子类无法重写父类中被final关键字修饰的方法。
编写代码进行测试:
class SuperClass {
public final void method() {
System.out.println("这是父类的method()方法");
}
}
public class SubClass {
public final void method() {
System.out.println("这是子类的method()方法");
}
}
尝试进行编译,Java编译器报错:
final关键字甚至可以修饰类,被final关键字修饰的类无法被继承。final关键字无法修饰抽象类与接口。
编写代码进行测试:
final class SuperClass {
}
public class SubClass extends SuperClass {
}
尝试进行编译,Java编译器报错:
类可以定义在类的内部,定义在一个类的内部的类被称为内部类(Inner Class),相对的,包含一个类的类被称为外部类(Outer Class)。
3.1 成员内部类在一个类中定义成员内部类,分别对外部类和内部类实例化:
class OuterClass {
public OuterClass() {
System.out.println("外部类被实例化");
}
public class InnerClass {
public InnerClass() {
System.out.println("内部类被实例化");
}
}
}
public class Test {
public static void main(String[] args) {
OuterClass out = new OuterClass();
OuterClass.InnerClass in = out.new InnerClass();
}
}
运行结果:
外部类被实例化 内部类被实例化
外部类可以间接访问成员内部类的任何属性和方法:
class OuterClass {
public OuterClass() {
InnerClass innerClass = new InnerClass();
innerClass.method(innerClass.field);
}
public class InnerClass {
private String field = "外部类可以间接访问内部类的属性和方法";
public void method(String str) {
System.out.println(str);
}
}
}
public class Test {
public static void main(String[] args) {
new OuterClass();
}
}
运行结果:
外部类可以间接访问内部类的属性和方法
成员内部类可以直接访问外部类的任何属性和方法:
class OuterClass {
private String field = "内部类可以直接访问外部类的属性和方法";
public void method(String str) {
System.out.println(str);
}
public class InnerClass {
public InnerClass() {
method(field); // 内部类对象隐式的在外部保存了一个引用,指向创建它的外部类对象,所以这里不用再创建外部类对象
}
}
}
public class Test {
public static void main(String[] args) {
OuterClass out = new OuterClass();
OuterClass.InnerClass in = out.new InnerClass();
}
}
运行结果:
内部类可以直接访问外部类的属性和方法
如果不想让外部类访问成员内部类的属性和方法,可以将成员内部类使用private关键字修饰:
class OuterClass {
private class InnerClass {
}
}
public class Test {
public static void main(String[] args) {
OuterClass out = new OuterClass();
OuterClass.InnerClass in = out.new InnerClass();
}
}
尝试编译,Java编译器报错:
喝水这一功能,多功能水壶可以以多种方式进行实现,但是类本身只能有一种实现方法,使用内部类可以有多种实现:
interface Drinkable {
void drink();
}
class MultifunctionalTeaBottle {
private class Straw implements Drinkable {
@Override
public void drink() {
System.out.println("多功能水壶可以用吸管喝水");
}
}
private class TeaLid implements Drinkable {
@Override
public void drink() {
System.out.println("多功能水壶可以用杯盖喝水");
}
}
public Straw useWithStraw() {
return new Straw();
}
public TeaLid useWithTeaLid() {
return new TeaLid();
}
}
public class Test {
public static void main(String[] args) {
MultifunctionalTeaBottle bottle = new MultifunctionalTeaBottle();
Drinkable drinkable1 = bottle.useWithStraw(); // 内部类向上转型为接口
drinkable1.drink();
Drinkable drinkable2 = bottle.useWithTeaLid(); // 内部类向上转型为接口
drinkable2.drink();
}
}
运行结果:
多功能水壶可以用吸管喝水 多功能水壶可以用杯盖喝水
在内部类中使用this关键字获取外部类的引用:
class OuterClass {
public int x = 5;
class InnerClass {
public int x = 10;
public void method() {
System.out.println(this.x); // 使用内部类的变量x
System.out.println(OuterClass.this.x); // 使用外部类的变量x
}
}
}
成员内部类的特点:
- 内部类可以用四种访问修饰符修饰,外部类只能修饰为public或缺省;
- 成员内部类不能声明静态属性或静态方法;
- 成员内部类可以随意使用外部类的所有成员方法以及成员变量(包括被private关键字修饰的),反之亦然;
- 如果成员内部类被private关键字修饰,那么外部类无法访问成员内部类的属性和方法。但成员内部类仍然可以访问外部类的属性和方法。
- 成员内部类的实例化依赖外部类对象的引用。
- 在内部类中访问自身引用使用this关键字,访问外部类引用则使用外部类类名.this。
内部类不仅可以在类中定义,也可以在局部位置定义,如方法体中:
class OuterClass {
public static void method() {
class InnerClass {
public InnerClass() {
System.out.println("内部类定义在方法体中");
}
}
new InnerClass();
}
}
public class Test {
public static void main(String[] args) {
OuterClass.method();
}
}
运行结果:
内部类定义在方法体中
局部内部类InnerClass是外部类OuterClass的方法method()的一部分,并非外部类OuterClass的一部分。
外部类不能访问局部内部类的属性和方法;局部内部类可以访问外部类的属性和方法,以及当前作用域的常量(可以访问但不能修改,因为是常量)。
匿名内部类是一种特殊的内部类,它的作用主要是用来简化抽象类或接口的代码实现。
如果想实现一个接口中的方法,通常需要先定义一个实现这个接口的类,再写实现方法,最后new一个这个类调用此方法:
interface A {
void method();
}
class AClass implements A {
@Override
public void method() {
// input your code here...
}
}
public class Test {
public static void main(String[] args) {
A a = new AClass();
a.method();
}
}
使用匿名内部类可以简化实现方式:
interface A {
void method();
}
public class Test {
public static void main(String[] args) {
A a = new A() {
@Override
public void method() {
// input your code here...
}
};
a.method();
}
}
在本章3.1节成员内部类中多功能水壶的例子,可以改为如下形式:
interface Drinkable {
void drink();
}
class MultifunctionalTeaBottle {
public Drinkable useWithStraw() {
return new Drinkable() { // 匿名内部类
@Override
public void drink() {
System.out.println("多功能水壶可以用吸管喝水");
}
};
}
public Drinkable useWithTeaLid() {
return () -> System.out.println("多功能水壶可以用杯盖喝水"); // 使用lambda表达式进一步简化匿名内部类
}
}
public class Test {
public static void main(String[] args) {
MultifunctionalTeaBottle bottle = new MultifunctionalTeaBottle();
Drinkable drinkable1 = bottle.useWithStraw(); // 内部类向上转型为接口
drinkable1.drink();
Drinkable drinkable2 = bottle.useWithTeaLid(); // 内部类向上转型为接口
drinkable2.drink();
}
}
匿名内部类没有名称,它的含义是创建一个实现接口或者抽象类的对象,主要作用是简化实现接口或抽象类的方式。
3.4 静态内部类编译含有匿名内部类的源码文件时,会生成名为“外部类名$序号”的字节码文件,序号1~n对应源码中1~n个匿名内部类。
使用static关键字修饰成员内部类后,这个内部类就被称为静态内部类。
静态内部类的特点:
- 静态内部类可以声明静态属性或方法;
- 静态内部类不可以访问外部类的非静态成员;
- 静态内部类实例化时不需依赖外部类对象的引用。
编写代码进行测试:
class OuterClass {
public int x = 5;
static class InnerClass {
public static void method() {
System.out.println(new OuterClass().x);
}
}
}
尝试编译,Java编译器报错:
3.5 内部类的继承编译含有成员内部类或静态内部类的源码文件时,会生成名为“外部类类名$内部类类名”的字节码文件。
内部类可以像外部类一样被继承:
class OuterClass {
class InnerClass {
}
}
public class Test extends OuterClass.InnerClass {
public Test(OuterClass out) {
out.super();
}
}
继承内部类的规则:
- 内部类的子类需要指定继承外部类类名.内部类类名;
- 继承一个成员内部类时,需要在所有的构造方法中添加一个外部类的引用。继承一个静态内部类则不用;



