目录
1. 面向对象编程概述(了解)
1.1 程序设计的思路
1.2 由实际问题考虑如何设计程序
2. Java语言的基本元素:类和对象
2.1 类和对象概述
2.2 类的成员概述
2.3面向对象完成功能的三步骤(重要)
步骤1:类的定义
步骤2:对象的创建
2.4 匿名对象 (anonymous object)
3. 对象的内存解析
3.1 JVM内存结构划分
3.3 练习
4. 类的成员之一:成员变量(field)
4.1 如何声明成员变量
4.2 成员变量 vs 局部变量
5. 类的成员之二:方法(method)
5.1 方法的引入
5.2 方法(method、函数)的理解
5.3 如何声明方法
5.4 如何调用实例方法
5.5 使用的注意点
5.6 关键字return的使用
5.7 方法调用内存分析
6. 对象数组
7. 再谈方法
7.1 方法的重载(overload)
7.1.1 概念及特点
7.2 可变个数的形参
7.3 方法的参数传递机制
7.3.1 形参和实参
7.3.2 参数传递机制:值传递
7.3.3 举例
7.4 递归(recursion)方法
8. 关键字:package、import
8.1 package(包)
8.1.1 语法格式
8.1.4 JDK中主要的包介绍
8.2 import(导入)
8.2.1 语法格式
8.2.2 应用举例
8.2.3 注意事项
9. 面向对象特征一:封装性(encapsulation)
9.1 为什么需要封装?
9.2 何为封装性?
9.3 Java如何实现数据封装
9.4 封装性的体现
9.4.1 成员变量/属性私有化
9.4.2 私有化方法
9. 类的成员之三:构造器(Constructor)
9.1 构造器的作用
9.2 构造器的语法格式
9.3 使用说明
10. 阶段性知识补充
10.1 类中属性赋值过程
10.2 JavaBean
10.3 UML类图
- Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类
- 面向对象的特征:封装、继承、多态、(抽象)
- 其他关键字的使用:this、super、package、import、static、final、interface、abstract等
1. 面向对象编程概述(了解)
1.1 程序设计的思路
面向对象,是软件开发中的一类编程风格、开发范式。除了 面向对象 ,还有 面向过程 、 指令式编程 和 函数式编程 。在所有的编程范式中,我们接触最多的还是面向过程和面向对象两种。
类比:史书类型纪传体:以人物传记为中心, “ 本纪 ” 叙述帝王, “ 世家 ” 记叙王侯封国和特殊人物, “ 列传 ” 记叙民间人物。编年体:按年、月、日顺序编写。国别体:是一部分国记事的历史散文,分载多国历史。
- 关注的焦点是 过程 :过程就是操作数据的步骤。如果某个过程的实现代码重复出现,那么就可以把这个过程抽取为一个 函数 。这样就可以大大简化冗余代码,便于维护。
- 典型的语言:C语言
- 代码结构:以 函数 为组织单位。
- 是一种“ 执行者思维 ”,适合解决简单问题。扩展能力差、后期维护难度较大。
- 关注的焦点是 类 :在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。
- 典型的语言:Java、C#、C++、Python、Ruby和PHP等
- 代码结构:以 类 为组织单位。每种事物都具备自己的 属性 和 行为/功能 。
- 是一种“ 设计者思维 ”,适合解决复杂问题。代码扩展性强、可维护性高。
1.2 由实际问题考虑如何设计程序
1.打开冰箱
2.把大象装进冰箱
3.把冰箱门关住
人{
打开(冰箱){
冰箱.开门();
}
操作(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.关门();
}
}
冰箱{
开门(){ }
2. Java语言的基本元素:类和对象
2.1 类和对象概述
曰: “ 白马非马,可乎? ”曰: “ 可。 ”曰: “ 何哉? ”曰: “ 马者,所以命形也。白者,所以命色也。命色者,非命形也,故曰白马非马。”
2.2 类的成员概述
面向对象程序设计的重点是 类的设计类的设计,其实就是 类的成员的设计
- 现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的 细胞 构成的。同理,Java代码世界是由诸多个不同功能的 类 构成的。
- 现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、…
- 属性:该类事物的状态信息。对应类中的 成员变量
成员变量 <=> 属性 <=> Field
- 行为:该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的 成员方法
2.3面向对象完成功能的三步骤(重要)
步骤1:类的定义
[修饰符] class 类名{
属性声明;
方法声明;
}
举例1:
pulic class Person{//声明属性ageint age;// 声明方法eat()pulic void eat(){System.out.println("人吃饭");} }
package com.nanchu;/*** @Author 南初* @Create 2024/2/16 9:58* @Version 1.0*/
public class Dog {// 声明属性String type; // 种类String nickName; // 昵称String hostName; // 主人名称// 声明方法public void eat(){ // 吃东西System.out.println("");}
}
package com.nanchu;/*** @Author 南初* @Create 2024/2/15 12:03* @Version 1.0*/
public class Person {String name;char gender;Dog dog;// 喂宠物public void feed(){dog.eat();}
}
步骤2:对象的创建
创建对象,使用关键字:new
创建对象语法:
//方式1:给创建的对象命名
//把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();
//方式2:
new 类名()//也称为匿名对象
举例:
class PersonTest{public static void main(String[] args){//创建Person类的对象Person per = new Person();//创建Dog类的对象Dog dog = new Dog();}
}
- 对象是类的一个实例,必然具备该类事物的属性和行为(即方法)。
- 使用" 对象名.属性 " 或 " 对象名.方法 "的方式访问对象成员(包括属性和方法)
//声明Animal类
public class Animal { //动物类public int legs;public void eat() {System.out.println("Eating.");}public void move() {System.out.println("Move.");}
}
//声明测试类
public class AnimalTest {public static void main(String args[]) {//创建对象Animal xb = new Animal();xb.legs = 4;//访问属性System.out.println(xb.legs);xb.eat();//访问方法xb.move();//访问方法}
}
图示理解:
public class Game{public static void main(String[] args){Person p = new Person();//通过Person对象调用属性p.name = "张三";p.gender = '男';p.dog = new Dog(); //给Person对象的dog属性赋值//给Person对象的dog属性的type、nickname属性赋值p.dog.type = "柯基犬";p.dog.nickName = "小白";//通过Person对象调用方法p.feed();}
}
2.4 匿名对象 (anonymous object)
- 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
如:new Person().shout();
- 使用情况
3. 对象的内存解析
3.1 JVM内存结构划分
堆(Heap) :此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一 点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
栈(Stack) :是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各 种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类 型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
方法区(Method Area) :用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的 代码等数据。
class Person { //类:人String name;int age;boolean isMale;
}
public class PersonTest { //测试类public static void main(String[] args) {Person p1 = new Person();p1.name = "赵同学";p1.age = 20;p1.isMale = true;Person p2 = new Person();p2.age = 10;Person p3 = p1;p3.name = "郭同学";}
}
内存解析图:
说明:
- 堆:凡是new出来的结构(对象、数组)都放在堆空间中。
- 对象的属性存放在堆空间中。
- 创建一个类的多个对象(比如p1、p2),则每个对象都拥有当前类的一套"副本"(即属 性)。当通过一个对象修改其属性时,不会影响其它对象此属性的值。
- 当声明一个新的变量使用现有的对象进行赋值时(比如p3 = p1),此时并没有在堆空间中创 建新的对象。而是两个变量共同指向了堆空间中同一个对象。当通过一个对象修改属性时, 会影响另外一个对象对此属性的调用。
package com.nanchu;/*** @Author 南初* @Create 2024/2/16 11:12* @Version 1.0*/
public class StudentTest {public static void main(String[] args) {System.out.println(new Student()); // com.nanchu.Student@4eec7777Student stu = new Student();System.out.println(stu); // com.nanchu.Student@3b07d329int[] arr = new int[5];System.out.println(arr); // [I@41629346}
}
答:对象地址直接打印对象名和数组名都是显示“类型@对象的hashCode值",所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
3.3 练习
public class StudentTest{public static void main(String[] args){System.out.println(new Student());//Student@7852e922Student stu = new Student();System.out.println(stu);//Student@4e25154fint[] arr = new int[5];System.out.println(arr);//[I@70dea4e}
}
class Car {String color = "red";int num = 4;void show() {System.out.println("color=" + color + ",num=" + num);}
}
class CarTest {public static void main(String[] args) {Car c1 = new Car(); //建立对象c1Car c2 = new Car(); //建立对象c2c1.color = "blue"; //对对象的属性进行修改c1.show(); //使用对象的方法c2.show();
}
}
4. 类的成员之一:成员变量(field)
4.1 如何声明成员变量
[修饰符1] class 类名{[修饰符2] 数据类型 成员变量名 [= 初始化值];
}
public class Person{private int age; // 声明private变量 agepublic String name = "zhangsan"; // 声明public变量 name
}
4.2 成员变量 vs 局部变量
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部等位置声明的变量称为局部变量。
其中, static 可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。接下来先学习实例变量。
- 相同点
- 不同点
1、声明位置和方式 (1)实例变量:在类中方法外 (2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同 (1)实例变量:堆 (2)局部变量:栈
3、生命周期 (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消 亡, 而且每一个对象的实例变量是独立的。 (2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡, 而且每一次方法调用都是独立。
4、作用域 (1)实例变量:通过对象就可以使用,本类中直接调用,其他类中“对象.实例变量” (2)局部变量:出了作用域就不能使用
5、修饰符(后面来讲) (1)实例变量:public,protected,private,final,volatile,transient等 (2)局部变量:final
6、默认值 (1)实例变量:有默认值 (2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
成员变量类型 | 初始值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0或写为:'\u0000' |
boolean | false |
引用类型 | null |
class Person {//人类//1.属性String name;//姓名int age = 1;//年龄boolean isMale;//是否是男性public void show(String nation) {//nation:局部变量String color;//color:局部变量color = "yellow";}
}
//测试类
class PersonTest {public static void main(String[] args) {Person p = new Person();p.show("CHN");}
}
5. 类的成员之二:方法(method)
5.1 方法的引入
《街霸》游戏中,每次人物出拳、出脚或跳跃等动作都需要编写50-80行的代码,在每次出拳、出脚或跳 跃的地方都需要重复地编写这50-80行代码,这样程序会变得 很臃肿 ,可读性也非常差。为了解决代码 重复编写的问题,可以将出拳、出脚或跳跃的代码提取出来放在一个{}中,并为这段代码起个名字,这样 在每次的出拳、出脚或跳跃的地方通过这个名字来调用这个{}的代码就可以了。
上述过程中,所提取出来的代码可以被看作是程序中定义的一个方法,程序在需要出拳、出脚或跳跃时 调用该方法即可。
5.2 方法(method、函数)的理解
- 方法 是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为 函数 或 过程 。
- 将功能封装为方法的目的是,可以 实现代码重用,减少冗余,简化代码
- Java里的方法 不能独立存在 ,所有的方法必须定义在类里。
- 举例1:
Math.random()的random()方法
Math.sqrt(x)的sqrt(x)方法
System.out.println(x)的println(x)方法
new Scanner(System.in).nextInt()的nextInt()方法
Arrays类中的binarySearch()方法、sort()方法、equals()方法
- 举例2:
public class Person{private int age;public int getAge() { //声明方法getAge()return age;}public void setAge(int i) { //声明方法setAgeage = i; //将参数i的值赋给类的成员变量age}
}
5.3 如何声明方法
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{方法体的功能代码
}
(1)一个完整的方法 = 方法头 + 方法体。
- 方法头就是 [修饰符] 返回值类型 方法名([形参列表])[throws 异常列表] ,也称为 方法签名 。通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
- 方法体就是方法被调用后要执行的代码。对于调用者来说,不了解方法体如何实现的,并不影响方法的使用。
- 修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面会一一学习。
- 返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者。
- 方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
- 形参列表:表示完成方法体功能时需要外部提供的数据列表。可以包含零个,一个或多个参数。
无论是否有参数,()不能省略
如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用逗号分隔,例 如:
一个参数: (数据类型 参数名)
二个参数: (数据类型1 参数1, 数据类型2 参数2)
参数的类型可以是基本数据类型、引用数据类型
- throws 异常列表:可选,在【第09章-异常处理】章节再讲
- return语句的作用是结束方法的执行,并将方法的结果返回去
- 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
- 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执 行,那么return后面不能跟返回值,直接写return ; 就可以。
- return语句后面就不能再写其他代码了,否则会报错:Unreachable code
package com.atguigu.test04.method;
/*** 方法定义案例演示*/
public class MethodDefineDemo {/*** 无参无返回值方法的演示*/public void sayHello(){System.out.println("hello");}/*** 有参无返回值方法的演示* @param length int 第一个参数,表示矩形的长* @param width int 第二个参数,表示矩形的宽* @param sign char 第三个参数,表示填充矩形图形的符号*/public 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*/public int getIntBetweenOneToHundred(){return (int)(Math.random()*100+1);}/*** 有参有返回值方法的演示* @param a int 第一个参数,要比较大小的整数之一* @param b int 第二个参数,要比较大小的整数之二* @return int 比较大小的两个整数中较大者的值*/public int max(int a, int b){return a > b ? a : b;}
}
5.4 如何调用实例方法
对象.方法名([实参列表])
package com.atguigu.test04.method;
/*** 方法调用案例演示*/
public class MethodInvokeDemo {public static void main(String[] args) {
//创建对象MethodDefineDemo md = new MethodDefineDemo();System.out.println("-----------------------方法调用演示-------------------------");
//调用MethodDefineDemo类中无参无返回值的方法sayHellomd.sayHello();md.sayHello();md.sayHello();
//调用一次,执行一次,不调用不执行System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中有参无返回值的方法printRectanglemd.printRectangle(5,10,'@');System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中无参有返回值的方法getIntBetweenOneToHundredmd.getIntBetweenOneToHundred();//语法没问题,就是结果丢失int num = md.getIntBetweenOneToHundred();System.out.println("num = " + num);System.out.println(md.getIntBetweenOneToHundred());
//上面的代码调用了getIntBetweenOneToHundred三次,这个方法执行了三次System.out.println("------------------------------------------------");
//调用MethodDefineDemo类中有参有返回值的方法maxmd.max(3,6);//语法没问题,就是结果丢失int bigger = md.max(5,6);System.out.println("bigger = " + bigger);System.out.println("8,3中较大者是:" + md.max(8,9));}
}
举例2:
//1、创建Scanner的对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入
//2、提示输入xx
System.out.print("请输入一个整数:"); //对象.非静态方法(实参列表)
//3、接收输入内容
int num = input.nextInt(); //对象.非静态方法()
5.5 使用的注意点
类{方法1(){}方法2(){}
}
错误示例:
类{方法1(){方法2(){ //位置错误}}
}
5.6 关键字return的使用
- return在方法中的作用:
- 注意点:在return关键字的直接后面不能声明执行语句
5.7 方法调用内存分析
- 方法 没有被调用 的时候,都在 方法区 中的字节码文件(.class)中存储。
- 方法 被调用 的时候,需要进入到 栈内存 中运行。方法每调用一次就会在栈中有一个 入栈 动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。
- 当方法执行结束后,会释放该内存,称为 出栈 ,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
- 栈结构:先进后出,后进先出。
public class Person {public static void main(String[] args) {Person p1 = new Person();p1.eat();}public static void eat() {sleep();System.out.println("人:吃饭");}public static void sleep(){System.out.println("人:睡觉");doSport();}
}
6. 对象数组
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,
学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
1) 生成随机数:Math.random(),返回值类型double;
2) 四舍五入取整:Math.round(double d),返回值类型long。
/*
* 定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。
*/
public class Student {int number;//学号int state;//年级int score;//成绩public void info(){System.out.println("number : " + number+ ",state : " + state + ",score : " + score);}
}
public class StudentTest {public static void main(String[] args) {
// Student s1 = new Student();
// s1.number = 1;
// s1.state = (int)(Math.random() * 6 + 1);//[1,6]
// s1.score = (int)(Math.random() * 101);//[0,100]
//
// Student s2 = new Student();
// s2.number = 2;
// s2.state = (int)(Math.random() * 6 + 1);//[1,6]
// s2.score = (int)(Math.random() * 101);//[0,100]
//
// //....
// 对象数组
// String[] arr = new String[10];
// 数组的创建Student[] students = new Student[20];
// 通过循环结构给数组的属性赋值for (int i = 0; i < students.length; i++) {
// 数组元素的赋值students[i] = new Student();
// 数组元素是一个对象,给对象的各个属性赋值students[i].number = (i + 1);students[i].state = (int) (Math.random() * 6 + 1);// [1,6]students[i].score = (int) (Math.random() * 101);// [0,100]}
// 问题一:打印出3年级(state值为3)的学生信息。for (int i = 0; i < students.length; i++) {if (students[i].state == 3) {
// System.out.println(
// "number:" + students[i].number + ",state:" + students[i].state+ ",score:" + students[i].score);students[i].info();}}System.out.println("******************************");
// 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
// 排序前for (int i = 0; i < students.length; i++) {
// System.out.println(
// "number:" + students[i].number + ",state:" +
// students[i].state + ",score:" + students[i].score);students[i].info();}System.out.println();
// 排序:for (int i = 0; i < students.length - 1; i++) {for (int j = 0; j < students.length - 1 - i; j++) {if (students[j].score > students[j + 1].score) {Student temp = students[j];students[j] = students[j + 1];students[j + 1] = temp;}}}
// 排序后:for (int i = 0; i < students.length; i++) {
// System.out.println(
// "number:" + students[i].number + ",state:" +
// students[i].state + ",score:" + students[i].score);students[i].info();}}
}
对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是 null ,所以很容易出现 空指针异常NullPointerException 。
(1)定义矩形类Rectangle,包含长、宽属性,area()返回矩形面积的方法,perimeter()返回矩形周长的方法,String getInfo()返回圆对象的详细信息(如:长、宽、面积、周长等数据)的方法
(2)在测试类中创建长度为3的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出
package com.atguigu.test08.array;
public class Rectangle {double length;double width;public double area(){//面积return length * width;}public double perimeter(){//周长return 2 * (length + width);}public String getInfo(){return "长:" + length +",宽:" + width +",面积:" + area() +",周长:" + perimeter();}
}
package com.atguigu.test08.array;import java.awt.*;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());}
内存解析:
7. 再谈方法
7.1 方法的重载(overload)
7.1.1 概念及特点
- 方法重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
参数列表不同,意味着参数个数或参数类型的不同
- 重载的特点:与修饰符、返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
- 重载方法调用:JVM通过方法的参数列表,调用匹配的方法。
//System.out.println()方法就是典型的重载方法,其内部的声明形式如下:
public class PrintStream {public void println(byte x)public void println(short x)public void println(int x)public void println(long x)public void println(float x)public void println(double x)public void println(char x)public void println(double x)public void println()
}
public class HelloWorld{public static void main(String[] args) {System.out.println(3);System.out.println(1.2f);System.out.println("hello!");}
}
举例2:
//返回两个整数的和
public int add(int x,int y){return x+y;
}
//返回三个整数的和
public int add(int x,int y,int z){return x+y+z;
}
//返回两个小数的和
public double add(double x,double y){return x+y;
}
举例3:方法的重载和返回值类型无关
public class MathTools {//以下方法不是重载,会报错public int getOneToHundred(){return (int)(Math.random()*100);}public double getOneToHundred(){return Math.random()*100;}
}
7.2 可变个数的形参
在JDK 5.0 中提供了Varargs(variable number of arguments)机制。即当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变个数的形参。
格式:
方法名 ( 参数的类型名 ... 参数名 )
举例:
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String...books);
特点:
1. 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
2. 可变个数形参的方法与同名的方法之间,彼此构成重载
3. 可变参数方法的使用与方法参数部分使用数组是一致的,二者不能同时声明,否则报错。
4. 方法的参数部分有可变形参,需要放在形参声明的最后
5. 在一个方法的形参中,最多只能声明一个可变个数的形参
案例分析:
案例1:n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串""
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;}
}
package com.atguigu.test05.param;
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"));}
}
案例2:求n个整数的和
public class NumberTools {public int total(int[] nums){int sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}return sum;}public int sum(int... nums){int sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}return sum;}
}
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}));//传入数组实参}
}
案例3:如下的方法彼此构成重载
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);}//求n个整数的最大值public int max(int... nums){int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常for (int i = 1; i < nums.length; i++) {if(nums[i] > max){max = nums[i];}}return max;}
/* //求n整数的最大值
public int max(int[] nums){ //编译就报错,与(int... nums)无法区分
int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
for (int i = 1; i < nums.length; i++) {
if(nums[i] > max){
max = nums[i];
}
}
return max;
}*/
/* //求n整数的最大值
public int max(int first, int... nums){ //当前类不报错,但是调用时会引起多个方法同时匹配
int max = first;
for (int i = 0; i < nums.length; i++) {
if(nums[i] > max){
max = nums[i];
}
}
return max;
}*/
}
7.3 方法的参数传递机制
7.3.1 形参和实参
- 形参(formal parameter):在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。
- 实参(actual parameter):在调用方法时,方法名后面括号()中的使用的值/变量/表达式称为实际参数,简称实参。
7.3.2 参数传递机制:值传递
Java里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
7.3.3 举例
public class Test {public static void main(String[] args) {int m = 10;int n = 20;System.out.println("m = " + m + ", n = " + n);
//交换m和n的值
// int temp = m;
// m = n;
// n = temp;ValueTransferTest1 test = new ValueTransferTest1();test.swap(m, n);System.out.println("m = " + m + ", n = " + n);}public void swap(int m,int n){int temp = m;m = n;n = temp;}
}
内存解析:
public class Test {public static void main(String[] args) {Data d1 = new Data();d1.m = 10;d1.n = 20;System.out.println("m = " + d1.m + ", n = " + d1.n);
//实现 换序ValueTransferTest2 test = new ValueTransferTest2();test.swap(d1);System.out.println("m = " + d1.m + ", n = " + d1.n);}public void swap(Data data){int temp = data.m;data.m = data.n;data.n = temp;}
}
class Data{int m;int n;
}
内存解析:
7.4 递归(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:计算1 ~ n的和
public class RecursionDemo {public static void main(String[] args) {RecursionDemo demo = new RecursionDemo();
//计算1~num的和,使用递归完成int num = 5;
// 调用求和的方法int sum = demo.getSum(num);
// 输出结果System.out.println(sum);}/*通过递归算法实现.参数列表:int返回值类型: int*/public int getSum(int num) {
/*
num为1时,方法返回1,
相当于是方法的出口,num总有是1的情况
*/if(num == 1){return 1;}
/*
num不为1时,方法返回 num +(num-1)的累和
递归调用getSum方法
*/return num + getSum(num-1);}
}
代码执行图解:
举例2:递归方法计算n!
public int multiply(int num){if(num == 1){return 1;}else{return num * multiply(num - 1);}
}
最后说两句:1. 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环 慢的多 ,所以在使用递归时要慎重。2. 在要求高性能的情况下尽量避免使用递归,递归调用既花时间又 耗内存 。考虑使用循环 迭代
8. 关键字:package、import
8.1 package(包)
package,称为包,用于指明该文件中定义的类、接口等结构所在的包。
8.1.1 语法格式
package 顶层包名.子包名 ;
举例:pack1\pack2\PackageTest.java
package pack1.pack2; //指定类PackageTest属于包pack1.pack2public class PackageTest{public void display(){System.out.println("in method display()");}
}
- 一个源文件只能有一个声明包的package语句
- package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
- 包名,属于标识符,满足标识符命名的规则和规范(全部小写)、见名知意
- 包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每.一次就表示一层文件目录。
- 同一个包下可以声明多个结构(类、接口),但是不能定义同名的结构(类、接口)。不同的包下可以定义同名的结构(类、接口)
- 包可以包含类和子包,划分 项目层次 ,便于管理
- 帮助 管理大型软件 系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 解决 类命名冲突 的问题
- 控制 访问权限
MVC是一种软件构件模式,目的是为了降低程序开发中代码业务的耦合度。
MVC设计模式将整个程序分为三个层次: 视图模型(Viewer)层 , 控制器(Controller)层 ,与 数据模型(Model)层 。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
视图层viewer:显示数据,为用户提供使用界面,与用户直接进行交互。
>相关工具类 view.utils
>自定义view view.ui
控制层controller:解析用户请求,处理业务逻辑,给予用户响应
>应用界面相关 controller.activity
>存放fragment controller.fragment
>显示列表的适配器 controller.adapter
>服务相关的 controller.service
>抽取的基类 controller.base
模型层model:主要承载数据、处理数据
>数据对象封装 model.bean/domain
>数据库操作类 model.dao
>数据库 model.db
8.1.4 JDK中主要的包介绍
java.lang ----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能 。java.net ----包含执行与网络相关的操作的类和接口。 java.io ----包含能提供多种输入/输出功能的 类。 java.util ----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。 java.text ----包含了一些java格式化相关的类 java.sql ----包含了java进行JDBC数据库编程的相关类/接口 java.awt ----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
8.2 import(导入)
为了使用定义在其它包中的Java类,需用import语句来显式引入指定包下所需要的类。相当于 import语句告诉编译器到哪里去寻找这个类 。
8.2.1 语法格式
import 包名.类名;
8.2.2 应用举例
import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
public class PackTest{public static void main(String args[]){Test t = new Test(); //Test类在pack1.pack2包中定义t.display();}
}
8.2.3 注意事项
- import语句,声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 如果使用 a.* 导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。
- 如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。
- (了解) import static 组合的使用:调用指定类或接口下的静态的属性或方法
9. 面向对象特征一:封装性(encapsulation)
9.1 为什么需要封装?
- 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要 碰电动机吗?
- 我要开车,我不需要懂离合、油门、制动等原理和维修也可以驾驶。
- 客观世界里每一个事物的内部信息都隐藏在其内部,外界无法直接操作和修改,只能通过指定的方 式进行访问和修改。
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“ 高内聚、低耦合 ”。
高内聚、低耦合是软件工程中的概念,也是UNIX 操作系统设计的经典原则。
内聚,指一个模块内各个元素彼此结合的紧密程度;耦合指一个软件结构内不同模块之间互连程度的度量。内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
而“高内聚,低耦合”的体现之一:
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅暴露少量的方法给外部使用,尽量方便外部调用。
9.2 何为封装性?
所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对象开放,向没必要开放的类或者对象隐藏信息。
通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
9.3 Java如何实现数据封装
实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控 制。
权限修饰符: public 、 protected 、 缺省 、 private 。具体访问范围如下:
修饰符 | 本类内部 | 本包内 | 其他包的子包 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
具体修饰的结构:
外部类:public、缺省
成员变量、成员方法、构造器、成员内部类:public、protected、缺省、private
9.4 封装性的体现
9.4.1 成员变量/属性私有化
概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。
实现步骤:
① 使用 private 修饰成员变量
private 数据类型 变量名 ;
public class Person {private String name;private int age;private boolean marry;
}
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 setAge(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());p.setAge(23);System.out.println("p.age = " + p.getAge());p.setMarry(true);System.out.println("p.marry = " + p.isMarry());}
}
成员变量封装的好处:
- 让使用者只能通过事先预定的方法来 访问数据 ,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改 ,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
9.4.2 私有化方法
/*** @Description 自定义的操作数组的工具类*/
public class ArrayUtil {/*** @Description 求int型数组的最大值* @param arr* @return*/public int max(int[] arr) {int maxValue = arr[0];for(int i = 1;i < arr.length;i++){if(maxValue < arr[i]){maxValue = arr[i];}}return maxValue;}/**** @Description 求int型数组的最小值* @return*/public int min(int[] arr){int minValue = arr[0];for(int i = 1;i < arr.length;i++){if(minValue > arr[i]){minValue = arr[i];}}return minValue;}/*** @Description 求int型数组的总和* @return*/public int sum(int[] arr) {int sum = 0;for(int i = 0;i < arr.length;i++){sum += arr[i];}return sum;}/*** @Description 求int型数组的元素的平均值* @return*/public int avg(int[] arr) {int sumValue = sum(arr);return sumValue / arr.length;}
// 创建一系列重载的上述方法
// public double max(double[] arr){}
// public float max(float[] arr){}
// public byte max(byte[] arr){}/**
/** @Description 遍历数组* @param arr
*/public void print(int[] arr) {for(int i = 0;i < arr.length;i++){System.out.print(arr[i] + " ");}System.out.println();}/*** @Description 复制数组arr* @return*/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;}/*** @Description 反转数组*/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;}}/*** @Description 数组的排序* @param desc 指明排序的方式。 ascend:升序 descend:降序*/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[j];
// arr[j] = arr[j + 1];// arr[j + 1] = temp;swap(arr,j,j+1);}}}}else if ("descend".equals(desc)){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[j];
// 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;}/*** @Description 查找指定的value值在arr数组中出现的位置* @param arr* @param value* @return 返回value值出现的位置 或 -1:未找到*/public int getValue(int[] arr, int value) {
//方法:线性查找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修饰。
9. 类的成员之三:构造器(Constructor)
我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢?
可以,Java给我们提供了 构造器(Constructor) ,也称为 构造方法 。
9.1 构造器的作用
new对象,并在new对象的时候为实例变量赋值。
举例:Person p = new Person(“Peter”,15) ;
解释:如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
9.2 构造器的语法格式
[修饰符] 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 s1 = new Student();//调用有参构造创建学生对象Student s2 = new Student("张三",23);System.out.println(s1.getInfo());System.out.println(s2.getInfo());}
}
9.3 使用说明
1. 当我们没有显式的声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器的修饰 符默认与类的修饰符相同
2. 当我们显式的定义类的构造器以后,系统就不再提供默认的无参的构造器了。
3. 在类中,至少会存在一个构造器。
4. 构造器是可以重载的。
10. 阶段性知识补充
10.1 类中属性赋值过程
1、在类的属性中,可以有哪些位置给属性赋值?
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过"对象.属性"或"对象.方法"的方式,给属性赋值
2、这些位置执行的先后顺序是怎样?
顺序:① - ② - ③ - ④
3、说明:
上述中的①、②、③在对象创建过程中,只执行一次。
④ 是在对象创建后执行的,可以根据需求多次执行。
10.2 JavaBean
- JavaBean是一种Java语言写成的可重用组件。
好比你做了一个扳手,这个扳手会在很多地方被拿去用。这个扳手也提供多种功能(你可以拿 这个扳手扳、锤、撬等等),而这个扳手就是一个组件。
- 所谓JavaBean,是指符合如下标准的Java类:
- 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
- 《Think in Java》中提到,JavaBean最初是为Java GUI的可视化编程实现的。你拖动IDE构建工具创建一个GUI 组件(如多选框),其实是工具给你创建Java类,并提供将类的属性暴露出来给你修改调整,将事件监听器暴露出来。
示例
public class JavaBean {private String name; // 属性一般定义为privateprivate 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;}
}
10.3 UML类图
- UML(Unified Modeling Language,统一建模语言),用来描述 软件模型 和 架构 的图形化语言。
- 常用的UML工具软件有 PowerDesinger 、 Rose 和 Enterprise Architect 。
- UML工具软件不仅可以绘制软件开发中所需的各种图表,还可以生成对应的源代码。
- 在软件开发中,使用 UML类图 可以更加直观地描述类内部结构(类的属性和操作)以及类之间的关 系(如关联、依赖、聚合等)。