看王争专栏关键字与内部类总结
目录
目录
一,final
final关键词可以修饰类,方法,变量(类的属性,局部变量,函数参数)
1)修饰类
final修饰的类不可被继承。
2)修饰方法
final修饰的方法不可被重复,但反向思考,子类可以将父类的非final方法重写成final方法。
3)修饰变量
final修饰的变量叫final变量或者常量,final变量只能被赋值一次,一旦赋值就不可以再变。
final变量有三种:类的成员变量,函数局部变量,函数的参数
3-1)类的成员变量
类的成员变量被赋值有两种方式。
1,在成员变量声明时
2,在构造器中
个人觉得还有第三种
实例化如下
private final String s;
{
s = "dfsa";
}
3-2)函数局部变量
函数的局部变量需要在被使用前赋值。
所以也是在声明时赋值,或者声明时不赋值,在使用前赋值也可
3-3)函数的参数
参数肯定是赋了的值的,只是final修饰的参数不可变。
另外也分基本类型和引用类型
3-4)final修饰的引用变量
final修饰的引用变量,指的是指定了哪个对象就不能更改指向别的对象,但是对象里面的属性是可以修改的。
比如 final char[] chars = new char[34];
里面的值是可以变的
而为什么String里面的值不可更改呢,其实是因为没有提供直接修改的方法,同时其返回引用类型的方法,返回的是副本,不是value本身。所以保证了String的不可变。
4)根据String的思路,如何设计一个不可变类
1,将类使用final修饰,可以避免继承类重写方法,修改属性,如下:
2,类的所有属性被final修饰,被赋值一次后就无法再修改,但是如果可以保证没有方法修改这个属性,也可以不设为final,如String中的hash,没有单独方法给其修改值,另外在hashcode中,因为value的不可变,其值也不会更改,可以不设为final
3,如果类的属性是引用类型,那么当其能过方法返回时,应该返回其复副本而不是本身,避免被修改,如:
二,static
static可以修饰变量,方法,代码块,嵌套类
1)static变量
static只能是类的成员变量,被static修饰的类的成员变量也叫静态变量,被static修饰的变量属于类而非对象,对这个类的所有对象共享。
对于静态变量,即可以通过类直接访问,也可以通过对象访问,如下:
同时被final和static修饰的变量叫静态常量,一般需要大写
静态变量与类的代码一样,存储在方法区中。
2)static方法
static修饰的方法属于类而非对象
static修饰的方法也是类的所有对象共享,可以不用创建对象,直接使用。
比如String的valueOf()方法。
另外需要注意的静态方法,只能访问静态变量。静态方法不能访问非静态变量与调用非静态方法,反过来非静态方法可以访问静态变量和调用静态方法。
3)代码块
有时候static定义的变量没法简单赋值完成,比如hashMap这类的,可以使用静态代码块,如下:
如果有多个,静态代码块执行的顺序与定义的顺序相同。
4)嵌套类
嵌套类也叫内部类,是指定义在一个类中的类,承载内部类的类,叫外部类。
而被static修饰的内部类叫静态内部类。
5)final与static修饰上的一些区别
1,final修饰的变量可以是类的成员变量,函数的局部变量,函数参数,而static只能是类的成员变量
2,final和static都可以修饰类,但static只能修饰嵌套类/内部类。
三,内部类/嵌套类(重)
内部类是指定义在一个类中的类,也叫内部类,承载内部类的类,叫外部类,常见的内部类有普通内部类,静态内部类,匿名内部类三种。内部类在编译成字节码后,会独立于外部类,成为一个单独的class文件,命名方式为外部类名$内部类名.class,而匿名内部类因为没有名字,会生成:外部类名$(序号).class,这个序号是1,2,3,4这样,表示这个匿名内部类是外部类的第几个匿名内部类。
1)普通内部类
1)如何创建
即直接定义在类中的类,如下:
2)如何调用
内部类如果只是被外部类调用,不管被private修饰还是public修饰,都是先创建外部类,再创建内部类即可,不用管权限。
但是如果要被外部类以外的类调用,那么要么被public修饰(protected等也可以在相应权限的地方被创建),要么继承一个外部接口,然后再创建,如下:
2)静态内部类(与普通内部类的区别)
1,静态内部类与普通类的区别
1)在访问权限上,内部类与外部类中方法拥有相同的访问权限,即像静态方法只能访问静态变量与调用静态方法,而普通方法可以访问所有变量以及调用所有方法一样。静态内部类只能访问外部类的静态变量与调用静态方法,而普通内部类可以访问外部类的所有属性和方法(包括private修饰的)。
2)静态内部类可以拥有静态方法和静态变量,而普通类内部类不能拥有静态变量和静态方法,但是在jdk16有了改变,普通类也可以拥有静态变量和静态方法。
3)静态内部类的创建与普通内部类存在不同,普通内部类必须先创建外部类的对象,然后通过对象创建内部类,而静态内部类,不需要创建外部类的对象,可以直接创建(且只能直接创建,通过对象创建会报错)。
2,静态内部类如何被外部调用
静态内部类也可以被private, protected,public,与无修饰四种修饰词来修饰,如果被private修饰,那么要么只能在外部类内部使用,要么也与普通内部类一样,通过实现一个接口,同时在外部类拥有创建这个内部类的方法。如:
List<Integer> list = Arrays.asList(new Integer[]{1,2,3,4,5});
list.add(3); 、//会报错,因为这个内部的ArrayList没有重写add方法。
即下面这个被private修饰的类,可以实现接口和继承类。
上面是在Arrays定义了一个实现了接口(AbstractList实现了List)的静态内部类,下面是提供了创建该类的方法。
。
如果是public的,或者另外两种修饰词在拥有权限的地方都是以下面方式创建。
即 外部类.内部类 instance = new 外部类.内部类()方式创建。
3)匿名内部类
在使用多线程时多使用匿名内部类,如下:
匿名内部类的权限与使用它的函数相同,即如果是静态方法,那么只能访问外部类的静态变量和静态方法,如果是非静态方法,那么可以访问外部类的所有变量和方法,哪怕是private修饰的也可以。
注意如果是局部变量,内部匿名类只能访问final修饰的局部变量。这是因为参数的传递是值传递,假如内部类对局部变量做了修饰,但是此时修改的只是内部类的这个变量,对外部类的变量本身是没有影响的(上面的c),这就很违反认知。所以被设计成只能访问final修饰的变量,内外保持一致。
关于匿名内部类访问局部变量,在jdk1.8做了优化,不再需要显示的用final修饰,但是事实上底层还是加了final的,如果只是访问不会报错,但一旦有修改操作,编译器还是会报错,即对局部变量还是需要是final修饰的或事实上不会修改的即可。参考如下:
匿名内部类访问局部变量是否需要 final 修饰(Java Kotlin 对比)_java局部变量不用写final-优快云博客
如下out不报错,a报错了,原因是在后面加了a的修改操作。
去掉上面的a = a + 3;报错就不会有了。
4)参数的访问
1,同样的参数名,即内,外部类都有,根据就近原则直接用访问的是内部类的
2,内部类用this.field,调用的是内部类的
3,外部类名.this.field,调用的是外部类的
四,思考题
1,看代码回答问题
前置知识:java的类加载是线程安全的(loadClass处用了synchronized上锁
),而类的加载是懒加载,只有当类被使用时(创建对象,调用静态方法)才会被加载,另外外部类的加载并不会引起内部类的加载。
1) 为什么这样实现的单例是线程安全的?
1,而上面的代码一旦被调用就会引起加载,而加载的同时完成了对类的创建,一个类只能被加载一次,上面的getInstance被调用时,访问了SingletonHolder的静态变量,触发加载SingletonHolder类。
2) 为什么这样实现的单例支持延迟加载?
因为类的加载本身就是懒加载。
3) 为什么SingletonHolder设计为静态内部类,是否可以是普通内部类?
普通内部类不能拥有静态变量与静态方法,但是要保证在类的加载过程中创建好instance,那么instance必须是静态的,所以内部类也必须是静态内部类。
4) 为什么将SingletonHolder设计为private的,而不是public?
防止被外部类以外的类所调用,即只能通过调用外部类的getInstance方法来触发内部类的加载,同时初始化内部类的静态变量instance。
综上:以上的单例模式设计,利用了类的加载是线程安全的,同时也是懒加载,只有当被使用时才会被加载。再利用单例模式的通用特性,私有构造方法,公有静态方法,保证了只有在调用getInstance方法时才会触发类的加载,而同时触发内部静态类的加载,即保证了线程安全,只有一个例,又保证了延迟加载;因为只有让instance在类加载时创建才是安全的,那么instance需要是静态的,所以需要是内部静态类,同时内部类不需要被别的地方调用,设置成private即可。
2,证明外部类的加载不会引起内部类的加载
public class InintOuterNoInitInter {
static {
System.out.println("外部类");
}
static void main(String[] args) {
System.out.println("开始测试");
InintOuterNoInitInter clazz = new InintOuterNoInitInter();
System.out.println("开始内部类");
InintOuterNoInitInter.Innter inter = new InintOuterNoInitInter.Innter();
System.out.println("结束");
List<Integer> list = Arrays.asList(new Integer[]{1,2,3,4,5});
// list.add(3);
// InintOuterNoInitInter.Innter inter2= clazz.new Innter(); //此处报错,不可这样用
InintOuterNoInitInter.Innter inter1 = new InintOuterNoInitInter.Innter();
}
protected static class Innter{
static {
System.out.println("内部类");
}
}
}