一、使用对象的原因
如果我们需要对猫进行一个描述(名字、年龄、走路、喵叫......)
①单独用变量来记录数据,把信息拆解了,不利于数据的管理;
②用数组来记录数据,会出现如下问题:
1)数据类型体现不出来;
2)只能用下标才能获取对应信息,导致变量名字、内容的对应关系不明确;
3)不能体现我们对象的行为。
一个程序就是一个世界,有很多事务(对象(属性、行为))
二、类与对象
2.1 类与对象的关系
1、类是抽象的,概念的,代表一类事物,比如人类、狗类...,即它是一种数据类型;
2、对象是具体的 ,实际的,代表一个具体的事务,比如一只具体的猫,即它是一个实例;
3、类是对象的模板,而对象是类的一个个体,对应一个实例。
2.2 对象在内存中的存在形式
对象名(对象引用)只是用来指向对象的,真正的对象是对象空间(内存中存储的地址和数据)。
public class Object01 {
public static void main(String[] args) {
Cat cat01 = new Cat();
cat01.age = 12;
cat01.color = "白色";
cat01.name = "小白";
}
}
class Cat{
String name;
String color;
int age;
}
2.3 属性与成员变量
2.3.1 两者关系
1、属性与成员变量是同一个意思。(这也有种叫法:字段(field))
2、属性是类的一个组成部分,一般是基本数据类型,也可以是引用数据类型(对象,数组)。
2.3.2 属性的注意事项
1、属性的定义语法类似变量: 访问修饰符 属性类型 属性名;
2、有四种访问修饰符:public,protected,默认,private;
3、访问修饰符的作用:控制属性或方法的访问范围;
4、如果属性不赋值,会有默认值,规则和数组一样;
2.4 类与对象的内层分配机制
Java内存结构分析
1、栈:一般存放基本数据类型(局部变量);
2、堆:存放对象(Cat cat,数组等);
3、方法区:常量池(常量,例如字符串),加载类信息;
2.5 成员方法(简称方法)
在某些情况下,我们需要定义成员方法。比如人类,除了基本的属性外(年龄,姓名...),我们人类有会有一些动作方法:说话、跑步...
2.5.1 方法调用小结
1、当程序执行到方法时,就会开辟一个独立的空间(栈空间);
2、当方法执行完毕后,或者执行到return语句时,就会返回;
3、返回到调用方法的地方(返回后,原本的开辟的栈就会销毁);
4、返回后,继续执行剩下的程序。
2.5.2 方法的作用(好处)
考虑这么一个情况,如果你的程序中有多个for循环输出,那你是不是要一遍一遍重写,而且当需要对输出部分的代码进行修改时,是不是还得一个一个重新改,这样子太麻烦了。我们是不是可以写一个for循环输出的方法,任何需要输出时调用改方法即可,而且要对输出部分代码进行修改时,只需要修改方法,就可以达到各个输出的部分全部修改。
1、提高代码复用率,降低代码冗余;
2、将实现的细节封装起来,然后提供其他用户来调用即可;
2.5.3 方法的定义
访问修饰符 返回数据类型 方法名(形参列表...){ //方法体
代码块;
return 返回值; //可有可没有。
}
例子:
class Person{
String name;
int age;
//方法(成员方法)
//
//1.public: 表示方法是公开的;
//2.void: 表示方法没有返回值;
//3.speak(): speak是方法名,()中是形参列表;
public void speak(){
System.out.println("我是个好人");
}
}
2.5.4 方法细节
①访问修饰符:
1、有四种访问修饰符:public,protected,默认,private;
2、访问修饰符的作用:控制属性或方法的访问范围;
②返回数据类型:
1、一个方法最多只有一个返回值;(需要返回多个结果可以返回数组)
2、返回类型可以为任意类型,包含基本类型和引用类型(数组、对象);
3、如果方法要求有返回数据类型,则方法体中最后执行的语句必须为 return 值;
(要求返回值类型必须和return的值一致或是兼容(就是可以自动类型转换))
4、如果方法的返回数据类型为void,方法体中可以没有return语句,或则只写 return。
③方法命名:
1、命名遵循小驼峰命名法!
2、方法名要见名知意!
④形参列表:
1、一个方法可以没有形参或则多个形参;
2、形参类型可以为任意类型,包含基本类型或引用类型;
3、实参和形参的类型要一致或兼容、个数、顺序必须一致;
⑤方法体:
1、方法不可嵌套定义;
⑥方法调用:
1、同一个类中的方法可以直接调用;
2、跨类调用方法的话,需要通过对象调用方法;
3、跨类调用方法时,还和访问修饰符相关;
⑦方法传参机制:
1、对于基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参!
(如果需要取得基本数据类型的地址可以将其转化为包装类)
2、对于引用数据类型,传递的是地址(值是地址),可以通过形参影响实参!
三、方法的递归调用
方法的递归,就是方法调用自己。
3.1 递归解决的问题
1、各种数学问题:8皇后问题,汉诺塔,乘阶问题,迷宫问题,球和篮子问题;
2、各种算法:二分查找,快排,归并排序,二分查找,分治算法等;
3、用栈来解决问题,递归代码简洁。
3.2 递归的规则
1、执行一个方法时,就创建一个新的受保护的独立空间(栈空间);
2、方法的局部变量的独立的,不会相互影响;
3、如果方法中使用的是引用数据类型变量(比如数组,对象),就会共享该引用类型的数据。
4、递归必须向退出递归的条件推进,否则会无限递归,出现StackOverflowError;
5、当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁;同时,当方法执行完毕或者返回时,该方法也就执行完毕。
3.3 例题
3.3.1 迷宫问题
public class Labyrinth {
public static void main(String[] args) {
//思路
//1.先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
int[][] map = LabyrinthTools.mapInit();
//2.输出迷宫(测试)
LabyrinthTools.outputOfMap(map);
//3.开始找路
LabyrinthTools.findway(map,1,1);
//4.输出迷宫(测试)
System.out.println("找到的路为:");
LabyrinthTools.outputOfMap(map);
}
}
class LabyrinthTools{
public static int[][] mapInit(){ //地图初始化
//思路
//1.先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
//2.规定:0表示可走,1表示障碍物
int[][] map = new int[8][7];
//3. 将四周全部设置为1
for(int i = 0; i < 7; i++){
map[0][i] = 1;
map[7][i] = 1;
}
for(int i = 0; i < 8; i++){
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
return map;
}
public static void outputOfMap(int[][] arr){ //输出地图
for(int i = 0; i < arr.length; i++){
System.out.println();
for(int j = 0; j < arr[i].length; j++){
System.out.print(arr[i][j]+" ");
}
}
System.out.println();
}
public static boolean findway(int[][] map, int i , int j){
//1. findway专门来找出迷宫的路径
//2. 如果找到,就返回true,否则就返回false
//3. map就是二维数组,即表示迷宫
//4. i和j表示老鼠当前的位置
//5. 入口为(1,1),出口为(6,5)
//6. 因为是递归找路,所以我们先规定map数组中各个值的含义:
// 0 表示可以走,且还没走过, 1 表示障碍物, 2 表示可以走, 3 表示走过了,但是走不通
//7. 当出口(6,5)的值2时,表示我们走通了
//8. 找路策略:先看下,后看右,再看上,最后看左
if(map[6][5] == 2){
return true;
}else{
if(map[i][j] == 0){//该位置为0,表示还没走,就走走看
//我们假定可以走通
map[i][j] = 2;
//开始找路策略:
if(findway(map,i+1,j)){
return true;
}else if(findway(map,i,j+1)){
return true;
}else if(findway(map,i-1,j)){
return true;
}else if(findway(map,i,j-1)){
return true;
}else{ //都走不通
map[i][j] = 3;
return false;
}
} else{//走过的就不要再走了
return false;
}
}
}
}
输出结果:(2表示通路)
3.3.2 汉诺塔
public class HanoiTower {
public static void main(String[] args) {
int num = 3;
char tower01 = 'A';
char tower02 = 'B';
char tower03 = 'C';
HanoiTowerTools.move(num,tower01,tower02,tower03);
}
}
class HanoiTowerTools{
public static void move(int num,char a,char b,char c){
//num表示要移动的个数,a、b、c表示A塔、B塔和C塔
//即A表示盘的出发点,B表示借助的塔,C表示目的地
//如果只有一个盘 num = 1
if(num == 1){
System.out.println(a + "->" + c);
}else{
//如果有多个盘,可以看成两个,即最下面的和上面的所有盘(num-1)
move(num-1,a,c,b);
//把最下面的盘移动到c
System.out.println(a + "->" + c);
//把b中的所有盘移动到c,借助a盘
move(num-1,b,a,c);
}
}
}
输出结果:
3.3.3 八皇后问题
public class EightQueens {
public static void main(String[] args) {
//初始化棋盘
int[][] checkerboard = EightQueensTools.checkerboardInit();
//八皇后摆放
EightQueensTools eigthQueensPlay = new EightQueensTools();
eigthQueensPlay.play(checkerboard,0);
System.out.println("总共有" + eigthQueensPlay.count + "个解");
}
}
class EightQueensTools{
int count = 0; //用于解法的计数
public static int[][] checkerboardInit(){ //棋盘初始化
int[][] checkerboard = new int[8][8];
return checkerboard;
}
public static void checkerboardPrint(int[][] checkboard){ //棋盘打印
for(int i = 0; i < checkboard.length; i++){
for(int j = 0; j < checkboard[i].length; j++){
System.out.print(checkboard[i][j] + " ");
}
System.out.println();
}
}
public void putQueenPlay(int[][] arr, int row){//八皇后摆放
for(int col = 0; col < 8; col++){//遍历本行的8种摆放情况
if(ableToPut(arr,row,col)){ //可以摆放
//该位置可以摆放,置1
arr[row][col] = 1;
//第八列也置1好了,得出解
if(row == 7){ //递归出口
System.out.println("找到一个解!");
checkerboardPrint(arr);
count++;
}else{
putQueenPlay(arr,row+1);
}
}
arr[row][col] = 0;
}
}
public void play(int[][] arr, int row){ //主要为了防止多次调用putQueenPlay方法而倒数count不准确
this.count = 0;
putQueenPlay(arr,row);
}
public static boolean ableToPut(int[][] arr, int row, int col){ //判断该位置是否能摆放皇后
//判断同行同列的有没有皇后
for(int i = 0; i < 8; i++){
if(arr[row][i] == 1){//同行有皇后
return false;
}else if(arr[i][col] == 1){//同列有皇后
return false;
}
}
//左上有皇后
for(int i = row, j = col;i >= 0 && j >= 0;i--,j--){
if(arr[i][j] == 1){
return false;
}
}
//左下有皇后(可省略)
for(int i = row, j = col;i < 8 && j >= 0;i++,j--){
if(arr[i][j] == 1){
return false;
}
}
//右上有皇后
for(int i = row, j = col;i >= 0 && j < 8;i--,j++){
if(arr[i][j] == 1){
return false;
}
}
//右下有皇后(可省略)
for(int i = row, j = col;i < 8 && j <8;i++,j++){
if(arr[i][j] == 1){
return false;
}
}
//都没有,可以摆放
return true;
}
}
运行结果:
......
四、方法重载(OverLoad)
4.1 基本介绍
Java中允许同一个类中,多个同名的方法存在,但形参列表要不一样。
(形参列表不同是重载,一样就是同名了!)
public class OverLoad01 {
public static void main(String[] args) {
System.out.println(OverLoadTools.calculate(1,2));
System.out.println(OverLoadTools.calculate(1.3,2.1));
}
}
class OverLoadTools{
public static int calculate(int n1, int n2){
System.out.println("调用了方法1");
return n1 + n2;
}
public static double calculate(double n1, double n2){
System.out.println("调用了方法2");
return n1 + n2;
}
}
4.2 重载的好处
1、减轻了起名的麻烦;
2、利于我们的接口编程。
4.3 方法重载的细节
1、方法名必须相同;
2、形参列表必须不同(形参的类型或个数或顺序必须有一个不一样);
3、返回类型:无要求(即使方法返回类型不同,但形参的类型或个数或顺序一样,就会出错)。
五、可变参数
5.1 基本介绍
Java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。这样就可以通过可变参数的实现。
5.2 基本语法
访问修饰符 返回类型 方法名 (数据类型... 形参名) { // ... 是固定的,要这样写
}
public class VarParameter {
public static void main(String[] args) {
System.out.println(VarParameterTools.calculate(10,11,13,12));
}
}
class VarParameterTools{
public static int calculate(int... n){ // 可以计算2个数、3个数、4、5...个数的和
// 1. int... 表示接受的是可变参数,类型是int,可接受多个int(0 - 多个)
// 2. 使用可变参数时,可以当作数组来使用,即 n 可当作数组
int sum = 0;
for(int i = 0; i < n.length; i++){
sum += n[i];
}
return sum;
}
}
5.3 可变参数的细节
1、可变参数的实参可以是0个或多个;
2、可变参数的实参也可以是一个数组;
3、可变参数可以和普通参数一起放在形参列表,但是可变参数一定要放最后;
4、一个形参列表中,最多只能有一个可变参数。
六、作用域
6.1 基本介绍
在面向对象中,变量作用域是非常主要的知识点。
1、在Java编程中,主要的变量就是属性(成员变量)和局部变量;
2、我们说的局部变量一般是指在成员方法中定义的变量;
3、Java中作用域的分类:全局变量和局部变量;
① 全局变量:也就是属性,作用域为整个类体;
② 局部变量:除了属性之外的其他变量,作用域为定义它的代码块。
4、全局变量可以不赋值,直接使用,因为有默认值;但是局部变量必须赋值后才能使用,因为没有默认值。
6.2 作用域细节(5点)
1、属性和局部变量可以重名,访问时遵循就近原则;
2、在同一个作用域中,两个变量不可重名;
3、属性的生命周期较长,伴随着对象的创建也创建,伴随着对象的死亡而死亡。局部变量的生命周期较短,伴随着它的代码块的执行而建立,伴随着代码块的结束而死亡。
4、作用范围的不同:
① 全局变量可以被本类使用,或其他类使用(通过对象调用);
② 局部变量只能在本类的对应方法中使用。
(如下图,main方法中可以调用OverLoadTools这个对象的属性name,但调用不了在OverLoadTools中定义的局部变量n3。)
5、修饰符的不同:
① 全局变量/属性可添加修饰符;
② 但局部变量不可以添加修饰符。
(如下图,name属性可以添加修饰符,而局部变量n3添加不了修饰符)
七、构造方法 / 构造器
之前我们在创建对象时,是先new一个对象,然后再给对象的属性赋值;如果我们现在要在创建对象时,就指定这个对象的属性,就需要使用到构造器。
7.1 基本介绍
构造方法又称为构造器(constructor),是类的一种特殊方法,主要作用是完成对新对象的初始化。(即构造器不是取创建对象,而只是对对象进行初始化!)
它拥有如下特点:
① 方法名和类名相同;
② 没有返回值;
③ 在创建新对象时,系统会自动调用该类的构造器来完成对象的初始化。
7.2 基本语法
修饰符 方法名 (形参列表) {
方法体;
}
public class VarParameter {
public static void main(String[] args) {
VarParameterTools test = new VarParameterTools("你好",23);
System.out.println("test的name:" + test.name);
System.out.println("test的age:" + test.age);
}
}
class VarParameterTools{
int age;
String name;
public VarParameterTools(String name, int age){
this.age = age;
this.name = name;
}
}
7.3 注意
1、构造器的修饰符可以是默认的,也可以是public、protectec、private;
2、构造器没有返回值;
3、方法名和类的名字必须一致;
4、形参列表和成员方法一样的规则;
5、构造器的调用,由系统完成。
7.4 构造器使用细节(7点)
1、一个类可以定义多个不同的构造器,即构造器方法的重载;
2、构造器名要与类名相同;
3、构造器没有返回值;
4、构造器是完成对象的初始化,而不是创建对象;
5、在创建对象时,系统自动调用该类的构造器(构造方法);
public class VarParameter {
public static void main(String[] args) {
VarParameterTools test = new VarParameterTools("你好",23);
System.out.println("test的name:" + test.name);
System.out.println("test的age:" + test.age);
VarParameterTools test2 = new VarParameterTools("你好,这是第二个test");
System.out.println("test2的name:" + test2.name);
System.out.println("test2的age:"+test2.age);
}
}
class VarParameterTools{
int age;
String name;
public VarParameterTools(String name, int age){
this.age = age;
this.name = name;
}
public VarParameterTools(String name){
this.name = name;
}
}
6、如果程序员没有定义构造器,系统会默认自动给类生成一个默认无参构造器(也叫默认构造器);
7、一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器了,除非自己再定义一次无参构造方法。(可用javap命令来反编译查看验证)
八、this关键字
8.1 问题描述
当我们需要方法中的形参名与对象的属性名一样时,根据变量作用域的就近原则,会出问题,方法中的代码块只会对局部变量进行操作。
public class VarParameter {
public static void main(String[] args) {
VarParameterTools test = new VarParameterTools("你好",23);
System.out.println("test的name:" + test.name);
System.out.println("test的age:" + test.age);
}
}
class VarParameterTools{
int age;
String name;
public VarParameterTools(String name, int age){
age = age;
name = name;
}
}
8.2 this简介
Java虚拟机会给每个对象分配this,代表当前对象。简单来说,哪个对象调用this,this就代表谁。
(可以这么理解:this是对象的一个隐藏的属性,其指向自己本身。)
public class VarParameter {
public static void main(String[] args) {
VarParameterTools test = new VarParameterTools("你好",23);
System.out.println("test的name:" + test.name);
System.out.println("test的age:" + test.age);
}
}
class VarParameterTools{
int age;
String name;
public VarParameterTools(String name, int age){
this.age = age;
this.name = name;
}
}
8.3 this的使用细节(5点)
1、this关键字可以访问本类的属性、方法和构造器;
2、this用于区分当前类的属性和局部变量;
3、访问成员方法的语法:this.方法名(参数列表);
4、访问构造器的语法:this(参数列表);
(注意只能再构造器中使用,即只能在构造器中调用另外一个构造器。)
(且使用了这种语法,该语句必须放在第一句。)
public class VarParameter {
public static void main(String[] args) {
VarParameterTools test = new VarParameterTools();
System.out.println("test的name:" + test.name);
System.out.println("test的age:" + test.age);
}
}
class VarParameterTools{
int age;
String name;
public VarParameterTools(){ //访问构造器语法:this(参数列表),必须是第一条语句
this("你好,这是测试",24);
}
public VarParameterTools(String name, int age){
System.out.println("调用了VarParameterTools(String name, int age)");
this.age = age;
this.name = name;
}
}
5、this不能在类定义的外部使用,只能在类定义的方法中使用。