修饰符
final
- 对于基本类型,final使数值不变
- 对于引用类型,final使引用不变,也就是不能引用其他对象:对其初始化之后便不能再让其指向另一个对象。
- 对于方法,则声明的方法不能被子类重写(但是可以被子类访问,前提是父类的final方法不是private的)
- 对于final类,则不允许被继承
- 在final修饰变量的时候:
- 成员变量:
- 必须在定义时候或者在初始化时候进行赋值,final变量一旦被初始化赋值之后,就不能再被赋值了。
- 被final修饰的变量有三种赋值方式。
- 被final static修饰的变量有两种赋值方式。
- 局部变量:
- 在使用之前被初始化即可
- 成员变量:
final作用于引用中
final int b = 1; b之后是不可以改的;那如果是这样的例子,A对象的x可以改吗
是可以改的,这也是引用类型的那一种,final是作用在A对象的引用上,而不是作用在A对象的数据成员x上,因此是可以改的。
final+private
final方法不能被子类重写,那么问题来了,为啥这样可以
在private 方法隐式地被指定为 final的时候,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法并不是重写了基类方法,而是在子类中定义了一个新的方法。(反正子类也无法访问父类的private方法)
final与普通变量的区别
public class FinalAndVariableDifference {
public static void main(String[] args) {
String a = "helloWord1";
final String b = "helloWord";
String F = "helloWord";
String c = b + 1; // 编译期间就初始化了
String e = F + 1; // 运行时候初始化
System.out.println((a == c));
System.out.println((a == e));
}
}
在String中”==”判断的是不是一个对象,指向的是一个地址
对于a,它在编译时候就已经确定放入了常量池中,b同理.
重新定义一个字符串String c = helloword1首先会从常量池中检查有没有helloword1这个常量,没有,就先入常量池,再返回。有,就直接返回!比如第一次定义String a = helloword1,就会先入池再从池中返回,第二次定义String c = helloword1,这个值就是直接从池中返回的2
由于变量b
被final修饰,因此会被当做编译器常量,所以在使用到b
的地方会直接将变量b
替换为它的值(这种情况我们成为编译器的优化)。而对于变量F
的访问却需要在运行时才能连接确定,所以返回false
只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,那是不是只要是被final修饰的变量就会进行优化呢?
当然不是!比如下面的这段代码就不会进行优化:
public class FinalAndVariableDifference {
public static void main(String[] args) {
String a = "helloWord2";
final String b = getHello(); //尽管是final修饰,但不会进行优化,因为它要运行时初始化才被确定
String c = b + 2;
System.out.println((a == c));
}
public static String getHello() {
return "helloWord";
}
}
运行结果: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只为静态变量分配一次内存空间。
实例变量:
每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员
public class StaticDemo {
static int value = 666;
public static void main(String[] args) throws Exception{
new StaticDemo().method();
}
private void method(){
int value = 123;
System.out.println(this.value);
}
}
运行结果: 666
里面定义的value=123
只是一个很普通的局部变量而已 。和成员变量没有半毛钱关系 。和静态变量也没有关系。
例子主要目的是说明一下this
也是可以访问static
的变量的!!!
构造方法不是静态方法!
static VS this
可以在静态方法内使用this或者super关键字吗
只能访问所属类的静态字段和静态方法,方法中不能
有 this 和 super 关键字,可以说static和this和super是互相矛盾的存在。
静态代码块不能访问普通变量
静态代码块执行顺序
public class IntegerTest {
static {
System.out.println("static");
}
{
System.out.println("构造代码块"); // 如果不实例化对象(也就是不执行构造方法),构造代码块是不会执行的!
}
public IntegerTest() {
System.out.println("IntegerTest.IntegerTest");
}
public static void main(String[] args) {
new IntegerTest();
}
}
static
构造代码块
IntegerTest.IntegerTest
--------------
public class IntegerTest {
static {
System.out.println("static");
}
{
System.out.println("普通代码块");
}
public IntegerTest() {
System.out.println("IntegerTest.IntegerTest");
}
public static void main(String[] args) {
// new IntegerTest(); 不实例化, 则{System.out.println("普通代码块");}不会执行
}
}
static
进阶版
基本上代码块分为三种:Static静态代码块、构造代码块、普通代码块
代码块执行顺序**静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块**
继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器 (可以结合类的加载器的初始化阶段进行学习)
- 构造代码块
只要该类实例了一个对象,构造代码就执行一次,利用每次创建对象的时候都会提前调用一次构造代码块特性,所以它可以做统计创建对象的次数功能。
1、构造代码块在创建对象时被调用,每次创建对象都会调用一次
2、构造代码块优先于构造函数执行,同时构造代码块的运行依赖于构造函数
“依赖”可理解为如果不实例化对象(也就是不执行构造方法),构造代码块是不会执行的!
3、构造代码块在类中定义
package com.gx.initializationblock;
public class Initializationblock {
int intA;
int intB;
public Initializationblock() {
System.out.println("无参构造器00000000");
}
public Initializationblock(int a) {
System.out.println("一个参数的构造器");
}
{
intA = 10;
intB = 15;
System.out.println("构造初始化块11111");
}
{
System.out.println("构造初始化块22222");
}
{
System.out.println("构造初始化块33333");
}
//静态初始化块
static {
System.out.println("静态初始化块01010101");
}
static {
System.out.println("静态初始化块0202020202");
}
public void method(){
{
System.out.println("普通初始化块");
}
}
}
package com.gx.initializationblock;
/* 初始化块一
* 因为静态块是在类的初始化阶段完成的,
* 因此在创建某个类的第二个对象时,该类的静态块就不会执行了
*
* 在单个类中,静态初始化块,初始化块,构造器
* 多个类的继承中初始化块、静态初始化块、构造器的执行顺序
在继承中,先后执行父类A的静态块,父类B的静态块,最后子类的静态块,然后再执行父类A的非静态块和构造器,然后是B类的非静态块和构造器,最后执行子类的非静态块和构造器
*/
public class Demo1 {
public static void main(String[] args) {
Initializationblock initializationblock = new Initializationblock();
initializationblock.method();
System.out.println("------------");
//多打印几个对象的目的是:好看出Static静态代码块只执行一次!!!
Initializationblock initializationblock2 = new Initializationblock(); //因为静态块是在类的初始化阶段完成的,因此在创建某个类的第二个对象时,该类的静态块就不会执行了
initializationblock2.method();
Initializationblock initializationblock3 = new Initializationblock();
initializationblock3.method();
}
}
结果:
静态初始化块01010101
静态初始化块0202020202
构造初始化块11111
构造初始化块22222
构造初始化块33333
无参构造器00000000
普通初始化块
------------
构造初始化块11111
构造初始化块22222
构造初始化块33333
无参构造器00000000
普通初始化块
构造初始化块11111
构造初始化块22222
构造初始化块33333
无参构造器00000000
普通初始化块
继承中的代码块执行顺序
假设B继承A,C继承B.
.
多个类的继承中初始化块、静态初始化块、构造器的执行顺序为:先后执行父类A的
静态块
,父类B的静态块,最后子类C的静态块,然后再执行父类A的
非静态块和构造器
,然后是B类的非静态块和构造器,最后执行子类的非静态块和构造器.
class A {
static {
System.out.println("A.static initializer");
}
{
System.out.println("A.instance initializer");
}
public A() {
System.out.println("A.A");
}
}
class B extends A{
static {
System.out.println("B.static initializer");
}
{
System.out.println("B.instance initializer");
}
public B() {
System.out.println("B.B");
}
}
class C extends B{
static {
System.out.println("C.static initializer");
}
{
System.out.println("C.instance initializer");
}
public C() {
System.out.println("C.C");
}
}
public class IntegerTest {
public static void main(String[] args) {
C c = new C();
C c1 = new C();
}
}
A.static initializer
B.static initializer
C.static initializer
A.instance initializer
A.A
B.instance initializer
B.B
C.instance initializer
C.C
A.instance initializer
A.A
B.instance initializer
B.B
C.instance initializer
C.C
Process finished with exit code 0
静态内部类
静态内部类与非静态内部类之间存在一个最大的区别:
非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围,但是静态内部类却没有。
没有这个引用就意味着:
1、它的创建是不需要依赖外围类的创建。
2、它不能使用任何外围类的非static成员变量和方法。
例子:静态内部类实现单例模式
public class Singleton {
// 声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}
// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
当 Singleton
类加载时,静态内部类 SingletonHolder
没有被加载进内存。
只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder
才会被加载,此时初始化 INSTANCE
实例,并且 JVM 能确保 INSTANCE
只被实例化一次。
抽象类和接口
抽象类和抽象方法都使用 abstract 关键字进行声明,如果一个类中包含抽象方法(没有抽象方法也可以声明,但是没必要),那么这个类必须声明为抽象类,并且抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。
- 抽象类可以实现代码的重用,模板方法设计模式是抽象类的一个典型应用.
- 比如初始化的时候需要根据不同活动进行不同功能开启判断,那么就可以定义一个活动的基类,将功能开启判断做成一个抽象方法,让所有活动都继承这个基类,然后在子类自行重写功能开启判断。
- 接口可以说成是抽象类的一种延伸,接口中的所有方法都必须是抽象的。实际上,接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
- 如果想要实现一个不可变类:
- (1)类声明为final,不可以被继承。
- (2)所有成员变量定义为私有和final(private final)。
- (3)不提供改变成员变量的方法。
- (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 : 对同一包内的类和所有子类可见。使用对象:变量、方法。注意:不能修饰类(外部类)。