本篇博文主要讲博主在学习Java
上的一些常用知识,内容涉及J2SE
到J2EE
。写作主要目的是方便自己查看巩固,当然也借此机会可以与其他人在学习上有一个相互交流的机会,有些笔记可能看起来理所当然,但是依然觉得有必要记录一下。
本文采取持续更新策略,但更新周期不定
集合与数组
根据数组来创建List
以字符串数组为例:
String[] arr = {"Reka", "Downey", "Leakey"};
List<String> list = Arrays.asList(arr);
以上创建的List
不可添加或删除容器中的元素,因为通过Arrays.asList
创建的ArrayList
实际上是Arrays
中的一个私有静态类,并不是传统意义上的java.util.ArrayList
;如果需要往容器中添加或者删除元素,必须将生成的List
进一步封装成java.util.ArrayList
:
注:在创建字符串数组arr
时使用的是JDK5
及以上的特性:一个花括号{}
代表一个一维数组,花括号嵌套时为多维数组。
List<String> list = new ArrayList<>(Arrays.asList("Reka", "Downey", "Leakey"));
根据List
来创建数组:
List<String> list = Arrays.asList("A", "B", "C");
String[] arr = (String[]) list.toArray(); // 运行正常
list = new ArrayList<String>(list);
arr = (String[]) list.toArray(); // 运行时抛出 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
Arrays.ArrayList
是直接调用clone
方法将数组转成List
,而java.util.ArrayList
则是委托给Arrays
的copyOf
方法,两者的实现不同导致结果不同(具体得看源码),为了确保转换的正确性,我们应该按下面这种方式进行转换:
List<String> list = Arrays.asList("A", "B", "C");
String[] arr = new String[list.size()];
arr = list.toArray(arr); // 运行正常
list = new ArrayList<String>(list);
arr = new String[list.size()];
arr = list.toArray(arr); // 运行正常
虽然实际上在调用List.toArray(T[] arr)
可以不需要赋值给arr
,但是为了保证安全与正确,最好还是将其回赋给arr
(因为当arr
的容量与List
的容量不等时返回的是一个新的数组),具体看源码。
在遍历List
时移除其中的元素
使用foreach
:
List<String> list = new ArrayList<>(Arrays.asList("Reka", "Downey", "Leakey"));
for (String s : list) {
if ("Reka".equals(s)) {
list.remove(s);
}
}
此时在执行remove
操作的时候将抛出java.util.ConcurrentModificationException
异常,下面看看使用JDK8
流式API
进行操作:
List<String> list = new ArrayList<>(Arrays.asList("Reka", "Downey", "Leakey"));
list.stream().filter("Reka"::equals).forEach(list::remove);
此时依然将抛出ConcurrentModificationException
异常,但是,如果元素Reka
并不在集合中的时候,上述两者都将不会抛出异常,正确的移除方式应该是通过Iterator
迭代器实现以确保安全:
List<String> list = new ArrayList<>(Arrays.asList("Reka", "Downey", "Leakey"));
Iterator<String> stringIterator = list.iterator();
while (stringIterator.hasNext()) {
String s = stringIterator.next();
if ("Reka".equals(s)) {
stringIterator.remove();
}
}
此时才能正确移除容器中的Reka
元素。注意:以上的情况都没有考虑多线程情况,在多线程情况下,List
也要做相应的修改以适应多线程访问。
反射
检测类中的某个字段是否是常用类型
(个人对常用类型的定义为:基础数据类型及其包装类、String
):
import java.lang.reflect.Field;
/**
* @author : Chung Junbin
* @email : <a href="mailto:rekadowney@163.com">发送邮件</a>
* @createDate : 2016-09-06 16:10
* @description :
*/
public class FieldTypeConsultant {
public static boolean isPrimitive(Field field) {
return field.getType().isPrimitive();
}
public static boolean isWrapped(Field field) {
Field type = null;
try {
type = field.getType().getDeclaredField("TYPE");
} catch (NoSuchFieldException ignored) {
// 没有这个字段则表示不是基础数据类型的包装类型
}
return type != null;
}
public static boolean isCommonType(Field field) {
return field.getType() == String.class || isPrimitive(field) || isWrapped(field);
}
}
另一种比较简单粗暴的做法是:
import java.lang.reflect.Field;
/**
* @author : Chung Junbin
* @email : <a href="mailto:rekadowney@163.com">发送邮件</a>
* @createDate : 2016-09-06 21:49
* @description :
*/
public class CurtFielTypeConsultant {
private static final Class[] primitiveClasses = {char.class, boolean.class, byte.class,
short.class, int.class, long.class, float.class, double.class/*, void.class*/};
private static final Class[] wrappedClasses = {Character.class, Boolean.class, Byte.class,
Short.class, Integer.class, Long.class, Float.class, Double.class/*, Void.class*/};
public static boolean isPrimitive(Field field) {
Class<?> typeOfField = field.getType();
for (Class<?> clazz : primitiveClasses) {
if (clazz == typeOfField) {
return true;
}
}
return false;
}
public static boolean isWrapped(Field field) {
Class<?> typeOfField = field.getType();
for (Class<?> clazz : wrappedClasses) {
if (clazz == typeOfField) {
return true;
}
}
return false;
}
public static boolean isCommonType(Field field) {
return field.getType() == String.class || isPrimitive(field) || isWrapped(field);
}
}
两种效果一致,但个人更加推崇第一种方式。
类路径下文件读写
读写类路径下的文件(配置文件和class
文件)
有如下目录结构
src
└──main
├──java
└──resources
│ log4j.properties
│
└──bundle
db.properties
提供文件检索类FielRetrieval
import java.io.File;
import java.net.URL;
import java.util.Optional;
/**
* @author : Chung Junbin
* @email : <a href="mailto:rekadowney@163.com">发送邮件</a>
* @createDate : 2016-09-07 10:03
* @description :
*/
public class FileRetrieval {
/**
* <p>读取 classpath 下的文件</p>
* <pre>
* 例如:目录结构为:
* src
* main
* resources
* bundle
* db.properties
* log4j.properties
* 那么读取 log4j.properties 应该传入 {@code log4j.properties} 作为参数;而读取
* db.properties 则应该传入 {@code bundle/db.properties}
* </pre>
*
* @param filename 文件名称(包括目录)
* @return 文件本身
*/
public static Optional<File> ofFilename(String filename) {
URL url = FileRetrieval.class.getClassLoader().getResource(filename);
if (url == null) {
return Optional.empty();
}
File file = new File(url.getFile());
return Optional.of(file);
}
}
附:FileRetrieval.class.getClassLoader().getResource(filename)
与 FileRetrieval.class.getResource('/' + filename)
是等效的;该类放在src/main/java
下则只能读写src/main/resources
下的配置文件和src/main/java
生成的类文件;反之放在src/test/java
下则只能读写src/test/resources
下的配置文件和src/test/java
生成的类文件
当然:如果仅仅是读取properties
文件,那么还有一种更加方便快捷的方式:
ResourceBundle log4jResourceBundle = ResourceBundle.getBundle("log4j");
ResourceBundle dbResourceBundle = ResourceBundle.getBundle("bundle/db");
System.out.println(dbResourceBundle.getString("mysql.username"));
注:ResourceBundle
传入的是basename
,basename
是不包括文件扩展名的。
字符编码
这里扯一下软件安装的事情:安装软件的时候,博主会尽量首选软件的压缩包而不是安装包,因为安装包有可能自动添加了一些环境变量,而且一不小心就有可能安装在默认路径上了,这样对软件管理相当不方便;博主更习惯创建一个存放所有软件的根目录,在软件根目录内部按照类别创建第二级目录,之后再根据那些软件定位到各自所属的科目下(文档、音乐、视频等都差不多一个模式)。
第二:软件安装完毕之后,第一次打开软件时,建议找到Preference
、Options
、Settings
等可以修改参数的菜单,之后一个个菜单看,将所有的字符编码都设置为UTF-8
,另外还可以设置一些首选下载目录、关闭自动生成备份文件功能等等。例如:我在安装IDEA
的时候宁愿花半个甚至一个、两个小时的时间将全局配置与项目配置的菜单项都看一遍,接着修改字符编码,修改默认Java
编译器、添加SVN
、Git
支持、导入GitHub
账户、指定Maven
路径、指定Tomcat
路径、配置字体(首选等宽字体)、修改文件模板、关闭不必要的插件等等一系列内容。
好了,有些偏题了。
在编程中用到字符编码的时候,博主首选的是JDK7
及以上的StandardCharsets.UTF_8
,StandardCharsets
是一个final
修饰的类,该类存放了六种在任何Java
平台上都保证可以使用的标准字符集。分别是UTF-8
、US-ASCII
、ISO-8859-1
、UTF-16BE
、UTF-16LE
、UTF-16
。首推当然是UTF-8
,其次是在网络传输中有时得用到的ISO-8859-1
。
这样以后使用字符集的时候就应该尽量选择可以接收Charset
的重载方法而不是使用接收字符集名称的方法。比如:
String message = "Message";
byte[] utf8 = message.getBytes("UTF-8"); // 不推荐,需要捕获异常
utf8 = message.getBytes(StandardCharsets.UTF_8); // 推荐
注:通过Charset.availableCharsets();
可以得到平台支持的字符集名称以及他们对应的字符集。
原创声明:
本文为个人原创,如有错误与不妥,欢迎提出!
另外,未经许可,谢绝转载。