Javase.类与对象

【本节目标】

  1. 掌握类的定义方式以及对象的实例化
  2. 掌握类中的成员变量和成员方法的使用
  3. 掌握对象的整个初始化过程
  4. 掌握封装特性
  5. 掌握代码块
  6. 掌握内部类

1. 面向对象的初步认知

1.1什么是面向对象

Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。

1.2面向对象与面向过程

  1. 传统洗衣服过程
    在这里插入图片描述
  • 传统的方式:注重的是洗衣服的过程,少了一个环节可能都不行。
  • 而且不同衣服洗的方式,时间长度,拧干方式都不同,处理起来就比较麻烦。如果将来要洗鞋子,那就是另
    一种放方式。
  • 按照该种方式来写代码,将来扩展或者维护起来会比较麻烦。
  1. 现代洗衣服过程
    在这里插入图片描述
  • 面向对象方式来进行处理,就不关注洗衣服的过程,具体洗衣机是怎么来洗衣服,如何来甩干的,用户不用去关心,只需要将衣服放进洗衣机,倒入洗衣粉,启动开关即可,通过对象之间的交互来完成的
  • 注意:面向过程和面向对象并不是一门语言,而是解决问题的方法,没有那个好坏之分,都有其专门的应用场景。

2.类定义和使用

面相对象程序设计关注的是对象,而对象是现实生活中的实体,比如:洗衣机。但是洗衣机计算机并不认识,需要开发人员告诉给计算机什么是洗衣机。
在这里插入图片描述
上图左侧就是对洗衣机简单的描述,该过程称为对洗衣机对象(实体)进行抽象(对一个复杂事物的重新认知),但是这些简化的抽象结果计算机也不能识别,开发人员可以采用某种面相对象的编程语言来进行描述,比如:Java语言。

2.1 简单认识类

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

  • 比如:洗衣机,它是一个品牌,在Java中可以将其看成是一个类别。
  • 属性:产品品牌,型号,产品重量,外观尺寸,颜色等。
  • 功能:洗衣,烘干、定时等。

在Java语言中,如何对上述的洗衣机类进行定义呢?

2.2 类的定义格式

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

// 创建类
class ClassName{
	field; // 字段(属性) 或者 成员变量
	method; // 行为 或者 成员方法
}
  1. class为定义类的关键字,ClassName为类的名字,{}中为类的主体
  2. 类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法。
class WashMachine {
    public String brand;//品牌
    public String type;//型号
    public double weight;//重量
    public double length;//长
    public double width;//宽
    public double height;//高
    public String color;//颜色

    public void washClothes() {//洗衣服
        System.out.println("洗衣功能");
    }
    public void dryClothes() {//脱水
        System.out.println("脱水功能");
    }
    public void setTime() {//定时
        System.out.println("定时功能");
    }
}

采用Java语言将洗衣机类在计算机中定义完成,经过javac编译之后形成.class文件, 在JVM的基础上计算机就可以识别了。

【注意事项】
- 类名注意采用大驼峰定义
- 成员前写法统一为public,后面会详细解释
- 此处写的方法不带static关键字,后面会详细解释

2.3 课堂练习

2.3.1 定义一个狗类

在这里插入图片描述
在这里插入图片描述

class PetDog {
    public String name;//名字
    public String color;//颜色

    //狗的属性
    public void barks() {
        System.out.println(this.name + "汪汪叫...");
    }
    //狗的行为
    public void wag() {
        System.out.println(this.name + "摇尾巴...");
    }
}

2.3.2 定义一个学生类

class Students {
    public String name;
    public String gender;
    public short age;
    public double score;

    public void DoClass() {
		//去上课
    }
    public void DoHomework() {
		//写作业
    }
    public void Exam() {
		//要考试
    }
}
【注意事项】
1. 一般一个文件当中只定义一个类
2. main方法所在的类一般要使用public修饰(注意:Eclipse 默认会在public修饰的类中找到main方法)
3. public 修饰的类必须要和文件名相同
4. 不要轻易去修改public修饰的类的名称,如果要修改,通过开发工具修改给。

3. 类的实例化

3.1 什么是实例化

定义了一个类,就相当于在计算机中定义了一种新的类型 与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型,比如上述的:PetDog类和Student类。它们都是类(一种新定义的类型)有了这些自定义的类型之后,就可以使用这些类来定义实例(或者称为对象)。
用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。

在这里插入图片描述

class PetDog {
    public String name;//名字
    public String color;//颜色
    // 狗的属性
    public void barks() {
        System.out.println(name + ": 旺旺旺~~~");
    }
    // 狗的行为
    public void wag() {
        System.out.println(name + ": 摇尾巴~~~");
    }
}
public class Test {
  //3.1什么是的实例化
    /*
    定义了一个类,就相当于在计算机中定义了一种的类型。
    用类类型创建对象的过程,称为类的实例化 Java中采用new 关键字,配合类名来实例化对象。
     */
  public static void main(String[] args) {
      PetDog petDog = new PetDog();
      petDog.name = "阿黄";
      petDog.color = "黑黄";
      petDog.barks();
      petDog.wag();
      System.out.println("============");
      PetDog petDog1 = new PetDog();
      petDog1.name = "旺财";
      petDog1.color = "棕黄";
      petDog1.barks();
      petDog1.wag();
  }
}

输出结果:
阿黄: 旺旺旺~~~
阿黄: 摇尾巴~~~
旺财: 旺旺旺~~~
旺财: 摇尾巴~~~
【注意事项】
- new关键字用来创建一个对象的实例
- 使用.来访问对象的属性与方法
- 同一个类可以创建多个对象

3.2 类和对象的说明

  1. 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些对象。
  2. 类是一种自定义类型,可以来定义变量。
  3. 一个类可以实例化出来的对象占用实际的物理空间,存储成员变量。
  4. 做个比方。类实例化出的对象就像是现实中的使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

在这里插入图片描述

4. this引用

4.1 为什么要有this引用

先看一个日期类的例子:

class Data {
    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 printData() {
        System.out.println(year + "-" + month + "-" + day);
    }

    public static void main(String[] args) {
        //构造三个日期类型的对象 data1 data2 data3
        Data data1 = new Data();
        Data data2 = new Data();
        Data data3 = new Data();

        //对data1 data2 data3的日期设置
        data1.setDay(2024, 6, 11);
        data2.setDay(2003, 8, 25);
        data3.setDay(2012, 2, 12);

        //打印日期中的内容
        data1.printData();
        data2.printData();
        data3.printData();
    }
}

以上代码定义了一个日期类,然后main方法中创建了三个对象,并通过Date类中的成员方法对对象进行设置和打印,代码整体逻辑非常简单,没有任何问题。

但是细想之下,有以下两个疑问:

  1. 形参名不小心与成员变量名相同:
public void setDay(int year, int month, int day){
	year = year;
	month = month;
	day = day;
}
  1. 三个对象都在调用setDateprintDate函数,但是这两个函数中没有任何有关对象的说明,setDateprintDate函数如何知道打印的是那个对象的数据呢?

在这里插入图片描述

4.2 什么是this引用

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

class Data3 {
    public int year;
    public int month;
    public int day;

    public void setDay(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public void printData() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }
}

注意this引用的是调用成员方法的对象。

public static void main(String[] args) {
     Data1 data1 = new Data1();
     data1.setDay(2024, 6, 11);
     data1.printData();
 }

在这里插入图片描述

4.3 this引用的特性

  1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
  2. this只能在“成员方法”中使用
  3. 在“成员方法”中,this只能引用当前对象,不能再引用其他对象
  4. this时“成员变量”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责调用成员方法对象的引用传递给该成员方法,this负责来接收。

在代码层面来简单演示
注意下图右侧中的Date类也是可以通过编译的。

在这里插入图片描述

5. 对象的构造及初始化

5.1 如何初始化对象

  1. 通过前面知识点的学习知道,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。
public static void main(String[] args) {
	int a;
	System.out.println(a);
}

//Error:java:可能为初始化变量a
  1. 要让上述代码通过编译,非常简单,只需在正式使用a之前,给a设置一个初始值即可。如果是对象:
public static void main(String[] args) {
	Date d = new Date();
	d.printDate();
	d.setDate(2024,6,13);
	d.printDate();
}

// 代码可以正常通过编译

需要调用之前写的SetDate方法才可以将具体的日期设置到对象中。通过上述例子发现两个问题:

1. 每次对象创建好后调用SetData方法设置具体日期,比较麻烦,那对象该如何实例化?
2. 局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?

5.2 构造方法

5.2.1 概念

构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次

class Data {
    public int year;
    public int month;
    public int day;

    //构造方法:
    //名字与类名相同,没有返回值类型设置为void也不行
    //一般情况下使用public修饰
    //在创建对象是由编译器自动调用的,并且在对象的生命周期内置调用一次
    public Data(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("Data(int, int, int)方法被调用了...");
    }

    public void printData() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }

    public static void main(String[] args) {
        //此处创建了一个Data类型的对象,并没有显式调用方法
        Data data = new Data(2024, 6, 12);//输出Data(int, int, int)方法被调用了
        data.printData();//2024-6-12
    }
}

注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。

5.2.2 特性

  1. 名字必须与类名相同
  2. 没有返回值类型,设置为void也不行
  3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
  4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
class Data3 {
    public int year;
    public int month;
    public int day;

    //无参构造方法
    public Data3() {
        this.year = 1999;
        this.month = 1;
        this.day = 1;
    }

    //带有三个参数的构造方法
    public Data3(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("Data(int, int, int)方法被调用了...");
    }

    public void printData() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }

    public static void main(String[] args) {
        //此处创建了一个Data类型的对象,并没有显式调用方法
        Data3 data3 = new Data3();
        data3.printData();//1999-1-1
    }
}

上述的两个构造方法:名字相同,参数列表不同,因此构成了方法重载。

  1. 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
public class Date {
	public int year;
	public int month;
	public int day;
	
	public void printDate() {
		System.out.println(year + "-" + month + "-" + day);
	}
	
	public static void main(String[] args) {
		Date d = new Date();
		d.printDate();
	}
}

上述Date类中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法

注意:一旦用户定义,编译器则不再生成。

class Data5 {
    public int year;
    public int month;
    public int day;

    public void printData() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }

    public static void main(String[] args) {
        //如果编译器会生成,则生成的构造方法一定是无参的
        //则此处创建对象是可以通过编译的
        //但实际情况:编译期报错
        Data5 data5 = new Data5();
        data5.printData();
    }
}

/*
Error:java: 无法将类 extend01.Date中的构造器 Date应用到给定类型;
需要: int,int,int
找到: 没有参数
原因: 实际参数列表和形式参数列表长度不同
*/
  1. 构造方法中,可以通过this调用其他构造方法来简化代码
class Data6 {
    public int year;
    public int month;
    public int day;

    //无参构造方法 -> 内部给各个成员赋值初始化,该部分成员功能与三个参数的构造方法是一样的。
    //此处可以在无参的构造方法通过this来调用带三个参数的构造方法
    //注意:this(1900, 1, 1)语句 必须是构造方法中的第一条语句
    public Data6() {
        this(1900, 1, 1);
        /*this.year = 1999;
        this.month = 1;
        this.day = 1;*/
    }

    //带有三个参数的构造方法
    public Data6(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("Data(int, int, int)方法被调用了...");
    }

    public void printData() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }

    public static void main(String[] args) {
        //此处创建了一个Data类型的对象,并没有显式调用方法
        Data6 data6 = new Data6();
        data6.printData();//1999-1-1
    }
}

注意:

  • this(…)必须是构造方法中的第一条语句。
  • 不能形成循环。
public Date() {
	this(1900,1,1);
}

public Date(int year, int month, int day) {
	this();
}

/*
无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用
编译报错:Error:(19, 12) java: 递归构造器调用
*/
  1. 绝大多数情况下使用public来修饰,特殊场景下会被private修饰。

5.3 默认初始化

在上文中提出的第二个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?

class Data7 {
    public int year;
    public int month;
    public int day;

    public Data7(int year, int month, int day) {
        //成员变量在定义的时候,并没有给初始值,为什么就可以使用呢?
        System.out.println(this.year);
        System.out.println(this.month);
        System.out.println(this.day);
    }

    public static void main(String[] args) {
        //此处a并没有初始化,编译时报错:
        //Error: java: 可能尚未初始化变量a
        /*int a;
        System.out.println(a);*/
        Data7 data7 = new Data7(2024, 6, 12);
    }
}

在这里插入图片描述
要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:

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

  2. 为对象分配内存空间。

  3. 处理并发安全问题
    比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突。

  4. 初始化所分配的空间
    即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:
    在这里插入图片描述

  5. 设置对象信息(关于对象内存模型的)。

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

5.4 就地初始化

在声明成员变量时,就直接给出了初始值。

class Date8 {
    public int year = 1900;
    public int month = 1;
    public int day = 1;

    public Date8() {
    }

    public Date8(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public void printData() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }

    public static void main(String[] args) {
        Date8 d1 = new Date8(2021, 6, 9);
        d1.printData();
        Date8 d2 = new Date8();
        d2.printData();
    }
}

注意:代码编译完成后,编译器会将所以给成员初始化的这些语句添加到各个构造方能法中。

6. 封装

6.1 封装的概念

**面向对象程序三大特性:封装、继承、多态。**而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。

  1. 比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。
    在这里插入图片描述

  2. 但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
    在这里插入图片描述

对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

封装:将数据和操作数据的方法进行了有机结合,隐藏对象的属性和实现的细节,仅对外公开接口来和对象进行交互。

6.2 访问限定符

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
在这里插入图片描述
比如:

public:可以理解为一个人的外貌特性,谁都可以看得到
default:对于自己家族中(同一个包)不是什么秘密,对于其他人来说就是隐私了
private:只有自己知道,其他人都不知道
class Computer {
    private String cpu;//cpu
    private String memory;//内存
    public String screen;//屏幕
    String brand;//品牌 -> default属性

    public Computer(String cpu, String memory, String screen, String brand) {
        this.cpu = cpu;
        this.memory = memory;
        this.screen = screen;
        this.brand = brand;
    }
    public void Boot() {
        System.out.println("开机...");
    }

    public void PowerOff() {
        System.out.println("关机...");
    }
    public void SurInternet() {
        System.out.println("上网...");
    }
}
public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer("i9", "16", "13*14", "QTl");
        System.out.println(computer.brand);//default属性:只能在本包中类访问
        System.out.println(computer.screen);//public属性:可以任何其他类型访问
        //System.out.println(computer.cpu);//private属性:只能在Computer类中访问,不能被其他类访问
    }
}

注意:一般情况下成员变量设置为private,成员方法设置为public。

6.3 封装扩展之包

6.3.1 包的概念

在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录

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

  • 在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式。
  • 比如:一个包中的类不想被其他包中的类使用。
  • 包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可

6.3.2 导入包中的类

  • Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date类。
public class Test {
	public static void main(String[] args) {
		java.util.Date date = new java.util.Date();
		// 得到一个毫秒级别的时间戳
		System.out.println(date.getTime());
	}
}
  • 但是这种写法比较麻烦一些, 可以使用 import语句导入包。
import java.util.Date;

public class Test {
	public static void main(String[] args) {
		Date date = new Date();
		// 得到一个毫秒级别的时间戳
	System.out.println(date.getTime());
}
}
  • 如果需要使用 java.util 中的其他类, 可以使用 import java.util.*
import java.util.*;

public class Test {
	public static void main(String[] args) {
		Date date = new Date();
		// 得到一个毫秒级别的时间戳
	System.out.println(date.getTime());
}
}
  • 但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况。
import java.util.*;
import java.sql.*;

public class Test {
	public static void main(String[] args) {
		// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
		Date date = new Date();
		System.out.println(date.getTime());
	}
}

// 编译出错
Error: java:Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
  • 在这种情况下需要使用完整的类名。
import java.util.*;
import java.sql.*;

public class Test {
	public static void main(String[] args) {
		java.util.Date date = new java.util.Date();
		System.out.println(date.getTime());
	}
}
  • 可以使用import static导入包中静态的方法和字段。
import static java.lang.Math.*;

public class Test {
	public static void main(String[] args) {
		double x = 30;
		double y = 40;
		// 静态导入的方式写起来更方便一些.
		// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
		double result = sqrt(pow(x, 2) + pow(y, 2));
		System.out.println(result);
	}
}

注意事项:

  • importC++#include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要。
  • import 只是为了写代码的时候更方便. import 更类似于 C++namespaceusing

6.3.3 自定义包

基本规则:

  • 在文件的最上方加上一个package语句指定该代码在哪个包中。
  • 包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(com.baidu.yiyan.demo1)。
  • 包名要和代码的路径相匹配,例如创建com.baidu.yiyan.demo1的包,那么会存在一个对应的路径com/baidu/yiyan/demo1来储存代码。
  • 如果一个类没有package语句,则该类被放到一个默认包中。

操作步骤:

  1. 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包

在这里插入图片描述

  1. 在弹出的对话框中输入包名, 例如 com.bit.demo11

在这里插入图片描述

  1. 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可

在这里插入图片描述
6. 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了。

在这里插入图片描述

  1. 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句。

在这里插入图片描述

6.3.4 包的访问权限控制举例

Computer类位于com.bit.demo1包中,TestComputer位置com.bit.demo2包中:

package com.bit.demo1;

public class Computer {
	private String cpu;//cpu
	private String memory;//内存
	public String screen;//屏幕
	String brand;//品牌
	
	public Computer(String brand, String cpu, String memory, String screen) {
		this.brand = brand;
		this.cpu = cpu;
		this.memory = memory;
		this.screen = screen;
	}
	
	public void Boot(){
		System.out.println("开机~~~");
	}
	
	public void PowerOff(){
		System.out.println("关机~~~");
	}
	
	public void SurfInternet(){
		System.out.println("上网~~~");
	}
}

///

package com.bite.demo2;
import com.bite.demo1.Computer;

public class TestComputer {
	public static void main(String[] args) {
		Computer p = new Computer("QTl", "i9", "16G", "13*14");
		System.out.println(p.screen);
		// System.out.println(p.cpu); // 报错:cup是私有的,不允许被其他类访问
		// System.out.println(p.brand); // 报错:brand是default,不允许被其他包中的类访问
	}
}

// 注意:如果去掉Computer类之前的public修饰符,代码也会编译失败

6.3.5 常见的包

  1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
  2. java.lang.reflect:java 反射编程包。
  3. java…net:进行网络编程开发包。
  4. java.sql:进行数据库开发的支持包。
  5. java.util:是java提供的工具程序包。(集合类等)非常重要
  6. java.io:I/O编程开发包。

7. static成员

7.1 再谈学生类

使用前文中介绍的学生类实例化三个对象s1、s2、s3,每个对象都有自己特有的名字、性别,年龄,学分绩点等成员信息,这些信息就是对不同学生来进行描述的,如下所示:

class Student {
    public String name;//姓名
    public String gender;//性别
    public int age;//年龄
    public double score;//分数
    public static String classRoom = "113";

    public Student(String name, String gender, int age, double score) {
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.score = score;
    }
    
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan", "男", 19, 8.8);
        Student s2 = new Student("lisi", "男", 28, 4.5);
        Student s3 = new Student("huangwu", "女", 15, 5.5);
    }
}

在这里插入图片描述
假设三个同学是同一个班的,那么他们上课肯定是在同一个教室,那既然在同一个教室,那能否给类中再加一个成员变量,来保存同学上课时的教室呢?答案是不行的。

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

7.2 static修饰成员变量

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

【静态方法特征】

  1. 不属于某个对象的,是类方法。
  2. 可以通过对象调用,也可以通过类名.静态方法名(…)式调用,更推荐后者。
  3. 不能在静态方法中访问任何静态成员变量。
  4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)。
public class Students {
    public String name;//姓名
    public String gender;//性别
    public int age;//年龄
    public double score;//分数
    public static String classRoom = "113";

    public Student(String name, String gender, int age, double score) {
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.score = score;
    }
    
///    

    public static void main(String[] args) {
        //静态成员变量可以直接通过类名来访问
        System.out.println(Student.classRoom);
        Student s1 = new Student("zhangsan", "男", 19, 8.8);
        Student s2 = new Student("lisi", "男", 28, 4.5);
        Student s3 = new Student("huangwu", "女", 15, 5.5);

        //也可以通过对象访问:但是classRoom是三个对象共享的
        System.out.println(s1.classRoom);
        System.out.println(s2.classRoom);
        System.out.println(s3.classRoom);
    }
}

同学以调试方式运行上述代码,然后在监视窗口中可以看到,静态成员变量并没有存储到某个具体的对象中

7.3 static修饰成员方法

一般类中的数据成员都设置为private,而成员方法设置为public,那设置之后,Student类中classRoom属性如何在类外访问呢?

public class Students {
    public String name;//姓名
    public String gender;//性别
    public int age;//年龄
    public double score;//分数
    public static String classRoom = "113";

    public Student(String name, String gender, int age, double score) {
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.score = score;
    }
}    
///    

public class TestStudent {
	public static void main(String[] args) {
		System.out.println(Student.classRoom);
	}
}

//编译失败:
Error: java: classRoom 在 extend01.Student 中是 private 访问控制

那static属性应该如何访问呢?

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

public class Student{
	// ...
	
	private static String classRoom = "113";
	
	// ...
	
	public static String getClassRoom(){
		return classRoom;
	}
}

public class TestStudent {
	public static void main(String[] args) {
		System.out.println(Student.getClassRoom());
	}
}

//执行结果
113

【静态方法特征】

  1. 不属于某个对象的,是类方法。
  2. 可以通过对象调用,也可以通过类名.静态方法名(…)式调用,更推荐后者。
  3. 不能在静态方法中访问任何静态成员变量。
public static String getClassRoom(){
	System.out.println(this);
	return classRoom;
}

//编译失败:Error: java: 无法从静态上下文中引用非静态 变量 this

public static String getClassRoom(){
	age += 1;
	return classRoom;
}

//编译失败:Error: java: 无法从静态上下文中引用非静态 变量 age
  1. 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用。
public static String getClassRoom(){
	doClass();
	return classRoom;
}

//编译报错:Error:(35, 9) java: 无法从静态上下文中引用非静态 方法 doClass()

7.4 static成员变量初始化

注意:静态成员变量一般不会放在构造方法初始化,构造方法中初始化的是与对象相关的实例化属性。

静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化。

  1. 就地初始化
    就地初始化指的是:在定义时直接给出初始值。
public class Student{
    private String name;
    private String gender;
    private int age;
    private double score;
    private static String classRoom = "113";

    // ...
}
  1. 静态代码块初始化
    先往下看!!!

8. 代码块

8.1 代码块概念以及分类

使用{}定义的一段代码被称为代码块,根据代码块定义的位置
1. 普通代码块
2. 构造代码块
3. 静态代码块
4. 同步代码块

8.2 普通代码块

普通代码块:定义在方法中的代码块。

public class Test {
    public static void main1(String[] args) {
        {
            //直接使用{}定义,普通代码块
            int a = 10;
            System.out.println("a = " + a);
        }
        int a2 = 100;
        System.out.println("a = " + a2);
    }
}

// 执行结果
a = 10
a2 = 100

8.3 构造代码块

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

public class Student {
    //实例代码块
    private String name;
    private String gender;
    private int age;
    private double score;
    private static String classRoom;//班级

    public Student() {
        System.out.println("I am a student init()");
    }

    //实例代码块
    {
        this.name = "zhangsan";
        this.gender = "男";
        this.age = 19;
        System.out.println("I am a instance init()");
    }

    public void show() {
        System.out.println("name:" + this.name + " age:" + this.age + " gender:" + this.gender);
    }
}

public class Test {
    public static void main2(String[] args) {
        Student student = new Student();
        student.show();
    }
}

//运行结果
I am a instance init()
I am a student init()
name:zhangsan age:19 gender:男

8.4 静态代码块

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

public class Student {
    //实例代码块
    private String name;
    private String gender;
    private int age;
    private double score;
    private static String classRoom;//班级

    public Student() {
        System.out.println("I am a student init()");
    }

    //实例代码块
    {
        this.name = "zhangsan";
        this.gender = "男";
        this.age = 19;
        System.out.println("I am a instance init()");
    }

    //静态代码块
    static {
        classRoom = "113";
        System.out.println("I am static init()");
    }

    public void show() {
        System.out.println("name:" + this.name + " age:" + this.age + " gender:" + this.gender);
    }
}

public class Test {
    public static void main2(String[] args) {
        Student student1 = new Student();
        Student student2 = new Student();
    }
}
【注意事项】
- 静态代码块不管生成多少个对象,其只会执行一次。
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的。
- 如果一个类中包含多个静态代码块,在编译代码时,编译会按照定义的先后次序依次执行(合并)。
- 实例代码块只有在创建对象时才会执行。

9. 内部类

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。

  • 在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现

public class OutClass {
	class InnerClass{
	}
}

// OutClass是外部类
// InnerClass是内部类

【注意事项】

  1. 定义在class类名{}花括号外部的,即使是一个文件中,都不能称为内部类。
public class A{
}

class B{
}

//A和B是两个独立的类,彼此没有关系

2.内部类和外部类共用了一个Java源文件,但是经过编译之后,内部类会形成单独的字节码文件

9.1 内部类的分类

先来看下,内部类都可以在一个类的那些位置进行定义。

public class OutClass {
    //成员位置定义:未被static修饰 --> 实例内部类
    public class InnerClass1 {

    }
    //成员位置定义:被static修饰 --> 静态内部类
    static class InnerClass2 {

    }

    public void method() {
        //方法也可以定义内部类 --> 局部内部类:几乎不用
        class InnerClass3 {

        }
    }
}
  1. 成员内部类(普通内部类:未被static修饰的成员内部类 和 静态内部类:被static修饰的成员内部类)
  2. 局部内部类(不谈修饰符)、匿名内部类。

注意内部类其实日常开发使用的不多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中
使用最多的是匿名内部类。

9.2 内部类

在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类。

9.2.1 实例内部类

即未被static修饰的成员内部类。

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

    // 实例内部类:未被static修饰
    class InnerClass {
        int c;

        public void methodInner() {
            // 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
            a = 100;
            b = 200;
            methodA();
            methodB();
            // 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的
            c = 300;
            System.out.println(c);
            // 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
            OutClass.this.c = 400;
            System.out.println(OutClass.this.c);
        }
    }

    public static void main(String[] args) {
        // 外部类:对象创建 以及 成员访问
        OutClass outClass = new OutClass();
        System.out.println(outClass.a);
        System.out.println(OutClass.b);
        System.out.println(outClass.c);
        outClass.methodA();
        outClass.methodB();
        System.out.println("=============实例内部类的访问=============");
        // 要访问实例内部类中成员,必须要创建实例内部类的对象
        // 而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类
        // 创建实例内部类对象
        OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
        // 上述语法比较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象
        OutClass.InnerClass innerClass2 = outClass.new InnerClass();
        innerClass2.methodInner();
    }
}

【注意事项】

  1. 外部类中的任何成员都可以在实例内部类方法中直接访问
  2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
  3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名
    称.this.同名成员 来访问
  4. 实例内部类对象必须在先有外部类对象前提下才能创建
  5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
  6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。

9.2.2 静态内部类

被static修饰的内部成员类称为静态内部类。

public class OutClass {
    private int a;
    static int b;
    public void methodA(){
        a = 10;
        System.out.println(a);
    }
    public static void methodB(){
        System.out.println(b);
    }
    // 静态内部类:被static修饰的成员内部类
    static class InnerClass{
        public void methodInner(){
        // 在内部类中只能访问外部类的静态成员
        // a = 100; // 编译失败,因为a不是类成员变量
                    b =200;
        // methodA(); // 编译失败,因为methodB()不是类成员方法
            methodB();
        }
    }
    public static void main(String[] args) {
        // 静态内部类对象创建 & 成员访问
        OutClass.InnerClass innerClass = new OutClass.InnerClass();
        innerClass.methodInner();
    }
}

【注意事项】

  1. 在静态内部类中只能访问外部类中的静态成员。
  2. 创建静态内部类对象时,不需要1先创建外部类对象。

9.3 局部内部类

定义在外部类的方法体或者{ }中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。

public class OutClass {
    int a = 10;
    public void method(){
        int b = 10;
        // 局部内部类:定义在方法体内部
        // 不能被public、static等访问限定符修饰
        class InnerClass{
            public void methodInnerClass(){
                System.out.println(a);
                System.out.println(b);
            }
        }
        // 只能在该方法体内部使用,其他位置都不能用
        InnerClass innerClass = new InnerClass();
        innerClass.methodInnerClass();
    }
    public static void main(String[] args) {
    // OutClass.InnerClass innerClass = null; 编译失败
    }
}

9.4 匿名内部类

后续再写抽象类与接口blog时再详细介绍。

10. 对象的打印

public class Person {
    String name;
    String gender;
    int age;

    public Person(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public static void main(String[] args) {
        Person person = new Person("zhangsan", "男", 19);
        System.out.println(person);
    }
}

//demo10.对象的打印.Person@1b6d3586

如果想要默认打印对象中的属性该如何处理呢?答案:重写toString方法即可

public class Person1 {
    String name;
    String gender;
    int age;

    public Person1(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person1{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        Person1 person1 = new Person1("zhangsan", "男", 19);
        System.out.println(person1);
    }
}

//输出结果
//Person1{name='zhangsan', gender='男', age=19}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值