实验6:类的特性
6.1 实验目的
- 掌握类的私有成员和共有成员的定义;
- 掌握类的私有成员的访问方法;
- 理解类的构造方法的作用和执行过程;
- 掌握类的构造方法的定义和关键词this的使用;
- 类的静态变量;
- 掌握方法的重载。
6.2 实验内容
由实验一到实验三构建的完整Bamboo类,后面实验中用到的核心代码会单独提出来:
public class Bamboo { /** * 竹子名字 */ private String name; /** * 竹子年龄 */ private int age; /** * 高度 */ private float height; /** * 直径 */ private double diameter; /** * 设置不能超过的最大年龄 */ public final static int MAX_AGE = 20; /** * 类变量 */ public static String temp; /** * 无参构造器 */ public Bamboo() { System.out.println("无参构造器被调用"); } /** * 全参构造器 * * @param name 名字 * @param age 年龄 * @param height 高度 * @param diameter 直径 */ public Bamboo(String name, int age, float height, double diameter) { this.name = name; this.age = age; this.height = height; this.diameter = diameter; System.out.println("全参构造器被调用"); } /** * 拷贝构造器 * * @param bamboo 被拷贝的对象 */ public Bamboo(Bamboo bamboo) { this(bamboo.getName(), bamboo.getAge(), bamboo.getHeight(), bamboo.diameter); System.out.println("拷贝构造器被调用"); } /** * 静态方法对实例变量和类变量进行赋值 */ public static void staticAssignment(){ temp = "静态方法赋值的静态变量"; //静态方法是在类加载时期生成的,实例变量是在类对象创建时期生成的 //根据先后关系,我们不能直接在静态变量中赋值 //因此在这里我们选择先创建一个对象再赋值 Bamboo bamboo = new Bamboo(); bamboo.name = "静态方法中创建的对象的名字"; System.out.println("静态赋值方法中的实例变量赋值:" + bamboo.name); } /** * 实例方法对实例变量和类变量进行赋值 */ public void instanceAssignment(){ this.name = "实例方法赋值的名字"; temp = "实例方法赋值的静态变量"; } public double volume() { return Math.PI * diameter / 2 * diameter / 2 * height; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if (age > MAX_AGE) { System.out.println("生长年限错误"); } else { this.age = age; System.out.println("姓名:" + this.name + " 年龄:" + this.age); } } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } public double getDiameter() { return diameter; } public void setDiameter(double diameter) { this.diameter = diameter; } }
6.2.1 编写一个Java程序,在程序中将实验5的Bamboo(竹子)类,的成员变量age改为私有成员变量,分别编写age的set和get方法,在main方法中新建该类的对象,设置name和age,如果所设置的年龄大于20,则输出”生长年限错误”,否则输出name和age;
【前提引入】
成员变量age改为私有成员变量,并通过get和set方法进行操作,这里就体现了Java这门纯面向对象OOP语言的一个特性:封装
那这里就好好提提封装:
- 介绍
封装就是把抽象除的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作才能对数据进行操作 - 好处
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理性
- 封装实现步骤
- 将属性进行私有化 private
- 提供一个公共的
set
方法,用于对属性判断并赋值 - 提供一个公共的
get
方法,用于获取属性的值
【Bamboo类相关核心代码】
/** * 竹子名字 */ private String name; /** * 竹子年龄 */ private int age; /** * 设置不能超过的最大年龄 */ public final static int MAX_AGE = 20; /** * 无参构造器 */ public Bamboo() { System.out.println("无参构造器被调用"); } public void setAge(int age) { if(age > MAX_AGE){ System.out.println("生长年限错误"); }else { this.age = age; System.out.println("姓名:" + this.name + " 年龄:" + this.age); } }
【运行流程】
年龄正常情况:
public static void main(String[] args) { Bamboo bamboo = new Bamboo(); bamboo.setName("正常年龄的小竹子"); bamboo.setAge(16); }
年龄不正常情况:
public static void main(String[] args) { Bamboo bamboo = new Bamboo(); bamboo.setName("不正常年龄的小竹子"); bamboo.setAge(25); }
6.2.2 为Bamboo类添加三个构造方法:其中无参构造方法为输出“Bamboo()构造方法被调用”;以及带有与所有成员变量参数个数和类型相同的构造方法;参数为Bamboo类自身的拷贝构造方法,该法采用this关键字调用第2个构造方法(具体构建参照PPT)。同时在每个构造方法中要输出“XXX构造方法被调用”。最后在main方法中分别调用三种构造方法创建类的对象。
【前提引入】
1️⃣ 构造方法又叫构造器,是类的一种特殊方法,它的主要作用是完成对新对象的初始化
[访问修饰符] 方法名(形参列表){ 方法体; }
🌿 说明:
- 构造器的修饰符可以默认
- 构造器没有返回值
- 方法名和类名必须一样
- 构造器的调用,是系统自动完成的(new 时)
🌿 细节说明:
- 一个类可以有多个构造器,即
构造器重载
- 如果程序员没有自定义类的构造器,那么系统会自动给类生成一个默认的无参构造器,也叫默认构造器💬 这里我们用
javap反编译指令
演示一下
- 定义一个
HelloWorld类
,我们就来一个空类,免麻烦
class HelloWorld{ }
- 使用
javac编译指令
生成字节码二进制文件HelloWorld.class
- 使用
javap反编译指令
查看代码,就可以看到生成的无参构造器了(红色框框)
- 一旦程序员定义了自己的构造器,默认的无参构造器就被覆盖了,就不能再使用默认的无参构造器了,除非程序员自己显示的定义一下
2️⃣ 讲到构造器,就又可以谈谈与涉及到构造器的对象创建流程
- 类加载:方法区中加载类信息(只会加载一次)
- 在堆中分配空间(地址)
- 完成对象初始化
- 默认初始化
- 显示初始化
- 构造器初始化
- 将对象在堆中的地址返回给栈中的对象引用
【Bamboo类相关核心代码】
/** * 无参构造器 */ public Bamboo() { System.out.println("无参构造器被调用"); } /** * 全参构造器 * * @param name 名字 * @param age 年龄 * @param height 高度 * @param diameter 直径 */ public Bamboo(String name, int age, float height, double diameter) { this.name = name; this.age = age; this.height = height; this.diameter = diameter; System.out.println("全参构造器被调用"); } /** * 拷贝构造器 * * @param bamboo 被拷贝的对象 */ public Bamboo(Bamboo bamboo) { this(bamboo.getName(), bamboo.getAge(), bamboo.getHeight(), bamboo.diameter); System.out.println("拷贝构造器被调用"); }
【运行流程】
public static void main(String[] args) { //调用无参构造器 Bamboo bamboo1 = new Bamboo(); //调用全参构造器 Bamboo bamboo2 = new Bamboo("小竹子", 18, 5.0f, 0.5); //调用全参构造器和拷贝构造器 Bamboo bamboo3 = new Bamboo(bamboo2); }
6.2.3 在Bamboo类中增加一个类变量,新建一个无参的静态方法对实例变量和类变量进行赋值,新建一个无参的对象方法对实例变量和类变量进行赋值,在main方法中分别调用上面两个方法,并分别显示其值(无需对Bamboo类中的所有实例变量进行赋值)。
【前提引入】
1️⃣ 类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,去到的都是相同的值,同样任何一个类的对象去修改它时,修改的也是同一个变量。
🌿 定义类变量
访问修饰符 static 数据类型 变量名;
🌿 访问类变量
类名.变量名 //推荐 对象名.变量名
2️⃣ 类方法
当方法中不涉及到任何和对象相关的成员,则可以将方法设计为静态方法,提高开发效率。
在程序实际开发中,往往会将一些通用的方法,设计成静态方法,这样就不需要创建对象就可以使用了,比如对数组排序,完成某个计算任务等
🌿 定义类方法
访问修饰符 static 返回值数据类型 方法名(形参列表){ }
🌿 访问类方法
类名.方法名(实参列表); //推荐 对象名.方法名(实参列表);
3️⃣ 注意事项
- 类方法,类变量和普通方法都是随着类的加载而加载,将结构信息存储在方法去。类方法中无this参数,普通方法中含有。而普通属性(也叫成员属性)是随着对象的创建而创建,随着对象的销毁而销毁。
- 类方法中只能访问当前本类的类方法或静态变量,普通成员方法即可以访问非静态成员,也可以访问静态成员。
其实也很理解为什么类方法不能访问当前本类的成员属性,因为类方法是在类加载时期就生成了,而成员属性是在对象创建时才生成。所以你想想,如果类加载时期在类方法中有了成员属性,但对象是在类加载之后才可以生成,这不很矛盾吗,这个成员属性该属于哪个对象呢?类加载时期对象还没生成呢!
【Bamboo类相关核心代码】
/** * 竹子名字 */ private String name; /** * 类变量 */ public static String temp; /** * 静态方法对实例变量和类变量进行赋值 */ public static void staticAssignment(){ temp = "静态方法赋值的静态变量"; //静态方法是在类加载时期生成的,实例变量是在类对象创建时期生成的 //根据先后关系,我们不能直接在静态变量中赋值 //因此在这里我们选择先创建一个对象再赋值 Bamboo bamboo = new Bamboo(); bamboo.name = "静态方法中创建的对象的姓名"; } /** * 实例方法对实例变量和类变量进行赋值 */ public void instanceAssignment(){ this.name = "实例方法赋值的名字"; temp = "实例方法赋值的静态变量"; }
【运行流程】
public static void main(String[] args) { Bamboo bamboo = new Bamboo(); bamboo.instanceAssignment(); System.out.println("实例方法 —— name:" + bamboo.getName() + " temp:" + Bamboo.temp); System.out.println("========================================="); bamboo.staticAssignment(); System.out.println("实例方法 —— temp:" + Bamboo.temp); }
6.2.4 编写一个Java程序,构造一个类,分别用方法的重载计算长方体体积,圆柱体和球体的体积,并在main方法中设置和输出结果。
【前提引入】
谈谈 方法重载
1️⃣ 基本介绍
Java中允许一个类中多个同名方法的存在,但要求形参列表不一致。
2️⃣ 重载的好处
- 减轻了记名的麻烦
- 减轻了起名的麻烦
3️⃣ 注意事项
- 方法名:必须相同
- 形参列表:必须不相同——形参类型或个数或顺序至少有一个不同,参数名相不相同无所谓,即方法重载与参数名无关
- 返回类型:无要求,如参数名一样,可相同可不同
【构建的类代码】
public class Util { /** * 计算长方体的体积 * * @param length 长 * @param width 宽 * @param height 高 * @return 长方体体积 */ public static double cal(double length, double width, double height) { return length * width * height; } /** * 计算圆柱体的体积 * * @param r 半径 * @param height 高 * @return 圆柱体体积 */ public static double cal(double r, double height) { return Math.PI * r * r * height; } /** * 计算球体的体积 * * @param r 半径 * @return 球体体积 */ public static double cal(double r) { return Math.PI * r * r * r * 4 / 3; } }
【运行流程】
public static void main(String[] args) { //求长2,宽3,高4的长方体体积 System.out.println("长方体体积:" + Util.cal(2, 3, 4)); //求半径2,高3的圆柱体体积 System.out.println("圆柱体体积:" + Util.cal(2, 3)); //求半径为2的球体的体积 System.out.println("球体体积:" + Util.cal(2)); }