类的基本语法
Java API 提供了一些现有的类,程序员可以使用这些类来创建对象,例如第 5 章学习的 String 类。除了使用现有的 Java 类,程序员还可以自定义 Java 类,接下来会详细地介绍如何定义和使用 Java 类。
我们在编写第一个 Java 程序时已经知道,类是 Java 程序的基本单元。Java 是面向对象的程序设计语言,所有程序都是由类组织起来的,也可以说“类是 Java 的一等公民”。下面是类定义的语法形式。
public class 类名{
//定义类属性
属性1类型:属性1名;
属性2类型:属性2名;
…
//定义方法
方法1定义
方法2定义
…
}
在 Java 中,class 是用来定义类的关键字,class 关键字后面是要定义的类的名称,然后有一对大括号,大括号里写的是类的主要内容。
类的主要内容分两部分,第一部分是类的属性定义,在前面的课程中学习过,在类内部、方法外部定义的变量称为成员变量,也可以称为成员属性,或简称为“属性”。第二部分是类的方法定义,通过方法的定义,描述类(对象)具有的动态行为,这些方法也可以称为成员方法,或简称为“方法”。
例如我们可以创建一个学生类 Student,代码如下:
public class Student {
String stuName; //学生姓名
int stuAge; //学生年龄
int stuSex; //学生性别
int stuGrade; //学生年级
//定义听课的方法,在控制台直接输出
public void learn() {
System.out.println(stuName + "正在认真听课!");
}
//定义写作业的方法,输入时间,返回字符串
public String doHomework(int hour) {
return "现在是北京时间:" + hour + "点," + stuName + " 正在写作业!";
}
}
定义好一个类后,就可以根据这个类创建(实例化)对象了。类就相当于一个模板,可以创建多个对象。创建对象的语法形式如下。
类名 对象名 = new 类名();
在学习使用 String 类时,已经使用过这种语法,所以大家对这样的语法形式并不陌生。创建对象时,要使用 new 关键字,后面要跟着类名(构造方法名),类名后的括号内可传递构造参数。
根据上面创建对象的语法,例如我们要在 Student 类里创建王云这个学生对象,代码如下。
Student wangYun = new Student();
这里,只创建了 wangYun 这个对象,并没有对这个对象的属性赋值,考虑到每个对象的属性值不一样,所以通常在创建对象后给对象的属性赋值。在 Java 语言中,通过 . 操作符来引用对象的属性和方法,具体的语法形式如下。
对象名.属性 ;
对象名.方法() ;
通过上面的语法形式,可以给对象的属性赋值,也可以更改对象属性的值或者调用对象的方法,具体的代码如下。
wangYun.stuName ="王云";
wangYun.stuAge = 22;
wangYun.stuSex = 1; //1代表男,2代表女
wangYun.stuGrade = 4; //4代表大学四年级
wangYun.learn(); //调用学生听课的方法
wangYun.doHomework(22); //调用学生写作业的方法,输入值22代表现在是22点
【练一练】实现一个简单的师生状态管理程序
前面的实验(类的创建和使用)中,我们定义了 Student 类后,使用 TestStudent 测试类创建了一个 Student 类的对象 wangYun,然后给 wangYun 对象的属性赋值并调用对象的方法。
接下来再定义一个老师类 Teacher,其具有的属性和方法如图所示。
下面将新定义一个 TestStuTea 类,用于组织这个新程序的程序结构。该程序中包含 2 个老师对象和 4 个学生对象,其基本信息分别如以下两个表所示。
老师基本信息表:
姓名 专业 课程 教龄
蒋涵 计算机应用 Java 基础 5
田斌 软件工程 前端技术 10
学生基本信息表:
姓名 年龄 性别 年级
王云 22 男 4
刘静涛 21 女 3
南天华 20 男 3
雷静 22 女 4
程序要完成的功能描述如下。
在程序开始运行时,需要在控制台依次输入所有老师和学生的基本信息。
在控制台输入完毕这些老师和学生的基本信息后,调用第一个老师讲课的方法,在控制台输出“(该老师的姓名)老师正在辛苦讲(该老师所授课程)课程”的信息。
依次调用所有学生听课的方法,在控制台输出“xx(该学生姓名)学生正在认真听课!”的信息。
依次调用所有学生写作业的方法,在控制台“现在是北京时间:20 点,xx(该学生姓名)正在写作业!”的信息,其中 20 是作为参数传递给写作业的方法的。
调用第二个老师批改作业的方法,依次批改所有学生的作业,在控制台输出“讲授 xx(该老师所授课程)课程的老师 xx(该老师姓名)已经批改完毕:xx(该学生姓名)的作业!”。
本次实验功能相对复杂,我们将需求分步实现:
新建一个 TestStuTea.java 文件,并定义 Student 类。
import java.util.Scanner;
//定义Student类
class Student { //不能使用public修饰
String stuName; //学生姓名
int stuAge; //学生年龄
int stuSex; //学生性别
int stuGrade; //学生年级
//定义听课的方法,在控制台直接输出
public void learn() {
System.out.println(stuName + "正在认真听课!");
}
//定义写作业的方法,输入时间,返回字符串
public String doHomework(int hour) {
return "现在是北京时间:" + hour + "点," + stuName + " 正在写作业!";
}
}
定义 Teacher 类,在 TestStuTea.java 文件最后添加如下代码:
class Teacher { //不能使用public修饰
String teaName; //老师姓名
String teaSpecialty; //老师专业
String teaCourse; //老师所讲授的课程
int teaYears; //老师教龄
//定义讲课的方法,在控制台直接输出
public void teach() {
System.out.println(teaName + "正在辛苦讲:" + teaCourse + " 课程!");
}
//定义批改作业的方法,输入值为一个学生对象,在控制台直接输出结果
public void checkHomework(Student stu) {
System.out.println("讲授:" + teaCourse + " 课程的老师:"
+ teaName + " 已经批改完毕: " + stu.stuName + " 的作业!");
}
}
定义 TestStuTea 类,并在类中定义 createStudent 方法,实现创建学生对象,并对属性赋值:
import java.util.Scanner;
public class TestStuTea {
//全局Scanner对象
static Scanner input = new Scanner(System.in);
//创建学生对象并赋值
public static Student createStudent() {
Student stu = new Student();
System.out.print("请输入学生姓名:");
stu.stuName = input.next();
System.out.print("请输入学生年龄:");
stu.stuAge = input.nextInt();
System.out.print("请输入学生性别数值(1代表男、2代表女):");
stu.stuSex = input.nextInt();
System.out.print("请输入学生年级:");
stu.stuGrade = input.nextInt();
return stu;
}
}
在 TestStuTea 类中定义 createTeacher 方法,实现创建老师对象,并对属性赋值:
import java.util.Scanner;
public class TestStuTea {
//全局Scanner对象
static Scanner input = new Scanner(System.in);
//创建老师对象并赋值
public static Teacher createTeacher() {
Teacher tea = new Teacher();
System.out.print("请输入老师姓名:");
tea.teaName = input.next();
System.out.print("请输入老师专业:");
tea.teaSpecialty = input.next();
System.out.print("请输入老师所讲授的课程:");
tea.teaCourse = input.next();
System.out.print("请输入老师教龄:");
tea.teaYears = input.nextInt();
return tea;
}
编写 main 方法,完成需求,完整代码如下:
import java.util.Scanner;
public class TestStuTea {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
Teacher[] tea = new Teacher[2]; //创建长度为2的数组tea,用于存放2个老师对象
Student[] stu = new Student[4]; //创建长度为4的数组stu,用于存放4个学生对象
for (int i = 0; i < tea.length; i++) {
System.out.println("请创建并输入第" + (i + 1) + "个老师的基本信息:");
tea[i] = createTeacher(); //调用createTeacher方法创建第i+1个老师对象并赋值
}
for (int j = 0; j < stu.length; j++) {
System.out.println("请创建并输入第" + (j + 1) + "个学生的基本信息:");
stu[j] = createStudent(); //调用createStudent方法创建第j+1个学生对象并赋值
}
//调用第一个老师讲课的方法,在控制台输出
tea[0].teach();
//依次调用所有学生听课的方法,在控制台输出
for (int j = 0; j < stu.length; j++) {
stu[j].learn();
}
//依次调用所有学生写作业的方法,在控制台输出
for (int j = 0; j < stu.length; j++) {
String tempStr = stu[j].doHomework(20); //其中20是作为参数传递给写作业的方法的
System.out.println(tempStr);
}
for (int j = 0; j < stu.length; j++) {
//调用第二个老师批改作业的方法,依次批改所有学生的作业,在控制台输出
tea[1].checkHomework(stu[j]);
}
}
//创建老师对象并赋值
public static Teacher createTeacher() {
Teacher tea = new Teacher();
System.out.print("请输入老师姓名:");
tea.teaName = input.next();
System.out.print("请输入老师专业:");
tea.teaSpecialty = input.next();
System.out.print("请输入老师所讲授的课程:");
tea.teaCourse = input.next();
System.out.print("请输入老师教龄:");
tea.teaYears = input.nextInt();
return tea;
}
//创建学生对象并赋值
public static Student createStudent() {
Student stu = new Student();
System.out.print("请输入学生姓名:");
stu.stuName = input.next();
System.out.print("请输入学生年龄:");
stu.stuAge = input.nextInt();
System.out.print("请输入学生性别数值(1代表男、2代表女):");
stu.stuSex = input.nextInt();
System.out.print("请输入学生年级:");
stu.stuGrade = input.nextInt();
return stu;
}
}
class Teacher { //不能使用public修饰
String teaName; //老师姓名
String teaSpecialty; //老师专业
String teaCourse; //老师所讲授的课程
int teaYears; //老师教龄
//定义讲课的方法,在控制台直接输出
public void teach() {
System.out.println(teaName + "正在辛苦讲:" + teaCourse + " 课程!");
}
//定义批改作业的方法,输入值为一个学生对象,在控制台直接输出结果
public void checkHomework(Student stu) {
System.out.println("讲授:" + teaCourse + " 课程的老师:"
+ teaName + " 已经批改完毕: " + stu.stuName + " 的作业!");
}
}
class Student { //不能使用public修饰
String stuName; //学生姓名
int stuAge; //学生年龄
int stuSex; //学生性别
int stuGrade; //学生年级
//定义听课的方法,在控制台直接输出
public void learn() {
System.out.println(stuName + "正在认真听课!");
}
//定义写作业的方法,输入时间,返回字符串
public String doHomework(int hour) {
return "现在是北京时间:" + hour + "点," + stuName + " 正在写作业!";
}
}
代码中使用了两个数组,分别存放了 2 个老师对象和 4 个学生对象(在对象被创建以前,数组中的元素值都是 null),然后使用 createTeacher()、createStudent() 方法创建了具体的老师和学生对象并赋值,之后再通过对 teach()、learn() 等方法的调用,输出老师和学生对象中的各个功能。
程序运行结果如图所示。
java中的封装
在企业面试中,经常问到,面向对象有哪些基本特性?答案应该是:封装、继承和多态。继承和多态在后面的章节会详细介绍,这里给同学们简要介绍一下封装。
封装的目的是简化编程和增强安全性。
简化编程是指,封装可以让使用者不必了解具体类的内部实现细节,而只是要通过提供给外部访问的方法来访问类中的属性和方法。例如 Java API 中的 Arrays.sort()方法,该方法可以用于给数组进行排序操作,开发者只需要将待排序的数组名放到 Arrays.sort()方法的参数中,该方法就会自动的将数组排好序。可见,开发者根本不需要了解 Arrays.sort()方法的底层逻辑,只需要简单的将数组名传递给方法即可实现排序。
增强安全性是指,封装可以使某个属性只能被当前类使用,从而避免被其他类或对象进行误操作。例如在 Student.java 的程序中,Student 的 stuAge 属性是 public 的形式体现的,但这样做实际存在着安全隐患:TestStudent 类(或在访问修饰符可见范围内的其他类)完全可以随意的对 stuAge 进行修改,如以下程序。
public class TestStudent {
public static void main(String[] args) {
Student wangYun = new Student();
wangYun.stuAge = -10;
...
}
}
如上,给 stuAge 赋了一个不符合逻辑的值,但语法是却正确的。因此这种做法,实际会给程序造成了一定程度上的安全问题。
封装的基本形式:
封装如何保证数据的安全性
在初识封装中我们提到 public 的形式体现的属性可以随意进行修改,那么如何避免此类问题呢?我们可以使用 private 修饰符来修饰 stuAge 属性,以此禁止 Student 以外的类对 stuAge 属性的修改。但这么做未免显得“过犹不及”,为了保证安全,也不至于让其他类无法访问吧!有没有一种办法,既能让其他类可以访问 Student 类中的 stuAge 属性,又能保证其他类始终是在安全的数值范围内修改 stuAge 值呢?
有,我们可以先用 private 修饰 stuAge 属性,然后再给该属性提供两个 public 修饰的、保证属性安全的访问方法(setter 方法和 getter 方法),即:
- 用 private 禁止其他类直接访问属性;
- 给 1 中的属性新增两个 public 修饰的 setter 和 getter 方法,供其他类安全的访问。
setter 方法用于给属性赋值,而 getter 访问用于获取属性的值。并且一般而言,setter 方法的名字通常是 set+属性名,getter 方法的名字通常是 get+属性名。
根据以上描述,先用 private 修饰 stuAge,禁止 TestStudent 类对 stuAge 的直接访问,以此保证 stuAge 安全性;然后新增 setStuAge()和 getStuAge()方法,一方面供 TestStudent 类间接的访问 stuAge 属性,另一方面也保证了 stuAge 的数据安全,详见以下程序。
public class Student {
private int stuAge ;
//获取stuAge的值
public int getStuAge() {
return stuAge;
}
//给stuAge赋一个合法的值
public void setStuAge(int age) {
//如果年龄在合理范围内,则正常赋值
if( age >0 && age <110)
stuAge = age;
else //如果对年龄的赋值不合理,则设置为默认值0
age = 0 ;
}
}
后续,其他类只需要调用 setStuAge()和 getStuAge()方法,就能对 stuAge 属性进行安全的赋值或取值,代码如下。
public class TestStudent3 {
public static void main(String[] args) {
Student wangYun = new Student();
/* 如果将stuAge赋值负数,setStuAge()就会将stuAge设置为默认值0,防止出现安全问题
wangYun.setStuAge(-10); */
wangYun.setStuAge(22);
int age = wangYun.getStuAge() ;
}
}
实际上,使用 setter 和 getter 的解决方案用到了一个程序设计的基本原则:逻辑代码不能写在变量中,而必须写在方法或代码块中。