предисловие
Недавно я прочитал "100 распространенных ошибок в Java-разработке бизнеса" от Geek Time и в сочетании с некоторыми ямами кода, на которые я обычно наступаю, я написал резюме. Надеюсь, оно будет полезно для всех. Спасибо за чтение~
адрес github, спасибо за каждую звезду
публика:маленький мальчик собирает улиток
1. Шесть типов типичных проблем с нулевым указателем
- Проблема нулевого указателя с типами-оболочками
- Проблема с нулевым указателем при каскадных вызовах
- Проблема с нулевым указателем в левой части метода Equals
- Контейнеры, подобные ConcurrentHashMap, не поддерживают k-v как null.
- Коллекции, массивы получают элементы напрямую
- Объект получает свойства напрямую
1.1 Проблема NULL указки обернутых типов
public class NullPointTest {
public static void main(String[] args) throws InterruptedException {
System.out.println(testInteger(null));
}
private static Integer testInteger(Integer i) {
return i + 1; //包装类型,传参可能为null,直接计算,则会导致空指针问题
}
}
1.2 Проблема нулевого указателя каскадных вызовов
public class NullPointTest {
public static void main(String[] args) {
//fruitService.getAppleService() 可能为空,会导致空指针问题
fruitService.getAppleService().getWeight().equals("18");
}
}
1.3 Проблема с нулевым указателем в левой части метода Equals
public class NullPointTest {
public static void main(String[] args) {
String s = null;
if (s.equals("666")) { //s可能为空,会导致空指针问题
System.out.println("公众号:捡田螺的小男孩,666");
}
}
}
1.4 Контейнер ConcurrentHashMap не поддерживает K-V как null.
public class NullPointTest {
public static void main(String[] args) {
Map map = new ConcurrentHashMap<>();
String key = null;
String value = null;
map.put(key, value);
}
}
1.5 Коллекции, массивы получают элементы напрямую
public class NullPointTest {
public static void main(String[] args) {
int [] array=null;
List list = null;
System.out.println(array[0]); //空指针异常
System.out.println(list.get(0)); //空指针一场
}
}
1.6 Объекты получают свойства напрямую
public class NullPointTest {
public static void main(String[] args) {
User user=null;
System.out.println(user.getAge()); //空指针异常
}
}
2. Установка даты в формате YYYY
В повседневной разработке часто необходимо отформатировать дату, но когда в верхнем регистре в верхнем регистре, есть подводные камни.
Пример счетчика:
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2019-12-31 转 YYYY-MM-dd 格式后 " + dtf.format(testDate));
результат операции:
2019-12-31 转 YYYY-MM-dd 格式后 2020-12-31
Разобрать:
Почему это 31 декабря 2019 года, а формат изменился, и стало 31 декабря 2020 года? Поскольку YYYY вычисляет год на основе недели, он указывает на год недели, к которому относится текущий день. Неделя начинается с воскресенья и заканчивается в субботу. Если эта неделя приходится на канун Нового года, эта неделя считается как на следующий год. Правильной позицией является использование формата yyyy.
Положительный пример:
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("2019-12-31 转 yyyy-MM-dd 格式后 " + dtf.format(testDate));
3. Яма численного расчета точности суммы
Взгляните на этот пример арифметики с плавающей запятой:
public class DoubleTest {
public static void main(String[] args) {
System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);
double amount1 = 3.15;
double amount2 = 2.10;
if (amount1 - amount2 == 1.05){
System.out.println("OK");
}
}
}
результат операции:
0.30000000000000004
0.19999999999999996
401.49999999999994
1.2329999999999999
Можно обнаружить, что результаты расчетов не соответствуют нашим ожиданиям, на самом деле, потому что компьютер хранит значения в двоичном формате, а также для чисел с плавающей запятой. Для компьютера 0,1 не может быть выражено точно, поэтому числа с плавающей запятой вызывают недостаток точности. следовательно,Расчет суммы, обычно используется тип BigDecimal
Для приведенного выше примера давайте изменим его на BigDecimal и посмотрим на эффект запуска:
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));
результат операции:
0.3000000000000000166533453693773481063544750213623046875
0.1999999999999999555910790149937383830547332763671875
401.49999999999996802557689079549163579940795898437500
1.232999999999999971578290569595992565155029296875
Результат оказался неверным.фактически, используйте BigDecimal для представления и вычисления чисел с плавающей запятой, вы должны использоватьСтроковый конструктордля инициализации BigDecimal следующим образом:
public class DoubleTest {
public static void main(String[] args) {
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));
}
}
При расчете суммы и использовании BigDecimal нам также понадобитсяОбратите внимание на десятичные точки BigDecimal и его восемь режимов округления..
4. Статические статические переменные зависят от переменных инстанцирования пружины, что может привести к ошибкам инициализации.
Я уже видел проекты с подобным кодом. Статические переменные зависят от bean-компонентов контейнера Spring.
private static SmsService smsService = SpringContextUtils.getBean(SmsService.class);
Этот статический smsService может быть недоступен, потому что порядок загрузки классов не определен, правильный способ его написания может быть следующим:
private static SmsService smsService =null;
//使用到的时候采取获取
public static SmsService getSmsService(){
if(smsService==null){
smsService = SpringContextUtils.getBean(SmsService.class);
}
return smsService;
}
5. Кодировка FileReader по умолчанию приводит к искажению символов
public class FileReaderTest {
public static void main(String[] args) throws IOException {
Files.deleteIfExists(Paths.get("jay.txt"));
Files.write(Paths.get("jay.txt"), "你好,捡田螺的小男孩".getBytes(Charset.forName("GBK")));
System.out.println("系统默认编码:"+Charset.defaultCharset());
char[] chars = new char[10];
String content = "";
try (FileReader fileReader = new FileReader("jay.txt")) {
int count;
while ((count = fileReader.read(chars)) != -1) {
content += new String(chars, 0, count);
}
}
System.out.println(content);
}
}
результат операции:
系统默认编码:UTF-8
���,�����ݵ�С�к�
Из текущих результатов мы можем узнать, что кодировка системы по умолчанию — utf8, которая считывается из демо, и есть искаженные символы. Зачем?
FileReader — это когдаНабор символов по умолчанию прежней машиныЧтобы прочитать файл, если вы хотите указать набор символов, вам нужно напрямую использовать InputStreamReader и FileInputStream.
Например:
public class FileReaderTest {
public static void main(String[] args) throws IOException {
Files.deleteIfExists(Paths.get("jay.txt"));
Files.write(Paths.get("jay.txt"), "你好,捡田螺的小男孩".getBytes(Charset.forName("GBK")));
System.out.println("系统默认编码:"+Charset.defaultCharset());
char[] chars = new char[10];
String content = "";
try (FileInputStream fileInputStream = new FileInputStream("jay.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, Charset.forName("GBK"))) {
int count;
while ((count = inputStreamReader.read(chars)) != -1) {
content += new String(chars, 0, count);
}
}
System.out.println(content);
}
}
6. Целочисленная кэш-память
public class IntegerTest {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println("a==b:"+ (a == b));
Integer c = 128;
Integer d = 128;
System.out.println("c==d:"+ (c == d));
}
}
результат операции:
a==b:true
c==d:false
Почему значение Integer не равно 128?Компилятор преобразует Integer a = 127 в Integer.valueOf(127).Давайте посмотрим на исходный код.
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Можно обнаружить, что если i находится в определенном диапазоне, кэш будет возвращен.
По умолчанию этот интервал кеша равен [-128, 127], поэтому в ежедневной разработке нашего бизнеса, если речь идет о сравнении значений Integer, нам нужно обратить внимание на эту яму. Так же установив в параметрах JVM плюс -XX:AutoBoxCacheMax=1000 можно настроить этот параметр interval, можете сами попробовать
7. Используйте ThreadLocal, информация о повторном использовании потоков приводит к путанице в яме
Использование ThreadLocal для кэширования информации может привести к путанице в информации. Взгляните на этот пример.
private static final ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
@GetMapping("wrong")
public Map wrong(@RequestParam("userId") Integer userId) {
//设置用户信息之前先查询一次ThreadLocal中的用户信息
String beforeUser = Thread.currentThread().getName() + ":" + currentUser.get();
//设置用户信息到ThreadLocal
currentUser.set(userId);
//设置用户信息之后再查询一次ThreadLocal中的用户信息
String afterUser = Thread.currentThread().getName() + ":" + currentUser.get();
//汇总输出两次查询结果
Map map = new HashMap();
map.put("before", beforeUser);
map.put("after", afterUser);
return map;
}
Понятно, что каждый раз, когдаbeforeUser
Все они должны быть нулевыми, но программа выполняется в Tomcat, поток, выполняющий программу, является рабочим потоком Tomcat, а рабочий поток Tomcat основан на пуле потоков.
Пул потоков будет повторно использовать фиксированное количество потоков.После повторного использования потоков вполне вероятно, что значение, полученное из ThreadLocal в первый раз, является значением, оставшимся от предыдущего запроса других пользователей. В настоящее время информация о пользователе в ThreadLocal является информацией о других пользователях.
Установите рабочий поток tomcat на 1
server.tomcat.max-threads=1
Пользователь 1, когда придет запрос, будет иметь следующие результаты, как и ожидалось:
Пользователь 2 запрашивает прийти, и будут получены следующие результаты,не так, как ожидалось:
Поэтому при использовании таких инструментов, как ThreadLocal, для хранения некоторых данных нужно уделять особое внимание явной очистке установленных данных после выполнения кода, например:
@GetMapping("right")
public Map right(@RequestParam("userId") Integer userId) {
String beforeUser = Thread.currentThread().getName() + ":" + currentUser.get();
currentUser.set(userId);
try {
String afterUser = Thread.currentThread().getName() + ":" + currentUser.get();
Map map = new HashMap();
map.put("before", beforeUser);
map.put("after", afterUser);
return map;
} finally {
//在finally代码块中删除ThreadLocal中的数据,确保数据不串
currentUser.remove();
}
}
8. Игнорирование возврата и разрыва переключателя
Строго говоря, это не следует считать ямой, но при написании кода некоторых друзей легко не заметить.
/*
* 关注公众号:
* 捡田螺的小男孩
*/
public class SwitchTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("testSwitch结果是:"+testSwitch("1"));
}
private static String testSwitch(String key) {
switch (key) {
case "1":
System.out.println("1");
case "2":
System.out.println(2);
return "2";
case "3":
System.out.println("3");
default:
System.out.println("返回默认值");
return "4";
}
}
}
Выходной результат:
测试switch
1
2
testSwitch结果是:2
переключатель будетСопоставление на всем протяжении дела, пока вы не столкнетесь с возвратом или разрывом.Итак, при написании кода обращайте внимание на то, хотите ли вы получить такой результат.
9. Несколько ям Arrays.asList
9.1 Примитивный тип нельзя использовать в качестве параметра метода Arrays.asList, иначе он будет использоваться как параметр.
public class ArrayAsListTest {
public static void main(String[] args) {
int[] array = {1, 2, 3};
List list = Arrays.asList(array);
System.out.println(list.size());
}
}
результат операции:
1
Исходный код Arrays.asList выглядит следующим образом:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
9.2 Список, возвращаемый Arrays.asList, не поддерживает добавление или удаление.
public class ArrayAsListTest {
public static void main(String[] args) {
String[] array = {"1", "2", "3"};
List list = Arrays.asList(array);
list.add("5");
System.out.println(list.size());
}
}
результат операции:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at object.ArrayAsListTest.main(ArrayAsListTest.java:11)
Список, возвращаемый Arrays.asList, не соответствует нашим ожиданиям. java.util.ArrayList
, но внутренний класс массивовArrayList
. ArrayList внутреннего класса реализует не метод добавления, а реализацию метода добавления родительского класса, который вызовет исключение.
9.3 При использовании Arrays.asLis модификации исходного массива повлияют на полученный список.
public class ArrayAsListTest {
public static void main(String[] args) {
String[] arr = {"1", "2", "3"};
List list = Arrays.asList(arr);
arr[1] = "4";
System.out.println("原始数组"+Arrays.toString(arr));
System.out.println("list数组" + list);
}
}
результат операции:
原始数组[1, 4, 3]
list数组[1, 4, 3]
Как видно из бегущих результатов, исходный массив изменился,Arrays.asList
Преобразованный список также изменился.Всем следует обратить внимание при его использовании.Вы можете использоватьnew ArrayList(Arrays.asList(arr))
Упакуйте это.
10. Яма принудительного вращения ArrayList.toArray()
public class ArrayListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(1);
list.add("公众号:捡田螺的小男孩");
String[] array21 = (String[])list.toArray();//类型转换异常
}
}
Поскольку возвращается тип объекта, массив типа объекта вынужден преобразовать массив строк, что произойдетClassCastException
. Решение состоит в том, чтобы перегрузить метод toArray(T[] a) с помощью toArray().
String[] array1 = list.toArray(new String[0]);//可以正常运行
11. Несколько ям используются ненормально
11.1 Не теряйте информацию об исключениях стека
public void wrong1(){
try {
readFile();
} catch (IOException e) {
//没有把异常e取出来,原始异常信息丢失
}
}
public void wrong2(){
try {
readFile();
} catch (IOException e) {
//只保留了异常消息,栈没有记录啦
log.error("文件读取错误, {}", e.getMessage());
}
}
Правильный метод печати должен быть фиолетовым
public void right(){
try {
readFile();
} catch (IOException e) {
//把整个IO异常都记录下来,而不是只打印消息
log.error("文件读取错误", e);
}
}
11.2 Не определяйте исключения как статические переменные
public void testStaticExeceptionOne{
try {
exceptionOne();
} catch (Exception ex) {
log.error("exception one error", ex);
}
try {
exceptionTwo();
} catch (Exception ex) {
log.error("exception two error", ex);
}
}
private void exceptionOne() {
//这里有问题
throw Exceptions.ONEORTWO;
}
private void exceptionTwo() {
//这里有问题
throw Exceptions.ONEORTWO;
}
исключение выброшено exceptionTwo, скорее всегоexceptionOne
О, исключение. Правильное использование метода, а не статической переменной, но должно быть новым.
private void exceptionTwo() {
throw new BusinessException("业务异常", 0001);
}
11.3 Не используйте e.printStackTrace() в рабочей среде;
public void wrong(){
try {
readFile();
} catch (IOException e) {
//生产环境别用它
e.printStackTrace();
}
}
Поскольку он занимает слишком много памяти, это приводит к блокировке, а журналы чередуются и их нелегко читать. Правильное использование заключается в следующем:
//放弃使用e.printStackTrace();
log.error("异常日志正常打印方式",e);
11.4 Что делать, если во время отправки пула потоков возникает исключение?
public class ThreadExceptionTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
IntStream.rangeClosed(1, 10).forEach(i -> executorService.submit(()-> {
if (i == 5) {
System.out.println("发生异常啦");
throw new RuntimeException("error");
}
System.out.println("当前执行第几:" + Thread.currentThread().getName() );
}
));
executorService.shutdown();
}
}
результат операции:
当前执行第几:pool-1-thread-1
当前执行第几:pool-1-thread-2
当前执行第几:pool-1-thread-3
当前执行第几:pool-1-thread-4
发生异常啦
当前执行第几:pool-1-thread-6
当前执行第几:pool-1-thread-7
当前执行第几:pool-1-thread-8
当前执行第几:pool-1-thread-9
当前执行第几:pool-1-thread-10
Можно обнаружить, что если это асинхронная задача, отправленная в пул потоков с использованием метода отправки, исключение будет проглочено, поэтому при ежедневном обнаружении, если есть предсказуемое исключение, вы можете принять следующие решения:
- 1. Перехват исключения в коде задачи try/catch
- 2. Получите выброшенное исключение через метод get объекта Future, а затем обработайте его.
- 3. Установите UncaughtExceptionHandler для рабочего потока и обработайте исключение в методе uncaughtException.
- 4. Перепишите метод afterExecute класса ThreadPoolExecutor для обработки переданной ссылки на исключение.
11.5 Обратите внимание на исключение, которое наконец перевыдает
public void wrong() {
try {
log.info("try");
//异常丢失
throw new RuntimeException("try");
} finally {
log.info("finally");
throw new RuntimeException("finally");
}
}
У метода не будет двух исключений, поэтому исключение finally попытаетсяпокрытие исключений. Правильный способ его использования должен быть, наконец, блок кодаОтвечает за собственный захват и обработку исключений..
public void right() {
try {
log.info("try");
throw new RuntimeException("try");
} finally {
log.info("finally");
try {
throw new RuntimeException("finally");
} catch (Exception ex) {
log.error("finally", ex);
}
}
}
12. Сериализация JSON, тип Long преобразуется в тип Integer!
public class JSONTest {
public static void main(String[] args) {
Long idValue = 3000L;
Map<String, Object> data = new HashMap<>(2);
data.put("id", idValue);
data.put("name", "捡田螺的小男孩");
Assert.assertEquals(idValue, (Long) data.get("id"));
String jsonString = JSON.toJSONString(data);
// 反序列化时Long被转为了Integer
Map map = JSON.parseObject(jsonString, Map.class);
Object idObj = map.get("id");
System.out.println("反序列化的类型是否为Integer:"+(idObj instanceof Integer));
Assert.assertEquals(idValue, (Long) idObj);
}
}
результат операции:
Exception in thread "main" 反序列化的类型是否为Integer:true
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
at object.JSONTest.main(JSONTest.java:24)
Внимание, После сериализации в строку Json строка Josn не имеет типа Long. А если десериализацию тоже получит Object, и число будет меньше максимального значения Integer, то оно будет преобразовано в Integer!
13. Используйте Executors для объявления пула потоков, проблема OOM newFixedThreadPool
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
});
}
IDE указывает параметры JVM: -Xmx8m -Xms8m :
результат операции:
Давайте посмотрим на исходный код, на самом деле newFixedThreadPool используетНеограниченная очередь!
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
...
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
...
}
Количество основных потоков в пуле потоков newFixedThreadPool фиксировано, и он использует почти неограниченную очередь блокировки LinkedBlockingQueue. Когда основной поток израсходован, задача будет помещена в очередь блокировки.Если время выполнения задачи относительно велико и не освобождается, все больше и больше задач будет накапливаться в очереди блокировки, и, наконец, использование памяти машина продолжит парить.Вызывает JVM OOM.
14. Направление большого файла или однократное чтение из базы данных слишком большого объема данных в память может привести к проблемам с OOM.
Если большой файл или база данных слишком много данных достигает памяти за один раз, это приведет к OOM. Поэтому, почему обычно рекомендуется запрашивать базу данных БД в пакетном режиме.
Если файл читается, общий файл не будет слишком большим, поэтому используйтеFiles.readAllLines()
. Зачем? Поскольку он напрямую считывает файл в память, считается, что он не будет использоваться до OOM.Вы можете посмотреть его исходный код:
public static List<String> readAllLines(Path path, Charset cs) throws IOException {
try (BufferedReader reader = newBufferedReader(path, cs)) {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
break;
result.add(line);
}
return result;
}
}
Если файл слишком большой, вы можете использоватьFiles.line()
Чтение по требованию, при чтении файлов, как правило, они необходимы после использования.закрыть поток ресурсовха-ха
15. Сначала запросите, а затем обновите/удалите параллельную проблему согласованности
В ежедневной разработке часто встречается такая реализация кода: сначала проверяют, остались ли свободные тикеты, а потом обновляют баланс тикетов.
if(selectIsAvailable(ticketId){
1、deleteTicketById(ticketId)
2、给现金增加操作
}else{
return “没有可用现金券”
}
Если он выполняется одновременно, это, вероятно, будет проблематичным. Следует использовать атомарность обновления / удаления базы данных. Правильное решение выглядит следующим образом:
if(deleteAvailableTicketById(ticketId) == 1){
1、给现金增加操作
}else{
return “没有可用现金券”
}
16. БД хранится в utf-8, а питы с ненормальными выражениями вставлены
Кодировка utf8, поддерживаемая более ранней версией MySQL, имеет максимальную длину символа 3 байта, но для хранения выражений требуется 4 байта, поэтому, если вы используете utf8 для хранения выражений, он сообщитSQLException: Incorrect string value: '\xF0\x9F\x98\x84' for column
, поэтому обычно используйте кодировку utf8mb4 для хранения выражений.
17. Яма, что весенняя сделка не вступает в силу
В ежедневном развитии бизнеса мы часто имеем дело с делами,признание сделки недействительнойВ основном это следующие сценарии:
- Базовый механизм базы данных не поддерживает транзакции
- Использование в непубличных модифицированных методах
- Свойство rollbackFor задано неправильно
- Этот метод класса вызывается напрямую
- Исключение было съедено try...catch, что привело к сбою транзакции.
Среди них легче всего наступить на две последние ямы.Аннотированный метод транзакции напрямую вызывает метод этого класса, псевдокод выглядит следующим образом:
public class TransactionTest{
public void A(){
//插入一条数据
//调用方法B (本地的类调用,事务失效了)
B();
}
@Transactional
public void B(){
//插入数据
}
}
Если вы живете с ловушкой исключения,Что транзакция также провалится~, псевдо код выглядит следующим образом:
@Transactional
public void method(){
try{
//插入一条数据
insertA();
//更改一条数据
updateB();
}catch(Exception e){
logger.error("异常被捕获了,那你的事务就失效咯",e);
}
}
18. Когда отражение встречает яму перегрузки метода
/**
* 反射demo
* @author 捡田螺的小男孩
*/
public class ReflectionTest {
private void score(int score) {
System.out.println("int grade =" + score);
}
private void score(Integer score) {
System.out.println("Integer grade =" + score);
}
public static void main(String[] args) throws Exception {
ReflectionTest reflectionTest = new ReflectionTest();
reflectionTest.score(100);
reflectionTest.score(Integer.valueOf(100));
reflectionTest.getClass().getDeclaredMethod("score", Integer.TYPE).invoke(reflectionTest, Integer.valueOf("60"));
reflectionTest.getClass().getDeclaredMethod("score", Integer.class).invoke(reflectionTest, Integer.valueOf("60"));
}
}
результат операции:
int grade =100
Integer grade =100
int grade =60
Integer grade =60
еслине через отражение, входящийInteger.valueOf(100)
, Принимая целое число перегружено. Но тогда это не отражено на эталонном методе, чтобы определить тип тяжелых долгов, ноОпределяется именем метода и типом параметра, передаваемым при получении метода путем отражения., правильное решение выглядит следующим образом:
getClass().getDeclaredMethod("score", Integer.class)
getClass().getDeclaredMethod("score", Integer.TYPE)
19. Яма временной метки mysql
При наличии оператора обновления метка времени может автоматически обновляться до текущего времени, см. демонстрацию
CREATE TABLE `t` (
`a` int(11) DEFAULT NULL,
`b` timestamp NOT NULL,
`c` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8
мы можем узнатьстолбец сТам естьCURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
, поэтому столбец c будет меняться по мере обновления записиОбновить до текущего времени. Но столбец b также будет обновляться, так как есть записиОбновить до текущего времени.
Вы можете использовать datetime вместо него, когда вам нужно обновить до текущего времени, поставьтеnow()
Просто назначьте его или измените этот параметр mysql.explicit_defaults_for_timestamp
.
20. Яма часового пояса базы данных mysql8
Прежде чем мы обновили базу данных mysql, новая версия — 8.0.12. Но после обновления я нашел функцию now(), и полученное время на 8 часов позже, чем время в Пекине.Получается, что mysql8 по умолчанию использует время в Соединенных Штатах и требует указать часовой пояс.
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&
serverTimezone=Asia/Shanghai
21. Проблемы линейной безопасности с SimpleDateFormat
public class SimpleDateFormatTest {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));
while (true) {
threadPoolExecutor.execute(() -> {
String dateString = sdf.format(new Date());
try {
Date parseDate = sdf.parse(dateString);
String dateString2 = sdf.format(parseDate);
System.out.println(dateString.equals(dateString2));
} catch (ParseException e) {
e.printStackTrace();
}
});
}
}
результат операции:
Exception in thread "pool-1-thread-49" java.lang.NumberFormatException: For input string: "5151."
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.example.demo.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:19)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-47" java.lang.NumberFormatException: For input string: "5151."
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.example.demo.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:19)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
SimpleDateFormat глобальной переменной имеет проблемы с безопасностью в случае параллелизма.
- SimpleDateFormat расширяет DateFormat
- Глобальная переменная Calendar поддерживается в классе DateFormat.
- И sdf.parse(dateStr), и sdf.format(date) сохраняются со ссылкой на календарь.
- Если SimpleDateFormat является статическим глобально общим, ссылки на календари также являются общими.
- А поскольку в Calendar нет механизма безопасности потоков, глобально общий SimpleDateFormat не является линейно безопасным.
Участие и благодарность
публика
Добро пожаловать в публичный аккаунт:маленький мальчик собирает улиток