查看笔记前,建议您先查阅:笔记说明

Java Note one


Day01

IDEA快捷键

取消Double shift搜索,改为CTRL + n;

查看类的方法(成员)列表:CTRL+F12

选择方法后,CTRL+B跟进方法,按住CTRL加鼠标也行。

快速生成变量:CTRL + ALT + V

选中代码后环绕(if、try catch、while):CTRL + ALT + T

抽取方法CTRL + ALT + M

方法提示实参:CTRL + P;

跳转至源代码,方法定义处:CTRL + B or CTRL + 鼠标左键

上一步返回,前进:CTRL + ALT + 左键 (右键);


Day02

基本数据类型

数据类型 关键字 内存占用 取值范围 备注
整数 byte 1 负的2的7次方 ~ 2的7次方-1(-128~127) 计算时会被当做int
short 2 负的2的15次方 ~ 2的15次方-1(-32768~32767) 计算时会被当做int
int 4 负的2的31次方 ~ 2的31次方-1
long 8 负的2的63次方 ~ 2的63次方-1 记得加L(手机号)
浮点数 float 4 1.401298e-45 ~ 3.402823e+38 记得加f
double 8 4.9000000e-324 ~ 1.797693e+308 默认
字符 char 2 0-65535 非负
布尔 boolean 1 true,false

int i = 077, 会被认定为八进制,从而 i = 63


print 和 println 和 printf

System.out.print(“内容”); 输出内容不换行

System.out.println(“内容”); 输出内容并换行

System.out.printf();该输出语句支持%s占位符

public class Test {
    public static void main(String[] args) {
        //两部分参数:
        //第一部分参数:要输出的内容%s(占位)
        //第二部分参数:填充的数据

        System.out.printf("你好啊%s","张三");//用张三填充第一个%s
        System.out.println();//换行
        System.out.printf("%s你好啊%s","张三","李四");//用张三填充第一个%s,李四填充第二个%s
    }
}

%s少填充会保错:MissingFormatArgumentException,多填充不会报。

核武器:

package day29;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Date;

public class PrintStreamDemo2 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("a.txt");

        //% n表示换行
        ps.printf("我叫%s %n", "阿玮");           // 我叫阿玮
        ps.printf("%s喜欢%s %n", "阿珍", "阿强");           // 阿珍喜欢阿强
        ps.printf("字母H的大写:%c %n", 'H');           // 字母H的大写:H
        ps.printf("8>3的结果是:%b %n", 8 > 3);           // 8>3的结果是:true
        ps.printf("100的一半是:%d %n", 100 / 2);           // 100的一半是:50
        ps.printf("100的16进制数是:%x %n", 100);           // 100的16进制数是:64
        ps.printf("100的8进制数是:%o %n", 100);           // 100的8进制数是:144
        ps.printf("50元的书打8.5折扣是:%f元%n", 50 * 0.85);           // 50元的书打8.5折扣是:42.500000元
        ps.printf("计算的结果转16进制:%a %n", 50 * 0.85);           // 计算的结果转16进制:0x1.54p5
        ps.printf("计算的结果转科学计数法表示:%e %n", 50 * 0.85);           // 计算的结果转科学计数法表示:4.250000e+01
        ps.printf("计算的结果转成指数和浮点数,结果的长度较短的是:%g %n", 50 * 0.85);           // 计算的结果转成指数和浮点数,结果的长度较短的是:42.5000
        ps.printf("带有百分号的符号表示法,以百分之85为例:%d%% %n", 85);           // 带有百分号的符号表示法,以百分之85为例:85%
        ps.println("---------------------");

        double num1 = 1.0;
        ps.printf("num: %.4g %n", num1);           // num: 1.000
        ps.printf("num: %.5g %n", num1);           // num: 1.0000
        ps.printf("num: %.6g %n", num1);           // num: 1.00000

        float num2 = 1.0F;
        ps.printf("num: %.4f %n", num2);           // num: 1.0000
        ps.printf("num: %.5f %n", num2);           // num: 1.00000
        ps.printf("num: %.6f %n", num2);           // num: 1.000000
        ps.println("---------------------");

        ps.printf("数字前面带有0的表示方式:%03d %n", 7);           // 数字前面带有0的表示方式:007
        ps.printf("数字前面带有0的表示方式:%04d %n", 7);           // 数字前面带有0的表示方式:0007
        ps.printf("数字前面带有空格的表示方式:% 8d %n", 7);           // 数字前面带有空格的表示方式:       7
        ps.printf("整数分组的效果是:%,d %n", 9989997);           // 整数分组的效果是:9,989,997
        ps.println("---------------------");

        //最终结果是10位,小数点后面是5位,不够在前面补空格,补满10位
        //如果实际数字小数点后面过长,但是只规定两位,会四舍五入
        //如果整数部分过长,超出规定的总长度,会以实际为准
        ps.printf("一本书的价格是:% 10.5f元%n", 49.8);           //一本书的价格是:  49.80000元
        ps.printf("%(f%n", -76.04);           // (76.040000)

        //%f,默认小数点后面7位,
        //<,表示采取跟前面一样的内容
        ps.printf("%f和%3.2f %n", 86.04, 1.789651);           // 86.040000和1.79
        ps.printf("%f和%<3.2f %n", 86.04, 1.789651);           // 86.040000和86.04
        ps.println("---------------------");           //

        Date date = new Date();
        // %t 表示时间,但是不能单独出现,要指定时间的格式
        // %tc 周二 12月 06 22:08:40 CST 2022
        // %tD 斜线隔开
        // %tF 冒号隔开(12小时制)
        // %tr 冒号隔开(24小时制)
        // %tT 冒号隔开(24小时制,带时分秒)
        ps.printf("全部日期和时间信息:%tc %n", date);           //全部日期和时间信息:周六 4月 29 22:42:14 CST 2023
        ps.printf("月/日/年格式:%tD %n", date);           //月/日/年格式:04/29/23
        ps.printf("年-月-日格式:%tF %n", date);           //年-月-日格式:2023-04-29
        ps.printf("HH:MM:SS PM格式(12时制):%tr %n", date);           //HH:MM:SS PM格式(12时制):10:42:14 下午
        ps.printf("HH:MM格式(24时制):%tR %n", date);           //HH:MM格式(24时制):22:42
        ps.printf("HH:MM:SS格式(24时制):%tT %n", date);           //HH:MM:SS格式(24时制):22:42:14

        System.out.println("---------------------");
        ps.printf("星期的简称:%ta %n", date);           //星期的简称:周六
        ps.printf("星期的全称:%tA %n", date);           //星期的全称:星期六
        ps.printf("英文月份简称:%tb %n", date);           //英文月份简称:4月
        ps.printf("英文月份全称:%tB %n", date);           //英文月份全称:四月
        ps.printf("年的前两位数字(不足两位前面补0):%tC %n", date);           //年的前两位数字(不足两位前面补0):20
        ps.printf("年的后两位数字(不足两位前面补0):%ty %n", date);           //年的后两位数字(不足两位前面补0):23
        ps.printf("一年中的第几天:%tj %n", date);           //一年中的第几天:119
        ps.printf("两位数字的月份(不足两位前面补0):%tm %n", date);           //两位数字的月份(不足两位前面补0):04
        ps.printf("两位数字的日(不足两位前面补0):%td %n", date);           //两位数字的日(不足两位前面补0):29
        ps.printf("月份的日(前面不补0):%te  %n", date);           //月份的日(前面不补0):29

        System.out.println("---------------------");
        ps.printf("两位数字24时制的小时(不足2位前面补0):%tH %n", date);           //两位数字24时制的小时(不足2位前面补0):22
        ps.printf("两位数字12时制的小时(不足2位前面补0):%tI %n", date);           //两位数字12时制的小时(不足2位前面补0):10
        ps.printf("两位数字24时制的小时(前面不补0):%tk %n", date);           //两位数字24时制的小时(前面不补0):22
        ps.printf("两位数字12时制的小时(前面不补0):%tl %n", date);           //两位数字12时制的小时(前面不补0):10
        ps.printf("两位数字的分钟(不足2位前面补0):%tM %n", date);           //两位数字的分钟(不足2位前面补0):42
        ps.printf("两位数字的秒(不足2位前面补0):%tS %n", date);           //两位数字的秒(不足2位前面补0):14
        ps.printf("三位数字的毫秒(不足3位前面补0):%tL %n", date);           //三位数字的毫秒(不足3位前面补0):308
        ps.printf("九位数字的毫秒数(不足9位前面补0):%tN %n", date);           //九位数字的毫秒数(不足9位前面补0):308000000
        ps.printf("小写字母的上午或下午标记(英):%tp %n", date);           //小写字母的上午或下午标记(英):下午
        ps.printf("小写字母的上午或下午标记(中):%tp %n", date);           //小写字母的上午或下午标记(中):下午
        ps.printf("相对于GMT的偏移量:%tz %n", date);           //相对于GMT的偏移量:+0800
        ps.printf("时区缩写字符串:%tZ%n", date);           //时区缩写字符串:CST
        ps.printf("1970-1-1 00:00:00 到现在所经过的秒数:%ts %n", date);           //1970-1-1 00:00:00 到现在所经过的秒数:1682779334
        ps.printf("1970-1-1 00:00:00 到现在所经过的毫秒数:%tQ %n", date);   // 1970-1-1 00:00:00 到现在所经过的毫秒数:1682779334308

        ps.close();
    }
}

System.err

// 控制台输出为红色
System.err.println("输入错误!请输入1-5以内的整数!");

文本块

JDK15之后新功能:

三个双引号,减少转义符号。

System.out.println("""
            "-------------欢迎来到Wahson学生管理系统----------------"
            1:添加学生
            2:删除学生
            3:修改学生
            4:查询学生
            5:退出
            请输入您的选择:""");
}

char

char转为int:

char与数字相加,底层转为int。所以输出的为ASCII.

char c = '2';
System.out.println(c + 0);  // 50    2的ASCII 为50

char 转为 数字

当我们用数字的字符串配合使用charAt()或toCharArray时,char存储的值是'1','2','3','4','5','6'这些,因此如果想要再转为数字,最快的方法就是用char值-48。(0->48, 1->49, 2->50)。

int强转为char,int将变成ASCII。

int i = 48;
char b = (char) i;
System.out.println(b);

char c = 48;
System.out.println(c);

char与数字相减转为int。

char a = '1';   // 1的 ASCII == 49;
int num = a - 48;   //  num == 1

char中文输出 ASCII

char a = '郑';
char b = '鄭';
System.out.println((int) a);  // 37073
System.out.println((int) b);  // 37165

char和String比较:

// 字符串1 和 字符1 是不一样的
char a = '1';

char转为字符:

String longText = "helloWorld";
char c = longText.charAt(1);
String result = String.valueOf(c);
System.out.println(result);  // e

A的ASCII:65, Z:90

a的ASCII:97, z:122

字符串与char互转:

String text1 = "你好罗辑";

char[] array1 = text1.toCharArray();
StringBuilder sb = new StringBuilder();
for (char c : array1) {
     sb.append((int) c).append(" ");
}
System.out.println(sb);

System.out.println("================================");

String value = "20320 22909 32599 36753";
String[] char1 = value.split(" ");

StringBuilder ssb = new StringBuilder();
for (String x: char1) {
  // not use Interger.valueof -> Integer!
   ssb.append((char) Integer.parseInt(x)).append(" ");
}
System.out.println(ssb);
System.out.println("================================");

\t

制表符,CMD填充到为8位,IDEA默认为4位。

System.out.println("name" + '\t' + "age");
System.out.println("Tom" + '\t' + "12");
System.out.println("\t".length());   // 1

IDEA输出结果:

name age
Tom 12


\r

来自第四份笔记中4.2.2使用字符数组读取。

System.out.print("你好啊:\n乔治华生66666");
System.out.print("\r");
System.out.print("Hello");
// 输出结果:
//你好啊:
//Hello
// 猜测:\r之后回到本行首,然后清理掉了本行数据重新print

用户输入 Scanner!!!

1)next()、nextLine():

可以接受任意数据,但是都会返回一个字符串。

比如:键盘录入abc,那么会把abc看做字符串返回。

​ 键盘录入123,那么会把123看做字符串返回。

2)nextInt():

​ 只能接受整数。

比如:键盘录入123,那么会把123当做int类型的整数返回。

​ 键盘录入小数或者其他字母,就会报错。

3)nextDouble():

​ 能接收整数和小数,但是都会看做小数返回。

​ 录入字母会报错。

Scanner sc = new Scanner(System.in);
int b = sc.nextInt();
String bb = sc.next();

方法底层细节 :

第一个细节:

next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回车,制表符其中一个就会停止接收数据。

第二个细节:

next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回车,制表符其中一个就会停止接收数据。但是这些符号 + 后面的数据还在内存中并没有接收。如果后面还有其他键盘录入的方法,会自动将这些数据接收。

Scanner sc = new Scanner(System.in);
String s1 = sc.next();
String s2 = sc.next();
System.out.println(s1);
System.out.println(s2);
//此时值键盘录入一次a b(注意a和b之间用空格隔开)
//那么第一个next();会接收a,a后面是空格,那么就停止,所以打印s1是a
//但是空格+b还在内存中。
//第二个next会去掉前面的空格,只接收b
//所以第二个s2打印出来是b

第三个细节:

nextLine()方法是把一整行全部接收完毕。

代码示例:

Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(s);
//键盘录入a b(注意a和b之间用空格隔开)
//那么nextLine不会过滤前面和后面的空格,会把这一整行数据全部接收完毕。

混用引起的后果

上面说的两套键盘录入不能混用,如果混用会有严重的后果。

代码示例:

Scanner sc = new Scanner(System.in);//①
int i = sc.nextInt();//②
String s = sc.nextLine();//③
System.out.println(i);//④
System.out.println(s);//⑤

当代码运行到第二行,会让我们键盘录入,此时录入123。

但是实际上我们录的是123+回车。

而nextInt是遇到空格,回车,制表符都会停止。

所以nextInt只能接受123,回车还在内存中没有被接收。

此时就被nextLine接收了。

所以,如果混用就会导致nextLine接收不到数据。

https://www.nowcoder.com/discuss/353147765045796864?sourceSSR=users

  1. nextLine()方法是读取一整行,以一个回车符作为结束标记停止扫描
  2. next() / nextInt() / nextDouble()等方法是,读取到第一个结束符【空格、回车、Tab键】作为结束标记停止扫描
  3. next()方法不会取走结束符,因此如果后面跟着nextLine(),则要先来个nextLine()先吞掉结束符,保证后面没事。
  4. next() / nextInt() / nextDouble()他们不会取走结束符号,把他们留在缓冲区;而nextLine()方***取走当前扫描行第一个非结束符之后、回车符之前的整行内容作为字符串进行返回,也就是可能会取走空格等,遇到回车结束【并不会取走回车】

结论(如何使用)

键盘录入分为两套:

  • next()、nextInt()、nextDouble()这三个配套使用。

如果用了这三个其中一个,就不要用nextLine()

  • nextLine()单独使用。

如果想要整数,那么先接收,再使用Integer.parseInt进行类型转换。

一个常见的bug

使用while(true)进行菜单编号sc.nextInt()选择,当用户输入字符时,会抛出异常,此时返回到while顶部的sc.nextInt()是不会吃掉bug的,会不停触发异常,无限进入try catch的异常处理分支,进入死循环。

在catch的异常处理分支内加入sc.nextLine()吃掉bug后,可正常工作。

还有一件事,建议这种菜单判断接收——字符串——,这样容易判断,default语句全吃,不会被sc.nextInt()抛异常。

        while (true) {
            try {
                showMenu();
                int code = 0;
                // 这里如果输入字符,如 a, 不会走到switch的default分支,而是抛出错误,进入catch分支,进入无限循环。
                code = sc.nextInt();
                System.out.println(code);

            switch (code) {
                // 。。。。。。。。。。。。。。。。。
                }
            } catch (Exception e) {
                System.err.println("输入错误!请输入1-5以内的整数!");
                // 加入下面这个代码可正常。
                sc.nextLine();
            }
        }

进制

输出结果:

System.out.println(017);   // 15
System.out.println(17);   // 17
System.out.println(0x17);  // 23

让十进制按指定进制输出

"%o"表示输出八进制整数

"%x"表示输出16进制整数

"%#x"表示输出带 “#” 的16进制整数

int aa = 88;
System.out.printf("%o", aa);// "%o"表示输出八进制整数。 130
System.out.println();
System.out.printf("%x", aa);  // 58
System.out.println();
System.out.printf("%#x", aa);  // 0x58
System.out.println();
System.out.println(Integer.toBinaryString(aa));  // 101  1000
System.out.println();

标识符

区分大小写,变量名用 Class 也没问题,但。。。


double保留小数

        double score = 9.0;
        double score1 = 9;
        double score4 = 9.5;
        //打印变量的信息
        System.out.println(score);  // 9.0
        System.out.println(score1);  // 9.0
        System.out.printf("%.4f\n", score1);    // 9.000
        System.out.printf("%9.4f\n", score1);   //     9.0000
        System.out.printf("%.0f\n", score4);   // 10

        System.out.println(String.format("%09d", 123));   // 000000123
        System.out.println(String.format("%+9d", 123));     //      +123

Day03

计算

小数参与计算,可能会溢出位数

        // 小数进行,有可能出错
        System.out.println(1.1 + 1.01);//        2.1100000000000003
        System.out.println(1.1 - 1.01);//        0.09000000000000008
        System.out.println(1.1 * 1.01);//        1.1110000000000002
        System.out.println(1.1 + 2.01);//        3.11

        System.out.println(10.0 / 3);//          3.3333333333333335

获得某个数字的个十百千位

公式:
获取任意一个数上每一位数。
个位:数字 % 10
十位:数字 / 10 % 10
百位:数字 / 100 % 10
千位:数字 / 1000 % 10 (最高位不用 取模)

隐式转换

数字与字符串的先后顺序:

        System.out.println("今年:" + 1 + 99 + 8);   // 今年:1998
        System.out.println(1 + 99 + 8 + "个花生");   // 108个花生
        System.out.println(1 + 99 + "个花生" + 888 + 112);   // 100个花生888112

//        byte short char进行计算,优先提升为int

char字符和数字变ASCII:

字符 + 字符, 数字 + 字符, 将转换为ASCII

// 字符 + 字符, 数字 + 字符, 将转换为ASCII
System.out.println(1 + 'a');  // 98
System.out.println('a' + "abc");  // aabc
System.out.println('a' + 'a');  // 194
// 扩展的赋值运算符中隐层还包含了一个强制转换。
short s = 1;
s += 1; // s = s + 1;    int -> short

逻辑运算符

& | !

特点:要考虑两边,两边都要参与

短路逻辑运算符:

&&

||

特点:从左到右,&&第一个为false就不用看了, || 第一个true就不用看了。

雷点

这种东西真的要小心,经常容易把 && 写成 ||,像下面的身份证最后一位验证,就debug才发现出来的问题。日期0223。

// 已修复,修复后IDEA不会提示你,某个condition always true.
// 牛批

if (a != 'X' && a != 'x' && (a > '9' || a < '0')) {
    return false;
}

三元运算符蜜汁嵌套

/*一座寺庙里住着三个和尚,已知他们的身高分别为150cm、210cm、165cm。
        请用程序实现获取这三个和尚的最高身高。*/

        int a = 150;
        int b = 210;
        int c = 165;

        int max =  (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
        System.out.println(max);

原码,反码,补码

00011100

8位1字节 8bit = 1byte

最大值:0111 1111 (+127)

最小值:1111 1111 (-127)?

原码

原码:十进制数据的二进制表现形式,最左边是符号位,0为正,1为负

反码

反码是为了解决原码不能计算负数而出现的。

反码:正数的补码反码是其本身,负数的反码是符号位保持不变,其余位取反

56 -> 0011 1000

-56 -> 在上面原码的基础上,第一位改为0,剩下位数取反 -> 1100 0111

-56 + 1 -> 1100 0111 + 1 = 1100 1000 = *55的反码

十进制数字 原码 反码
+0 00000000 00000000
-0 10000000 11111111
-1 10000001 11111110
-2 10000010 11111101
-3 10000011 11111100
-4 10000100 11111011
-5 10000101 11111010
-6 10000110 11111001
-7 10000111 10001000

如上图:

-4 + 1 = -3,按原码计算式有问题的,按反码计算没毛病

但此时,-0如果+1就跨越了, -5 + 6用反码计算为0, -4 + 7为2

补码

补码:正数的补码是其本身,负数的补码是在其反码的基础上+1

取反+1

补码能多记录一个特殊值-128,该数据在1个字节下,没有反码和原码。

计算机中的存储和计算机都是以补码的形式进行的。

十进制数字 原码 反码 补码
+0 00000000 00000000 00000000
-0 10000000 11111111 00000000
-1 10000001 11111110 11111111
-2 10000010 11111101 11111110
-3 10000011 11111100 11111101
-4 10000100 11111011 11111100
-5 10000101 11111010 11111011
-6 10000110 11111001 11111010
-7 10000111 10001000 11111001
... ... ... ...
-126 11111110 10000001 10000010
-127 11111111 10000000 10000001
-128 10000000

解决了 -5 + 6这些跨0问题。

-128 补码 10000000

强制转换的问题

        int a = 300;  // 00000000 00000000 00000001 00101100
        byte b = (byte) a;
        System.out.println(b);  // 44

        int c = 200;  // 00000000 00000000 00000000 11001000
        byte d = (byte) c; // 11001000(补码)  ->  -1 后 取反 11000111 ->  00111000 -> 56 <- -56的原码
        System.out.println(d);  // -56

逻辑与

逻辑与

逻辑或

逻辑或

左移

左移

右移

右移

高位补的数与原来最高位一样。

//  11111111111111111111111111110000
int x = -16;   
System.out.println(Integer.toBinaryString(x));
System.out.println(x >> 2);    //  -4 相当于 ->  -16 除于 4
//  11111111111111111111111111111100
System.out.println(Integer.toBinaryString(x >> 2));

无符号右移

无符号右移


Day04流程

if

if else if,若满足第一个分支,则后续else if不走了。

if {}完接下一条语句 还是 if {}完接着else构成要考虑清楚。

像是下面这种遍历数组,加, 或者 ]的,要if else,不然上面走完还会再走一次下面语句。

for (int i = 0; i < list.size(); i++) {
    if (i == list.size() - 1) {
        System.out.print(list.get(i));
    } else {

        System.out.print(list.get(i) + ", ");
    }
}

接上面,如果while或for里嵌套if else然后if里又有if else,类似下面这样:

进入内层 if 后,若条件不满足,不会往下走外层 else,而是回到 for,类似continue;

for (int i = 0; i < userId.length(); i++) {

    if (    condition  1    ) {
        if (  condition  2        ) {
            return false;
        }

    } else {
        if (   condition  3 ) {
            return false;
        }
    }
}

switch

拿switch的表达式与下面每一个case进行匹配。

如果匹配上了,就会执行对应的语句体,如果发现了break,那么会结束整个switch。

如果没有发现break,就会执行下一个case语句体,一直遇到break为止。

  • switch记得break; 否则会case穿透 ,满足case后面所有的输出都会打印
        int a = 77;
        switch (a) {
            case 17:
                System.out.println("17");
            case 77:
                System.out.println("77 number");
            case 88:
                System.out.println("88");
            case 99:
                System.out.println("99");
            default:
                System.out.println("default");
        }

输出:

77 number
88
99
default

  • switch() 括号内,无法是Object,只能是 byte, short, int, char, String, 枚举
  • case后面的值只能是字面量,不能是变量
  • case给出的值不允许重复
  • default也要break; 但可以省略,可以放在任何地方。
  • 如果switch()外被while(true)包围,case的break只退出switch,除非while前loop:while,break loop;

switch在JDK12的新特性

不用break;

一句输出,则可以省略大括号;

int number = 1;
switch (number) {
    case 1 -> System.out.println("一");
    case 2 -> System.out.println("二");
    case 3 -> System.out.println("三");
    default -> System.out.println("四");
}

case结果多个值指向同一个

Scanner sc = new Scanner(System.in);
System.out.println("请输入星期x: ");
int i = sc.nextInt();

switch (i) {
    case 1, 2, 3, 4, 5 -> System.out.println("工作日");
    case 6, 7 -> System.out.println("休息日");
    default -> System.out.println("wrong input");
}

for

  • for的三要素可以空,就是死循环
  • for的初始条件可以定义两个,用,, 不用再 int
for (int i = 0, j = arr.length - 1; i < j; i++, j--) {

while 取数字各个位

详情见下面LeetCode题,

// 将数字各个位存入数组,要么下面方法,或者变String,再toCharArray,然后char通过String.valueOf()变String再Integer.parseInt()变回数字。
int index = 0; // 再来个数组反转,顺序就对了,
while (num != 0) {
    array[index++] = num % 10;   // 第一次取到的是个位
    num /= 10;
}

while和for的区别

for和while

for初始化语句可以提出去,不写

forwhile


do while

先执行一次循环体,再判断


求闰年

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

        /*中国有闰年的说法。闰年的规则是:四年一闰,百年不闰,四百年再闰。
        (年份能够被4整除但不能被100整除算是闰年,年份能被400整除也是闰年)。
        请打印出1988年到2019年的所有闰年年份。*/

        for (int i = 1988; i <= 2019; i++) {
            if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0) {
                System.out.println(i + "是闰年");
            }
        }
    }
}

LeetCode算法题--回文数

需求:给你一个整数X。
如果 x 是一个回文整数打印 true,否则,返回false。

解释:回文数是指正序 (从左向右) 和倒序 (从右向左) 读都是一样的整数

例如,121 是回文,而123 不是。

讲真,我第一眼觉得,把它转为String,然后拿去头尾相比。

结果:可行!

import java.util.Scanner;

public class Palindrome {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入需要判断的数字: ");

        int i = sc.nextInt();

        char[] number = String.valueOf(i).toCharArray();

        boolean flag = true;
        for (char a = 0; a < number.length / 2; a++ ) {
            if (number[a] == number[number.length - 1 - a ]) {
                continue;
            } else {
                flag = false;
                break;
            }
        }

        System.out.println(flag ? i + "是回文数" : i + "不是回文数!!");

    }
}

另一种思路,while连续除,拿到每一个数字,感觉麻烦(结果:还好)

核心:从右往左获取每一位数字

解题思路:字符串 String s = 个位 + 十位 + 百位 +....., 顺序颠倒后与原数字相比。

编写思路:

  1. 通过不断除10取模(%10),会先拿到 个位,
  2. 拿到个位后,放入字符串,接下来拿十位,
  3. 拿十位则要将 num/10 后的结果再 除10取模(%10),继续循环
  4. 最高位也要加入,因此,判定结果为num != 0, 最高位加入后,除于10会得到0
import java.util.Scanner;

public class Palindrome2 {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入需要判断的数字: ");

        int i = sc.nextInt();  // 12321

        // ===从右往左获取每一位数字===
        int num = i;
        StringBuilder s = new StringBuilder();
        while (num != 0) {
            // 这里取模后的值应直接加入,不能赋值回去
            s.append(num % 10);     //   1       2      3      2       1
            num /= 10;               //  1232   123    12      1       0
        }

        System.out.println("翻转:" + s);

        System.out.println(Integer.parseInt(s.toString()) == i);

    }
}

黑马思路: 与楼上类似,不过不同在于其,将数字颠倒,但不用字符串,自己乘

public class Palindrome3 {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入需要判断的数字: ");

        int i = sc.nextInt();  // 12321

        int number = i;
        // 将数字颠倒,但不用字符串,自己乘
        // num 为结果
        int num = 0;

        while (number != 0) {
            int ge = number % 10;    //   1    2   3   2  1
            number /= 10;            //  1232   123   12   1   0
            num = num * 10 + ge; // 原先最低位将不断*10     1  12   123   1232  12321
        }
        System.out.println(num == i);
    }
}

LeetCode算法题--求商和余数

需求:给定两个整数,被除数和除数(都是正数,且不超过int的范围)

将两数相除,要求不使用乘法、除法和 % 运算符

得到商和余数。

结果:还行

不让除,我就加

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

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入被除数:");   //  10
        int be_divided = sc.nextInt();
        System.out.println("请输入除数:");     //  3
        int divided = sc.nextInt();

        int num = 0;
        int count = 0;

        while ((be_divided - num) >= divided) {  // 7  4  1(break)
            num += divided;   //  3  6  9
            count++;  // 1  2  3

        }
        System.out.println(be_divided + " / " + divided + " = " + count + " 余数为: " + (be_divided - num));

    }
}

黑马思路:类似,不断减去被减数,直到小于除数。

public class Remainder2 {
    public static void main(String[] args) {
        /*需求:给定两个整数,被除数和除数(都是正数,且不超过int的范围) 。
        将两数相除,要求不使用乘法、除法和 % 运算符。
        得到商和余数。

        分析:
            被除数 / 除数 = 商 ... 余数

        int a = 100;
        int b = 10;

        100 - 10 = 90
        90 - 10 = 80
        80 - 10 = 70
        70 - 10 = 60
        ...
        10 - 10 = 0 (余数)

        */

        //1.定义变量记录被除数
        int dividend = 100;
        //2.定义变量记录除数
        int divisor = 37;
        //3.定义一个变量用来统计相减了多少次
        int count = 0;
        //3.循环 while
        //在循环中,不断的用被除数 - 除数
        //只要被除数 是大于等于除数的,那么就一直循环
        while(dividend >= divisor){
            dividend = dividend - divisor;
            //只要减一次,那么统计变量就自增一次
            count++;
        }
        //当循环结束之后dividend变量记录的就是余数
        System.out.println("余数为:" + dividend);
        //当循环结束之后,count记录的值就是商
        System.out.println("商为:" + count);

    }
}

Day5循环高级

break continue

break:

break

continue:

continue


猜质数

核心:从2开始,到 被猜数 (这里一定是等于)的平方根 Math.sqrt(),for循环。

例如:两个数相乘能达到 被猜数 ,则必然一个数大于平方根,一个小于。(除了平方根本身),所以推一边就行。

i <= Math.sqrt(number)

public static boolean isPrime(int number) {
    for (int i = 2; i <= Math.sqrt(number); i++) {   // 一定是等于,不然49就成了质数
        if (number % i == 0) {
            return false;
        }
    }
    return true;
}

Random

  1. 导包
import java.util.Random;
导包的动作必须出现在类定义的上边。
  1. 创建对象
Random r = new Random ();
上面这个格式里面,只有r是变量名,可以变,其他的都不允许变。
  1. 生成随机数
int number = r.nextInt(随机数的范围);
上面这个格式里面,只有number是变量名,可以变,其他的都不允许变。

随机数范围的特点:从0开始,不包含指定值。

比如:参数为10,生成的范围 [0,10)

秘诀

用来生成任意数到任意数之间的随机数,例如 7-15

  1. 让这个数头尾减去一个数,使范围从0开始 -7 (0-8)
  2. 尾巴+1 因为左闭右开(8+1=9),9即为括号里的参数
  3. 最终的结果,再加上第一步减去的数
r.nextInt(9) + 7;

经典题目:生成验证码

生成五位验证码,每一位可以是大写,小写字母或者数字

answer from Bing chat AI:

import java.util.Random;

public class VerificationCode {

    // 定义一个字符数组,包含大写字母,小写字母和数字
    private static final char[] CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();

    // 定义一个随机数生成器
    private static final Random RANDOM = new Random();

    // 生成一个五位验证码的方法
    public static String generateCode() {
        // 创建一个字符串缓冲区,用于存储验证码
        StringBuilder sb = new StringBuilder();
        // 循环五次,每次从字符数组中随机选择一个字符,并添加到字符串缓冲区中
        for (int i = 0; i < 5; i++) {
            int index = RANDOM.nextInt(CHARS.length);
            sb.append(CHARS[index]);
        }
        // 返回字符串缓冲区的内容作为验证码
        return sb.toString();
    }

    // 测试方法
    public static void main(String[] args) {
        // 生成并打印一个五位验证码
        System.out.println(generateCode());
    }
}

Day5数组

数组的静态初始化

完整格式:

数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,元素4...};

比如:

int[] arr = new int[]{11,22,33};

double[] arr = new double[]{1.1,1.2,1.3};

整数 int 存进去自动转换 double

简化格式:

数据类型[] 数组名 = {元素1,元素2,元素3,元素4...};

比如:

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

double[] array = {1.1,1.2,1.3};

数组地址值

int[] arr1 = {1, 2, 3, 4};
System.out.println(arr1);  //  [I@3b07d329

String[] arr4 = new String[]{"zhangsan","lisi","wangwu"};
System.out.println(arr4);  // [Ljava.lang.String;@41629346

double[] arr2 = {1.1, 2.1, 3.1};
System.out.println(arr2); // [D@41629346

以[I@6d03e736为例:

[ :表示现在打印的是一个数组。

I:表示现在打印的数组是int类型的。 D为Double

@:仅仅是一个间隔符号而已。

6d03e736:就是数组在内存中真正的地址值。(十六进制的)

但是,我们习惯性会把[I@6d03e736这个整体称之为数组的地址值。

数组遍历——数组名.fori

for(int i = 0; i < arr.length; i++){
    //在循环的过程中,i依次表示数组中的每一个索引
    sout(arr[i]);//就可以把数组里面的每一个元素都获取出来,并打印在控制台上了。
}

下面这种for只能取数据查看,不能用于修改数据!

修改数据只能用上面这种方式。

// 增强型,单纯输出好用,其他看情况
for (int k : arr) {
    System.out.print(k + " ");
    // k = 100; 这样修改是不成功的。
}

数组遍历不一定要单向,可以定义头和尾指针,同时向中间走。

数组动态初始化

数据类型[] 数组名 = new 数据类型[数组的长度];

动态初始化的数组具有默认值,即使用户没添加,数组的长度也不为0;

//1.定义一个数组,存3个人的姓名,姓名未知
String[] arr = new String[3];    // 没有 {}
System.out.println(arr.length);  // 3

数组的默认初始值

整数类型:0

小数类型:0.0

布尔类型:false

字符类型:'\u0000' (char 空格)

引用类型:null(String 也是引用数据类型也是 null)

数组求最值注意点

默认取数组第一位为初始化最值

如果 int max = 0;而数组全为负数,则GG。

// 第一次不用与自己比
for(int i = 1; i < arr.length; i++)

题目:随机打乱数组

需求:定义一个数组,存入1-5,要求打乱数组中所有数据的顺序。

核心:遍历数组,让第 i 个数据与随机一个索引交换。

//1.定义数组存储1~5
int[] arr = {1, 2, 3, 4, 5};
//2.循环遍历数组,从0索引开始打乱数据的顺序
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
    //生成一个随机索引
    int randomIndex = r.nextInt(arr.length);
    //拿着随机索引指向的元素 跟 i 指向的元素进行交换
    int temp = arr[i];
    arr[i] = arr[randomIndex];
    arr[randomIndex] = temp;
}
//当循环结束之后,那么数组中所有的数据已经打乱顺序了
for (int k : arr) {
    System.out.print(k + " ");
}

来自bing chat的两种方法

// 使用Collections.shuffle()方法
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ShuffleArray {
    public static void main(String[] args) {
        // 创建一个int类型数组
        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9 ,10 ,11 ,12 ,13 ,14 ,15};
        // 将数组转换为列表
        List<Integer> list = Arrays.asList(array);
        // 随机打乱列表元素
        Collections.shuffle(list);
        // 将列表转换回数组
        array = list.toArray(new int[list.size()]);
        // 打印结果
        System.out.println(Arrays.toString(array));
    }
}

// 使用Fisher–Yates shuffle算法
import java.util.Arrays;
import java.util.Random;

public class ShuffleArray {
    public static void main(String[] args) {
        // 创建一个int类型数组
        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9 ,10 ,11 ,12 ,13 ,14 ,15};
        // 创建一个随机数生成器
        Random random = new Random();
        // 遍历数组从最后一个元素开始
        // ======索引等于0时不用再交换========
        for (int i = array.length - 1; i > 0; i--) {
            // 随机选择一个索引从0到i(包含)
            int j = random.nextInt(i + 1);
            // 将当前元素和随机选择的元素交换位置
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        // 打印结果
        System.out.println(Arrays.toString(array));
    }

    // 下面是从前往后,当然不好用,这random计算量感人===========================
    for (int i = 0; i < arr.length - 1; i++) {
            // 从当前位置到最后一个位置中随机选择一个元素
            int j = random.nextInt(arr.length - i) + i;
            // 交换当前元素和选中的元素
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
     }
}

简单易懂的介绍:

https://juejin.cn/post/7043574180570202148

二、算法过程

Fisher–Yates shuffle 算法生成随机序列的过程如下:

  1. 得到一个 [1, ..., N] 的数组。
  2. 从当前区间中随机获得一个数字 K。
  3. 从低位开始,得到第 K 个数字,把它放到新数组(ans)的最后一位。
  4. 重复第二步,直到所有的数字都被取出。

如果第二步是真随机,那么新数组(ans)就是原数组的一个随机序列。

其时间复杂度为 O(n),空间复杂度为 O(1)。

三、等概率

以数组 [a, b, c] 为例:

  • 第一次取出 a 的概率为: 1/3。
  • 第二次取出 a 的概率为:2/3(取出b和c的概率) * 1/2 = 1/3。
  • 第三次取出 a 的概率为:2/3(取出b和c的概率) 1/2(取出b或者c的概率) 1 = 1/3。

以下回答来自Bing AI Chat

  • Fisher-Yates shuffle算法能够保证每个数字出现在某个位置上的概率都是1/n,这是因为算法在每次循环中都是从剩余的元素中随机选择一个与当前位置的元素进行交换。
  • 举个例子,假设我们有一个长度为3的数组[1,2,3],并且我们使用Fisher-Yates shuffle算法来打乱它。
  • 在第一次循环中,我们从3个元素中随机选择一个与第一个元素进行交换。因此,每个元素都有1/3的概率被选中并放置在第一个位置上。
  • 在第二次循环中,我们从剩下的2个元素中随机选择一个与第二个元素进行交换。因此,对于任意一个没有被放置在第一个位置上的元素,它都有1/2的概率被选中并放置在第二个位置上。
  • 最后,在第三次循环中,只剩下1个元素没有被确定位置,它将直接被放置在最后一个位置上。
  • 综上所述,在Fisher-Yates shuffle算法结束后,每种排列出现的概率都是(1/3) (1/2) (1/1) = 1/6。也就是说,在所有6种可能的排列中,每种排列出现的概率都是相等的。因此,每个数字出现在某个位置上的概率也都是相等的。

Fisher-Yates shuffle算法能够产生均匀分布的随机排列,这意味着对于一个长度为n的数组,它能够生成n!种不同的排列,每种排列出现的概率都是1/n!。
下面是一种简单的证明方法:
假设我们有一个长度为n的数组,我们使用Fisher-Yates shuffle算法来打乱它。在第一次循环中,我们从n个元素中随机选择一个与第一个元素进行交换。因此,每个元素都有1/n的概率被选中并放置在第一个位置上。
在第二次循环中,我们从剩下的n-1个元素中随机选择一个与第二个元素进行交换。因此,对于任意一个没有被放置在第一个位置上的元素,它都有1/(n-1)的概率被选中并放置在第二个位置上。
以此类推,在第k次循环中(k<=n),我们从剩下的n-k+1个元素中随机选择一个与第k个元素进行交换。因此,对于任意一个没有被放置在前k-1个位置上的元素,它都有1/(n-k+1)的概率被选中并放置在第k个位置上。
综上所述,在Fisher-Yates shuffle算法结束后,每种排列出现的概率都是(1/n) (1/(n-1)) (1/2) (1/1) = 1/n!。


二维数组

静态初始化

二维数组

写法:

        // 建议:一个数组写一行
        int[][] arr3 = {
                {1, 2, 3},
                {1, 3, 5, 7, 9, 11},
                {2, 4, 6, 8, 10, 12}
        };
        // arr3[2][2]  == 6

遍历

二维数组的长度指的是int[x][y]中的x

        // 增强型for
        for (int[] outArray : arr3) {
            for (int innerArrya : outArray) {
                System.out.print(innerArrya + " ");
            }
            System.out.println();
        }

        System.out.println("===================================");

        // 经典型for
        for (int i = 0; i < arr3.length; i++) {
            for (int j = 0; j < arr3[i].length; j++) {
                System.out.print(arr3[i][j] + " ");
            }
            System.out.println();
        }

动态初始化

  • 只定义有多少个一维是可以的。
int[][] array = new int[3][];

二维数组1

下面方法报错:

int[][] array = new int[4][3];

// Array initializer is not allowed here
array[0] = {22, 66, 44};

// 下面方法可以
int[] arr1 = {22, 33, 44};
array[0] = arr1;

二维数组的内存图

二维内存图


一维和二维数组判断是否相等

感谢chatGPT补充

一维

对于一维数组,可以使用Arrays.equals()方法来判断它们是否相同。

例如,假设有以下两个一维int类型的数组:

int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};

则可以使用Arrays.equals()方法来比较它们是否相同:

boolean isEqual = Arrays.equals(arr1, arr2);

如果isEqual变量的值为true,则表示两个数组内容相同。

二维

可以使用Arrays.deepEquals()方法来判断两个二维int类型内容是否相同。

例如,假设有以下两个二维int类型的数组:

int[][] arr1 = {{1, 2}, {3, 4}};
int[][] arr2 = {{1, 2}, {3, 4}};

则可以使用Arrays.deepEquals()方法来比较它们是否相同:

boolean isEqual = Arrays.deepEquals(arr1, arr2);

如果isEqual变量的值为true,则表示两个数组内容相同。

如果把上面arr2或arr1改为Integer类型,即使内容一样,类型不一样,也是false。


二维数组的完全复制

直接引用传递就寄了(3月12日)

Bing Chat AI补充

你好,这是Bing。😊

如果你想要将checkArray的数组完全复制给imageArr,而不是只复制它们的引用,你可以使用我之前提到的两种方法:System.arraycopy()或者clone()。这两种方法都可以创建一个新的数组,并且复制原始数组的所有元素。例如:

// 使用System.arraycopy()方法
for (int i = 0; i < checkArray.length; i++) {
  // 将checkArray[i]的所有元素复制到imageArr[i]的相应位置
  System.arraycopy(checkArray[i], 0, imageArr[i], 0, checkArray[i].length);
}

// 使用clone()方法
for (int i = 0; i < checkArray.length; i++) {
  // 将checkArray[i]的所有元素复制到一个新的数组,并赋值给imageArr[i]
  imageArr[i] = checkArray[i].clone();
}

还有一种方法是使用Arrays.copyOf()或者Arrays.copyOfRange()方法,它们也可以创建一个新的数组,并且复制原始数组的一部分或者全部元素。例如:

// 使用Arrays.copyOf()方法
for (int i = 0; i < checkArray.length; i++) {
  // 将checkArray[i]的所有元素复制到一个新的数组,并赋值给imageArr[i]
  imageArr[i] = Arrays.copyOf(checkArray[i], checkArray[i].length);
}

// 使用Arrays.copyOfRange()方法
for (int i = 0; i < checkArray.length; i++) {
  // 将checkArray[i]从第0个元素开始到最后一个元素结束的部分复制到一个新的数组,并赋值给imageArr[i]
  imageArr[i] = Arrays.copyOfRange(checkArray[i], 0, checkArray[i].length);
}

这三种方法都可以实现二维数组的完全复制,但是它们有一些区别。System.arraycopy()方法和Arrays.copyOfRange()方法更灵活,因为它们可以指定源数组和目标数组的范围和位置。而clone()方法和Arrays.copyOf()方法更简单,因为它们不需要指定任何参数。你可以根据你的需求选择合适的方法。

Java内存分配

java

内存2

内存3

数组内存

数组的内存图

两个数组指向同一个空间的内存图

同一个内存


数组习题

有一个数组,其中有十个元素从小到大依次排列 {12,14,23,45,66,68,70,77,90,91}。

再通过键盘录入一个整数数字。

要求:把数字放入数组序列中,生成一个新的数组,并且数组的元素依旧是从小到大排列的。

执行效果如下:

请输入一个整数数字:
50
生成的新数组是:12 14 23 45 50 66 68 70 77 90 91

操作步骤

  1. 定义原数组arr,和一个比原数组长度大1的新数组brr。
  2. 通过键盘录入的方式输入变量num的值。
  3. 定义变量index用于记录num存放的索引位置。
  4. 遍历数组,小于或等于num的元素直接存放到原来的位置,大于num的元素往后移动一个位置。
  5. 存放结束之后,中间会空出一个位置,在遍历时用index记录这个位置。把num放在这个索引处。
  6. 最终遍历新数组打印结果。

难点:首先要比较比 input 小的数据,再一个个放进去。index同时++

如果先比较比 input 大的数据,则原数组后面个个都比他大。

import java.util.Scanner;

public class ArrayTest5 {
    public static void main(String[] args) {
        /*有一个数组,其中有十个元素从小到大依次排列 {12,14,23,45,66,68,70,77,90,91}。
        再通过键盘录入一个整数数字。要求:把数字放入数组序列中,生成一个新的数组,
        并且数组的元素依旧是从小到大排列的。执行效果如下:*/

        int[] arr = new int[]{12, 14, 23, 45, 66, 68, 70, 77, 90, 91};
        int[] new_arr = new int[11];
        // 输出原数组
        for (int x : arr) {
            System.out.print(x + " ");
        }
        System.out.println();

        Scanner sc = new Scanner(System.in);
        System.out.println("Please input: ");
        int input = sc.nextInt();

        // 定义变量代表要插入的位置
        int index = 0;

        for (int i = 0; i < arr.length; i++) {
            // 如果元素小于等于要插入的数字,则直接存放
            if (arr[i] <= input) {
                new_arr[i] = arr[i];
                // 把 i后面的位置记录下来
                index++;
            } else {
                // 如果元素大于要插入的数字,则往后一个位置存放
                new_arr[i + 1] = arr[i];
            }
        }
        // index存储的就是要插入的位置
        new_arr[index] = input;

        for (int x : new_arr) {
            System.out.print(x + " ");
        }

    }
}

Day6 方法

无参数方法定义和调用

  • 定义格式:static
    public static void 方法名 (   ) {
        // 方法体;
    }

形参和实参

  1. 形参:形式参数,方法定义中的参数

    等同于变量定义格式,例如:int number

  2. 实参:实际参数,方法调用中的参数

    等同于使用变量或常量,例如: 10 number


注意事项

  • 方法不能嵌套定义
public class MethodDemo {
    public static void main(String[] args) {

    }

    public static void methodOne() {
        public static void methodTwo() {
            // 这里会引发编译错误!!!
        }
    }
  • void表示无返回值,可以省略return,也可以单独的书写return,后面不加数据
return;

方法重载

  • 方法重载概念

    方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载

    • 多个方法在同一个类中
    • 多个方法具有相同的方法名
    • 多个方法的参数不相同,类型不同或者数量不同。(个数,类型,顺序)
  • 注意:
    • 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
    • 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关 (不是说返回值不一样就一定不是),换句话说不能通过返回值来判定两个方法是否相互构成重载
public class MethodDemo {
    public static void fn(int a) {
        //方法体
    }
    public static int fn(double a) {
        //方法体
    }
}

// Attention
public class MethodDemo {
    public static float fn(int a) {
        //方法体
    }
    public static int fn(int a , int b) {
        //方法体
    }
}

方法的内存

方法的基本内存原理

弹匣——先进后出

被调用时进栈,运行完时出栈。调用链同理。

基本数据类型:

内存角度解释:数据值是存储在自己的空间中的。

特点:赋值给其他变量,也是赋的真实的值。

基本数据类型

引用数据类型:

内存角度解释:数据值是存储在其他的空间中的。自己空间存储的是地址值。

特点:赋值给其他变量,也是赋的地址值。

引用数据类型

方法传递基本数据类型的内存原理:

方法的值传递

方法传递引用数据类型的内存原理:

方法的值传递2


题目:数值排序

在主方法中通过键盘录入三个整数。定义一个方法,方法接收三个整数变量,

在方法中从大到小依次打印三个变量。执行效果如下:

请输入第一个整数:10
请输入第二个整数:30
请输入第三个整数:20
从大到小的顺序是: 30 20 10 

操作步骤

  1. 使用键盘录入分别录入三个整数。
  2. 定义method方法,方法的参数是三个int类型,方法的返回值类型是void。

    2.1. 定义整数变量max用于存储最大值,定义min变量用于存储最小值。

    2.2. 使用if..else..多分支判断语句或者三元运算符计算三个整数中的最大值并赋值给max。

    2.3. 使用if..else..多分支判断语句或者三元运算符计算三个整数中的最小值并赋值给min。

    2.4. 定义变量mid代表中间数,三个整数的和减去max,再减去min,就是中间数的值。

    2.5. 依次打印最大值,中间值和最小值。

  3. 在主方法中调用method方法,传入参数。
public class Test5 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入第一个数");
        int num1 = sc.nextInt();
        System.out.println("请输入第二个数");
        int num2 = sc.nextInt();
        System.out.println("请输入第三个数");
        int num3 = sc.nextInt();

        //获取最大值
        int max = getMax(num1, num2, num3);
        //获取最小值
        int min = getMin(num1, num2, num3);
        //获取中间值
        int mid = (num1 + num2 + num3) - max - min;

        System.out.println(max + " " + mid + " " + min);
    }

    public static int getMax(int a, int b, int c) {
        int temp = a > b ? a : b;
        int max = temp > c ? temp : c;
        return max;
    }

    public static int getMin(int a, int b, int c) {
        int temp = a < b ? a : b;
        int min = temp < c ? temp : c;
        return min;
    }
}

Day8对象

类和对象

  • 类是对事物的一种描述,对象则为具体存在的事物
  • 类的五大成员:属性,方法,构造方法,代码块,内部类

对象初始化值:

与数组类似。

初始化值


类的定义

类的组成是由属性和行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)
  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
  • 一个Java文件可以定义多个class类,且只能一个类是public修饰,而且public修饰的类名必须成为代码文件名。
public class 类名 {
    // 成员变量
    变量1的数据类型 变量1;
    变量2的数据类型 变量2;
    …
    // 成员方法
    方法1;
    方法2;    
    // 构造方法
    // 代码块
    // 内部类
}

对象练习

  • 需求:首先定义一个学生类,然后定义一个学生测试类,在学生测试类中通过对象完成成员变量和成员方法的使用
  • 分析:
    • 成员变量:姓名,年龄…
    • 成员方法:学习,做作业…
  • 示例代码:

这里成员方法没有加 static

调用Student类生成对象,使用对象成员方法和变量要在(main)方法中完成。

public class Student {
    //成员变量
    String name;
    int age;

    //成员方法
    public void study() {
        System.out.println("好好学习,天天向上");
    }

    public void doHomework() {
        System.out.println("键盘敲烂,月薪过万");
    }
}
/*
    学生测试类
 */
public class StudentDemo {
    public static void main(String[] args) {
        //创建对象
        Student s = new Student();

        //使用对象
        System.out.println(s.name + "," + s.age);

        s.name = "林青霞";
        s.age = 30;

        System.out.println(s.name + "," + s.age);

        s.study();
        s.doHomework();
    }
}

Javabean类

  • 定义类的补充注意事项
    1. 用来描述一类事物的类,专业叫做:Javabean类

      在 Javabean类中,是不写main方法的。

    2. 在以前,编写main方法的类,叫做测试类

      我们可以在测试类中创建 javabean 类的对象并进行赋值调用。


封装

4.1 封装思想

  1. 封装概述
    是面向对象三大特征之一(封装,继承,多态)

    对象代表什么,就得封装对应的数据,并提供数据对应的行为。(人画圆,画圆方法属于Circle类,根据Circle类的半径) 。

  2. 封装代码实现

    将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问成员变量private,提供对应的getXxx()/setXxx()方法

对象成为一个整体,有属性,有行为,可以直接调用。

封装

对于对象数组,初始化为null,如果此时遍历数组,用到了getXxx()方法,null的对象则会报空指针异常。

以成员变量age为例,如果setAge()方法可以在方法的内部定义校验机制,如果age不合法则采取相应措施。保证数据的安全性。


4.2 private关键字

private是一个修饰符,可以用来修饰成员(成员变量,成员方法)

  • 被private修饰的成员,只能在本类 (其他类不能使用 对象.变量名和方法名使用)进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作。
    • 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
    • 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
  • 示例代码:

Alt + insert即可快速生成 getter and setter。

or PTG插件。

/*
    学生类
 */
class Student {
    //成员变量
    String name;
    private int age;

    //提供get/set方法
    public void setAge(int a) {
        if(a<0 || a>120) {
            System.out.println("你给的年龄有误");
        } else {
            age = a;
        }
    }

    public int getAge() {
        return age;
    }

    //成员方法
    public void show() {
        System.out.println(name + "," + age);
    }
}
/*
    学生测试类
 */
public class StudentDemo {
    public static void main(String[] args) {
        //创建对象
        Student s = new Student();
        //给成员变量赋值
        s.name = "林青霞";
        s.setAge(30);
        //调用show方法
        s.show();
    }
}

4.3 this关键字

this的本质:所在方法调用者的地址值。

  • this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
    • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
    • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
public class Student {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

就近原则:

如果类的成员变量方法内的局部变量同名,则就近局部变量。


构造方法

  • 作用:创建对象 Student stu = new Student();
  • 格式:
    public class 类名{
      修饰符 类名( 参数 ) {
    
      }
    }
  • 功能:创建对象的时候,虚拟机会自动调用构造方法。主要是完成对象数据的初始化

注意事项

  • 构造方法的创建

如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法

  • 构造方法的重载

如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

  • 推荐的使用方式

无论是否使用,都手工书写无参数构造方法

  • 重要功能!

可以使用带参构造,为成员变量进行初始化


对象内存图

内存字节码

单个创建对象内存流程

对象内存图解

执行完毕后,main方法出栈,没有变量指向对象,则被当做垃圾回收。

两个对象的内存图

与上面类似,就是.class字节码文件不用再次加载。

两个对象内存图

两个引用指向同一个对象,懂的都懂,和数组类似。如果一个对象被赋值null,则该链接断开,但另外一个链接仍在。

两个连接一个

this的内存原理

调用者(对象)的内存地址:

所在方法调用者的地址值。s1.setAge()则是s1的地址值,s2.setAge()则是s2的地址值。

this内存图

成员变量和局部变量的区别

  • 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
  • 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
  • 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
  • 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
区别 成员变量 局部变量
类中位置不同 类中,方法外 方法内,方法申明上
初始化值不同 有默认初始化值 没有,使用之前需要完成赋值
内存位置不同 堆内存(对象) 栈内存(方法)
生命周期不同 随着对象的创建而存在,对着对象的消失而消失 随着方法的调用(进栈)而存在,对着方法的运行结束(出栈)而消失
作用域 整个类中有效 当前方法中有效

内存图:

成员和局部内存