JAVA(4)学习笔记:JVM虚拟机上的栈、大驼峰命名法和小驼峰命名法、实参和形参、重载方法、调用栈、递归练习(汉诺塔+斐波那契数列)、数组的定义、数组的初始化、增强for循环。

接上次的博客:JAVA学习(3)——知识整理以及一些简单程序(猜数字游戏、求各种自幂数、求出一个数字的二进制位中1的个数、获取一个数二进制序列中所有的偶数位和奇数位、求公约数的多种实现方式、输入密码程序)_di-Dora的博客-CSDN博客

目录

JVM虚拟机上的栈 

大驼峰命名法和小驼峰命名法都是命名规范,常用于编程中命名变量、类、方法等标识符。

快捷键:fac ( ) . sout + 回车--------> System.out.println(fac ( ) )

JAVA中,实参的值永远是赋值给形参的,它们本质上是两个实体。

重载方法和继承是面向对象编程中的两个重要概念。

javap-c和javap-v的区别:

javap是一个Java类反编译工具,可以将.class文件中的字节码文件反编译为Java源代码。它有两个常用的选项-c和-v。

调用栈(Call Stack),也称为执行栈(Execution Stack)、控制栈(Control Stack)或运行时堆栈(Runtime Stack),是一种用于实现子程序调用和异常处理的计算机数据结构。在 Java 中,调用栈用于实现方法调用和异常处理的基本机制之一。

方法调用的时候,会有一个“栈”这样的内存空间描述当前的调用关系,成为调用栈:

栈溢出错误:​编辑

递归练习:

数组的定义:

数组的初始化主要分为动态初始化和静态初始化。

数组越界异常: 

算术异常(ArithmeticException)是Java中的一个运行时异常,通常在以下情况下抛出:

for each 循环,又称“增强for循环”:


JVM虚拟机上的栈 

函数调用前,方法存在JVM的方法区里,调用该方法时才会放到栈里。

在Java中,每当一个方法被调用时,JVM会在当前线程的栈上创建一个新的栈帧,用于保存方法的局部变量、操作数栈、返回地址和异常处理信息等。这个栈帧称为方法栈帧,也叫函数栈帧,它包含了Java程序执行过程中的一些重要信息,用于支持方法调用和返回。

方法栈帧是按照一定规则被组织在Java虚拟机栈中的。当一个线程开始执行一个方法时,会在Java虚拟机栈中分配一个新的栈帧,栈帧包含了该方法的局部变量表、操作数栈、动态链接信息、方法返回地址和附加信息等。在方法执行过程中,每当遇到一个方法调用时,JVM会在栈上再创建一个新的栈帧,用于保存被调用方法的状态和信息。当这个被调用方法执行完成后,它的栈帧会被弹出,当前方法的状态恢复,并继续执行。

Java虚拟机栈是线程私有的,每个线程都拥有自己的Java虚拟机栈。在Java虚拟机中,栈的大小是可以动态调整的,但是在实际应用中,应该尽量避免过深的栈层级,因为过深的栈层级可能会导致栈溢出的问题。

对于Java虚拟机来说,方法的字节码是存放在方法区(Method Area)中的。在方法调用时,Java虚拟机,会为该方法创建一个栈帧(Stack Frame),其中包含了方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。这个栈帧会被存放在Java虚拟机栈(JVM Stack)中,用于执行该方法的字节码指令。

当方法执行完毕后,Java虚拟机会弹出该方法的栈帧,同时也会释放该栈帧所占用的内存空间。此时,栈帧中的局部变量表、操作数栈等信息也会被清空。

当一个方法被调用时,该方法的代码并没有真正执行,它只是一个定义在类中的代码块。当程序执行到调用该方法的代码行时,会在调用线程的栈中为该方法创建一个新的栈帧,并将栈帧压入栈顶。

在该方法执行过程中,Java虚拟机会为该方法创建一个栈帧,其中包含了方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。这个栈帧会被存放在Java虚拟机栈中,用于执行该方法的字节码指令。

如果该方法有返回值,返回值将被压入调用方法的栈帧中。如果该方法在执行过程中抛出了异常,异常信息也会被抛给调用方法的栈帧中的异常处理程序。

当方法执行完毕后,Java虚拟机会弹出该方法的栈帧,同时也会释放该栈帧所占用的内存空间。此时,栈帧中的局部变量表、操作数栈等信息也会被清空。

在Java中,方法区是用于存储类的信息、常量池、静态变量、即时编译器编译后的代码等。它在JVM启动时被创建,并且它的内存空间是不会随着方法的调用而被销毁的,只有当JVM退出时才会被销毁。因此,当一个方法被调用完毕后,该方法所占用的栈帧会被销毁,但是方法区中的相关信息仍然存在,供其他方法调用使用。

总之,栈帧是Java虚拟机支持方法调用和返回的重要组成部分,它用于保存方法的状态和信息,并提供了一种方便的方式来实现方法调用和返回。方法调用栈是Java中实现方法调用和异常处理的基本机制之一,它可以保证方法的执行顺序和正确性,并提供了异常处理机制,让程序更加健壮和稳定。

大驼峰命名法和小驼峰命名法都是命名规范,常用于编程中命名变量、类、方法等标识符。

大驼峰命名法(也称为帕斯卡命名法)是一种命名方式,其中每个单词的第一个字母都大写,单词之间没有分隔符。例如:MyClass, MyVariable, MyMethod 等。通常,大驼峰命名法用于命名类和接口。

小驼峰命名法(也称为驼峰式命名法)是一种命名方式,其中第一个单词的第一个字母小写(如果只有一个单词,就全部小写),之后每个单词的第一个字母都大写,单词之间没有分隔符。例如:myVariable, myMethod 等。通常,小驼峰命名法用于命名变量和方法。

在Java编程中,推荐使用大驼峰命名法来命名类和接口,使用小驼峰命名法来命名变量和方法。这有助于提高代码的可读性和可维护性。

快捷键:fac ( ) . sout + 回车--------> System.out.println(fac ( ) )

JAVA中,实参的值永远是赋值给形参的,它们本质上是两个实体。

重载方法和继承是面向对象编程中的两个重要概念。

重载方法(Overloading)是指在同一个类中,方法名相同但参数类型或参数个数不同的多个方法,它们具有相同的方法名,但是方法的参数列表不同。在调用重载方法时,根据传入参数的不同,自动匹配相应的方法进行调用。

继承(Inheritance)是指一个类从另一个类获取属性和方法的过程,子类可以继承父类的属性和方法,并且可以根据需要扩展或修改父类的行为。子类通过继承可以重用父类的代码,并且可以实现代码的复用和可维护性。

两者的区别在于,重载方法是在同一个类中定义多个方法,而继承是在不同的类之间进行的。重载方法主要用于参数不同但功能类似的方法,通过方法名和参数列表的不同来区分不同的方法。继承主要用于子类扩展或修改父类的行为,并且可以重用父类的代码。在重载方法中,同一个方法名的多个方法是相互独立的,而在继承中,子类是基于父类的基础上进行扩展和修改的。

另外,重载方法是编译时多态,即在编译阶段根据传入的参数类型或数量进行方法的选择,而继承是运行时多态,即在运行时根据对象的实际类型进行方法的选择。

JAVA中值的交换:

在Java中,可以通过调用方法来交换两个数的值。可以使用传统的交换方法,也可以使用Java 8中提供的lambda表达式来实现。

传统交换方法:

public static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

调用该方法:

int x = 10;
int y = 20;
swap(x, y);
System.out.println(x + " " + y); // 输出10 20

注意,由于Java中只有值传递,因此上述方法并不能成功交换变量的值。可以通过传递变量的引用来实现:

public static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

调用该方法:

int[] arr = new int[]{10, 20};
swap(arr, 0, 1);
System.out.println(arr[0] + " " + arr[1]); // 输出20 10  //中间输出一个空格

使用lambda表达式实现:

IntUnaryOperator swap = i -> i == 0 ? 1 : 0;

int x = 10;
int y = 20;

x = x + y;
y = x - y;
x = x - y;

System.out.println(x + " " + y); // 输出20 10

以上是两种常用的实现方式,可以根据实际需求选择合适的方式。

方法签名:经过编译器编译修改过之后的方法的最终的名字。具体方式:方法全路径名+参数列表+返回值类型,

javap-c和javap-v的区别:

javap是一个Java类反编译工具,可以将.class文件中的字节码文件反编译为Java源代码。它有两个常用的选项-c和-v。

如下这段代码:

public class TestMethod {
    public static void main(String[] args) {
        add(1,2);
        add(1.5,2.5);
    }
    public static  int add(int x,int y){
        return x+y;
    }
    public  static  double add(double x,double y){
        return x+y;
    }
}

上述代码经过编译之后,然后使用JDK自带的javap反汇编工具查看,具体操作:
1.先对工程进行编译生成class字节码文件
2在控制台中进入到要查看的class所在的目录
3.输入:javap-v 字节码文件名字即可

特殊字符数据类型
Vvoid
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
[数组(以[开头,配合其他的特殊字符,表述对应数据类型的数组,几个[表述几维数组)
L引用类型,以L开头,以;结尾,中间是引用类型的全类名

javap -c会显示每个方法的字节码指令,并且会将它们与Java代码中的源代码行号相匹配。这使得我们可以比较源代码和字节码,从而更好地理解Java程序的内部工作原理。例如,javap -c MyClass会显示MyClass类中所有方法的字节码。

javap -v选项则提供了更多的详细信息,例如常量池、类名、方法签名、异常表等等。这些信息对于理解类的内部结构和实现细节非常有帮助。例如,javap -v MyClass会显示MyClass类的所有详细信息。

总之,javap -c主要用于查看Java代码和字节码之间的关系,而javap -v则提供了更多的详细信息以帮助我们理解Java类的内部结构和实现细节。

调用栈(Call Stack),也称为执行栈(Execution Stack)、控制栈(Control Stack)或运行时堆栈(Runtime Stack),是一种用于实现子程序调用和异常处理的计算机数据结构。在 Java 中,调用栈用于实现方法调用和异常处理的基本机制之一。

方法调用的时候,会有一个“栈”这样的内存空间描述当前的调用关系,成为调用栈:

调用栈是一种后进先出(LIFO)的数据结构,它存储了正在执行的方法以及每个方法所使用的局部变量、操作数栈等信息。每一次的方法调用就称为一个“栈帧”。在方法调用时,Java 虚拟机会为该方法创建一个栈帧(Stack Frame),其中包含了方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。这个栈帧会被存放在调用栈中,用于执行该方法的字节码指令。

当方法执行完毕后,Java 虚拟机会弹出该方法的栈帧,同时也会释放该栈帧所占用的内存空间。此时,栈帧中的局部变量表、操作数栈等信息也会被清空。

调用栈在程序运行过程中扮演着重要的角色。通过调用栈,程序可以实现方法之间的相互调用,实现程序逻辑的顺序执行。同时,调用栈也是实现异常处理的重要工具。当程序执行过程中发生异常时,Java 虚拟机会在调用栈中寻找相应的异常处理程序,从而实现异常处理。

栈溢出错误:

递归出现栈溢出错误,通常是由于递归没有终止条件递归深度太深导致的。

在递归过程中,每次递归都会在栈中创建一个新的栈帧,用于保存当前的状态。如果递归没有终止条件,那么栈中的栈帧会不断增加,直到超过栈的最大容量,从而导致栈溢出错误。

另外,递归深度(递归深度指递归函数调用自身的次数,也就是递归函数形成的递归层数。每一次递归调用都会在函数调用栈中压入一个新的栈帧,当递归次数过多时,函数调用栈会达到最大限制,导致栈溢出错误。因此,在进行递归操作时,需要注意递归深度是否会超过系统允许的最大深度,避免出现栈溢出的错误。)过大也会导致栈溢出错误。每个线程的栈大小是有限制的,当递归的深度超过栈的容量时,也会抛出栈溢出错误。

因此,在编写递归程序时,一定要注意设置好递归终止条件,避免递归深度过大导致栈溢出错误。

递归练习:

写一个递归方法,输入一个非负整数,返回组成它的数字之和:

public static int func( int n ) {
    if(n<10) {
        return n;
     }
     int tmp= func(n/10)+n%10;
     return tmp;
} 

 斐波那契数列:

 public static int count = 0;
 public static int fib(int n) {
       if(n == 1) {
            count++;
            return 0;
        }
        if(n == 2) {
            count++;
            return 1;
        }
        return fib(n-1) + fib(n-2);
    }


    public static void main(String[] args) {
        System.out.println(fib(1));
        System.out.println(fib(2));
        System.out.println(fib(3));
        System.out.println(fib(4));
        System.out.println(fib(40));
        System.out.println(count);
    }

 最好不要用递归来写斐波那契数列,因为重复计算量太大了,效率低。

现在我们用循环(递归)的方法来实现:

public static int fib(int n) {
    if(n==1){
        return 0;
    }
    if(n==2){
        return 1;
    }
    int f1=0;
    int f2=1;
    int f3=-1;
    for(int i=3;i<=n;i++){
        f3=f1+f2;
        f1=f2;
        f2=f3;
    }
    return f3;
}

求1!+2!+3!+4!+........+n!的和:

public static void main(String[] args) {
    System.out.println(m);
    Scanner scan =new Scanner(System.in);
    System.out.println("输入n:>");
    int n=scan.nextInt();
    int k=fun(n);
    System.out.println(k);
}
public static int funFac(int n) {
    if(n == 1) {
        return 1;
    }
    int tmp = n * funFac(n-1);
    return tmp;
}
//求1!+2!+3!+4!+........+n!的和
public static int fun(int n) {
    int sum=0;
    if(n==1){
        return 1;
    }
    else{
        return funFac(n)+fun(n-1);
    }
}

汉诺塔问题是一个经典的递归问题,问题描述如下:假设有三根柱子A、B、C,其中A柱子上有N个盘子,从上到下依次变大,现要求将这N个盘子从A柱子移动到C柱子上,每次只能移动一个盘子,并且大盘子不能放在小盘子上面。

实现递归求解汉诺塔问题的过程如下:

public class HanoiTower {
    public static void hanoi(int n, char a, char b, char c) {
        if (n == 1) {
            System.out.println("Move disk " + n + " from " + a + " to " + c);
        } else {
            hanoi(n - 1, a, c, b);
            System.out.println("Move disk " + n + " from " + a + " to " + c);
            hanoi(n - 1, b, a, c);
        }
    }

    public static void main(String[] args) {
        int n = 3;
        hanoi(n, 'A', 'B', 'C');
    }
}

在上述代码中,hanoi方法的参数n表示当前需要移动的盘子数,a、b、c表示三根柱子的编号。如果n等于1,则直接将第一个柱子上的盘子移动到第三个柱子上,否则先将n-1个盘子从第一个柱子通过第三个柱子移动到第二个柱子上,然后将第n个盘子从第一个柱子移动到第三个柱子上,最后将n-1个盘子从第二个柱子通过第一个柱子移动到第三个柱子上。以此类推。

在main方法中,我们将需要移动的盘子数设置为3,并将三根柱子的编号分别设置为A、B、C。执行hanoi方法后,程序会输出每次移动的盘子编号和移动的起始柱子和目标柱子,直到所有盘子都移动到第三个柱子上,输出的结果如下:

数组的定义:

在Java中,数组是一个对象,而不是一个类。 Java中的数组是一个特殊的对象,用于存储同一类型的元素的固定大小的有序集合。每个数组都有一个长度,它确定了可以存储在数组中的元素的数量。Java中的数组是从Object类继承而来的,因此它们具有Object类的所有方法和属性。但是,数组的实现方式是不同于普通类的,Java在底层实现了一些机制来支持数组的操作和管理。

public static void main1(String[] args) {

    int [] arr = {1,2,3,4,5};  //定义时同时初始化,语法的精简

    int [] arr2 = new int [10];//与C语言不同的是,int这里默认是10个0;
   
    int [] arr3 = new int [10] {1,2,3,4,5,6,7} //10不可以写!!!
    int [] arr3 = new int [] {1,2,3,4,5,6,7} //改为这个

    System.out.println(array1);
    System.out.println(array2);
    System.out.println(array3);
    
    //输出后,发现这里存储的是"地址"——其实不是一个真实的地址,而是一个哈希值,唯一的
    //[ I @1b6d3586  
    //这些数组后面赋值的这些内存,放在的是 堆 上
    //我们把存储了这些类似地址的变量叫做"引用变量"。
}

数组的初始化主要分为动态初始化和静态初始化。

1、动态初始化:在创建数组时,直接指定数组中元素的个数:

int[] array = new int[10];

2、静态初始化:在创建数组时不直接指定数组元素个数,而是直接将具体的数据内容进行指定。

语法格式:int[] 数组名称 = { data1, data2, data3, ... , data n };

静态初始化虽然没有指定数组的长度,但是编译器在编译时会根据{}中元素个数该确定数组的长度。

虽然Java的数组创建也可以像C语言那样去写,但是不推荐!

静态和动态初始化可以分为两部分来写,但是省略格式就不可以。

    int[] array4;
    array4 = new int[10];

   int[] array5;
   array5 = new int[]{1,2,3,4,5};

   /* int[] array6;
    array6 = {1,2,3,4,5,6,7,8,9,0};*/
   //此处只能在定义的同时直接赋值
    public static void main2(String[] args) {
        char[] array = new char[10];            //默认为0

        double[] array2 = new double[10];       //默认为0.0

        boolean[] array3 = new boolean[10];     //默认为false

        String[] strings = new String[10];      //默认为null
    }
类型默认值
byte0
short0
int0
long0
float0.0f
double0.0
char/u0000
booleanfalse

数组越界异常: 



public class Test2 {

    public static void main(String[] args) {
        int[] array1 = {1,2,3,4};
        System.out.println(array1[0]);
        System.out.println(array1[1]);
        System.out.println(array1[2]);
        System.out.println(array1[3]);
        //System.out.println(array1[4]);

    }

}

算术异常(ArithmeticException)是Java中的一个运行时异常,通常在以下情况下抛出:

整数除以零;
取模时除数为零;
尝试计算一个负数的平方根;
在一个整数类型中尝试使用过大或过小的值进行计算。
当算术异常被抛出时,它表示算术运算无法正确执行。在处理算术异常时,应该注意避免除以零的情况,以及在使用整数时要谨慎处理边界情况,以避免出现不可预测的结果。

for each 循环,又称“增强for循环”:

for each 循环,又称“增强for循环”,是 Java 5 引入的一种新的循环方式,用于遍历数组或集合中的元素,其语法格式为:

for (元素类型 变量名 : 数组或集合) {
    // 循环体
}

其中,元素类型是指数组或集合中元素的数据类型,变量名是循环中每次迭代的元素值所存储的变量名。在循环体内可以通过变量名来操作数组或集合中的元素。

使用 for each 循环的好处是可以简化代码,并且使代码更加易读。同时,由于 for each 循环底层实现时使用了迭代器(Iterator)对象,因此可以保证在遍历集合时不会出现 ConcurrentModificationException 异常,避免了使用普通 for 循环时可能出现的线程安全问题。

array1 . length:

在 Java 中,length 是数组的一个属性,而不是方法。因此,我们可以使用 arrayName.length 来获取数组的长度,其中 arrayName 是数组的名称。这个属性是由 Java 编程语言定义的,Java 数组在创建时就已经确定了长度,因此可以通过 length 属性获取数组的长度。

System.out.println(array1.length);
for (int i = 0; i < array1.length; i++) {
    System.out.print(array1[i]+" ");
    }
    System.out.println();
//for each 循环 、 增强for循环
  for (int x : array1) {
       System.out.print(x+" ");
       }
       System.out.println();

根据对下标的依赖性来选择用for循环还是for each循环:

增强 for 循环的局限性主要在于无法访问循环索引以及无法在循环中修改数组或集合的大小。

对于需要修改数组或集合大小的情况,通常需要使用传统的 for 循环。

例如,假设有一个数组,需要对其中的元素进行修改,并且有些元素需要删除或添加。使用增强 for 循环无法删除或添加元素,只能修改元素的值。而传统的 for 循环可以通过修改数组下标来实现删除或添加元素的操作。例如,以下代码演示了如何使用传统的 for 循环删除数组中的某个元素:

int[] arr = {1, 2, 3, 4, 5};
int target = 3;
int[] newArr = new int[arr.length - 1];
int j = 0;

for (int i = 0; i < arr.length; i++) {
    if (arr[i] != target) {
        newArr[j++] = arr[i];
    }
}

// newArr = {1, 2, 4, 5}

在上面的例子中,需要删除数组中的元素 3。使用传统的 for 循环,先创建一个新的数组 newArr,长度为原数组长度减 1,然后遍历原数组,如果元素不等于 3,则将其添加到新数组中。最终得到的新数组就是删除了元素 3 的结果。

另外,增强 for 循环只适用于遍历数组和实现了 Iterable 接口的集合类型,对于其他类型的集合(例如 Map)则不适用。在这种情况下,需要使用迭代器或传统的 for 循环来遍历集合。

总之,增强 for 循环适用于遍历数组和集合,并且在遍历时不需要修改数组或集合的大小(例如,对于数组,我们不能在遍历时增加或删除元素,否则会导致程序运行时异常;对于集合,虽然可以使用迭代器遍历时修改集合的大小,但是在增强 for 循环中仍然不建议这样做。这是因为增强 for 循环在编译时会将数组或集合转换为迭代器,而迭代器在使用时会记录其遍历的位置和状态,如果在遍历时修改了数组或集合的大小,会导致迭代器记录的位置和状态不正确,从而引发运行时异常。因此,增强 for 循环更适合用于遍历数组和集合时读取元素的值,而不是修改数组或集合。如果需要修改数组或集合的元素或大小,,或者需要访问循环索引,或者需要遍历其他类型的集合(例如 Map),建议使用传统的 for 循环或其他遍历方式,并在循环中使用索引或迭代器来访问和修改元素。)。

增强 for 循环是用于遍历数组和集合的简化语法。这种循环的语法结构简单,易于阅读和书写,适用于遍历数组和集合等数据结构。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值