Java进阶篇
Java 是一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此,Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java 具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。
Java 可以编写桌面应用程序、Web 应用程序、分布式系统和嵌入式系统应用程序等。
面向对象设计
面向对象的程序设计是通过对象对程序进行设计,对象代表一个实体,实体可以清楚地被识别。
Java 作为一种面向对象语言,有以下基本概念:
- 包(Package)
- 接口(Interface)
- 抽象类(Abstract Class)
- 类(Class)
- 对象(Object)
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
- 消息解析(Message Parsing / Method Resolution)
- 注解(Annotation)
源文件声明规则
当在一个源文件中定义多个类,并且还有 import、package 语句时,要特别注意这些规则。
- package 语句应该在源文件的首行。
- 在同一源文件中,不能给不同的类不同的包声明。
- import 语句应该放在 package 语句和类定义之间。
- import 语句和 package 语句对源文件中定义的所有类都有效。
- 源文件的名称应该和 public 类的类名保持一致。
- 一个源文件中只能有一个 public 类。
- 一个源文件可以有多个非 public 类。
包(Package)
包是 Java 中组织类和接口的机制,相当于文件系统中的文件夹。它用于将相关的类和接口分组,避免命名冲突,并控制访问权限。
详细说明:
- 命名规范:通常使用公司域名的倒写作为包名的前缀,例如
com.example.project.module。全部小写,点号分隔。 - 物理结构:包名必须与源代码文件的目录结构完全一致。例如,包
com.lizhao.vehicle表示类文件位于com/lizhao/vehicle/目录下。 - 包声明:每个
.java文件最多只能有一个package语句,且必须放在文件的第一行(注释除外)。 - 默认包:如果没有声明
package,类属于“无名包”,一般不推荐用于正式项目。 - 导入包:使用
import语句可以简化类名书写。import java.util.*;导入包下的所有类和接口,但不递归导入子包。 - 静态导入:
import static可以导入类的静态成员,使代码更简洁。
src
│
├── com
│ └── lizhao
│ └── vehicle
│ ├── Vehicle.java
│ ├── Car.java
│ └── Plane.java
└── VehicleApplication.java
// src/VehicleApplication.java
import com.lizhao.vehicle.Vehicle;
import com.lizhao.vehicle.Car;
import com.lizhao.vehicle.Plane;
public class VehicleApplication {
public static void main (String[] args) {
Vehicle vehicle = new Vehicle();
System.out.println(vehicle.toString());
Car car = new Car("小汽车", "black");
System.out.println(car.getColor());
Plane plane = new Plane();
plane.fly();
}
}
// src/com/lizhao/vehicle/Vehicle.java
package com.lizhao.vehicle;
public class Vehicle {
public String name = "vehicle";
public String toString() {
return this.name;
};
}
// src/com/lizhao/vehicle/Car.java
package com.lizhao.vehicle;
public class Car {
String name;
String color;
public Car () {}
public Car (String name, String color) {
this.name = name;
this.color = color;
}
public void setName (String name) {
this.name = name;
}
public String getName () {
return this.name;
}
public void setColor (String color) {
this.color = color;
}
public String getColor () {
return this.color;
}
}
// src/com/lizhao/vehicle/Plane.java
package com.lizhao.vehicle;
public class Plane {
String name = "飞机";
public void fly() {
System.out.println("fly:" + this.name);
};
}
常用 Java 包:
java.lang:核心类(String、Math、System),自动导入。java.util:工具类、集合框架(List、Set、Arrays)。java.io:输入输出操作。java.net:网络编程。java.sql:数据库编程。java.awt / javax.swing:图形界面。
接口(Interface)
接口是 Java 中的一种引用类型,是抽象方法的集合。接口定义了一种规范或契约,任何实现该接口的类都必须提供接口中所有方法的具体实现(除非该类是抽象类)。
详细说明:
- 接口中的方法默认是
public abstract(Java 8 之前)。 - 接口中的变量默认是
public static final,即常量。 - 接口不能实例化,没有构造方法。
- 一个类可以实现多个接口(多实现),弥补了单继承的不足。
- 接口之间可以多继承(
extends多个接口)。
src
│
├── com
│ └── lizhao
│ └── sports
│ ├── Sports.java
│ ├── Player.java
└── Main.java
// src/Main.java
import com.lizhao.sports.Sports;
import com.lizhao.sports.Player;
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.play(Sports.BASKETBALL);
player.play(Sports.FOOTBALL);
}
}
// src/com/lizhao/sports/Sports.java
package com.lizhao.sports;
public interface Sports {
int BASKETBALL = 1;
int FOOTBALL = 2;
int TENNIS = 3;
void play(int sportType);
}
// src/com/lizhao/sports/Player.java
package com.lizhao.sports;
public class Player implements Sports {
@Override
public void play(int sportType) {
switch(sportType) {
case BASKETBALL:
System.out.println("打篮球");
break;
case FOOTBALL:
System.out.println("踢足球");
break;
case TENNIS:
System.out.println("打网球");
break;
default:
System.out.println("未知运动");
}
}
}
注意事项:
- 实现接口方法时,不能抛出比接口声明更宽泛的强制性异常。
- 重写方法必须保持方法名一致,返回值类型需相同或兼容(支持协变返回)。
- 一个类只能继承一个父类,但能同时实现多个接口。
- 接口支持多重继承,一个子接口可以继承多个父接口,
extends后跟多个接口名(逗号分隔)。 - 标识接口是没有方法和属性的空接口,用于标记类属于某种特定类型。一般用于建立公共父接口、为类添加数据类型(通过多态)。
抽象类(Abstract Class)
抽象类是使用 abstract 关键字修饰的类,它不能被实例化,通常作为父类被其他类继承。抽象类可以包含抽象方法(没有方法体)和具体方法。
详细说明:
- 抽象方法:只有声明,没有实现,使用
abstract修饰。子类必须重写所有抽象方法(除非子类也是抽象类)。 - 普通方法:抽象类中可以定义普通方法,子类直接继承使用。
- 成员变量:抽象类可以拥有普通成员变量,与普通类相同。
- 构造方法:抽象类可以有构造方法,但只能被子类通过
super调用。 - 使用场景:当多个子类有共同的属性和行为,但某些方法需要子类各自实现时,使用抽象类。
src
│
├── com
│ └── lizhao
│ └── shape
│ ├── Shape.java
│ ├── Circle.java
│ ├── Rectangle.java
└── Main.java
// src/Main.java
import com.lizhao.shape.Shape;
import com.lizhao.shape.Circle;
import com.lizhao.shape.Rectangle;
public class Main {
public static void main(String[] args) {
Shape c = new Circle("blue", 5.0);
Shape r = new Rectangle("green", 3.0, 4.0);
System.out.println(c.area());
System.out.println(r.area());
c.displayColor();
}
}
// src/com/lizhao/shape/Shape.java
package com.lizhao.shape;
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// 抽象方法:子类必须实现
public abstract double area();
// 具体方法:子类可以直接使用
public void displayColor() {
System.out.println("Color: " + color);
}
}
// src/com/lizhao/shape/Circle.java
package com.lizhao.shape;
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// src/com/lizhao/shape/Rectangle.java
package com.lizhao.shape;
public class Rectangle extends Shape {
private double width, height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
类(Class)
类是 Java 中面向对象编程的基本单元,是创建对象的模板。
类定义了对象所拥有的属性(成员变量)和行为(方法)。
类的组成:
- 构造方法:用于创建并初始化对象。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。构造方法的名称必须与类同名。
- 成员变量(字段):存储对象的状态。
- 方法:定义对象的行为。
- 初始化块:在构造方法之前执行。
- 内部类:定义在类内部的类。
// src/Main.java
import com.lizhao.Student;
public class Main {
public static void main(String[] args) {
Student s1 = new Student("Hello", 18);
s1.introduce();
s1.study("数学");
Student s2 = new Student("Lizhao", 19);
s2.introduce();
Student.school = "第二中学";
s1.introduce();
s2.introduce();
System.out.println("\n--- 内部类示例 ---");
Student.Score score1 = s1.new Score(90, 88);
score1.printScore();
Student.SchoolInfo schoolInfo = new Student.SchoolInfo("教育路123号");
schoolInfo.printInfo();
Student.printSchool();
}
}
// src/com/lizhao/Student.java
package com.lizhao;
public class Student {
public static String school = "第一中学";
private static int studentCount = 0;
private String name;
private int age;
private String id;
// ========== 初始化块 ==========
// 实例初始化块:每次创建对象时,在构造方法之前执行
{
id = generateId();
studentCount++; // 每创建一个学生,总数加1
System.out.println("执行初始化块,当前学生总数:" + studentCount);
}
// 静态初始化块:类加载时执行一次
static {
System.out.println("静态初始化块:Student类被加载");
System.out.println("学校:" + school);
}
// ========== 构造方法 ==========
public Student() {
this("未知", 0);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("执行构造方法:创建学生 " + name);
}
// ========== 方法 ==========
// 实例方法
public void introduce() {
String info = "我叫" + name + ",今年" + age + "岁";
System.out.println(info + ",学号:" + id + ",学校:" + school);
}
public void study(String subject) {
String action = "正在学习";
System.out.println(name + action + subject);
}
// 静态方法
public static void printSchool() {
System.out.println("全校名称:" + school + "," + "学生总数:" + studentCount);
}
// getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 私有方法:生成学号
private String generateId() {
return "S" + System.currentTimeMillis();
}
// ========== 内部类 ==========
// 成员内部类
public class Score {
private int chinese;
private int math;
public Score(int chinese, int math) {
this.chinese = chinese;
this.math = math;
}
public void printScore() {
// 内部类可以访问外部类的私有成员
System.out.println(name + "的成绩 - 语文:" + chinese + ",数学:" + math);
}
}
// 静态内部类
public static class SchoolInfo {
private String address;
public SchoolInfo(String address) {
this.address = address;
}
public void printInfo() {
System.out.println("学校:" + school + ",地址:" + address);
}
}
}
对象(Object)
对象是类的具体实例,具有状态和行为。在内存中,对象是存储具体数据的一块区域。
详细说明:
- 创建对象:使用
new关键字调用构造方法。 - 对象引用:变量存储的是对象的引用(内存地址),而不是对象本身。
- 对象生命周期:从
new创建开始,到不再被引用后被垃圾回收器回收。 - 匿名对象:没有引用的对象,例如
new Student("Tom").introduce();,使用一次后即成为垃圾。
注意事项:
==比较两个引用是否指向同一个对象;equals()一般用于比较内容,需要根据业务需求重写。- 对象的销毁由垃圾回收器自动完成,不需要手动释放内存(除非使用了外部资源如文件、网络连接,需要显式关闭)。
封装(Encapsulation)
面向对象编程的三大特征:封装、继承、多态。
封装是将对象的状态(属性)隐藏在对象内部,通过公共方法(getter/setter)来访问和修改,从而保护数据的完整性和安全性。
详细说明:
- 将成员变量声明为
private。 - 提供公共的
getter和setter方法。 - 在
setter中可以加入校验逻辑。
package com.lizhao;
public class Student {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
setAge(age);
setScore(score);
}
public String getName() {
return name;
}
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
} else {
System.out.println("姓名不能为空");
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在 0-150 之间");
}
}
public double getScore() {
return score;
}
public void setScore(double score) {
if (score >= 0 && score <= 100) {
this.score = score;
} else {
System.out.println("成绩必须在 0-100 之间");
}
}
public void printInfo() {
System.out.println("姓名:" + name + ",年龄:" + age + ",成绩:" + score);
}
}
继承(Inheritance)
面向对象编程的三大特征:封装、继承、多态。
继承是面向对象的核心特性之一,子类可以复用父类的属性和方法,并可以添加新的成员或重写父类的方法。
详细说明:
- 关键字:
extends用于类继承,implements用于实现接口。 - 单继承:Java 中一个类只能有一个直接父类,但可以间接继承多个祖先类。
- 方法重写:子类可以重新实现父类的方法,但需要保持方法签名一致。
super关键字:用于访问父类的成员或调用父类的构造方法。super()必须放在子类构造方法的第一行。- 访问性:子类可以访问父类的
public和protected成员,以及同包下的默认成员。不能访问private成员。
package com.lizhao.shape;
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
多态(Polymorphism)
面向对象编程的三大特征:封装、继承、多态。
多态指同一个行为具有多个不同表现形式。在 Java 中,多态分为编译时多态(重载)和运行时多态(重写)。
通常所说的多态指的是运行时多态:父类引用指向子类对象,调用方法时表现出子类的行为。
多态的必要条件:
- 继承关系或接口实现。
- 子类重写父类方法(或实现接口方法)。
- 父类引用指向子类对象。
多态的体现:
- 方法重写。
- 接口实现。
- 抽象类。
注意:静态方法不被多态影响,始终根据引用类型调用。
public class Main {
static class Shape {
public void draw() {
System.out.println("绘制一个形状");
}
public double getArea() {
return 0;
}
}
static class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("⚪ 绘制一个半径为 " + radius + " 的圆形");
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
static class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println("■ 绘制一个 " + width + " x " + height + " 的矩形");
}
@Override
public double getArea() {
return width * height;
}
}
public static void main(String[] args) {
// 1. 父类引用指向子类对象
System.out.println("【1. 父类引用指向子类对象】");
Shape shape1 = new Circle(5);
Shape shape2 = new Rectangle(4, 6);
// 调用同一个方法,表现出不同的行为
shape1.draw();
shape2.draw();
System.out.println();
// 2. 多态参数:方法接收父类类型,可以传入任何子类
System.out.println("【2. 多态参数】");
printShapeInfo(new Circle(3));
printShapeInfo(new Rectangle(5, 8));
System.out.println();
// 3. 多态数组:可以存放不同类型的对象
System.out.println("【3. 多态数组】");
Shape[] shapes = {
new Circle(2.5),
new Rectangle(3, 4),
new Circle(4),
new Rectangle(2, 9)
};
// 统一遍历处理,不需要关心具体是什么形状
for (int i = 0; i < shapes.length; i++) {
System.out.print("第" + (i+1) + "个:");
shapes[i].draw();
}
System.out.println();
// 4. 计算总面积(多态的便利)
System.out.println("【4. 计算总面积(多态的便利)】");
double totalArea = 0;
for (Shape shape : shapes) {
totalArea += shape.getArea();
System.out.printf("面积:%.2f\n", shape.getArea());
}
System.out.printf("总面积:%.2f\n", totalArea);
}
// 多态参数:不需要为每个形状单独写方法
public static void printShapeInfo(Shape shape) {
System.out.print("形状信息:");
shape.draw();
System.out.printf("面积:%.2f\n\n", shape.getArea());
}
}
重载(Overload)
在同一个类中,多个方法拥有相同的名称但参数列表不同(参数数量、类型、顺序不同),称为重载。
重载是编译时多态。
详细说明:
- 参数列表区分:编译器根据参数个数、类型、顺序来区分不同方法。
- 返回类型无关:仅返回类型不同不足以构成重载,也不是重写,而是编译错误。
- 访问修饰符无关:可以不同。
- 异常无关:可以抛出不同的异常。
- 发生在同一类或继承关系中:子类也可以重载父类的方法(方法名相同但参数不同)。
public class Calculator {
// 参数个数不同
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// 参数类型不同
public double add(double a, double b) {
return a + b;
}
// 参数顺序不同
public void print(String name, int age) {
System.out.println(name + " is " + age);
}
public void print(int age, String name) {
System.out.println("Age " + age + ", name " + name);
}
}
// 不是重载,也不是重写,而是编译错误
// 父类已有 int add(int, int),子类参数列表相同、返回类型不兼容,不构成重写,也不构成重载,属于编译错误。
class AdvancedCalculator extends Calculator {
public double add(int a, int b) {
return (double) a + b;
}
}
重写(Override)
子类重新实现父类中的某个方法,要求方法名、参数列表、返回类型(或父类方法返回类型的子类)完全相同。
重写是运行时多态。
详细说明:
- 注解:推荐使用
@Override,让编译器检查是否正确地重写了父类方法。 - 访问权限:子类重写方法的访问权限不能低于父类方法的权限(例如父类是
protected,子类可以是public)。 - 返回类型:可以返回父类方法返回类型的子类(协变返回类型)。
- 异常:子类方法不能抛出比父类方法更宽泛的检查异常;可以不抛出异常或抛出更具体的异常。
- 构造方法:不能被重写。
- 静态方法:不能被重写。如果在子类中定义了相同的静态方法,那是“隐藏”而不是重写。
final和private:final方法不能被重写;private方法不能被继承,也就不存在重写。- Super 关键字: 子类中调用父类的被重写方法时,要使用 super 关键字。
class Parent {
protected Object getValue() throws Exception {
return "Parent";
}
public static void say() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
public String getValue() { // 返回类型从 Object 变成 String(子类)
try {
super.getValue();
} catch (Exception e) {
e.printStackTrace();
}
return "Child";
}
// 这不是重写,是隐藏
// 静态方法不存在多态,永远根据引用类型决定调用哪个方法。
public static void say() {
System.out.println("Child");
}
}
public class Main {
public static void main(String[] args) {
Parent p1 = new Parent();
Parent p2 = new Child(); // 父类引用指向子类对象
Child c = new Child();
try {
System.out.println(p1.getValue());
} catch (Exception e) {
e.printStackTrace();
}
// 真正调用的方法由引用类型决定,不是对象类型
p1.say(); // Parent
p2.say(); // Parent
c.say(); // Child
}
}
消息解析(Message Parsing / Method Resolution)
在面向对象编程中,“消息”是指对象之间的方法调用。消息解析(或方法解析)是指编译器或运行时如何确定调用的是哪个方法的过程。
Java 中消息解析分为编译时解析(静态绑定)和运行时解析(动态绑定)。
编译时解析(静态绑定):
- 发生场景:
private方法、static方法、final方法、构造方法以及重载方法的调用。 - 解析时机:在编译阶段,编译器根据变量的声明类型(编译时类型)和方法签名(包括参数类型)确定调用的具体方法。
- 特点:效率高,因为调用地址在编译时确定。
运行时解析(动态绑定):
- 发生场景:普通实例方法(非
private、非static、非final)的重写方法调用。 - 解析时机:运行时,JVM 根据对象的实际类型(运行时类型)来确定调用哪个方法。
- 特点:支持多态,但有一定的性能开销(JVM 使用虚方法表优化)。
重载解析:
- 当调用一个重载方法时,编译器会尝试匹配最合适的方法。匹配规则:
- 精确匹配(参数类型完全相同)。
- 自动类型转换(如
int转long,int转double)。 - 装箱/拆箱(如
int转Integer)。 - 可变参数(
...)。
- 如果多个方法匹配,编译器会选择最具体的一个;如果无法确定,则报歧义错误。
重写解析:
- 对于重写的方法,JVM 在运行时通过动态分派决定调用子类还是父类的方法。
- 使用
invokevirtual指令,查找对象的实际类型的方法表。
注解(Annotation)
注解是 Java 5 引入的一种元数据(关于数据的数据),它可以被添加到类、方法、字段、参数等程序元素上,为程序提供额外的信息,但不会直接影响代码的执行。
注解的作用:
编译器指令:告诉编译器如何处理代码。如:@Override、@SuppressWarnings。
编译时处理:生成代码、配置文件等。如:@Data(Lombok)。
运行时处理:通过反射读取注解,执行逻辑。如:@Test(JUnit)、@RequestMapping(Spring)。
Java 内置注解(最常用):
@Override:标记方法是重写父类方法,编译器会检查。@Deprecated:标记方法/类已过时,不推荐使用。@SuppressWarnings:抑制编译器警告。@FunctionalInterface:标记函数式接口(只有一个抽象方法)。@SafeVarargs:抑制堆污染警告。
// src/Main.java
import com.lizhao.annotation.MyAnnotation;
import com.lizhao.processor.MyAnnotationRunner;
public class Main {
public static void main(String[] args) {
MyAnnotationRunner.processAnnotations(Main.class);
}
@MyAnnotation(value = "MyAnnotation1", message = "message")
public static void useMyAnnotation1() {
System.out.println("useMyAnnotation1");
}
}
// src/com/lizhao/annotation/MyAnnotation.java
package com.lizhao.annotation;
import java.lang.annotation.*;
@Target({
ElementType.TYPE, // 类、接口、枚举
ElementType.METHOD, // 方法
ElementType.FIELD, // 字段
ElementType.PARAMETER, // 参数
ElementType.CONSTRUCTOR // 构造方法
})
//@Retention(RetentionPolicy.SOURCE) // 只在源码中
//@Retention(RetentionPolicy.CLASS) // 默认,字节码中存在
@Retention(RetentionPolicy.RUNTIME) // 保留:运行时可见
public @interface MyAnnotation {
// 注解属性(看起来像方法)
String value() default "";
String message() default "";
}
// src/com/lizhao/processor/MyAnnotationRunner.java
package com.lizhao.processor;
import java.lang.reflect.Method;
public class MyAnnotationRunner {
public static void processAnnotations(Class<?> clazz) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(com.lizhao.annotation.MyAnnotation.class)) {
com.lizhao.annotation.MyAnnotation annotation =
method.getAnnotation(com.lizhao.annotation.MyAnnotation.class);
String value = annotation.value();
String message = annotation.message();
System.out.println("========================================");
System.out.println("发现被注解标记的方法:" + method.getName());
System.out.println(" - value 值:" + value);
System.out.println(" - message 值:" + message);
System.out.println("========================================");
try {
// 静态方法:invoke 的第一个参数传 null
method.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}