Java面向对象学习的三条主线(4-6章,此三章即面向对象编程)
- Java 类及类的成员:属性、方法、构造器;代码块、内部类
- 面向对象的三大特征:封装性、继承性、多态性、(抽象性)
- 其他关键字:this、super、static、final、abstract、interface、package、import等
1 面向过程与面向对象
- 面向对象(OOP)(Object Oriented Programming)与面向过程(POP)(Procedure Oriented Programming)
二者都是一种思想,面向对象都是相对于面向过程而言的。
面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
面向对象:将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等
面向过程并不比面向对象简单
- 程序员从面向过程的执行者转化成面向对象的指挥者
2 Java语言基本元素:类和对象
2.1 概念
- 类(Class)和对象(Object)是面向对象的核心概念
类是对一类事物的描述,是抽象的、概念上的定义 (抽象概念)
对象是实际存在的该类事物的每个个体,因而也称为实例(instance) (实实在在的东西),对象的功能完全取决于当初类的设计
面向对象的设计重点是类的设计;设计类其实是设计类的成员
- 万事万物皆对象
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
2.2 类和对象的使用(面向对象思想落地的实现)
-
创建类,设计类的成员
class Person{ //属性 String name; int age = 1; boolean isMale; //方法 public void eat(){ System.out.println("人可以吃饭"); }
-
创建类的对象 =
类的实例化 = 实例化类
//2. 创建Person类的对象 Person p1 = new Person();
-
通过“对象.属性” 或 “对象.方法” 调用对象的结构
//3. 调用对象的结构:属性、方法 //调用属性:“对象.属性” p1.name = "Tom"; p1.isMale = true; System.out.println(p1.name); //调用方法:“对象.方法” p1.eat(); p1.sleep(); p1.talk("Chinese");
整体完整代码如下: public class PersonTest { public static void main(String[] args){ //2. 创建Person类的对象 Person p1 = new Person(); //3. 调用对象的结构:属性、方法 //调用属性:“对象.属性” 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);//null System.out.println(p2.isMale);//false Person p3 = p1;//将p1变量保存到对象地址值赋值给p3,导致p1和p3指向 //了堆空间中的同一个对象实体 System.out.println(p3.name);//Tom p3.age = 10; System.out.println(p1.age);//10 } } //1. 创建类、设计类的成员 class Person{ //属性 String name; int age = 1; boolean isMale; //方法 public void eat(){ System.out.println("人可以吃饭"); } public void sleep(){ System.out.println("人可以睡觉"); } public void talk(String language){ System.out.println("人可以说话,使用的是:" + language); } }
2.3 一个类的多个对象
如果创建了一个类的多个对象,则每个对象都独立拥有一套类的属性(非static的)意味着,如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
Person p3 = p1;//将p1变量保存的对象地址值赋值给p3,导致p1和p3指向了堆空间中同一个对象实体
System.out.println(p3.name);//Tom
p3.age = 10;
System.out.println(p1.age);//10
2.4 对象的内存解析
画内存解析主要的注意点:
- 内存结构:栈(局部变量)、堆(new出来的结构:对象(非static的成员变量)、数组)
- 变量:
成员变量
局部变量(方法内、方法形参、构造器内、构造器形参、代码块内)
3 类中的属性和方法
3.1 类中属性的使用
属性(成员变量)vs 局部变量
1)相同点
1.1 定义变量的格式:数据类型 变量名 = 变量值
1.2 先声明,后使用
1.3 变量都有其对应的作用域
2)不同点
2.1 在类中声明的位置不同
属性:直接定义在类的一对{ }内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部变量
2.2 关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符。
常用的权限修饰符:private、public、缺省、protected(到封装性时再说)
目前使用缺省就行
局部变量:不可以使用权限修饰符
2.3 默认初始化值的情况:
属性:类的属性,根据其类型,都有其默认初始化值。
整型(byte、short、int、long):0
浮点型(float、double):0.0
字符型(char):0(或’\u0000’)
布尔型(boolean):false
引用数组类型(类、数组、接口):null
局部变量:没有默认初始化值
意味着,我们在调用局部变量之前,一定要显式赋值
特别地,形参在调用时赋值即可
2.4 内存中加载的位置:
属性:加载到堆空间(非static)
局部变量:加载到栈空间
3.2 类中方法的声明和使用
方法:描述应该具有的功能。
比如:Math类:sqrt() \ random() \ …
Scanner类:nextXxx() …
Arrays类:sqrt() \ binarySearch() \ toString() \ equals() \ …
1)举例
public void eat(){ }
public void sleep(int hour){ }
public String getName(){ }
public String getNation(){ }
2)方法的声明
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
注意:static、final、abstract来修饰的方法后面讲
3)说明
3.1 关于权限修饰符:默认当前权限修饰符先都使用public
Java规定的4种权限修饰符:private、public、缺省、protected(后面封装性再讲)
3.2 返回值类型:有返回值 vs 没有返回值
3.2.1 如果方法有返回值,则必须在方法声明时指定返回值类型,同时,方法中需要使用return关键字来返回指定类型的变量或常量,“return 数据;
”
3.2.2 如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return,但若使用,只能“return;”表示结束此方法的意思
(此时return后不写执行语句,因为写了也不会执行)
3.2.3 我们定义方法该不该有返回值
① 题目要求
② 凭经验
3.3 方法名:属于标识符,遵循标识符的规则和规范. “见名知意”
3.4 形参列表:方法可以声明0个、1个、或多个形参。
3.4.1 格式:数据类型1 形参1, 数据类型2 形参2,…
3.4.2 我们定义方法时,该不该定义形参?
① 题目要求
② 凭经验:具体问题具体分析
3.5 方法体:方法功能的体现
4)return关键字的使用
- 使用范围:使用在方法体中
- 作用: ① 结束方法
② 针对于有返回值类型的方法,使用“return 数据”方法返回所要的数据。 - 注意点:return关键字后面不可以声明执行语句。
5)方法使用注意点
特殊地,方法A中又调用了方法A:递归方法
方法中不可以定义方法(方法之间彼此都是独立的)
练习
public class StudentTest {
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);
stus[i].score = (int)(Math.random()*(100 - 0 + 1) + 0);
}
//遍历学生数组
for(int i = 0;i < stus.length;i++){
// System.out.println(stus[i]);//打印的是地址值
// System.out.println(stus[i].number + "," + stus[i].state + ","
// + stus[i].score);//打印出各个信息,用的是对象.属性
System.out.println(stus[i].info()); //还可以用对象.方法,因为student类中定义了此方法
}
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;i++){
for(int j = 0;j < stus.length - 1 - i;j++){
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 + ",年级:" + state
+ ",成绩:" + score;
}
}
4 JVM内存结构
编译完程序后,生成一个或多个字节码文件。我们使用 JVM 中的类的加载器对生成的字节码文件进行解释运行,意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。
虚拟机栈:即为平时提到的栈结构,我们将局部变量存储在栈结构中
堆:我们将 new 出来的结构(比如:数组、对象)加载在堆空间中。补充:对象的属性(非 static 的)加载在堆空间中
方法区:类的加载信息、常量值、静态域
一、理解“万事万物皆对象”
-
在 Java 语言范畴中,我们都将功能结构等封装到类中,通过类的实例化,来调用具体的功能结构
-
涉及到 Java 语言与前端的 Html、后端的数据库交互时,前后端的结构在 Java 层面交互时,都体现为类、对象
二、内存解析的说明
- 引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
三、匿名对象的使用
-
理解:我们创建的对象,没有显式地赋给一个变量名,即为匿名对象
-
特征:匿名对象只能调用一次(new 一次后以后就不能用了)
-
使用
public class InstanceTest {
public static void main(String[] args){
Phone p = new Phone();
System.out.println(p);
p.sendEmail();
p.playGame();
//匿名对象
// new Phone().sendEmail();
// new Phone().playGame();//new了两次,造了两个不同的对象
new Phone().price = 1999;
new Phone().showPrice();//0.0 注意两次new,不同
PhoneMall mall = new PhoneMall();
//匿名对象的使用
mall.show(new Phone());
}
}
class PhoneMall{
public void show(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);
}
}
5 再谈方法
5.1 方法的重载
- 定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或参数类型不同即可
”两同一不同“:同一个类、相同方法名
参数列表不同:参数个数不同,参数类型不同
-
举例: Arrays类中重载的sort() / binarySarch()
-
判断是否是重载:
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系,严格按照定义来即可
- 在通过对象调用方法时,如何确定某一个指定的方法:
方法名—> 参数列表(此时要看这两部分是否匹配)
public class OverLoadTest {
//如下4个方法构成了重载
public void getSum(int i,int j){
System.out.println(i + j);
}
public void getSum(double d1,double d2){
}
public void getSum(String s,int i){
}
public void getSum(int i,String s){
}
// public int getSum(int i,int j){
// return 0;
// }
// public void getSum(int m,int n){
//
// }
}
5.2 可变形参
JavaSE中提供了VarArgs 机制,允许直接定义能和多个实参相匹配的形参,从而,可以用一种更简单的方式来传递个数可变的实参。
使用
2.1 可变个数形参的格式:修饰符 返回值类型 方法名 (数据类型… 变量名){ }
2.2 当调用可变个数形参的方法时,传入的参数的个数可以是0个、1个、2个…
2.3 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
2.4 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载换句话说,二者不能共存
2.5 可变个数形参在方法的形参中,必须声明在末尾
2.6 可变个数形参在方法中,最多只能声明一个形参
5.3 方法参数的值传递机制
关于变量的赋值 ValueTransferTest.java
如果变量时基本数据类型:此时,赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
方法形参的传递机制,值传递
形参:方法定义时,声明的小括号内的参数
实参:方法调用时,实际传递给形参的数据
值传递机制:
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
如果参数是引用数据类型,此时实参赋给形参的是实参存储的数据的地址值。
/*
* 定义一个Circle类,包含一个double型的radius属性代表圆的半径
* 一个findArea()方法返回圆的面积
* */
public class Circle {
double radius;//半径
//求圆的面积
public double findArea(){
return Math.PI * radius * radius;
}
}
/*
* 定义一个PsaaObject,在类中定义一个方法printAreas()
* 该方法的定义如下:public void printAreas(Circle c,int time)
* 在printAreas方法中打印输出1到time之间的每个整体半径数,以及对应的面积
* 例如:time为5,则输出半径1,2,3,4,5,以及对应的圆面积
* */
public class PassObject {
public static void main(String[] args) {
PassObject test = new PassObject();
Circle c = new Circle();
test.printAreas(c, 5);
System.out.println("now radius is " + c.radius);
}
public void printAreas(Circle c,int time){
System.out.println("Radius\t\tArea");
for(int i = 1;i <= time;i++){
//设置圆的半径
c.radius = i;
System.out.println(c.radius + "\t\t" + c.findArea());
}
c.radius = time + 1;
}
}
5.4 递归方法(了解)
-
一个方法体内调用它自身
-
方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无需循环控制递归一定要向着已知的方向递归,否则这种递归就变成了无穷递归,类似于死循环
6 面向对象特征之一:封装与隐藏
高内聚低耦合:把该隐藏的隐藏起来,把该暴露的暴露出来。代码见Day10 com.atguigu.java AnimalTest.java
6.1 问题的引入
当我们创建一个类的对象以后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。这里,赋值操作要受属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是, 在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如set.legs),同时我们需要避免用户在使用“对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private)。
-->此时,针对于属性就体现了封装性
6.2 封装性的体现:(这只是封装性的一个体现而已)
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值。因此公共的get和set方法是为了设置和获取私有的属性。
拓展:封装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式
6.3 封装性的体现
需要权限修饰符来配合。
1) Java规定的4种权限(从小到大排列):private、缺省、protected、public。
2)4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
3)具体的:4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
4)修饰类的话:只能使用缺省、public
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
7 类的成员之三:构造器(构造方法)
类的结构之三:构造器(或构造方法、constructor)的使用
7.1 构造器的作用
-
创建对象
-
初始化对象的信息
7.2 说明
如果没有显式的构造器,则系统默认提供一个空参的构造器(这个构造器权限与类一致)
定义构造器的格式:权限修饰符 类名(形参列表){ }
一个类中定义的多个构造器,彼此构成重载。
一旦我们显式地定义了类的构造器之后,系统就不再提供默认的空参构造器,因此要用时需要自己再定义
一个类中,至少会有一个构造器(这句话至始至终都是对的)
public class TriAngle {
private double base;
private double height;
public TriAngle(){
}
public TriAngle(double b,double h){
base = b;
height = h;
}
public void setBase(double b){
base = b;
}
public void setHeight(double h){
height = h;
}
public double getBase(){
return base;
}
public double getHeight(){
return height;
}
}
public class TriAngleTest {
public static void main(String[] args) {
TriAngle t1 = new TriAngle();
t1.setBase(2.0);
t1.setHeight(2.4);
System.out.println("base: " + t1.getBase() + "height: " + t1.getHeight());
TriAngle t2 = new TriAngle(5.1,5.6);
System.out.println("base: " + t2.getBase() + "height: " + t2.getHeight());
}
}
总结:属性赋值的先后顺序
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.方法”或“对象.属性”的方式赋值
以上操作的先后顺序:① - ② - ③ - ④
7.3 拓展
JavaBean是一种Java语言写成的可重用的组件
所谓的JavaBean 是指符合如下标准的Java类:
>类是公共的
>有一个无参的公共的构造器
>有属性,且有对应的get、set方法
public class Customer {
private int id;
private String name;
public Customer(){
}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
}
8 this关键字的使用
8.1 this可以用来修饰:属性、方法、构造器
this 理解为:当前对象或当前正在创建的对象
8.2 this修饰属性和方法
1)在类的方法中,我们可以使用“this.属性”或“this.方法”的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略“this.”。特殊情况下,如果方法的形参和类的属性重名时,我们必须显式使用“this.变量”的方式,表明此变量是属性,而非形参。
2)在类的方法中,我们可以使用“this.属性”或“this.方法”的方式,调用当前正在创建的对象属性或方法。 但是,通常情况下,我们都选择省略“this.”。特殊情况下,如果构造器的形参和类的属性重名时,我们必须显式使用“this.变量”的方式,表明此变量是属性,而非形参。
8.3 this调用构造器
① 我们在类的构造器中,可以显式地使用“this(形参列表)”方式,调用本类中指定的其他构造器
② 构造器中不能通过“this(形参列表)”方式调用自己。
③ 如果一个类中有n个构造器,则最多有n - 1个构造器中使用了“this(形参列表)”
④ 规定:“this(形参列表)”必须声明在当前构造器首行。
⑤ 构造器内部最多只能声明一个“this(形参列表)”,用来调用其他的构造器(也可以看作是和上一项的规定不冲突,必须放在首行,若有多个,不能保证都在首行) 。
第四章的实验题目:exer3 包 exer4 包分别对应两个实验
9 关键字:package import的使用
9.1 package关键字的使用
-
为了更好地实现项目中类的管理,提供包的概念。
-
使用package 声明类或接口所属的包,声明在源文件的首行(先写它就行)。
-
包,属于标识符,遵循标识符的命名规则和规范(xxxyyyzzz)、“见名知意”。
-
每" . "一次,就代表一层文件目录。
补充:同一个包下,不能命名同名的接口、类。 不同的包下,可以命名同名的接口、类。
9.2 MVC设计模式
9.3 import关键字的使用
import:导入
-
在源文件中显式地使用import结构导入指定包下的类、接口。
-
声明在包的声明和类的声明之间。
-
如果需要导入多个结构,并列写出即可。
-
可以使用“xxx.*”的方式,表示可以导入xxx包下的所有结构。
-
如果使用的类或接口是java.lang包下定义的,则可以省略import结构。
-
如果使用的类或接口是本包下定义的,则可以省略import结构。
-
如果在源文件中,使用了不同包下的同名类,则必须至少有一个类需要以全类名的方式显示。
-
使用“xxx.*”方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显示导入。
-
import static:导入指定类或接口中的静态结构:属性或方法。