Skip to content

1.类与对象

1.1 类与对象关系示意图

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

1.2 对象在内存中存在的形式

String类型会先放在常量池中,将地址引用 在执行new的时候,要加载类的信息,主要加载属性和方法 img_74.png

1.3 属性/成员变量/字段

  1. 从概念或叫法上看: 成员变量 = 属性 = field字段(即 成员变量是用来表示属性的)
  2. 属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象、数组)。

1.4 注意事项和使用细节

  1. 属性的定义语法同变量,示例: 访问修饰符 属性类型 属性名; 访问修饰符: 控制属性的访问范围 有四种访问修饰符 public protected 默认 private
  2. 属性的定义类型可以为任意类型,包含基本类型或引用类型
  3. 属性如果不赋值,有默认值,规则和数组一致。
数据类型默认值
byte0
short0
int0
long0L
float0.0f
double0.0d
char'\u0000'
booleanfalse
引用类型null
  1. Person p1 = new Person(); p1是对象名(对象的引用),new Person();才是实在的对象,在堆中创建空间

1.5 如何创建对象

  1. 先声明后创建
java
Cat cat ; //声明对象 cat
cat = new Cat(); //创建
  1. 直接创建
java
Cat cat = new Cat();
  1. 访问属性
java
基本语法
对象名.属性名;
案例演示赋值和输出
cat.name ;
cat.age;
cat.color;

1.6 类与对象的内存分配机制

img_75.pngimg_76.png

1.7 类和对象的内存分配机制

Java 内存的结构分析

  1. 栈: 一般存放基本数据类型(局部变量)
  2. 堆: 存放对象(Cat cat , 数组等)
  3. 方法区: 常量池(常量,比如字符串), 类加载信息

Java 创建对象的流程简单分析 Person p = new Person(); p.name = “jack”; p.age = 10

  1. 先加载 Person 类信息(属性和方法信息, 只会加载一次)
  2. 在堆中分配空间, 进行默认初始化(看规则)
  3. 把地址赋给 p , p 就指向对象
  4. 进行指定初始化, 比如 p.name =”jack” p.age = 10
    img_77.pngimg_78.png

2.成员方法

在某些情况下,我们需要定义成员方法(简称方法)。比如人类:除了有一些属性(年龄、姓名、身高、体重等)外,我们人类还有一些行为比如:可以说话、跑步等,通过学习还可以做算术题。这时就要用成员方法来完成。现在要求对Person类完事。

2.1 方法的快速入门

java
//1. public 表示方法是公开的
//2. int :表示方法执行后,返回一个 int 值
//3. getSum 方法名
//4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数
//5. return res; 表示把 res 的值, 返回
public int getSum(int num1, int num2) {
    int res = num1 + num2;
    return res;
}

2.2 方法调用机制

  1. 在栈中加载main方法创建对象
  2. 在栈中创建getSum方法,在该空间中给参数变量赋值
  3. 执行逻辑,返回结果 img_79.png

2.3 成员方法的定义

aiignore
访问修饰符 返回数据类型 方法名(形参列表..) {//方法体
语句;
return 返回值;
}
  1. 参数列表: 表示成员方法输入。例如:cal(int n),getSum(int num1,int num2)
  2. 返回数据类型: 表示成员方法输出,void表示没有返回值
  3. 方法主体: 表示为了实现某一功能代码块
  4. return语句不是必须的

2.4 成员方法使用细节

访问修饰符(作用是控制方法使用范围)
如果不写默认访问有四种: public、protected、默认、private

返回数据类型

  1. 一个方法最多有一个返回值
  2. 返回类型可以为任意类型,包含基本类型和引用类型(数组、对象)
  3. 如果方法要求有返回类型。则方法体最后执行的语句必须为return值;而且要求返回值类型必须和return的值类型一致或者兼容
  4. 如果方法是void。则方法体中可以没有return语句,或者只写return;

方法名
遵循驼峰命名法,最好见名知义,表达出该功能的意思即可,比如得到两个数的和命名为getSum。
如果有特殊情况要按照开发文档规范命名

形参列表

  1. 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int n1,int n2)
  2. 参数类型可以为任意类型,包含基本类型或引用类型,比如printArr(int[][] map)
  3. 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
  4. 方法定义时的参数称为形式参数,简称形参。方法调用时的传入参数称为实际参数,简称实参。实参和形参的类型要一致或兼容,个数、顺序必须一致

方法体 里面写完成功能的具体语句,可以为输入、输出、变量、预算、分支、循环、方法调用,但里面不能在定义方法。

方法调用细节

  1. 在同一个类中方法调用: 直接调用即可。比如print(参数)
  2. 跨类中的方法A类调用方法B类: 需要通过对象名调用。比如: 对象名.方法名(参数)
  3. 跨类的方法调用和方法的访问修饰符相关

3.成员方法传参机制

3.1 基本数据类型的传参机制

img_80.png

java
//编写一个 main 方法
public static void main(String[] args) {
    int a = 10;
    int b = 20;
    //创建 AA 对象 名字 obj
    AA obj = new AA();
    obj.swap(a, b); //调用 swap
    System.out.println("main 方法 a=" + a + " b=" + b);//a=10 b=20
}

class AA {
    public void swap(int a,int b){
        System.out.println("\na 和 b 交换前的值\na=" + a + "\tb=" + b);//a=10 b=20
        //完成了 a 和 b 的交换
        int tmp = a;
        a = b;
        b = tmp;
        System.out.println("\na 和 b 交换后的值\na=" + a + "\tb=" + b);//a=20 b=10
    }
}

img_81.png

3.2 引用数据类型的传参机制

B类中编写一个方法test100,可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化?会变化

java
public static void main(String[] args) {
    int[] arr = {1,2,3};
    test100(arr);
    System.out.println("主方法");
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }

}

public static void test100(int[] arr){
    arr[0] = 100;
    System.out.println("test100方法");

    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}

img_82.png

B类中编写一个方法test200,可以接收一个Person(age,sal)对象,在方法中修改该对象属性,看看原来的对象是否变化?会变化

java
public static void main(String[] args) {
   B b = new B();
   Person p = new Person();
    p.name = "jack";
    p.age = 10;
    b.test200(p);

    System.out.println("main 的 p.age=" + p.age);//10000
}

class B {
    public void test200(Person p) {
        p.age = 10000; //修改对象属性
    }
}
java
public static void main(String[] args) {
    B b = new B();
    Person p = new Person();
    p.name = "jack";
    p.age = 10;
    b.test200(p);
    System.out.println("main 的 p.age=" + p.age);//10
}

class B {
    public void test200(Person p) {
        p = null;
    }
}
java
public static void main(String[] args) {
   B b = new B();
   Person p = new Person();
    p.name = "jack";
    p.age = 10;
    b.test200(p);
    System.out.println("main 的 p.age=" + p.age);//10
}

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

结论
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。

3.3 成员方法返回类型是引用类型的细节

编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象。对象是两个独立的对象,只是他们的属性相同

java
public static void main(String[] args) {
    Person p = new Person();
    p.name = "milan";
    p.age = 100;
    //创建 tools
    MyTools tools = new MyTools();
    Person p2 = tools.copyPerson(p);
    //到此 p 和 p2 是 Person 对象,但是是两个独立的对象,属性相同
    System.out.println("p 的属性 age=" + p.age + " 名字=" + p.name);
    System.out.println("p2 的属性 age=" + p2.age + " 名字=" + p2.name);
    //这里老师提示: 可以同 对象比较看看是否为同一个对象
    System.out.println(p == p2);//false
}

class Person {
    String name;
    int age;
}

class MyTools {
    //编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象,
    //注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
    //编写方法的思路
    //1. 方法的返回类型 Person
    //2. 方法的名字 copyPerson
    //3. 方法的形参 (Person p)
    //4. 方法体, 创建一个新对象,并复制属性,返回即可
    public Person copyPerson(Person p) {
        //创建一个新的对象
        Person p2 = new Person();
        p2.name = p.name; //把原来对象的名字赋给 p2.name
        p2.age = p.age; //把原来对象的年龄赋给 p2.age
        return p2;
    }
}

4.方法递归调用

简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂问题,同时可以让代码变得简洁

4.1 递归的举例

打印问题

java
public static void main(String[] args) {

    TP tp = new TP();
    tp.test(4);

}

class TP{
    public void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }
}

img_83.png

阶乘问题

java
public static void main(String[] args) {
    TP tp = new TP();
    int res = tp.factorial(5);
    System.out.println("5 的阶乘 res =" + res);

}

class TP{
    //factorial 阶乘
    public int factorial(int n) {
        if (n == 1) {
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}

img_84.png

4.2 递归使用遵守规则

  1. 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
  2. 方法的局部变量是独立的,不会相互影响,比如n变量
  3. 如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用数据类型的数据
  4. 递归必须向退出递归的条件逼近,否则就是无限递归,会出现StackOverflowError栈内存溢出异常
  5. 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

4.3 递归使用练习

  1. 请使用递归的方式求出斐波那契数1,1,2,3,5,8,13...给你一个整数,求出它的值是多少
java
public static void main(String[] args) {

    TP tp = new TP();
    int fibonacci = tp.fibonacci(7);
    System.out.println(fibonacci);

}

class TP{
    public int fibonacci(int n) {
        if (n > 0) {
            if (n == 1 || n == 2) {
                return 1;
            } else {
                return fibonacci(n - 1) + fibonacci(n - 2);
            }
        } else {
            System.out.println("输入参数有误");
            return -1;
        }
    }
}
  1. 猴子吃桃问题: 有一堆桃子,猴子第一天吃了其中的一半,并在多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时发现只有一个桃子了。问题最初有多少个桃子?
java
public static void main(String[] args) {
    TP tp = new TP();
    int fibonacci = tp.fibonacci(1);
    System.out.println(fibonacci);
}

class TP{
    public int fibonacci(int n) {
        if (n == 10) {
            return 1;
        }else if (n >= 1 && n < 10){
            return (fibonacci(n + 1) + 1) * 2;
        }else {
            return -1;
        }
    }
}

4.4 方法递归应用案例

4.4.1 迷宫问题

java
public static void main(String[] args) {
    //创建一个8行7列地图
    int[][] map = new int[8][7];

    //设置每行的第一个和最后一个是1
    for(int i = 0; i < map.length;i++){
        map[i][0] = 1;
        map[i][map[i].length - 1] = 1;
    }

    //设置每列的第一个和最后一个是1
    for(int i = 0; i < map[i].length;i++){
        map[0][i] = 1;
        map[map.length - 1][i] = 1;
    }

    map[3][1] = 1;
    map[3][2] = 1;

    System.out.println("当前地图");
    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("");
    }

    T t = new T();
    t.findWay(map,1,1);

    System.out.println("找完路之后的地图");
    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("");
    }
}

class T{
    //1. findWay 方法就是专门来找出迷宫的路径
    //2. 如果该位置是0,就返回 true ,是1代表是墙 2代表已经走过不能再走 3 代表该位置上下左右都走不通 返回false
    //3. map 就是二维数组,即表示迷宫
    //4. i,j 就是老鼠的位置,初始化的位置为(1,1)
    //5. 因为我们是递归的找路,所以我先规定 map 数组的各个位置值的含义
    // 0 表示可以走 1 表示障碍物 2 表示可以走 3 表示走过,但是走不通是死路
    //6. 当 map[6][5] =2 就说明找到通路,就可以结束,否则就继续找.
    // 7. 先确定老鼠找路策略 上->右->下->左
    public Boolean findWay(int[][] map,int i,int j){
        //设定最终找到坐标是map[6][5] 就不再找了
        if (map[6][5] == 2){
            return true;
        }else {
            //如果当前位置是0 代表可以走
            if (map[i][j] == 0){
                //先把该点设置为2 表示可以走
                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 {
                    //如果四个位置都走不通,就将该位置置为3
                    map[i][j] = 3;
                    return false;
                }
            }
            //当前位置除了0其他都代表走不通
            return false;
        }
    }
}

执行流程解析
最开始在map[1][1]位置,该位置是0所以可以走
先将该位置置为2,代表已经走过,走map[1-1][1]的位置 img_85.png

递归调用该方法,传入的参数是map 0 1map[0][1]位置是1代表走不了,返回false,所以返回直接返回上一个方法栈
上一个方法栈继续以参数1,1走if的第二个分支判断,就是map[1][1+1],代表向右走 img_86.png

map[1][2]位置是0,则将该位置置为2,然后再继续往上尝试以此类推 img_87.png

回溯现象
将初始地图22位置改为1,以下右上左的方向找
img_88.png

就会出现一个3的位置,就是找了一圈都找不到能走的位置就回到原来的位置找下一个方向
img_89.png

思路分析
从main方法带着1 1参数进入第一个方法栈,1 1位置是0所以开始走if 先将该位置置为2 然后开始再次调用该方法走下一个位置
img_90.png

进入第二个方法栈 带着 1 + 1 1 也就是2 1 参数进入,2 1 位置也是0 也可以进入if,先把该位置置为2,然后再次调用方法 img_91.png

进入第三个方法栈带着3 1 参数该位置是1,所以直接返回false 弹出栈返回第二个方法栈 img_92.png

返回第二个方法栈之后,还是2 1 参数 继续往下执行if分支 img_93.png

再次调用方法,进入第三个方法栈判断,该位置还是1,返回false,继续第二方法栈 img_94.png

第二方法栈再次往下进行if分支判断进入方法 img_95.png

第三方法栈带着1 1 参数,发现还不是0,还是返回false,所以再次进入第二方法栈 img_96.png

再次进入第三方法栈 带着2 0 参数发现还是不行,所以再次返回false img_97.png

进入到第二方法栈,走到else分支,将该位置值置为3,返回false img_98.png

返回false之后,进入第一个方法栈,还是1 1 参数只不过进行第二次分支判断,过程和之前类似,还是再次进入第二方法栈只不过换了参数判断,直到有ruturn返回 img_99.png

4.4.2 汉诺塔

java
public static void main(String[] args) {
    Tower tower = new Tower();
    tower.tower(3,'A','B','C');
}

class Tower{
    public void tower(int num,char a,char b,char c){
        //如果只有一个盘就直接a -> c
        if (num==1){
            System.out.println(a + "->" + c);
        }else {
            //先移动最大的盘上面的所有到b,借助c
            tower(num-1,a,c,b);
            //然后再将最大的盘移动到c
            System.out.println(a + "->" + c);
            //最后再将b的盘移动到c
            tower(num-1,b,a,c);
        }

    }
}

4.4.3 递归实现树形列表

数据库表有一个树形结构所有数据的表,parent_id = 0的是顶级节点,剩下的就找对应id是哪个哪个就是他的父节点 img_100.png

树节点数据类

java
/**
 *  TreeNode 树节点 (定义每一个节点的信息,即每一个节点对应一条数据信息)
 */
@Data
public class TreeNode {
 
    /** 节点ID */
    private Integer id;
 
    /** 父节点ID:顶级节点为0 */
    private Integer parentId;
 
    /** 节点名称 */
    private String label;
 
    /** 子节点 */
    private List<TreeNode> children;
 
    public TreeNode(Integer id, Integer parentId, String label) {
        this.id = id;
        this.parentId = parentId;
        this.label = label;
    }
}

构建树

java
// 保存参与构建树形的所有数据(通常数据库查询结果)
    public List<TreeNode> nodeList = new ArrayList<>();
    
    /**
     *  构造方法
     *  @param nodeList 将数据集合赋值给nodeList,即所有数据作为所有节点。
     */
    public TreeBuild(List<TreeNode> nodeList){
        this.nodeList = nodeList;
    }

    /**
     *   获取需构建的所有根节点(顶级节点) "0"
     *   @return 所有根节点List集合
     */
    public List<TreeNode> getRootNode(){
        // 保存所有根节点(所有根节点的数据)
        List<TreeNode> rootNodeList = new ArrayList<>();
        // treeNode:查询出的每一条数据(节点)
        for (TreeNode treeNode : nodeList){
            // 判断当前节点是否为根节点,此处注意:若parentId类型是String,则要采用equals()方法判断。
            if (0 == treeNode.getParentId()) {
                // 是,添加
                rootNodeList.add(treeNode);
            }
        }
        return rootNodeList;
    }
    
    /**
     *  根据每一个顶级节点(根节点)进行构建树形结构
     *  @return  构建整棵树
     */
    public List<TreeNode> buildTree(){
        // treeNodes:保存一个顶级节点所构建出来的完整树形
        List<TreeNode> treeNodes = new ArrayList<TreeNode>();
        // getRootNode():获取所有的根节点
        for (TreeNode treeRootNode : getRootNode()) {
            // 将顶级节点进行构建子树
            treeRootNode = buildChildTree(treeRootNode);
            // 完成一个顶级节点所构建的树形,增加进来
            treeNodes.add(treeRootNode);
        }
        return treeNodes;
    }
    
    /**
     *  递归-----构建子树形结构
     *  @param  pNode 根节点(顶级节点)
     *  @return 整棵树
     */
    public TreeNode buildChildTree(TreeNode pNode){
        List<TreeNode> childTree = new ArrayList<TreeNode>();
        // nodeList:所有节点集合(所有数据)
        for (TreeNode treeNode : nodeList) {
            // 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
            if (treeNode.getParentId().equals(pNode.getId())) {
                // 再递归进行判断当前节点的情况,调用自身方法
                childTree.add(buildChildTree(treeNode));
            }
        }
        // for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
        pNode.setChildren(childTree);
        return pNode;
    }

测试案例

java
/**
 *  TreeController 树控制层
 *  方式:传递所有数据集合作为参数,调用buildTree()构建树形。
 */
@RestController
@RequestMapping("/tree")
public class TreeController {
 
    @GetMapping("/treeTest")
    public AjaxResult treeTest(){
 
        // 模拟测试数据(通常为数据库的查询结果)
        List<TreeNode> treeNodeList = new ArrayList<>();
        treeNodeList.add(new TreeNode(1,0,"顶级节点A"));
        treeNodeList.add(new TreeNode(2,0,"顶级节点B"));
        treeNodeList.add(new TreeNode(3,1,"父节点是A"));
        treeNodeList.add(new TreeNode(4,2,"父节点是B"));
        treeNodeList.add(new TreeNode(5,2,"父节点是B"));
        treeNodeList.add(new TreeNode(6,3,"父节点的ID是3"));
 
        // 创建树形结构(数据集合作为参数)
        TreeBuild treeBuild = new TreeBuild(treeNodeList);
        // 原查询结果转换树形结构
        treeNodeList = treeBuild.buildTree();
        // AjaxResult:个人封装返回的结果体
        return AjaxResult.success("测试数据",treeNodeList);
    }
}

结果

java
{
    "msg”:“ 测试数据”,
    "code": 200,
    "data": [
            {
            "id": 1,
            "parentId": 0,
            "label":"顶级节点A",
            "children": [
            {
                    "id": 3,
                    "parentId": 1,
                    "label":“ 父节点是A"
                    "children": [
                        "id": 6,
                        "parentId": 3,
                        "label":“ 父节点的ID是3
                    }
                ]
            }
        ]
    }, 
    {
        "id": 2,
        "parentId": 0,
        "labe1":“ 顶级节点B",
        "children": [{
                "id": 4,
                "parentId": 2,
                "label":“ 父节点是B"
            },
            {
                "id": 5,
                "parentId": 2,
                "label":" 父节点是B
            }
        ]
    }
]
}

5.方法的重载

java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!
比如:System.out.println();
out 是 PrintStream 类型

5.1 方法重载的案例

java
class MyCalculator {
    //下面的四个 calculate 方法构成了重载
    //两个整数的和
    public int calculate(int n1, int n2) {
        System.out.println("calculate(int n1, int n2) 被调用");
        return n1 + n2;
    }

    //没有构成方法重载, 仍然是错误的,因为是方法的重复定义
    // public void calculate(int n1, int n2) {
    // System.out.println("calculate(int n1, int n2) 被调用");
    // int res = n1 + n2;
    // }
    //看看下面是否构成重载, 没有构成,而是方法的重复定义,就错了
    // public int calculate(int a1, int a2) {
    // System.out.println("calculate(int n1, int n2) 被调用");
    // return a1 + a2;
    // }
    //一个整数,一个 double 的和
    public double calculate(int n1, double n2) {
        return n1 + n2;
    }

    //一个 double ,一个 Int 和
    public double calculate(double n1, int n2) {
        System.out.println("calculate(double n1, int n2) 被调用..");
        return n1 + n2;
    }

    //三个 int 的和
    public int calculate(int n1, int n2, int n3) {
        return n1 + n2 + n2;
    }
}

5.2 方法重载的细节

  1. 方法名: 必须相同
  2. 形参列表: 必须不同(形参列表或个数或顺序,至少有一样不同,参数名无要求)
  3. 返回类型: 无要求

6.可变参数

java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现

java
访问修饰符 返回类型 方法名(数据类型... 形参名) {
}

6.1 案例

编写一个方法sum,可以计算2个数的和,3个数的和,4,5....

java
//可以计算 2 个数的和,3 个数的和 , 4. 5, 。。
//可以使用方法重载
public int sum(int n1, int n2) {//2 个数的和
    return n1 + n2;
}
public int sum(int n1, int n2, int n3) {//3 个数的和
    return n1 + n2 + n3;
}
public int sum(int n1, int n2, int n3, int n4) {//4 个数的和
    return n1 + n2 + n3 + n4;
}

//使用可变参数
//1. int... 表示接受的是可变参数,类型是 int ,即可以接收多个 int(0-多)
//2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
//3. 遍历 nums 求和即可
public int sum(int... nums) {
    //System.out.println("接收的参数个数=" + nums.length);
    int res = 0;
    for(int i = 0; i < nums.length; i++) {
        res += nums[i];
    }
    return res;
}

6.2 注意事项

  1. 可变参数的实参可以是0个或是任意多个
  2. 可变参数的实参可以是数组
  3. 可变参数的本质就是数组
  4. 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
  5. 一个形参列表中只能出现一个可变参数

7.作用域

7.1 基本使用

  1. 在java编程中,主要的变量就是属性(成员变量)和局部变量
  2. 我们说的局部变量一般是指在成员方法中定义的变量。
  3. java中作用域的分类 全局变量: 也就是属性,作用域为整个类体
    局部变量: 也就是除了属性之外的其他变量,作用域为定义它的代码块中
  4. 全局变量(属性)可以不赋值,直接使用,因为有默认值。局部变量必须赋值后才能使用,因为没有默认值。
java
class Cat {
    //全局变量:也就是属性,作用域为整个类体 Cat 类:cry eat 等方法使用属性
    //属性在定义时,可以直接赋值
    int age = 10; //指定的值是 10
    //全局变量(属性)可以不赋值,直接使用,因为有默认值,
    double weight; //默认值是 0.0
    public void hi() {
        //局部变量必须赋值后,才能使用,因为没有默认值
        int num = 1;
        String address = "北京的猫";
        System.out.println("num=" + num);
        System.out.println("address=" + address);
        System.out.println("weight=" + weight);//属性
    }
    public void cry() {
        //1. 局部变量一般是指在成员方法中定义的变量
        //2. n 和 name 就是局部变量
        //3. n 和 name 的作用域在 cry 方法中
        int n = 10;
        String name = "jack";
        System.out.println("在 cry 中使用属性 age=" + age);
    }
    public void eat() {
    System.out.println("在 eat 中使用属性 age=" + age);
    //System.out.println("在 eat 中使用 cry 的变量 name=" + name);//错误
    }
}

7.2 注意事项和使用细节

  1. 属性和局部变量可以重名,访问时遵循就近原则
  2. 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
  3. 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。
  4. 作用域范围不同 全局变量(属性): 可以被本类使用,或其他类使用(通过对象调用) 局部变量: 只能在本类中对应的方法中使用
  5. 修饰符不同 全局变量(属性)可以加修饰符 局部变量不可以加修饰符

8.构造方法/构造器

8.1 基本介绍

构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:

  1. 方法名和类名相同
  2. 没有返回值
  3. 在创建对象时,系统会自动调用该类的构造器完成对象的初始化

8.2 使用方法

java
[修饰符] 方法名(形参列表){
方法体;
}
  1. 构造器的修饰符可以默认,也可以是public protected private
  2. 构造器没有返回值
  3. 方法名和类名必须一样
  4. 参数列表和成员方法一样的规则
  5. 构造器的调用由系统完成

8.3 基本使用

java
public class ConstructorTest {
    public static void main(String[] args) {

        person jack = new person("jack", 18);
        System.out.println(jack.age);
        System.out.println(jack.name);
    }
}

class person{
    String name;
    int age;

    public person(String pName, int pAge){
        System.out.println("person constructor");
        name = pName;
        age = pAge;
    }
}

img_101.png

8.4 注意事项和使用细节

  1. 一个类可以定义多个不同的构造器,即构造器重载 比如: 我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄
  2. 构造器名和类名要相同
  3. 构造器没有返回值
  4. 构造器是完成对象的初始化,不是创建对象
  5. 在创建对象时,系统自动调用该类的构造方法
  6. 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如Dog(){}。使用javap反编译指令可以查看
  7. 一旦定义了自己的构造器,默认构造器就被覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下。

9.javap的使用

  1. javap是jdk提供的一个命令行工具,javap能对给定的class文件提供的字节代码进行反编译
  2. 通过它,可以对照源代码和字节码,从而了解很多编译器内部的工作,对更加深入的理解如何提高程序执行效率等问题有极大帮助
  3. 使用格式
bash
# 反汇编并显示字节码指令
javap -c MyClass

# 显示所有信息(包括私有成员)
javap -p -v MyClass

# 显示静态常量
javap -constants MyClass

以下是 javap 工具常用的命令及其说明:

命令选项说明
-help显示 javap 的帮助信息
-version显示 javap 的版本信息
-v-verbose输出附加信息(如行号、局部变量表等)
-l输出行号和局部变量表
-public仅显示公共类和成员(默认)
-protected显示受保护/公共类和成员
-package显示包/受保护/公共类和成员(默认)
-p-private显示所有类和成员(包括私有)
-c反汇编代码,显示字节码指令
-s输出内部类型签名
-sysinfo显示类的系统信息(路径、大小、日期、MD5 哈希等)
-constants显示静态最终常量
-classpath <path>指定查找类文件的路径
-bootclasspath <path>覆盖引导类加载器的位置

10.对象创建的流程分析

java
class Person{
    int age = 90;
    String name;
    Person(String n,int a){
        name = n;
        age = a;
    }
}
Person p = new Person("小倩",20);

流程分析

  1. 加载Person类信息(Person.class),只会加载一次
  2. 在堆中分配空间(地址)
  3. 完成对象初始化 默认初始化age=0 name=null 显示初始化age=90 name=null 构造器初始化age=20 name="小倩"
  4. 在对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用) img_102.png

11.this关键字

11.1 this的引出与概述

将构造器中的dName和dAge改成name和age之后,再次通过该构造器初始化对象,就会发现两个参数变成null和0了,就是因为改完名之后,由于就近原则,现在的age和name是该类没有赋值的age和name,所以就需要this关键字 img_103.png

什么是this
java虚拟机会给每个对象分配一个this,代表当前对象。不同的对象的this指代的不同 img_104.png

使用this解决赋值问题

java
class person{
    String name;
    int age;

    public person(String name, int age){
        System.out.println("person constructor");
        this.name = name;
        this.age = age;
    }
}

11.2 this的深入理解

img_105.pngimg_106.png 由于传进该构造方法的参数名本身要赋值给的类的成员变量值重名,所以在赋值的时候,等号左面的变量找的是该方法自己的局部变量,这样写相当于在该方法内,自己给自己的局部变量再改了一遍相同的值,由于就近原则,所以在走完该构造方法该方法出栈之后,局部变量就被销毁,对象中的属性值还是空的 img_107.png 如果构造器形参列表的属性名跟该类的属性名不相同,那么等号左面的name就代表着该类的成员变量,是给对象的属性赋值,所以堆中的person对象的两个属性就被赋值了,即使运行完构造方法弹出栈,形参列表局部变量消失,但是对象还存在,所以在主方法栈中在调用person对象的属性是有值得 img_108.png 在该类的构造方法中使用this.属性其实就相当于在main方法中使用jack.属性的效果是一样的,因为在new的时候是先将类的基本信息加载好的,里面的属性都是在堆中存在且有默认值的,所以在调用构造方法初始化对象的时候,使用this.属性就是调用堆中的属性给其赋值,所以this.name代表的是堆中的person对象的那么属性,而name是该方法的形参局部变量的name,所以可以成功赋值,而且this的hashcode和person对象的hascode值是一样的,所有可以判断this和main中的person对象指的是同一个对象 img_109.pngimg_110.png

11.3 this关键字的使用细节

  1. this 关键字可以用来访问本类的属性、方法、构造器
  2. this 用于区分当前类的属性和局部变量
  3. 访问成员方法的语法: this.方法名(参数列表);
  4. 访问构造器语法: this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一 条语句)
  5. this 不能在类定义的外部使用,只能在类定义的方法中使用。

11.4 this的使用案例

java
public class ConstructorTest {
    public static void main(String[] args) {

        person jack = new person("jack", 18);
        person tom = new person("tom", 20);
        System.out.println(jack.ComparePerson(tom));
    }
}

class person{
    String name;
    int age;

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

    public Boolean ComparePerson(person person) {

        //this代表对象jack,谁调用代表谁,要是tom待用就代表tom对象,传进来的参数代表tom
        if(this.name.equals(person.name) && this.age == person.age){
            return true;
        }else {
            return false;
        }
    }

    }

img_111.png

Released under the MIT License.