Home / Blog / 编程语言
Tech · 编程语言 · Java

java 手册

H by Haofly
· 2016-06-27 · updated 2023-07-26 · 61 views

安装方法

参考How To Install Java with Apt-Get on Ubuntu 16.04

  • JDK是Java开发的一个工具包,其他的工具包还有J2SE、JAVA SE

  • JDK8和JDK1.8是两种新旧的命名方式,其实是一个东西

    Java SEJDK发布时间
    Java SE 8JDK 1.82014
    Java SE 11JDK112018
    Java SE 17JDK 172021

数据类型

  • final关键字: 修饰类表示该类不能被继承,内部所有成员变量都是final的; 类的private方法也会隐式地指定为final方法。修饰变量时,如果是基本数据类型的变量,则其数值在初始化之后就不能更改; 如果是引用类型的变量,则初始化后不能被指向另一个对象。

  • object.getField() == 1: 这种比较可能出现空指针异常

  • 获取对象的类: object.getClass()

  • 使用Optional来减少空指针异常:

    public static Optional<List<String>> getA(boolean a) {
      if (a) {
        String [] results = {"a", "b", "c"};
        return Optional.of(Arrays.asList(results));
      }
      return Optional.empty();	// 不用返回null或者空数组了
    }
    
    public static void test() {
      Optional<List<String>> re = getA(true);
      re.isPresent();	// 对象是否存在
      re.ifPresent(result -> {
        console.log(result);
      });
    }

Integer/Long/Double/Float/BigDecimal/AtomicInteger数字

  • 千万不要用Double/Float来定义金额,因为经常会出现浮点数的精度问题,最好用大数类,例如BigDecimal/BigInteger
  • AtomicInteger是一个线程安全整数类,同时只有一个线程可以对其操作
a.longValue();	// 整型转长整型
longValue.intValue();	// long转换为int
1L;	// 直接将数字转换成Long型
String.valueOf(123); // 整型转字符串,避免用toString出现空指针异常
(byte)1;	// int to byte,int转字节
(int) myByte;	// byte to int,字节转int
(float) 1;	// int to float
new Long(12);	// Integer转Long

Math.ceil(9.2);	// 向上取整
Math.floor(9.2);// 向下取整
Math.round(9.2); // 四舍五入
Math.abs(-0.9);	// 取绝对值

a == 0 ? false : true;	// 整型转换为布尔
a ? 1 : 0;	// 布尔转换为整型

BigDecimal.ZERO;	// 直接就是BigDecimal类型的0
a.add(b);	// BigDecimal加法
a.subtract(b);	// BigDecimal减法
a.multiply(b);	// BigDecimal乘法
a.divide(b);	// BigDecimal除法
a.compareTo(b); // 比较BigDecimal,结果为0表示相当,为-1表示小于,为1表示大于,>-1表示大于等于,小于1表示小于等于。不要用equals方法来比较BigDecimal对象,如果scale不一样,会直接返回false
a.setScale(2);	// 四舍五入保留两位小数
a.setScale(2, BigDecimal.ROUND_DOWN); // 向下取整
a.setScale(2, BigDecimal.ROUND_UP); // 向上取整

String/StringBuffer字符串

String不同的是,StringBufferStringBuilder类的对戏那个能够被多次修改,并且不产生新的未使用的对戏那个。

// String
String a = "World";
String b = new String(array);	// 将array拼接成字符串
String[] c = new String[] {"A", "B", "C"};
List<String> list = Arrays.asList(c);	// String[] 转换为 List<String>
int len = b.length();			// 得到字符串长度
b.concat(a);					// 连接字符串
b + a;							// 连接字符串
System.out.printf("test %s", a);
String.format("test %s", a);// 格式化字符串
b.charAt(0);					// 得到指定索引的字符
a.compareTo(Object o);			
a.compareToIgnoreCase(String str);// 比较字符串
a.startsWith(String prefix);
a.endsWith(String suffix);			// 验证字符串是否以某个子字符串结尾
a.indexOf(String str);				// 返回子字符串首次出现的位置,验证是否包含某个子字符串,没找到返回-1
a.contains(str);			// 直接检验是否包含某个子字符串
a.matches(".*?");					// 验证字符串是否复合正则表达式
a.replaceAll(String regex, String replacement); // 替换字符串
String[] strArr = a.split(String regex);			// 拆分字符串,字符串分隔/字符串分割
a.trim();						// 移除首尾空白
Integer.parseInt(str);		// 字符串转整型
Long.parseLong(str);		// 字符串转Long型
String.join(",", new String[]{"foo", "bar"})	// 合并字符串,类似PHP的implode,字符串中间添加空格
new BigDecimal("1.00"); // String转BigDecimal
  
// 判断字符串是否为空
str == null;
"".equals(str);
str.length <= 0;
str.isEmpty();

// StringBuffer
StringBuffer c = new StringBuffer('Hello World');
c.append(String s);		// 在字符串尾部追加
c.reverse();			// 反转字符串
c.capacity();			// 返回当前字符串的容量

// 日期时间
Date date = new Date();
System.out.println(date.toString());

// 字符数组转字符串,不用toString方法
char[] data = {'a', 'b', 'c'};
String str = new String(data);

// ArrayList<Chracter> to String 
String getStringRepresentation(ArrayList<Character> list)
{    
    StringBuilder builder = new StringBuilder(list.size());
    for(Character ch: list)
    {
        builder.append(ch);
    }
    return builder.toString();
}

// 字符串反转
StringBuilder sb = new StringBuilder("content");
StringBuilder re = sb.reverse();

List list = new ArrayList(myCollections);	// Collections转list

// URLDecode/URLEncode,需要注意的是,如果出现特殊符号%,后面跟着中文,那么decode居然会报错
URLDecode.decode("test", "utf-8");
JSON
// Json字符串转换为Dto
MyDto myDto = new Gson().fromJson(jsonString, MyDto.class);
new JsonParser().parse(jsonString).getAsJsonObject().get("key1").toString();	// 直接获取指定的key的值,而不用新建一个对象。但是有个坑是这样得到的字符串两边会带上引号。。。

// 验证是否是Json字符串
try {
  JSONObject result = JSONObject.parseObject(string);
  return null != result;
} catch (Exception e) {
  return false;
}

// 任意对象转JSON字符串
import com.google.gson.Gson;
Gson gson = new Gson();
String jsonString = gson.toJson(myObj);
System.out.println(jsonString);
正则匹配
  • java的正则匹配没有findAll的概念,需要自己在正则中加入类似()*来实现多次匹配
Pattern p = Pattern.compile("(a.*?a)*", Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);	// 可以配置大小写不敏感;查找多行
Matcher matcher = p.matcher("content");
// 遍历匹配结果方式一
while (matcher.find()) {
  System.out.println(matcher.group());
}
// 遍历匹配结果方式二
if (matcher.find() && matcher.groupCount() >= 1) {
  matches = new ArrayList();
  for (int i = 1; i <= matcher.groupCount(); i++) {
    System.out.println(matcher.group(i));
  }
}

// 正则替换
str.replaceAll(reg, "");

Array/Vector/Stack/Enumeration/Collections/ArrayList数组

  • 数组的大小是无法改变的,如果要实现改变数组长度,可以采取新建一个数组然后返回新数组的指针的方式。
// 初始化&赋值
typeName[] arrayName; // 声明数组的基本方式,也可以typeName arrayName[]
typeName arrayName[][] = new typeName[3][4];	// 定义多维数组
double[] myList = new double[5];	// 创建指定长度的数组
List<String> names = Arrays.asList("xxx","yyy","zzz");	// 直接初始化固定长度的数组,但是要超过一个元素才行
List<String> list1 = new ArrayList<>();	// 初始化一个空数组,之后用add添加元素
list1.addAll(list2);	// 将数组2合并到数组1
List<String> names = new ArrayList<String>() {
  {
    for (int i = 0; i< 10; i++) {
      add("add"+i);
    }
  }
};

Arrays.asList("a", "b").size(); // 获取数组长度length
Arrays.asList("a", "b").contains("c"); // 数组是否包含某个值

// 遍历数组
for (double element: myList) {}
for (int i = 0 ; i < myList.size(); i++) {}

// 遍历数组并移除元素
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("banana")) {
        iterator.remove(); // 根据条件删除元素
    }
}

// Vector类,动态数组
// Stack栈
Stack<Integer> d = new Stack<integer>();
d.push(int a);

// Enumeration枚举类型
Enumeration<String> days;	// 定义枚举变量
Vector<String> dayNames = new Vector<StringL>();
dayNames.add("Sunday");	// 添加枚举元素
days = dayNames.elements();

// 数组反转
List<String> new = Lists.reverse(lists1);

// 数组分片
List<E> subList(fromIndex, toIndex);

Set/HashSet/Stream集合

String[] myList = new String[] { "a", "b" };
Set<String> mySet = new HashSet<String>(Arrays.asList(myList));	// 初始化
Stream API
  • 是一系列对集合便利操作的工具集,类似Laravel里面的Collection
  • Concat: 合并两个流: Stream.concat(stream1, stream2)
  • foreach: 遍历
  • map: 映射,返回新的元素
  • mapToInt/mapToDouble/mapToLong: 映射成指定的数字类型,映射完成后可以使用summaryStatistics方法得到统计的结果然后使用getMax/getMin/getSum/getAverage等方法
  • filter: 过滤,仅保留返回true的元素
  • limit: 仅保留指定数量的元素
  • sorted: 排序
  • Collectors: 用于返回列表或字符串
// 过滤
Record record = list.stream()
  .filter(record -> "name".equals(record.getName()))
  .findFirst()
  .orElse(null);

// 排序
Record record = list.stream()
  .sorted(Comparator.comparingInt(Record::getTime))
  .reversed()
  .collect(Collectors.toList());

Dictionary/Hashtable/Map/ConcurrentHashMap字典

  • ConcurrentHashMap是线程安全的
// Dictionary字典
// Hashtable

// map的初始化
HashMap<String, String> map = new HashMap<String, String>();
map.put("key", "value");
HashMap<String, String> map = new HashMap<String, String>() {
  {
    map.put("key1", "value1");
    map.put("key2", "value2");
  }
};

map.containsKey(Object key);	// 是否包含某个key
map.containsValue(Object value);	// 是否包含某个value
map.equals(Object o);	// 比较指定的对象与此映射是否相等个
map.get(Object key);	// 获取某个key的值
map.isEmpty();	// 是否为空
map.put(K key, V value);	// 设置值
map.remove(Object key);	// 移除某个键值对
map.size();	// 获取键值对数量
map.values();	// 返回所有的value,是一个Collection对象

// Map的遍历
Map<String, String> map = new HashMap<String, String>();
// 遍历方法一
for (Map.Entry<String, String> entry : map.entrySet()) {
  System.out.println(entry.getKey(), entry.getValue);
}
// 遍历方法二
for (String key : map.keySet()) {String value = map.get(Key);}
for (String value : map.values()) {}

// Map转为Json格式字符串
String jsonStr = new Gson().toJson(myMap);

Queue队列

Queue<String> queue = new LinkedList<String>();	// 定义队列
queue.offer("a");	// 添加元素,如果无法添加会返回false
queue.add("a");	// 添加元素,如果无法添加会抛出来异常
queue.poll();	// 返回第一个元素,并在队列中删除,没有会返回null
queue.remove();	// 从队列删除第一个元素,没有会抛出异常
queue.element();	// 返回第一个元素,没有会抛出异常
queue.peek();	// 返回第一个元素,没有会返回null

时间处理

Date date = new Date();	// 获取时间对象
Long timestamp = date.getTime();			// 获取时间戳(毫秒)
System.currentTimeMillis();	// 毫秒级时间戳
Date date = new Date(1234567890000); // 毫秒级时间戳转Date对象
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-05-01 00:00:00");	// 获取指定日期的date

// 获取今天开始的时间
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date zero = calendar.getTime();

// 获取ISO8601格式的时间,类似2019-12-12T12:12:12Z
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setTimeZone(tz);
return df.format(new Date());

// 解析CST格式的时间
String dateStr = "Wed Sep 11 10:10:10 CST 2020"; 
Date date = (Date) df.parse(df);

// 时间计算
date1.before(date2);	// 判断date1是否在date2之前

Calendar now = Calendar.getInstance()
now.setTime(date);	// 可以指定其他的date,不用非要是进Tina
now.set(Calendar.DATE, now.get(Calendar.DATE) + 7);	// 计算7天后的时间

类/对象/方法

  • 类中可以使用static {}设置静态代码块,有助于优化程序性能,static块可以放置于类中的任何地方,当类初次被加载的时候,会按照static块的顺序来执行每个块,并且只会执行一次。
  • 泛型类使用<T>来表示,? extends 类名(上边界限定)表示只要继承某个类的都可以,? super 类名(下边界限定)表示只要是某个类的父类都可以,单独的?(无边界限定)表示没有任何限制
// 一个类可以有多个构造方法
public class Sample {
  static {	// 静态代码块
    long a = System.nanoTime();
    testMethod();
  }
  public Sample() {}	// 不带参数的构造方法
  public Sample(String param1) {}	// 带参数的构造方法
  private static void testMethod() {}
}

// 泛型类,T可以传入任意类型
public class MyClass<T> {
  // 成员变量
  private T t;
  public MyClass(T t) {
    super();
    this.t = t;
  }
  public T getT() {return t;}
}

// 方法中使用Optinal表示可能为null,很大程度上能帮助调用者了解内部可能返回null
public Optinal<User> getUser(Long id) {
  if (null != id) {return Optinal.of(new User());}
  return Optinal.empty();
}
Optional<user> userOp = getUser(1L);
if (userOp.isPresent()) {...} else {...}

Function接口

  • Java8新增的函数式编程方法,主要用来做lambda表达式

  • 任何标注了@FunctionalInterfaced都接口都表示是一个函数式的接口

  • Functiond源码简介:

    @FunctionalInterface
    public interface Function<T, R> {	// T表示入参,R表示出参
      R apply(T t);
      // compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后在执行当前Function的apply,
      // 相当于 a.compose(b).apply(1) = a.apply(b.apply(1))
      default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
      };
      // andTthen是先执行当前的逻辑,再执行传入的逻辑。
      // 相当于a.andThen(b).apply(1) = b.apply(a.apply(1))
      default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
      };
      static <T> Function<T, T> identity() {
        return t -> t;
      };
    }
  • 例子:

Function<Integer,Integer> test=i->i+1;
test.apply(1);	// 会得到2

异常处理

  • 异常类的getMessage()toString()方法的区别,前者仅仅返回错误的信息,如果是空指针异常,一般返回的是null,而后者则会包含异常类的类型信息。建议如果是打印日志则使用toString() 方法,因为更详细,如果是给用户看的信息可以使用getMessage方法仅展示给用户关键信息

文件/文件夹

// 以行为单位读取文件
File file = new File(fileName);
BufferedReader reader = null;
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
while ((tempString = reader.readLine()) != null) {
	System.out.println("line " + line + ": " + tempString);
	line++;
}
reader.close();

// 读取整个文件
File file = new File(fileName);
if (file.isFile() && file.exists()) {
  long fileLength = file.length();
  byte[] fileContent = new byte[(int) fileLength];
  FileInputStream in = new FileInputStream(file);
  in.read(fileContent);
  in.close();
  String[] fileContentArr = new String(fileContent);	// 结果字符串数组
}

// 写入文件/新建文件
File file = new File(path);	// new File第二个参数如果为true,表示追加
if (!file.exists()) {
  file.createNewFile();
}
FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
bw.close();

// 新建文件夹
File dir = new File("/tmp/test/deep");
if (!dir.exists()) {
  dir.mkdirs();
}

Shell

// Java执行shell命令
String cmd = "ls | grep abc";
String[] commands = {"/bin/sh", "-c", cmd}; // 加入/bin/sh可以防止很多命令执行出错或者转义出错
Process process = Runtime.getRuntime().exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((s = stdInput.readline()) != null) {
  System.out.println(s);
}

  • JavaSE程序可以打包成Jar包(与平台无关的格式),JavaWeb程序可以打包成War包
import java.io.*;		// 导入java_installation/java/io下的所有类
java -jar myjar.jar;	// 直接用命令行运行jar包
java -cp myjar.jar com.example.MainClass	// 指定jar入口

线程/进程

  • ThreadLocal: 保证线程安全(一次HTTP请求从开始到响应都是在一个线程内部,其他用户是在其他的线程里面)
Thread current = THread.currentThread();	// 获取当前进程
current.getId();	// 获取当前进程Id

多线程

  • 线程池只能放入实现Runnable/callable类的线程,不能放入继承Thread的类
// 方法一、继承Thread类,缺点是无法多重继承
public class MyThread extends Thread {
  @Override
  public void run()
  {
    System.out.println("线程执行");
  }
}
new MyThread().start();	

// 方法二、实现Runnable接口,适合多线程共享资源
public class MyThread implements Runnable {
  public void run () {
    System.out.println("线程执行");
  }
}

MyThread mythread = new MyThread();
new Thread(mythread).start();

三方库

Jsch SSH连接工具

一个很老很久没有更新的工具,文档example比较全,但是只有这个工具用户量大一点,其他的用户量太少不敢用(Apache sshd则是因为文档太少,官方文档是针对它的server端)。执行shell命令的时候建议使用ChannelExec而不是ChannelShell(因为后者的输出流里面类似于一个终端,会包含一些没用的命令提示符).

Jsch jSch = new JSch();
jSch.addIdentity("name", prvKeyStr.getBytes, pubKeyStr.getBytes, keyPass.getBytes);	// 加载私钥公钥和私钥密码
Session session = jSch.getSession(username, ip, port);	// 新建session
session.setConfig("StrictHostKeyChecking", "no");	// 不进行静态host-key校验,否则可能出现UnknownHostKey错误
session.setTimeout(10000);	// 设置连接超时时间毫秒
session.connect();	// 连接

// 执行命令并获取返回结果
ChannelExec channelExec = (ChannelExec) this.session.openChannel("exec");
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream error = new ByteArrayOutputStream();
channelExec.setCommand("ls");	// 实际执行的命令
channelExec.setOutputStream(out);
channelExec.setErrStream(error);
channelExec.connect();
int sleepCount = 0;
do {		// 等待命令返回,官方手册是用的这种方法
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        result.setExitCode(1);
        result.setStderr(SERVER_EXEC_ERROR + e.getMessage());
        return result;
    }
} while (!channelExec.isClosed() && sleepCount++ < 60);
out.toString();	// 标准输出
error.toString();	// 标准错误输出
channelExec.getExitStatus();	// 获取返回状态码

TroubleShooting

  • Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $,这是在使用Gson解析字符串时的报错,一般是因为字符串非标准Json格式造成的

  • Unchecked assignment for ‘java.util.ArrayList’ to ‘java.util.ArrayList <…>: 可能是定义ArrayList的时候没有使用<>,可以用下面两种方法进行定义:

    ArrayList<MyList> myList = new ArrayList<>();
    ArrayList<MyList> myList = new ArrayList<MyList>();
  • **com.alibaba.fastjson.JSONException: default constructor not found. **: fastjson的坑,要求对应class必须有默认的构造函数(空参数)

  • fastjson出现$ref: $.data[2].indexs[0]: 又是fastjson的坑,如果是需要序列化的对象中有对同一个对象的依赖,那么在JSON序列化中可能会将后续的对象转成这种字符串

扩展阅读
Haofly · 豪翔天下 · 2016-06-27

评论 · Comments

评论由 Giscus 提供,需用 GitHub 账号登录;留言会同步到这个仓库的 Discussions 里。