关于final
final修饰符,用来修饰类、方法和变量,final修饰的类不能够被继承,修饰的方法可以被继承,重载,但是不能被子类重写(即重新定义)。
(1) final类
被声明为final的类(class)不可以被继承(extends)。
(2) final修饰方法
final修饰的方法可以被子类继承,但是不能被子类修改(重写),声明final方法的主要目的是防止该方法的内容被修改。
如果认为方法足够完整,或者强制子类不允许修改可以用final修饰该方法。另外,final方法是静态绑定的,在编译期就确定属于哪个类,所以final方法比非final方法在调用时会快一些。
下面这段话摘自《Java编程思想》第四版第143页:
“使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。 在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。“
(3)final变量
final声明的变量可以是基本类型变量也可以是引用变量,基本类型如:int,double,boolean等,被final修饰的基本类型的变量的值在编译时候就已经确定了它的确定值,换句话说就是提前知道了此变量的内容到底是个啥,相当于一个编译期常量。引用类型有封装类型和class声明的类(其实类也是封装),封装类型如:Long,Integer,LocalDateTime,BooK等。
被声明为final的对象的引用不能指向不同的对象,不同的对象在内存中地址是不同的。但是final对象里的数据可以被改变,即使改变了对象地址中某个属性(其他引用)的值并没有影响到这个类实例化出来的对象在内存中数据。
也就是说final对象的引用不能改变,但是里面的值可以改变。比如:
final Item item = Item.ofItemName("heihei", "meimei");
//编译器不会报错
item.setCreateTime(LocalDateTime.now()).setItemName("meimeis");
但是,如果是基本类型和不可变类型被修改了或者被指向了不同的对象,在编译时就不会通过,因为此时对象的引用已经改变了!
//编译失败,不可修改 -
final String str = "aaa";
str = "bbb";
//封装类型 +
final Integer integer = new Integer("32");
integer = 56;
//编译期提示失败,BigDecimal类被class修饰是不可变类型 -
final BigDecimal decimal = new BigDecimal(150);
decimal = new BigDecimal("56");
//可以修改引用类型内部的元素 +
final Item item = new Item();
item.setId(25L);
//使对象指向了不同的内存地址 -
item = new Item();
小结
- final方法比非final快一些
- final关键字提高了性能。JVM和Java应用都会缓存final变量。
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
- 使用final关键字,JVM会对方法、变量及类进行优化。
this关键字
- 如果访问本类方法时,一定要加上this关键字(虽然不加也不会报错)
- 如果要调用本类的的其他构造方式时,那么必须使用this的形式完成。
- 还有一个便是当前对象与其他对象比较时
- 出现对象比较的操作之中。
- 其他的情况出现最多的链表、树等数据结构的实现上。
this更多的情况是出现在一些设计里面,如果你只是根据业务的开发框架开发代码,那么这个this表示当前对象这一概念的出现很少的。
volatile修饰符
volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。一个volatile对象引用可能是null。
集合与线程安全
在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的。在jdk1.2之后,就出现许许多多非线程安全的类。 下面是这些线程安全的同步的类:
- vector:比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
- statck:堆栈类,继承了Vector
- hashtable:比hashmap多了个线程安全
- enumeration:枚举,相当于迭代器
- 除了这些之外,其他的都是非线程安全的类和接口。比如常用的ArrayList、HashMap都是线程不安全的。
StringBuffer 和 StringBuilder
和String类不同,StringBuffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder类和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问)。
由于StringBuilder相较于StringBuffer有速度优势,所以多数情况下建议使用StringBuilder类。然而在应用程序要求线程安全的情况下,则必须使用StringBuffer类。
可变参数
JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。
- 一个方法中只能指定一个可变参数
- 它必须是方法的最后一个参数
- 任何普通的参数必须在它之前声明
//正确
public void getStr(String ...str){
// ...
}
//正确
public void getStr2(int a, String ...str){
// ...
}
//错误
public void getStr2(String ...str, int a){
// ...
}
UTF-8和GBK编码转换
下面哪段程序能够正确的实现了GBK编码字节流到UTF-8编码字节流的转换:byte[] src,dst;
- A、dst=String.fromBytes(src,”GBK”).getBytes(“UTF-8”)
- B、dst=new String(src,”GBK”).getBytes(“UTF-8”)
- C、dst=new String(“GBK”,src).getBytes()
- D、dst=String.encode(String.decode(src,”GBK”)),”UTF-8” )
正确答案:B
操作步骤就是先解码再编码,先通过GBK编码还原字符串,在该字符串正确的基础上得到“UTF-8”所对应的字节串。
访问控制修饰符
- 修饰符说明private私有的,在同一类内可见。
- 默认没有声明引用时在同一包(包括子类和非子类)内可见。默认不使用任何修饰符。
- protected受保护的,对同一包内的类和所有子类可见。
- public共有的,对所有类可见。
主要是默认和protected这两个修饰符,总结起来就是:
- 默认的:同一包下可访问;
- protected:同一包和所有子类可访问;
(1)这里的可见、可访问指的是能不能通过 "类的对象.变量名"
的方式访问,这是因为除static声明的变量属于类变量外,其他的都属于实例变量,是属于某个对象的!
如,item.createTime直接访问createTime变量,对于那些私有的变量,很多情况下会对外提供public的setter和getter方法来供外部访问。
(2)要注意的是,对于有继承关系的子类来说,比如 class A extends B,A直接继承拥有了默认的(在同一包下)、protected、public的这个字段,可以直接使用该字段,而不用通过再次的实例化父类或“父类对象.字段”的形式访问,因为在实例化A类的时候父类B已经实例化好了。特别的,对于protected来说,如下形式是编译不能通过的。
package net.gaox.b
public class B{
protected String age = "20";
public String birthday = "1995";
}
package net.gaox.a
public class A extends B{
public void see(){
B b = new B();
//错误!不同包下的子类不能通过实例出来的父类获取protected的变量
String str = b.age;
//正确,A类继承了B,直接拥有了该字段
String str2 = age;
//正确,birthday为public,可以访问
String str3 = b.birthday;
}
}
结论就是:
在上面总结的修饰符的约束前提下,对于非继承类关系,需要使用 “实例变量.变量名的形式访问”(static类静态变量除外);
对于有继承关系的子类来说, 子类直接继承了拥有了默认的(在同一包下)、protected、public的这个字段,可以直接使用该字段。
List遍历时删除的几种方式比较
1. 会报错的删除方式
在Iterator遍历时使用list删除
while(it.hasNext()){
String item = it.next();
list.remove(item);
}
foreach遍历方式中删除
for (String s : list){
list.remove(s);
}
以上都是报java.util.ConcurrentModificationException,某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection,因为在这些情况下,迭代的结果是不确定的。
而对于foreach实际上使用的是iterator进行处理的,而iterator是不允许集合在iterator使用期间通过list删除的,也就是第一种方式,也就是说上面两种方式相当于是同一种。
2. 不会报错,但是有可能漏删或不能完全的删除方式
漏删的情况(索引下标)
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 5, 5, 8, 9));
for (int i = 0; i < list.size(); i++) {
if (5 == list.get(i)) {
list.remove(i);
}
}
//list结果:1,3,5,8,9。只删除了一个5,还有一个没有完全删除
//删去[2]后,位置[3]的5就补到刚删除的位置,但i的值在增加,于是就错过了我们计划删除的位置
不能完全删除的情况
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 5, 5, 8, 9));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
}
//list现在还剩下:3,5,9三个元素
//每次删除一个元素,后一个位置的元素就会补充到刚被删除的位置
//最终结果就是只删除原集合奇数位置的数据
以上两种情况通过for循环遍历删除,都没有正确达到目的,都是因为在remove后list.size()发生了变化(一直在减少),同时后面的元素会往前移动,导致list中的索引index指向的数据有变化。同时我们的for中的i是一直在加大的!
3. List遍历过程中删除元素的推荐做法
还是使用Iterator遍历,但是不用list来remove。如下代码:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 5, 5, 8, 9));
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
Integer item = it.next();
if (5 == item) {
it.remove();
}
}
///最终list的元素为:1,3,8,9
对于iterator的remove()方法,也有需要我们注意的地方:
- 每调用一次iterator.next()方法,只能调用一次remove()方法。
- 调用remove()方法前,必须调用过一次next()方法。
Java中的四种引用:强引用、软引用、弱引用、虚引用
四种级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用
引用类型 | 被垃圾回收的时机 | 用途 | 生命周期 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | gc运行后终止 |
虚引用 | Unknown | Unknown | Unknown |
1. 强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如下的定义方式:
//强引用,在堆中创建了String这个对象,通过栈中的变量str引用这个对象
String str = new String("abc");
//强引用,str2也指向了堆中创建的String对象
String str2 = str;
这两个引用都是强引用.只要存在对堆中String对象的引用,gc就不会回收该对象,如果显式的设置引用str和str2为null,则gc就会认为堆中的String对象已经不存在其他引用了,此时该对象处于可回收的状态,但是到底什么时候回收该对象,取决于gc的算法。
2. 软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存,比如在Android中经常把Bitmap作为软引用来缓存图片。
String str = new String("abc");
//软引用
SoftReference<String> soft = new SoftReference<String>(str);
str = null;
System.out.println("before gc:" + soft.get());
System.gc();
//被设置为null并且经历过gc,对象不一定被回收
System.out.println("after gc:" + soft.get());
输出结果:
before gc: abc
after gc: abc
不论是强引用、软引用、弱引用或者虚引用都是针对某个对象来说的,当我们某个对象需要设置为软引用时,只需要给该对象套入到软引用对象中即可。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3. 弱引用(WeakReference)
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
可以用如下代码来说明过程:
String str = new String("abc");
//弱引用
WeakReference<String> soft = new WeakReference<String>(str);
str = null;
System.out.println("before gc:" + soft.get());
System.gc();
//被设置为null并且经历过gc,对象被回收
System.out.println("after gc:" + soft.get());
输出结果:
before gc :abc
after gc: null
4. 虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾**回收**。虚引用主要用来跟踪对象被垃圾回收的活动,并且无法通过虚引用获得对象。
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 建立虚引用之后通过get方法返回结果始终为null。
GC 回收软引用和弱引用对象的过程是一样
- 首先将软引用或弱引用的
referent
设置为null,不再引用堆中的对象; - 将堆中的对象
new String("abc")
设置为可结束的(finalizable); - 当堆中的
new String("abc")
对象的 finalize() 方法被运行而且该对象占用的内存被释放,str被添加到它的ReferenceQueue中。