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