2020 tplink提前批面经
- 应聘岗位:IT软件工程师
- 状态:一面过,二面没去
一面
- 自我介绍
- 介绍项目(Docker、DNS相关)
- 项目简介
- 介绍Docker
- Docker和虚拟机的区别,相比虚拟机的优点
- Docker和lxc的区别
- DNS解析流程
- Java
- String和StringBuffer的区别,什么时候用哪个
- Java如何避免内存溢出(没明白)-> 讲下gc
Collection
接口需要用户做迭代,称为外部迭代。而使用Stream
则代替用户做了迭代,称为内部迭代。List<String> names = menu.stream()
// 中间操作
.filter(d -> d.getCalories())
.map(Dist::getName)
.limit(3)
// 终端操作
.collect(toList());
filter
或sorted
等中间操作会返回另一个流,这让多个操作可以连接起来形成一个查询。除非流水线上触发一个终端操作,否则中间操作不会执行任何处理,因为中间操作一般都可以合并起来,在终端操作时一次性全部处理,这种技术称作循环合并。List
、Integer
,甚至是void
。filter
方法:接受一个谓词(返回boolean
的函数)作为参数,并返回一个包括所有符合谓词的元素的流。List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
distinct
方法:无参数,返回一个元素各异的流(根据流所生成元素的hashCode()
和equals()
方法实现)。List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
/* Output:
2
4
*/
limit(n)
方法:返回一个不超过给定长度n
的流。如果流是有序的,则最多返回钱n
个元素;如果流是无序的(例如Set
),则返回结果不会以任何顺序排列。skip(n)
方法:返回一个去掉前n
个元素的流。如果流中的元素不足n
个,则返回一个空流。与limit(n)
是互补操作。map
方法:接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素(与转换的区别是,该步骤是创建一个新的版本而不是修改)。List<String> dishName = menu.stream()
.map(Dish::getName)
.collect(toList());
flatMap
方法:传入的方法生成的流不会各自独立,而是会被合并,扁平化称为一个流。anyMatch
检查谓词是否至少匹配一个元素;allMatch
会检查谓词是否匹配所有元素,相对的noneMatch
则会确保流中没有任何元素与给定的谓词匹配。这三种方法都用到了短路。findAny
可以返回流中的任意元素,而findFirst
则返回有出现顺序的流中的第一个元素。使用时配合Optional<T>
使用。此外findAny
在使用并行流时限制较少,如果不关心返回哪个元素,那么应该使用findAny
。int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce
求一组整数的和,reduce
接受一个初始值和一个函数,它将使用函数反复结合每个元素,直到流被归约成一个值。reduce
可以让内部实现得以选择并行执行操作。但是,传递给reduce
的Lambda不能更改状态(如实例变量),而且操作必须满足结合律才可以按任意顺序执行。IntStream
、DoubleStream
和LongStream
分别将流中的元素特化为int
、double
和long
,从而避免的暗含的装箱成本。数值流包含进行常用归约的方法,如求和sum
,找到最大元素max
等。mapToInt
、mapToDouble
和mapToLong
,而使用boxed
方法可以将数值流转回对象流(各个基本类型对应的包装类型)。range
和rangeClosed
是可以用于IntStream
和LongStream
的静态方法,作用是生成范围内的数,区别是range
不包含结束值而rangeClosed
包含。Stream.of
可以通过显式值创建一个流,可以接受任意数量的参数。而Stream.empty
可以得到一个空流。Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
Stream<String> emptyStream = Stream.empty();
Arrays.stream
可以从数组创建一个流,接受一个数组作为参数。java.nio.file.Files
中的很多静态方法都会返回一个流,例如Files.lines
,它会返回一个由指定文件中的各行构成的字符串流。iterate
方法接受一个初始值,以及一个一次应用在每个产生的新值上的Lambda。iterate
操作基本上是顺序的,因为结果取决于前一次应用。该操作将生成一个无限流,没有结尾,因为值是按需计算的,可以永远计算下去。因此流和集合的一个关键的区别在于流是无界的。generate
方法也可以生成无限流,它接受一个Supplier<T>
类型的Lambda提供新的值。boolean
值的函数。// 顶层接口
public interface ApplePredicate {
boolean test(Apple apple);
}
// 各种具体实现,可以不断增加
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
// 使用部分,无需变动
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (p.test(apple)) result.add(apple);
}
return result;
}
// 仍然需要上面代码的顶层接口,但无需实际定义类
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple a) {
return "red".equals(a.getColor());
}
});
List<Apple> result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
public interface Predicate<T> {
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T e: list) {
if (p.test(e)) result.add(e);
}
return result;
}
//...
List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
Comparetor
、Runnable
、Callable
等。@FunctionalInterface
:用于标注接口为函数式接口,如果该接口没有定义抽象方法或者包含多个抽象方法,编译时会出现错误。其作用类似于@Override
。java.util.function.Predicate<T>
提供一个test()
方法,接受一个泛型T
对象,返回一个boolean
。java.util.function.Consumer<T>
提供一个accept()
方法,接受一个泛型T
对象,返回为void
。java.util.function.Function<T, R>
提供一个apply()
方法,接受一个泛型T
对象,返回一个泛型R
对象。Predicate<T>
的IntPredicate
,其参数为int
而不是Integer
可以避免装箱。try/catch
块包裹。下面代码为示例。@FunctionalInterface
public interface BufferredReaderProcessor {
String process(BufferedReader b) throws IOException;
}
//...
BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();
Function<BufferedReader, String> f = (BufferedReader b) -> {
try {
return b.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
// 没有类型推断
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 有类型推断
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
final
或者事实上是final
。关于事实上是final
的变量,见下面代码。不符合事实上是final
的变量出现时,编译器将会发出错误信息。int n = 1337;
Runnable r = () -> System.out.println(n);
// Error: java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
n = 31337;
(Apple a) -> a.getWeight();
// 等价于
Apple::getWeight
方法引用主要有三类:
Integer
的parseInt()
,写作Integer::parseInt
)String
对象的length()
,写作String::length
)x
,其拥有实例方法getValue()
,则可写x::getValue
)注意,第二种实例是Lambda本身的参数,而第三种实例来自外部。
ClassName::new
来对构造函数进行引用。注意选择正确的接口接收构造函数引用,见下面代码示例。class Apple {
private int w;
public Apple() {
w = 10;
}
public Apple(int w) {
this.w = w;
}
public static void main(String[] args) throws Exception {
Supplier<Apple> c = Apple::new;
Apple a1 = c.get();
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.get(20);
}
}
ClassName::new
会选择匹配函数式接口对应方法参数列表的构造函数,而无需指定参数列表。
or
连接两个Lambda)。这些方法的实现使用了接口的默认方法。以一个Comparator
为例:
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
reversed()
将比较器结果逆序:inventory.sort(comparing(Apple::getWeight).reversed());
thenComparing()
来进一步比较:inventory.sort(comparing(Apple::getWeight))
.reversed()
.thenComparing(Apple::getCountry));
negate
、and
和or
,其优先级是从左往右。// 从现有的Predicate创建结果的非
Predicate<Apple> notRedApple = redApple.negate();
// 使用and和or创建复杂的Predicate对象
Predicate<Apple> p = redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor()));
Function
接口将Lambda表达式复合起来。其中提供andThen()
和compose()
两个方法,它们都会返回一个Function
实例。andThen()
会先对输入应用调用该方法的Function
,后对输入应用该方法的Function
参数;compose()
则是先对输入应用该方法的Function
参数,后对输入应用调用该方法的Function
class Even {
private int i = 0;
public int next() {
++i; // [1]
++i;
return i;
}
}
next()
方法返回的一定是偶数。但是在并发的条件下,[1]处如果线程被挂起,被另外一个线程调用该对象的next()
方法时则会返回奇数。class Even {
private int i = 0;
public synchronized int next() {
++i; // [1]
++i;
return i;
}
}
synchronized
关键字进行同步控制,应将涉及竞争的数据成员都声明为private
,仅允许通过方法来访问这些成员。然后将相关的方法声明为synchronized
。synchronized
方法的时候,此对象都被加锁,此时该对象上的其他synchronized
方法只有等前一个方法调用完毕并释放了锁之后才能被调用。synchronized
方法,计数会递减;当计数为0时,锁被完全释放,此时其他任务可以使用此资源。Class
对象的一部分),所以synchronized static
方法可以在类的范围内防止对static
数据的并发访问。Lock
对象必须被显式地创建、锁定和释放。相比起synchronized
,代码缺乏优雅性,但是更加灵活;并且,如果使用synchronized
时某些事物失败的将会抛出异常,没有机会进行清理工作,而使用Lock
对象可以在finally
子句中进行清理工作。class Even {
private int i = 0;
private Lock lock = new ReentrantLock();
public int next() {
lock.lock();
try {
++i;
++i;
return i;
} finally {
lock.unlock();
}
}
}
当变量声明为
volatile
时,变量将具备以下两个特性:
保证此变量对所有线程的可见性,即一个线程修改了volatile变量后,其余线程可以立即获得修改后的值。
禁止指令重排序优化,即设置内存屏障,保证volatile变量修改更新到所有CPU上。
volatile
上的原子操作不必刷新到主存中去,因此其他读取该域的任务也不必看到这个新值。volatile
的,否则,这个域就应该只能经由同步来访问。同步也会导致向主存中刷新,因此如果一个域完全由synchronized
方法或语句块来防护,那就不必将其设置为是volatile
的。volatile
将无法工作。AtomicInteger
、AtomicLong
、AtomicReference
等特殊的原子性变量类,提供原子性条件更新操作compareAndSet(expectedValue, updateValue)
,这是由硬件(CPU指令)支持的。synchronized
指定某个对象,此对象的锁将用于对指定代码段进行同步控制。这段代码被称为临界区,或者同步控制块。synchronized
同步临界区时指定的同步对象为当前对象,即synchronized (this)
。当然也可以指定其它对象,但注意确保所有任务都是在同一个对象上同步的。run()
方法返回,但是任务的线程还可以被中断。sleep()
使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行。wait()
使线程挂起,直到线程得到了notify()
或者notifyAll()
消息(或者在Java中等价的signal()
或signalAll()
消息),线程才会进入就绪状态。Thread
类包含interrupt()
方法,可以终止被阻塞任务,这个方法将设置线程的中断状态。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将跑出InterruptedException
。当抛出该异常或者该任务调用Thread#interrupted()
时,中断状态将被复位。Executor
上调用shutdownNow()
,那么它将发送一个interrrupt()
调用给它启动的所有线程。如果希望只中断某个任务,那么需要通过调用submit()
而不是executor()
启动任务,这样可以持有线程的上下文。submit()
将放回一个泛型Future<?>
,可以在其上调用cancel()
,由此中断某个任务。如果将true
传递给cancel()
,那么它就会有在该线程上调用interrupt()
以停止这个线程的权限。interrupt()
不能中断正在试图获取synchronized
锁或者试图执行I/O操作的线程,因为操作系统并未提供该功能。Thread.interrupted()
来检查中断状态,不仅仅可以得知interrupt()
是否被调用过,还可以清除中断状态。class LiftOff implements Runnable {
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return String.format("#%d(%s)", id, countDown > 0? countDown:"LiftOff");
}
@Override
public void run() {
while (countDown-- > 0) {
System.out.println(status());
Thread.yield();
}
}
}
Runnable
接口并实现run()
方法,即可执行自定义任务。Thread.yield()
是对线程调度器的一种建议,告知其可以让出CPU转移给其它线程。public class Main {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 5; i++) {
new Thread(new LiftOff()).start();
}
}
}
Thread
类,或者向Thread
构造器中传入Runnable
对象,来构建由不同线程执行任务的对象。Executor
能够管理异步任务的执行,无须显式地管理线程的生命周期。public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}
ExecutorService
对象使用静态方法Executors.newCachedThreadPool()
创建,ExecutorService#execute()
为每一个任务都创建了一个线程。ExecutorService#shutdown()
方法可以防止新任务提交给该Executor
,而当前线程将继续运行在shutdown()
被调用之前提交的所有任务,待所有任务均执行完毕后,当前线程将会尽快退出。ChchedThreadPool
在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程。public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}
FixedThreadPool
可以一次性预先执行代价高昂的线程分配,因而也可以限制线程的数量。由于不需要为每个任务都固定付出创建线程的开销,因此可以节省时间。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程以尽快得到服务。在这个过程中,由于线程总数是有限的,因此不会出现资源滥用。SingleThreadExecutor
类似于线程数量为1的FixedThreadPool
,可以用于在另一个线程中连续运行任何事物(长期服务),例如监听进入的套接字连接的任务。如果向SingleThreadExecutor
提交了多个任务,那么这些任务将会排队执行,所有的任务将使用相同的线程。这能够保证在任意时刻在任意线程中都只有唯一的任务在运行,不需要在共享资源上处理同步并有限制的使用这些资源。Callable
接口。Callable
是一种具有类型参数的泛型,它的类型参数表示的是从方法call()
中返回值的类型。向Executor
提交任务时使用ExecutorService#submit()
。class Test implements Callable<String> {
private int id;
public Test(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "result: " + id;
}
}
public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<String>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(exec.submit(new Test(i)));
}
for (Future<String> r: result) {
try {
System.out.println(r.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
exec.shutdown();
}
}
exec.shutdown();
}
}
ExecutorService#submit()
方法会产生Future
对象,它用Callable
返回的结果的特定类型进行参数化。可以调用Future#isDone()
来查询Future
是否已经完成,如果完成可以调用Future#get()
获取对应结果。也可以直接调用Future#get()
,此时会发生阻塞,直到结果准备就绪。TimeUnit.MILLSECONDS.sleep()
来中止任务执行给定的时间,同时会抛出InterruptedException
异常。由于异常不能跨线程传播回主线程,因此必须在本地处理所有在任务内部产生的异常。class Test implements Runnable {
private int countDown = 5;
private volatile double d;
private int priority;
public Test(int priority) {
this.priority = priority;
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
while(true) {
for (int i = 1; i < 100000; i++) {
d += (Math.PI + Math.E) / (double)i;
if (i % 1000 == 0) Thread.yield();
}
System.out.println(this);
if (--countDown == 0) return;
}
}
}
public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new Test(Thread.MAX_PRIORITY));
exec.execute(new Test(Thread.MIN_PRIORITY));
}
}
}
getPriority()
来读取现有线程的优先级,并且可以在任何时候通过调用setPriority()
来修改优先级。MAX_PRIORITY
、NORM_PRIORITY
和MIN_PRIORITY
。Thread#setDaemon()
来设置线程为后台线程,但必须在线程启动之前设置。class DaemonThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
class Daemon implements Runnable {
@Override
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
for (int i = 0; i < 10; i++) {
exec.execute(new Daemon());
}
System.out.println("all start.");
TimeUnit.MILLISECONDS.sleep(500);
}
}
ExecutorService
创建方法可以被重载为接收一个ThreadFactory
对象,而这个工厂对象将用于创建新的线程。在上面代码中,自定义的工厂将新的线程均设置成为后台线程。Thread#isDaemon()
方法来确定线程是否为一个后台线程。后台线程创建的线程都将自动设置成为后台线程。class Daemon implements Runnable {
@Override
public void run() {
try {
System.out.println("starting daemon");
TimeUnit.SECONDS.sleep(1); // [1]
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("run?");
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Daemon());
t.setDaemon(true);
t.start();
}
}
finally
子句)。这不是关闭后台进程优雅的方式,相应的,非后台的Executor
控制的所有任务可以同时被关闭,这是一种更好的解决方式。join()
方法,此时调用join()
的线程将被挂起,直到目标线程结束才回复(即t.isAlive
为假)。也可以在调用join()
时带上一个超时参数,如果发生超时目标线程还没有结束,join()
方法也会返回。class ThrowException implements Runnable {
@Override
public void run() {
throw new RuntimeException();
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
try {
exec.execute(new ThrowException());
} catch (Exception e) {
e.printStackTrace();
}
}
}
try
语句无法捕获线程中的异常。class ThrowException implements Runnable {
@Override
public void run() {
throw new RuntimeException();
}
}
class CatchException implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught: " + e);
}
}
class ExceptionThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new CatchException());
return t;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool(new ExceptionThreadFactory());
exec.execute(new ThrowException());
}
}
/* Output:
caught: java.lang.RuntimeException
*/
Thread.UncaughtException
接口可以为每个Thread
对象都附着一个异常处理器。其中uncaughtException()
方法会在线程因未捕获异常而退出前被调用。上面代码中创建了一个ThreadFactory
子类来为线程添加异常处理器。@Override
,表示当前的方法定义将覆盖超类中的方法。如果注解的方法没有对应到超类中的方法,编译器会发出错误提示。@Deprecated
,如果在其它代码中使用了注解为@Deprecated
的元素,那么编译器会发出警告信息。@SuppressWarnings
,关闭不当的编译器警告信息。@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@
以外,@Test
的定义很像一个空的接口。元注解 | 说明 |
---|---|
@Target |
表示该注解可以用于什么地方,可能的参数包括:CONSTRUCTOR (构造器声明),FIELD (域声明),LOCAL_VARIABLE (局部变量声明),METHOD (方法声明),PACKAGE (包声明),PARAMETER (参数声明),TYPE (类、接口、注解、枚举) |
@Retention |
表示需要再什么级别保存该注解信息。可选的参数包括:SOURCE (注解将被编译器丢弃),CLASS (注解在class文件中可用,但会被VM丢弃),RUNTIME (VM在运行期也会保留注解,可以通过反射读取注解信息) |
@Documented |
将此注解包含在Javadoc中 |
@Inherited |
允许子类继承父类中的注解 |
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface UseCase {
public int id();
public String desc() default "No description";
}
class PasswordUtils {
@UseCase(id = 47, desc = "Need one or more numeric")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UseCase(id = 48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
@UseCase(id = 49, desc = "Same with the previous")
public boolean chekcForNewPassword(List<String> prevPasswords, String password) {
return !prevPasswords.contains(password);
}
}
public class Main {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for (Method m: cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);
if (uc != null) {
System.out.println(uc.id() + " " + uc.desc());
useCases.remove((Integer)uc.id());
}
}
for (int i: useCases) {
System.out.println(i + "Not found");
}
}
public static void main(String[] args) throws Exception {
List<Integer> useCases = new ArrayList<>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
getAnnoation()
方法返回指定类型的注解对象。如果被注解方法上没有该类型的注解,则返回null
值。如果注解中没有指定注解字段值,则返回定义注解字段时的默认值。注解元素可用的类型:
Class
enum
String
Annotation
如果使用了其它类型则会报错。另外,注解可以作为元素的类型,这意味着注解可以嵌套。
null
作为其值。public class Main {
static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
static void full(int[] arr, int start) {
if (start == arr.length - 1) System.out.println(Arrays.toString(arr));
else {
// 将当前最左边和后面的值依次交换
for (int i = start; i < arr.length; i++) {
swap(arr, start, i);
// 递归解决子数组全排列
full(arr, start + 1);
// 复原数组
swap(arr, start, i);
}
}
}
public static void main(String[] args) throws Exception {
full(new int[]{1,2,3,4}, 0);
}
}
public class Main {
static void dict(int[] arr) {
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
while (true) {
// 从最右起,找出为升序的相邻两个数,记录小的位置i
int i;
for (i = arr.length - 2; i >= 0; i--) {
if (arr[i] < arr[i + 1]) break;
}
// 如果没有则表示完成字典排序
if (i == -1) break;
// 从最右起,找出比arr[i]大的第一个数,记录位置j
int j;
for (j = arr.length - 1; j > 0; j--) {
if (arr[j] > arr[i]) break;
}
// 交换i和j
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
// 对i + 1到最后进行排序
Arrays.sort(arr, i + 1, arr.length);
System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) throws Exception {
dict(new int[]{1, 1, 2, 4});
}
}
enum
的标志,可以用来表示某种“开/关”信息。其内部实现是将一个long
值作为位向量,因此EnumSet
非常高效。enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
}
public class Test {
public static void main(String[] args) throws Exception {
EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
points.add(AlarmPoints.BATHROOM);
System.out.println(points);
points.addAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.KITCHEN));
System.out.println(points);
points = EnumSet.allOf(AlarmPoints.class);
points.removeAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.KITCHEN));
System.out.println(points);
points.removeAll(EnumSet.range(AlarmPoints.OFFICE1, AlarmPoints.OFFICE4));
System.out.println(points);
points = EnumSet.complementOf(points);
System.out.println(points);
}
}
EnumSet.of()
方法被重载了很多次,不仅为可变数量参数进行类重载,还为接收1到5个显式的参数的情况进行类重载。即当使用2到5个参数时,会调用对应的重载方法,而使用1个或者多于5个参数时,会调用可变参数的of()
方法。注意,1个参数不会导致编译器构造可变参数数组。在JDK 11中,EnumSet.of()
不同的重载方法有:1至5个显式声明参数的方法,和一个参数列表为(E first, E... rest)
的方法。
在创建EnumSet
对象时,如果枚举实例小于等于64个,则使用一个long
值,否则使用一个long
数组。
EnumMap
要求其中的键必须来自一个enum
。由于枚举实例总数量是确定的,EnumMap
内部使用了数组实现。调用put()
方法时键只能是枚举实例,其它操作和普通的Map
基本一致。enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
}
interface Command {
void action();
}
public class Test {
public static void main(String[] args) throws Exception {
EnumMap<AlarmPoints, Command> em = new EnumMap<>(AlarmPoints.class);
em.put(AlarmPoints.KITCHEN, new Command() {
public void action() {
System.out.println("Kichen fire!");
}
});
em.put(AlarmPoints.BATHROOM, new Command() {
public void action() {
System.out.println("Bathroom alart!");
}
});
for(Map.Entry<AlarmPoints, Command> e: em.entrySet()) {
System.out.print(e.getKey() + ": ");
e.getValue().action();
}
try {
em.get(AlarmPoints.UTILITY).action();
} catch (Exception e) {
System.out.println(e);
}
}
}
/* Output:
BATHROOM: Bathroom alart!
KITCHEN: Kichen fire!
java.lang.NullPointerException
*/
EnumMap
没有为某个键调用put()
方法来存入相应的值的话,那么其对应的值就为null
。foreach
中元素的泛型声明不可省略enum
实例编写方法,从而为每个枚举实例赋予各自不同的行为。首先需要为enum
定义一个或多个方法(或者抽象方法),然后为选择枚举实例进行覆盖方法(如果是抽象方法则每个枚举实例都要实现该方法)。public enum Test {
DATE_TIME {
String getInfo() {
return DateFormat.getDateInstance().format(new Date());
}
},
CLASSPATH {
String getInfo() {
return System.getenv("CLASSPATH");
}
},
VERSION {
String getInfo() {
return System.getProperty("java.version");
}
},
TEST {
String getInfo() {
return "test info";
}
int getNumber() {
return 222222;
}
};
abstract String getInfo();
int getNumber() {
return 111111;
}
public static void main(String[] args) throws Exception {
for (Test t: values()) {
System.out.println(t.getInfo() + " " + t.getNumber());
}
}
}
/* Output:
2019年7月21日 111111
.;C:\Program Files\Java\jdk-11.0.3\lib\dt.jar;C:\Program Files\Java\jdk-11.0.3\lib\tools.jar; 111111
11.0.3 111111
test info 222222
*/
enum Handle {
PLUS {
boolean modify(int src, int target) {
switch(target - src) {
case 1:
System.out.println("plus");
return true;
default: return false;
}
}
},
MINUS {
boolean modify(int src, int target) {
switch(target - src) {
case -1:
System.out.println("minus");
return true;
default: return false;
}
}
},
ABS {
boolean modify(int src, int target) {
switch(target + src) {
case 0:
System.out.println("abs");
return true;
default: return false;
}
}
};
abstract boolean modify(int src, int target);
}
public class Test {
static void modify(int src, int target) {
System.out.print(src + " ");
for(Handle h: Handle.values()) {
if(h.modify(src, target)) return;
}
System.out.println("can't modify");
}
public static void main(String[] args) throws Exception {
Random r = new Random(47);
int target = r.nextInt(10) - 5;
System.out.println(target);
for (int i = 0; i < 10; i++) {
int src = r.nextInt(10) - 5;
modify(src, target);
}
}
}
/* Output:
3
0 can't modify
-2 can't modify
-4 can't modify
-4 can't modify
4 minus
3 can't modify
-5 can't modify
-3 abs
2 plus
3 can't modify
*/
enum
对其实例有严格的限制,非常适合用来表现不同的状态和输入。enum Check {
EVEN {
void next(char n) {
switch(n) {
case '0':
c = ODD;
return;
case '1':
return;
default:
c = ERROR;
}
}
},
ODD {
void next(char n) {
switch(n) {
case '0':
c = EVEN;
return;
case '1':
return;
default:
c = ERROR;
}
}
},
ERROR;
private static Check c;
void next(char n) { throw new RuntimeException("no such method."); };
public static void check(String num) {
c = EVEN;
char[] ns = num.toCharArray();
for (char n: ns) {
c.next(n);
if(c == ERROR) break;
}
switch(c) {
case ODD:
System.out.println("odd 0s");
return;
case EVEN:
System.out.println("even 0s");
return;
case ERROR:
System.out.println("incorrect input");
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
Check.check("01001100001");
Check.check("000011110000");
Check.check("00110020");
}
}
/* Output:
odd 0s
even 0s
incorrect input
*/
EVEN
、ODD
、ERROR
,前两种状态根据输入的不同决定将要转移的下一个状态,而ERROR
则表示输入有误,仅作为最终状态。enum Outcome { WIN, DRAW, LOSE}
interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.DRAW; }
public Outcome eval(Scissors s) { return Outcome.WIN; }
public Outcome eval(Rock r) { return Outcome.LOSE; }
public String toString() { return "Paper"; }
}
class Scissors implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.LOSE; }
public Outcome eval(Scissors s) { return Outcome.DRAW; }
public Outcome eval(Rock r) { return Outcome.WIN; }
public String toString() { return "Scissors"; }
}
class Rock implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.WIN; }
public Outcome eval(Scissors s) { return Outcome.LOSE; }
public Outcome eval(Rock r) { return Outcome.DRAW; }
public String toString() { return "Rock"; }
}
public class Test {
static final int SIZE = 10;
private static Random r = new Random(47);
public static Item newItem() {
switch(r.nextInt(3)) {
default:
case 0: return new Scissors();
case 1: return new Rock();
case 2: return new Paper();
}
}
public static void match(Item a, Item b) {
System.out.println(a + " vs. " + b + ": " + a.compete(b));
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < SIZE; i++) {
match(newItem(), newItem());
}
}
}
enum Outcome { WIN, DRAW, LOSE}
enum RoShamBo2 {
PAPER(Outcome.DRAW, Outcome.LOSE, Outcome.WIN),
SCISSORS(Outcome.WIN, Outcome.DRAW, Outcome.LOSE),
ROCK(Outcome.LOSE, Outcome.WIN, Outcome.DRAW);
private Outcome vPaper, vScissors, vRock;
RoShamBo2(Outcome paper, Outcome scissors, Outcome rock) {
this.vPaper = paper;
this.vScissorc = scissors;
this.vRock = rock;
}
public Outcome compete(RoShamBo2 it) {
switch(it) {
default:
case Outcome.PAPER: return vPaper;
case Outcome.SCISSORS: return vScissors;
case Outcome.ROCK: return vRock;
}
}
}
// 调用略
enum
分发。enum Outcome { WIN, DRAW, LOSE}
enum RoShamBo3 {
PAPER {
public Outcome compete(RoShamBo3 it) {
switch(it) {
default:
case PAPER: return DRAW;
case SCISSORS: return LOSE;
case ROCK: return WIN;
}
}
};
// SCISSORS, ROCK略
}
enum Outcome { WIN, DRAW, LOSE}
enum RoShamBo5 {
PAPER, SCISSORS, ROCK;
static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>> table = new EnumMap<>(RoShamBo5.class);
static {
for (RoShamBo5 it: RoShamBo5.values()) {
table.put(it, new EnumMap<>(RoShamBo5.class));
}
initRow(PAPER, Outcome.DRAW, Outcome.LOSE, Outcome.WIN);
initRow(SCISSORS, Outcome.WIN, Outcome.DRAW, Outcome.LOSE);
initRow(ROCK, Outcome.LOSE, Outcome.WIN, Outcome.DRAW);
}
static void initRow(RoShamBo5 it, Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
EnumMap<RoShamBo5, Outcome> row = RoShamBo5.table.get(it);
row.put(RoShamBo5.PAPER, vPAPER);
row.put(RoShamBo5.SCISSORS, vSCISSORS);
row.put(RoShamBo5.ROCK, vROCK);
}
public Outcome compete(RoShamBo5 it) {
return table.get(this).get(it);
}
}
EnumMap
实现类似于表格的方法,从而达到了分发的目的。当然也可以使用二维数组来实现。enum
的values()
方法,可以遍历enum
实例,返回一个enum
实例的数组,并且数组中实例的顺序和声明时的顺序保持一致。enum
时,编译器会为你生成一个相关的类,这个类继承自java.lang.Enum
。ordinal()
方法返回一个int
值,表示每个enum
实例在声明中的次序,从0
开始计算。==
来比较enum
实例,编译器会自动提供equals()
和hashCode()
方法。Enum
实现了Comparable
接口,拥有compareTo()
方法。name()
方法返回enum
实例声明时的名字,和toString()
方法相同。valueOf()
根据参数给出的实例名称返回相应的enum
实例,如果不存在该名称的实例将会抛出异常。
enum
是java中的语法糖,实际反编译后是继承自Enum
类。
enum
除了不能被继承,和普通的类基本相同。public enum Main {
WEST("west"),
NORTH("north"),
EAST("east"),
SOUTH("south");
private String desc;
Main(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public static void main(String[] args) {
for (Main m: Main.values()) {
System.out.println(m + " " + m.getDesc());
}
}
}
/* Output:
WEST west
NORTH north
EAST east
SOUTH south
*/
enum
添加方法,必须先定义enum
实例,并且在实例序列最后添加一个分号。否则在编译时会得到错误消息。enum
添加构造方法,注意声明enum
实例时就是在使用enum
的构造方法。此外,enum
的构造方法无所谓是否private
,因为就算不声明private
其构造方法也只能在enum
内部使用。toString()
方法,以提供定制化效果。
enum
的构造方法不能被反射调用,当反射调用构造方法时会检查对应类是否为枚举类。
case
语句中不需要enum
类来修饰enum
实例。// 反编译enum代码
final class Apple extends java.lang.Enum<Apple> {
public static final Apple A;
public static final Apple B;
public static final Apple C;
public static Apple[] values();
public static Apple valueOf(java.lang.String);
static {};
}
values()
方法和valueOf(String)
方法是编译器向enum
中加入的静态方法。enum
标记为final
类,因此无法继承enum
。public class Enums {
private static Random rand = new Random(42);
public static <T extends Enum<T>> T random(Class<T> ec) {
return random(ec.getEnumConstants());
}
public static <T> T random(T[] values) {
return values[rand.nextInt(values.length)];
}
}
<T extends Enum<T>>
限制泛型必须为enum
,使用Class<T>
可以获取枚举的实例数组。interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI, LENTILS
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO
}
}
public class Main {
public static void main(String[] args) throws Exception {
Food f = Food.Appetizer.SALAD;
f = Food.Coffee.BLACK_COFFEE;
f = Food.Dessert.BLACK_FOREST_CAKE;
}
}
在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。