5155 字
26 分钟
Java 类与对象

1.定义类#

public class Dog {
String breed;
int size;
String colour;
int age;
void eat() {
}
void run() {
}
void sleep(){
}
void name(){
}
}
  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

注意:

1.一般一个文件当中只定义一个类

2.main方法所在的类一般要使用public修饰

3.public修饰的类必须要与文件名相同

4.不要随意修改public修饰的类的名字

2.创建对象(类的实例化)#

对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 new 来创建一个对象。
  • 初始化:使用 new 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

public class Puppy{
public Puppy(String name){
//这个构造器仅有一个参数:name
System.out.println("小狗的名字是 : " + name );
}
public static void main(String[] args){
// 下面的语句将创建一个Puppy对象
Puppy myPuppy = new Puppy( "Sirens" );
}
}

输出内容为以下:

小狗的名字是:Sirens

3.访问实例(成员)变量与方法#

通过已创建的对象来访问成员方法:

/* 实例化对象 */
Object referenceVariable = new Constructor();
/* 访问类中的变量 */
referenceVariable.variableName;
/* 访问类中的方法 */
referenceVariable.methodName();

实质上在main方法中通过.来访问对象中的变量以及方法

4.构造方法#

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。

下面是一个构造方法示例:

public class Puppy{
public Puppy(){
}
public Puppy(String name){
// 这个构造器仅有一个参数:name
}
}

注意:

this(…)必须是构造方法中第一条语句

5.综上范例#

public class Employee{
private String name;
private int age;
private String designation;//称号
private double salary;
public Employee(String name,int age){
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void setDesignation(String designation){
this.designation = designation;
}
public String getDesignation(){
return designation;
}
public void setSalary(double salary){
this.salary = salary;
}
public double getSalary(){
return salary;
}
// 打印信息
public void printEmployee() {
System.out.println(this);
}
// 重写 toString 方法
@Override
public String toString() {
return "名字: " + name + "\n" +
"年龄: " + age + "\n" +
"职位: " + designation + "\n" +
"薪水: " + salary;
}
}

this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该

引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

  • 在类中访问类的变量时,尽量养成使用this的习惯,防止出现形参赋值给形参的情况

默认初始化#

对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如

数据类型默认值
byte0
char’\u0000’
short0
int0
long0L
booleanfalse
float0.0f
double0.0
referencenull

6.源文件声明规则#

当在一个源文件中定义多个类,并且还有 import 语句和 package 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
  • 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
  • import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

7.封装#

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

7.1访问限定符#

范围privatedefaultprotectedpublic
同一包中的同一类
同一包中的不同类
不同包中的子类
不同包中的非子类

public:可以理解为一个人的外貌特征,谁都可以看得到

default: 对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了

private:只有自己知道,其他人都不知道

说明

protected主要是用在继承中,继承部分详细介绍

default权限指:什么都不写时的默认权限

访问权限除了可以限定类中成员的可见性,也可以控制类的可见性

7.2实现Java封装的步骤#

1.修改属性的可见性来限制对属性的访问(一般限制为private),例如:

public class Person {
private String name;
private int age;
}

这段代码中,将 nameage 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。

2.对每个值属性提供对外的公共方法访问,也就是创建一对赋取值的方法,用于对私有属性的访问,例如:

public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}

采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。

以上实例中public方法是外部类访问该类成员变量的入口。

通常情况下,这些方法被称为getter和setter方法。

因此,任何要访问类中私有成员变量的类都要通过这些gettersetter方法。

8.Java包(Package)#

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间

包的作用

  • 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

package pkg1[.pkg2[.pkg3…]];

8.1 创建包#

基本规则

在文件的最上方加上一个 package 语句指定该代码在哪个包中.

包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 io.github.sirens007 )

包名要和代码路径相匹配. 例如创建io.github.sirens007的包, 那么会存在一个对应的路径 io.github.sirens007来存储代码.

如果一个类没有 package 语句, 则该类被放到一个默认包中.

即图中软件包,点击后输入 io.github.sirens007则会创建一个三级目录

8.2 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 io.github.sirens007;

这样,你就可以在当前源文件中直接使用 sirens007 类的方法、变量或常量。

也可以使用通配符 * 来引入整个包或包的子包:

import io.sirens007.mypackage.*;

这样,你可以导入 io.sirens007.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 包中的所有类

8.2.1 常见的包#

1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。

2. java.lang.reflect 反射编程包;

3. java.net:进行网络编程开发包。

4. java.sql:进行数据库开发的支持包。

5. java.util:java提供的工具程序包。(集合类等) 非常重要

6. java.io/O编程开发包。

8.3 Package 的目录结构#

类放在包中会有两种主要的结果:

  • 包名成为类名的一部分,正如我们前面讨论的一样。
  • 包名必须与相应的字节码所在的目录结构相吻合。

下面是管理你自己 java 中文件的一种简单方式:

将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:

Car.java
package vehicle;
public class Car {
// 类实现
}

接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。

....\vehicle\Car.java

现在,正确的类名和路径将会是如下样子:

  • 类名 -> vehicle.Car
  • 路径名 -> vehicle\Car.java (在 windows 系统中)

通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是 runoob.com,所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。

例如:有一个 io.github.test 的包,这个包包含一个叫做 Hello.java 的源文件,那么相应的,应该有如下面的一连串子目录:

Terminal window
....\io\github\test\Hello.java

编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀。 例如:

Hello.java
package io.github.test;
public class Hello {
}
class Google {
}

现在,我们用-d选项来编译这个文件,如下:

$javac -d . Hello.java

这样会像下面这样放置编译了的文件:

Terminal window
.\io\github\test\Hello.class
.\io\github\test\Google.class

你可以像下面这样来导入所有 \io\github\test\ 中定义的类、接口等:

import io.github.test.*;

编译之后的 .class 文件应该和 .java 源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。

Terminal window
<path-one>\sources\io\github\test\Hello.java
<path-two>\classes\io\github\test\Google.class

这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到你程序中使用的所有类型。

9.static 成员#

9.1static 修饰成员变量#

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的

它与类相关而不是与实例相关,即无论创建多少个类实例,静态变量在内存中只有一份拷贝,被所有实例共享。

重点:类的静态成员变量特性

1.不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中

2.既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问

3.静态类变量存储在方法区中

4.生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)

类的加载和卸载

加载(Load):当你第一次使用这个类(比如 new、访问静态变量/方法)时,JVM 的类加载器会把它加载到 方法区

卸载(Unload):当一个类不再被任何地方使用时,JVM 才有可能把它卸载。

9.2static 修饰成员方法#

Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。

重点:静态方法特性

1.不属于某个具体的对象,是类方法

2.可以通过对象调用,也可以通过 类名.静态方法名(...)方式调用,更推荐类名调用

3.不能在静态方法中访问任何非静态成员变量

4. 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用

5. 静态方法无法重写,不能用来实现多态(此处大家暂时不用管,后序多态位置详细讲解)。

补充第三点:

  • 静态变量/方法(static)
    属于 类本身,不依赖某个对象,类一加载就存在。
    调用方式:类名.方法() 或者 对象.方法()(其实推荐用类名)。
  • 非静态变量/方法(实例成员)
    属于 对象实例,必须通过 new 出来的对象才会存在。
    调用方式:对象.方法()

**Q1:为什么静态方法不能访问非静态变量呢 **

因为静态方法执行的时候,对象可能还没有创建

eg:

class Demo {
int age = 10; // 非静态变量(实例变量)
static int count = 0; // 静态变量
static void printInfo() {
System.out.println(age); // ❌ 报错!
}
}

运行逻辑:

  1. JVM 加载 Demo 类时,count 已经存在(方法区 / 元空间里)。
  2. 但是 age 是实例变量,只有在执行 new Demo() 时,才会分配内存到堆里。
  3. static void printInfo() 可能在没有任何对象的时候被调用,这时 JVM 不知道该取哪个对象的 age

所以编译器直接禁止你在静态方法里访问 age

Q2:. 静态方法中为什么不能调用任何非静态方法?

我们先复习一下静态和非静态方法

静态方法
属于类本身,类加载时就存在,不依赖对象。调用时 JVM 直接用「类信息」执行,不需要 new 对象。

非静态方法
属于对象实例,必须依赖对象才能调用。JVM 在调用时,默认会传入一个隐藏的 this 指针,告诉方法「我是谁」,这样方法才能访问对象里的实例变量。

他们的关键区别在于是否有this

调用非静态方法时,编译器会在字节码层面自动加上 this
比如写:

obj.sayHello();

编译后相当于:

Demo.sayHello(obj);

调用静态方法时,不存在 this,因为它不依赖对象。

Demo.staticHello();

编译后就是单纯的 Demo.staticHello(),没有对象上下文

因此静态方法里没有this,所以编译器根本不知道你想让哪个对象去执行这个非静态方法。

解决办法:

方法一:在静态方法里创建对象

static void test() {
Demo d = new Demo();
d.sayHello(); // ✅ 通过对象调用非静态方法
}

方法二:把目标方法也改成静态

static void sayHello() {
System.out.println("Hello from static method");
}
static void test() {
sayHello(); // ✅ 直接调用
}

总结就是: **静态方法不能调用非静态方法,是因为静态方法没有 this,而非静态方法必须依赖 this 才能运行 **

9.3static 成员变量初始化#

初始化方法有以下:

1.就地初始化

2.通过get、set方法初始化

3.构造方法初始化(相对较少)

4.代码块初始化

10.代码块#

10.1 代码块分类#

使用{}定义的一段代码称为代码块,根据代码块定义的位置以及关键字,又可分为以下四种:

  • 静态代码块(一般用于初始化静态成员变量)
  • 非静态代码块/实例代码块/构造代码块
  • 同步代码块…

代码块执行顺序#

静态代码块 -> 实例代码块 -> 构造方法

注意: 静态代码块只会执行一次(涉及知识点:双亲委派模型

补充:如果静态/实例代码块有平级,那么就和他们各自类型的先后顺序有关系

构造方法主要看调用了的哪个构造

注意事项

  • 静态代码块不管生成多少个对象,其只会执行一次
  • 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
  • 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
  • 实例代码块只有在创建对象时才会执行
public class Main{
String number = "123";
{
number = "125";
System.out.println("eg coder");
}
Main(String number){
this.number = number;
System.out.println("struction func");
}
static{
String number = "124";
System.out.println("static coder");
}
}
//输出结果:
//static coder
//eg coder
//struction func

对象的打印#

平常我们直接打印对象时,会直接输出对象名+ @ +一串字符 这实际上是toString默认方法

如图下

但当我们自己写上了toString方法的时候,就会输出我们自己想要的内容了

快捷键:Alt+Insert选中toString即可(静态方法默认不输出,需要可自己补充)

Java 类与对象
https://sirens007.github.io/posts/class-object/
作者
Sirens007
发布于
2025-09-22
许可协议
CC BY-NC-SA 4.0