Java基础回顾系列-第五天-高级编程之API类库

Java基础类库

String类是字符串的首选类型,其最大的特点是内容不允许修改;
StringBuffer与StringBuilder类的内容允许修改;
StringBuffer是JDK1.0的时候提供的,属于线程安全的操作;StringBuilder是在JDK1.5提供的,属于线程不安全操作;

StringBuffer

线程安全,可变的字符序列。持有synchronized关键字。效率比StringBuilder低。

StringBuilder

线程不安全,可变的字符序列。

String

public final class String implements java.io.Serializable, Comparable<String>, CharSequence

  • String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用以提升执行效率。而从Java SE5/6开始,就渐渐摈弃这种方式了。因此在现在的Java SE版本中,不需要考虑用final去提升方法调用效率。只有在确定不想让该方法被覆盖时,才将方法设置为final。
  • String类中所有的成员属性,从上面可以看出String类其实是通过char数组来保存字符串的。
  • 从上面的三个方法可以看出,无论是sub操、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

在这里要永远记住一点:
“对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

package javase.util;

public class JavaAPIDemo {
	public static void main(String[] args) {
		String str1 = "hello world";
		String str2 = new String("hello world");
		String str3 = "hello world";
		String str4 = new String("hello world");

		System.out.println(str1==str2); // false
		System.out.println(str1==str3); // true
		System.out.println(str2==str4); // false

		
		String str5 = str1 + str2; // String中的“+”等同于StringBuilder中的append
	}
}

在这里插入图片描述
在这里插入图片描述  在这里插入图片描述

CharSequence接口

public interface CharSequence
Since:1.4
已有的子类:CharBuffer, Segment, String, StringBuffer, StringBuilder

方法说明
char charAt​(int index)获取指定索引字符
int length()获取字符串的长度
CharSequence subSequence​(int start, int end)截取部分字符串
package javase.util;

public class JavaAPIDemo {
	public static void main(String[] args) {
		// 子类实例向上转型
		CharSequence string = "String字符串";
		CharSequence stringBuffer = new StringBuffer();
		CharSequence stringBuilder = new StringBuilder();

		// 获取指定索引字符
		char c = string.charAt(0);
		System.out.println("c = " + c); // S

		// 获取字符串的长度
		int length = string.length();
		System.out.println("length = " + length); // 9
		
		// 字符串截取
		CharSequence charSequence = string.subSequence(0, 6);
		System.out.println("charSequence = " + charSequence); // String
		
	}
}

AutoCloseable接口

AutoCloseable接口位于java.lang包下,从JDK1.7开始引入。 在关闭之前可以保存资源(例如文件或套接字句柄)的对象。 主要用于日后进行资源开发的处理上,以实现资源的自动关闭(释放资源),例如:在以后进行文件、网络、数据库开发的过程中由于服务器的资源有限。

在1.7之前,我们通过try{} finally{} 在finally中释放资源。
在finally中关闭资源存在以下问题:
1、自己要手动写代码做关闭的逻辑;
2、有时候还会忘记关闭一些资源;
3、关闭代码的逻辑比较冗长,不应该是正常的业务逻辑需要关注的;

对于实现AutoCloseable接口的类的实例,将其放到try后面(我们称之为:带资源的try语句),在try结束的时候,会自动将这些资源关闭(调用close方法)。
带资源的try语句的3个关键点:
1、由带资源的try语句管理的资源必须是实现了AutoCloseable接口的类的对象。
2、在try代码中声明的资源被隐式声明为fianl。
3、通过使用分号分隔每个声明可以管理多个资源。

示例:

package javase.util;

/**
 * 自动关闭实例
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		// 2、由带资源的try语句管理的资源必须是实现了AutoCloseable接口的类的对象。
		try (ConnectionLock connectionLock = new ConnectionInnerLock()){
			connectionLock.work();
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
}
// 1、接口继承/类实现AutoCloseable接口
interface ConnectionLock extends AutoCloseable {

	/**
	 * 任务事项
	 */
	void work();

	/**
	 * 释放资源
	 */
	void unlock();
}

class ConnectionInnerLock implements ConnectionLock {
	@Override
	public void work() {
		System.out.println("连接成功...");
		for (int i = 0; i < 10; i++) {
			System.out.println("i = " + i);
		}
		System.out.println("任务结束...");
	}

	/**
	 * 释放资源
	 */
	@Override
	public void unlock() {
		System.out.println("释放资源");
	}

	@Override
	public void close() throws Exception {
		this.unlock();
	}
}

Runtime

Runtime 类代表着Java程序的运行时环境,每个Java程序都有一个Runtime实例,该类会被自动创建,我们可以通过Runtime.getRuntime() 方法来获取当前程序的Runtime实例。

获取最大可用内存空间:public long maxMemory();默认的配置为本机系统内存的四分之一
获取可用内存空间:public long totalMemory();默认的配置为本机系统六十四分之一
获取空闲内存空间:public long freeMemory()
手工进行GC处理:public void gc();

package javase.util;

public class JavaAPIDemo {
	public static void main(String[] args) {
		// 获取实例对象
		Runtime runtime = Runtime.getRuntime();

		//获取可用内存
		long value = Runtime.getRuntime().freeMemory();
		System.out.println("可用内存为:"+value/1024/1024+"mb");
		//获取jvm的总数量,该值会不断的变化
		long  totalMemory = Runtime.getRuntime().totalMemory();
		System.out.println("全部内存为:"+totalMemory/1024/1024+"mb");
		//获取jvm 可以最大使用的内存数量,如果没有被限制 返回 Long.MAX_VALUE;
		long maxMemory = Runtime.getRuntime().maxMemory();
		System.out.println("可用最大内存为:"+maxMemory/1024/1024+"mb");

		// 获取jvm可用的处理器核心的数量。获取本机的CPU内核数,并发访问量
		int availableProcessors = runtime.availableProcessors();
		System.out.println("可用的处理器核心的数量为: " + availableProcessors);

		// 运行垃圾收集器。
		runtime.gc();


		// 执行系统命令:比如打开一个程序等
//		runtime.exec()


		// 以Java格式返回Java Runtime Environment的版本Runtime.Version。
		Runtime.Version version = Runtime.version();
		System.out.println("version = " + version); // 11.0.2+9-LTS
	}
}

System

参考博文:Java中的System类

Cleaner

JDK9之后用cleaner机制代替了finalize机制,提供了内存清理的另一方法。 我们都知道finalize机制饱受诟病,因为它回收对象前要先执行Object.finalize()中的逻辑,降低了内存回收的效率,而且它不能保证被及时执行,这点很致命,导致对象不能及时被回收,例如如果利用finalize关闭打开的文件时,因为系统的文件描述符是有限的,如果不能及时的关闭文件,会导致无法打开更多的文件。

垃圾回收是java的最大特点之一,可以大大的解决了c++中依靠程序员去释放内存的压力,但是java提供的用来做为GC前的最后一道防线的finalize方法却并不友好

finalize的缺点

首先finalize的执行时间并不确定,其次finalize会阻碍GC的快速回收,在jvm进行GC时会启动一个finalizethread,当遇到有重写了finalize方法的对象时,会将对象放入finalizethread的中,并形成一个队列,暂时挂起,且运行时间并不确定,这就导致了对象回收的缓慢,如果队列中存在重写的finalize方法有死锁问题则会导致后面的方法都无法执行,这就是我们不提倡重写finalize的原因。

Java9垃圾回收机制

在java9中已经将finalize方法废弃。占有非堆资源的对象实例,类应该提供一个方法以明确释放这些资源,如果合适的话他们也应该实现AutoCloseable接口。
java.lang.ref.Cleaner和java.lang.ref.PhantomReference提供更灵活和有效的方式,在对象无法再访问时释放资源。

传统回收示例:

package javase.util;

import java.lang.ref.Cleaner;

/**
 * 传统的垃圾回收
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		Obj obj = new Obj();
		obj = null;
		System.gc(); // 垃圾回收
	}
}
class Obj {
	public Obj(){
		System.out.println("对象产生了");
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println("对象回收了");
		new Exception("死之前说句话");
	}
}

package javase.util;

import java.lang.ref.Cleaner;

/**
 * Cleaner实现回收
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		while (true) {
			new CleaningExample();
		}
	}
}
class CleaningExample implements AutoCloseable {
	// A cleaner, preferably one shared within a library
	private static final Cleaner cleaner = Cleaner.create();

	static class State implements Runnable {

		State() {
			System.out.println("init");// initialize State needed for cleaning action
		}

		public void run() {
			System.out.println("clean");// cleanup action accessing State, executed at most once
		}
	}

	private final State state;
	private final Cleaner.Cleanable cleanable;

	public CleaningExample() {
		this.state = new State();
		this.cleanable = cleaner.register(this, state);
	}

	public void close() {
		cleanable.clean();
	}
}

对象克隆

Object类中:protected Object clone() throws CloneNotSupportedException
首先,如果此对象的类未实现接口Cloneable,则 CloneNotSupportedException抛出a。请注意,所有数组都被认为是实现接口,Cloneable并且clone数组类型方法的返回类型T[] 是T[]T是任何引用或基本类型。否则,此方法创建此对象的类的新实例,并使用该对象的相应字段的内容初始化其所有字段,就像通过赋值一样; 这些字段的内容本身不会被克隆。因此,该方法执行该对象的“浅拷贝”,而不是“深拷贝”操作。
该类Object本身并不实现接口 Cloneable,因此clone在类的对象上调用该方法Object将导致在运行时抛出异常。

类实现Cloneable接口以向该Object.clone()方法指示该方法合法地为该类的实例制作字段的字段副本。
在未实现Cloneable接口的实例上调用Object的clone方法会 导致 CloneNotSupportedException抛出异常。
按照惯例,实现此接口的类应Object.clone使用公共方法覆盖 (受保护)。

数字操作类

Math数学计算类

package javase.util;

public class JavaAPIDemo {
	public static void main(String[] args) {
		/**
		 *Math.sqrt()//计算平方根
		 *Math.cbrt()//计算立方根
		 *Math.pow(a, b)//计算a的b次方
		 *Math.max( , );//计算最大值
		 *Math.min( , );//计算最小值
		 */

		System.out.println(Math.sqrt(16));   //4.0
		System.out.println(Math.cbrt(8));    //2.0
		System.out.println(Math.pow(3,2));     //9.0
		System.out.println(Math.max(2.3,4.5));//4.5
		System.out.println(Math.min(2.3,4.5));//2.3

		/**
		 * abs求绝对值
		 */
		System.out.println(Math.abs(-10.4));    //10.4
		System.out.println(Math.abs(10.1));     //10.1

		/**
		 * ceil天花板的意思,就是返回大的值
		 */
		System.out.println(Math.ceil(-10.1));   //-10.0
		System.out.println(Math.ceil(10.7));    //11.0
		System.out.println(Math.ceil(-0.7));    //-0.0
		System.out.println(Math.ceil(0.0));     //0.0
		System.out.println(Math.ceil(-0.0));    //-0.0
		System.out.println(Math.ceil(-1.7));    //-1.0

		/**
		 * floor地板的意思,就是返回小的值
		 */
		System.out.println(Math.floor(-10.1));  //-11.0
		System.out.println(Math.floor(10.7));   //10.0
		System.out.println(Math.floor(-0.7));   //-1.0
		System.out.println(Math.floor(0.0));    //0.0
		System.out.println(Math.floor(-0.0));   //-0.0

		/**
		 * random 取得一个大于或者等于0.0小于不等于1.0的随机数
		 */
		System.out.println(Math.random());  //小于1大于0的double类型的数
		System.out.println(Math.random()*2);//大于0小于1的double类型的数
		System.out.println(Math.random()*2+1);//大于1小于2的double类型的数

		/**
		 * rint 四舍五入,返回double值
		 * 注意.5的时候会取偶数    异常的尴尬=。=
		 */
		System.out.println(Math.rint(10.1));    //10.0
		System.out.println(Math.rint(10.7));    //11.0
		System.out.println(Math.rint(11.5));    //12.0
		System.out.println(Math.rint(10.5));    //10.0
		System.out.println(Math.rint(10.51));   //11.0
		System.out.println(Math.rint(-10.5));   //-10.0
		System.out.println(Math.rint(-11.5));   //-12.0
		System.out.println(Math.rint(-10.51));  //-11.0
		System.out.println(Math.rint(-10.6));   //-11.0
		System.out.println(Math.rint(-10.2));   //-10.0

		/**
		 * round 四舍五入,float时返回int值,double时返回long值
		 */
		System.out.println(Math.round(10.1));   //10
		System.out.println(Math.round(10.7));   //11
		System.out.println(Math.round(10.5));   //11
		System.out.println(Math.round(10.51));  //11
		System.out.println(Math.round(-10.5));  //-10
		System.out.println(Math.round(-10.51)); //-11
		System.out.println(Math.round(-10.6));  //-11
		System.out.println(Math.round(-10.2));  //-10
	}
}

Random随机数生成类

参考博文:
JAVA中Random分析 https://shift-alt-ctrl.iteye.com/blog/2432102
Java-Random类常用方法详解:https://blog.csdn.net/goodbye_youth/article/details/81110123

BigInteger/BigDecimal大数字操作类

参考博文:
java大数详解

日期操作类

可变性:像日期和时间这样的类应该是不可变的。
偏移性: Date中的年份是从1900开始的,而月份都从0开始。
格式化: 格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
总结: 对日期和时间的操作一直是Java程序员最痛苦的地方之一。一般地,我们会使用Joda-Time外部依赖Lib进行开发。

Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。

  • java.time – 包含值对象的基础包
  • java.time.chrono – 提供对不同的日历系统的访问
  • java.time.format – 格式化和解析时间和日期
  • java.time.temporal – 包括底层框架和扩展特性
  • java.time.zone – 包含时区支持的类

Date

public Date() {
this(System.currentTimeMillis());
}
public Date(long date) {
fastTime = date;
}

从Date的构造函数可以观察得出:

将long转为Date:public Date(long date)
将Date转为long:public long getTime()

SimpleDateFormat

线程不安全的类。以及解决方案
详解参考:https://mp.weixin.qq.com/s/YmmM1KdGX_g46Sn_vFQraA
方法:

  • Date转字符串: public final String format​(Date date)
  • 字符串转Date: public Date parse​(String source) throws ParseException

构造:

  • public SimpleDateFormat​(String pattern)

LocalDate(本地时间)

LocalTime(本地时间)

LocalDateTime(本地日期时间)

Java的Date,Calendar类型使用起来并不是很方便,而且Date类(据说)有着线程不安全等诸多弊端。同时若不进行封装,会在每次使用时特别麻烦。于是Java8推出了线程安全、简易、高可靠的时间包。并且数据库中也支持LocalDateTime类型,在数据存储时候使时间变得简单。Java8这次新推出的包括三个相关的时间类型:LocalDateTime年月日十分秒;LocalDate日期;LocalTime时间;三个包的方法都差不多。
在这里插入图片描述

参考博文:https://blog.csdn.net/kingboyworld/article/details/75808108

ZonedDateTime(时区)

Duration(持续时间)

DateTimeFormatter

方法:

  • public static DateTimeFormatter ofPattern​(String pattern):静态方法 , 返 回 一 个 指 定 字 符 串 格 式 的DateTimeFormatter
    -public String format​(TemporalAccessor temporal): 格式化一个日期、时间,返回字符串
  • public TemporalAccessor parse​(CharSequence text):将指定格式的字符序列解析为一个日期、时间

具体的格式可以看APIhttps://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/time/format/DateTimeFormatter.html

package javase.util;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;

public class JavaAPIDemo {
	public static void main(String[] args) {
		LocalDateTime now = LocalDateTime.now();
		// 格式化
		DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
		String format = now.format(dateTimeFormatter);
		System.out.println(" [yyyy-MM-dd HH:mm:ss],format = " + format);

		format = now.format(DateTimeFormatter.ISO_DATE);
		System.out.println(" [2019-04-26],format = " + format);
		format = now.format(DateTimeFormatter.BASIC_ISO_DATE);
		System.out.println(" [20190426],format = " + format);
		format = now.format(DateTimeFormatter.ISO_TIME);
		System.out.println(" [15:48:25.3219472],format = " + format);
		format = now.format(DateTimeFormatter.ISO_DATE_TIME);
		System.out.println(" [2019-04-26T15:45:11.9338055],format = " + format);

		// 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
		format = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));
		System.out.println(" [2019年4月26日],format = " + format);
	}
}

其它API

在这里插入图片描述

与传统日期处理的转换

在这里插入图片描述

正则表达式类

详细参考:https://mp.weixin.qq.com/s/uzmvuBy3_hTCanQ-MlBO0Q

Pattern正则表达式编译

匹配信息:https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/regex/Pattern.html
获取实例:public static Pattern compile​(String regex)

Matcher匹配

获取实例:Pattern类 public Matcher matcher​(CharSequence input)

示例

package javase.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestMain {
	public static void main(String[] args) {
		// 要求取出“#{内容}”标记中的所有内容
		String str = "INSERT INTO dept(deptno,dname,loc) VALUES (#{deptno},#{dname},#{loc})" ;
		String regex = "#\\{\\w+\\}" ;
		Pattern pat = Pattern.compile(regex) ; // 编译正则表达式
		Matcher mat = pat.matcher(str) ;
		while(mat.find()) {	// 是否有匹配成功的内容
			System.out.println(mat.group(0).replaceAll("#|\\{|\\}", ""));
		}
	}
}

国际化程序实现

Locale

构造方法:

  • public Locale​(String language)
  • public Locale​(String language, String country)

获取本地Local:public static Locale getDefault()
常量:Locale.CHINA

package javase.util;


import java.util.Locale;

public class TestMain {
	public static void main(String[] args) {
		// 构造函数
		Locale locale = new Locale("zh", "CN");
		System.out.println("locale = " + locale); // zh_CN

		// 静态方法
		Locale aDefault = Locale.getDefault();
		System.out.println("aDefault = " + aDefault); // zh_CN

		// 常量
		Locale china = Locale.CHINA;
		System.out.println("china = " + china); // zh_CN
		
	}
}

ResourceBundle获取资源文件

package javase.util;


import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;

public class TestMain {
	public static void main(String[] args) {
		// 构造函数
		Locale locale = new Locale("zh", "CN");
		System.out.println("locale = " + locale); // zh_CN

		// 静态方法
		Locale aDefault = Locale.getDefault();
		System.out.println("aDefault = " + aDefault); // zh_CN

		// 常量
		Locale china = Locale.CHINA;
		System.out.println("china = " + china);

		// 获取资源文件
		ResourceBundle resourceBundle = ResourceBundle.getBundle("资源文件名", aDefault);
		// 读取资源文件信息
		String key = resourceBundle.getString("资源文件中的key");

		// 消息格式化
		String format = MessageFormat.format(key, "参数1", "参数2");
		
	}
}

开发支持类库

Arrays

参考博文:Java-Arrays类常用方法详解 https://blog.csdn.net/goodbye_youth/article/details/81003817

二分法查找:public static int binarySearch​(byte[] a, byte key)
数组比较:public static int compare​(double[] a, double[] b)
数组相等判断:public static boolean equals​(char[] a, char[] a2)
数组填充:public static void fill​(byte[] a, byte val)
数组排序:public static void sort​(Object[] a)
数组转String:public static String toString​(char[] a)

需要依赖排序。

UUID

简介:
UUID 含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准。也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。
UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。在这样的情况下,就不需考虑数据库建立时的名称重复问题。

UUID 的目的是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。
如此一来,每个人都可以建立不与其它人冲突的 UUID。在这样的情况下,就不需考虑数据库建立时的名称重复问题。
目前最广泛应用的 UUID,即是微软的 Microsoft’s Globally Unique Identifiers (GUIDs),而其他重要的应用,
则有 Linux ext2/ext3 档案系统、LUKS 加密分割区、GNOME、KDE、Mac OS X 等等。

UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。
按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字

UUID由以下几部分的组合:
(1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
(2)时钟序列。
(3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。
在ColdFusion中可以用CreateUUID()函数很简单地生成UUID,其格式为:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),
其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。而标准的UUID格式为:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),可以从cflib 下载CreateGUID() UDF进行转换。

Optional 空指针异常处理

参考博文:使用Java8中Optional机制的正确姿势 https://www.jb51.net/article/127425.htm
理解、学习与使用 JAVA 中的 OPTIONAL https://www.cnblogs.com/zhangboyu/p/7580262.html

Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。

我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException:

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            if (isocode != null) {
                isocode = isocode.toUpperCase();
            }
        }
    }
}

你看到了,这很容易就变得冗长,难以维护。

为了简化这个过程,我们来看看用 Optional 类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional 奇迹的时刻。

创建 Optional 实例

  • public static <T> Optional<T> of​(T value):
  • public static <T> Optional<T> ofNullable​(T value)

访问 Optional 对象的值

  • public T get()

验证是否存在值

  • public boolean isPresent():是否存在值
  • public boolean isEmpty():是否没有值

返回默认值

  • public T orElse​(T other): 如果存在值,则返回该值,否则返回其他值。
  • public T orElseGet​(Supplier<? extends T> supplier):如果存在值,则返回该值,否则返回由供应函数生成的结果。

返回异常

  • public T orElseThrow():如果存在值,则返回该值,否则抛出NoSuchElementException。

转换值

  • public <U> Optional<U> map​(Function<? super T,​? extends U> mapper)

过滤值

  • public Optional<T> filter​(Predicate<? super T> predicate)

链式操作
待补充。

ThreadLocal

ThreadLocal是啥?以前面试别人时就喜欢问这个,有些伙伴喜欢把它和线程同步机制混为一谈,事实上ThreadLocal与线程同步无关。ThreadLocal虽然提供了一种解决多线程环境下成员变量的问题,但是它并不是解决多线程共享变量的问题。那么ThreadLocal到底是什么呢?

API介绍:

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。 ThreadLocal实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下。
public void set(Object value) 设置当前线程的线程局部变量的值;
public Object get() 该方法返回当前线程所对应的线程局部变量;
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。

除了这四个方法,ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。

单线程情况下:

package javase.util;

/**
 * 多线程的情况
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		new Thread(()->{
			Message msg = new Message() ;	// 实例化消息主体对象
			msg.setInfo("第一个线程的消息"); // 设置要发送的内容
			Channel.setMessage(msg); // 设置要发送的消息
			Channel.send(); // 发送消息
		},"消息发送者A") .start() ;
		new Thread(()->{
			Message msg = new Message() ;	// 实例化消息主体对象
			msg.setInfo("第二个线程的消息"); // 设置要发送的内容
			Channel.setMessage(msg); // 设置要发送的消息
			Channel.send(); // 发送消息
		},"消息发送者B") .start() ;
		new Thread(()->{
			Message msg = new Message() ;	// 实例化消息主体对象
			msg.setInfo("第三个线程的消息"); // 设置要发送的内容
			Channel.setMessage(msg); // 设置要发送的消息
			Channel.send(); // 发送消息
		},"消息发送者C") .start() ;
	}
}

class Channel {	// 消息的发送通道
	private static Message message ;
	private Channel() {}
	public static void setMessage(Message m) {
		message = m ;
	}
	public static void send() {	// 发送消息
		System.out.println("【消息发送】" + message.getInfo());
	}
}
class Message {	// 要发送的消息体
	private String info ;
	public void setInfo(String info) {
		this.info = info;
	}
	public String getInfo() {
		return info;
	}
}

结果:

【消息发送】第二个线程的消息
【消息发送】第二个线程的消息
【消息发送】第二个线程的消息

package javase.util;

/**
 * 多线程的情况
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		new Thread(()->{
			Message msg = new Message() ;	// 实例化消息主体对象
			msg.setInfo("第一个线程的消息"); // 设置要发送的内容
			Channel.setMessage(msg); // 设置要发送的消息
			Channel.send(); // 发送消息
		},"消息发送者A") .start() ;
		new Thread(()->{
			Message msg = new Message() ;	// 实例化消息主体对象
			msg.setInfo("第二个线程的消息"); // 设置要发送的内容
			Channel.setMessage(msg); // 设置要发送的消息
			Channel.send(); // 发送消息
		},"消息发送者B") .start() ;
		new Thread(()->{
			Message msg = new Message() ;	// 实例化消息主体对象
			msg.setInfo("第三个线程的消息"); // 设置要发送的内容
			Channel.setMessage(msg); // 设置要发送的消息
			Channel.send(); // 发送消息
		},"消息发送者C") .start() ;
	}
}

class Channel {	// 消息的发送通道
	private static final ThreadLocal<Message> THREADLOCAL = new ThreadLocal<Message>() ;
	private Channel() {}
	public static void setMessage(Message m) {
		THREADLOCAL.set(m);
	}
	public static void send() {	// 发送消息
		System.out.println("【消息发送】" + THREADLOCAL.get().getInfo());
	}
}
class Message {	// 要发送的消息体
	private String info ;
	public void setInfo(String info) {
		this.info = info;
	}
	public String getInfo() {
		return info;
	}
}

输出结果:

【消息发送】第二个线程的消息
【消息发送】第三个线程的消息
【消息发送】第一个线程的消息

定时调度

定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和多线程技术还是有非常大的关联的。在JDK中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务,但封装任务的类却是TimerTask类。

Timer
        //只执行一次
        public void schedule(TimerTask task, long delay);
        public void schedule(TimerTask task, Date time);
 
        //循环执行
        // 在循环执行类别中根据循环时间间隔又可以分为两类
        public void schedule(TimerTask task, long delay, long period) ;
        public void schedule(TimerTask task, Date firstTime, long period) ;
 
 
        public void scheduleAtFixedRate(TimerTask task, long delay, long period)
        public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
TimerTask

只执行一次:

Timer timer = new Timer();

//延迟1000ms执行程序
timer.schedule(new TimerTask() {
   @Override
   public void run() {
       System.out.println("IMP 当前时间" + this.scheduledExecutionTime());
   }
}, 1000);
//延迟10000ms执行程序
timer.schedule(new TimerTask() {
   @Override
   public void run() {
       System.out.println("IMP 当前时间" + this.scheduledExecutionTime());
   }
}, new Date(System.currentTimeMillis() + 10000));

循环执行:

Timer timer = new Timer();
        
//前一次执行程序结束后 2000ms 后开始执行下一次程序
timer.schedule(new TimerTask() {
   @Override
   public void run() {
       System.out.println("IMP 当前时间" + this.scheduledExecutionTime());
   }
}, 0,2000);

//前一次程序执行开始 后 2000ms后开始执行下一次程序
timer.scheduleAtFixedRate(new TimerTask() {
   @Override
   public void run() {
       System.out.println("IMP 当前时间" + this.scheduledExecutionTime());
   }
},0,2000);

Base64加解密

参考:http://www.runoob.com/java/java8-base64.html

比较器

Comparable(自然排序)

Comparable是排序接口。若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
此外,实现此接口的对象可以用作有序映射中的键或有序集合中的集合,无需指定比较器。
该接口定义如下:

public interface Comparable<T> {
	public int compareTo(T o);
}

T表示可以与此对象进行比较的那些对象的类型。
此接口只有一个方法compare,比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
现在我们假设一个Person类,代码如下:

class Person {
	String name;
	int age;
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}
}

现在有两个Person类的对象,我们如何来比较二者的大小呢?我们可以通过让Person实现Comparable接口:

package javase.util;

import java.util.Arrays;

/**
 *排序
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		Person[] people = new Person[]{new Person("xujian", 20), new Person("xiewei", 10)};
		System.out.println("排序前");
		for (Person person : people) {
			System.out.print(person.getName() + ":" + person.getAge());
		}
		Arrays.sort(people);
		System.out.println("\n排序后");
		for (Person person : people) {
			System.out.print(person.getName() + ":" + person.getAge());
		}
	}
}

/**
 * 根据年龄排序
 */
class Person implements Comparable<Person>{
	String name;
	int age;
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}

	@Override
	public int compareTo(Person o) {
		return this.age - o.getAge();
	}
}

Comparator(定制排序)

当元素的类型没有实现Comparable接口而又不方便修改代码,或者实现了Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。 Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。也就是说,我们可以通过实现Comparator来新建一个比较器,然后通过这个比较器对类进行排序。该接口定义如下:

@FunctionalInterface
public interface Comparator<T> {
	int compare(T o1, T o2);
	boolean equals(Object obj);
}

注意:

  1. 若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。
  2. int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。

现在假如上面的Person类没有实现Comparable接口,该如何比较大小呢?我们可以新建一个类,让其实现Comparator接口,从而构造一个“比较器"。

package javase.util;

import java.util.Arrays;
import java.util.Comparator;

/**
 *排序
 */
public class JavaAPIDemo {
	public static void main(String[] args) {
		Person[] people = new Person[]{new Person("xujian", 20), new Person("xiewei", 10)};
		System.out.println("排序前");
		for (Person person : people) {
			System.out.print(person.getName() + ":" + person.getAge());
		}
		// 使用比较器
		Arrays.sort(people, new Comparator<Person>() {
			@Override
			public int compare(Person o1, Person o2) {
				return o1.getAge() - o2.getAge();
			}
		});
		
		// Lambda 排序比较器
		Arrays.sort(people, (o1, o2)->{
			return o1.getAge() - o2.getAge();
		});

		System.out.println("\n排序后");
		for (Person person : people) {
			System.out.print(person.getName() + ":" + person.getAge());
		}
	}
}

/**
 * 根据年龄排序
 */
class Person{
	String name;
	int age;
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}
}

Comparable和Comparator区别比较

  • Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而
    Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。
  • Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
  • 两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。

总结一下,两种比较器Comparable和Comparator,后者相比前者有如下优点:
1、如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法。
2、实现Comparable接口的方式比实现Comparator接口的耦合性 要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修 改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。实际上实现Comparator 接口的方式后面会写到就是一种典型的策略模式。
当然,这不是鼓励用Comparator,意思是开发者还是要在具体场景下选择最合适的那种比较器而已。

System类

  • System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
  • 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
  • 成员变量
    • System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
  • 成员方法
    • native long currentTimeMillis()
      该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
    • void exit(int status)
      该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等
    • void gc()
      该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
    • String getProperty(String key):
      该方法的作用是获得系统中属性名为key的属性对应的值。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值