目录
一、static关键字
1、static关键字的基本概念
2、static关键字修饰内部类
3、static关键字修饰方法
4、static关键字修饰变量
5、static关键字修饰代码块
6、static静态导包
7、static小结
二、final关键字
1、修饰类
2、修饰方法
3、修饰变量
4、深入理解final关键字
三、代码块
1、静态代码块
2、构造代码块
3、普通代码块
4、执行顺序
5、父类和子类执行顺序
被static关键字修饰的不需要创建对象去调用,直接根据类名就可以去访问。也就是说,被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
Java内存结构图:
Java堆区 :
栈区:
方法区:
从上图可以知道,静态变量存放在方法区中,并且被所有线程所共享
Java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。下面看看如何使用。
public class test
{
public static class B{
public B()
{
System.out.println("test类的内部类B");
}
public void method()
{
System.out.println("B类的静态内部方法");
}
}
public static void main(String[] args) {
B b = new test.B();
b.method();
}
}
test类的内部类B
B类的静态内部方法
如果没有static修饰B,则只能new一个test类的实例,再通过实例创建内部类。
修饰方法的时候,可以通过类名来直接进行调用
class A{
public static void method()
{
System.out.println("静态方法");
}
}
public class test
{
public static void main(String[] args) {
//直接通过类名调用
A.method();
//先创建对象实例,再通过对象调用
A a = new A();
a.method();
}
}
静态方法
静态方法
被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。
public class test
{
private static String name = "静态变量";
public static void main(String[] args) {
System.out.println(test.name);
}
}
静态变量
静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
执行顺序:
格式为:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
import static java.lang.Math.max;;
public class test
{
public static void main(String[] args) {
int max = max(1,2);
System.out.println(max);
}
}
(1)特点:
(2)成员变量和静态变量的区别:
1、生命周期的不同:
成员变量随着对象的创建而存在随着对象的回收而释放。
静态变量随着类的加载而存在随着类的消失而消失。
2、调用方式不同:
成员变量只能被对象调用。
静态变量可以被对象调用,也可以用类名调用。(推荐用类名调用)
3、别名不同:
成员变量也称为实例变量。
静态变量称为类变量。
4、数据存储位置不同:
成员变量数据存储在堆内存的对象中,所以也叫对象的特有数据。
静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。
(3)静态使用时需要注意的事项:
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
Final修饰的方法不可被重写,如果处于某种原因,不希望子类重写父类的某个方法,则可以使用final关键字修饰该方法。
如果父类中的方法的是公有的,则子类中不能有一个一样方法名,一样参数的方法,但如果父类中的方法是私有的,那么子类中完全可以写一个一样的方法。
对于private类型的方法,由于其只能在当前类中可见,其子类无法访问到该方法,所以子类无法重写该方法,那么,如果子类中存在一个与父类private方法有相同方法名,一样的参数列表,相同的返回值的方法,也不是方法的重写,只是重新定义了一个新的方法。因此,final修饰一个private方法,依然可以在其子类中定义和父类private类型一样的方法,不会有程序错误。
public class PrivateFinalText{
//如果将访问修饰符改成public,则其子类中的方法定义在程序编译时会报错
private final void text();
}
class Sub extends PrivateFinalText{
//下面的方法完全没有问题
public void text();
}
修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
1.类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
}
true
false
为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的 值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));
}
public static String getHello() {
return "hello";
}
}
false
2.被final修饰的引用变量指向的对象内容可变吗?
在上面提到被final修饰的引用变量一旦初始化赋值之后就不能再指向其他的对象,那么该引用变量指向的对象的内容可变吗?看下面这个例子:
public class Test {
public static void main(String[] args) {
final MyClass myClass = new MyClass();
System.out.println(++myClass.i);
}
}
class MyClass {
public int i = 0;
}
1
这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
3.final和static
很多时候会容易把static和final关键字混淆,static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。看下面这个例子:
public class Test {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i);
System.out.println(myClass1.j);
System.out.println(myClass2.i);
System.out.println(myClass2.j);
}
}
class MyClass {
public final double i = Math.random();
public static double j = Math.random();
}
运行这段代码就会发现,每次打印的两个j值都是一样的,而i的值却是不同的。从这里就可以知道final和static变量的区别了。
1、格式:在java类中(方法中不能存在静态代码块)使用static关键字和{}声明的代码块
public class CodeBlock {
static{
System.out.println("静态代码块");
}
}
2、执行时机:静态代码块在类被加载的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,会按照书写顺序依次执行。
3、静态代码块的作用:一般情况下,如果有些代码需要在项目启动的时候就执行,这时候就需要静态代码块。比如一个项目启动需要加载的很多配置文件等资源,我们就可以都放入静态代码块中。
4、静态代码块不能存在任何方法体中:
这个应该很好理解,首先我们要明确静态代码块是在类加载的时候就要运行了。我们分情况讨论:
5、静态代码块不能访问普通变量:普通变量只能通过对象来调用,是不能放在静态代码块中的
1、格式:在java类中使用{}声明的代码块(和静态代码块的区别是少了static关键字)
public class CodeBlock {
static{
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
}
2、执行时机:构造代码块在创建对象时被调用,每次创建对象都会调用一次,但是优先于构造函数执行。需要注意的是,听名字我们就知道,构造代码块不是优先于构造函数执行,而是依托于构造函数,也就是说,如果你不实例化对象,构造代码块是不会执行的。如果存在多个构造代码块,则执行顺序按照书写顺序依次执行。
3、构造代码块的作用:
构造函数的作用类似,都能对对象进行初始化,并且只要创建一个对象,构造代码块都会执行一次。但是反过来,构造函数则不一定每个对象建立时都执行(多个构造函数情况下,建立对象时传入的参数不同则初始化使用对应的构造函数)。利用每次创建对象的时候都会提前调用一次构造代码块特性,我们可以做诸如统计创建对象的次数等功能。
普通代码块和构造代码块的区别是,构造代码块是在类中定义的,而普通代码块是在方法体中定义的。且普通代码块的执行顺序和书写顺序一致
class CodeBlock {
static{
System.out.println("静态代码块2");
}
{
System.out.println("构造代码块");
}
public CodeBlock(){
System.out.println("无参构造函数");
}
public void sayHello(){
{
System.out.println("普通代码块");
}
}
}
public class test
{
static{
System.out.println("静态代码块1");
}
public static void main(String[] args) {
System.out.println("执行了main方法");
new CodeBlock().sayHello();
System.out.println("---------------");
new CodeBlock().sayHello();
}
}
静态代码块1
执行了main方法
静态代码块2
构造代码块
无参构造函数
普通代码块
---------------
构造代码块
无参构造函数
普通代码块
我们创建了两个匿名对象,但是静态代码块只是调用了一次。
对象的初始化顺序:
首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有构造代码块,如果有就执行父类的构造代码块,父类的构造代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有构造代码块,如果有就执行子类的构造代码块。子类的构造代码块执行完毕再去执行子类的构造方法。
总之一句话,静态代码块内容先执行,接着执行父类构造代码块和构造方法,然后执行子类构造代码块和构造方法。
参考:https://www.cnblogs.com/ysocean/p/8194428.html
参考:https://www.cnblogs.com/dolphin0520/p/3736238.html