1、== 和equals区别
(1)基本数据类型: == 比较的是值,不能用equals
(2)引用数据类型: == 比较的是对象的地址,equals如果没重写本质上就是==;equals如果重写了,则不一定,需要看源码。比如String类先比较地址,地址不一样在比较每个字符的值。Integer类底层先把数值转为int类型再用 == 比较值
//Object类的equals方法
public boolean equals(Object obj) {
return (this == obj);
}
//String类的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//Integer类的equals方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
2、string、stringbuffer、stringbuild
(1)存放string对象的区域:堆区、常量池(在元空间也可以理解为方法区中)
A、如果是 =‘’…‘’,则对象存放在常量池。如果常量池中有相同的值则重复利用,否则在池中重新开辟空间
String s1 = "123"; //这种情况生成一个对象在常量池中
String s2 = "123"; //这种情况不生成对象,因为在常量池中有
B、如果是new出的,则对象存放在堆区,但是指向常量池中的对象,至于在常量池中是否生成对象取决于常量池中是否有相同的值。
String s3 = new String("hello"); //这种情况生成两个对象一个在堆中一个在常量池中
String s4 = new String("hello"); //这种情况生成一个对象在堆中,至于hello在常量池中已经生成
关系图如下:
(2)String是一个不可变的类,当修改值的时候并不会对原有的对象进行修改,而是在常量池中从新开辟一个空间,然后把这个新对象的地址给原来的引用。但是我们可以用反射暴力修改原对象的值,代码如下
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String s1 = "123";
String s2 = "123";
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s1对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s1);
//把value所引用的数组中的第3个字符改为'e'
value[2] = 'e';
valueFieldOfString.setAccessible(false);
System.err.println(s1);
System.err.println(s2);
}
(3)性能:stringbuild>stringbuffer>string
- string每次改变都会重新开辟空间、赋值,所以是最慢的
- 在多线程的情况下stringbuffer能保证线程是同步的,也就是单位时间内只有一个线程执行,所以比stringbuild慢
(4)线程安全:stringbuffer>stringbuild
(5)使用情况:
- string适合改变少时
- Stringbuild适合多变且无需考虑线程同步时
- Stringbuffer适合多变且需要考虑线程同步时
3、final、finally、finalize区别
(1)final
- 修饰的类不能被继承
- 修饰的方法不能被重写
- 修饰的引用型变量不可指向其它对象,但是该对象的值可以修改
- 修饰的基本类型变量不可改值
(2)finally
- finally块通常放在try、catch的后面,有时可以直接放在try 的后面。
- finally中的语句是正常执行或者处理异常之后必须执行的语句,finally块一般是用来关闭(释放)物理资源(数据库连接,网络连接,磁盘文件等)。无论是否发生异常,资源都必须进行关闭。当没有必要资源需要释放时,可以不用定义finally块。
- finally块中的代码总能执行,这就说明无论try、catch块中执行怎样的代码,是否发生异常,还是正常运行,finally块一定会被执行,如果想要finally块不执行,除非在try、catch块中调用退出虚拟机的方法,否则finally块无论怎么样还是会被执行
注:这里需要说明一下return和finally的执行顺序
- 如果try代码块里有return语句,而finally代码块里没有return语句,程序会先执行finally代码块里的代码然后再执行try代码块里的return语句;
- 如果try代码块和finally代码块里都有return语句,try代码块里的return语句会优先finally代码块里的return语句执行,但不会把返回的结果返回给主函数,而是会把finally代码块里return的结果返回给主函数。
(3)finalize:不推荐使用
-
finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
-
特殊情况下,需要程序员实现finalize,比如当对象被回收需要释放一些资源时:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
-
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法。
4、泛型
参考文档:https://blog.csdn.net/s10461/article/details/53941091
5、for和foreach区别
(1)区别
- foreach是for在特定情况下的简化版本
- foreach适用于数组或实现了Iterable的集合类,foreach就是使用Iterable接口下的迭代器Iterator来实现对集合的遍历的。for的适用场景更广
- foreach不可以添加/删除/修改集合元素。而for可以(但不推荐用for删,可能会漏删);foreach和for都可以修改元素里面的属性
(2)Iterator和Iterable的区别
Iterator是迭代器父接口,Iterable是Collection分支容器类的父接口。在Iterable中会引入Iterator作为方法的返回值,此处对应抽象工厂模式,目的是让迭代器和容器类分离开来,让不同的容器类对应各自特有的迭代器(详情可以研究抽象工厂模式),并且每一次调用Iterable的Iterator()方法,都会返回一个从头开始的Iterator对象,各个Iterator对象之间不会相互干扰,这样保证了可以同时对一个数据结构进行多个遍历(这是因为每个循环都是用了独立的迭代器Iterator对象)
Iterable接口
package java.lang;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
ArrayList类
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//省略代码
public Iterator<E> iterator() {
return new Itr();
}
//省略代码
}
HashSet类
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
//省略代码
public Iterator<E> iterator() {
return map.keySet().iterator();
}
//省略代码
}
(3)foreach不能添加/删除/修改集合元素的原因
当用foreach进行此类操作时会抛出异常:java.util.ConcurrentModificationException(修改并发异常)。之所以会抛出这个异常是因为触发了Java集合的错误检测机制——fail-fast 。
1)fail-fast简介:
fail-fast即快速失败,它是Java集合的一种错误检测机制。当方法检测到集合有并发修改迹象,且这种迹象不被允许时,将会触发这种机制,抛出java.util.ConcurrentModificationException。需要注意的是,即使不是多线程环境,如果单线程违反了规则,同样也有可能会触发该机制从而抛出java.util.ConcurrentModificationException。
2)foreach底层源码解析
很不幸,foreach这种代码没办法通过单纯的代码跟进查看源码,所以只能采用查看字节码的方式来了解底层调用逻辑。
- 首先我们先创建一个测试类
public class TestForEach {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("1");
list.add("2");
for (String s:list){
System.out.println(s);
list.remove("1");
list.add("2");
}
}
}
- 然后通过 javac TestForEach.java 命令生成字节码文件,通过idea等工具打开字节码文件,如下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.digitalcommunicationmodule.digitalradio;
import java.util.ArrayList;
import java.util.Iterator;
public class TestForEach {
public TestForEach() {
}
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add("1");
var1.add("2");
Iterator var2 = var1.iterator();
while(var2.hasNext()) {
String var3 = (String)var2.next();
System.out.println(var3);
var1.remove("1");
var1.add("2");
}
}
}
一目了然,发现其实是先调用iterator()生成迭代器,之后用迭代器进行遍历
- 也可以通过javap -c TestForEach命令查看生成的字节码文件
Compiled from "TestForEach.java"
public class com.digitalcommunicationmodule.digitalradio.TestForEach {
public com.digitalcommunicationmodule.digitalradio.TestForEach();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 1
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: ldc #6 // String 2
20: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
25: pop
26: aload_1
27: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
32: astore_2
33: aload_2
34: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
39: ifeq 80
42: aload_2
43: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
48: checkcast #10 // class java/lang/String
51: astore_3
52: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
55: aload_3
56: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
59: aload_1
60: ldc #4 // String 1
62: invokeinterface #13, 2 // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z
67: pop
68: aload_1
69: ldc #6 // String 2
71: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
76: pop
77: goto 33
80: return
}
- 接下来便可继续往下跟进,进入到ArrayList类,搜索iterator()方法,发现有很多重载的方法,通过debug加断点尝试发现进入的是如下方法
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//省略代码
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
//省略代码
}
}
分析上述代码可知,当调用next()方法时会先调用checkForComodification()方法,当modCount != expectedModCount时便抛出ConcurrentModificationException异常。这样抛异常的原因便找到了。
- 接下来分析modCount 和expectedModCount这两个变量的意义
- modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
- expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。如下图:
- 所以最后的结论是:迭代器开始遍历之前该迭代器的expectedModCount (期望集合修改次数)已经被modCount(实际修改次数)赋值了,然而在foreach遍历中删除数据或添加数据都是对modCount(实际修改次数)的修改,又因为是调用集合类自己的方法,所以不会对expectedModCount (期望集合修改次数)进行修改(这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除或添加了),所以就造成了不等的情况,所以就会抛出一个java.util.ConcurrentModificationException(修改并发异常),用来提示用户,可能发生了并发修改。
(4)for删除元素漏删的原因
因为普通for循环是根据索引删除的,由于两个相同值在相邻的位置,当删除第一个值之后,集合发生改变要重新排序索引(因为集合发生改变他的的底层Object[]要做位移操作,这里是要向前位移一个索引),所以后面那个要被删除的值就被挪到了删除值的索引位置,从而避免了删除也就造成了漏删。当然我们可以倒着遍历,用i- -的方式,这样也能解决漏删的问题
所以当我们对集合进行删除、修改、添加元素操作时应当用iterator方式,如果并发操作,需要对iterator对象加锁
List<String> stringListFor = getStringList();
System.out.println("删之前:"+stringListFor);
Iterator<String> iterator = stringListFor.iterator();
while (iterator.hasNext()){
if(iterator.next().equals("chengxi")){
iterator.remove();
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153463.html