1.对于类与对象的认识
我们之前学习c语言就是在面向过程。面向过程注重过程是如何实现的。
以洗衣服为例,面向过程的解决思路如下:
面向过程维护或者扩展起来会比较麻烦。
Java是一门面向对象的语言,注重对象间的协作与交互而不需要关注具体细节如何实现。
依旧以洗衣服为例,面向对象的解决思路如下:
此时我们就只需要关注人,衣服,洗衣液,洗衣机之间的关系,不需要关注洗衣机内部如何工作。
2.类的定义及使用
2.1类与对象的初步认识
类是对对象的特性的描述,对象是类的具体实例。
以洗衣机为例,所有洗衣机都有品牌,功率,型号,容量,工作方式等特性。依据这些特性我们才能生产出符合要求的洗衣机。
2.2类的定义格式
定义类需要使用 class
关键字。
class text {
field; //字段/属性/成员变量
method; //方法
}
- class 紧随其后的是类名。{ }内部是类的主体。
- 类包含的内容称为类的成员。
- 属性是用来描述类的,称为类的成员属性或类成员变量。
- 方法用于说明类的功能,称为类的成员方法。
这样我们就可以创建一个洗衣机类:
class WashingMachine{
public String brand;//品牌
public String type;//型号
public double capacity;//容量
public double power;//功率
public void washClothes(){
System.out.println("洗衣功能");
}
public void dryClothes(){
System.out.println("脱水功能");
}
}
注意: 类名命名为大驼峰,方法命名为小驼峰。
3.类的实例化
3.1什么是实例化?
定义一个类后相当于在计算机中创建了一个类型,类似于int , double
。
用类创建对象的过程就成为类的实例化。
Java中使用 new
关键字来实例化对象。
public static void main1(String[] args) {
WashingMachine w1 = new WashingMachine();
w1.washClothes();
w1.dryClothes();
}
注意:
- 类后紧跟对象的命名。
- 使用
.
来访问对象的属性或方法。
运行结果:
3.2 类和对象的说明
- 类相当于模型,用于对一个实体进行描述,限定了成员。
- 类是一种自定义类型,可以定义定义变量。
- 一个类可以实例化多个对象,实例化出的对象占用实际的物理空间,储存类成员变量。
- 做一个比方,类相当于建筑图纸,并没有占用实际空间。而实例化对象相当于照着图纸建造房子,此时可以储存数据。
4.this引用
4.1 为什么要用this引用?
举个例子:构造函数形参与成员变量不同命名时,我们可以很好的区分
class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
year = y;
month = m;
day = d;
}
public void printDate(Date this){
System.out.println(this.year + "/" + this.month + "/" + this.day);
}
}
public class C0507 {
public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
Date d1 = new Date();
Date d2 = new Date();
Date d3 = new Date();
// 对d1,d2,d3的日期设置
d1.setDay(2020, 9, 15);
d2.setDay(2020, 9, 16);
d3.setDay(2020, 9, 17);
// 打印日期中的内容
d1.printDate();
d2.printDate();
d3.printDate();
}
但如果当形参名与成员变量名相同,我们就需要思考:
- 究竟是谁赋值给谁?是成员变量给成员变量?还是参数给参数?
public void setDay(int year, int month, int day){
year = year;
month = month;
day = day;
}
- 三个对象都调用了setDay()和printDate()函数,但是这两个函数没有任何有关对象的说明,那么setDay()是如何知道应该设置哪个对象的信息,printDate()是如何知道应该打印哪个对象的信息??
事实上,编译器不会报错,这是的变量默认为最近小括号内部的变量,这里也就是形参。这相当于自己给自己赋值,是无效的。
为了方便区分局部变量和成员变量,用 this.变量 来指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有的成员变量的操作都可以通过this引用去访问。
public void setDay(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
4.2 this引用的特性
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型。
- this只能在成员方法中使用。
- 在成员方法中,this只能引用当前对象,不可以指向其他对象。
- this是成员方法的第一个隐藏参数,编译器会自动传递,不需要手动编写。当成员方法执行时,编译器会负责将调用成员方法对象的引用传递给成员方法,this负责接收。
5.对象的构造及初始化
Java中未初始化的局部变量不能使用。
这时只需要在创建变量时初始化就可以直接使用。
类中的字段也可以初始化。
5.1构造方法
构造方法是一个特殊的成员方法,名字与类名相同,在创建对象时,由编译器自动调用,并且整个对象的生命周期只调用一次。
注意:
- 名字必须与类名相同,没有返回值,也不可以写
void
- 创建对象由对象自动调用,并且在对象的生命周期只调用一次(相当于人出生,每个人只能出生一次)
- 构造函数的作用是对对象中的成员进行初始化,并不负责为对象开辟空间
- 构造方法可以重载(可以根据需求输入不同参数来调用不同的构造方法)
public class Date {
public int year;
public int month;
public int day;
// 无参构造方法
public Date(){
this.year = 1900;
this.month = 1;
this.day = 1;
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(year + "-" + month + "-" + day);
}
public static void main(String[] args) {
Date d = new Date();
d.printDate();
}
}
上述两个构造方法名字相同,参数列表不同,因此构成了方法的重载。
注意:
- 如果没有显式定义,编译器会自动生成默认的无参的构造方法。
- 一旦用户定义,编译器就不会自动生成。
- 可以用
this
调用其他构造方法来简化代码,this
必须在第一条语句
public class Date {
public int year;
public int month;
public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(1900,1,1);必须是构造方法中第一条语句
public Date(){
//System.out.println(year); 注释取消掉,编译会失败
this(1900, 1, 1);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
- this调用不可以构成环
public Date(){
this(1900,1,1);
}
public Date(int year, int month, int day) {
this();
}//编译报错:Error:(19, 12) java: 递归构造器调用
5.2默认初始化
在新建一个对象后,JVM层面会进行以下工作:
- 检测对象的对应类是否加载,如果没有加载则加载
- 为对象分配空间
- 处理并发安全问题
- 初始化所分配的空间
即:对象空间申请好以后,对象中包含的成员就设置好了初始值
数据类型 | 默认值 |
---|---|
byte | 0 |
char | ‘\u0000’ |
short | 0 |
int | 0 |
long | 0 |
boolean | false |
double | 0.0 |
reference | null |
- 设置对象头信息
- 调用构造方法,为各位各位成员赋值
6.封装
6.1封装的概念
面向对象的三大特性:封装,继承,多态 封装简单来说,就是包装起来,屏蔽细节。
例如:一部手机留给用户的只有充电口,听筒,扬声器等等,内部结构被手机壳封装起来。而手机真正工作时起作用的是内部组件
封装: 将数据和操作数据的方法进行有机结合,吟唱对象的属性和实现细节,仅对外公开接口来和对象进行交互
6.2访问限定符
访问权限用来控制方法或字段能否在类外使用
范围 | private | default | protect | public |
---|---|---|---|---|
同一包中同一类 | ✅ | ✅ | ✅ | ✅ |
同一包中不同类 | ✅ | ✅ | ✅ | |
不同包中的子类 | ✅ | ✅ | ||
不同包中的非子类 | ✅ |
default
是指什么都不写的默认情况。
6.3 包
为了更好的管理类,把多个类组织在一起,成为软件包。有点类似于目录,包的本质就是文件夹。在同一工程中,允许有相同的类名,只要在不同的包中即可。
6.3.1 导入包
Java中已经提供了很多现成的类来给我们使用,例如Date类可以使用java.util.Date
导入java.utill
这个包中的Date
类。
使用 import
语句可以导入包。
import java.util.Date;
如果需要导入包中的其他类,也可以用*
import java.util.*
6.3.2 自定义包
在文件最上方就加上package
语句指定该文件在哪个包中
新建: src -> 新建 -> 软件包
常见的包:
- java.lang :系统基础类(String,Object)
- java.net:进行网络编程开发包
- java.sql:进行数据开发的支持包
- java.util:工具程序包(集合类)
- Java.io:I/O编程开发包
7.static
在Java中被static修饰的成员,称之为静态成员,也称为类成员,不属于某个具体的对象,而是所有对象共享的。
7.1 static修饰成员变量
特性:
- 不属于某个对象,而是类的属性,是所有对象共享的,不存在某个对象的空间中。
- 既可以通过对象来访问,也可以通过类名来访问,但一般更推荐通过类名访问。
- 类变量存储在方法区
- 生命周期伴随类的一生。
7.2 static修饰成员方法
Java中被static
修饰的成员方法称为静态成员方法,是类的方法,不是某个对象特有的。静态成员一般通过静态方法来访问。
特性:
- 不属于某个对象,是类方法
- 可以通过对象调用,也可以通过
类.静态方法
调用。更推荐后者 - 不能在静态方法中直接调用非静态成员变量,可以先创建对象,通过对象引用访问
static
修饰的方法中不能有this
,这两个关键字在逻辑上是冲突的,前者是属于类的,不属依赖对象,后者是某个具体的对象
8.代码块
使用{}
定义的一段代码称为代码块。根据代码块的定义位置和关键字,可以分为以下三种:
- 普通代码块: 定义在方法中的代码块,可以限定局部变量的生命周期和作用域,不经常使用
- 构造代码块/实例代码快: 定义在类中的代码块,不加任何修饰符 。 构造代码块一般用于初始化实例成员变量
- 静态代码块: 使用
static
定义的代码块称为静态代码块,一般用于初始化静态成员变量
执行顺序:
这三类代码块的执行顺序与定义顺序无关,同种类型根据定义顺序依次执行
示例:
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
//实例代码块
{
this.name = "daisy";
this.age = 12;
this.gender = "man";
System.out.println("实例代码执行");
}
// 静态代码块
static {
classRoom = "202";
System.out.println("静态代码执行");
}
// 构造方法
public Student(){
System.out.println("构造方法执行");
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
}
}
运行结果:
注意:
- 静态代码块不论生成了多少对象,都只执行一次
- 静态成员变量是类的属性,因此是JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后顺序依次执行(相当于合并)
- 实例代码块只有在创建对象的时候执行
9.内部类
9.1 什么是内部类
内部类是指将一个类定义在另一个类(外部类)或方法内部的特殊类结构。它是Java封装特性的重要体现,能够让内部结构与外部类形成紧密关联,同时避免与其他类产生命名冲突,让代码组织更具逻辑性。
举个简单的语法示例,如下代码中InnerClass
就是定义在OutClass
中的内部类:
public class OutClass {
// 内部类
class InnerClass{
}
}
// OutClass:外部类
// InnerClass:内部类
注意:
- 独立字节码文件:内部类与外部类共用同一个Java源文件,但编译后会生成单独的字节码文件,命名格式通常为“外部类名$内部类名.class”。
- 非嵌套类不算内部类:若两个类定义在同一个
.java
文件中,但不在彼此的类体内部,它们只是独立的类,并非内部类与外部类的关系。例如:
public class A{
}
class B{
}
// A和B是两个独立类,无内部类与外部类关系
9.2 内部类的分类
根据内部类定义的位置和修饰符不同,可分为成员内部类、局部内部类和匿名内部类。其中成员内部类又分为实例内部类和静态内部类,是日常开发中相对常用的类型。
1. 成员内部类
成员内部类定义在外部类的成员位置(与外部类的属性、方法同级),根据是否被static
修饰,又可细分为实例内部类和静态内部类。
(1) 实例内部类(未被static修饰)
实例内部类依赖于外部类对象存在,只有创建了外部类对象,才能创建实例内部类对象。它可以直接访问外部类的任意成员(包括private
修饰的成员),但当与外部类存在同名成员时,需要通过特定语法区分。
代码示例:
public class OutClass {
private int a; // 外部类私有成员
static int b; // 外部类静态成员
int c; // 外部类普通成员
// 外部类普通方法
public void methodA(){
a = 10;
System.out.println(a);
}
// 外部类静态方法
public static void methodB(){
System.out.println(b);
}
// 实例内部类
class InnerClass{
int c; // 与外部类同名成员
public void methodInner(){
// 1. 直接访问外部类任意成员
a = 100; // 访问外部类私有成员
b = 200; // 访问外部类静态成员
methodA(); // 调用外部类普通方法
methodB(); // 调用外部类静态方法
// 2. 访问同名成员:优先访问内部类自身成员
c = 300;
System.out.println(c); // 输出:300
// 3. 访问外部类同名成员:外部类名.this.成员名
OutClass.this.c = 400;
System.out.println(OutClass.this.c); // 输出:400
}
}
public static void main(String[] args) {
// 创建外部类对象
OutClass outClass = new OutClass();
// 方式1:直接通过外部类对象创建实例内部类对象
OutClass.InnerClass inner1 = outClass.new InnerClass();
// 方式2:简化语法(不推荐,可读性较差)
OutClass.InnerClass inner2 = new OutClass().new InnerClass();
// 调用实例内部类方法
inner2.methodInner();
}
}
核心特性:
- 访问权限:可直接访问外部类任意访问限定符(
private
、default
、protected
、public
)修饰的成员。 - 同名成员处理:当内部类与外部类成员同名时,优先访问内部类自身成员;若需访问外部类同名成员,需使用
外部类名.this.成员名
语法。 - 对象创建依赖:必须先创建外部类对象,才能通过
外部类对象.new 内部类名()
的方式创建实例内部类对象。 - 外部类访问内部类:外部类不能直接访问实例内部类成员,需先创建实例内部类对象才能访问。
(2) 静态内部类(被static修饰)
静态内部类不依赖于外部类对象,属于外部类本身,可直接通过外部类名访问。它的核心限制是:只能访问外部类的静态成员(静态属性、静态方法),无法访问外部类的非静态成员。
代码示例:
public class OutClass {
public int a = 1;
public int b = 2;
public static int c = 3;
//静态内部类
static class InnerClass{
public int d = 4;
public static int e = 5;
public void test() {
System.out.println("静态内部类成员方法");
}
}
}
public class Test {
public static void main(String[] args) {
OutClass.InnerClass inner = new OutClass.InnerClass();
}
}
OutClass.InnerClass
作为一个整体的类型
核心特性:
- 成员访问限制:仅能访问外部类的静态成员,若需访问外部类非静态成员,需先创建外部类对象。
- 对象创建独立:无需依赖外部类对象,直接通过
外部类名.内部类名()
的方式创建对象。 - 访问权限控制:与实例内部类相同,受
public
、private
等访问限定符约束(若被private
修饰,仅外部类可访问)。
2.匿名内部类
匿名内部类是一种没有显式名称的内部类,它通常用于创建只需要使用一次的类实例。匿名内部类可以简化代码,尤其是在需要临时创建接口实现类或子类的场景中非常有用。
interface IA{
void test01();
}
public class Test {
IA a = new IA() {
@Override
public void test01() {
System.out.println("test");
}
};
}
这里可以理解为a实现了接口IA并重写了test01方法
3. 局部内部类
局部内部类定义在外部类的方法体或代码块({}
)内部,作用域仅限于定义它的方法或代码块,日常开发中使用极少,仅需了解基本语法和特性即可。
代码示例:
public class OutClass {
int a = 10; // 外部类成员
public void method(){
int b = 10; // 方法局部变量
// 局部内部类:定义在方法体内部
class InnerClass{
public void methodInner(){
// 可访问外部类成员和方法局部变量
System.out.println(a); // 输出:10
System.out.println(b); // 输出:10
}
}
// 仅能在当前方法体内创建并使用局部内部类对象
InnerClass inner = new InnerClass();
inner.methodInner();
}
public static void main(String[] args) {
OutClass out = new OutClass();
out.method();
// 错误:无法在方法体外访问局部内部类
// OutClass.InnerClass inner = null;
}
}
核心特性:
- 作用域局限:仅能在定义它的方法或代码块内部使用,外部无法访问。
- 无访问修饰符:不能被
public
、static
、private
等修饰符修饰。 - 字节码命名:编译后生成的字节码文件命名格式为“外部类名$数字内部类名.class”(如
OutClass$1InnerClass.class
)。
9.3 内部类的应用场景
虽然内部类在日常开发中使用频率不如普通类,但在特定场景下能显著提升代码优雅性和封装性:
- 实现隐藏逻辑:当某个类仅为外部类服务(如辅助外部类实现特定功能),使用内部类可避免对外暴露该类,减少代码耦合。例如:
ArrayList
中的迭代器类Itr
就是内部类,仅为ArrayList
提供遍历功能。 - 解决多重继承问题:Java不支持类的多重继承,但可通过内部类间接实现——外部类和内部类可分别继承不同的类,从而让外部类间接拥有多个类的特性。
- 简化代码结构:在编写事件监听、线程匿名任务等场景时,匿名内部类(后续讲解)可减少类的定义数量,让代码更紧凑。
总结
- 实例内部类:依赖外部类对象,可访问外部类任意成员,需通过外部类对象创建。
- 静态内部类:不依赖外部类对象,仅能访问外部类静态成员,可直接通过外部类名创建。