可变参数

Java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。比如下面这几个方法,都是求和的函数,只不过参数个数不同,那么就可以改成一个方法

  1. public int sum(int n1, int n2) {
  2. return n1 + n2;
  3. }
  4. public int sum(int n1, int n2, int n3) {
  5. return n1 + n2 + n3;
  6. }
  7. public int sum(int n1, int n2, int n3, int n4) {
  8. return n1 + n2 + n3 + n4;
  9. }
  10. public int sum(int... nums) { //上面三个合成为这个带有可变参数的方法
  11. int sum = 0;
  12. for (int i = 0; i < nums.length; i++) {
  13. sum += nums[i];
  14. }
  15. return sum;
  16. }

1. 基本语法

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

2. 使用细节

  1. 可变参数的实参可以为0个或任意多个。
    2. 可变参数的实参可以用数组来表示。
    1. Person a = new Person();
    2. int[] b = { 1, 2, 3 };
    3. System.out.println(a.sum(b));
    image.png
    3. 可变参数的本质就是数组,因此可以调用各种数组的方法(比如length等)。
    4. 可变参数可以与普通类型的参数一起放在形参列表,但必须保证可变参数在最后。 ```java public int sum(int… nums, double t) { //错误

public int sum(double t, int… nums) { //正确

  1. **5. 一个形参列表只能出现一个可变参数。**
  2. ```java
  3. public int sum(int... nums, double... ds) { //错误

作用域

1. 简介

在Java中,主要的变量是 成员变量(也就是属性,也叫全局变量)局部变量局部变量一般是指在成员方法中定义的变量,成员变量则是在类中定义的变量
全局变量作用域为整个类,局部变量作用域为定义它的代码块。

  1. class Person {
  2. int num; //全局变量
  3. public void showScore() {
  4. num = 10; //可以直接调用
  5. }
  6. }
  7. public void showScore() {
  8. int score = 100; //定义局部变量
  9. }
  10. public void test() {
  11. score = 20;
  12. //这样就会报错,因为score只在showScore方法中可以使用
  13. }

全局变量可以不赋值直接使用,因为有默认值。而局部变量必须赋值后才能使用,因为没有默认值。

  1. class Person {
  2. int num1;
  3. public void test() {
  4. int num2;
  5. System.out.println(num1); //不会报错
  6. System.out.println(num2); //会报错:没有进行初始化
  7. }
  8. }

2. 使用细节

1. 属性和局部变量可以重名,访问时遵循就近原则。

  1. class Person {
  2. int num1 = 10;
  3. public void test() {
  4. int num1 = 200000;
  5. System.out.println(num1);
  6. //根据就近原则,输入200000
  7. }
  8. }

2. 同一个作用域中(比如在同一个成员方法里),两个局部变量不能重名

  1. public void test() {
  2. int num1 = 200000;
  3. int num1 = 300;
  4. //报错
  5. System.out.println(num1);
  6. }

3. 属性的生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁(也即一次方法的调用)。
4. 全局变量(属性)可以被本类使用,也可以被其它类使用(通过对象调用,当然前提得符合修饰符规范),而局部变量只能在本类的对应方法中使用。

  1. class Person {
  2. int num = 10;
  3. }
  4. class Test {
  5. public void showNum(int n) {
  6. System.out.print(n);
  7. }
  8. public static void main(String[] args) {
  9. Person a = new Person();
  10. Test b = new Test();
  11. b.showNum(a.num); //通过对象调用属性,同包下可以调用默认属性
  12. }
  13. }

5. 修饰符不同:全局变量可以加修饰符,局部变量不可以加修饰符(只允许使用final)。

构造方法

1. 简介

构造方法又叫构造器。是类的一种特殊的方法,它的主要作用是完成对新对象的初始化,它有以下特点:1. 方法名和类名相同。 2. 没有返回值。 3. 在创建对象时,系统会自动调用该类的构造器完成对象的初始化。

2. 使用细节

1. 一个类可以定义多个不同的构造器,即构造器重载,系统根据参数不同调用不同的构造器。
2. 构造器名与类名相同,且没有返回值。
3. 构造器是完成对象的初始化,并不是构造对象。
4. 不能自己主动调用构造器,在创建对象时系统自动调用。
5. 如果没有定义构造器,系统会自动给类生成一个默认的无参构造器(也叫默认构造器),比如Person(){},使用 javap指令,可以反编译查看。直接javap + 类名 即可。

  1. class Person {}

image.png 6. 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,如果还想使用,那必须使用重载的方法,自己再定义一下。

静态变量

1. 简介

静态变量也叫类变量或静态属性,是该类的所有对象共享的变量任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

  1. public static int a;

2. 类变量内存布局

image.png
根据版本的不同,static变量的存储位置可能在堆中也可能在静态域。下面是两个相关的博客:
Java static变量保存在哪?_晚晴小筑-CSDN博客_java static变量存储在哪里
java中的静态变量和Class对象究竟存放在哪个区域? - 知乎
不管static变量在哪里,有几条共识—— 1. static 变量是同一个类所有对象共享。 2. static类变量,在类加载的时候就生成了(因此可以用类名进行调用,即使没有创建对象实例也可以访问)。

3. 如何定义静态变量

语法: 访问修饰符 static 数据类型 静态变量名;推荐,也可以访问修饰符和static调换位置)

4. 如何访问静态变量

类名.静态变量名(推荐) 或者 对象名.静态变量名
注意静态变量的访问修饰符的访问权限和范围和普通属性相同。

5. 注意事项

  1. 什么时候需要用静态变量:当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用静态变量:比如定义学生类,统计所有学生共交多少钱。
    2. 静态变量与实例变量(普通属性)区别:静态变量是该类的所有对象共享的,而实例变量是每个对象独享的。
    3. 加上static称为 类变量 或 静态变量。否则称为实例变量/普通变量/非静态变量
    4. 实例变量不能通过 类名.静态变量名 的方式访问。
    5. 静态变量是在类加载时就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用静态变量了。
    6. 静态变量的生命周期是随类的加载开始,随着类消亡而销毁。

静态方法

1. 基本介绍

静态方法也叫类方法,形式如下:

  1. 访问修饰符 static 数据返回类型 方法名(){ }
  2. //推荐 static 访问修饰符 数据返回类型 方法名(){ }

使用方式: 类名.静态方法名 或 对象名.静态方法名。

2. 使用场景

当方法中不涉及到任何和对象相关的成员也就是不需要声明一个对象),则可以将方法设计成静态方法,提高开发效率,比如Math类,Arrays类的方法等
在实际开发中,往往会将一些通用的方法设计成静态方法,这样不需要创建对象就可以使用了,比如冒泡排序,打印一维数组等

3. 注意事项

  1. 静态方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中无this参数,普通方法中隐含着this参数。
    2. 静态方法可以通过类名调用,也可以通过对象名调用(遵循继承规则,只要能继承,可以用父类名调用,也可以用子类名调用)。普通方法只能通过对象名调用。
    3. 静态方法中不允许使用和对象有关的关键字,比如this和super。
    4. 静态的方法,只能访问静态的成员(没有this,只能访问静态变量和静态方法)。非静态的方法,可以访问静态成员和非静态成员。
    1. public class Person {
    2. private int id;
    3. private static int total = 0;
    4. public static void setTotalPerson(int total){
    5. this.id = total; //错误,不能使用this
    6. this.total = total; //错误,不能使用this
    7. Person.total = total; //正确,如果不重名的话可以不加类名
    8. }
    9. }
    5. 不能用普通方法给静态变量赋值。
    1. public class Person {
    2. public static int id = this.show(); //报错
    3. public int show(){
    4. System.out.println("调用show");
    5. return 10;
    6. }
    7. }

    main方法

    1. 解释形式

    1. public static void main(String[] args){}
    1. public:因为main方法是Java虚拟机进行调用的,因此访问修饰符需要使用public。
    2. static:Java虚拟机在执行main方法时不必创建对象,所以该方法必须是static。
    3. String[] args:main方法接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
    image.png
    1. public class args{
    2. public static void main(String[] args) {
    3. System.out.println("传入的第一个参数是"+args[0]);
    4. System.out.println("传入的第二个参数是"+args[1]);
    5. System.out.println("传入的第三个参数是"+args[2]);
    6. }
    7. }
    image.png
    在IDEA中传入参数:
    image.png
    image.png

    2. 特别提示

  2. 在main方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
    2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。 ```java public class Test {
    public static double PI = 3.14;
    public int ss = 10;
    public static void test100(){
    1. System.out.println("调用静态方法");
    } public static void main(String[] args) {
    1. test100();
    2. System.out.println(PI);
    3. System.out.println(ss); //报错
    } }
  1. <a name="jzKDN"></a>
  2. ##
  3. <a name="KstxL"></a>
  4. ## final关键字
  5. **final可以修饰类、属性、方法和局部变量。**
  6. <a name="s2tDj"></a>
  7. ### 使用
  8. **1. 当不希望类被继承时,可以用final修饰。**<br />**2. 当不希望父类的某个方法被子类 重写 时,可以用final关键字修饰。**<br />**3. 当不希望类的某个属性的值被修改,可以用final修饰。**<br />**4. 当不希望某个局部变量被修饰,可以使用final修饰。**
  9. ```java
  10. public void test(){
  11. final int a = 10;
  12. a= 20; //报错
  13. }

注意事项

  1. final修饰的属性又叫常量,一般用 XX_XX_XX命名(比如 TAX_RATE),常量名使用全大写
    1. public final static TAX_RATE = 0.2;
  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在下列位置之一:(1)定义时 (2) 在构造器中 (3) 在代码块中(代码块相当于构造器的补充)
    3. 如果final修饰的属性是静态的,则初始化位置只能是:(1) 定义时 (2) 在静态代码块,不能在构造器中赋值(因为可以用类名调用静态属性,并没有调用构造器)。
    4. final类不能继承,但是可以创建对象(实例化)。
    5. 如果类不是final类,但是含有final方法,虽然该方法不能重写,但是可以被继承。
    1. public static void main(String[] args) { //a是有test方法的子类
    2. Student a = new Student();
    3. a.test(); //输出父类的test方法
    4. }
  3. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法(因为根本就无法继承,也就没办法重写方法了)。
    7. final不能修饰构造器。
    8. final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理,可以使用 static final 设置一个类常量,类常量的定义位于main方法的外部。 ```java public class Person { public static final double PI = 3.14;
    static{
    1. System.out.println("类加载,调用静态代码块");
    } }

public class Test {
public static void main(String[] args) { System.out.println(Person.PI); //只输出 3.14,不调用静态代码块
} }

  1. 9. 包装类(Integer,Double,Float,Boolean等)**都是finalString也是final类。**
  2. <a name="fFtVZ"></a>
  3. ## 代码块
  4. <a name="STi0O"></a>
  5. ### **1. 基本介绍**
  6. 代码块又称为初始化块,属于类中的成员(即类的一部分),类似于方法,将逻辑语句封装在方法体中,用{}包围起来。<br />但和方法不同,**没有方法名,没有返回值,没有参数,只有方法体**,而且不用通过对象或类显式调用,而是**加载类时或创建对象时隐式调用**。
  7. <a name="tTmdZ"></a>
  8. ### 2. 基本语法
  9. ```java
  10. (static){...};

1. static关键字和分号(;)可写可不写。
2. 代码块分为两类,使用static修饰的叫做静态代码块,没有static修饰的叫做普通代码块/非静态代码块
3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)。

3. 好处

1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作。
2. 场景:如果多个构造器中都有重复的语句,可以抽取到代码块中,提高代码的重用性。

  1. public class Person {
  2. private String name;
  3. private int age;
  4. private String job;
  5. {
  6. System.out.println("输入信息中...");
  7. System.out.println("请稍后...");
  8. System.out.println("信息输入成功");
  9. } //代码块
  10. public Person(String name, int age, String job) {
  11. System.out.println("调用三个参数的构造器");
  12. this.name = name;
  13. this.age = age;
  14. this.job = job;
  15. }
  16. public Person(String name) {
  17. System.out.println("调用一个参数的构造器");
  18. this.name = name;
  19. } //不管用哪个构造器创建对象,都会先调用代码块的内容 }
  20. public class Test {
  21. public static void main(String[] args) {
  22. Person a = new Person("shang",10,"teacher");
  23. }
  24. }

image.png

4. 注意事项

  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行
    2. 类什么时候被加载 ——(1) 创建对象实例时(new) (2) 创建子类对象实例,父类也会被加载 (3) 使用类的静态成员时(静态属性,静态方法)。注意只会执行一次。 ```java public class Person {
    private String name;
    public static int id = 10; //静态属性
    public static void show(){ //静态方法
    1. System.out.println("调用静态方法");
    }
    {
    1. System.out.println("调用普通代码块");
    }
    static{
    1. System.out.println("调用静态代码块");
    }
    public Person(String name) {
    1. System.out.println("调用构造器");
    2. this.name = name;
    } }

public class Test {
public static void main(String[] args) { Person a = new Person(“Jack”);
System.out.println(Person.id);
Person.show();
} }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23175776/1641721713547-a230608c-cdc9-405d-bf28-5f03e66c8514.png#clientId=u13ae415c-e9de-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=161&id=u7cf477e5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=389&originWidth=923&originalType=url&ratio=1&rotation=0&showTitle=false&size=32201&status=done&style=none&taskId=uf9476abd-a44d-4e42-a4ed-ab01243fc70&title=&width=382)![image.png](https://cdn.nlark.com/yuque/0/2022/gif/23175776/1641721712928-8d83c3ab-d142-45d3-aa3e-d91cf35081cd.gif#clientId=u13ae415c-e9de-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u23574132&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1&originWidth=1&originalType=url&ratio=1&rotation=0&showTitle=false&size=43&status=done&style=none&taskId=ud92616cd-834d-497a-9f75-193bbbdcfbb&title=)<br />发现在创建对象时,静态代码块和普通代码块都调用了,**因为静态代码块已经调用过一次了,因此调用静态成员时没有调用静态代码块。**<br />**3. 普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次,就会调用一次(可以理解为是构造器的补充)。**如果只是使用类的静态成员,普通代码块并不会执行。
  2. ```java
  3. public class Test {
  4. public static void main(String[] args) {
  5. Person a = new Person("Jack");
  6. Person b = new Person("Tom");
  7. Person c = new Person("Smith");
  8. }
  9. }

image.png
4. 创建一个对象时,在一个类中的调用先后顺序(1) 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用 (2) 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,与静态判断方法相同) (3) 调用构造方法

  1. public class Person {
  2. public static int id = getId(); //静态变量初始化
  3. {
  4. System.out.println("调用普通代码块");
  5. } //普通代码块
  6. public int num = getNum(); //普通变量初始化
  7. static{
  8. System.out.println("调用静态代码块");
  9. } //静态代码块
  10. public static int getId(){
  11. System.out.println("调用静态方法");
  12. return 10;
  13. }
  14. public int getNum(){
  15. System.out.println("调用普通方法");
  16. return 100;
  17. }
  18. public Person() {
  19. System.out.println("调用构造器!");
  20. }
  21. }

首先看静态变量初始化以及静态代码块的位置,发现静态变量初始化在前,因此先加载静态变量初始化为了更明显的看出先后,因此调用了方法。再分析普通变量初始化和普通代码块,最后是构造器。

  1. public class Test {
  2. public static void main(String[] args) {
  3. Person a = new Person(); //因为是初始化一个对象,因此静态代码块和普通代码块都调用
  4. }
  5. }

image.png
5. 构造器的最前面其实 隐含了super()和普通代码块。静态代码块和属性初始化在类加载时就执行完毕了,因此优先于构造器和普通代码块。而构造器执行时先执行super和普通代码块,这与第四点相互印证。

  1. public class Person {
  2. {
  3. System.out.println("调用父类的普通代码块");
  4. }
  5. public Person() {
  6. //super() 隐藏语句,Object类没有输出信息
  7. //调用普通代码块 隐藏语句
  8. System.out.println("调用父类的构造器");
  9. }
  10. }
  11. public class Student extends Person{
  12. {
  13. System.out.println("调用子类的普通代码块");
  14. }
  15. public Student(){
  16. // super() 隐藏语句
  17. // 调用普通代码块 隐藏语句
  18. System.out.println("调用子类的构造器");
  19. }
  20. }
  21. public class Test {
  22. public static void main(String[] args) {
  23. Student a = new Student();
  24. }
  25. }

image.png
6. 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员。
7. 静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。

  1. public class Test {
  2. static {
  3. i = 0; // 给变量复制可以正常编译通过
  4. System.out.print(i); // 这句编译器会提示“非法向前引用”
  5. }
  6. static int i = 1;
  7. }

5. 调用顺序总结

1. 父类的静态代码块和静态属性(优先级相同,按书写顺序执行)
2. 子类的静态代码块和静态属性(优先级相同,按书写顺序执行)
3. 父类的普通代码块和普通属性初始化(优先级相同,按书写顺序执行)
4. 父类的构造方法
5. 子类的普通代码块和普通属性初始化(优先级相同,按书写顺序执行)
6. 子类的构造方法
注:为了更好看到属性初始化的顺序,一般用方法给属性初始化,方法里加输出。

  1. public static int id = getId(); //静态变量初始化
  2. public static int getId(){
  3. System.out.println("调用静态方法");
  4. return 10;
  5. }

image.png