java基础

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。java基础,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

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

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!