6 -类和对象详解

一.面向对象

1.1 什么是面向对象

Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。 面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。

1.2 面向对象和面向过程

拿洗衣服来举例:

面向过程

拿盆子--》放水--》放衣服--》放洗衣粉--》手搓--》换水--》放洗衣粉--》手搓--》拧干--》晾衣服

按照该种方式来写代码,任何一个环节都不能出错,否则要改写整串代码。将来扩展或者维护起来会比较麻烦

面向对象

喊:“妈,我衣服该洗了!”

孝顺的孩子

总共有三个对象:我,衣服,妈

整个洗衣服的过程就是,我负责衣服交给妈妈,妈妈负责去洗

整个过程是对象之间交互完成的,而不需要关心洗衣服的过程。

二.类的定义

2.1 什么是类

类是用来对一个实体(对象) 进行描述的,实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干
什么),描述完成后计算机就可以识别了

比如狗类

属性:什么品种,什么颜色,体重大小
功能:撒娇,看家,吃饭

2.2 类的定义

在java中定义类时需要用到class关键字,具体语法如下

class ClassName{

field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法

}

1.类中包含的内容称为类的成员。

2.属性主要是用来描述类的,称之为类的成员属性或者类成员变量。

3.方法主要说明类具有哪些功能,称为类的成员方法。

举例,定义一个狗类

class dog{
//成员属性
public String racial;
public String color;
public int height;


//成员方法
public void play() {
System. out.println("撒娇");
}

public void quard() {
System. out.println("看家");
}

public void eat() {
System. out.println("吃饭");
}
}

注意事项

  • 定义类名一般使用大驼峰,方法名和成员变量名使用小驼峰

  • 此处成员前面写法统一加上public,且不带static(下文解释)

我们再定义一个学生类

class Student {
public String name;
public int age;

public void doHomework() {
System. out.println("做家庭作业");
}
public void getName() {
System. out.println("My name is"+name);
}
}

此时就会发现out目录对应的文件夹下生成了三个字节码文件

因此是一个类对应一个.class文件

注意

1.public修饰的类名必须要和文件名相同

2.一般一个文件只定义一个类

3.不能轻易修改public修饰的类名,如果要修改就和文件名一起修改

用idea更改文件名的方式如下图

一个.java文件只有一个public类

一个.java文件有多个类

4.多个类可以有多个main函数

三.类的实例化

3.1 什么是实例化

定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型。

用类类型创建对象的过程,称为类的实例化。

1.在Java中实例化需要用到new关键字

2.一个类可以实例化多个对象

如下例

class Student {
public String name;
public int age;

public void doHomework() {
System. out.println("做家庭作业");
}
public void getName() {
System. out.println("My name is "+name);
}

public static void main(String[] args) {
Student st1=new Student();//用new实例化学生1
st1.name="李雷";
st1.age=18;
st1.getName();

Student st2=new Student();
st2.name="韩梅梅";
st2.age=18;
st2.doHomework();
}
}

上图的代码中可以看到用new关键字实例化出的对象通过赋值符“=”传给引用变量,引用变量可以用“.”访问该对象的成员

3.2 类和对象的关系

1. 类只是一个 模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.
2. 类是一种自定义的类型,可以用来定义变量.
3. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量

如下图,用户自定义了一个房子类,它就相当于一张图纸,根据这张图纸建造出来了房子,就是房子类的实例化

四.this引用

4.1 为什么要有this引用

如下例

在学生类中定义一个成员函数,用来给学生类成员变量赋值,然后再把它打印出来

class Student {
public String name;
public int age;

public void doHomework() {
System. out.println("做家庭作业");
}

//定义set函数更改成员变量
public void setInformation(String name,int age) {
name=name;
age=age;
}

//定义get函数获取成员变量
public void getInformation()
{
System. out.println("My name is "+name+" and my age is "+age);

}
public static void main(String[] args) {
Student st1=new Student();
st1.setInformation("李雷",18);
st1.getInformation();

Student st2=new Student();
st2.setInformation("韩梅梅",18);
st2.getInformation();
}
}

结果发现并不是预料中的那样

这是因为局部优先原则

实际上赋值符的左右两遍都是我们传入的参数,而不是成员变量

如果想解决这个问题,有两种办法,一是给形参改个名字,二是在成员变量前面加上this.

public void setInformation(String name,int age) {
        this.name=name;
        this.age=age;
}

运行结果

上文中已经提到,实际上成员方法并没有存储在对象中,定义的时候也没有传入对象的信息,这个方法怎么知道要操作谁的成员变量呢?

public static void main(String[] args) {
Student st1=new Student();
st1.setInformation("李雷",18);
st1.getInformation();

Student st2=new Student();
st2.setInformation("韩梅梅",18);
st2.getInformation();
}

对象st1和对象st2都调用了getInformation函数

public void getInformation()
{
System. out.println("My name is "+name+" and my age is "+age);

}

但是getInformation函数在定义的时候不知道哪个对象会使用它

this就是这个问题的答案

4.2 什么是this引用

this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过不需要用户来传递,由编译器自动完成。

有点懵?看下面代码就一目了然了

Student st1=new Student();
st1.setInformation("李雷",18);
st1.getInformation();

上面的代码在编译器看来是这个样子的

st1.getInformation(Student this);

对象在调用成员方法的时候会把自己的引用传给该方法,但是getInformation方法并没有这个参数呀,实际上是编译器帮忙隐藏了

把编译器的小动作展开就是下面这样

public void getInformation(Student this)
{
System. out.println("My name is "+this.name+" and my age is "+this.age);

}

4.3 this引用的特性

1.this的类型,哪个类型的对象调用就是哪个对象的引用类型

2.this只能在非静态成员方法中使用(与static有关,下文会解释)

3.在成员方法中,this只会是当前对象的引用,不会引用其他对象

4.this是非静态成员方法的第一个隐藏的参数,在该成员方法执行时,this由编译器自动传递

你看到的代码VS编译器看到的代码

你看到的代码

编译器看到的代码

建议在成员方法体内,每一个要操作的成员变量前面都加上this.

五.对象的构造及初始化

每次创建一个对象都要调用set函数设置它的信息,感觉很麻烦,可不可以让它在创建的时候自己初始化呢?

勤劳的编译器回答:Of course!

5.1 构造方法

构造方法是一个特殊的成员方法,见下表

普通方法

构造方法

返回值

可自定义返回类型

没有返回值,方法名前面加void也不行

方法名

可自定义方法名

只能是类名

调用时间

可以在对象创建后随意调用

只能在创建对象时调用一次

是否可以重载

可以

可以

来写一下学生类的构造方法

class Student {
public String name;
public int age;

//定义了不带参数的构造方法
public Student() {
this.name="王五";
this.age=18;
System. out.println("调用了不带参数的构造");
}

//定义了带两个参数的构造方法
public Student(String name,int age) {
this.name=name;
this.age=age;
System. out.println("调用了带参数的构造");
}

//输出学生信息
public void getInformation()
{
System. out.println("My name is "+this.name+" and my age is "+this.age);

}


public static void main(String[] args) {
Student st1=new Student();//会调用不带参数的构造
Student st2=new Student("韩梅梅",18);//会调用带参数的构造

st1.getInformation();
st2.getInformation();
}

}

来看运行结果验证

可以通过构造方法重载,根据用户的不同需求调用不同的构造方法

5.2 构造方法的特性

特性一.如果用户没有显式定义构造方法,编译器会默认生成一份不带参数的构造方法(什么也没干),一旦用户定义了构造方法,编译器不会生成

比如定义一个狗类(不能可着学生造)

class Dog{
    public String name;
    public String color;
    public int age;
}

在上面的代码中,没有定义任何一个构造方法,编译器会帮助生成一个类似于下面的构造方法

//编译器默认生成的构造方法
Dog() {
}

如果定义了任何一个构造方法,编译器就不会再生成

lass Dog{
    public String name;
    public String color;
    public int age;
    
   //定义了一个带三个参数的构造方法
    Dog(String name,String color,int age) {
        this.name=name;
        this.color=color;
        this.age=age;
    }
   
    public static void main(String[] args) {
        Dog kitty=new Dog("kitty","pink",2);//在创建对象的时候传入参数
        Dog piter=new Dog();//报错,已经显式定义了构造方法
                            //编译器不会再生成无参构造
                           //因此创建对象的时候必须传参
    }
}

特性二.在构造方法中,可以通过this调用其他方法来简化代码

this引用的三种用途

this+成员变量

操作当前对象的成员

this+成员方法

调用该方法

this(参数列表)

只能在构造方法中使用,用来调用另一个构造方法


所以,无参的构造方法可以这样使用

public Student() {//无参构造,默认姓名是王五,年龄是18
//this.name="王五";
//this.age=18;
this("王五",18);//在不带参数的构造方法里调用带参数的构造
}

public Student(String name,int age) {
this.name=name;
this.age=age;
}
public static void main(String[] args) {
   Student st1=new Student();
    }

上面代码的执行顺序如下:

但是this(...)这条语句的使用场景有诸多限制

1.只能在构造方法中调用

Student() {
this.name="wangwu";
this.age=18;
}
public void getInformation()
{
this();// error,不可以在普通方法中调用构造函数
System. out.println("My name is "+this.name+" and my age is "+this.age);
}

2.只能放在构造方法的第一行

Student() {
System. out.println("无参构造");
this("王五",18);// error,调用构造函数的this语句只能放在第一行
}
Student(String name,int age) {
this.name=name;
this.age=age;
}

3.不能形成环

Student() {

this("王五",18);
}

Student(String name,int age) {
this();//不可以,两个构造方法无限调用
}

Student() {

this();//不可以,不能自己调用自己
}

5.3 成员变量的初始化

来看下面一组代码

class Student {
public String name;
public int age;

public void getInformation()
{
System. out.println("My name is "+this.name+" and my age is "+this.age);

}
public static void main(String[] args) {
Student st1=new Student();//实例化对象
st1.getInformation();//输出该对象的成员变量
}

}

是不是敏锐地察觉到,成员变量并没有初始化,直接输出会报错?

这里就要谈到局部变量和成员变量的区别了,放表!!!

局部变量

成员变量

定义方式

定义在方法内的变量

定义在类内方法外的变量

生存周期

只存在于方法的栈帧中

从对象的创建到对象的销毁

是否自动初始化

不会,必须先初始化再使用

可以进行默认初始化或者就地初始化

5.3.1 默认初始化

Student st=new Student();

在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:

1. 检测对象对应的类是否加载了,如果没有加载则加载

2. 为对象分配内存空间

3. 处理并发安全问题

比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突

4. 初始化所分配的空间,这一过程就是默认初始化

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

数据类型

默认值

byte

0

char

'\u0000

short

0

int

0

long

0

boolean

false

float

0.0f

double

0.0

引用类型

null

5. 设置对象头信息(关于对象内存模型后面会介绍)

6. 调用构造方法,给对象中各个成员赋值

5.3.2 就地初始化

在声明成员变量时就给出了初始值,这种方式称为就地初始化

class Student {
public String name="大圣";
int age=10000;
}

这种方式相当于在构造函数执行前先初始化成员变量

六.封装

6.1 封装的概念

面向对象程序三大特性:封装、继承、多态

下面来详细讲解一下封装,何为封装?简单来说就是套壳屏蔽细节

就好像平时使用的手机,身为一个用户,我们的基本素养是知道怎么用它来打游戏,刷视频,追剧,而不需要关心它是怎么把游戏界面投射到屏幕上的,是怎么放出音乐的

实际上,这就是手机的封装

Java中,封装通过访问限定符private实现

6.2 访问限定符

使用范围

private

默认

protected

public

同一包中的同一类

yes

yes

yes

yes

同一包中的不同类

yes

yes

yes

不同包中的子类

yes

yes

不同包中的非子类

yes

  1. private表示该成员只能用在当前类中

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

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

  1. public没有使用范围的限制

在理解访问限定符的权限之前,先来看看什么是包

6.3 包

6.3.1 包的概念

在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。简单来说,包就是一个文件夹,里面装的是类

比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。

包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。

包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可

6.3.2 导入包中的类

Java提供了很多包供我们使用,例如Date类,Arrays类

可以通过下面的方式导入

方法一:import 包名.类名

import java.util.Arrays;//导入util包下的Arrays类
import java.util.Date;//导入util包下的Date类

public class test {
public static void main(String[] args) {

int[] array={2,5,8,9,0};
Arrays. sort(array); //使用Arrays类的方法

Date date=new Date();//创建Date对象

}
}

对于静态的成员要使用import static 包名.类名导入

import static java.lang.Math.sqrt //sqrt是静态方法

public class test {
public static void main(String[] args) {

double d= sqrt(16);
System. out.println(d);
}
}

方法二:import 包名.*

导入静态成员同样需要加static标识符

其中*是通用符,用到了哪个类就充当哪个类,而不是导入这个包的全部路径

import java.util.*;

public class test {
public static void main(String[] args) {

int[] array={2,5,8,9,0};
Arrays. sort(array); //*在此处充当Arrays类

Date date=new Date();//*在此处充当Date类

}
}

不同包下的不同类可以是同名的,如果把它们都导入,需要用完整的类名显式指定究竟使用哪一个类

比如util和sql包下都有Date类

import java.util.*;
import java.sql.*;

public class test {
public static void main(String[] args) {
java.util.Date date=new java.util.Date();//需要指明
}
}

注意事项: import 并不是C语言中的#include,如果导入的包没有使用,不会在编译时像头文件那样展开,import只是为了写代码的时候更方便

6.3.3 自定义包中的类

下面自定义包的方式是在idea上操作的

可以用package创建一个包

注意事项:

1.包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.Baidu.www ).当然也可以自己定义

2.包名和代码路径相匹配. 例如创建 com.Baidu.www 的包, 那么会存在一个对应的路径com/Baidu/www 来存储代码.比如刚刚创建了一个com.Wuyang的包,就会存在这样一个路径

在com包和Wuyang包中分别创建类test1和test2

就会出现下面的一行package+包名的代码,用来声明该类放在哪个包下面


3.直接从src创建一个类,该类会被放在一个默认包中,开头没有package语句,并且这个类不能被导到其他文件里

4.包名要全部小写,此处的com.Wuyang是错误示范!

6.3.4 常见的包

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

2. java.lang.reflflect:java 反射编程包;

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

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

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

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

6.4 详解三种访问权限

下文主要说明private,默认和public(protected继承时说明阐述)

private

由该访问限定符修饰的成员只能在当前类中使用

class Dog {
private String name="旺财";
private int age=2;

public void play() {
System. out.println("汪汪~");
}
}
public class test1 {
public static void main(String[] args) {

Dog pet=new Dog();//创建一只宠物狗

System. out.println(pet.name);//error,该成员只能在Dog内部使用

pet.play();//可以正常输出“汪汪~”
}
}

那么该如何操作这个类的成员变量呢?可以定义多个方法

class Dog {
    private String name;
    private int age;

    public void play() {
        System.out.println("汪汪~");
    }

    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;
    }
}

通过调用public修饰的方法就可以更改或查找成员变量的值

idea也提供了快捷键帮助我们生成set和get方法

鼠标右键--》generate--》getter或setter或两种都选

默认权限

可以在同一包下的不同类内使用

Cat类和test1在同一个包下
####################################
public class Cat {
String name="Kitty";//成员变量前没有访问限定符,是默认权限
int age=2;

}
public class test1 {
public static void main(String[] args) {
Cat pet2=new Cat();
System. out.println("我的宠物猫是:"+pet2.name);
}
}

Cat类在.com包下,test在默认包下
####################################
import com.Cat;

public class test {
public static void main(String[] args) {
Cat pet=new Cat();
pet.name="Wendy";
}
}

上面的程序如果运行会出现下面的结果

public

只要该类被导入,可以再任意场景下使用,不再举例

需要注意的是,访问限定符不仅可以修饰类内部的成员,还可以修饰类(private除外)

Cat类被放在.com包里,且是默认权限

package com;
class Cat {
String name="Kitty";
int age=2;

}

test2被放在com.Wuyang包里

package com.Wuyang;
import com.Cat;//导入默认权限的Cat类,会发生报错

public class test2 {
}

七.static成员

7.1 static的意义

再把我们的学生类拉出来,给这个类中增加一个成员变量--classRoom

class Student {
private String name;
private int age;
public String classRoom;

public static void main(String[] args) {

//创建对象学生1并赋值
Student st1=new Student();
st1.name="liming";
st1.age=18;
st1.classRoom="301";

//创建对象学生2并赋值
Student st2=new Student();
st1.name="wangwei";
st1.age=18;
st1.classRoom="301";
}

}

可以看到,学生1和学生2的教室都是301,他们的内存分布如下图

之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。

在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的

7.2 static修饰成员变量

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

因为静态成员变量不依赖于对象存在,访问方式也有所不同

方法一.类名+变量名

class Student {
private String name;
private int age;
public static String classRoom;
}

public class test {
public static void main(String[] args) {
Student. classRoom="301";
System. out.println(Student. classRoom);
}
}
//输出结果
301

方法二.对象名+变量名(不推荐)

class Student {
private String name;
private int age;
public static String classRoom;
}

public class test {
public static void main(String[] args) {
Student st1=new Student();
st1. classRoom="301";
System. out.println(st1. classRoom);
Student st2=new Student();
System. out.println(st2. classRoom);
}
}
//输出结果
301
301

可以看到,通过对象st1给静态成员classRoom赋值,意味着改变这个类的classRoom

这种方式并不能显式说明静态变量是归类所有而不是某一对象,因此不推荐这种写法

变量类型

静态成员变量

非静态成员变量

生存周期

从类被加载到类被卸载

对象被创建到对象被销毁

存储方式

存储在方法区上

存储在堆上,对象内部

访问方式

可以直接通过类名访问

只能构建对象,通过该对象的引用访问

个数

只有一个,不属于任何对象

一个对象拥有一份

7.3 static修饰成员方法

思考一下这个问题:如果classRoom的访问权限是private。如果仍然想在类外访问,需要调用public的方法,见下例

class Student {
private String name;
private int age;
private static String classRoom;//classRoom是私有成员

public void setClassRoom(String classRoom) {
Student. classRoom = classRoom;
}

public String getClassRoom() {
return classRoom;
}
}
public class test {
public static void main(String[] args) {
Student.classRoom="301";//会报错

//通过创建对象,调用public权限的方法访问classRoom
Student st=new Student();
st. setClassRoom("301");
System. out.println(st. getClassRoom());
}
}

既然classRoom不依赖对象存在,能不能不通过对象这种方式而是直接用类名访问classRoom访问呢?

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

静态成员方法的调用同样分为类名.方法或者对象名.方法(推荐使用前者)

上面的代码就可以改成这样

class Student {
private String name;
private int age;
private static String classRoom;

//定义静态成员方法get和set
public static void setClassRoom(String classRoom) {
Student. classRoom = classRoom;
}

public static String getClassRoom() {
return classRoom;
}
}
public class test {
public static void main(String[] args) {

//通过类名调用静态成员方法
Student. setClassRoom("301");
System. out.println(Student. getClassRoom());
}
}

下面来做一个小测试

class Student {
public static String classRoom;

public static String getClassRoom() {
return classRoom;
}
}
public class test {
public static void main(String[] args) {
Student p=null;//定义引用变量P指向空

p. classRoom="402";//通过p修改静态成员变量classRoom
System. out.println(p. getClassRoom());//通过p访问方法get
}
}

猜猜这个破坏者P是否能成功调用静态方法,将学生们的教室修改成“402”,并成功调用get方法呢?

答案是可以的!

第一个问题的答案:classRoom是属于Student这个类的,所以Student类型的引用变量P是可以对其进行操作的

第二个问题的答案要谈到静态成员方法的特性了

1.普通的成员方法在调用的时候都会传递一个隐藏的this引用,但是静态成员方法不需要

所以即使p是一个空引用,它仍是Student类型的引用变量,是可以调用不需要传this引用的方法的

2.普通成员方法可以通过this.成员访问其他成员(包括静态和非静态),静态成员方法只能访问静态成员

class Student {
private String name;
private int age;
public static String classRoom;

public static String printName() {
System. out.println("My name is "+name);
}//报错,静态成员方法体内不能访问非静态成员
}

7.4 static成员变量的初始化

静态成员变量是在类被加载的时候初始化的,分为两种方式

1.就地初始化

和普通成员变量就地初始化的格式一样,直接在定义静态成员变量的后面添上赋值语句即可

class Student {
public static String classRoom=“301”;
}

2.静态代码块初始化

先来看看什么是代码块

7.4 代码块

7.4.1 代码块的概念及分类

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字可分为4种

1.普通代码块

2.构造代码块

3.静态块

4.同步代码块(多线程部分才会用到,此处不提)

7.4.2 普通代码块

普通代码块:定义在方法内部的代码块

举例

public class test {
public static void main(String[] args) {
int a=0;
if(a==1) {
System. out.println("a=1");
} else {
System. out.println(("a!=1"));
}
}
}

if和else语句{}内部的即为普通代码块

7.4.3 构造代码块

构造块:定义在类中的代码块(不加修饰符)。也叫: 实例代码块构造代码块一般用于初始化实例成员变量

举例

public class test {
int number1;
String number2;

//构造代码块对成员number1和number2初始化
{
this.number1=10;
this.number2="Being positive";
}


public static void main(String[] args) {
test test1=new test();

//输出number1和number2的值
System. out.println("num1="+test1.number1);
System. out.println("num2="+test1.number2);
}
}

//输出结果
num1=10
num2=Being positive

注意:

1.构造代码块的执行顺序要先于构造函数

下面来验证一下

public class test {
int number1;
String number2;

//定义构造代码块
{
this.number1=10;
this.number2="Being positive";
}

//定义构造函数
test() {
System. out.println("还未初始化number1和number2");
System. out.println("number1="+number1+", number2="+number2);
this.number1=100;
this.number2="Don't lose heart!";
}


public static void main(String[] args) {
test test1=new test();
System. out.println("num1="+test1.number1);
System. out.println("num2="+test1.number2);
}
}

输出结果如下

2.就地初始化的顺序和执行构造代码块的顺序取决于代码先后顺序

参照下面代码就很好理解了

public class test {
//代码块1
{
number1=20;
number2="Cherish everything you own!";
System. out.println("第一次初始化后number1="+this.number1+", number2="+this.number2);
}

//就地初始化
int number1=1000;
String number2="Being happy!";

//代码块2
{
System. out.println("第二次初始化后number1="+this.number1+", number2="+this.number2);
this.number1=10;
this.number2="Being positive";
}


public static void main(String[] args) {
test test1=new test();
System. out.println("第三次初始化后number1="+test1.number1+", number2="+test1.number2);
}
}

运行结果如下

可以看到,成员变量初始化的顺序是按照就地和构造代码块的先后顺序决定的

7.4.4 静态代码块

使用static定义的代码块称为静态代码块。只能 用于初始化静态成员变量

举例

public class test {

int number1;
String number2;
static double number3=10.24;//静态变量就地初始化

//静态代码块
static {
number1=10;//error,静态代码块内不允许出现非静态成员
System. out.println("静态代码块执行前:"+ number3);
number3=2023;
System. out.println("静态代码块执行后:"+ number3);
}


public static void main(String[] args) {
test test1=new test();
}
}

输出结果

注意:静态成员变量不能定义在静态代码块的后面,它们都是随着类的加载被加载的,其先后顺序依照代码顺序

7.4.5 代码块总结

由于普通代码块过于简单,在此不做赘述,主要分析构造代码块和静态代码块

构造代码块

静态代码块

执行时间

创建对象时

加载类时

执行次数

创建多个对象就会被执行多次

只会在类被加载时执行一次

功能

只能初始化普通成员变量

只能初始化静态成员变量

在此说明一下,类加载就是使用这个类(创建对象或使用静态成员方法)的时候

比如下面的场景

public class test {
static {
System. out.println("执行了静态代码块");
}

static void func() {
System. out.println("调用了func方法");
}

public static void main(String[] args) {
//test test1=new test();通过创建变量加载test类
test. func();//通过使用方法加载test类
}
}

总结执行顺序:静态代码块>构造代码块>构造方法

八.对象的打印

我们已经知道,通过引用变量可以获取到一个对象的虚拟地址,然后就可以使用println方法把打印出来

//定义Person类
class Person {
private String name;
private int age;

}


public class test {

public static void main(String[] args) {
//创建对象
Person yang=new Person();
//输出对象地址
System. out.println(yang);
}

}

输出结果

但是很多时候我们并不需要打印这个对象的地址,而是想要知道它内部成员的值,这就不得不写一个方法来实现,有没有更简单的操作呢?

先来看一下println的实现

可以看到,println是调用了valueOf方法,valueOf方法又调用了toString方法,所以可以通过重写toString方法来打印我们想要的值

class Person {

//就地初始化
private String name="yang";
private int age=18;

@Override//注解,会帮忙检查是否重写了该方法
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class test {

public static void main(String[] args) {
Person yang=new Person();
System. out.println(yang);
}

}

有了这个toString,我们就不需要傻乎乎地再造一个方法了,直接调用包提供的println就行

运行结果如下

你是不是以为这个toString方法是我自己写的?

当然不是,强大的idea是我懒惰的理由

关于方法的重写会在下一篇讲解,耐心等待继承和多态吧!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不 会敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值