前言
学习面向对象内容的三条主线:
1.Java类及类的成员:
①属性、方法、构造器;(重要)
②代码块、内部类;
2.面向对象的三大特征:
①封装
②继承性
③多态性
④如果是四大特征,则多加一个"抽象性"
3.其它关键字:
this、super、static、final、abstract、interface、package、import等
"大处着眼,小处着手"
一、面向过程与面向对象
何谓“面向对象”的编程思想?
首先解释一下“思想”。
先问你个问题:你想做个怎样的人?
可能你会回答:我想做个好人,孝敬父母,尊重长辈,关爱亲朋…
你看,这就是思想。这是你做人的思想,或者说,是你做人的原则。做人有做人的原则,编程也有编程的原则。这些编程的原则呢,就是编程思想。
面向过程(POP) 与面向对象(OOP)
- 二者都是一种思想,面向对象是相对面向过程而言的。
- 面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。
- 面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
- 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
- 面向对象:Object Oriented Programming
- 面向过程:Procedure Oriented Programming
/*
面向过程与面向对象的区别:
"人把大象装进冰箱"
1.面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
①把冰箱门打开
②抬起大象,塞进冰箱
③把冰箱门关上
2.面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
人
{
打开(冰箱)
{
冰箱.开开();
}
抬起(大象)
{
大象.进入(冰箱);
}
关闭(冰箱)
{
冰箱.闭合();
}
}
冰箱
{
开开()
{
}
闭合()
{
}
}
大象
{
进入(冰箱)
{
}
}
*/
面向对象的思想概述:
程序员从面向过程的执行者转化成了面向对象的指挥者
- 面向对象分析方法分析问题的思路和步骤:
1.根据问题需要,选择问题所针对的现实世界中的实体。
2.从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
3.把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
4.将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
1.1、练习
1.我要开车去丽江,这句话包含的类有什么?
答:我、车、丽江
2.体会以下几个经典案例涉及到的类。
人在黑板上画圈:人、黑板
列车司机紧急刹车:司机、车
售货员统计收获小票的金额:售货员、小票
你把门关上了:你、门
二、 类和对象
类和对象是Java语言的2个基本要素。
面向对象的思想概述:
- 类(Class)和对象(Object)是面相对象的核心概述
1.类:对一类事物的描述,是抽象的、概念上的定义
2.对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)。 - 可以理解为:类= 抽象概念的人;对象= 实实在在的某个人
- 面向对象程序设计的重点是类的设计;
- 设计类,其实就是设计类的成员。
2.1、Java 类及类的成员
- 现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的细胞构成的。同理,Java 代码世界是由诸多个不同功能的类构成的。
- 现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、… 那么,Java 中用类 class 来描述事物也是如此。常见的类的成员有:
属性:对应类中的成员变量
行为:对应类中的成员方法
Field = 属性 = 成员变量
Method = (成员)方法 = 函数
生活中描述事物无非就是描述事物的属性和行为。
如:人有身高、体重等属性,有说话、打球等行为。
2.2、类与对象的创建及使用
如何使用java类?
java类的实例化,即创建类的对象。
一、类和对象的使用(面向对象思想落地的实现):
1.创建类,设计类的成员
2.创建类的对象
3.通过“对象.属性”或“对象.方法”调用对象的结构
二、类的多个对象:
如果创建类一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非 static 的)
意味着:如果我们修改一个对象的属性 a,则不影响另外一个对象属性 a 的值。
// 测试类
public class PersonTest
{
public static void main(String[] args)
// main()方法:程序的入口
{
// "创建Person类的对象"
Person p1 = new Person();
// 类是引用类型变量
// 参考前面学习调用键盘时的Scanner scan = new Scanner(System.in);
// "创建后调用对象结构:属性、方法"
// 调用属性:"对象.属性"
p1.name = "Tom";// 调用后赋值
p1.isMale = true;
System.out.println(p1.name);// 调用输出
// 调用方法:"对象.方法"
p1.eat();// 调用后会实现方法内的代码作用
p1.sleep();
p1.talk("Chinese");// 方法设有参数时,调用需设置参数值
//******************************
Person p2 = new Person();
System.out.println(p2.name);
// 是否输出Tom?
// 输出引用数据类型的默认初始化值null
// 知识点:属性的默认初始化值与数据类型的一致
// 此时调用的是p2,p2是新创建Person类的对象
// 故调用p2.name时还未赋值
//******************************
Person p3 = p1;
// 将p1保存的对象地址值赋值给p3
// 此时p1和p3指向堆空间中的同一个对象实体。
// 相似与数组array1 = array2;赋值地址值
System.out.println(p3.name);
// 输出Tom
p3.age = 10;
// p3和p1调用的是同一个地址值
// 故修改p3后,亦相当于修改p1
System.out.println(p1.age);
// 输出10
}
}
/*
* 类的语法格式:
* 修饰符 class 类名{
* 属性声明;
* 方法声明;
* }
* 说明:修饰符 public:类可以被任意访问类的正文要用{ }括起来
*/
// 1.创建类,设计类的成员
class Person
// person人
{
// 属性:
String name;// 姓名
int age = 1;// 年龄
boolean isMale;// 是否是男性
// 方法:
public void eat()
// 吃eat
{
System.out.println("人可以吃饭");
}
public void sleep()
// sleep睡眠
{
System.out.println("人可以睡觉");
}
public void talk(String language)
// ()内是参数 这里是创建了一个字符串类型的变量参数
// talk说话、交谈 language语言
{
System.out.println("人可以说话,使用的是:" + language);
}
}
内存解析
- 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。
- 方法区(MethodArea),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
案例一:
Person p1 = new Person();
p1.name = "Tom";
p1.isMale = true;
Person p2 = new Person();
System.out.println(p2.name);// null
Person p3 = p1;
p3.age = 10;
案例二:
Person p1= newPerson();
p1.name = "胡利民";
p1.age = 23;
Person p2 = new Person();
p2.age = 10;
三、类的成员之一:属性
类中属性的使用:
属性(成员变量) vs 局部变量
-
相同点:
①定义变量的格式:数据类型 变量名 = 变量值;
②先声明,后使用
③变量都有其对应的作用域(超出作用域失效) -
不同点:
①在类中声明的位置的不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码快、构造器形参、构造器内部的变量
②关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符(修饰符见下方)
局部变量:不可以使用权限修饰符
③默认初始化值的情况
属性:类的属性,根据其类型,都有其默认初始化值
局部变量:没有默认初始化值
public class Attribute
// attribute属性
{
}
class User
// 用户user
{
// 直接定义在类的{}内的变量称作:
// 属性(或成员变量)
String name;
int age;
boolean isMale;
public void talk(String language)// talk说话
// ()内称为"形参",属于局部变量
{
System.out.println("我们使用" + language + "进行交流");
}
public void eat()// eat吃
{
String food = "烙饼";// food食物
// 定义在方法内的变量是"局部变量"
System.out.println("北方人喜欢吃" + food);
}
}
- 常用的权限修饰符:private、public、缺省、protected(详见封装性)
- 目前大家声明属性时,都使用缺省即可。
缺省:
String name;
int age;
boolean isMale;
// 属性前未有修饰符的就属于"缺省"(未写修饰符默认缺省)
private、public、protected:
private String name;
// 此时无法调用name
public int age;
protected boolean isMale;
方法内的变量及局部变量不可以使用修饰符:
形参使用修饰符报错
方法内变量使用修饰符报错
class User
{
// 直接定义在类的{}内的变量称作:
// 属性(或成员变量)
int age;
public void talk(String language)
// ()内称为"形参",属于局部变量
{
System.out.println("我们使用" + language + "进行交流");
}
public void eat()
{
//public String food = "烙饼";
// 局部变量使用修饰符"报错"
System.out.println("北方人喜欢吃" + food);
}
}
整型(byte、short、int、long)
浮点型(float、double):0.0
字符型(char):0(或‘\u0000’)
布尔型(boolean):false
引用数据类型(类、数组、接口):null
public class Attribute
{
public static void main(String[] args)
{
User u1 = new User();
System.out.println(u1.name);// 输出null
// String类型默认初始化值null
System.out.println(u1.age);// 输出0
// int类型默认初始化值0
System.out.println(u1.isMale);// 输出false
// boolean类型默认初始化值false
}
}
class User
{
String name;
public int age;
boolean isMale;
}
局部变量没有默认初始化值,未赋值时不能调用
形参并不在其内部直接调用,故我们在外部调用时赋值即可
public class Attribute
{
public static void main(String[] args)
{
User u1 = new User();
u1.talk("调用赋值");
}
}
class User
{
public void talk(String language)// 形参
{
System.out.println("我们使用" + language + "进行交流");
// 这里不属于调用,故未报错
}
}
在内存中加载的位置
属性:加载到堆空间(对象存放再堆空间,属性是在对象内部一起创建在堆空间)
局部变量:加载到栈空间(变量加载到栈空间)
练习题
练习1:
编写教师类和学生类,并通过测试类创建对象进行测试
Student类
属性:
name:String age:int major:String interests:String
方法:say() 返回学生的个人信息
Teacher类
属性:
name:String age:int teachAge:int course:String
方法:say() 输出教师的个人信息
public class Shool
{
public static void main(String[] args)
{
Student s1 = new Student();
s1.say("小明",16);
System.out.println();// 换行
Teacher t1 = new Teacher();
t1.say("宋老师",32);
}
}
class Student
{
String name;
int age;
String major;// major主修科目
String interests;// interests兴趣
public void say(String name,int age)// say说
{
System.out.println("这个学生是:" + name + "\n" + "年龄:" + age);
}
}
class Teacher
{
String name;
int age;
int teachAge;// 教学年龄
String course;// course课程
public void say(String name,int age)
{
System.out.println("这个老师是:" + name + "\n" + "年龄:" + age);
}
}
四、 类的成员之二:方法
4.1、类中方法的声明和使用
方法:描述类应该具有的功能
比如:
Math类:sqrt() 开方 或 random()…
Scanner类:nextXxx() …
Arrays类: sort( ) 排序 或 binarySearch() 查找 或 toString() 输出 或 equals()…
1.方法的分类
①按照是否有形参及返回值
②举例:
public void eat(){} 无返回值无形参
public void sleep(int hour){} 无返回值有形参
public String getName(){} 有返回值无形参
public String getNation(String nation){} 有返回值有形参
知识点:
在方法上使用void表示没有返回值
return有返回值,返回的类型在方法体中
(例public String getName(){}是返回String类型的值)
2.方法的声明
①格式:
权限修饰符 返回值类型 方法名(形参列表)
// 形参列表根据实际情况可以没有,也可以不止一个
{
方法体
// 通过方法名调用方法时,实际执行的是方法体
}
②注意点:static、final、abstract 来修饰的方法,后面再讲。
3.说明
1 关于权限修饰符:
目前方法主要使用的权限修饰符是public
Java规定的四种权限修饰符:private、public、缺省、protected
(具体到封装性再具体讲)
2 返回值类型:
有返回值 vs 没有返回值
①如果方法有返回值,则必须在方法声明时指定返回值的类型。
同时方法中需要使用return关键字来返回指定类型的变量或常量。
如果方法没有返回值,则方法声明时,使用void来表示。
通常没有返回值的方法中,就不需要使用return。
但是,如果需要使用的话,只能"return;"表示结束此方法。
②我们定义方法该不该有有返回值?
题目要求
凭经验:具体问题具体分析
3 方法名:
属于标识符,需要遵循标识符的规则和规范(小驼峰),并且"见名知意"
4 形参列表:
方法可以声明0个、1个或多个形参。
①格式:数据类型 形参1,形参2,形参3…
②我们定义方法是该不该定义形参?
题目要求
凭经验:具体问题具体分析
5 方法体:方法功能的体现
4.return关键字的使用:
1.使用范围:使用在方法体中
2.作用:
①结束方法(类似break;)
②针对于有返回值类型的方法,使用"return 数据"方式返回所要的数据
③注意点:return关键字后不可以声明执行语句
5.方法的使用
方法的使用中,可以调用当前类的属性或方法
特殊的:方法a调用了方法a,称作递归调用(但是需要避免死循环导致栈溢出)
方法中不可以定义方法
6.具体代码
public class Customer
// customer客户
{
public static void main(String[] args)
{
Methods met = new Methods();
// 创建方法的对象
met.eat();// 调用类中的方法
// eat()方法前使用的权限修饰符是public才能调用
/*测试形参是否需要设置的问题
int[] arr= new int[]{1,2,3,499,138,153,158,150,415};
met.sort(arr);
*/
met.getNation("中国");
}
}
class Methods
// methods方法
{
// 属性
String name;
int age;
boolean isMale;
// 方法
// 权限修饰符:把public修改为private时,则无法调用此方法
public void eat()// 不带形参
// void表示没有返回值
{
System.out.println("客户吃饭");
}
public void sleep(int hour)// 带形参
{
System.out.println("休息了" + hour + "个小时");
return;
// 在无返回值的方法中使用的时候,作用是结束方法
//System.out.println();
// return后不能声明表达式
}
public String getName()// 不带形参
// String表示返回值的类型是字符串类型
{
// return "Tom";// 返回常量
return name;// 返回变量值
// return用来返回值
// 在方法中可以调用属性
}
public String getNation(String nation)// 带形参// nation国籍
{
eat();// 例:方法内调用方法
String info = "我的国籍是:" + nation;
return info;
}
// "体会形参是否需要设置"
/*
public void sort(int[] arr)
// 我们需要操作哪个数据就把此数据传到形参中
{
}
public void sort()
// 未使用形参则固定了方法内要操作的值
{
int[] arr= new int[]{1,2,3,499,138,153,158,150,415};
// 而写到方法内部则固定了值
// 例如这里的排序,我们需要排不同数组的话就不能在方法内固定数组
}
*/
}
4.2、练习题
练习1
创建一个Person类,其定义如下:
Person类
public class Person
{
// 定义属性
String name;
int age;
/*
* sex:1表示为男性
* sex:0表示为女性
*/
int sex;
// 声明方法
public void study()
{
System.out.println("studying");
}
public void showAge()
{
System.out.println("age:" + age);
}
public int addAge(int i)
{
age += i;
return age;
}
}
测试类
public class PersonTest
{
public static void main(String[] args)
{
// 创建类的对象
Person p1 = new Person();
// 设置属性的值
p1.name = "Tom";
p1.age = 18;
p1.sex = 1;
p1.study();
p1.showAge();
int newAge = p1.addAge(2);
System.out.println(p1.name+ "的新年龄为:" + newAge);
System.out.println(p1.age);// 20
System.out.println();// 换行
// *********************************
Person p2 = new Person();
p2.showAge();// 输出0(整型的默认初始化值)
p2.addAge(10);
p2.showAge();// 10
}
}
练习2
- 要求:利用面向对象的编程方法,设计类Circle计算圆的面积。
// 测试类
public class CircleTest
{
public static void main(String[] args)
{
// 创建类的对象
Circle c1 = new Circle();
// 调用
c1.radius = 2.1;
System.out.println(c1.findArea1());// 方法1
c1.findArea2();// 方法2
}
}
// 功能类
class Circle
// Circle圆
{
// 属性
double radius;// radius半径
// 计算圆的面积的方法
// 方法1:有返回值
public double findArea1()// area面积
{
/*
圆的面积公式为:
S圆=πr²
*/
double area = 3.14 * radius * radius;
// π也可以使用 Math.PI
return area;
}
// 方法2:没有返回值
public void findArea2()
{
double area = 3.14 * radius * radius;
System.out.println("面积是:"+ area);
}
}
练习3
- 3.1 编写程序,声明一个method方法,在方法中打印一个108的型矩形,在main方法中调用该方法。
public class Exer3Test
{
public static void main(String[] args)
{
// 创建类的对象
Rectangular e1 = new Rectangular();
// 调用方法
e1.method();
}
}
class Rectangular
// rectangular矩形
{
public void method()
{
for(int i = 0;i < 10;i++)
// 外层循环控制行数
{
for(int j = 0;j < 8;j++)
{
System.out.print("* ");
}
System.out.println();// 换行
}
}
}
- 3.2修改上一个程序,在method方法中,除打印一个108的型矩形外,再计算该矩形的面积,并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。
public class Exer3Test
{
public static void main(String[] args)
{
// 创建类的对象
Rectangular e1 = new Rectangular();
// 调用方法
// 方式1:
int area = e1.method();
System.out.println("面积为:" + area);
// 方式2:
System.out.println(e1.method());
}
}
class Rectangular
// rectangular矩形
{
public int method()
{
for(int i = 0;i < 10;i++)
// 外层循环控制行数
{
for(int j = 0;j < 8;j++)
{
System.out.print("* ");
}
System.out.println();// 换行
}
// 矩形的面积=长*宽
return 10 * 8;// 题目已经给出长和宽,所以这里直接计算
}
}
- 3.3 修改上一个程序,在method方法提供m和n两个参数,方法中打印一个mn的型矩形,并计算该矩形的面积,将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。
public class Exer3Test
{
public static void main(String[] args)
{
// 创建类的对象
Rectangular e1 = new Rectangular();
// 调用方法
// 方式1:
int area = e1.method(6,4);
System.out.println("面积为:" + area);
// 方式2:
System.out.println(e1.method(6,4));
}
}
class Rectangular
// rectangular矩形
{
public int method(int m,int n)
{
for(int i = 0;i < 10;i++)
// 外层循环控制行数
{
for(int j = 0;j < 8;j++)
{
System.out.print("* ");
}
System.out.println();// 换行
}
// 矩形的面积=长*宽
return m * n;// 题目已经给出长和宽,所以这里直接计算
}
}
练习4
对象数组题目:定义类Student,包含三个属性:
学号number(int),年级state(int),成绩score(int)。
创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
(1) 生成随机数:Math.random(),返回值类型double;
(2) 四舍五入取整:Math.round(double d),返回值类型long。
public class Exer4Test
{
public static void main(String[] args)
{
// new一个对象,当需要new的数量较多时,一个一个new费时费力
// Student s1 = new Student();
// 使用数组来完成创建多个对象的操作:
// 声明Student类型的数组
Student[] stus = new Student[20];// 动态初始化
// 循环在数组中创建多个对象
for (int i = 0;i < stus.length;i++)
{
// 给数组元素赋值
stus[i] = new Student();
// 给Student对象的属性赋值
// 学号
stus[i].number = i + 1;
// 年级:[1,6]
stus[i].state = (int) (Math.random() * (6 - 1 + 1) + 1);
// 随机数公式:(int) (Math.random()*(b-a+1)+a);
// 成绩:[0,100]
stus[i].score = (int) (Math.random() * (100 - 0 + 1) + 0);
}
// 遍历学生数组
/*方式1
for (int i = 0;i < stus.length;i++)
{
//System.out.println(stus[i]);// 地址值
System.out.println(stus[i].number + "\t" + stus[i].state + "\t" + stus[i].score);
// 打印各个属性的值
}
*/
// 方式2
for (int i = 0;i < stus.length;i++)
{
System.out.println(stus[i].info());
// 直接调用方法进行遍历
}
// 问题一:打印出3年级(state值为3)的学生信息。
System.out.println("以下是3年纪学生的信息:");
for(int i = 0;i < stus.length;i++)
{
if(stus[i].state == 3)
{
System.out.println(stus[i].info());
// 同样可以直接调用方法输出打印
}
}
// 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
System.out.println("以下是按成绩排序后的信息:");
// 冒泡排序
for(int i = 0;i < stus.length - 1;i++)// -1是因为最后一个元素已经不需要排了
// 外层控制轮数
{
for(int j = 0;j < stus.length - 1 - i;j++)// -i可以保证不重复操作已操作过的元素
{
if(stus[j].score > stus[j + 1].score)
{
// 注意:这里需要交换的是数组的元素(student对象)并不是成绩属性
Student temp = stus[j];// 临时变量用于交换
stus[j] = stus[j + 1];
stus[j + 1] = temp;
}
}
}
// 遍历数组
for (int i = 0;i < stus.length;i++)
{
System.out.println(stus[i].info());
// 直接调用方法进行遍历
}
}
}
class Student
{
// 属性
int number;// 学号
int state;// 年级
int score;// 成绩
// 显示学生信息的方法
public String info()
{
return "学号:" + number + "\t年级:" + state + "\t成绩:" + score;
}
}
优化代码:将操作数组的功能封装到方法中
public class Exer4Test
{
public static void main(String[] args)
{
// 使用数组来完成创建多个对象的操作:
// 声明Student类型的数组
Student[] stus = new Student[20];// 动态初始化
// 循环在数组中创建多个对象
for (int i = 0;i < stus.length;i++)
{
// 给数组元素赋值
stus[i] = new Student();
// 给Student对象的属性赋值
// 学号
stus[i].number = i + 1;
// 年级:[1,6]
stus[i].state = (int) (Math.random() * (6 - 1 + 1) + 1);
// 随机数公式:(int) (Math.random()*(b-a+1)+a);
// 成绩:[0,100]
stus[i].score = (int) (Math.random() * (100 - 0 + 1) + 0);
}
// 创建类的对象
Exer4Test temp = new Exer4Test();
// 遍历学生数组
temp.print(stus);
System.out.println("换行");
// 问题一:打印出3年级(state值为3)的学生信息。
temp.searchState(stus,3);
System.out.println("换行");
// 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
temp.shot(stus);
// 遍历数组
temp.print(stus);
}
// 遍历数组的操作
public void print(Student[] stus)// 数组作为形参
{
for (int i = 0;i < stus.length;i++)
{
System.out.println(stus[i].info());
// 直接调用方法进行遍历
}
}
// 打印某一年纪的学生信息的方法
public void searchState(Student[] stus,int state)
{
for(int i = 0;i < stus.length;i++)
{
if(stus[i].state == state)
{
System.out.println(stus[i].info());
// 同样可以直接调用方法输出打印
}
}
}
public void shot(Student[] stus)
{
// 冒泡排序
for(int i = 0;i < stus.length - 1;i++)// -1是因为最后一个元素已经不需要排了
// 外层控制轮数
{
for(int j = 0;j < stus.length - 1 - i;j++)// -i可以保证不重复操作已操作过的元素
{
if(stus[j].score > stus[j + 1].score)
{
// 注意:这里需要交换的是数组的元素(student对象)并不是成绩属性
Student temp = stus[j];// 临时变量用于交换
stus[j] = stus[j + 1];
stus[j + 1] = temp;
}
}
}
}
}
class Student
{
// 属性
int number;// 学号
int state;// 年级
int score;// 成绩
// 显示学生信息的方法
public String info()
{
return "学号:" + number + "\t年级:" + state + "\t成绩:" + score;
}
}
练习5
扩展题(仅了解)
声明一个日期类型MyDate: 有属性:年year,月month, 日day。 创建2个日期对象,分别赋值为:你的出生日期,你对象的出生日期,并显示信息。
4.3 理解"万事万物皆对象"
1.在Java语言范畴中,我们都将功能、结构等封装到类中,通过的实例化,来调用具体的功能结构。
Scanner,String等
文件:File
网络资源:URL
2.涉及Java语言与前端Htm1、后端的数据库交互时,前后端的结构在Java层面交互时,都体现类、对象。
4.4 对象数组的内存解析
Student[] stus= newStudent[5];
stus[0] = new Student();
sysout(stus[0].state);//1
sysout(stus[1]);//null
sysout(stus[1].number);//异常
stus[1] = new Student();
sysout(stus[1].number);//0
class Student{
int number;//学号
int state = 1;//年级
int score;//成绩
}
内存解析的说明
1.引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
public class InstanceTest
{
public static void main(String[] args)
{
Phone p = new Phone();// 创建类的对象
System.out.println(p);// 输出地址值和类型
p = null;
System.out.println(p);// 输出null
}
}
class Phone
{
}
4.5 匿名对象的使用
匿名对象的使用
1.理解:我们创建的对象,没有显式的赋给一个变量名。即匿名对象
2.特征:匿名对象只能调用一次
3.使用:如下代码
public class InstanceTest
{
public static void main(String[] args)
{
Phone p = new Phone();// 创建类的对象
System.out.println(p);// 输出地址值和类型
/*
p = null;
System.out.println(p);// 输出null
*/
p.sendEmail();
p.playGame();
// 上面例子的使用对象名的调用
// "匿名对象"
//new Phone();// 仅new出对象,未赋值给变量,故没有名字
new Phone().sendEmail();
// 匿名对象创建后直接调用(但是匿名对象仅能使用一次)
new Phone().playGame();
// 每创建一个匿名对象就是新创建的(故内存消耗较大)
// 更直观的体现出匿名对象
new Phone().price = 1999;// 给此对象的属性赋值
new Phone().showPrice();// 0.0
// ps:一个匿名对象的变化并不影响另一个
PhoneMall mall = new PhoneMall();
// 匿名对象的使用:
mall.show(new Phone());// 调用方法时传了一个匿名对象
// 此情况可以多次使用匿名对象,但仍有限制,详见下方show()
}
}
class PhoneMall
{
public void show(Phone Phone)// 此形参的类型是Phone
{
Phone.sendEmail();
Phone.playGame();
// 匿名对象放入形参的情况 仅可以使用方法中已经写好的次数
}
}
class Phone
{
double price;// 价格
public void sendEmail()
{
System.out.println("发邮件");
}
public void playGame()
{
System.out.println("玩游戏");
}
public void showPrice()
{
System.out.println("手机价格:" + price);
}
}
4.6 自定义数组工具类的使用
工具类
public class ArrayUtil
// 数组工具类
{
// 求数组的最大值
public int getMax(int[] arr)
{
// 定义变量用于记录最大值
int maxValue = arr[0];
// 初始值为数组的第一个元素,以此来避免数组均是负数的情况出现求值错误
// 若记录变量的初始值是数组的第一个元素,则循环可以从第二个元素开始
for (int i = 1;i < arr.length;i++)
// 遍历数组,判断较大值赋给变量,得到最大值
{
if (arr[i] > maxValue)
{
maxValue = arr[i];
}
}
return maxValue;// 返回最大值
}
// 求数组的最小值
public int getMin(int[] arr)
{
// 定义变量用于记录最大值
int minValue = arr[0];
for (int i = 1;i < arr.length;i++)
// 遍历数组,判断较小值赋给变量,得到最小值
{
if (arr[i] < minValue)
{
minValue = arr[i];
}
}
return minValue;
}
// 求数组的总和
public int getSum(int[] arr)
{
// 定义变量用于记录总和
int sum = 0;
for (int i = 0;i < arr.length;i++)
{
sum += arr[i];
}
return sum;
}
// 求数组的平均值
public int getAvg(int[] arr)
{
// 在方法中调用求总和的方法
// 总和除以长度得到平均值
return getSum(arr) / arr.length;
}
// 反转数组
public void reverse(int[] arr)
{
for (int i = 0,j = arr.length - 1;i < arr.length / 2;i++,j--)
// j = arr.length - 1是数组的最后一个元素
// i < arr.length / 2循环数组长度的一半实现反转完成
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 复制数组
public int[] copy(int[] arr)
{
int[] arr2 = new int[arr.length];
// 以原数组的长度来确定复制后数组的长度
for (int i = 0;i < arr.length;i++)
{
arr2[i] = arr[i];
// 循环赋值数组每个元素,实现复制
}
return arr2;
}
// 数组排序
public void sort(int[] arr)
{
// 冒泡排序
for (int i = 0;i < arr.length - 1;i++)
// 外层循环控制轮数
{
for (int j = 0;j < arr.length - 1 - i;j++)
// j < arr.length - 1 - i确保不重复执行已完成排序的元素
{
// 从小到大排序
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 遍历数组
public void print(int[] arr)
{
for (int i = 0;i < arr.length;i++)
{
System.out.print(arr[i] + "\t");
}
System.out.println();//换行
}
// 查找指定元素
public int getIndex(int[] arr, int dest)
{
// 线性查找:
for (int i = 0;i < arr.length;i++)
{
if (dest == arr[i])
{
return i;
// 使用return后直接结束方法,则这里不需要break
}
}
return -1;// 返回负数表示没有找到
}
}
测试类
public class ArrayUtilTest
// 数组工具类的测试
{
public static void main(String[] args)
{
ArrayUtil util = new ArrayUtil();
// 创建数组工具类的对象
int[] arr = new int[]{32,34,32,5,3,54,654,-98,0,-53,5};
// 调用测试:
// 求最大值
int max = util.getMax(arr);
// 用int类型的变量来接收方法返回的值
System.out.println("最大值为:" + max);
// 求最小值
int min = util.getMin(arr);
System.out.println("最小值为:" + min);
// 求总和
int sum = util.getSum(arr);
System.out.println("总和为:" + sum);
// 求平均值
int avg = util.getAvg(arr);
System.out.println("平均值为:" + avg);
// 查找指定元素
System.out.print("查找:");
int index = util.getIndex(arr,5);
if(index >= 0)
{
System.out.println("找到了,索引为:" + index);
}
else
{
System.out.println("没找到目标");
}
System.out.println("遍历数组:");
// 遍历数组
util.print(arr);
// 数组排序
util.sort(arr);
System.out.println("排序后的遍历:");
// 遍历数组
util.print(arr);
System.out.println("反转数组:");
// 反转数组
util.reverse(arr);
// 遍历数组
util.print(arr);
System.out.println("复制数组:");
// 复制数组
int[] arr2 = util.copy(arr);
// 遍历数组
util.print(arr2);
}
}
4.7 方法的重载(overload)
ps:同名不同参(形参),类似中文的一词多义
-
定义:在同一个类中,允许存在个以上的同名方法,只要它们的参数个数或者参教类型不同即可。
“两同一不同”
两同:同一个类,相同方法名
一不同:参数列表不同(参数类型不同,参数个数不同) -
举例:Array类中重载的sort() / binarySearch()
-
判断是否是重载:跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系
-
在通过对象调用方法时,如何确定某个指定的方法:
方法名—>参数列表
public class OverLoadTest
// 重载测试
{
public static void main(String[] args)
{
// 重载方法的调用:
OverLoadTest test = new OverLoadTest();// 创建类的对象
test.getSum(1,2);
test.getSum(1,2);
}
// "两同一不同"的具体使用
public void getSum(int i,int j)
{
System.out.println("1");
// 为了方便区分,这里输出一个1
}
public void getSum(double d1,double d2)
{
System.out.println("2");
}
public void getSum(String s1,int i)
{
System.out.println("3");
}
public void getSum(int i,String s1)
{
System.out.println("4");
}
// 错误事例:
// 1.是否有返回值和返回值类型的不同不属于重载
// public int getSum(int i,int j)
// {
// return 0;
// }
// 2.形参内的变量名的不同不属于重载
// public void getSum(int m,int n)
// {
// System.out.println(m + n);
// }
// 3.修饰符的不同不属于重载
// private void getSum(int i,int j)
// {
// }
}
练习1
1.判断:
与void show(int a,char b,double c){}构成重载的有:
a) void show(int x,char y,double z){}// no(形参类型相同)
b) int show(int a,double c,char b){}// yes
c) void show(int a,double c,char b){}// yes
d) boolean show(int c,char b){}// yes
e) void show(double c){}// yes
f) double show(int x,char y,double z){}// no(形参类型相同)
g) void shows(){double c}// no(方法名不同)
ps:b和c双方不算重载
练习2
1.编写程序,定义三个重载方法并调用。方法名为mOL。
三个方法分别接收一个int参数、两个int参数、一个字符串参数。
分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。
在主类的main ()方法中分别用参数区别调用三个方法。
2.定义三个重载方法max(),
第一个方法求两个int值中的最大值,
第二个方法求两个double值中的最大值,
第三个方法求三个double值中的最大值,
并分别调用三个方法。
public class OverloadExer
{
public static void main(String[] args)
{
// 第一题:
OverloadExer o1 = new OverloadExer();
o1.mOL(1);
o1.mOL(1,2);
o1.mOL("三");
// 第二题:
int temp = o1.max(9,6);
System.out.println(temp);
double temp2 = o1.max(5.4,6.8);
System.out.println(temp2);
double temp3 = o1.max(4.15,3.07,5.37);
System.out.println(temp3);
}
// 第一题:
// 如下3个方法属于重载
public void mOL(int i)
{
System.out.println(i * i);
}
public void mOL(int i,int j)
{
System.out.println(i * j);
}
public void mOL(String s)
{
System.out.println(s);
}
// 第二题:
public int max(int i,int j)
{
return (i > j)? i : j;
}
public double max(double d1,double d2)
{
return (d1 > d2)? d1 : d2;
}
public double max(double d1,double d2,double d3)
{
double max = (d1 > d2)? d1 : d2;
return (max > d3)? max : d3;
}
}
4.7 可变个数的形参
1.jdk 5.0新增的内容
2.具体内容:
⑴格式:数据类型 … 变量名
⑵当调用可变个数形参的方法时,传入的参数个数可以是:0个、1个、多个
⑶可变个数形参的方法与本类中方法名相同,形参不同点方法之间构成重载
⑷可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存
⑸可变个数形参在方法的形参中,必须声明在末尾
⑹可变个数形参在方法中,最多只能声明一个可变形参
public class MethodArgsTest
{
public static void main(String[] args)
{
MethodArgsTest test = new MethodArgsTest();// 创建类的对象
test.show(12);// 调用的是1.
// test.show("hello");// 调用的是2.(有匹配优先级)
// 具有可变个数的形参的方法的调用:
// 此类形参可以在调用时定义个数
test.show("hell0");// 一个
test.show("hello","world");// 两个
test.show();// 零个
// 可变个数形参相比于数组形参较为简便
// 数组形参的调用:
test.show(new String[]{"aa","bb","cc"});
}
// 1.
public void show(int i)
{
}
// 2.(为了不影响3.故将2.先注释掉)
// public void show(String s)
// {
// System.out.println("string");
// }
// 3.属于可变个数的形参
public void show(String ... strs)
{
System.out.println("可变个数的形参");
for(int i = 0;i < strs.length;i++)
// 可变个数形参也相当于数组形参,故可以使用数组的一些相关关键字
{
System.out.println(strs[i]);
}
}
// 3和4不能共存
// 4.(数组形参与可变个数形参被编译器认为相同)
// public void show(String[] strs)
// {
//
// }
// 5.
// public void show(String ... strs,int i)
// 不能声明在其它形参之前
// 因为在调用时时候不能方便传入的数据分别是哪个形参的
public void show(int i,String ... strs)
{
}
}
4.8 方法参数的值传递机制(重点)
关于变量的赋值
①基本数据类型:赋值的是变量所保存的数据
②引用数据类型:赋值的是变量保存的数据的地址值
public class ValueTransferTest
{
public static void main(String[] args)
{
System.out.println("基本数据类型的情况:");
int m = 10;
int n = m;
System.out.println("m = " + m + ",n = " + n);
n = 20;
System.out.println("m = " + m + ",n = " + n);
// ps:m和n都属于基本类型的数据,所以不涉及到地址值之类的
// 故n改变了,m不变
System.out.println("引用数据类型的情况:");
Order o1 =new Order();// 创建类的对象
o1.orderId = 1001;
Order o2 = o1;// 赋值以后,o1与o2的地址值相同,都指向了堆空间中同一个对象实体
// 赋值的是o1的地址值赋给o2这个变量
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
o2.orderId = 1002;
// o2通过与o1相同的地址值调用堆空间中此地址值对应的对象实体
// 修改orderId此属性为1002
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
// 地址值相同,故o1.orderId与o2.orderId相同
}
}
class Order
{
int orderId;
}
针对基本数据类型
方法形参的传递机制:值传递
-
形参:方法定义时,声明的()内的参数
实参:方法调用时,实际传递给形参的数据 -
值传递机制:
①如果变量是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
public class ValueTransferTest1
{
public static void main(String[] args)
{
// 原值
int m = 10;
int n = 20;
System.out.println("m = " + m + ",n = " + n);
// 交换两个变量值的操作:
// 方式1
// int temp = m;// 定义临时变量用于中转
// m = n;
// n = temp;
// 方式2
ValueTransferTest1 test = new ValueTransferTest1();
/*方式2的情况1(输出语句在mian中时)
test.swap(m,n);
// 在方法内交换的是方法的形参的值,并不是main()中的m和n
// 方法运行后形参的值被自动回收
System.out.println("m = " + m + ",n = " + n);
// 10 20 此时未能实现交换
*/
}
// 方式2
public void swap(int m,int n)
{
int temp = m;// 定义临时变量用于中转
m = n;
n = temp;
}
}
针对引用数据类型
- 值传递机制:
②如果参数是引用数据类型,此时实参赋值给形参的是实参存储数据的地址值。
*/
public class ValueTransferTest2
{
public static void main(String[] args)
{
Date date = new Date();
date.m = 10;
date.n = 20;
System.out.println("m = " + date.m + ",n = " + date.n);
// 交换m和n的值
// int temp = date.m;
// date.m = date.n;
// date.n = temp;
ValueTransferTest2 test = new ValueTransferTest2();
test.swap(date);
// 把Date类的对象date内存放的地址值赋给形参
System.out.println("m = " + date.m + ",n = " + date.n);
// 故实现了交换
}
public void swap(Date date)
{
int temp = date.m;
date.m = date.n;
date.n = temp;
}
}
class Date
{
int m;
int n;
}
练习1
- 画出内存解析
public class TransferTest3{
public static void main(String args[]){
TransferTest3 test=new TransferTest3();
test.first();
}
public void first(){
int i=5;
Value v=new Value();
v.i=25;
second(v,i);
System.out.println(v.i);
}
public void second(Value v,int i){
i=0;
v.i=20;
Value val=new Value();
v=val;
System.out.println(v.i+" "+i);
}
}
class Value {
int i= 15;
}
- 输出结果:
- 15 0
- 20
练习2
仅作了解
- 方法一
// 直接在方法内输出
public static void method(int a,int b)
{
a = a * 10;
b = b * 20;
System.out.println("a=" + a);
System.out.println("b=" + b);
System.exit(0);// 退出程序
// 使的main()内的输出语句未能执行
}
- 方法二
// 重新设置打印流
public static void method(int a, int b)
{
PrintStream ps=new PrintStream(System.out)
@Override
{
public void println(String x)
{
if ("a=10".equals(x))
{
x = "a=100":
}
else if ("b=10".equals(x))
{
x = "b=200";
}
super.println(x);
}
}
System.serOut(ps);
}
练习3
/*
* 微软:
定义一个int型的数组:int[] arr = new int[]{12,3,3,34,56,77,432};
让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组。
*/
//错误写法
for(int i= 0;i < arr.length;i++){
arr[i] = arr[i] / arr[0];
}
// 正序运算时首位置元素的值除以自己后发生变化,导致其它元素运算后错误
//正确写法1
for(int i = arr.length –1;i >= 0;i--){
arr[i] = arr[i] / arr[0];
}
// i-- 倒序运算
//正确写法2
int temp = arr[0];
for(int i=运算 0;i < arr.length;i++){
arr[i] = arr[i] / temp;
}
// 在循环外定义临时变量记录数组首位置元素的值
练习4
/*
求输出的结果:
* int[] arr = new int[10];
* System.out.println(arr);// 地址值?
*
* char[] arr1 = new char[10];
* System.out.println(arr1);// 地址值?
*/
public class ArrayPrint
{
public static void main(String[] args)
{
// 1.
int[] arr = new int[]{1,2,33};
System.out.println(arr);// 地址值
// 使用println关键字传递了(arr)参数
// 传进去的是一个Object的对象
// 匹配了println(Object)方法,输出了地址值
// 2.
char[] arr1 = new char[]{'a','b','c'};
System.out.println(arr1);// abc
// 使用println关键字传递了(arr1)参数
// 传递的是char类型数组arr1
// 匹配了println(char[])方法,方法的方法体是遍历数组
}
}
练习5
将对象作为参数传递给方法
(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积。
(2)定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:
public void printAreas(Circle c,int time)
在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
例如,time为5,则输出半径1,2,3,4,5,以及对应的圆面积。
(3)在main方法中调用printAreas()方法,调用完毕后输出当前半径值。
程序运行结果示例图:
Circle类
public class Circle
{
double radius;// 半径
// 求圆面积
public double findArea()
{
// 圆面积 = π * r²
return Math.PI * radius * radius;
// Math.PI 常量 π
}
}
PassObject类
public class PassObject
{
public static void main(String[] args)
{
// 创建当前方法所在类的对象
PassObject test = new PassObject();
// 创建类的对象
Circle c = new Circle();
// 调用方法
test.printAreas(c, 5);
// test.printAreas(new Circle(),5);// 可以直接使用匿名对象
System.out.println("now radins is:" + c.radius);
}
public void printAreas(Circle c, int time)
{
System.out.println("Radius\t\tAreas");
for (int i = 1;i <= time;i++)
{
//设置圆的半径
c.radius = i;
// 输出半径和面积
System.out.println(c.radius + "\t\t" + c.findArea());
}
// 再次重新赋值
c.radius = time + 1;
}
}
4.9 递归(recursion)方法
递归方法的使用(了解)
1.递归方法:一个方法体内调用他自身(类似套娃)
2.方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
3.递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
练习1
public class RecursionTest
{
public static void main(String[] args)
{
// 练习1.计算1-n所有自然数的和
// 方式1.for循环
int sum = 0;// 记录总和
for (int i = 1;i <= 100;i++)
{
sum += i;
}
System.out.println("for循环总和:" + sum);
// 方法2.
RecursionTest test = new RecursionTest();
System.out.println("递归:" + test.getSum(100));
}
// 练习1.计算1-n所有自然数的和
// 方式2.递归方法
public int getSum(int n)
{
if (n == 1)
{
return 1;
}
else
{
return n + getSum(n - 1);
}
}
}
练习2
public class RecursionTest2
{
public static void main(String[] args)
{
// 例2.计算1-n所有自然数的乘积
// 方式1.for循环
int sum2 = 1;// 记录总和
for (int i = 2;i <= 10;i++)
{
sum2 *= i;
}
System.out.println("for循环乘积:" + sum2);
// 方法2.
RecursionTest2 test = new RecursionTest2();
System.out.println("递归:" + test.getSum2(10));
}
// 例2.计算1-n所有自然数的乘积:n!
// 方式2.递归方法
public int getSum2(int n)
// 这里的返回值是int类型,注意存储范围
{
if (n == 1)
{
return 1;
}
else
{
return n * getSum2(n - 1);
}
}
}
练习3
public class RecursionTest3
{
public static void main(String[] args)
{
// 练习3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
// 其中n是大于0的整数,求f(10)的值。
RecursionTest3 test = new RecursionTest3();
System.out.println(test.f(10));
}
// 练习3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
// 其中n是大于0的整数,求f(10)的值
public int f(int n)
{
if(n == 0)
{
return 1;
}
else if(n == 1)
{
return 4;
}
else
{
// return f(n + 2) - 2 * f(n + 1);
// 错误示例:数值无限变大没有终止的方向形成无限递归,报错栈溢出
// 递归方法应向下运算,最终完成终止
return 2 * f(n - 1) + f(n - 2);
// 例f(10) = 2* f(9) + f(8)
}
}
}
练习4
public class RecursionTest4
{
public static void main(String[] args)
{
/*练习4
输入一个数据n,计算斐波那契数列(Fibonacci)的第n个值
1 1 2 3 5 8 13 21 34 55
规律:一个数等于前两个数之和
要求:计算斐波那契数列(Fibonacci)的第n个值,并将整个数列打印出来
*/
RecursionTest4 test = new RecursionTest4();
System.out.println(test.f(10));
}
// 练习4
public int f(int n)
{
if (n == 1 || n == 2)
{
return 1;
}
else
{
return f(n - 1) + f(n - 2);
}
}
// 练习5:汉诺塔
// 练习6:快排
}
4. 10 String类型和字符串常量池
public class ValueTransferTest
{
public static void main(String[] args)
{
String s1 = "hello";// 特殊的String类型 可以不需要new就直接赋值字符串数据
// 栈中变量s1:地址值
// 指向字符串常量池中
// hello的地址值赋给s1
// 而hello在底层是用char[]数组存放的
// 数组的长度为字符的个数
// **字符串常量池中的常量是不可变的**
ValueTransferTest test = new ValueTransferTest();
test.change(s1);
// s1地址值赋给形参s
// 看下方方法内注释
System.out.println(s1);
// 输出hello
}
public void change(String s)
// 实参的地址值传个形参s
{
s = "hi~";
// s 重新赋值 hi~
// 但是字符串常量池中的常量是不可变的
// 故重新定义一个char[]数组存放hi~
// hi~ 的地址值赋给 s
}
}
五、面向对象特征之一:封装与隐
-
封装性的引入与体现
为什么需要封装?封装的作用和含义?
我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
我要开车,… -
我们程序设计追求“高内聚,低耦合”。
高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
低耦合:仅对外暴露少量的方法用于使用。 -
隐藏对象内部的复杂性,只对外公开简单的接口。
便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。