可变参数
Java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。比如下面这几个方法,都是求和的函数,只不过参数个数不同,那么就可以改成一个方法。
public int sum(int n1, int n2) {
return n1 + n2;
}
public int sum(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
public int sum(int n1, int n2, int n3, int n4) {
return n1 + n2 + n3 + n4;
}
public int sum(int... nums) { //上面三个合成为这个带有可变参数的方法
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
1. 基本语法
2. 使用细节
- 可变参数的实参可以为0个或任意多个。
2. 可变参数的实参可以用数组来表示。Person a = new Person();
int[] b = { 1, 2, 3 };
System.out.println(a.sum(b));
3. 可变参数的本质就是数组,因此可以调用各种数组的方法(比如length等)。
4. 可变参数可以与普通类型的参数一起放在形参列表,但必须保证可变参数在最后。 ```java public int sum(int… nums, double t) { //错误
public int sum(double t, int… nums) { //正确
**5. 一个形参列表只能出现一个可变参数。**
```java
public int sum(int... nums, double... ds) { //错误
作用域
1. 简介
在Java中,主要的变量是 成员变量(也就是属性,也叫全局变量) 和 局部变量。局部变量一般是指在成员方法中定义的变量,成员变量则是在类中定义的变量。
全局变量作用域为整个类,局部变量作用域为定义它的代码块。
class Person {
int num; //全局变量
public void showScore() {
num = 10; //可以直接调用
}
}
public void showScore() {
int score = 100; //定义局部变量
}
public void test() {
score = 20;
//这样就会报错,因为score只在showScore方法中可以使用
}
全局变量可以不赋值直接使用,因为有默认值。而局部变量必须赋值后才能使用,因为没有默认值。
class Person {
int num1;
public void test() {
int num2;
System.out.println(num1); //不会报错
System.out.println(num2); //会报错:没有进行初始化
}
}
2. 使用细节
1. 属性和局部变量可以重名,访问时遵循就近原则。
class Person {
int num1 = 10;
public void test() {
int num1 = 200000;
System.out.println(num1);
//根据就近原则,输入200000
}
}
2. 在同一个作用域中(比如在同一个成员方法里),两个局部变量不能重名。
public void test() {
int num1 = 200000;
int num1 = 300;
//报错
System.out.println(num1);
}
3. 属性的生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁(也即一次方法的调用)。
4. 全局变量(属性)可以被本类使用,也可以被其它类使用(通过对象调用,当然前提得符合修饰符规范),而局部变量只能在本类的对应方法中使用。
class Person {
int num = 10;
}
class Test {
public void showNum(int n) {
System.out.print(n);
}
public static void main(String[] args) {
Person a = new Person();
Test b = new Test();
b.showNum(a.num); //通过对象调用属性,同包下可以调用默认属性
}
}
5. 修饰符不同:全局变量可以加修饰符,局部变量不可以加修饰符(只允许使用final)。
构造方法
1. 简介
构造方法又叫构造器。是类的一种特殊的方法,它的主要作用是完成对新对象的初始化,它有以下特点:1. 方法名和类名相同。 2. 没有返回值。 3. 在创建对象时,系统会自动调用该类的构造器完成对象的初始化。
2. 使用细节
1. 一个类可以定义多个不同的构造器,即构造器重载,系统根据参数不同调用不同的构造器。
2. 构造器名与类名相同,且没有返回值。
3. 构造器是完成对象的初始化,并不是构造对象。
4. 不能自己主动调用构造器,在创建对象时系统自动调用。
5. 如果没有定义构造器,系统会自动给类生成一个默认的无参构造器(也叫默认构造器),比如Person(){},使用 javap指令,可以反编译查看。直接javap + 类名 即可。
class Person {}
6. 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,如果还想使用,那必须使用重载的方法,自己再定义一下。
静态变量
1. 简介
静态变量也叫类变量或静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
public static int a;
2. 类变量内存布局
根据版本的不同,static变量的存储位置可能在堆中也可能在静态域。下面是两个相关的博客:
Java static变量保存在哪?_晚晴小筑-CSDN博客_java static变量存储在哪里
java中的静态变量和Class对象究竟存放在哪个区域? - 知乎
不管static变量在哪里,有几条共识—— 1. static 变量是同一个类所有对象共享。 2. static类变量,在类加载的时候就生成了(因此可以用类名进行调用,即使没有创建对象实例也可以访问)。
3. 如何定义静态变量
语法: 访问修饰符 static 数据类型 静态变量名;(推荐,也可以访问修饰符和static调换位置)
4. 如何访问静态变量
类名.静态变量名(推荐) 或者 对象名.静态变量名
注意静态变量的访问修饰符的访问权限和范围和普通属性相同。
5. 注意事项
- 什么时候需要用静态变量:当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用静态变量:比如定义学生类,统计所有学生共交多少钱。
2. 静态变量与实例变量(普通属性)区别:静态变量是该类的所有对象共享的,而实例变量是每个对象独享的。
3. 加上static称为 类变量 或 静态变量。否则称为实例变量/普通变量/非静态变量。
4. 实例变量不能通过 类名.静态变量名 的方式访问。
5. 静态变量是在类加载时就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用静态变量了。
6. 静态变量的生命周期是随类的加载开始,随着类消亡而销毁。
静态方法
1. 基本介绍
静态方法也叫类方法,形式如下:
访问修饰符 static 数据返回类型 方法名(){ }
//推荐 static 访问修饰符 数据返回类型 方法名(){ }
2. 使用场景
当方法中不涉及到任何和对象相关的成员(也就是不需要声明一个对象),则可以将方法设计成静态方法,提高开发效率,比如Math类,Arrays类的方法等。
在实际开发中,往往会将一些通用的方法设计成静态方法,这样不需要创建对象就可以使用了,比如冒泡排序,打印一维数组等。
3. 注意事项
- 静态方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中无this参数,普通方法中隐含着this参数。
2. 静态方法可以通过类名调用,也可以通过对象名调用(遵循继承规则,只要能继承,可以用父类名调用,也可以用子类名调用)。普通方法只能通过对象名调用。
3. 静态方法中不允许使用和对象有关的关键字,比如this和super。
4. 静态的方法,只能访问静态的成员(没有this,只能访问静态变量和静态方法)。非静态的方法,可以访问静态成员和非静态成员。
5. 不能用普通方法给静态变量赋值。public class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
this.id = total; //错误,不能使用this
this.total = total; //错误,不能使用this
Person.total = total; //正确,如果不重名的话可以不加类名
}
}
public class Person {
public static int id = this.show(); //报错
public int show(){
System.out.println("调用show");
return 10;
}
}
main方法
1. 解释形式
1. public:因为main方法是Java虚拟机进行调用的,因此访问修饰符需要使用public。public static void main(String[] args){}
2. static:Java虚拟机在执行main方法时不必创建对象,所以该方法必须是static。
3. String[] args:main方法接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。public class args{
public static void main(String[] args) {
System.out.println("传入的第一个参数是"+args[0]);
System.out.println("传入的第二个参数是"+args[1]);
System.out.println("传入的第三个参数是"+args[2]);
}
}
在IDEA中传入参数:
2. 特别提示
- 在main方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。 ```java public class Test {
public static double PI = 3.14;
public int ss = 10;
public static void test100(){
} public static void main(String[] args) {System.out.println("调用静态方法");
} }test100();
System.out.println(PI);
System.out.println(ss); //报错
<a name="jzKDN"></a>
##
<a name="KstxL"></a>
## final关键字
**final可以修饰类、属性、方法和局部变量。**
<a name="s2tDj"></a>
### 使用
**1. 当不希望类被继承时,可以用final修饰。**<br />**2. 当不希望父类的某个方法被子类 重写 时,可以用final关键字修饰。**<br />**3. 当不希望类的某个属性的值被修改,可以用final修饰。**<br />**4. 当不希望某个局部变量被修饰,可以使用final修饰。**
```java
public void test(){
final int a = 10;
a= 20; //报错
}
注意事项
- final修饰的属性又叫常量,一般用 XX_XX_XX命名(比如 TAX_RATE),常量名使用全大写。
public final static TAX_RATE = 0.2;
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在下列位置之一:(1)定义时 (2) 在构造器中 (3) 在代码块中(代码块相当于构造器的补充)
3. 如果final修饰的属性是静态的,则初始化位置只能是:(1) 定义时 (2) 在静态代码块,不能在构造器中赋值(因为可以用类名调用静态属性,并没有调用构造器)。
4. final类不能继承,但是可以创建对象(实例化)。
5. 如果类不是final类,但是含有final方法,虽然该方法不能重写,但是可以被继承。public static void main(String[] args) { //a是有test方法的子类
Student a = new Student();
a.test(); //输出父类的test方法
}
- 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法(因为根本就无法继承,也就没办法重写方法了)。
7. final不能修饰构造器。
8. final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理,可以使用 static final 设置一个类常量,类常量的定义位于main方法的外部。 ```java public class Person { public static final double PI = 3.14;
static{
} }System.out.println("类加载,调用静态代码块");
public class Test {
public static void main(String[] args) {
System.out.println(Person.PI); //只输出 3.14,不调用静态代码块
}
}
9. 包装类(Integer,Double,Float,Boolean等)**都是final,String也是final类。**
<a name="fFtVZ"></a>
## 代码块
<a name="STi0O"></a>
### **1. 基本介绍**
代码块又称为初始化块,属于类中的成员(即类的一部分),类似于方法,将逻辑语句封装在方法体中,用{}包围起来。<br />但和方法不同,**没有方法名,没有返回值,没有参数,只有方法体**,而且不用通过对象或类显式调用,而是**加载类时或创建对象时隐式调用**。
<a name="tTmdZ"></a>
### 2. 基本语法
```java
(static){...};
1. static关键字和分号(;)可写可不写。
2. 代码块分为两类,使用static修饰的叫做静态代码块,没有static修饰的叫做普通代码块/非静态代码块。
3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)。
3. 好处
1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作。
2. 场景:如果多个构造器中都有重复的语句,可以抽取到代码块中,提高代码的重用性。
public class Person {
private String name;
private int age;
private String job;
{
System.out.println("输入信息中...");
System.out.println("请稍后...");
System.out.println("信息输入成功");
} //代码块
public Person(String name, int age, String job) {
System.out.println("调用三个参数的构造器");
this.name = name;
this.age = age;
this.job = job;
}
public Person(String name) {
System.out.println("调用一个参数的构造器");
this.name = name;
} //不管用哪个构造器创建对象,都会先调用代码块的内容 }
public class Test {
public static void main(String[] args) {
Person a = new Person("shang",10,"teacher");
}
}
4. 注意事项
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
2. 类什么时候被加载 ——(1) 创建对象实例时(new) (2) 创建子类对象实例,父类也会被加载 (3) 使用类的静态成员时(静态属性,静态方法)。注意只会执行一次。 ```java public class Person {
private String name;
public static int id = 10; //静态属性
public static void show(){ //静态方法
}System.out.println("调用静态方法");
{
}System.out.println("调用普通代码块");
static{
}System.out.println("调用静态代码块");
public Person(String name) {
} }System.out.println("调用构造器");
this.name = name;
public class Test {
public static void main(String[] args) {
Person a = new Person(“Jack”);
System.out.println(Person.id);
Person.show();
}
}
![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. 普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次,就会调用一次(可以理解为是构造器的补充)。**如果只是使用类的静态成员,普通代码块并不会执行。
```java
public class Test {
public static void main(String[] args) {
Person a = new Person("Jack");
Person b = new Person("Tom");
Person c = new Person("Smith");
}
}
4. 创建一个对象时,在一个类中的调用先后顺序:(1) 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用) (2) 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,与静态判断方法相同) (3) 调用构造方法
public class Person {
public static int id = getId(); //静态变量初始化
{
System.out.println("调用普通代码块");
} //普通代码块
public int num = getNum(); //普通变量初始化
static{
System.out.println("调用静态代码块");
} //静态代码块
public static int getId(){
System.out.println("调用静态方法");
return 10;
}
public int getNum(){
System.out.println("调用普通方法");
return 100;
}
public Person() {
System.out.println("调用构造器!");
}
}
首先看静态变量初始化以及静态代码块的位置,发现静态变量初始化在前,因此先加载静态变量初始化,为了更明显的看出先后,因此调用了方法。再分析普通变量初始化和普通代码块,最后是构造器。
public class Test {
public static void main(String[] args) {
Person a = new Person(); //因为是初始化一个对象,因此静态代码块和普通代码块都调用
}
}
5. 构造器的最前面其实 隐含了super()和普通代码块。静态代码块和属性初始化在类加载时就执行完毕了,因此优先于构造器和普通代码块。而构造器执行时先执行super和普通代码块,这与第四点相互印证。
public class Person {
{
System.out.println("调用父类的普通代码块");
}
public Person() {
//super() 隐藏语句,Object类没有输出信息
//调用普通代码块 隐藏语句
System.out.println("调用父类的构造器");
}
}
public class Student extends Person{
{
System.out.println("调用子类的普通代码块");
}
public Student(){
// super() 隐藏语句
// 调用普通代码块 隐藏语句
System.out.println("调用子类的构造器");
}
}
public class Test {
public static void main(String[] args) {
Student a = new Student();
}
}
6. 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员。
7. 静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
public class Test {
static {
i = 0; // 给变量复制可以正常编译通过
System.out.print(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}
5. 调用顺序总结
1. 父类的静态代码块和静态属性(优先级相同,按书写顺序执行)
2. 子类的静态代码块和静态属性(优先级相同,按书写顺序执行)
3. 父类的普通代码块和普通属性初始化(优先级相同,按书写顺序执行)
4. 父类的构造方法
5. 子类的普通代码块和普通属性初始化(优先级相同,按书写顺序执行)
6. 子类的构造方法
注:为了更好看到属性初始化的顺序,一般用方法给属性初始化,方法里加输出。
public static int id = getId(); //静态变量初始化
public static int getId(){
System.out.println("调用静态方法");
return 10;
}