Java常见避坑汇总

文章详细介绍了Java中String对象的replace和replaceAll方法的区别,以及split方法的使用。此外,讨论了Arrays.asList在转化为List时的限制和处理方式,以及在List中进行元素删除时应注意的迭代器使用方法。最后,文章提到了SimpleDateFormat的线程不安全问题及其三种解决方案。

一、String对象中的replace和replaceAll的区别?

//replace方法:支持字符和字符串的替换
public String replace(char oldChar, char newChar)
public String replace(CharSequence target, CharSequence replacement)

//replaceAll方法:基于正则表达式的字符串替换
public String replaceAll(String regex, String replacement)

//测试代码
String str = "AAA|BBB|CCC";
str = str.replace("|", ";");
System.out.println("==========" + str);  //==========AAA;BBB;CCC

String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", ";");
System.out.println("==========" + str);  //==========AAA;BBB;CCC

String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", "\\;");
System.out.println("==========" + str);  //==========AAA;BBB;CCC

二、String类中split()方法的使用

//最常用的split()方法也就是单个参数的方法
public String[] split(String regex) {
      return split(regex, 0);
}
//重载方法
public String[] split(String regex, int limit) {
// Try fast splitting without allocating Pattern object
    String[] fast = Pattern.fastSplit(regex, this,limit);
    if (fast != null) {
        return fast;
    }
    return Pattern.compile(regex).split(this, limit);
}

//测试代码
String str = "AAA|BBB|CCC|";
String[] strArr = str.split("\\|");
System.out.println("==========" + Arrays.toString(strArr) + ",数组长度为:" + strArr.length);
//打印结果:==========[AAA, BBB, CCC],数组长度为:3

String str = ";;AAA;BBB;CCC";
String[] strArr = str.split(";");
System.out.println("==========" + Arrays.toString(strArr) + ",数组长度为:" + strArr.length);
//打印结果:==========[, , AAA, BBB, CCC],数组长度为:5

String str = ";;AAA;BBB;;CCC;";
String[] strArr = str.split(";");
System.out.println("==========" + Arrays.toString(strArr) + ",数组长度为:" + strArr.length);
//打印结果:==========[, , AAA, BBB, , CCC],数组长度为:6

三、Arrays.asList() 详解

/**
* 1、该方法是将数组转化成List集合的方法
* 2、该方法适用于对象型数据的数组(String、Integer...)
* 3、该方法不建议使用于基本数据类型的数组(byte,short,int,long,float,double,boolean)
* 4、该方法将数组与List列表链接起来:当更新其一个时,另一个自动更新
* 5、方法得到的List的长度是不可改变的,不支持add()、remove()、clear()等方法
* 6、Arrays.asList(strArray)返回值是java.util.Arrays类中一个私有静态内部类java.util.Arrays.ArrayList,它并非java.util.ArrayList类
* 7、java.util.Arrays.ArrayList类具有 set(),get(),contains()等方法,但是不具有添加add()或删除remove()方法 
*/

//测试代码
String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", "\\;");
String[] strArr = str.split(";");
List<String> strList = Arrays.asList(strArr);
//程序就会抛出异常(java.lang.UnsupportedOperationException)
strList.remove("BBB");
//如果List只是用来遍历,用Arrays.asList()就可以了
//如果List还要添加或删除元素,需要new一个java.util.ArrayList

//测试代码
String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", "\\;");
String[] strArr = str.split(";");
List<String> strList = new ArrayList<>(Arrays.asList(strArr));
strList.remove("BBB");
System.out.println("==========" + strList + ",strList长度为:" + strList.size());
//打印结果:==========[AAA, CCC],strList长度为:2

四、List的remove()方法使用

//list删除会导致当前元素之后的元素位置发生改变
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
for (int i = 0; i < list.size(); i++) {
    if ("bb".equals(list.get(i))) {
        list.remove(i);
    }
}
System.out.println(Arrays.toString(list.toArray()));
//你认为应该输出的打印结果:[aa, cc]
//实际输出的打印结果:[aa, bb, cc]

//正确使用方法1:倒序循环
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
for (int i = list.size() - 1; i >= 0; i--) {
    if ("bb".equals(list.get(i))) {
        list.remove(i);
    }
}
System.out.println(Arrays.toString(list.toArray()));

//正确使用方法2:顺序循环时,删除当前位置的值,下一个值就会补到当前位置,所以需要执行i–操作
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
for (int i = 0; i < list.size(); i++) {
    if ("bb".equals(list.get(i))) {
        list.remove(i);
        i--;
    }
}
System.out.println(Arrays.toString(list.toArray()));

//正确使用方法3:使用迭代器的remove()方法
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("bb".equals(it.next())) {
        //使用list的remove,会发生java.util.ConcurrentModificationException异常
        //list.remove("bb");
        it.remove();
    }
}
System.out.println(Arrays.toString(list.toArray()));

五、SimpleDateFormat线程不安全问题

//问题复现
public class SimpleDateFormatTest {

    // 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) {

        String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};

        MyThread[] myThreads = new MyThread[5];

        // 创建线程
        for (int i = 0; i < 5; i++) {
            myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
        }

        // 启动线程
        for (int i = 0; i < 5; i++) {
            myThreads[i].start();
        }
    }


    public static class MyThread extends Thread {

        private SimpleDateFormat simpleDateFormat;
        //要转换的日期字符串
        private String dateString;

        private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
            this.simpleDateFormat = simpleDateFormat;
            this.dateString = dateString;
        }

        @Override
        public void run() {
            try {
                Date date = simpleDateFormat.parse(dateString);
                String newDate = simpleDateFormat.format(date);
                if (!newDate.equals(dateString)) {
                    System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
                            + " 转换成的日期为:" + newDate);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    
}

//控制台输出:
//ThreadName=Thread-2 报错了,日期字符串:2020-09-12 转换成的日期为:2020-09-13
//ThreadName=Thread-0 报错了,日期字符串:2020-09-10 转换成的日期为:2202-09-12
问题原因分析:
SimpleDateFormart 继承自 DateFormart,在 DataFormat 类内部有一个 Calendar 对象引用,
SimpleDateFormat 转换日期都是靠这个 Calendar 对象来操作的,比如 parse(String),
format(date) 等类似的方法,Calendar 在用的时候是直接使用的,而且是改变了 Calendar 的值,这样情况在多线程下就会出现线程安全问题,如果 SimpleDateFormart 是静态的话,那么多个 thread 之间就会共享这个 SimpleDateFormart,同时也会共享这个 Calendar 引用,那么就出现数据赋值覆盖情况,也就是线程安全问题。
//解决方法1:每次使用就创建一个新的 SimpleDateFormat
public class SimpleDateFormatTest {

    // 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) {

        String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};

        MyThread[] myThreads = new MyThread[5];

        // 创建线程
        for (int i = 0; i < 5; i++) {
            myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
        }

        // 启动线程
        for (int i = 0; i < 5; i++) {
            myThreads[i].start();
        }
    }


    public static class MyThread extends Thread {

        private SimpleDateFormat simpleDateFormat;
        //要转换的日期字符串
        private String dateString;

        private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
            this.simpleDateFormat = simpleDateFormat;
            this.dateString = dateString;
        }

        @Override
        public void run() {
            try {
                Date date = DateUtil.parse(dateString);
                String newDate = DateUtil.format(date);
                if (!newDate.equals(dateString)) {
                    System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
                            + " 转换成的日期为:" + newDate);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    private static class DateUtil {
        private static final String formatPattern = "yyyy-MM-dd";

        private static Date parse(String dateString) throws ParseException {
            return new SimpleDateFormat(formatPattern).parse(dateString);
        }

        private static String format(Date date) {
            return new SimpleDateFormat(formatPattern).format(date);
        }
    }

}

//解决方法2:使用synchronized 锁
public class SimpleDateFormatTest {

    // 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) {

        String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};

        MyThread[] myThreads = new MyThread[5];

        // 创建线程
        for (int i = 0; i < 5; i++) {
            myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
        }

        // 启动线程
        for (int i = 0; i < 5; i++) {
            myThreads[i].start();
        }
    }


    public static class MyThread extends Thread {

        private SimpleDateFormat simpleDateFormat;
        //要转换的日期字符串
        private String dateString;

        private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
            this.simpleDateFormat = simpleDateFormat;
            this.dateString = dateString;
        }

        @Override
        public void run() {
            try {
                Date date = DateUtil.parse(dateString);
                String newDate = DateUtil.format(date);
                if (!newDate.equals(dateString)) {
                    System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
                            + " 转换成的日期为:" + newDate);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    private static class DateUtil {

        private static Date parse(String dateString) throws ParseException {
            synchronized (simpleDateFormat) {
                return simpleDateFormat.parse(dateString);
            }
        }

        private static String format(Date date) {
            synchronized (simpleDateFormat) {
                return simpleDateFormat.format(date);
            }
        }
    }

}

//解决方法3:ThreadLocal(推荐使用)
public class SimpleDateFormatTest {

    // 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) {

        String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};

        MyThread[] myThreads = new MyThread[5];

        // 创建线程
        for (int i = 0; i < 5; i++) {
            myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
        }

        // 启动线程
        for (int i = 0; i < 5; i++) {
            myThreads[i].start();
        }
    }


    public static class MyThread extends Thread {

        private SimpleDateFormat simpleDateFormat;
        //要转换的日期字符串
        private String dateString;

        private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
            this.simpleDateFormat = simpleDateFormat;
            this.dateString = dateString;
        }

        @Override
        public void run() {
            try {
                Date date = DateUtil.parse(dateString);
                String newDate = DateUtil.format(date);
                if (!newDate.equals(dateString)) {
                    System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
                            + " 转换成的日期为:" + newDate);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    private static class DateUtil {

        private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd");
            }
        };

        private static Date parse(String dateString) throws ParseException {
            return threadLocal.get().parse(dateString);
        }

        private static String format(Date date) {
            return threadLocal.get().format(date);
        }
    }

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值