【java笔记】final,static,abstract怎么用?java关键字大总结
1. java中都有什么关键字
1)48个关键字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。
1 | 1.访问修饰符的关键字(3个) |
2)2个保留字(现在没用以后可能用到作为关键字):goto、const。
1 | const——常量,常数:用于修改字段或局部变量的声明。 |
3)3个特殊直接量:true、false、null。
1 | null (空值) |
2. final
在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。
https://www.cnblogs.com/dolphin0520/p/3736238.html
2.1 修饰类
当final修饰一个类时,表示这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
好处在于:
确保类的安全性和稳定性,因为其他开发者不能通过继承来修改或扩展类的行为。这在创建核心类库或框架时尤其有用,因为库或框架的设计者可能希望确保某些类的结构和实现不被改变。
在某些情况下,将类声明为
final可能会带来性能上的优化。编译器和虚拟机在处理final类时可以进行一些额外的优化措施,因为它们知道这个类不会有任何子类。例如,静态字段和常量可以被更快地访问,因为它们不需要考虑子类的需求。final类可以与abstract关键字结合使用,创建一个不能被实例化且不能被继承的类。这种情况下,类主要用于定义接口或模板,供其他类实现。同时,final类也可以包含final字段,这样可以保证字段的值在初始化后不可更改。
举个例子,Java中的String类就是final的,这意味着没有其他类可以继承String。这样做的目的是确保字符串的不可变性,因为字符串在Java中是经常使用的对象,其不变性是Java设计的一个重要方面。
2.2 修饰方法
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。
内嵌调用是一种编译优化技术,它允许编译器将一个方法的代码“内嵌”或“展开”到调用该方法的代码中,而不是生成调用该方法的代码。这样做的好处是可以减少运行时的调用开销,因为不需要进行方法调用的额外步骤,如参数传递、返回值处理等。然而,如果final方法的体过大,内嵌可能会导致生成的代码变得非常庞大,这可能会对性能产生负面影响,因为JVM需要更多的时间来解释执行这些字节码。
在现代的Java版本中,由于JVM和即时编译器(JIT)的优化技术已经非常成熟,编译器在决定是否进行内嵌时会更加智能,因此不再需要显式地使用final方法来进行性能优化。编译器和运行时会根据实际情况决定是否采用内嵌调用,以实现最佳性能。
- 类的private方法会隐式地被指定为final方法。
2.3 修饰变量
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
类的final变量和普通变量有什么区别?
当final作用于成员变量,该成员变量必须在定义时或构造器中进行初始化赋值,而final变量一旦被赋值,就不能再次赋值。局部的final变量只需要在使用前进行初始化即可,同理也是不能被二次赋值。
当final变量是基本数据类型和String时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。和C语言中的宏替换有点像。
被final修饰的引用变量指向的对象内容可变吗?
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
final和static
static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。
3. static
static可以修饰类的成员方法或者成员变量,被static修饰的方法或者变量可以在没有创建对象的情况下进行调用。也就是不依赖于对象进行访问,只要类被加载,就可以通过类名进行访问。
static方法
没有this的方法。在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
最常见的static方法就是main方法,程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
static变量
没有this的变量。静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
static代码块
形成类中的静态代码块可以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。当一个类实例化的时候,会调用构造器,但是静态块(static block)会在类的任何构造器(Constructor)被调用之前执行。如果一个类有父类,那么会先加载父类,再加载子类。总的来说,执行顺序是:父类静态块,子类静态块,父类构造器,子类构造器。
static关键字不会影响到变量或者方法的作用域。只有private,protected,public可以影响访问权限。
静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,如果new出一个对象,那么该对象的所有静态方法和静态变量都可以通过对象this访问(只要访问权限足够)。但是会混淆普通和静态,所以不建议这么做。
static关键字不能用于修饰局部变量。
4. abstract
abstract 关键字可以修饰类或方法。
abstract 类可以扩展(增加子类),但不能直接实例化。
abstract 方法不在声明它的类中实现,但必须在某个子类中重写。
abstract 关键字不能应用于 static、private 或 final 方法,因为这些方法不能被重写,因此,不能在子类中实现。
final 类的方法都不能是 abstract,因为 final 类不能有子类。
使用场景
- 当你想要定义一个接口,但同时想要提供一些默认实现时,可以使用抽象类。
- 当你想要强制子类实现特定的方法时,可以使用抽象方法。
- 使用
abstract关键字可以帮助你定义一个模板,强制子类实现特定的方法,同时提供一些默认行为。这有助于实现代码的复用和多态性。
5.interface
定义行为规范:
- 接口允许你定义一个类必须遵守的规则或行为,但不提供实现。这使得接口成为了定义类的行为规范的一种方式。
解耦合:
- 接口提供了一种机制,使得代码可以在不知道具体实现的情况下与组件交互,从而降低了代码之间的耦合度。
支持多重继承:
- Java不支持类的多重继承,但可以通过接口实现类似多重继承的效果。一个类可以实现多个接口,从而继承多个接口中定义的行为。
标记接口:
一些接口只用于标记,不包含任何方法或字段,如
java.io.Serializable和java.lang.Cloneable。Serializable接口并没有任何方法,但是使用了该接口就表明该类可以进行序列化。
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
37
38
39
40
41
42import java.io.*;
public class Employee implements Serializable {
private static final long serialVersionUID = 1L; // 定义序列化版本UID
private String name;
private int age;
private double salary;
// 构造函数、getter和setter省略
// 假设有一个方法来打印员工信息
public void printInfo() {
System.out.println("Name: " + name + ", Age: " + age + ", Salary: " + salary);
}
}
public class Main {
public static void main(String[] args) {
Employee emp = new Employee();
emp.name = "John Doe";
emp.age = 30;
emp.salary = 50000.00;
// 序列化对象
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.ser"))) {
out.writeObject(emp);
System.out.println("Employee object has been serialized");
} catch (IOException i) {
i.printStackTrace();
}
// 反序列化对象
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.ser"))) {
Employee deserializedEmp = (Employee) in.readObject();
System.out.println("Employee object has been deserialized");
deserializedEmp.printInfo();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}


