第五章、面向对象编程01

1、类与对象

1.1 基本介绍

image-20211116083255685

  • 类是抽象的,概念的,代表一类事物,比如人类,猫类.., 即它是数据类型
  • 对象是具体的,实际的,代表一个具体事物, 即 是实例
  • 类是对象的模板,对象是类的一个个体,对应一个实例

对象创建在内存中是怎么存在的:

image-20211116083416950

在堆中开辟对象的内存空间,它的常量存储在方法区中,栈中存储存储这个对象的引用地址,这个变量名称指向堆中对象的内存地址。

对象的属性如果不赋值是有默认值的。

1.2 内存介绍

这个部分以后会在JVM中好好介绍的:

  • 栈:一般是存放基本数据类型的,比如局部变量等
  • 堆:存放对象的
  • 方法区:存放经常使用的东西,常量池(字符串常量池和int常量池等),类加载信息

2、成员方法

2.1 基本介绍

成员方法:就是对象操作的方法叫成员方法,引用就相当于是一个遥控器,遥控器遥控电视机做什么就相当于是成员方法(使用的一些功能)。

语法:

访问修饰符 [static] 返回值类型 方法名(形式参数列表){
    方法体;
    return xxx;//不一定有
}
复制代码
  • 访问修饰符 (作用是控制 方法使用的范围):

    • 如果不写默认访问, [有四种: public, protected, 默认, private]
  • 返回值类型:

    • 一个方法最多有一个返回值

    • 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)

    • 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的 值类型一致或兼容

    • 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;

  • 形参列表:

    • 可以有零到多个,最后传入的参数必须是同类型或兼容的类型

实例:

public class Method01 {
    public static void main(String[] args) {
        Person person = new Person();
        person.learn();
    }
}

class Person{
    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void learn(){
        System.out.println("学习");
    }
}
复制代码

2.2 方法的传参机制(重要)

  • 如果是基本数据类型,是值拷贝
public class BasicTypeParameterDeliver{
    public static void main(String[] args) {
        int a = 10,b = 20;
        Method method = new Method();
        method.swap(a,b);
        System.out.println("main方法的参数a = " + a + ", b = " + b);	//10,20
    }
}

class Method{
    public void swap(int a,int b){
        System.out.println("\na和b交换前的值\na=" + a + ",\tb=" + b);	//10,20
        int temp = a;
        a = b;
        b = temp;
        System.out.println("\na和b交换后的值\na=" + a + ",\tb=" + b);	//20,10
    }
}
复制代码
  • 如果是引用数据类型,是引用拷贝
public class BasicTypeParameterDeliver2 {
    public static void main(String[] args) {
        //测试

        B b = new B();
        // int[] arr = {1, 2, 3};
        // b.test100(arr);//调用方法

        // System.out.println(" main 的 arr 数组 ");
        // //遍历数组

        // for(int i = 0; i < arr.length; i++) {
        // System.out.print(arr[i] + "\t");
        // }
        // System.out.println();
        //测试

        Person1 p = new Person1();
        p.name = "jack";
        p.age = 10;
        b.test200(p);
        //测试题, 如果 test200 执行的是 p = null ,下面的结果是 10
        //测试题, 如果 test200 执行的是 p = new Person();..., 下面输出的是 10
        System.out.println("main 的 p.age=" + p.age);//10000
    }
}

class Person1 {
    String name;
    int age;
}

class B {
    public void test200(Person1 p) {
        p = new Person1();
        p.name = "tom";
        p.age = 99;

    }

    public void test100(int[] arr) {
        arr[0] = 200;//修改元素

        //遍历数组
        System.out.println(" test100 的 arr 数组 ");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}
复制代码

2.3 递归(非常重要)

递归就是一个方法不断的调用自己,如果没有设置停止条件可能会造成栈内存溢出(StackOverflowError)。不断递归就是不断调用方法的过程,每次递归都会在栈中开辟一块空间,而下面的空间是不会释放的。

斐波那契数列问题

这样一个数列:1、1、2、3、5、8、13、21、34..., 也就是F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)这样一个数列。

public class Fibonacci {
    public static void main(String[] args) {
        System.out.println(fibonacci(4));
    }

    private static int fibonacci(int num){
        if (num >= 1){
            if (num == 1 || num == 2){
                return 1;
            }else {
                return fibonacci(num-2) + fibonacci(num-1);
            }
        }else {
            System.out.println("请选择大于等于1的整数");
            return -1;
        }
    }
}
复制代码

迷宫问题

更加难一点的是迷宫问题,需要考虑很多种情况,这个题是,找到出口。

img

思路就是使用递归,选择一种行走方式,如果碰到墙了就换个方向,如果没碰到墙就递归调用。

public class MiGong {
    public static void main(String[] args) {
        int[][] map = new int[8][7];
        //把四边设置为墙,墙用1表示
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //把中间部分的墙设置好
        map[3][1] = 1;
        map[3][2] = 1;
        map[4][3] = 1;

        System.out.println("初始状态");
        showMap(map);

        setWay(map,1,1);

        //查看地图
        System.out.println("最终状态");
        showMap(map);

    }

    /**
     * 解开地图路线的方法
     *      数字表示的含义:
     *          0表示没走过
     *          1表示墙
     *          2表示可以走
     *          3表示走不通
     * @param map   需要解开的地图
     * @param i     初始位置行索引
     * @param j     初始位置列索引
     */
    public static boolean setWay(int[][] map,int i,int j){
        /*
            规定一下路线选择的优先级
                1.先向下走
                2.向下走不了向右走
                3.向又走不了向上走
                4.向上走不了向左走
         */

        if (map[i][j] == 2){
            return true;
        }else {
            if (map[i][j] == 0){
                map[i][j] = 2;
                if (setWay(map,i+1,j)){
                    return true;
                }else if (setWay(map,i,j+1)){
                    return true;
                }else if (setWay(map,i-1,j)){
                    return true;
                }else if (setWay(map,i,j-1)){
                    return true;
                }else{
                    map[i][j] = 3;
                    return false;
                }
            }
            return false;
        }
    }

    /**
     * 显示map全貌
     * @param map
     */
    public static void showMap(int[][] map){
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
}
复制代码

结果如下:

初始状态
1 1 1 1 1 1 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 0 0 0 1 
1 0 0 1 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 1 1 1 1 
最终状态
1 1 1 1 1 1 1 
1 2 0 0 0 0 1 
1 2 2 2 0 0 1 
1 1 1 2 2 0 1 
1 0 0 1 2 0 1 
1 0 0 0 2 2 1 
1 0 0 0 2 2 1 
1 1 1 1 1 1 1 

Process finished with exit code 0
复制代码

汉诺塔问题

汉诺塔.jpg

几个圆圈开始时在一遍,要移动到另一边,但下面的盘子要比上面大。

这个问题的解决方法是,一共三个位置,初始位置A、目标位置C、中转位置B,如果一共5个圆盘在A处,中间要完成的一定是4个圆盘在B处才能把最大的从A移到C,如果要让圆盘到B处,就一定是A是初始位置,C是中转位置,而B是目标位置,也就是要让3个圆盘在A处才行......以此类推即可得到问题的答案。

代码如下:

public class HanoiTower {
    public static void main(String[] args) {
        Tower.hanoi(3,'a','b','c');
    }

}

class Tower{
    /**
     * 移动汉诺塔的方法
     * @param nums  一共几个圆盘
     * @param a     位置1
     * @param b     位置2
     * @param c     位置3
     */
    public static void hanoi(int nums,char a,char b,char c){
        if (nums == 1){
            System.out.println(a + "-->" + c);
        }else {
            hanoi(nums-1,a,c,b);
            System.out.println(a + "-->" + c);
            hanoi(nums-1,b,a,c);
        }
    }
}
复制代码

八皇后问题

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法(92)。

img

public class Queens {
    int max = 8; //一共8个皇后,定义初始值
    int[] array = new int[max];
    static int count = 0;

    public static void main(String[] args) {
        Queens queens = new Queens();
        queens.put(0);
        System.out.println(count);
    }

    //结果输出方法
    private void print() {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
        }
        System.out.println();
    }

    //检查放到第n个皇后时,是否与之前的皇后冲突
    private boolean check(int n) {
        for (int i = 0; i < n; i++) {
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[i] - array[n])) {
                return false;
            }
        }
        return true;
    }

    //放置第n个皇后
    private void put(int n) {
        if (n == max) { //因为下标是从0开始的,所以当n=8时代表再放就是第九个皇后了,就是已经放完了8个。直接打印结果即可。
            count++;
            print();
            return;
        }
        for (int i = 0; i < max; i++) {
            array[n] = i;//先把这个皇后放到此行的第一个位置
            if (check(n)) {//检查放第n个皇后时是否冲突,不冲突的话就继续放置第n+1个。
                put(n + 1);
            }
        }//如果冲突了就继续执行 array[n] = i这就相当于这一行的这个位置是不行的,我们要试试下个位置了
    }
}
复制代码

2.4 方法重载

可以有同名的方法,但形参列表可以不一致。

public class MethodOverload {

    public int accumulate(int num1,int num2){
        return num1 + num2;
    }
    
    public String accumulate(String num1,String num2){
        return num1 + num2;
    }
    
    public float accumulate(float n1,float n2){
        return n1 + n2;
    }
    
    public double accumulate(double n1,double n2){
        return n1 + n2;
    }
}
复制代码

总结一下:

  • 方法名要相同
  • 形参列表:必须不同(类型,个数,顺序至少有一个不同)
  • 返回值类型:不要求

为什么不通过返回值类型去判断呢?

重载一般是编译阶段就已经确定好用哪个了,可是如果重载也可以由返回值决定,在程序都没运行完,怎么知道返回值是什么类型的呢?所以不成立,甚至有的方法都没有返回值,程序员本身某一行代码也不需拿到返回值,所以他就写了一行:f()

所以谁知道这一行代码要调用的是有返回值的方法呢?还是void的方法呢?所以不能用返回值判断重载。

2.5 可变参数

比如有些时候,我们并不知道传入的参数到底有多少,可以采用下面这种方式:

访问修饰符 返回类型 方法名(数据类型... 形参名) { 
	//方法体;
}
复制代码

实例:

public class VarParameter {
    public static void main(String[] args) {
        System.out.println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

    public static int sum(int... num){
        int result = 0;
        for (int i = 0; i < num.length; i++) {
            result+= num[i];
        }
        return result;
    }
}
复制代码

可变参数可以是0-任意个,可变参数的实参可以是数组,而且本质也是个数组。

2.6 作用域

image-20211116090532880

public class Variables {
    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println(dog);
        dog.introduceIfself();
    }
}

class Dog{
    //全局变量可以设置值
    String name = "大黄";
    //也可以不设置
    int age;

    public Dog() {
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void introduceIfself(){
        String name = "一条土狗阿黄";
        int age = 4;
        System.out.println(name + age);
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Dog{");
        sb.append("name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }
}
复制代码

细节:

  • 局部变量必须提前初始化好才能被使用,成员变量可以不赋值,直接使用
  • 全局变量和局部变量重名时,遵循就近原则
  • 全局变量是对象销毁时销毁,而局部变量是代码块或方法结束是销毁
  • 全局变量可加修饰符,局部变量不能加修饰符

2.7 构造方法

用于创建对象的,基本语法:

[修饰符] 方法名(形参列表){
	方法体;
}
复制代码

需要注意:

  • 构造器也可以有修饰符,比如public、protected、private、默认
  • 构造器没有返回值
  • 构造器的方法要和类名一样
  • 在创建对象的时候,系统会自动调用相应构造器完成初始化
  • 一个类可以设置多个不同的构造器,形成重载
  • 如果没有定义构造器,系统会给一个默认构造器;而如果定义了构造器,系统就不再给默认构造器了

像下面这样写就是报错的:

public class ConstructorDetails {
    public static void main(String[] args) {
        Worker worker = new Worker();
    }
}

class Worker{
    String name;
    int age;

    public Worker(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
复制代码

因为没有了默认构造器。

2.8 this关键字

通过构造器这一张就可以看看this关键字的作用,先看这串代码:

class A{
    int id;
    String username;
    String password;

    public A(int Aid, String Ausername, String Apassword) {
        id = Aid;
        username = Ausername;
        password = Apassword;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("A{");
        sb.append("id=").append(id);
        sb.append(", username='").append(username).append('\'');
        sb.append(", password='").append(password).append('\'');
        sb.append('}');
        return sb.toString();
    }
}
复制代码

这里的构造器要给id、username、password设置属性值,但是为了避免成员变量名和形参名重复,就把名字改了,其实完全不用这样,直接使用this即可:

public A(int id, String username, String password) {
    this.id = id;
    this.username = username;
    this.password = password;
}
复制代码

这里的this表示当前的对象,this.id就表示这个对象的id属性 = id值,这样就很清楚了。

细节:

  • this可以访问本类的属性、方法、构造器,可以这么理解this.方法名(); 也就是谁调的这个方法,this就表示哪个对象
  • this的另一个作用就是刚才区分类的属性名和局部变量的
  • 可以使用this(参数列表)来访问构造器,这个多用于构造器定义,必须放在第一条语句
public class ThisDetails01 {
    public static void main(String[] args) {
        Person2 person = new Person2("张三",10);
    }
 
    
}

class Person2{
    String name;
    int age;

    public Person2() {
    }

    public Person2(String name, int age) {
        //这行才是精髓
        this();
        this.name = name;
        this.age = age;
    }
}
复制代码

猜你喜欢

转载自juejin.im/post/7128971872607141924
今日推荐