修饰符

final

  • 对于基本类型,final使数值不变
  • 对于引用类型,final使引用不变,也就是不能引用其他对象:对其初始化之后便不能再让其指向另一个对象。
    • image.png
  • 对于方法,则声明的方法不能被子类重写(但是可以被子类访问,前提是父类的final方法不是private的)
  • 对于final类,则不允许被继承
  1. 在final修饰变量的时候:
    1. 成员变量:
      1. 必须在定义时候或者在初始化时候进行赋值,final变量一旦被初始化赋值之后,就不能再被赋值了。
      2. 被final修饰的变量有三种赋值方式。
        1. image.png
      3. 被final static修饰的变量有两种赋值方式。
        1. image.png
      4. image.png

    2. 局部变量:
      1. 在使用之前被初始化即可
      2. image.png

image.png

final作用于引用中

2修饰符(了解) - 图7
final int b = 1; b之后是不可以改的;那如果是这样的例子,A对象的x可以改吗
是可以改的,这也是引用类型的那一种,final是作用在A对象的引用上,而不是作用在A对象的数据成员x上,因此是可以改的

final+private

final方法不能被子类重写,那么问题来了,为啥这样可以

2修饰符(了解) - 图8
在private 方法隐式地被指定为 final的时候,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法并不是重写了基类方法,而是在子类中定义了一个新的方法。(反正子类也无法访问父类的private方法)

final与普通变量的区别

  1. public class FinalAndVariableDifference {
  2. public static void main(String[] args) {
  3. String a = "helloWord1";
  4. final String b = "helloWord";
  5. String F = "helloWord";
  6. String c = b + 1; // 编译期间就初始化了
  7. String e = F + 1; // 运行时候初始化
  8. System.out.println((a == c));
  9. System.out.println((a == e));
  10. }
  11. }

image.png
在String中”==”判断的是不是一个对象,指向的是一个地址
对于a,它在编译时候就已经确定放入了常量池中,b同理.

重新定义一个字符串String c = helloword1首先会从常量池中检查有没有helloword1这个常量,没有,就先入常量池,再返回。有,就直接返回!比如第一次定义String a = helloword1,就会先入池再从池中返回,第二次定义String c = helloword1,这个值就是直接从池中返回的2
由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的值(这种情况我们成为编译器的优化)。而对于变量F的访问却需要在运行时才能连接确定,所以返回false

只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,那是不是只要是被final修饰的变量就会进行优化呢
当然不是!比如下面的这段代码就不会进行优化:

  1. public class FinalAndVariableDifference {
  2. public static void main(String[] args) {
  3. String a = "helloWord2";
  4. final String b = getHello(); //尽管是final修饰,但不会进行优化,因为它要运行时初始化才被确定
  5. String c = b + 2;
  6. System.out.println((a == c));
  7. }
  8. public static String getHello() {
  9. return "helloWord";
  10. }
  11. }
  12. 运行结果:false

被final修饰的变量不一定会进行优化,优化的前提是编译时就已经能够确定

static

https://blog.csdn.net/qq_44543508/article/details/102736466
static的主要意义是在于:

  • 创建独立于具体**对象**独立于具体
  • 用来形成静态代码块以优化程序性能
  • static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。
  • 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
    • static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
    • 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。

在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。

应用场景

如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量
静态变量:
static修饰的成员变量叫做静态变量【也叫做类变量】,静态变量是属于这个类,而不是属于是对象。
实例变量:
没有被static修饰的成员变量叫做实例变量,实例变量是属于这个类的对象的。
static是不允许用来修饰局部变量,因为局部变量属于方法(在静态方法也不能使用局部static变量).

静态变量与实例变量的区别

静态变量:
静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
实例变量:
每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员

  1. public class StaticDemo {
  2. static int value = 666;
  3. public static void main(String[] args) throws Exception{
  4. new StaticDemo().method();
  5. }
  6. private void method(){
  7. int value = 123;
  8. System.out.println(this.value);
  9. }
  10. }

运行结果: 666
里面定义的value=123只是一个很普通的局部变量而已 。和成员变量没有半毛钱关系 。和静态变量也没有关系。
例子主要目的是说明一下this也是可以访问static的变量的!!!
构造方法不是静态方法

static VS this

可以在静态方法内使用this或者super关键字吗
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,可以说static和this和super是互相矛盾的存在。

静态代码块不能访问普通变量

静态代码块执行顺序

  1. public class IntegerTest {
  2. static {
  3. System.out.println("static");
  4. }
  5. {
  6. System.out.println("构造代码块"); // 如果不实例化对象(也就是不执行构造方法),构造代码块是不会执行的!
  7. }
  8. public IntegerTest() {
  9. System.out.println("IntegerTest.IntegerTest");
  10. }
  11. public static void main(String[] args) {
  12. new IntegerTest();
  13. }
  14. }
  15. static
  16. 构造代码块
  17. IntegerTest.IntegerTest
  18. --------------
  19. public class IntegerTest {
  20. static {
  21. System.out.println("static");
  22. }
  23. {
  24. System.out.println("普通代码块");
  25. }
  26. public IntegerTest() {
  27. System.out.println("IntegerTest.IntegerTest");
  28. }
  29. public static void main(String[] args) {
  30. // new IntegerTest(); 不实例化, 则{System.out.println("普通代码块");}不会执行
  31. }
  32. }
  33. static

进阶版
基本上代码块分为三种:Static静态代码块、构造代码块、普通代码块
代码块执行顺序**静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块**
继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器 (可以结合类的加载器的初始化阶段进行学习)

  • 构造代码块

只要该类实例了一个对象,构造代码就执行一次,利用每次创建对象的时候都会提前调用一次构造代码块特性,所以它可以做统计创建对象的次数功能。
1、构造代码块在创建对象时被调用,每次创建对象都会调用一次
2、构造代码块优先于构造函数执行,同时构造代码块的运行依赖于构造函数
依赖”可理解为如果不实例化对象(也就是不执行构造方法),构造代码块是不会执行的!
3、构造代码块在类中定义

  1. package com.gx.initializationblock;
  2. public class Initializationblock {
  3. int intA;
  4. int intB;
  5. public Initializationblock() {
  6. System.out.println("无参构造器00000000");
  7. }
  8. public Initializationblock(int a) {
  9. System.out.println("一个参数的构造器");
  10. }
  11. {
  12. intA = 10;
  13. intB = 15;
  14. System.out.println("构造初始化块11111");
  15. }
  16. {
  17. System.out.println("构造初始化块22222");
  18. }
  19. {
  20. System.out.println("构造初始化块33333");
  21. }
  22. //静态初始化块
  23. static {
  24. System.out.println("静态初始化块01010101");
  25. }
  26. static {
  27. System.out.println("静态初始化块0202020202");
  28. }
  29. public void method(){
  30. {
  31. System.out.println("普通初始化块");
  32. }
  33. }
  34. }
  35. package com.gx.initializationblock;
  36. /* 初始化块一
  37. * 因为静态块是在类的初始化阶段完成的,
  38. * 因此在创建某个类的第二个对象时,该类的静态块就不会执行了
  39. *
  40. * 在单个类中,静态初始化块,初始化块,构造器
  41. * 多个类的继承中初始化块、静态初始化块、构造器的执行顺序
  42. 在继承中,先后执行父类A的静态块,父类B的静态块,最后子类的静态块,然后再执行父类A的非静态块和构造器,然后是B类的非静态块和构造器,最后执行子类的非静态块和构造器
  43. */
  44. public class Demo1 {
  45. public static void main(String[] args) {
  46. Initializationblock initializationblock = new Initializationblock();
  47. initializationblock.method();
  48. System.out.println("------------");
  49. //多打印几个对象的目的是:好看出Static静态代码块只执行一次!!!
  50. Initializationblock initializationblock2 = new Initializationblock(); //因为静态块是在类的初始化阶段完成的,因此在创建某个类的第二个对象时,该类的静态块就不会执行了
  51. initializationblock2.method();
  52. Initializationblock initializationblock3 = new Initializationblock();
  53. initializationblock3.method();
  54. }
  55. }
  56. 结果:
  57. 静态初始化块01010101
  58. 静态初始化块0202020202
  59. 构造初始化块11111
  60. 构造初始化块22222
  61. 构造初始化块33333
  62. 无参构造器00000000
  63. 普通初始化块
  64. ------------
  65. 构造初始化块11111
  66. 构造初始化块22222
  67. 构造初始化块33333
  68. 无参构造器00000000
  69. 普通初始化块
  70. 构造初始化块11111
  71. 构造初始化块22222
  72. 构造初始化块33333
  73. 无参构造器00000000
  74. 普通初始化块

继承中的代码块执行顺序

假设B继承A,C继承B.
.image.png
多个类的继承中初始化块、静态初始化块、构造器的执行顺序为:先后执行父类A的静态块,父类B的静态块,最后子类C的静态块,然后再执行父类A的非静态块和构造器,然后是B类的非静态块和构造器,最后执行子类的非静态块和构造器.

  1. class A {
  2. static {
  3. System.out.println("A.static initializer");
  4. }
  5. {
  6. System.out.println("A.instance initializer");
  7. }
  8. public A() {
  9. System.out.println("A.A");
  10. }
  11. }
  12. class B extends A{
  13. static {
  14. System.out.println("B.static initializer");
  15. }
  16. {
  17. System.out.println("B.instance initializer");
  18. }
  19. public B() {
  20. System.out.println("B.B");
  21. }
  22. }
  23. class C extends B{
  24. static {
  25. System.out.println("C.static initializer");
  26. }
  27. {
  28. System.out.println("C.instance initializer");
  29. }
  30. public C() {
  31. System.out.println("C.C");
  32. }
  33. }
  34. public class IntegerTest {
  35. public static void main(String[] args) {
  36. C c = new C();
  37. C c1 = new C();
  38. }
  39. }
  40. A.static initializer
  41. B.static initializer
  42. C.static initializer
  43. A.instance initializer
  44. A.A
  45. B.instance initializer
  46. B.B
  47. C.instance initializer
  48. C.C
  49. A.instance initializer
  50. A.A
  51. B.instance initializer
  52. B.B
  53. C.instance initializer
  54. C.C
  55. Process finished with exit code 0

静态内部类

静态内部类与非静态内部类之间存在一个最大的区别:
非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围,但是静态内部类却没有。
没有这个引用就意味着:
1、它的创建是不需要依赖外围类的创建
2、它不能使用任何外围类的非static成员变量和方法。
例子:静态内部类实现单例模式

  1. public class Singleton {
  2. // 声明为 private 避免调用默认构造方法创建对象
  3. private Singleton() {
  4. }
  5. // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
  6. private static class SingletonHolder {
  7. private static final Singleton INSTANCE = new Singleton();
  8. }
  9. public static Singleton getUniqueInstance() {
  10. return SingletonHolder.INSTANCE;
  11. }
  12. }

Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。
只有当调用 getUniqueInstance()方法从而触发 SingletonHolder.INSTANCESingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

抽象类和接口

抽象类和抽象方法都使用 abstract 关键字进行声明,如果一个类中包含抽象方法(没有抽象方法也可以声明,但是没必要),那么这个类必须声明为抽象类,并且抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承

  • 抽象类可以实现代码的重用,模板方法设计模式是抽象类的一个典型应用.
    • 比如初始化的时候需要根据不同活动进行不同功能开启判断,那么就可以定义一个活动的基类,将功能开启判断做成一个抽象方法,让所有活动都继承这个基类,然后在子类自行重写功能开启判断。
  • 接口可以说成是抽象类的一种延伸,接口中的所有方法都必须是抽象的。实际上,接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final
  • 如果想要实现一个不可变类:
    1. - 1)类声明为final,不可以被继承。
    2. - 2)所有成员变量定义为私有和finalprivate final)。
    3. - 3)不提供改变成员变量的方法。
    4. - 4)通过构造器初始化成员,若构造器传入引用数据类型需要进行深拷贝。
    • Java8开始,接口也可以拥有默认的方法实现了,为啥Java团队要做这个支持呢?实际上,在Java上深耕多年的老司机其实都被接口没有默认实现搞过,维护成本太高了,你想想,如果你现在有100个类实现了一个接口,而在这个时候,需要给接口新增一个方法,特么Java8之前不允许给接口加默认实现,所以你就得改100个类,这特么不得搞出人命吗?
  • 抽象类和接口默认实现的区别
    • 抽象类,是一个模板,是一个半成品,让子类去完善。接口是一个规范,是更抽象的,让类按照接口的规范实现方法。
    • 接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用.
    • 答案:
      • 抽象类可以有构造方法,接口中不能有构造方法。
      • 抽象类中可以有普通成员变量,接口中没有普通成员变量
      • 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
      • 抽象类中可以包含静态方法,接口中不能包含静态方法
      • 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
      • 一个类可以实现多个接口,但只能继承一个抽象类。

private|protect

作者:程序员库森
链接:https://www.nowcoder.com/discuss/597041
来源:牛客网

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。注意:不能修饰类(外部类)

2修饰符(了解) - 图11