Java 包(package)
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
包的作用
-
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
-
2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
-
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
包语句的语法格式为:
package pkg1[.pkg2[.pkg3…]];
例如,一个Something.java 文件它的内容
package net.java.util;
public class Something{
...
}
那么它的路径应该是 net/java/util/Something.java 这样保存的。 package(包) 的作用是把不同的 java 程序分类保存,更方便的被其他 java 程序调用。
一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。
以下是一些 Java 中的包:
- java.lang-打包基础的类
- java.io-包含输入输出功能的函数
开发者可以自己把一组类和接口等打包,并定义自己的包。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。
由于包创建了新的命名空间(namespace),所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。
创建包
创建包的时候,你需要为这个包取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。
包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。
如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。
例子
让我们来看一个例子,这个例子创建了一个叫做animals的包。通常使用小写的字母来命名避免与类、接口名字的冲突。
在 animals 包中加入一个接口(interface):
/* 文件名: Animal.java */
package animals;
interface Animal {
public void eat();
public void travel();
}
接下来,在同一个包中加入该接口的实现:
package animals;
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
然后,编译这两个文件,并把他们放在一个叫做animals的子目录中。 用下面的命令来运行:
import 关键字
为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。
在 Java 中,import 关键字用于导入其他类或包中定义的类型,以便在当前源文件中使用这些类型。
import 关键字用于引入其他包中的类、接口或静态成员,它允许你在代码中直接使用其他包中的类,而不需要完整地指定类的包名。
在 java 源文件中 import 语句必须位于 Java 源文件的头部,其语法格式为:
import package1[.package2…].(classname|*);
import 语句位于 package 语句之后:
// 第一行非注释行是 package 语句
package com.example;
// import 语句引入其他包中的类
import java.util.ArrayList;
import java.util.List;
// 类的定义
public class MyClass {
// 类的成员和方法
}
如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。
可以使用 import语句来引入一个特定的类:
import com.runoob.MyClass;
这样,你就可以在当前源文件中直接使用 MyClass 类的方法、变量或常量。
也可以使用通配符 * 来引入整个包或包的子包:
import com.runoob.mypackage.*;
这样,你可以导入 com.runoob.mypackage 包中的所有类,从而在当前源文件中使用该包中的任何类的方法、变量或常量。注意,使用通配符 * 导入整个包时,只会导入包中的类,而不会导入包中的子包。
在导入类或包时,你需要提供类的完全限定名或包的完全限定名。完全限定名包括包名和类名的组合,以点号 . 分隔。
import java.util.ArrayList; // 引入 java.util 包中的 ArrayList 类
import java.util.*; // 引入 java.util 包中的所有类
import com.example.MyClass; // 引入 com.example 包中的 MyClass 类
import com.example.*; // 引入 com.example 包中的所有类
例子
下面的 payroll 包已经包含了 Employee 类,接下来向 payroll 包中添加一个 Boss 类。Boss 类引用 Employee 类的时候可以不用使用 payroll 前缀,Boss 类的实例如下。
package payroll;
public class Boss
{
public void payEmployee(Employee e)
{
e.mailCheck();
}
}
如果 Boss 类不在 payroll 包中又会怎样?Boss 类必须使用下面几种方法之一来引用其他包中的类。
使用类全名描述,例如:
payroll.Employee
用 import 关键字引入,使用通配符 *:
import payroll.*;
使用 import 关键字引入 Employee 类:
import payroll.Employee;
注意:
类文件中可以包含任意数量的 import 声明。import 声明必须在包声明之后,类声明之前。
package 的目录结构
类放在包中会有两种主要的结果:
- 包名成为类名的一部分,正如我们前面讨论的一样。
- 包名必须与相应的字节码所在的目录结构相吻合。
下面是管理你自己 java 中文件的一种简单方式:
将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:
// 文件名 : Car.java
package vehicle;
public class Car {
// 类实现
}
接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。
....\vehicle\Car.java
现在,正确的类名和路径将会是如下样子:
-
类名 -> vehicle.Car
-
路径名 -> vehicle\Car.java (在 windows 系统中)
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是 runoob.com,所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。
例如:有一个 com.runoob.test 的包,这个包包含一个叫做 Runoob.java 的源文件,那么相应的,应该有如下面的一连串子目录:
....\com\runoob\test\Runoob.java
编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀。 例如:
// 文件名: Runoob.java
package com.runoob.test;
public class Runoob {
}
class Google {
}
现在,我们用-d选项来编译这个文件,如下:
$javac -d . Runoob.java
这样会像下面这样放置编译了的文件:
.\com\runoob\test\Runoob.class
.\com\runoob\test\Google.class
你可以像下面这样来导入所有 \com\runoob\test\ 中定义的类、接口等:
import com.runoob.test.*;
编译之后的 .class 文件应该和 .java 源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。
<path-one>\sources\com\runoob\test\Runoob.java
<path-two>\classes\com\runoob\test\Google.class
这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到你程序中使用的所有类型。
类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。
<path- two>\classes 是 class path,package 名字是 com.runoob.test,而编译器和 JVM 会在 <path-two>\classes\com\runoob\test 中找 .class 文件。
一个 class path 可能会包含好几个路径,多路径应该用分隔符分开。默认情况下,编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。
设置 CLASSPATH 系统变量
用下面的命令显示当前的CLASSPATH变量:
- Windows 平台(DOS 命令行下):C:\> set CLASSPATH
- UNIX 平台(Bourne shell 下):# echo $CLASSPATH
删除当前CLASSPATH变量内容:
- Windows 平台(DOS 命令行下):C:\> set CLASSPATH=
- UNIX 平台(Bourne shell 下):# unset CLASSPATH; export CLASSPATH
设置CLASSPATH变量:
- Windows 平台(DOS 命令行下): C:\> set CLASSPATH=C:\users\jack\java\classes
- UNIX 平台(Bourne shell 下):# CLASSPATH=/home/jack/java/classes; export CLASSPATH
Java 反射(Reflection)
Java 反射(Reflection)是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。
反射 API
Java 的反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:
java.lang.Class
:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。java.lang.reflect.Field
:表示类的字段(属性)。提供了访问和修改字段的能力。java.lang.reflect.Method
:表示类的方法。提供了调用方法的能力。java.lang.reflect.Constructor
:表示类的构造函数。提供了创建对象的能力。
工作流程
- 获取
Class
对象:首先获取目标类的Class
对象。 - 获取成员信息:通过
Class
对象,可以获取类的字段、方法、构造函数等信息。 - 操作成员:通过反射 API 可以读取和修改字段的值、调用方法以及创建对象。
以下是 Java 反射的基本使用方式及其常见应用。
1. 获取 Class 对象
每个类在 JVM 中都有一个与之相关的 Class 对象。可以通过以下方式获取 Class 对象:
通过类字面量
Class<?> clazz = String.class;
通过对象实例
String str = "Hello";
Class<?> clazz = str.getClass();
通过 Class.forName() 方法:
Class<?> clazz = Class.forName("java.lang.String");
2. 创建对象
可以使用反射动态创建对象:
Class<?> clazz = Class.forName("java.lang.String");
Object obj = clazz.getDeclaredConstructor().newInstance();
3. 访问字段
可以通过反射访问和修改类的字段:
Class<?> clazz = Person.class;
Field field = clazz.getDeclaredField("name");
field.setAccessible(true); // 如果字段是私有的,需要设置为可访问
Object value = field.get(personInstance); // 获取字段值
field.set(personInstance, "New Name"); // 设置字段值
4. 调用方法
可以通过反射调用类的方法:
Class<?> clazz = Person.class;
Method method = clazz.getMethod("sayHello");
method.invoke(personInstance);
Method methodWithArgs = clazz.getMethod("greet", String.class);
methodWithArgs.invoke(personInstance, "World");
5. 获取构造函数
可以使用反射获取和调用构造函数:
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("John", 30);
6. 获取接口和父类
可以使用反射获取类实现的接口和父类:
Class<?> clazz = Person.class;
// 获取所有接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
System.out.println("Interface: " + i.getName());
}
// 获取父类
Class<?> superClass = clazz.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
以下是一个完整的示例,展示了如何使用反射来创建对象、访问字段和调用方法:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取 Class 对象
Class<?> clazz = Person.class;
// 创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("John", 30);
// 访问字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println("Name: " + nameField.get(person));
// 修改字段
nameField.set(person, "Doe");
System.out.println("Updated Name: " + nameField.get(person));
// 调用方法
Method greetMethod = clazz.getMethod("greet", String.class);
greetMethod.invoke(person, "World");
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void greet(String message) {
System.out.println(name + " says: " + message);
}
}
java.lang.reflect
java.lang.reflect
是 Java 反射机制的核心包,提供了操作类及其成员(字段、方法、构造函数等)的类和接口。通过这些 API,开发者可以在运行时动态地查询和修改类的结构。
以下是 java.lang.reflect
包中的主要类和接口的详细介绍:
1. Class
类
- 功能:表示类的对象,提供了获取类信息的方法,如字段、方法、构造函数等。
- 主要方法:
getFields()
:获取所有公共字段。getDeclaredFields()
:获取所有声明的字段,包括私有字段。getMethods()
:获取所有公共方法。getDeclaredMethods()
:获取所有声明的方法,包括私有方法。getConstructors()
:获取所有公共构造函数。getDeclaredConstructors()
:获取所有声明的构造函数,包括私有构造函数。getSuperclass()
:获取类的父类。getInterfaces()
:获取类实现的所有接口。
2. Field
类
- 功能:表示类的字段(属性),提供了访问和修改字段值的方法。
- 主要方法:
get(Object obj)
:获取指定对象的字段值。set(Object obj, Object value)
:设置指定对象的字段值。getType()
:获取字段的数据类型。getModifiers()
:获取字段的修饰符(如 public、private)。
3. Method
类
- 功能:表示类的方法,提供了调用方法的能力。
- 主要方法:
invoke(Object obj, Object... args)
:调用指定对象的方法。getReturnType()
:获取方法的返回类型。getParameterTypes()
:获取方法的参数类型。getModifiers()
:获取方法的修饰符(如 public、private)。
4. Constructor
类
- 功能:表示类的构造函数,提供了创建对象的能力。
- 主要方法:
newInstance(Object... initargs)
:创建一个新实例,使用指定的构造函数参数。getParameterTypes()
:获取构造函数的参数类型。getModifiers()
:获取构造函数的修饰符(如 public、private)。
示例代码
以下是使用 java.lang.reflect
包进行反射操作的示例:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取 Class 对象
Class<?> clazz = Car.class;
// 创建 Car 对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object car = constructor.newInstance("Toyota", 2020);
// 访问和修改字段
Field modelField = clazz.getDeclaredField("model");
Field yearField = clazz.getDeclaredField("year");
// 设置字段为可访问(如果字段是私有的)
modelField.setAccessible(true);
yearField.setAccessible(true);
// 打印原始字段值
System.out.println("Original Model: " + modelField.get(car));
System.out.println("Original Year: " + yearField.get(car));
// 修改字段值
modelField.set(car, "Honda");
yearField.set(car, 2024);
// 打印修改后的字段值
System.out.println("Updated Model: " + modelField.get(car));
System.out.println("Updated Year: " + yearField.get(car));
// 调用方法
Method startMethod = clazz.getMethod("start");
startMethod.invoke(car);
}
}
class Car {
private String model;
private int year;
public Car(String model, int year) {
this.model = model;
this.year = year;
}
public void start() {
System.out.println("The " + model + " car of year " + year + " is starting.");
}
}