Java面向对象解析(封装、static、代码块)
前言
- 类:类是一个模板,它是描述一类对象的属性和行为。
- 对象:对象是类的一个实例,有属性和行为。例如:一只猫是一个对象,它的属性有:名字、颜色、品种;行为有:抓老鼠、喵喵叫、吃饭等。
下图中汽车为类(class),具体的每辆车为汽车类的对象(object),对象包含了汽车的颜色和品种等。
面向对象
- Java是一门纯面向对象的语言(Object Oriented Program)。
- 面相对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。
面向对象和面向过程
面向过程
面向过程注重的是操作过程中的因果逻辑。
例如:上学
- 面向过程中我们需要去研究公交车的一系列运行活动,还要去考虑路上发生的各种状况之间的因果关系。
面向对象
面向对象注重的是对象和对象之间的交互。
例如:上学
- 面向对象我们只需要关注人乘坐公交车对象到达学校,至于公交车如何运行我们不需要关注。
Java中类的定义
- Java中使用关键字
class
来定义类。
public class Cat {
String name; // 名字
String color; // 毛色
int age; // 年龄
String breed; // 品种
void eat() {
System.out.println("吃饭");
}
void sleep() {
System.out.println("睡觉");
}
void play() {
System.out.println("玩耍");
}
void catchMouse() {
System.out.println("抓老鼠");
}
}
一个类中可以包含以下变量类型:
- **局部变量:**在方法中定义。变量的声明和初始化都在方法内,方法结束,变量自动销毁。
- **成员变量:**在方法外,类的内部定义。成员变量在创建对象时实例化,每个对象有单独属于自己的成员变量。成员变量可以被类中的方法、构造方法和特定类的语句块访问。
- 类变量:声明在类的内部,方法的外部,必须是被
static
修饰。
一个类可以拥有多个方法。
类的实例化
类就相当于一种新的自定义类型,和
Java
中的基本数据类型(int
、double
)相似,而创建这种自定义类型对象,我们称为实例化。
Java
中使用关键字new
来实例化对象。
public class CatTest {
public static void main(String[] args) {
Cat c = new Cat();
c.name = "黑旗";
c.age = 3;
c.breed = "奶牛猫";
c.color = "黑色";
c.eat();
c.catchMouse();
}
}
- 如上述代码所示,通过实例化的对象
c
来调用成员变量,给成员变量赋值,也可以调用其成员方法。
构造方法
Java
中,创建对象至少要调用一个构造方法,我们在实例化Cat
对象时,new Cat()
就是在调用public Cat(){}
构造方法。但显然我们并没有写任何构造方法,Java
会自动帮我们生成一个无任何参数的构造方法。
public class Cat {
public Cat() {
System.out.println("没有参数的构造方法");
}
}
public class CatTest {
public static void main(String[] args) {
Cat c = new Cat();
}
}
// 输出结果:
// 没有参数的构造方法
- 由此可以得出结论,当我们自己写了无任何参数的构造方法,
Java
就会调用我们自己写的构造方法来实例化对象。
public class Cat {
public Cat(String name) {
System.out.println("带一个参数的构造方法");
}
}
public class CatTest {
public static void main(String[] args) {
Cat c = new Cat();
}
}
// 输出结果:
// java: 无法将类 com.max.classes.Cat中的构造器 Cat应用到给定类型;
// 需要: java.lang.String
// 找到: 没有参数
// 原因: 实际参数列表和形式参数列表长度不同
- 如上述代码可见,当我们提供带有一个参数的构造方法,但是向通过不带参数的构造方法实例化对象时,编译是不能通过的,因为当我们提供一个构造方法时,编译器就不会再为我们提供默认的构造方法了,我们只能调用自己编写的特定构造方法来实例化对象。
public class CatTest {
public static void main(String[] args) {
Cat c = new Cat("小花");
}
}
// 输出结果:
// 带一个参数的构造方法
构造方法的特性
- 名字必须和类名相同。
- 没有返回值,也不能设置为
void
。 - 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次。
- 构造方法可以重载。
- 如果没有显示定义,编译器会生成默认的构造方法,而且一定是无参数的。
- 构造方法中,可以通过
this
调用其他构造方法来简化代码。- 注:
this()
必须是构造方法中的第一条语句。 - 不能形成环。
- 注:
public class Cat {
public Cat() {
this("小花", "花色", 2, "三花猫");
}
public Cat(String name, String color, int age, String breed) {
name = name;
color = color;
age = age;
breed = breed;
}
}
- 绝大多数情况下,使用
public
进行修饰,特殊场景下会被private
修饰。
this引用
- 构造方法可以帮助我们在实例化对象时,就对对象的成员变量赋值。但若带有参数的构造方法中的形式参数名和成员变量名相同时:
public class Cat {
public Cat(String name, String color, int age, String breed) {
name = name;
color = color;
age = age;
breed = breed;
}
}
public class CatTest {
public static void main(String[] args) {
Cat c = new Cat("小花", "花色", 2, "三花猫");
System.out.println(c.name + " " + c.color + " " + c.age + " " + c.breed);
}
}
// 输出结果:
// null null 0 null
- 由于局部变量优先原则,构造方法中并没有成员变量,因此并没有对成员变量赋值。这就需要
this
引用来帮我们区分成员变量和局部变量。
实际上,在类内部的方法中,都有一个隐藏的形参,编译器为我们提供的对象的引用。
public Cat(Cat this, String name, String color, int age, String breed) {}
这个
Cat this
就是编译为我们提供的,指代当前的对象。
- 因此我们构造方法就可以写成一下样式:
public class Cat {
public Cat(String name, String color, int age, String breed) {
this.name = name;
this.color = color;
this.age = age;
this.breed = breed;
}
}
public class CatTest {
public static void main(String[] args) {
Cat c = new Cat("小花", "花色", 2, "三花猫");
System.out.println(c.name + " " + c.color + " " + c.age + " " + c.breed);
}
}
// 输出结果:
// 小花 花色 2 三花猫
- 通过调试我们也可以看出
this
和对象c
地址相同,说明this
就是该对象。
this引用的特性
this
只能在成员方法中使用。- 在成员方法中,
this
只能引用当前对象,不能再引用其他对象。 this
是成员方法第一个隐藏的参数,编译器会自动传递。
默认初始化
public static void main(String[] args) {
int a;
System.out.println(a);
}
// error:java: 可能尚未初始化变量a
- 当局部变量声明,但未初始化和赋值时,编译会报错。但我们发现成员变量未赋值时,编译器不会报错,我们照常可以使用该成员变量,这是因为编译器会自动对成员变量赋初始值。如下表所示:
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0 |
boolean | false |
char | ‘\u0000’ |
引用数据类型 | null |
封装
面向对象三大特性:封装、继承、多态。
- 封装就是隐藏细节。例如我们的手机,我们在不拆开手机的情况下是无法看到主板的;再比如电视,电视的一系列程序我们是不知道的,我们只需要使用遥控器就可以操作电视。这就是程序员对程序进行了封装,只提供特定的接口来供使用者使用,以保证程序不被破坏。
访问修饰限定符
- 在我们最开始学习
Java
,接触的第一个方法main
方法,就会看到有public
关键字,这就是访问修饰限定符。 Java
中一共有四种访问修饰限定符:private
:私有的。default
:默认的,什么都不写时的默认权限。protected
:受保护的。public
:公有的。
序号 | 范围 | private | default | protected | public |
---|---|---|---|---|---|
1 | 同一包中的同一类 | ✅ | ✅ | ✅ | ✅ |
2 | 同一包中的不同类 | ✅ | ✅ | ✅ | |
3 | 不同包中的子类 | ✅ | ✅ | ||
4 | 不同包中的非子类 | ✅ |
包的概念
为了更好的管理类,把多个类放在一组,称为软件包。
在同一个工程中,允许存在相同名称的类,只要处于不同的包中即可。
导入包中的类
Java
中提供了很多现成的类供我们使用,如Date
类,它在java.util
这个包下,我们要想使用,就需要导入这个包。
- 使用包名+类实例化对象。
java.util.Date date = new java.util.Date();
- 使用
import
导入包。
import java.util.Date;
Date date = new Date();
static成员
当我们创建中国人这个类时,除了名字、年龄、身份证号等属性外,还应该有国籍这个属性。但既然是中国人,国籍一定是中国,这就没有必要让每一个对象都单独有一份数据,只需要把国籍这个属性变成类的属性即可。
static修饰成员变量
static
修饰成员变量,称为静态成员变量/类变量,它不再属于某个具体的对象,而是所有对象共享的,是属于类的。
public class ChinaPerson {
private String name;
private int age;
private String id;
public static String nation = "中国";
}
public class ChinaPersonTest {
public static void main(String[] args) {
ChinaPerson chinaPerson1 = new ChinaPerson();
ChinaPerson chinaPerson2 = new ChinaPerson();
ChinaPerson chinaPerson3 = new ChinaPerson();
ChinaPerson chinaPerson4 = new ChinaPerson();
ChinaPerson chinaPerson5 = new ChinaPerson();
System.out.println();
}
}
如图可知,对象中没有nation
这个属性,说明nation
不再属于对象。
System.out.println(ChinaPerson.nation); //输出:中国
- 静态成员变量/类变量通过
类名.
的方式调用
特性
- 不属于某个具体的对象,是类的属性,所有对象共享,不存储在某个对象的空间中。
- 既可以通过对象访问,也可以通过类名访问,但一般推荐使用类名访问。
- 类变量存储在方法区中。
- 随类的加载而创建,随类的卸载而销毁。
static修饰成员方法
- 当我们将静态成员变量用
private
修饰,就不能在该类外访问该变量,因此我们需要提供接口来访问修改它。 - 静态成员变量一般通过静态成员方法来访问。
private static String nation = "中国";
public static String getNation() {
return nation;
}
public static void setNation(String nation) {
ChinaPerson.nation = nation;
}
特性
- 静态成员方法不能访问成员变量,因为静态成员方法属于类方法,不需要创建对象就可以访问,因此没有对象概念。
- 静态方法中不能调用非静态方法,原因如上。
- 可以通过对象调用,也可以通过类名调用,更推荐使用类名调用。
- 不属于某一个具体的对象,而是属于这个类的方法。
代码块
{}
定义的一段代码就称为代码块。根据定义的位置和关键字,可分为四种:
- 普通代码块
- 构造代码块
- 静态代码块
- 同步代码块
普通代码块
- 普通代码块:定义带方法中的代码块。
public class Test {
public static void main(String[] args) {
{// 普通代码块
int x = 20;
System.out.println(x);
}
int x = 10;
System.out.println(x);
}
}
构造代码块
- 构造代码块:也叫实例代码块,一般用于初始化成员变量。
public class Cat {
private String name; // 名字
String color; // 毛色
protected int age; // 年龄
public String breed; // 品种
{
System.out.println("这是一个构造代码块");
name = "小花";
color = "花色";
age = 2;
breed = "三花猫";
}
public Cat() {
}
public Cat(String name, String color, int age, String breed) {
this.name = name;
this.color = color;
this.age = age;
this.breed = breed;
}
}
public class CatTest {
public static void main(String[] args) {
Cat c1 = new Cat();
Cat c2 = new Cat();
Cat c3 = new Cat();
Cat c4 = new Cat();
Cat c5 = new Cat();
}
}
// 输出结果:
// 这是一个构造代码块
// 这是一个构造代码块
// 这是一个构造代码块
// 这是一个构造代码块
// 这是一个构造代码块
- 由此可见,构造代码块是实例化一个对象就调用一次的。
静态代码块
- 静态代码块:
static
定义的代码块,一般用于初始化静态成员变量。
public class ChinaPerson {
private String name;
private int age;
private String id;
private static String nation;
static {
nation = "中国";
System.out.println("这是一个静态代码块");
}
}
public class ChinaPersonTest {
public static void main(String[] args) {
ChinaPerson p1 = new ChinaPerson();
ChinaPerson p2 = new ChinaPerson();
ChinaPerson p3 = new ChinaPerson();
ChinaPerson p4 = new ChinaPerson();
ChinaPerson p5 = new ChinaPerson();
}
}
// 输出结果:
// 这是一个静态代码块
- 由此可见,静态代码块不管生成多少个对象,只会执行一次。
- 静态成员变量是类的属性,因此在JVM加载类时开辟空间并初始化。
- 如果一个类包含多个静态代码块时,在编译代码时,编译器会按照定义的先后顺序执行。
- 实例代码块只有在创建对象时才会执行。