第五章 面向对象基础

1、面向对象编程设计思想

1.1、编程语言概述

Java是一种计算机程序设计语言。所有的计算机程序一直都是围绕着两件事在进行的,程序设计就是用某种语言编写代码来完成这两件事,所以程序设计语言又称为编程语言(编写程序的语言)。

  1. 如何表示和存储数据
    • 基本数据类型的常量和变量:表示和存储一个个独立的数据
    • 对象:表示和存储与某个具体事物相关的多个数据(例如:某个学生的姓名、年龄、联系方式等)
    • 数据结构:表示和存储一组对象,数据结构有数组、链表、栈、队列、散列表、二叉树、堆……
  2. 基于这些数据都有什么操作行为,其实就是实现什么功能
    • 数据的输入和输出
    • 基于一个或两个数据的操作:赋值运算、算术运算、比较运算、逻辑运算等
    • 基于一组数据的操作:统计分析、查找最大值、查找元素、排序、遍历等

1.2、程序设计方法

C语言是一种面向过程的程序设计语言,因为C语言是在面向过程思想的指引下去设计、开发计算机程序的。

Java语言是一种面向对象的程序设计语言,因为Java语言是在面向对象思想的指引下去设计、开发计算机程序的。

其中面向对象和面向过程都是一种编程思想,基于不同的思想会产生不同的程序设计方法。

  1. 面向过程的程序设计思想(Process-Oriented Programming),简称POP

    • 关注的焦点是过程:过程就是操作数据的步骤,如果某个过程的实现代码在很多地方重复出现,那么就可以把这个过程抽象为一个函数,这样就可以大大简化冗余代码,也便于维护。

    • 代码结构:以函数为组织单位。独立于函数之外的数据称为全局数据,在函数内部的称为局部数据。

  2. 面向对象的程序设计思想( Object Oriented Programming),简称 OOP

    • 关注的焦点是类:面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。某个事物的一个具体个体称为实例或对象。
    • 代码结构:以类为组织单位。每种事物都具备自己的属性(即表示和存储数据,在类中用成员变量表示)和行为/功能(即操作数据,在类中用成员方法表示)。

2、类与对象

​ 类和对象是面向对象编程的两个主要方面。Java 中的一切都与类、对象及其属性和方法相关联。例如:在现实生活中,汽车是一个物体。汽车有属性,比如重量和颜色,也有方法,比如行驶和刹车。

​ 类就像一个构造对象的模具,或者创建对象的“蓝图”。

对象
宝马、奔驰、比亚迪、保时捷
水果 苹果、香蕉、哈密瓜、芒果
手机 iPhone、华为、小米、VIVO

2.1、如何创建类

关键字:class(小写)

1
2
3
【修饰符】 class 类名{

}

类的定义格式举例:

1
2
3
public class Student{

}

2.2、对象的创建

关键字:new

1
2
3
4
5
6
7
8
new 类名()//也称为匿名对象

//给创建的对象命名
//或者说,把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();

Student student1 = new Student();
Student student2 = new Student();

3、类属性(成员变量)

3.1 声明类成员变量

​ 类属性是类中的变量。类属性的另一个术语是字段。下面创建类一个 “人” 类,并为它添加了三个属性,名字、性别和年龄。

1
2
3
4
5
public class Person{
String name;
char gender;
int age;
}
  • 位置要求:必须在类中,方法外

  • 类型要求:可以是 Java 的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)

3.2 对象的实例变量

实例变量的特点

(1)实例变量的值是属于某个对象的

  • 必须通过对象才能访问实例变量
  • 每个对象的实例变量的值是独立的

(2)实例变量有默认值

分类 数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
数据类型 默认值
引用类型 数组,类,接口 null

实例变量的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 对象.实例变量

public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "张三";
p1.age = 23;
p1.gender = '男';

Person p2 = new Person();
/*
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
*/
System.out.println("p1对象的实例变量:");
System.out.println("p1.name = " + p1.name);
System.out.println("p1.age = " + p1.age);
System.out.println("p1.gender = " + p1.gender);

System.out.println("p2对象的实例变量:");
System.out.println("p2.name = " + p2.name);
System.out.println("p2.age = " + p2.age);
System.out.println("p2.gender = " + p2.gender);
}
}

4、 方法(Method)

4.1 方法的概念

方法也叫函数,是一组代码语句的封装,从而实现代码重用,从而减少冗余代码,通常它是一个独立功能的定义,方法是一个类中最基本的功能单元。

1
2
3
4
5
6
Math.random()的random()方法
Math.sqrt(x)的sqrt(x)方法
System.out.println(x)的println(x)方法

Scanner input = new Scanner(System.in);
input.nextInt()的nextInt()方法

4.2 方法的特点

(1)必须先声明后使用

类,变量,方法等都要先声明后使用

(2)不调用不执行,调用一次执行一次。

4.3 如何声明方法

声明方法的位置

声明方法的位置==必须在类中方法外==,即不能在一个方法中直接定义另一个方法。

声明位置示例:

1
2
3
4
5
6
7
8
类{
方法1(){

}
方法2(){

}
}

错误示例:

1
2
3
4
5
6
7
类{
方法1(){
方法2(){ //位置错误

}
}
}

声明方法的语法格式

1
2
3
【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】{
方法体的功能代码
}

(1)一个完整的方法 = 方法头 + 方法体。

  • 方法头就是 【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】,也称为方法签名,通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
  • 方法体就是方法被调用后要指定的代码,也是完成方法功能的具体实现代码,对于调用者来说,不了解方法体如何实现的,并影响方法的使用。

(2)方法头可能包含5个部分,但是有些部分是可能缺省的

  • 修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面会一一学习。其中根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法。==接下来咱们先学习实例方法==。

返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者

  • 基本数据类型
  • 引用数据类型
  • 无返回值类型:void

方法名:给方法起一个名字,见名知意,能准确代表该方法功能的名字

参数列表:表示完成方法体功能时需要外部提供的数据列表

  • 无论是否有参数,()不能省略

  • 如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用逗号分隔,例如:

    • 一个参数: (数据类型 参数名)
    • 二个参数: (数据类型1 参数1, 数据类型2 参数2)
  • 参数的类型可以是基本数据类型、引用数据类型

  • throws 异常列表:可选,在异常章节再讲

(3)方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码

关于方法体中return语句的说明:

  • return语句的作用是结束方法的执行,并将方法的结果返回去

  • 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。

  • 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。

  • return语句后面就不能再写其他代码了,否则会报错:Unreachable code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 方法定义案例演示
*/
public class MethodDefineDemo {
/**
* 无参无返回值方法的演示
*/
void sayHello(){
System.out.println("hello");
}

/**
* 有参无返回值方法的演示
* @param length int 第一个参数,表示矩形的长
* @param width int 第二个参数,表示矩形的宽
* @param sign char 第三个参数,表示填充矩形图形的符号
*/
void printRectangle(int length, int width, char sign){
for (int i = 1; i <= length ; i++) {
for(int j=1; j <= width; j++){
System.out.print(sign);
}
System.out.println();
}
}

/**
* 无参有返回值方法的演示
* @return
*/
int getIntBetweenOneToHundred(){
return (int)(Math.random()*100+1);
}

/**
* 有参有返回值方法的演示
* @param a int 第一个参数,要比较大小的整数之一
* @param b int 第二个参数,要比较大小的整数之二
* @return int 比较大小的两个整数中较大者的值
*/
int max(int a, int b){
return a > b ? a : b;
}
}

4.4 如何调用实例方法

方法调用语法格式

1
对象.非静态方法(【实参列表】)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 方法调用案例演示
*/
public class MethodInvokeDemo {
public static void main(String[] args) {
//创建对象
MethodDefineDemo md = new MethodDefineDemo();

System.out.println("-----------------------方法调用演示-------------------------");

//调用MethodDefineDemo类中无参无返回值的方法sayHello
md.sayHello();
md.sayHello();
md.sayHello();
//调用一次,执行一次,不调用不执行

System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中有参无返回值的方法printRectangle
md.printRectangle(5,10,'@');

System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中无参有返回值的方法getIntBetweenOneToHundred
md.getIntBetweenOneToHundred();//语法没问题,就是结果丢失

int num = md.getIntBetweenOneToHundred();
System.out.println("num = " + num);

System.out.println(md.getIntBetweenOneToHundred());
//上面的代码调用了getIntBetweenOneToHundred三次,这个方法执行了三次

System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中有参有返回值的方法max
md.max(3,6);//语法没问题,就是结果丢失

int bigger = md.max(5,6);
System.out.println("bigger = " + bigger);

System.out.println("8,3中较大者是:" + md.max(8,9));
}
}

回忆之前的代码:

1
2
3
4
5
6
7
8
//1、创建Scanner的对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入

//2、提示输入xx
System.out.print("请输入一个整数:"); //对象.非静态方法(实参列表)

//3、接收输入内容
int num = input.nextInt(); //对象.非静态方法()

形参和实参

  • 形参(formal parameter):在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
  • 实参(actual parameter):调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
  • 调用时,实参的个数、类型、顺序顺序要与形参列表一一对应。如果方法没有形参,就不需要也不能传实参。
  • 无论是否有参数,声明方法和调用方法是==()都不能丢失==

返回值问题

方法调用表达式是一个特殊的表达式:

  • 如果被调用方法的返回值类型是void,调用时不需要也不能接收和处理(打印或参与计算)返回值结果,即方法调用表达式==只能==直接加;成为一个独立语句。
  • 如果被调用方法有返回值,即返回值类型不是void,
    • 方法调用表达式的结果可以作为赋值表达式的值,
    • 方法调用表达式的结果可以作为计算表达式的一个操作数,
    • 方法调用表达式的结果可以作为另一次方法调用的实参,
    • 方法调用表达式的结果可以不接收和处理,方法调用表达式直接加;成为一个独立的语句,这种情况,返回值丢失。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MethodReturnValue {
public static void main(String[] args) {
//创建对象
MethodDefineDemo md = new MethodDefineDemo();

//无返回值的都只能单独加;成一个独立语句
//调用MethodDefineDemo类中无参无返回值的方法sayHello
md.sayHello();
//调用MethodDefineDemo类中有参无返回值的方法printRectangle
md.printRectangle(5,10,'@');

//有返回值的
//(1)方法调用表达式可以作为赋值表达式的值
int bigger = md.max(7,3);
System.out.println("bigger = " + bigger);

//(2)方法调用表达式可以作为计算表达式的一个操作数
//随机产生两个[1,100]之间的整数,并求和
int sum = md.getIntBetweenOneToHundred() + md.getIntBetweenOneToHundred();
System.out.println("sum = " + sum);

//(3)方法调用表达式可以作为另一次方法调用的实参
int x = 4;
int y = 5;
int z = 2;
int biggest = md.max(md.max(x,y),z);
System.out.println("biggest = " + biggest);

//(4)方法调用表达式直接加;成为一个独立的语句,这种情况,返回值丢失
md.getIntBetweenOneToHundred();
}
}

4.5 实例方法使用当前对象的成员

在实例方法中还可以使用当前对象的其他成员。在Java中当前对象用this表示。

  • this:在实例方法中,表示调用该方法的对象

  • 如果没有歧义,完全可以省略this。

使用this.

案例:矩形类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Rectangle {
int length;
int width;

int area() {
return this.length * this.width;
}

int perimeter(){
return 2 * (this.length + this.width);
}

void print(char sign) {
for (int i = 1; i <= this.width; i++) {
for (int j = 1; j <= this.length; j++) {
System.out.print(sign);
}
System.out.println();
}
}

String getInfo(){
return "长:" + this.length + ",宽:" + this.width +",面积:" + this.area() +",周长:" + this.perimeter();
}
}

可省略 this 的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Rectangle {
int length;
int width;

int area() {
return length * width;
}

int perimeter(){
return 2 * (length + width);
}

void print(char sign) {
for (int i = 1; i <= width; i++) {
for (int j = 1; j <= length; j++) {
System.out.print(sign);
}
System.out.println();
}
}

String getInfo(){
return "长:" + length + ",宽:" + width +",面积:" + area() +",周长:" + perimeter();
}
}

4.6 实例变量与局部变量的区别

1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中

2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈

3、生命周期

  • 实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,而且每一个对象的实例变量是独立的。
  • 局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,而且每一次方法调用都是独立。

4、作用域

  • 实例变量:通过对象就可以使用,本类中“this.,没有歧义还可以省略this.”,其他类中“对象.”
  • 局部变量:出了作用域就不能使用

5、修饰符(后面来讲)

  • 实例变量:public,protected,private,final,volatile,transient等
  • 局部变量:final

6、默认值

  • 实例变量:有默认值
  • 局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。

5、参数问题

5.1 特殊参数之一:可变参数

JDK1.5之后,当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变参数。可变参数的格式:

1
【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){  }

可变参数的特点和要求:

(1)一个方法最多只能有一个可变参数

(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个

(3)在声明它的方法中,可变参数当成数组使用

(4)其实这个书写“≈”

1
【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型[] 形参名){  }

只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。

方法只有可变参数

案例:求n个整数的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class NumberTools {
int total(int[] nums){
int he = 0;
for (int i = 0; i < nums.length; i++) {
he += nums[i];
}
return he;
}

int sum(int... nums){
int he = 0;
for (int i = 0; i < nums.length; i++) {
he += nums[i];
}
return he;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class TestVarParam {
public static void main(String[] args) {
NumberTools tools = new NumberTools();

System.out.println(tools.sum());//0个实参
System.out.println(tools.sum(5));//1个实参
System.out.println(tools.sum(5,6,2,4));//4个实参
System.out.println(tools.sum(new int[]{5,6,2,4}));//传入数组实参

System.out.println("------------------------------------");
System.out.println(tools.total(new int[]{}));//0个元素的数组
System.out.println(tools.total(new int[]{5}));//1个元素的数组
System.out.println(tools.total(new int[]{5,6,2,4}));//传入数组实参
}
}

方法包含非可变参数和可变参数

  • 非可变参数部分必须传入对应类型和个数的实参;
  • 可变参数部分按照可变参数的规则传入0~n个对应类型的实参或传入1个对应类型的数组实参;

案例:

n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串””

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class StringTools {
String concat(char seperator, String... args){
String str = "";
for (int i = 0; i < args.length; i++) {
if(i==0){
str += args[i];
}else{
str += seperator + args[i];
}
}
return str;
}
}
1
2
3
4
5
6
7
8
9
10
11

public class StringToolsTest {
public static void main(String[] args) {
StringTools tools = new StringTools();

System.out.println(tools.concat('-'));
System.out.println(tools.concat('-',"hello"));
System.out.println(tools.concat('-',"hello","world"));
System.out.println(tools.concat('-',"hello","world","java"));
}
}

5.2 方法的参数传递机制

方法的参数传递机制:实参给形参赋值,那么反过来形参会影响实参吗?

  • 方法的形参是基本数据类型时,形参值的改变不会影响实参;
  • 方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
    • 注意:String、Integer等特殊类型容易错

形参是基本数据类型

案例:编写方法,交换两个整型变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class PrimitiveTypeParam {
void swap(int a, int b){//交换两个形参的值
int temp = a;
a = b;
b = temp;
}

public static void main(String[] args) {
PrimitiveTypeParam tools = new PrimitiveTypeParam();
int x = 1;
int y = 2;
System.out.println("交换之前:x = " + x +",y = " + y);//1,2
tools.swap(x,y);//实参x,y是基本数据类型,给形参的是数据的“副本”,调用完之后,x与y的值不变
System.out.println("交换之后:x = " + x +",y = " + y);//1,2
}
}

形参是引用数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class ReferenceTypeParam {
void swap(MyData my){//形参my是引用数据类型,接收的是对象的地址值,形参my和实参data指向同一个对象
//里面交换了对象的两个实例变量的值
int temp = my.x;
my.x = my.y;
my.y = temp;
}

public static void main(String[] args) {
ReferenceTypeParam tools = new ReferenceTypeParam();
MyData data = new MyData();
data.x = 1;
data.y = 2;
System.out.println("交换之前:x = " + data.x +",y = " + data.y);//1,2
tools.swap(data);//实参是data,给形参my的是对象的地址值,调用完之后,x与y的值交换
System.out.println("交换之后:x = " + data.x +",y = " + data.y);//2,1
}

}
1
2
3
4
public class MyData{
int x;
int y;
}

3、形参是数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

public class ArrayTypeParam {
void sort(int[] arr){//给数组排序,修改了数组元素的顺序,这里对arr数组进行排序,就相当于对nums数组进行排序
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length - i; j++) {
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}

void iterate(int[] arr){//输出数组的元素,元素之间使用空格分隔,元素打印完之后换行
//这个方法没有修改元素的值
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}

public static void main(String[] args) {
ArrayTypeParam tools = new ArrayTypeParam();

int[] nums = {4,3,1,6,7};
System.out.println("排序之前:");
tools.iterate(nums);//实参nums把数组的首地址给形参arr,这个调用相当于输出nums数组的元素
//对数组的元素值没有影响

tools.sort(nums);//对nums数组进行排序

System.out.println("排序之后:");
tools.iterate(nums);//输出nums数组的元素
//上面的代码,从头到尾,堆中只有一个数组,没有产生新数组,无论是排序还是遍历输出都是同一个数组
}
}

4、形参指向新对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class AssignNewObjectToFormalParam {
void swap(MyData my){
my = new MyData(); //这里让my形参指向了新对象,此时堆中有两个MyData对象,和main中的data对象无关
int temp = my.x;
my.x = my.y;
my.y = temp;

}

public static void main(String[] args) {
//创建这个对象的目的是为了调用swap方法
AssignNewObjectToFormalParam tools = new AssignNewObjectToFormalParam();

MyData data = new MyData();
data.x = 1;
data.y = 2;
System.out.println("交换之前:x = " + data.x +",y = " + data.y);//1,2
tools.swap(data);//调用完之后,x与y的值交换?
System.out.println("交换之后:x = " + data.x +",y = " + data.y);//1,2
}
}

6、方法的重载

  • 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
  • 参数列表:数据类型个数不同,数据类型不同(按理来说数据类型顺序不同也可以,但是很少见,也不推荐,逻辑上容易有歧义)。
  • 重载方法调用:JVM通过方法的参数列表,调用匹配的方法。
    • 先找个数、类型最匹配的
    • 再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错

案例,用重载实现:

(1)定义方法求两个整数的最大值

(2)定义方法求三个整数的最大值

(3)定义方法求两个小数的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MathTools {
//求两个整数的最大值
public int max(int a,int b){
return a>b?a:b;
}

//求两个小数的最大值
public double max(double a, double b){
return a>b?a:b;
}

//求三个整数的最大值
public int max(int a, int b, int c){
return max(max(a,b),c);
}
}

7、对象数组

数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。

即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。

注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。

7.1 对象数组的声明和使用

案例:

(1)定义矩形类,包含长、宽属性,area()求面积方法,perimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法

(2)在测试类中创建长度为5的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class Rectangle {
double length;
double width;

double area(){//面积
return length * width;
}

double perimeter(){//周长
return 2 * (length + width);
}

String getInfo(){
return "长:" + length +
",宽:" + width +
",面积:" + area() +
",周长:" + perimeter();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public class ObjectArrayTest {
public static void main(String[] args) {
//声明并创建一个长度为3的矩形对象数组
Rectangle[] array = new Rectangle[3];

//创建3个矩形对象,并为对象的实例变量赋值,
//3个矩形对象的长分别是10,20,30
//3个矩形对象的宽分别是5,15,25
//调用矩形对象的getInfo()返回对象信息后输出
for (int i = 0; i < array.length; i++) {
//创建矩形对象
array[i] = new Rectangle();

//为矩形对象的成员变量赋值
array[i].length = (i+1) * 10;
array[i].width = (2*i+1) * 5;

//获取并输出对象对象的信息
System.out.println(array[i].getInfo());
}
}
}

8、构造器

我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。

可以,Java给我们提供了构造器(Constructor)。

1、构造器的作用

new对象,并在new对象的时候为实例变量赋值。

2、构造器的语法格式

构造器又称为构造方法,那是因为它长的很像方法。但是和方法还是有所区别的。

1
2
3
4
5
6
7
8
【修饰符】 class 类名{
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

public class Student {
private String name;
private int age;

// 无参构造
public Student() {}

// 有参构造
public Student(String name,int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}

注意事项:

  1. 构造器名必须与它所在的类名必须相同。
  2. 它没有返回值,所以不需要返回值类型,甚至不需要void
  3. 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
  4. 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
  5. 构造器是可以重载的,既可以定义参数,也可以不定义参数。
  6. 构造器的修饰符只能是权限修饰符,不能被其他任何修饰
1
2
3
4
5
6
7
8
9
10
11
12
13

public class TestStudent {
public static void main(String[] args) {
//调用无参构造创建学生对象
Student s1 = new Student();

//调用有参构造创建学生对象
Student s2 = new Student("张三",23);

System.out.println(s1.getInfo());
System.out.println(s2.getInfo());
}
}

3、同一个类中的构造器互相调用

  • this():调用本类的无参构造
  • this(实参列表):调用本类的有参构造
  • this()和this(实参列表)只能出现在构造器首行
  • 不能出现递归调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

public class Student {
private String name;
private int age;

// 无参构造
public Student() {
// this("",18);//调用本类有参构造
}

// 有参构造
public Student(String name,int age) {
this();//调用本类无参构造
this.name = name;
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}

9、静态

9.1 静态关键字(static)

在类中声明的实例变量,其值是每一个对象独立的。但是有些成员变量的值不需要或不能每一个对象单独存储一份,即有些成员变量和当前类的对象无关。

在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用和当前类的对象无关,那么创建对象就有点麻烦了。

此时,就需要将和当前类的对象无关的成员变量、成员方法声明为静态的(static)。

9.2 静态变量

1、语法格式

有static修饰的成员变量就是静态变量。

1
2
3
【修饰符】 class 类{
【其他修饰符】 static 数据类型 静态变量名;
}

2、静态变量的特点

  • 静态变量的默认值规则和实例变量一样。
  • 静态变量值是所有对象共享。
  • 静态变量的值存储在方法区。
  • 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
  • 如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。
  • 静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。
分类 数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
数据类型 默认值
引用类型 数组,类,接口 null

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

public class Employee {
private static int total;//这里私有化,在类的外面必须使用get/set方法的方式来访问静态变量
static String company; //这里缺省权限修饰符,是为了演示在类外面演示“类名.静态变量”的方式访问
private int id;
private String name;

{
//两个构造器的公共代码可以提前到非静态代码块
total++;
id = total; //这里使用total静态变量的值为id属性赋值
}

public Employee() {
}

public Employee(String name) {
this.name = name;
}

public void setId(int id) {
this.id = id;
}

public int getId() {
return id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public static int getTotal() {
return total;
}

public static void setTotal(int total) {
Employee.total = total;
}

@Override
public String toString() {
return "Employee{company = " + company + ",id = " + id + " ,name=" + name +"}";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public class TestStaticVariable {
public static void main(String[] args) {
//静态变量total的默认值是0
System.out.println("Employee.total = " + Employee.getTotal());

Employee c1 = new Employee("张三");
Employee c2 = new Employee();
System.out.println(c1);//静态变量company的默认值是null
System.out.println(c2);//静态变量company的默认值是null
System.out.println("Employee.total = " + Employee.getTotal());//静态变量total值是2

Employee.company = "尚硅谷";
System.out.println(c1);//静态变量company的值是尚硅谷
System.out.println(c2);//静态变量company的值是尚硅谷

//只要权限修饰符允许,虽然不推荐,但是也可以通过“对象.静态变量”的形式来访问
c1.company = "超级尚硅谷";

System.out.println(c1);//静态变量company的值是超级尚硅谷
System.out.println(c2);//静态变量company的值是超级尚硅谷
}
}

3、静态类变量和非静态实例变量、局部变量

  • 静态类变量(简称静态变量):存储在方法区,有默认值,所有对象共享,生命周期和类相同,还可以有权限修饰符、final等其他修饰符
  • 非静态实例变量(简称实例变量):存储在堆中,有默认值,每一个对象独立,生命周期每一个对象也独立,还可以有权限修饰符、final等其他修饰符
  • 局部变量:存储在栈中,没有默认值,每一次方法调用都是独立的,有作用域,只能有final修饰,没有其他修饰符

9.3 静态方法

1、语法格式

有static修饰的成员方法就是静态方法。

1
2
3
4
5
【修饰符】 class 类{
【其他修饰符】 static 返回值类型 方法名(形参列表){
方法体
}
}

2、静态方法的特点

  • 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
  • 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
  • 静态方法可以被子类继承,但不能被子类重写。
  • 静态方法的调用都只看编译时类型。
1
2
3
4
5
6
7
8
9
10

public class Father {
public static void method(){
System.out.println("Father.method");
}

public static void fun(){
System.out.println("Father.fun");
}
}
1
2
3
4
5
6
7

public class Son extends Father{
// @Override //尝试重写静态方法,加上@Override编译报错,去掉Override不报错,但是也不是重写
public static void fun(){
System.out.println("Son.fun");
}
}
1
2
3
4
5
6
7
8
9
10

public class TestStaticMethod {
public static void main(String[] args) {
Father.method();
Son.method();//继承静态方法

Father f = new Son();
f.method();//执行Father类中的method
}
}

9.4 静态和非静态的区别

1、本类中的访问限制区别

静态的类变量和静态的方法可以在本类的任意方法、代码块、构造器中直接访问。

非静态的实例变量和非静态的方法==只能==在本类的非静态的方法、非静态代码块、构造器中直接访问。

即:

  • 静态直接访问静态,可以
  • 非静态直接访问非静态,可以
  • 非静态直接访问静态,可以
  • 静态直接访问非静态,不可以

2、在其他类的访问方式区别

静态的类变量和静态的方法可以通过“类名.”的方式直接访问;也可以通过“对象.”的方式访问。(但是更推荐使用==”类名.”==的方式)

非静态的实例变量和非静态的方法==只能==通过“对象.”方式访问。

3、this和super的使用

静态的方法和静态的代码块中,==不允许==出现this和super关键字,如果有重名问题,使用“类名.”进行区别。

非静态的方法和非静态的代码块中,可以使用this和super关键字。

9.5 静态导入

如果大量使用另一个类的静态成员,可以使用静态导入,简化代码。

1
2
import static 包.类名.静态成员名;
import static 包.类名.*;

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import static java.lang.Math.*;

public class TestStaticImport {
public static void main(String[] args) {
//使用Math类的静态成员
System.out.println(Math.PI);
System.out.println(Math.sqrt(9));
System.out.println(Math.random());

System.out.println("----------------------------");
System.out.println(PI);
System.out.println(sqrt(9));
System.out.println(random());
}
}