第3章 面向对象(上)

3.1 面向对象的思想

面向过程: 分析出解决问题所需要的步骤,然后用函数把这些步骤一一实现,使用的时候依次调用就可以了。

面向对象: 把构成问题的事务按照一定规则划分为多个独立的对象,然后通过调用对象的方法来解决问题。

面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象映射现实中的事物,使用对象的关系描述事物之间的联系,这种思想就是面向对象。

面向对象具有三大特性:封装,继承,多态

1.封装性 封装是面向对象的核心思想,它有两层含义,一是指把对象的属性和行为看成是一个密不可分的整体,将这两者“封装”在一起(即封装在对象中);另外一层含义指“信息隐藏”,将不想让外界知道的信息隐藏起来。例如,驾校的学员学开车,只需要知道如何操作汽车,无需知道汽车内部是如何工作的。

2.继承性 继承性主要描述的是类与类之间的关系,通过继承,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展。例如,有一个汽车类,该类描述了汽车的普通特性和功能。进一步再产生轿车类,而轿车类中不仅应该包含汽车的特性和功能,还应该增加轿车特有的功能,这时,可以让轿车类继承汽车类,在轿车类中单独添加轿车特性和方法就可以了。继承不仅增强了代码的复用性、提高开发效率,还降低了程序产生错误的可能性,为程序的维护以及扩展提供了便利。

3.多态性 多态性指的是在一个类中定义的属性和方法被其它类继承后,它们可以具有不同的数据类型或表现出不同的行为,这使得同一个属性和方法在不同的类中具有不同的语义。例如,当听到“Cut” 这个单词时,理发师的行为是剪发,演员的行为表现是停止表演,不同的对象,所表现的行为是不一样的。多态的特性使程序更抽象、便捷,有助于开发人员设计程序时分组协同开发。

3.2 类与对象

在面向对象中,为了做到让程序对事物的描述与事物在现实中的形态保持一致,面向对象思想中提出了两个概念,即类和对象。在Java程序中类和对象是最基本、最重要的单元。类表示某类群体的一些基本特征抽象,对象表示一个个具体的事物。

例如,在现实生活中,学生就可以表示为一个类,而一个具体的学生,就可以称为对象。一个具体的学生会有自己的姓名和年龄等信息,这些信息在面向对象的概念中称为属性;学生可以看书和打篮球,而看书和打篮球这些行为在类中就称为方法。类与对象的关系如右图。

在上图中,学生可以看作是一个类,小明、李华、大军都是学生类型的对象。类用于描述多个对象的共同特征,它是对象的模板。对象用于描述现实中的个体,它是类的实例。对象是根据类创建的,一个类可以对应多个对象。

在面向对象的思想中最核心的就是对象,而创建对象的前提是需要定义一个类,类是Java中一个重要的引用数据类型,也是组成Java程序的基本要素,所有的Java程序都是基于类的。

3.2.1 类的定义

类是对象的抽象,用于描述一组对象的共同特征和行为。类中可以定义成员变量和成员方法,其中,成员变量用于描述对象的特征,成员变量也被称作对象的属性;成员方法用于描述对象的行为,可简称为方法。

类的定义格式如下所示:

class 类名{
   成员变量;
   成员方法;
}

根据上述格式定义一个学生类,成员变量包括姓名(name)、年龄(age)、性别(sex);成员方法包括读书read()。学生类定义的示例代码如下所示。

class Student {
    String name;    	// 定义String类型的变量name
    int age;        	// 定义int类型的变量age
    String  sex;    	// 定义String类型的变量sex
	// 定义 read () 方法
	void read() {  
		System.out.println("大家好,我是" + name + ",我在看书!");
	}
}

脚下留心:局部变量与成员变量的不同

在Java中,定义在类中的变量被称为成员变量,定义在方法中的变量被称为局部变量。如果在某一个方法中定义的局部变量与成员变量同名,这种情况是允许的,此时,在方法中通过变量名访问到的是局部变量,而并非成员变量。

class Student {
	int age = 30;       		// 类中定义的变量被称作成员变量
	void read() {  
		int age = 50;  	// 方法内部定义的变量被称作局部变量
		System.out.println("大家好,我" + age + "岁了,我在看书!");
	}
}

上面的代码中,在Student类的read ()方法中有一条打印语句,访问了变量age,此时访问的是局部变量age,也就是说当有另外一个程序调用read ()方法时,输出的age值为50,而不是30。

3.2.2 对象的创建与使用

在Java程序中可以使用new关键字创建对象,具体格式如下:

类名 对象名称 = null;
对象名称 = new 类名();

上述格式中,创建对象分为声明对象和实例化对象两步,也可以直接通过下面的方式创建对象,具体格式如下:

类名 对象名称 = new 类名();

例如,创建Student类的实例对象,示例代码如下:

Student stu = new Student();

上述代码中,new Student() 用于创建Student类的一个实例对象,Student stu则是声明了一个Student类型的变量stu。运算符 “=”将新创建的Student对象地址赋值给变量stu,变量stu引用的对象简称为stu对象。

class Student {
	String name;       			 // 声明姓名属性
	void read() {  
		System.out.println("大家好,我是" + name + ",我在看书!");
	}
}
public class Test {
    public static void main(String[] args[]) {  
		Student stu = new Student(); //创建并实例化对象
	}
}

上述代码在main()方法中实例化了一个Student对象,对象名称为stu。使用new关键字创建的对象是在堆内存分配空间。stu对象的内存分配如下图。

创建Student对象后,可以使用对象访问类中的某个属性或方法,对象属性和方法的访问通过“.”运算符实现,具体格式如下:

对象名称.属性名
对象名称.方法名

接下来通过一个案例学习对象属性和方法的访问。

1 class Student {
2	String name;       		// 声明姓名属性
3	void read() {  
4		System.out.println("大家好,我是" + name);
5	}
6 }
7 class Example01 {
8	public static void main(String[] args) {
9		Student stu1 = new Student(); 	// 创建第一个Student对象
10	Student stu2 = new Student(); 		// 创建第二个Student对象
11	stu1.name = "小明";                 	// 为stu1对象的name属性赋值
12	stu1.read();                  		// 调用对象的方法
13	stu2.name = "小华";
14	stu2.read();
15	}
16 }

上述代码中,第2~5行代码声明了一个String类型的name属性和一个read()方法,第9~10行代码创建了stu1对象和stu2对象;第11行代码为stu1对象name属性赋值;第12行代码通过stu1对象调用read()方法。第13行代码为stu2对象name属性赋值;第14行代码通过stu2对象调用read()方法。

从运行结果可以看出,stu1对象和stu2对象在调用read()方法时,打印的name值不相同。这是因为stu1对象和stu2对象是两个完全独立的个体,它们分别拥有各自的name属性,对stu1对象的name属性进行赋值并不会影响到stu2对象name属性的值。

为stu1对象和stu2对象中的属性赋值后,stu1对象和stu2对象的内存变化如下图。

3.2.3 对象的引用传递

类属于引用数据类型,引用数据类型就是指内存空间可以同时被多个栈内存引用。接下来通过一个案例为大家详细讲解对象的引用传递。

1 class Student {
2	  String name;       	// 声明姓名属性
3     int age;           		// 声明年龄属性
4	  void read() {  
5		  System.out.println("大家好,我是"+name+",年龄"+age);
6	  }
7 }
8 class Example02 {
9	public static void main(String[] args) {
10		Student stu1 = new Student (); //声明stu1对象并实例化
11		Student stu2 = null;          //声明stu2对象,但不对其进行实例化
12		stu2 = stu1;                       // stu1给stu2分配空间使用权。
13		stu1.name = "小明";          // 为stu1对象的name属性赋值
14		stu1.age = 20;
15		stu2.age = 50;
16		stu1.read();                  	  // 调用对象的方法
17		stu2.read();
18	}
19 }

上述代码中,第2~3行代码分别声明了一个String类型的name属性和一个int类型的age属性;第4~6行代码定义了一个read()方法。第10行代码声明stu1对象并实例化;第11行代码声明stu2对象,但不对其进行实例化。第12行代码把stu1对象的堆内存空间使用权分配给stu2。第13~14行代码为stu1对象的name属性和age属性赋值;第15行代码为stu2对象的age属性赋值;第16~17行代码分别使用stu1对象和stu2对象调用read()方法。

上述代码中,对象引用传递的内存分配如下图。首先声明对象stu1并为stu1对象开辟空间。

然后stu1对象为stu2对象分配使用权,stu1和stu2指向同一内存。

stu1对象为属性赋值。

通过stu2修改age属性的值。

一个栈内存空间只能指向一个堆内存空间,如果想要再指向其它堆内存空间,就必须先断开已有的指向才能分配新的指向。

3.2.4 访问控制

针对类、成员方法和属性,Java提供了4种访问控制权限,分别是private、default、protected和public。这4种访问控制权限按级别由小到大依次排列,如下图。

4种访问控制权限,具体介绍如下。 (1)private(当前类访问级别):private属于私有访问权限,用于修饰类的属性和方法。类的成员一旦使用了private关键字修饰,则该成员只能在本类中进行访问。 (2)default:如果一个类中的属性或方法没有任何的访问权限声明,则该属性或方法就是默认的访问权限,默认的访问权限可以被本包中的其它类访问,但是不能被其他包的类访问。

(3)protected:属于受保护的访问权限。一个类中的成员使用了protected访问权限,则只能被本包及不同包的子类访问。 (4)public:public属于公共访问权限。如果一个类中的成员使用了public访问权限,则该成员可以在所有类中被访问,不管是否在同一包中。

访问范围privatedefaultprotectedpublic
同一类中
同一包中的类
不同包的子类
全局范围

下面通过一段代码演示四种访问控制权限修饰符的用法。

public class Test {
    public int aa;			//aa可以被所有的类访问
    protected boolean bb; 		//可以被所有子类以及本包的类使用
    void cc() { 			//default 访问权限,能在本包范围内使用
        System.out.println("包访问权限");
    }
    //private权限的内部类,即这是私有的内部类,只能在本类使用
    private class InnerClass {
    }
}

类名Test只能使用public修饰或者不写修饰符。局部成员是没有访问权限控制的,因为局部成员只在其所在的作用域内起作用,不可能被其他类访问到。错误示例代码如下所示:

public class Test {
{
    public int aa;			//错误,局部变量没有访问权限控制
    protected boolean bb; 		//错误,局部变量没有访问权限控制
}
    void cc() { 			//default 访问权限,能在本包范围内使用
        System.out.println("包访问权限");
    }
private class InnerClass {  //private权限的内部类,即这是私有的内部类,只能在本类使用
    }
}

运行上述代码,程序会报错,如下图。

如果一个Java源文件中定义的所有类都没有使用public修饰,那么这个Java源文件的文件名可以是一切合法的文件名;如果一个源文件中定义了一个public修饰的类,那么这个源文件的文件名必须与public修饰的类名相同。

3.3 封装

3.3.1 为什么要封装

在Java面向对象的思想中,封装是指一种将抽象性函数式接口的实现细节部分包装、隐藏起来的方法。封装可以被认为是一个保护屏障,防止本类的代码和数据被外部类定义的代码随机访问。

1 class Student{
2	String name;       		// 声明姓名属性
3  	 int age;           		// 声明年龄属性
4	void read() {  
5		System.out.println("大家好,我是"+name+",年龄"+age);
6	}
7 }
8 public class Example03 {
9	public static void main(String[] args) {
10		Student stu = new Student();	// 创建学生对象
11		stu.name = "张三";	              	// 为对象的name属性赋值
12		stu.age = -18;	                  // 为对象的age属性赋值
13		stu.read();	                       	// 调用对象的方法
14	}
15 }

在上述代码中,第12行代码将年龄赋值为-18岁,这在程序中是不会有任何问题的,因为int的值可以取负数。但是在现实中,-18明显是一个不合理的年龄值。为了避免这种错误的发生,在设计Student类时,应该对成员变量的访问作出一些限定,不允许外界随意访问,这就需要实现类的封装。

3.3.2 如何实现封装

类的封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类提供的方法实现对内部信息的操作访问。 在Java开发中,在定义一个类时,将类中的属性私有化,即使用private关键字修饰类的属性,被私有化的属性只能在类中被访问。如果外界想要访问私有属性,则必须通过setter和getter方法设置和获取属性值。

1 class Student{
2   private String name;       		// 声明姓名属性
3	private  int age;           		// 声明年龄属性
4	public String getName() {
5    	return name;
6   }
7   public void setName(String name) {
8      this.name = name;
9   }
10  public int getAge() {
11       return age;
12  }
13  public void setAge(int age) {
14       if(age<=0){
15          System.out.println("您输入的年龄有误!");
16       } else {
17          this.age = age;
18       }
19    }
20    public void read() {  
21		System.out.println("大家好,我是"+name+",年龄"+age);
22	}
23 }
24 public class Example04 {
25	public static void main(String[] args) {
26		Student stu = new Student();	// 创建学生对象
27		stu.setName("张三");	              	// 为对象的name属性赋值
28		stu.setAge(-18);	               	// 为对象的age属性赋值
29		stu.read();	               // 调用对象的方法
30	}
31 }

使用private关键字将属性name和age声明为私有变量,并对外界提供公有的访问方法,其中,getName()方法和getAge()方法用于获取name属性和age属性的值,setName()方法和setAge()方法方法用于设置name属性和age属性的值。程序运行结果如下图。

【案例3-1】 基于控制台的购书系统

伴随互联网的蓬勃发展,网络购书系统作为电子商务的一种形式,正以其高效、低成本的优势逐步成为新兴的经营模式,人们已经不再满足互联网的用途仅仅局限于信息的浏览和发布,更渴望着能够充分享受互联网带来的更多便利。网络购书系统正适应了当今社会快节奏地生活,使顾客足不出户便可以方便快捷轻松地选购自己喜欢的图书。

本案例要求,使用所学知识编写一个基于控制台的购书系统,实现购书功能。程序输出所有图书的信息,包括每本书的编号、书名、单价、库存。 顾客购书时,根据提示输入图书编号选购需要的书,并根据提示输入购买书的的数量。购买完毕后输出顾客的订单信息,包括订单号、订单明细、订单总额。

 1 public class Book {
 2     private int id;    // 编号
 3     private String name;  // 书名
 4     private double price;  // 价格
 5     private int storage;   // 库存
 6     // 有参构造
 7     public Book(int id, String name, double price, int storage) {
 8         this.id = id;
 9         this.name = name;
 10         this.price = price;
 11         this.storage = storage;
 12     }
 13     // 获取书号
 14     public int getId() {
 15         return id;
 16     }
 17     // 获取书名
 18     public String getName() {
 19         return name;
 20     }
 21     // 获取价格
 22     public double getPrice() {
 23         return price;
 24     }
 25     // 获取库存
 26     public int getStorage() {
 27         return storage;
 28     }
 29 }
 1 public class OrderItem {
 2     private Book book;
 3     private int num;
 4     // 有参构造方法
 5     public OrderItem(Book book, int num) {
 6         this.book = book;
 7         this.num = num;
 8     }
 9     // 获取图书对象
 10 public Book getBook() {
 11       return book;
 12 }
 13     // 获取订购图书数量
 14 public int getNum() {
 15       return num;
 16     }
 17 }
 1 public class Order {
 2     private String orderId;
 3     private OrderItem items[];
 4     private double total;
 5     // 有参构造
 6     public Order(String orderId) {
 7         this.orderId = orderId;
 8         this.items = new OrderItem[3];
 9     }
 10 // 获取订单号
 11 public String getOrderId() {
 12         return orderId;
 13 }
 14 // 获取订单列表
 15 public OrderItem[] getItems() {
 16         return items;
 17 }
 18 // 获取订单总额
 19 public double getTotal() {
 20         calTotal();
 21         return total;
 22 }
 23 // 指定一个订单项
 24 public void setItem(OrderItem item, int i) {
 25         this.items[i] = item;
 26 }
 27 // 计算订单总额
 28 public void calTotal() {
 29 double total = 0;
 30 for (int i = 0; i < items.length; i ++) {
 31       total += items[i].getNum() * items[i].getBook().getPrice();
 32 }
 33       this.total = total;
 34 }
 35 }
 1 import java.util.Scanner;
 2 public class PayBooks {
 3     public static void main(String[] args) {
 4         Book books[] = new Book[3];
 5         //模拟从数据库中读取图书信息并输出
 6         outBooks(books);
 7         // 顾客购买图书
 8         Order order = purchase(books);
 9         // 输出订单信息
 10         outOrder(order);
 11     }
 12     // 顾客购买图书
 13     public static Order purchase(Book books[]) {
 14         Order order = new Order("00001");
 15         OrderItem item = null;
 16         Scanner in = new Scanner(System.in);
 17         for (int i = 0; i < 3; i ++) {
 18             System.out.println("请输入图书编号选择图书:");
 19             int cno = in.nextInt();
 20             System.out.println("请输入购买图书数量:");
 21             int pnum = in.nextInt();
 22             item = new OrderItem(books[cno-1],pnum);
 23             order.setItem(item, i);
 24             System.out.println("请继续购买图书。");
 25         }
 26         in.close();
 27         return order;
 28     }
 29     // 输出订单信息
 30     public static void outOrder(Order order) {
 31         System.out.println("\n\t图书订单");
 32         System.out.println("图书订单号:"+order.getOrderId());
 33         System.out.println("图书名称\t购买数量\t图书单价");
 34         System.out.println("--------------------------------------");
 35         OrderItem items[] = order.getItems();
 36         for(int i = 0; i < items.length; i ++) {
 37 System.out.println(items[i].getBook().getName()+"\t"+items[i].getNum(
 38 )+"\t"+items[i].getBook().getPrice());
 39             //System.out.println("\n");
 40         }
 41 System.out.println("---------------------------------------");
 42  System.out.println("订单总额:\t\t"+order.getTotal());
 43 }
 44     // 模拟从数据库中读取图书信息并输出
 45     public static void outBooks(Book books[]) {
 46         books[0] = new Book(1,"Java教程",30.6,30);
 47         books[1] = new Book(2,"JSP教程",42.1,40);
 48         books[2] = new Book(3,"SSH架构",47.3,15);
 49         System.out.println("\t图书列表");
 50         System.out.println("图书编号\t图书名称\t\t图书单价\t库存数量");
 51         System.out.println("---------------------------------------");
 52         for (int i = 0; i < books.length; i ++) {
 53             System.out.println(i+1+"\t"+books[i].getName()+"\t"+
 54                         books[i].getPrice()+"\t"+books[i].getStorage());
 55         }
 56         System.out.println("---------------------------------------");
 57     }
 58 }

3.4 构造方法

实例化一个对象后,如果要为这个对象中的属性赋值,则必须通过直接访问对象的属性或调用setter方法才可以,如果需要在实例化对象的时为这个对象的属性赋值,可以通过构造方法实现。构造方法(也被成为构造器)是类的一个特殊成员方法,在类实例化对象时自动调用。

3.4.1 定义构造方法

构造方法是一个特殊的成员方法,在定义时,有以下几点需要注意: (1)构造方法的名称必须与类名一致。 (2)构造方法名称前不能有任何返回值类型的声明。 (3)不能在构造方法中使用return返回一个值,但是可以单独写return语句作为方法的结束。

接下来通过一个案例演示构造方法的定义。

1 class Student{
2	public Student() {  
3		System.out.println("调用了无参构造方法");
4	}
5 }
6 public class Example05 {
7	public static void main(String[] args) {
8             System.out.println("声明对象...");
9		    Student stu = null;           	//声明对象
10         System.out.println("实例化对象...");
11         stu = new Student();     			//实例化对象
12	}
13 }

在一个类中除了定义无参的构造方法外,还可以定义有参的构造方法,通过有参的构造方法可以实现对属性的赋值。接下来演示有参构造方法的定义与调用。

1 class Student{
2    private String name;
3    private int age;
4    public Student(String n, int a) 5{
6       name = n;
7       age = a;
8   }
9 	 public void read(){
10        System.out.println("我是:"+name+",年龄:"+age);
11    }
12 }
13 public class Example06 {
14      public static void main(String[] args) {
15         Student stu = new Student("张三",18);    // 实例化Student对象
16         stu.read();
17      }
18 }

Student类增加了私有属性name和age,并且定义了有参的构造方法Student (String name, int a)。第14行代码实例化Student对象,该过程会调用有参的构造方法,并传入参数“张三”和18,分别赋值给name和age。 由运行结果可以看出,stu对象在调用read()方法时, name属性已经被赋值为张三,age属性已经被赋值为20。

3.4.2 构造方法的重载

与普通方法一样,构造方法也可以重载,在一个类中可以定义多个构造方法,只要每个构造方法的参数或参数个数不同即可。在创建对象时,可以通过调用不同的构造方法为不同的属性赋值。

接下来通过一个案例学习构造方法的重载。

1 class Student{
2    private String name;
3    private int age;
4    public Student() { }
5    public Student(String n) {
6             name = n;
7    }
8    public Student(String n, int a) {
9             name = n;
10        age = a;
11    }
12 	 public void read(){
13       System.out.println("我是:"+name+",年龄:"+age);
14    }
15 }
16 public class Example07 {
17      public static void main(String[] args) {
18         Student stu1 = new Student("张三");
19         Student stu2 = new Student("张三",18);   // 实例化Student对象
20         stu1.read();
21         stu2.read();
22      }
23 }

上述代码中,第5~11行代码声明了Student类的两个重载的构造方法。在main()方法中,第18~21行代码在创建stu1对象和stu2对象时,根据传入参数个数不同,stu1调用了只有一个参数的构造方法;stu2调用的是有两个参数的构造方法。

多学一招:默认构造方法

在Java中的每个类都至少有一个构造方法,如果在一个类中没有定义构造方法,系统会自动为这个类创建一个默认的构造方法,这个默认的构造方法没有参数,方法体中没有任何代码,即什么也不做。

下面程序中Student类的两种写法,效果是完全一样的。

第一种写法:

class Student {				
}		

第二种写法:

class Student{
	public Student(int age){
        System.out.println(age);
    }
}

第二种写法:对于第一种写法,类中虽然没有声明构造方法,但仍然可以用new Student()语句创建Student类的实例对象,在实例化对象时调用默认构造方法。

上面的Student类中定义了一个有参构造方法,这时系统就不再提供默认的构造方法。接下来再编写一个测试程序调用上面的Student类。

public class Example08 { 
	public static void main(String[] args) {
		Student stu = new Student(); 	// 实例化 Student对象
	}
}

运行程序,编译器报错,如下图。

编译器提示无法将Student类的无参构造方应用到有参构造方法,原因是调用new Student ()创建Student类的实例对象时,需要调用无参构造方法,而Student类中定义了一个有参的构造方法,系统不再提供无参的构造方法。为了避免上面的错误,在一个类中如果定义了有参的构造方法,最好再定义一个无参的构造方法。 需要注意的是,构造方法通常使用public进行修饰。

【案例3-2】 银行存取款

对于银行存取款的流程,人们非常熟悉,用户可在银行对自己的资金账户进行存款、取款、查询余额等操作,极大的便利了人民群众对资金的管理。 本案例要求,使用所学知识编写一个程序实现银行存取款功能。案例要求如下: (1)创建账户,初始存款为500。 (2)向账户存入1000元。 (3)从账户取出800元。

 1 public class BankAccount {
 2     int account_number;//账号
 3     double leftmoney;//存款余额
 4 public double getleftmoney () {     //查询余额
 5     return leftmoney;
 6 }
 7 public void savemoney(double money) {   //存款
 8     leftmoney+=money;
 9 }
 10 public void getmoney (double money){  //取款
 11     leftmoney-=money;
 12 }
 13 public BankAccount (int number, double money){ //构造方法,用来初始化变量
 14     account_number=number;
 15     leftmoney=money;
 16 }
 17 public static void main(String[] args) {
 18     BankAccount ba=new BankAccount(123456,500);
 19     ba.savemoney(1000);
 20    System.out.println("存入1000元后,您的余额为:"+ba.getleftmoney());
 21    ba.getmoney(2000);
 22    System.out.println("取款800元后,您的余额为:"+ba.getleftmoney());
 23 }
 24 }

【案例3-3】查看手机配置与功能

随着科技的发展,手机的使用已经普及到每个人,手机的功能越来越多并且越来越强大,人们在生活中越来越依赖手机。 有两款配置和功能都不同的手机,配置信息包括品牌、型号、操作系统、价格和内存;手机功能包括自动拨号、游戏和播放歌曲。本案例要求使用所学知识编写一个程序实现手机配置及功能查看,并将查看结果打印在控制台。

 1 public class Phone {
 2     String brand;  // 品牌
 3     String type;   // 型号
 4     String os;     // 操作系统
 5     int price;    // 价格
 6     int memorySize;   // 内存
 7     // 无参构造
 8     public Phone(){   
 9     }
 10     // 有参构造
 11     public Phone(String brand, String type, String os, int price, int
 12  memorySize) {
 13         this.brand = brand;
 14         this.type = type;
 15         this.os = os;
 16         this.price = price;
 17         this.memorySize = memorySize;
 18     }
 19     // 关于本机
 20     public void about() {
 21         System.out.println("品牌:"+brand+"\n"+"型号:"+type+"\n"+
 22                             "操作系统:"+os+"\n"+"价格:"+price+"\n"+"内存:
 23 "+memorySize+"\n");
 24     }
 25     // 打电话
 26     public void call(int num) {
 27         System.out.println("使用自动拨号功能:");
 28         String phoneNo = "";
 29         switch (num) {
 30         case 1: phoneNo = "爸爸的号。";break;
 31         case 2: phoneNo = "妈妈的号。";break;
 32         case 3: phoneNo = "爷爷的号。";break;
 33         case 4: phoneNo = "奶奶的号。";break;
 34         }
 35         System.out.println(phoneNo);
 36     }
 37     // 打游戏
 38     public void playGame() {
 39         System.out.println("玩扫雷游戏。");
 40     }
 41     // 下载音乐
 42     public void downloadMusic(String song) {
 43         System.out.println("开始下载。。。。");
 44         System.out.println("下载完成。。。。");
 45     }
 46     // 播放音乐
 47     public void playMusic(String song) {
 48         System.out.println("播放歌曲:"+song);
 49     }
 50 }
 1 public class PhoneTest {
 2 	public static void main(String[] args) {
 3         // 通过无参构造创建手机对象一
 4         Phone p1 = new Phone();
 5         p1.brand = "苹果";
 6         p1.type = "iphoneX";
 7         p1.os = "ios";
 8         p1.price = 8888;
 9         p1.memorySize = 16;
 10         // 测试p1的各项功能
 11         p1.about();
 12         p1.call(3);
 13         p1.playGame();
 14         p1.playMusic("我的中国心");
 15         System.out.println("********************");
 16         Phone p2 = new Phone("华为","华为荣耀20","Android",6666,16);
 17         // 测试p2 的各项功能
 18         p2.about();
 19         p2.call(4);
 20         p2.playGame();
 21         p2.playMusic("北京欢迎你");
 22     }
 23 }

3.5 this关键字

在Java开发中,当成员变量与局部变量发生重名问题时,需要使用到this关键字分辨成员变量与局部变量, Java中的this关键字语法比较灵活,其主要作用主要有以下3种。 (1)使用this关键字调用本类中的属性。 (2)this关键字调用成员方法。 (3)使用this关键字调用本类的构造方法。

3.5.1 使用this关键字调用本类中的属性

在类的构造方法中,如果参数名称与类属性名称相同,则会导致成员变量和局部变量的名称冲突。

1 class Student {
2     private String name;
3     private int age;
4   // 定义构造方法
5    public Student(String name,int age) {
6           name = name;
7           age = age;
8     }
9    public String read(){
10        return "我是:"+name+",年龄:"+age;
11    }
12 }
13 public class Example09 {
14     public static void main(String[] args) {
15         Student stu = new Student("张三", 18);
16         System.out.println(stu.read());
17     }
18 }

从运行结果可以看出,stu对象姓名为null,年龄为0,这表明构造方法中的赋值并没有成功。这是因为参数名称与对象成员变量名称相同,编译器无法确定哪个名称是当前对象的属性。为了解决这个问题,Java提供了关键字this指代当前对象,通过this可以访问当前对象的成员。

使用this关键字指定当前对象属性。

1 class Student {
2     private String name;
3     private int age;
4   // 定义构造方法
5    public Student(String name,int age) {
6       this.name = name;
7       this.age = age;
8     }
9 public String read(){
10        return "我是:"+name+",年龄:"+age;
11    }
12 }
13 public class Example10 {
14     public static void main(String[] args) {
15         Student stu = new Student("张三", 18);
16         System.out.println(stu.read());
17     }
18 }

在构造方法之中,使用this关键字明确标识出了类中的两个属性“this.name”和“this.age”,所以在进行赋值操作时不会产生歧义。程序运行结果如下图。

3.5.2 使用this关键字调用成员方法

通过this关键字调用成员方法,具体示例代码如下:

class Student {
	public void openMouth() {
		...
	}
	public void read() {
		this.openMouth();
	}
}

3.5.3 使用this关键字调用构造方法

构造方法是在实例化对象时被Java虚拟机自动调用,在程序中不能像调用其他成员方法一样调用构造方法,但可以在一个构造方法中使用“this(参数1,参数2…)”的形式调用其他的构造方法。接下来通过一个案例演示使用this关键字调用构造方法。

class Student {
	private String name;
	private int age;
	public Student () {
		System.out.println("实例化了一个新的Student对象。");
	}
	public Student (String name,int age) {
		this();                  // 调用无参的构造方法
this.name = name;
    this.age = age;
	}
	public String read(){
        return "我是:"+name+",年龄:"+age;
	}
}
public class Example11 { 
	public static void main(String[] args) {
	    Student stu = new Student ("张三",18); // 实例化 Student对象
         System.out.println(stu.read());
	}
}

在使用this调用类的构造方法时,应注意以下几点: (1)只能在构造方法中使用this调用其他的构造方法,不能在成员方法中通过this调用其他构造方法。 (2)在构造方法中,使用this调用构造方法的语句必须位于第一行,且只能出现一次。下面程序的写法是错误的:

public Student(String name) {
   System.out.println("有参的构造方法被调用了。");
   this(name); 			//不在第一行,编译错误!
}

(3)不能在一个类的两个构造方法中使用this互相调用,错误程序如下。

class Student {
	public Student () {
         this("张三");  		    		// 调用有参构造方法
		System.out.println("无参的构造方法被调用了。");
	}
	public Student (String name) {
		this();                  		// 调用无参构造方法
		System.out.println("有参的构造方法被调用了。");
	}
}

3.6 代码块

代码块,简单来讲,就是用“{}”括号括起来的一段代码,根据位置及声明关键字的不同,代码块可以分为4种:普通代码块、构造块、静态代码块、同步代码块。

3.6.1 普通代码块

普通代码块就是直接在方法或是语句中定义的代码块,具体示例如下。

public class Example12 { 
	public static void main(String[] args) {
	   {
         int age = 18;
         System.out.println("这是普通代码块。age:"+age);
        } 
         int age = 30;
         System.out.println("age:"+age);
	}
}

在上述代码中,每一对“{}”括起来的代码都称为一个代码块。Example12是一个大的代码块,在Example12代码块中包含了main()方法代码块,在main()方法中又定义了一个局部代码块,局部代码块对main()方法进行了“分隔”,起到了限定作用域的作用。局部代码块中定义了变量age,main()方法代码块中也定义了变量age,但由于两个变量处在不同的代码块,作用域不同,因此并不相互影响。

3.6.2 构造块

构造代码块是直接在类中定义的代码块。接下来通过一个案例演示构造代码块的作用。

1 class Student{
2    String name;    				//成员属性
3    {
4        System.out.println("我是构造代码块");       //与构造方法同级
5    }
6    //构造方法
7    public Student(){
8        System.out.println("我是Student类的构造方法");
9    }
10 }
11 public class Example12  {
12    public static void main(String[] args) 13{
14        Student stu1 = new Student();
15        Student stu2 = new Student();
16    }
17}

上述代码中,第3~5行表示的代码块定义在成员位置,与构造方法、成员属性同级,这就是构造块。 由运行结果可以得出以下两点结论: (1)在实例化Student类对象stu1、stu2时,构造块的执行顺序大于构造方法(这里和构造块写在前面还是后面没有关系)。 (2)每当实例化一个Student类对象,都会在执行构造方法之前执行构造代码块。

3.7 static关键字

在定义一个类时,只是在描述某事物的特征和行为,并没有产生具体的数据。只有通过new关键字创建该类的实例对象时,才会开辟栈内存及堆内存,在堆内存中要保存对象的属性时,每个对象会有自己的属性。如果希望某些属性被所有对象共享,就必须将其声明为static属性。如果属性使用了static关键字进行修饰,则该属性可以直接使用类名称进行调用。除了修饰属性,static关键字还可以修饰成员方法。

3.7.1 静态属性

如果在Java程序中使用static修饰属性,则该属性称为静态属性(也称全局属性),静态属性可以使用类名直接访问,访问格式如下:类名.属性名

在学习静态属性之前,先来看一个案例。

1 class Student {
2    String name;                    	// 定义name属性
3    int age;                         	// 定义age属性
4    String school = "A大学";    	// 定义school属性
5    public Student(String name,int age){
6        this.name = name;
7        this.age = age;
8    }
9 public void info(){
10   System.out.println("姓名:" + this.name+",年龄:" +this. age+",学校:" + school);  
11  }
12 }
13 public class Example13 {
14    public static void main(String[] args) {
15        Student stu1 = new Student("张三",18);    // 创建学生对象
16        Student stu2 = new Student("李四",19);
17        Student stu3 = new Student("王五",20);
18        stu1.info();
19        stu2.info();
20        stu3.info();
21    }
22 }

上述代码中,第5~7行代码声明了Student类的有参构造,第9~12行代码输出了name和age属性的值。第16~21行代码分别定义了Studen类的三个实例对象,并分别使用三个实例对象调用info()方法。

上述案例中,三名均学生均来自于A大学。下面,我们考虑一种情况:假设A大学改名为了B大学,而且此Student类已经产生了10万个学生对象,那么意味着,如果要修改这些学生对象的学校信息,则要把这10万个对象中的学校属性全部修改,共修改10万遍,这样肯定是非常麻烦的。 为了解决上述问题,可以使用static关键字修饰school属性,将其变为公共属性。这样,school属性只会分配一块内存空间,被Student类的所有对象共享,只要某个对象进行了一次修改,全部学生对象的school属性值都会发生变化。

接下来修改上述代码,使用static关键字修饰school属性。

1 class Student {
2    String name;                          	// 定义name属性
3    int age;                               	// 定义age属性
4    static String school = "A大学";    	// 定义school属性
5    public Student(String name,int age){
6        this.name = name;
7        this.age = age;
8    }
9 public void info(){
10   System.out.println("姓名:" +this. name+",年龄:" +this.age+",学校:" + school);  
11 }
12 }
13 public class Example14 {
14    public static void main(String[] args) {
15        Student stu1 = new Student("张三",18);        // 创建学生对象
16        Student stu2 = new Student("李四",19);
17        Student stu3 = new Student("王五",20);
18        stu1.school = "B大学";
19        stu1.info();
20        stu2.info();
21        stu3.info();
22    }
23 }

在上述代码中,第4行代码使用static关键字修饰了school属性,第19行代码使用stu1对象为school属性重新赋值。 在运行结果中可以发现,只修改了一个stu1对象的学校属性,stu1和stu2对象的school属性内容都发生了变化,说明使用static声明的属性是对所有对象共享的。

school属性修改前的内存如下图。

3.7.2 静态方法

如果想要使用类中的成员方法,就需要先将这个类实例化,而在实际开发时,开发人员有时希望在不创建对象的情况下,通过类名就可以直接调用某个方法,要实现这样的效果,只需要在成员方法前加上static关键字,使用static关键字修饰的方法通常称为静态方法。

同静态变量一样,静态方法也可以通过类名和对象访问,具体如下所示。 类名.方法 或 实例对象名.方法

接下来通过一个案例来学习静态方法的使用。

1 class Student {
2    private String name;                         		// 定义name属性
3    private int age;                              		// 定义age属性
4    private static String school = "A大学";   	// 定义school属性
5    public Student(String name,int age){
6        this.name = name;
7        this.age = age;
8    }
9    public void info(){
10      System.out.println("姓名:" +this.name+",年龄:" + this.age+",学校:" + school);
12    }
13   public String getName() {
14     return name;
15   }
16   public void setName(String name) {
17     this.name = name;
18   }
19 public void setName(String name) {
20     this.name = name;
21   }
22   public int getAge() {
23     return age;
24   }
25   public void setAge(int age) {
26     this.age = age;
27   }
28   public static String getSchool() {
29      return school;
30   }
31   public static void setSchool(String school) {
32      Student.school = school;
33   }
34 }
35 class Example15 {
36    public static void main(String[] args) {
37        Student stu1 = new Student("张三",18);      // 创建学生对象
38        Student stu2 = new Student("李四",19);
39        Student stu3 = new Student("王五",20);
40	   stu1.setAge(20);
41        stu2.setName("小明");
42	   Student.setSchool("B大学");
43        stu1.info();
44        stu2.info();
45        stu3.info();
46    }
47 }

Student类将所有的属性都进行了封装,所以想要更改属性就必须使用setter方法。第13~30行代码声明了name、age和school属性的getter和setter方法,第37~39行代码分别对name、age和school属性的值进行修改,但是school属性是使用static声明的,所以可以直接使用类名Student进行调用。

静态方法只能访问静态成员,因为非静态成员需要先创建对象才能访问,即随着对象的创建,非静态成员才会分配内存。而静态方法在被调用时可以不创建任何对象。

3.7.3 静态代码块

在Java类中,用static关键字修饰的代码块称为静态代码块。当类被加载时,静态代码块会执行,由于类只加载一次,因此静态代码块只执行一次。在程序中,通常使用静态代码块对类的成员变量进行初始化。

接下来通过一个案例学习静态代码块的使用。

class Student{
    String name;    				//成员属性
    {
        System.out.println("我是构造代码块");
    }
    static {
        System.out.println("我是静态代码块");
    }
    public Student(){      			//构造方法
        System.out.println("我是Student类的构造方法");
    }
}
class Example16{
    public static void main(String[] args) {
        Student stu1 = new Student();
        Student stu2 = new Student();
        Student stu3 = new Student();
    }
}

上述代码中,第3~5行代码声明了一个构造代码块,第6~8行声明了一个静态代码块,第15~17行代码分别实例化了3个Student对象。 从运行结果可以看出,代码块的执行顺序为静态代码块、构造代码块、构造方法。 static修饰的量会随着class文件一同加载,属于优先级最高的。在main()方法中创建了3个Student对象,但在3次实例化对象的过程中,静态代码块中的内容只输出了一次,这就说明静态代码块在类第一次使用时才会被加载,并且只会加载一次。

【案例3-4】:学生投票系统

某班级投票竞选班干部,班级学生人数为10人,每个学生只能投一票,投票成功提示“感谢你的投票”。若重复投票,提示“请勿重复投票”。当投票总数达到10或者主观结束投票时,统计投票学生人数和投票结果。本案例要求编程一个程序实现学生投票。

 1 import java.util.HashSet;
 2 import java.util.Set;
 3 public class Voter {
 4     // 属性的定义
 5     private static final int MAX_COUNT = 100;    // 最大投票数
 6     private static int count;                   // 投票数
 7     // 静态变量,存放已经投票的学生
 8     private static Set<Voter> voters = new HashSet<Voter>();
 9     private String name;
 10     private String answer;
 11     // 构造方法
 12     public Voter(String name) {
 13         this.name = name;
 14     }
 15     // 投票
 16     public void voterFor(String answer) {
 17         if (count == MAX_COUNT){
 18             System.out.println("投票结束。");
 19             return ;
 20         }
 21         if (voters.contains(this)){
 22             System.out.println(name+",请勿重复投票。");
 23         } else {
 24             this.answer = answer;
 25             count ++;
 26             voters.add(this);
 27             System.out.println(name+" 感谢你的投票。");
 28         }
 29     }
 30     // 打印投票结果
 31     public static void printVoterResult() {
 32         System.out.println("当前投票数为:"+count);
 33         System.out.println("参与投票的学生和结果如下:");
 34         
 35         for (Voter voter: voters) {
 36             System.out.println(voter.name+" 意见 "+voter.answer);
 37         }
 38     }
 39     public static void main(String[] args) {
 40         // 创建参与投票的学生对象
 41         Voter tom = new Voter("Tom");
 42         Voter jack = new Voter("Jack");
 43         Voter mike = new Voter("Mike");
 44         // 学生开始投票
 45         tom.voterFor("是");
 46         tom.voterFor("否");
 47         jack.voterFor("是");
 48         mike.voterFor("是");
 49         // 打印投票结果
 50         Voter.printVoterResult();
 51     }
 52 }

3.8 本章小结

本章详细介绍了面向对象的基础知识。首先介绍了面向对象的思想;其次介绍了类与对象之间的关系,包括类的定义、对象的创建与使用等;接着介绍了类的封装;然后介绍了构造方法,包括构造方法的定义与重载;最后介绍了代码块的使用以及static关键字的使用。通过本章的学习,读者已经对Java中面向对象的思想有了初步的认识,熟练掌握好这些知识,有助于学习下一章的内容。深入理解面向对象的思想,对以后的实际开发也是大有裨益的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

笔触狂放

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

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

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

打赏作者

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

抵扣说明:

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

余额充值