Java学习笔记(三)——基础知识的一些细节

一、for循环细节

  • (1)for(;循环条件;)中的初始化和变量迭代可以写到其他地方,但是两边的分号不能省略
  • (2)循环初始值可以有多条初始化语句,但要求类型一样,并且中间用逗号隔开,循环变量迭代也可以有多条变量迭代语句,中间用逗号隔开,例如:
1
2
3
for(inti=0,j=0;i<count;i++,j+=2){
System.out.println("i=" + i + "j=" + j);
}

二、do while循环

  • (1)do while是关键字
  • (2)与while的区别:while是先判断再执行,do while先执行,再判断,也就是说一定会执行一次。

三、数组

  • 基本数据类型赋值,是具体的数据,而且相互不影响,因为其本质上是值拷贝
  • 数组在默认情况下是引用传递,其赋的值是地址,所以会相互影响。

如何只实现数组拷贝,而不相互影响呢?——数据区独立

1
2
3
4
5
6
7
int[] arr1 = {1,1,1};
//开辟新的数据空间arr2
int[] arr2 = new int[arr1.length];
//遍历拷贝
for(int i = 0 ; i < arr1.length ; i++){
arr2[i] = arr1[i];
}

动态数组扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.Scanner;

public class Array{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);

int[] arr = {1,2,3};

do{
int[] arrNew = new int[arr.length + 1];

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

System.out.println("请输入需要添加的元素");
int addNum = myScanner.nextInt();
arrNew[arrNew.length - 1] = addNum;

arr = arrNew;

System.out.println("===扩容后的数组情况===");
for(int i =0 ; i< arr.length;i++){
System.out.println(arr[i] + "\t");
}
System.out.println("===是否继续添加 y/n ===");
char key = myScanner.next().charAt(0);
if(key == 'n'){
break;
}

}while(true);

System.out.println("你退出了添加");
}
}

四、冒泡排序

冒泡排序流程分析

初始数组[24,69,80,57,13]

第1轮排序:目标把最大的数放在最后 (红色表示正在比较的两个数)
第1次比较:[$\textcolor{Red}{24}$,$\textcolor{Red}{69}$,80,57,13]

第2次比较:[24,$\textcolor{Red}{69}$,$\textcolor{Red}{80}$,57,13]

第3次比较:[24,69,$\textcolor{Red}{57}$,$\textcolor{Red}{80}$,13]

第4次比较:[24,69,57,$\textcolor{Red}{13}$,$\textcolor{Red}{80}$]

第2轮排序:目标把第2大的数放在倒数第2个位置(加粗表示正在已经确定位置的数)

第1次比较:[$\textcolor{Red}{24}$,$\textcolor{Red}{69}$,57,13,80]

第2次比较:[24,$\textcolor{Red}{57}$,$\textcolor{Red}{69}$,13,80]

第3次比较:[24,57,$\textcolor{Red}{13}$,$\textcolor{Red}{69}$,80]

第3轮排序:目标把第3大的数放在倒数第3个位置

第1次比较:[$\textcolor{Red}{24}$,$\textcolor{Red}{57}$,13,69,80]

第2次比较:[24,$\textcolor{Red}{13}$,$\textcolor{Red}{57}$,69,80]

第4轮排序:目标把第4大的数放在倒数第4个位置

第1次比较:[$\textcolor{Red}{13}$,$\textcolor{Red}{24}$,57,69,80]

冒泡排序特点

  • 1、一共有5个元素

  • 2、一共进行了4轮排序(4个外层循环)

  • 3、每1轮排序确定一个数的位置,比如第1轮排序确定最大的数

  • 4、当进行比较时,如果前面的数大于后面的数就交换。

  • 5、每一轮比较逐渐减少,第n轮比较n-1次

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class sort {
public static void main(String[] args){
int[] arry = {24 , 69 , 80 , 57 , 13 };//
int temp = 0 ;//
//外层
for(int i =0; i < arry.length-1; ++i){
//里层排序
for(int j = 0 ; j < arry.length-i-1 ; ++j){
if(arry[j] > arry[j+1]){
temp = arry[j];
arry[j] = arry[j+1];
arry[j+1] = temp;
}
}
System.out.println("\n"+"第"+ (i+1) +"轮排序的结果是:");
for(int k = 0 ; k < 5 ; ++k){
System.out.print(arry[k]+"\t");
}
}
}
}

五、二维数组

杨辉三角

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

1 5 10 10 5 1

核心特征:从第3行开始,对于非第一个元素和最后一个元素的元素值。arr[i] [j] = arr[i-1] [j] +arr[i-1] [j-1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class yanghui{
public static void main(String[] args){
int high = 10;//杨辉三角的层数
int[][] yanghui = new int[high][];
for(int i = 0 ; i < yanghui.length; ++i){
yanghui[i] = new int[i+1];//重点,要给每一个一维数组开辟空间
for(int j = 0 ; j < yanghui[i].length; j++){
if(j == 0 || j == yanghui[i].length - 1){
yanghui[i][j] = 1;
}
else{
yanghui[i][j] = yanghui[i-1][j] + yanghui[i-1][j-1];
}
}
}

for(int i = 0 ; i < yanghui.length ; ++i){
for(int j = 0 ; j < yanghui[i].length; j++){
System.out.print(yanghui[i][j]+"\t");
}
System.out.print("\n");
}
}
}

六、类和对象的内存分配机制

image-20220326141638803

Java内存的结构分析

  • 1、栈:一般存放基本数据类型,局部变量
  • 2、堆:存放对象(Cat cat,数组等)
  • 3、方法区:常量池(常量,比如字符串(因为字符串在java里本身就是一个类)),类加载信息

Java创建对象的流程简单分析

1
2
3
Person p = new Person();
p.name = "jack";
p.age = 10;
  • 1、先加载Person类信息(属性和方法信息,指挥加载一次)
  • 2、在堆中分配空间,进行默认初始化(看规则)
  • 3、把地址赋给P,P就指向对象。
  • 4、进行指定初始化,比如p.name = ‘“jack”

Java方法调用机制

  • 1、当程序执行到方法时,就会开辟一个独立的栈空间
  • 2、当方法执行完毕,或者执行到return语句,就会返回
  • 3、返回到调用方法的地方
  • 4、返回后继续执行方法后面的代码
  • 5、当main方法执行完毕,整个程序退出

Java方法使用细节

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

七、==和equals()的区别

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。
1
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
1
2
3
4
5
6
7
8
9
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true

String 中的 equals 方法是被重写过的,因为 Objectequals 方法是比较的对象的内存地址,而 Stringequals 方法比较的是对象的值。

当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

八、方法重载与重写

重载和重写的区别

重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理

重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

重载

发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。

如果多个方法(比如 StringBuilder 的构造方法)有相同的名字、不同的参数, 便产生了重载。

1
2
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder("HelloWorld");

编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数, 就会产生编译时错误, 因为根本不存在匹配, 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution))。

Java 允许重载任何方法, 而不只是构造器方法。

综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理

重写

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

  1. 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  3. 构造方法无法被重写

综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。

区别点 重载方法 重写方法
发生范围 同一个类 子类
参数列表 必须修改 一定不能修改
返回类型 可修改 子类方法返回值类型应比父类方法返回值类型更小或相等
异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期

方法的重写要遵循“两同两小一大”(以下内容摘录自《疯狂 Java 讲义》,issue#892open in new window ):

  • “两同”即方法名相同、形参列表相同;
  • “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
  • “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

⭐️ 关于 重写的返回值类型 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。

详见:Java基础常见知识&面试题总结(上) | JavaGuide

九、构造方法/构造器

基本介绍

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

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

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Constructor{
public static void mian(String[] args){
//当我们new 一个对象时,直接通过构造器指定名字和年龄
Person p1 = new Person("Smith",80);
}
}

class person{
String name;
int age;
//构造器
public Person(String pName,int pAge){
name = pName;
age = pAge;
}
}

构造器细节

  • 一个类可以定义多个不同的构造器,即构造器重载
  • 构造器名和类名要相同(不同的话就会被视为方法)
  • 构造器没有返回值(方法有)
  • 构造器是完成对象的初始化,并不是创建对象(初始化之前,对象已经存在了)
  • 在创建对象时,系统自动的调用该类的构造方法
  • 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造(也叫默认构造器),比如Person(){},使用javap指令反编译可以看到。
  • 一旦定义了自己的构造器,默认的构造器就被覆盖了,不能再使用默认的无参构造器,除非显式定义,即Person(){}

十、对象创建的流程分析

  • 案例流程分析(经典面试题):
1
2
3
4
5
6
7
8
9
10
class Person{
int age = 90;
String name;
Person(String n ,int a){
name = n;//给属性赋值
age = a;//..
}
}

Person p = new Person("云韵",20);

image-20220418143940251

  • 1、加载Person类信息(Person.class),只会加载一次
  • 2、在堆中分配空间(地址)
  • 3、完成对象初始化
    • 3.1、默认初始化:age=0 ,name=null
    • 3.2、显式初始化:age=90,name=null
    • 3.3、构造器的初始化:age=20,name=云韵(这个字符串在常量池)
  • 4、把对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用)

十一、this关键字

什么是this

java虚拟机会给每个对象分配this,代表当前对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Dog{
public String name;
public int age;
public Dog{String name, int in_age}{
this.name = name;
this.age = in_age;
}

public void info(){
System.out.println(this.name+"\t"+this.hashCode());
}
}
//java中可以使用hashCode获取对象的地址,但不是真正的地址(因为java是运行在jvm虚拟机里面的),只是可以视为一个地址)
//使用hashCode()方法

总结:哪个对象调用,this就代表哪个对象

this使用细节

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