关键字
目录
static关键字
static描述
Java中static表示全局或者静态的意思,可以用来修饰成员变量、成员方法、代码块、内部类和导包。在Java中并不存在全局变量的概念,但是我们可以通过static来实现一个“伪全局”的概念,被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载了,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象,所以被static修饰的成员变量和成员方法可以直接使用类名调用。
static变量
static修饰的成员变量称作静态变量,静态变量被所有的对象所共享,在内存中只有一个,它会随着类的加载而加载。static是不允许用来修饰局部变量。
静态变量和非静态变量区别:
-
静态变量(类变量):静态变量被所有的对象所共享,也就是说我们创建了一个类的多个对象,多个对象共享着一个静态变量,如果我们修改了静态变量的值,那么其他对象的静态变量也会随之修改。
-
非静态变量(实例变量):如果我们创建了一个类的多个对象,那么每个对象都有它自己该有的非静态变量。当你修改其中一个对象中的非静态变量时,不会引起其他对象非静态变量值得改变。
static方法
static修饰的成员方法称作静态方法,这样我们就可以通过“类名. 方法名”进行调用。由于静态方法在类加载的时候就存在了,所以它不依赖于任何对象的实例就可以进行调用,因此对于静态方法而言,是木有当前对象的概念,即没有this、super关键字的。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
并且由于独立于任何实例,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
static代码块
static修饰的代码块也叫静态代码块,会随着JVM加载类的时候而加载这些静态代码块,并且会自动执行。它们可以有多个,可以存在于该类的任何地方。JVM会按照它们的先后顺序依次执行它们,而且每个静态代码块只会被初始化一次,不会进行多次初始化。
类加载顺序
1.首先加载父类的静态字段或者静态语句块
2.子类的静态字段或静态语句块
3.父类普通变量以及语句块
4.父类构造方法被加载
5.子类变量或者语句块被加载
6.子类构造方法被加载
注意:
-
当class文件被加载进内存,开始初始化的时候,被static修饰的变量或者方法即被分配了内存,而其他变量是在对象被创建后,才被分配了内存的。
-
静态代码块(只加载一次)
-
构造方法(创建一个实例就加载一次)
-
静态方法,调用的时候才会加载,不调用的时候不会加载
-
静态变量和静态代码块的加载顺序由编写先后决定
static内部类
前面类中有所说明。
static导包
用static修饰导包的格式是 import static 包名,static不能写在import前面,这样可以指定导入某个类中的指定静态资源,并且不需要使用类名.资源名,可以直接使用资源名。
Web应用项目案例:
// 静态导入com.base.BaseOutputBeanFactory包,在调用BaseOutputBeanFactory类的静态方法不用在方法名称前加类名称。 import static com.base.BaseOutputBeanFactory.*; @Slf4j @RestController public class BaseContoller { private BaseService baseService; @Autowired public void setBaseService(BaseService baseService) { this.baseService = baseService; } @RequestMapping(value = "/page") public BaseOutput page(@RequestBody SysUserInput input) { return getSuccessBaseOutput(baseService.page(input)); } @RequestMapping(value = "/list") public BaseOutput list(@RequestBody QuerySysUserInput input) { return getSuccessBaseOutput(baseService.selectList(input)); } }
static总结
static是非常重要的一个关键字,它的用法也很丰富,以下总结为:
①、static可以用来修饰成员变量、成员方法、代码块、内部类和导包。
②、用来修饰成员变量,将其变为静态变量,从而实现所有对象对于该成员的共享,通过“类名.变量名”即可调用。
③、用来修饰成员方法,将其变为静态方法,可以直接使用“类名.方法名”的方式调用,常用于工具类。
④、静态方法中不能使用关键字this和super 。
⑤、静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键。
⑥、静态代码块是先加载父类的静态代码块,然后再加载子类静态代码块,而且只会加载一次。
⑦、static修饰成员变量、成员方法、代码块会随着类的加载而加载。
⑧、静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便,但是代码的可读性降低。
final关键字
final描述
final从字面意思是不可更改的,最终的意思,它可以用来修饰类、成员方法、变量(包括成员变量和局部变量)、参数。
-
final修饰类:表示不能被继承,没有子类,final类中的方法默认是final的;
-
final修饰方法:表示不能被重写,但可以被继承,可以被重载,这里很多人会弄混;
-
final修饰成员变量:表示常量,只能被赋值一次,赋值后值不再改变,变量是只读的。final变量经常和static关键字一起使用,作为常量。
-
final修饰参数(形参):表明该形参的值不能被修改,即不能被重新赋值了;
final修饰类
final修饰类,表示该类不能再被继承了。如果一个类中的功能是完整的,那么我们可以将该类用final来修饰,它不需要被继承来扩展别的功能了。我们最常用String、Integer、System等类就是用final修饰的,其不能再被其它类所继承。
final修饰方法
final修饰方法,表示这个方法不能被子类方法重写。如果你认为一个方法的功能已经足够完整了,不需要再去子类中扩展的话,我们可以声明此方法为final。比如Object类中getClass()方法就是final修饰的。
final修饰变量
final修饰变量表示是常量,一般都和static一起使用,用来定义全局常量。final无论修饰的是成员变量还是局部变量,都必须进行显式初始化。
final修饰参数
final修饰参数准确的来说应该是修饰形参,表明该形参的值不能被修改。使用final修饰形参时,表明此形参是一个常量。当我们调用方法的时候,给形参进行赋值,一旦赋值以后,就只能在该方法使用,而且不能进行重新赋值操作,否则编译报错。如果形参是引用类型,则引用变量不能再指向其他对象,但是该对象的内容是可以改变的。
finally关键字
finally描述
finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。
finally使用场景
-
资源释放:finally块经常用于释放打开的资源,如文件、数据库连接、网络连接等。无论是否发生异常,我们都希望在使用完资源后将其关闭或释放,以避免资源泄漏。
// IO流使用原则是先开后关,后面会详细讲解。 FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream("file.txt"); // 使用fileInputStream读取文件内容 } catch (IOException e) { // 处理异常 } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { // 处理关闭文件流时的异常 } } }
-
数据库事务处理:在数据库操作中,我们通常使用try-catch-finally结构来处理事务。在finally块中,我们可以确保无论是否发生异常,都会执行事务的提交或回滚操作,以保持数据的一致性。
Connection connection = null; try { connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password"); // 执行数据库操作 connection.commit(); } catch (SQLException e) { // 处理数据库操作异常 if (connection != null) { try { connection.rollback(); } catch (SQLException ex) { // 处理回滚异常 } } } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { // 处理关闭连接时的异常 } } }
-
资源回收:finally块还可以用于释放其他类型的资源,如线程、锁等。在finally块中,我们可以确保在使用完资源后进行适当的清理和释放。
Lock lock = new ReentrantLock(); try { lock.lock(); // 执行需要加锁的操作 } finally { lock.unlock(); }
-
异常处理:finally块可以用于处理异常后的一些清理工作,例如记录日志、发送通知等。无论是否发生异常,finally块中的代码都会被执行。
finally是否会执行
-
finally不一定会执行,在执行try块之前直接return,我们发现finally块是不会执行的。
public class TryCatchTest { private static int total() { int i = 11; if (i == 11) { return i; } try { System.out.println("执行try"); } finally { System.out.println("执行finally"); } return 0; } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行main:11
-
在执行try块之前制造一个错误,直接爆红。
public class TryCatchTest { private static int total() { return 1 / 0; try { System.out.println("执行try"); //这行爆红,原因就是无法访问 } finally { System.out.println("执行finally"); } return 0; } public static void main(String[] args) { System.out.println("执行main:" + total()); } }
-
执行了try块,finally块不一定会执行。
public class TryCatchTest { private static int total() { try { System.out.println("执行try"); System.exit(0); } catch (Exception e) { System.out.println("执行catch"); } finally { System.out.println("执行finally"); } return 0; } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行try
finally执行时机
-
finally块执行在try块的return之前。
public class TryCatchTest { private static int total() { try { System.out.println("执行try"); return 11; } finally { System.out.println("执行finally"); } } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行try 执行finally 执行main:11
-
finally执行在catch块return的执行前。
public class TryCatchTest { private static int total() { try { System.out.println("执行try"); return 1 / 0; } catch (Exception e) { System.out.println("执行catch"); return 11; } finally { System.out.println("执行finally"); } } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行try 执行catch 执行finally 执行main:11
-
finally块的执行时机应该是return之前,那理论上我们应该先
++i
使得i等于1
,在执行return i
; 自然会返回1。可是结果却返回了0,这是因为Java程序会把try或者catch块中的返回值保留,也就是暂时的确认了返回值,然后再去执行finally代码块中的语句。等到finally代码块执行完毕后,如果finally块中没有返回值的话,就把之前保留的返回值返回出去。public class TryCatchTest { private static int total() { int i = 0; try { System.out.println("执行try:" + i); return i; } finally { ++i; System.out.println("执行finally:" + i); } } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行try:0 执行finally:1 执行main:0
-
含有finally块的方法返回值时,要对于return出现的地方进行具体分析。在finally块中进行return操作的话,则方法整体的返回值就是finally块中的return返回值。如果在finally块之后的方法内return,则return的值就是进行完上面的操作后的return值。
public class TryCatchTest { private static int total() { try { System.out.println("执行try"); return 1; } finally { System.out.println("执行finally"); return 2; } } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行try 执行finally 执行main:2
public class TryCatchTest { private static int total() { int i = 1; try { System.out.println("执行try:" + i); return i; } finally { ++i; System.out.println("执行finally:" + i); return i; } } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 输出结果: 执行try:1 执行finally:2 执行main:2
public class TryCatchTest { private static int total() { int i = 1; try { System.out.println("执行try:" + i); } finally { ++i; System.out.println("执行finally:" + i); } return i; } public static void main(String[] args) { System.out.println("执行main:" + total()); } } 执行结果: 执行try:1 执行finally:2 执行main:2
this关键字
this描述
表示当前对象的引用或正在创建的对象。可用于调用本类的属性、方法、构造器。
this使用场景
-
引用当前对象的成员变量:使用this关键字可以引用当前对象的成员变量。当成员变量与方法参数或局部变量同名时,使用this关键字可以明确指定要引用的是成员变量而不是方法参数或局部变量。this成员变量会先从本类声明的成员变量列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员变量列表中查找。
public class BaseJavaSEApplication { private String param; public BaseJavaSEApplication(String param) { this.param = param; } }
-
调用当前对象的其他方法:使用this关键字可以调用当前对象的其他方法。当前对象的成员方法,先从本类声明的成员方法列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员方法列表中查找。
public class BaseJavaSEApplication extends BaseJava { public void test() { //this可写可不写,在没有指定this、super时先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯 this.show(); //super调用当前对象父类的方法 super.show(); } @Override public void show() { System.out.println("this..."); } } class BaseJava { public void show() { System.out.println("super..."); } }
-
在构造方法中调用其他构造方法:使用this关键字可以在一个构造方法中调用同一个类的其他构造方法,调用时要放在构造方法的首行。
public class BaseJavaSEApplication { private String param; public BaseJavaSEApplication() { this("param"); } public BaseJavaSEApplication(String param) { this.param = param; } }
-
返回当前对象:在某些情况下,我们可能需要在方法中返回当前对象。使用return this;语句可以返回当前对象的引用,从而支持链式调用。
-
匿名内部类中的引用:在匿名内部类中,如果需要引用外部类的实例,可以使用外部类名.this来获取外部类的引用。
super关键字
super描述
super是用于在当前类中访问父类的一个特殊关键字,可用于调用父类的属性、方法、构造器,它不是对象的引用,在子类中使用,不能在静态代码块和静态方法中使用super。
super使用场景
-
super可用于访问父类中定义的属性。在子类中访问父类的成员变量,如果是当子类的成员变量与父类的成员变量重名时,必定是调用父类的成员变量
public class ParentClass { public int parentVariable; } public class ChildClass extends ParentClass { public void method() { // 引用父类的成员变量 int parentVariable = super.parentVariable; } }
-
super可用于调用父类中定义的成员方法。在子类中调用父类的成员方法,如果是当子类重写了父类的成员方法时,必定是调用父类的成员方法。
public class ParentClass { public void method() { System.out.println("Parent..."); } } public class ChildClass extends ParentClass { public void method() { super.method(); // 调用父类的method方法 } }
-
super可用于在子类构造器中调用父类的构造器,调用时要放在构造方法的首行。
public class ParentClass { private int parentVariable; public ParentClass(int parentVariable) { this.parentVariable = parentVariable; } } public class ChildClass extends ParentClass { public ChildClass() { super(); // 调用父类的无参构造方法 } }
this、super小结
-
在使用this、super调用构造器的时候,this、super语句必须放在构造方法的第一行,否则编译会报错。
-
由于this、super关键字调用构造器的时候都必须出现在第一行,所以它两不能同时出现。
-
不能在子类中使用父类构造方法名来调用父类构造方法,因为父类的构造方法不被子类继承。
-
调用父类的构造方法的唯一途径是使用 super 关键字,如果子类中没显式调用,则编译器自动调用 super(),也就是说会一直调到Object类,因为Object是所有类的父类。
-
子类会默认使用super()调用父类默认构造器,如果父类没有默认构造器,则子类必须显式的使用super()来调用父类构造器。
-
super和this都不能出现在静态方法和静态代码块中,因为super和this都是存在于对象中的
代码块
普通代码块
在类中方法的方法体中用{}括起来的代码,通常也叫局部代码块。代码块内的变量作用域仅限于代码块内部,代码块能访问代码块外部的变量。
public class Test { public static void main(String[] args) { // 普通代码块 { int x = 10; System.out.println("x = " + x); } // 在普通代码块外无法访问x变量 // System.out.println("x = " + x); // 编译错误 // 在普通代码块外可以访问其他变量 int y = 20; System.out.println("y = " + y); { System.out.println("y = " + y); } } } 结果: x = 10 y = 20 y = 20
构造代码块
直接在类中用"{}"定义且不加任何关键字的代码块称为构造代码块。构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。如果存在多个构造代码块,执行顺序由他们在代码中出现的次序决定,先出现先执行。构造代码块(Constructor Block)是在Java类中用来初始化实例变量的一种特殊代码块。
public class BaseJavaSEApplication { private int x; // 构造代码块 { x = 10; System.out.println("构造代码块1被执行x="+x); } // 构造代码块 { x = 20; System.out.println("构造代码块2被执行x="+x); } // 构造函数 public BaseJavaSEApplication() { System.out.println("无参构造函数被调用"); } public BaseJavaSEApplication(int value) { System.out.println("有参构造函数被调用"); x = value; } public static void main(String[] args) { BaseJavaSEApplication obj1 = new BaseJavaSEApplication(); // 创建对象时构造代码块被执行 System.out.println("x = " + obj1.x); BaseJavaSEApplication obj2 = new BaseJavaSEApplication(20); // 创建对象时构造代码块被执行 System.out.println("x = " + obj2.x); } } 结果: 构造代码块1被执行x=10 构造代码块2被执行x=20 无参构造函数被调用 x = 20 构造代码块1被执行x=10 构造代码块2被执行x=20 有参构造函数被调用 x = 20
原理:
其实构造代码块并不是真正的在构造方法之前执行的,而是在构造方法中执行的。JVM在编译的时候会把构造代码块插入到每个构造函数的最前面,构造代码块随着构造方法的执行而执行,如果某个构造方法调用了其他的构造方法,那么构造代码块不会插入到该构造方法中以免构造代码块执行多次。可以从编译后生成的class文件可以看出来。
静态代码块
在类中用static关键字修饰的代码块叫静态代码块。每个静态代码块只会执行一次,并且由于JVM在加载类时会加载静态代码块,所以静态代码块先于主方法执行。如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。
public class BaseJavaSEApplication extends BaseJavaSE { static { System.out.println("BaseJavaSEApplication类静态代码块"); } public BaseJavaSEApplication() { System.out.println("BaseJavaSEApplication类构造器"); } public static void main(String[] args) { new BaseJavaSEApplication(); } } class BaseJavaSE { static { System.out.println("BaseJavaSE类静态代码块"); } public BaseJavaSE() { System.out.println("BaseJavaSE类构造器"); } } 结果: BaseJavaSE类静态代码块 BaseJavaSEApplication类静态代码块 BaseJavaSE类构造器 BaseJavaSEApplication类构造器
静态代码块特点:
-
静态代码块不能存在于任何方法体内。
-
静态代码块不能直接访问实例变量和实例方法,需要通过类的实例对象来访问。
-
静态代码块是先加载父类的静态代码块,然后再加载子类静态代码块,是随着类的加载而加载,而且只会加载一次。
同步代码块
同步代码块(Synchronized Block)是在Java中用于实现线程同步的一种机制。它可以确保在同一时间只有一个线程可以访问被同步的代码块,从而避免多个线程同时访问共享资源导致的数据不一致或竞态条件的问题。同步代码块使用关键字 synchronized 来修饰,并使用一对花括号 {} 将代码块括起来。在同步代码块中,我们需要指定一个对象作为锁,这个对象可以是任意的Java对象。当一个线程进入同步代码块时,它会尝试获取锁,如果锁已经被其他线程持有,则该线程会被阻塞,直到锁被释放。
public class BaseJavaSEApplication { private Object lock = new Object(); public void increment() { //同步代码块 synchronized (lock) { } } }
代码块执行顺序
静态代码块 > mian()方法 > 构造代码块 > 构造方法 > 局部代码块。其中静态代码块只执行一次。构造代码块和局部代码块再每次创建对象是都会执行。