包
Java使用package解决类名冲突
一个类总是属于某个包,真正的完整类名是包名.类名
在定义class的时候,我们需要在第一行声明这个class属于哪个包。
小明的Person.java文件:
package ming; // 申明包名ming
public class Person {
}
包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系
没有定义包名的class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法。
按照包结构把java文件组织起来,例如(ming,hong,mr.jun都是包)
所有的Java文件对应的目录层次要和包的层次一致
编译后的.class文件也需要按照包结构存放。如果使用IDE,把编译后的.class文件放到bin目录下,那么,编译的文件结构就是:
编译的命令相对比较复杂,我们需要在src目录下执行javac命令:
javac -d ../bin ming/Person.java hong/Person.java mr/jun/Arrays.java
在IDE中,会自动根据包结构编译所有Java源码,所以不必担心使用命令行编译的复杂命令。
包作用域
位于同一个包的类,可以访问包作用域的字段和方法。不用public、protected、private修饰的字段和方法就是包作用域。例如,Person类定义在hello包下面:
package hello;
public class Person {
// 包作用域:
void hello() {
System.out.println("Hello!");
}
}
Main类也定义在hello包下面
package hello;
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.hello(); // 可以调用,因为Main和Person在同一个包
}
}
在一个class中引用其他的class的方法:
- 直接写出完整类名(太麻烦不推荐)
// Person.java
package ming;
public class Person {
public void run() {
mr.jun.Arrays arrays = new mr.jun.Arrays();
}
}
- 使用import语句
// Person.java
package ming;
// 导入完整类名:
import mr.jun.Arrays;
public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}
可以使用import mr.jun.* 导入mr.jun包的所有class,但是不推荐
寻找class
Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:
如果是完整类名,就直接根据完整类名查找这个class;
如果是简单类名,按下面的顺序依次查找:
查找当前package是否存在这个class;
查找import的包是否包含这个class;
查找java.lang包是否包含这个class。
如果按照上面的规则还无法确定类名,则编译报错。
编写class的时候,编译器会自动帮我们做两个import动作:
默认自动import当前package的其他class;
默认自动import java.lang.*。
一个工程项目的文件结构
注意,包名必须完全一致,包没有父子关系,com.apache和com.apache.abc是不同的包。
最佳实践
如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。
把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
小结
Java内建的访问权限包括public、protected、private和package权限;
Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
final修饰符不是访问权限,它可以修饰class、field和method;
一个.java文件只能包含一个public类,但可以包含多个非public类。
修饰符限定访问作用域
修饰类的成员
public、protected、private三者修饰的类的成员访问作用域如下
上表中的默认就是指没有修饰符修饰
修饰类、接口
定义为public的class、interface可以被其他任何类访问:(导包上面说过了,class和interface一般都定义成public,不然干啥???)
嵌套类(内部类)
定义在一个class内部的class称为嵌套类(nested class),Java支持好几种嵌套类。
内部类概述
- 把类定义在其他类的内部,就叫做嵌套类
- 内部类可以访问外部类的field和method,包括private
- 外部类要访问内部类的成员,必须先创建对象(但是一般内部类就是不想让外部访问,比如body和heart)
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
加入定义的Outer是一个普通类,而Inner是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。示例代码如下:
public class Main {
public static void main(String[] args) {
Outer outer = new Outer("Nested"); // 实例化一个Outer
Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
inner.hello();
}
}
class Outer {
private String name;
Outer(String name) {
this.name = name;
}
class Inner {
void hello() {
System.out.println("Hello, " + Outer.this.name);
}
}
}
Inner Class除了有一个this指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。
内部类位置
- 成员位置:成员内部类
- 局部位置(内部类定义在外部类某个方法中的局部变量):局部内部类
成员内部类
- 外界创建对象(实际开发一般不创建,内部类就是不想外部访问)
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象; - 成员内部的常见修饰符
private为了保证数据的安全性
static为了让数据访问更方便
被静态修饰的成员内部类智能访问外部类的静态成员
内部类的方法
非静态方法
外部类名.内部类名 对象名 = new 外部类名.内部类名();
静态方法
上面创建的对象访问;或者外部类名.内部类名.方法名();
下面的程序在控制台分别输出:30,20,10
局部内部类
- 可以直接访问外部类的成员(很容易理解,和外部类方法差不多)
- 可以创建内部类对象,通过对象调用内部类方法
看下面的例子
匿名类(Anonymous Class)
匿名类也是一种定义Inner class的方法,且不需要再Outer class中明确定义这个class,而是在方法内部,通过匿名类来定义,示例代码如下
public class Main {
public static void main(String[] args) {
Outer outer = new Outer("Nested");
outer.asyncHello();
}
}
class Outer {
private String name;
Outer(String name) {
this.name = name;
}
void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
}
观察asyncHello()方法,我们在方法内部实例化了一个Runnable。Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。在定义匿名类的时候就必须实例化它,定义匿名类的写法如下:
Runnable r = new Runnable() {
// 实现必要的抽象方法…
};
匿名类和Inner Class一样,可以访问Outer Class的private字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义Inner Class可以少写很多代码。
静态内部类
public class Main {
public static void main(String[] args) {
Outer.StaticNested sn = new Outer.StaticNested();
sn.hello();
}
}
class Outer {
private static String NAME = "OUTER";
private String name;
Outer(String name) {
this.name = name;
}
static class StaticNested {
void hello() {
System.out.println("Hello, " + Outer.NAME);
}
}
}
用static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this,但它可以访问Outer的private静态字段和静态方法。如果把StaticNested移到Outer之外,就失去了访问private的权限。
小结
Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种:
Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;
Static Nested Class是独立类,但拥有Outer Class的private访问权限。
classpath和jar
不要设置classpath!对大多数情况编译器能够找到class路径,IDE也可以帮助解决,不必多此一举。
在大型项目中,不可能手动编写MANIFEST.MF文件,再手动创建zip包。Java社区提供了大量的开源构建工具,例如Maven,可以非常方便地创建jar包。
小结
JVM通过环境变量classpath决定搜索class的路径和顺序;
不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
jar包相当于目录,可以包含很多.class文件,方便下载和使用;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。
模块
Java 9引入的概念
廖雪峰老师关于模块的讲解