学习面向对象内容的三条主线
Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类
面向对象的特征:封装、继承、多态
其他关键字的使用:this、super、package、import、static、final、interface、abstract等
面向对象编程
面向对象的程序设计思想( Object Oriented Programming),简称 OOP
- 关注的焦点是 类 :在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽 象出来,用类来表示。
- 典型的语言:Java、C#、C++、Python、Ruby和PHP等
- 代码结构:以 类 为组织单位。每种事物都具备自己的 属性 和 行为/功能 。
- 是一种“ 设计者思维 ”,适合解决复杂问题。代码扩展性强、可维护性高。
Java语言的基本元素:类和对象
类和对象概述
类(Class)和对象(Object)是面向对象的核心概念。
1、什么是类
类:具有相同特征的抽象描述,是抽象的、概念上的定义
2、什么是对象
对象:实际存在的该类事物的每个个体,是具体的,因此特被称为实例(instance)。
类的成员概述
面向对象程序设计的重点是类的设计
类的设计,其实就是类的成员的设计
Java中用类class来描述事物也是如此。类,是一组相关属性和行为的集合,这也是类最基本的两 个成员。
面向对象完成功能的三步骤
步骤1:类的定义
类的定义使用关键字:class。格式如下:
[修饰符] class 类名
{
属性声明;
方法声明;
}
步骤2:对象的创建
- 创建对象,使用关键字:new
- 创建对象语法:
//方式1:给创建的对象命名
//把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();
//方式2:
new 类名() //也称为匿名对象
步骤3:对象调用属性或方法
- 对象是类的一个实例,必然具备该类事物的属性和行为(即方法)
- 使用" 对象名.属性 " 或 " 对象名.方法 "的方式访问对象成员(包括属性和方法)
匿名对象 (anonymous object)
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
如:new Person().shout();
使用情况 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
我们经常将匿名对象作为实参传递给一个方法调用。
对象的内存解析
JVM内存结构划分
HotSpot Java虚拟机的架构图如下。其中我们主要关心的是运行时数据区部分(Runtime Data Area)。
其中: 堆(Heap) :
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一 点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
栈(Stack) :是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各 种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类 型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
方法区(Method Area) :用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的 代码等数据。
对象名储存的是对象地址
直接打印对象名和数组名都是显示“类型@对象的hashCode值",所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
类的成员之一:成员变量(field)
如何声明成员变量
- 语法格式
[修饰符1] class 类名{
[修饰符2] 数据类型 成员变量名 [= 初始化值];
}
说明:
位置要求:必须在类中,方法外
修饰符
常用的权限修饰符有:private、缺省、protected、public
其他修饰符:static、final
数据类型
任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
成员变量名
属于标识符,符合命名规则和规范即可。
初始化值
根据情况,可以显式赋值;也可以不赋值,使用默认值
成员变量 vs 局部变量
1、变量的分类:成员变量与局部变量
- 变量的分类:成员变量与局部变量 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部等位置声明的变量称为局部变量。
成员变量 与 局部变量 的对比
相同点
- 变量声明的格式相同: 数据类型 变量名 = 初始化值
- 变量必须先声明、后初始化、再使用。
- 变量都有其对应的作用域。只在其作用域内是有效的
不同点
- 声明位置和方式 (1)实例变量:在类中方法外 (2)局部变量:在方法体{}中或方法的形参列表、代 码块中
- 在内存中存储的位置不同 (1)实例变量:堆 (2)局部变量:栈
- 生命周期 (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消 亡, 而且每一个对象的实例变量是独立的。 (2)局部变量:和方法调用的生命周期一样,每一次方法 被调用而在存在,随着方法执行的结束而消亡, 而且每一次方法调用都是独立。
- 作用域 (1)实例变量:通过对象就可以使用,本类中直接调用,其他类中“对象.实例变量” (2)局 部变量:出了作用域就不能使用
- 修饰符(1)实例变量:public,protected,private,final,volatile,transient等 (2)局部变 量:final
- 默认值 (1)实例变量:有默认值 (2)局部变量:没有,必须手动初始化。其中的形参比较特殊, 靠实参给它初始化。
对象属性的默认初始化赋值
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值
类的成员之二:方法(method)
方法(method、函数)的理解
- 方法 是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为 函数 或 过程 。
- 将功能封装为方法的目的是,可以 实现代码重用,减少冗余,简化代码
- Java里的方法 不能独立存在 ,所有的方法必须定义在类里。
举例:
- Math.random()的random()方法 Math.sqrt(x)的sqrt(x)方法
- System.out.println(x)的println(x)方法
- new Scanner(System.in).nextInt()的nextInt()方法
- Arrays类中的binarySearch()方法、sort()方法、equals()方法
如何声明方法
1、声明方法的语法格式
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{
方法体的功能代码
}
(1)一个完整的方法 = 方法头 + 方法体。
- 方法头就是 [修饰符] 返回值类型 方法名([形参列表])[throws 异常列表] ,也称为 方法签名 。通 常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
- 方法体就是方法被调用后要执行的代码。对于调用者来说,不了解方法体如何实现的,并不影响方 法的使用。
方法头可能包含五个部分:修饰符,返回值类型,方法名,形参列表,throws异常列表
方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码
关于方法体中return语句的说明:
- return语句的作用是结束方法的执行,并将方法的结果返回去
- 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的 类型与声明的返回值类型一致或兼容。
- 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执 行,那么return后面不能跟返回值,直接写return ; 就可以。
- return语句后面就不能再写其他代码了,否则会报错:Unreachable code
补充:方法的分类:按照是否有形参及返回值
如何调用实例方法
方法通过方法名被调用,且只有被调用才会执行。
1、方法调用语法格式
对象.方法名([实参列表])
调用实例方法的注意点
- 必须先声明后使用,且方法必须定义在类的内部
- 调用一次就执行一次,不调用不执行。
- 方法中可以调用类中的方法或属性,不可以在方法内部定义方法。
类{
方法1(){
}
方法2(){
}
}
关键字return的使用
return在方法中的作用:
作用1:结束一个方法
作用2:结束一个方法的同时,可以返回数据给方法的调用者
注意点:在return关键字的直接后面不能声明执行语句
方法调用内存分析
- 方法 没有被调用 的时候,都在 方法区 中的字节码文件(.class)中存储。
- 方法 被调用 的时候,需要进入到 栈内存 中运行。方法每调用一次就会在栈中有一个 入栈 动作,即 给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。
- 当方法执行结束后,会释放该内存,称为 出栈 ,如果方法有返回值,就会把结果返回调用处,如 果没有返回值,就直接结束,回到调用处继续执行下一条指令。
- 栈结构:先进后出,后进先出。
方法的重载 (overload)
概念及特点
方法重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
参数列表不同,意味着参数个数或参数类型的不同(两同一不同)
重载的特点:与修饰符、返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参 数类型)。调用时,根据方法参数列表的不同来区别。
重载方法调用:JVM通过方法的参数列表,调用匹配的方法。
先找个数、类型最匹配的
再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错
可变参数的形参
在JDK 5.0 中提供了Varargs(variable number of arguments)机制。即当定义一个方法时,形参的类型可以 确定,但是形参的个数不确定,那么可以考虑使用可变个数的形参。
格式:
方法名(参数的类型名 ...参数名)
特点:
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的,二者不能同时声明,否则报错。
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在一个方法的形参中,最多只能声明一个可变个数的形参
方法的参数传递机制
形参和实参
形参(formal parameter):在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。
实参(actual parameter):在调用方法时,方法名后面括号()中的使用的值/变量/表达式称为实际 参数,简称实参。
参数传递机制:
Java里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本(复制品)传入方法内,而参数 本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
递归(recursion)方法
递归方法调用:方法自己调用自己的现象就称为递归。
递归的分类:直接递归、间接递归。
直接递归:方法自身调用自己。
public void methodA(){
methodA();
}
间接递归:可以理解为A()方法调用B()方法,B()方法调用C()方法,C()方法调用A()方法。
public static void A(){
B();
}
public static void B(){
C();
}
public static void C(){
A();
}
说明:
- 递归方法包含了一种 隐式的循环 。
- 递归方法会 重复执行 某段代码,但这种重复执行无须循环控制。
- 递归一定要向 已知方向 递归,否则这种递归就变成了无穷递归,停不下来,类似于 死循环 。最终 发生 栈内存溢出
1. 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环 慢的 多 ,所以在使用递归时要慎重。
2. 在要求高性能的情况下尽量避免使用递归,递归调用既花时间又 耗内存 。考虑使用循环迭 代
对象数组
数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用类型中的类时,我们称为对象 数组。
注意点
对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建, 数组的元素的默认值就是 null ,所以很容易出现 空指针异常NullPointerException 。
关键字:package、import
package(包)
package,称为包,用于指明该文件中定义的类、接口等结构所在的包。
语法格式
package 顶层包名.子包名 ;
说明:
一个源文件只能有一个声明包的package语句
package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
包名,属于标识符,满足标识符命名的规则和规范(全部小写)、见名知意
包通常使用所在公司域名的倒置:com.atguigu.xxx。
大家取包名时不要使用" java.xx "包
包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每.一次就表示一层文件目录。
同一个包下可以声明多个结构(类、接口),但是不能定义同名的结构(类、接口)。不同的包下 可以定义同名的结构(类、接口)
包的作用
- 包可以包含类和子包,划分 项目层次 ,便于管理
- 帮助 管理大型软件 系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 解决 类命名冲突 的问题
- 控制 访问权限
import(导入)
为了使用定义在其它包中的Java类,需用import语句来显式引入指定包下所需要的类。相当于 import语 句告诉编译器到哪里去寻找这个类 。
语法格式
import 包名.类名;
注意事项
- import语句,声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 如果使用 a.* 导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.*的方式,一 次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。
- 如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。
- import static 组合的使用:调用指定类或接口下的静态的属性或方法
面向对象特征一:封装性(encapsulation)
为什么需要封装?
随着程序越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要 遵循“ 高内聚、低耦合 ”。
那什么又是“高内聚,低耦合”?
高内聚、低耦合是软件工程中的概念,也是UNIX 操作系统设计的经典原则。
内聚,指一个模块内各个元素彼此结合的紧密程度;耦合指一个软件结构内不同模块之间互连程度 的度量。内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅暴露少量的方法给外部使用,尽量方便外部调用。
何为封装性?
所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对 象开放,向没必要开放的类或者对象隐藏信息。
通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
Java如何实现数据封装
- 实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控 制。
- 权限修饰符: public 、 protected 、 缺省 、 private 。具体访问范围如下:
封装性的体现
成员变量/属性私有化
概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。
实现步骤:
① 使用 private 修饰成员变量
private 数据类型 变量名 ;
代码如下:
public class Person{
private String name;
private int age;
private boolean marry;
}
② 提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下:
public class Person{
private String name;
private int age;
private boolean marry;
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
public void set Age(int a){
age = a;
}
public int getAge(){
return age;
}
public void setMarry(boolean m){
marry = m;
}
public boolean isMarry(){
return marry;
}
}
③ 测试:
public class PersonTest{
public static void main(String[] args){
Person p = new Person();
//实例变量私有化,跨类是无法直接使用的
/*p.name = "张三";
p.age = 23;
p.marry = true;*/
p.setName("张三");
System.out.println("p.name = "+p.getName());
成员变量封装的好处:
让使用者只能通过事先预定的方法来 访问数据 ,从而可以在该方法里面加入控制逻辑,限制对成员 变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
便于修改 ,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问 方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实 现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
私有化方法
public class ArrayUtil{
public int max(int[] arr){
//求int型数组的最大值
int maxValue = arr[0];
for(int i = 1;i<arr.length;i++){
if(maxValue < arr[i]){
maxValue = arr[i];
}
}
return maxValue;
}
public int sum(int[] arr){
int sum = 0;
//求数组元素的总和
for(int i = 0;i<arr.length;i++){
sum += arr[i];
}
return sum;
}
public int avg(int[] arr){
//求int类型数组的元素平均值
int sumValue = sum(arr);
return sumValue / arr.length;
}
//创建一些重载上述方法
//public double max(double[] arr){}
//public float max(float[] arr){}
//public byte max(byte[] arr){}
public void print(int[] arr){
//遍历数组
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
public int[] copy(int[] arr){
//复制数组
int[] arr1 = new int[arr.length];
for(int i = 0;i < arr.length;i++){
arr1[i] = arr[i];
}
return arr1;
}
public void reverse(int[] arr){
//反转数组
for(int i = 0,j = arr.length - 1;i < j;i++,j--){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public void sort(int[] arr,String desc) {
if ("ascend".equals(desc)) {//if(desc.equals("ascend")){
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// int temp = arr[1];
// arr[j] = arr[j + 1];
// arr[j + 1] = temp;
swap(arr, j, j + 1);
}
}
}
}else{
System.out.println("您输入的排列方式有误!");
}
}
private void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public int getValue(int[] arr,int value){
//线性查找 指定的value值在arr数组中出现的位置
for(int i = 0;i < arr.length;i++){
if(value == arr[i]){
return i;
}
}
return -1;
}
}
注意: 开发中,一般成员实例变量都习惯使用private修饰,再提供相应的public权限的get/set方法访问。 对于final的实例变量,不提供set()方法。(后面final关键字的时候讲) 对于static final的成员变量,习惯上使用public修饰。
类的成员之三:构造器(Constructor)
我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢?
可以,Java给我们提供了 构造器(Constructor) ,也称为 构造方法 。
构造器的作用
new对象,并在new对象的时候为实例变量赋值
举例:Person p = new Person("Perter",15);
解释:如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序 代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们 要“洗澡”了。
构造器的语法格式
[修饰符] class 类名{
[修饰符] 构造器名(){
// 实例初始化代码
}
[修饰符] 构造器名(参数列表){
// 实例初始化代码
}
}
说明:
1.构造名必须与它所在的类名相同。
2.它没有返回值,所以不需要返回值类型,也不需要void。
3.构造器的修饰符只能是权限修饰符,不能被其他任何修饰。比如,不能被static、final、
synchronized、abstract、native修饰,不呢能有return语句返回值
代码如下:
public class Student{
private String name;
private int age;
//无参构造
public Student() {}
//有参构造
public Student(String n,int a){
name = n;
age = a;
}
public String getName(){
return name;
}
public void setName(String n){
name = n;
}
public int getAge() {
return age;
}
public void setAge(int a){
age = a;
}
public String getInfo(){
return"姓名"+name+",年龄"+age;
}
}
public class TestStudent{
public static void main(String[] args){
//调用无参构造创建学生对象
Student s = new Student();
//调用有参构造创建学生对象
Student s2 = new Student("张三",23);
System.out.println(s1.getInfo());
System.out.println(s2.getInfo());
}
}
使用说明
1. 当我们没有显式的声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器的修饰 符默认与类的修饰符相同
2. 当我们显式的定义类的构造器以后,系统就不再提供默认的无参的构造器了。
3. 在类中,至少会存在一个构造器。
4. 构造器是可以重载的。
阶段性知识补充
类中属性赋值过程
1、在类的属性中,可以有哪些位置给属性赋值?
① 默认初始化 ② 显式初始化 ③ 构造器中初始化 ④ 通过"对象.属性"或"对象.方法"的方式,给属性赋值
2、这些位置执行的先后顺序是怎样?
顺序:① - ② - ③ - ④
3、说明:
- 上述中的①、②、③在对象创建过程中,只执行一次。
- ④ 是在对象创建后执行的,可以根据需求多次执行。
JavaBean
JavaBean是一种Java语言写成的可重用组件。 好比你做了一个扳手,这个扳手会在很多地方被拿去用。这个扳手也提供多种功能(你可以拿 这个扳手扳、锤、撬等等),而这个扳手就是一个组件。 所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行 打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来 使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
《Think in Java》中提到,JavaBean最初是为Java GUI的可视化编程实现的。你拖动IDE构建工具创 建一个GUI 组件(如多选框),其实是工具给你创建Java类,并提供将类的属性暴露出来给你修改 调整,将事件监听器暴露出来。
示例
public class JavaBean {
private String name; // 属性一般定义为private
private int age;
public JavaBean() {
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
}
UML类图
- UML(Unified Modeling Language,统一建模语言),用来描述 软件模型 和 架构 的图形化语言。
- 常用的UML工具软件有 PowerDesinger 、 Rose 和 Enterprise Architect 。
- UML工具软件不仅可以绘制软件开发中所需的各种图表,还可以生成对应的源代码。
- 在软件开发中,使用 UML类图 可以更加直观地描述类内部结构(类的属性和操作)以及类之间的关 系(如关联、依赖、聚合等)。
+表示 public 类型, - 表示 private 类型,#表示protected类型
方法的写法: 方法的类型(+、-) 方法名(参数名: 参数类型):返回值类型
斜体表示抽象方法或类。