Java之所以被開發,是要達到以下五個目的:
- 應當使用物件導向程式設計方法學
- 應當允許同一程式在不同的電腦平台執行
- 應當包括內建的對電腦網路的支援
- 應當被設計成安全地執行遠端程式碼
- 應當易於使用,並借鑑以前那些物件導向語言(如C++)的長處。
Java 在各個版本的演進
1.0-1.4 中的 java.lang.Thread
5.0 中的 java.util.concurrent
6.0 中的 Phasers 等
7.0 中的 Fork/Join 框架
8.0 中的 Lambda、Stream
9.0 中的 Jigsaw 模組化
CHAPTER 1 Basic
Anonymous inner - > Lambda
傳統匿名語法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("inside runnable using an anonymous inner class");
}
}).start();
lambda 於建構子中
new Thread(() -> System.out.println("inside Thread constructor using lambda")).start();
lambda 賦與變數
Runnable r = () -> System.out.println("lambda expression implementing the run method");
lambda 實做 FilenameFilter
String[] names = directory.list((dir, name) -> name.endsWith(".java"));
System.out.println(Arrays.asList(names));
lambda 區塊
String[] names = directory.list((File dir, String name) -> {
return name.endsWith(".java");
});
使用 方法參考(method reference) 存取 print
// Consumer<Integer> functional interface
Consumer<Integer> printer = System.out::println;
Stream.of(3, 1, 4, 1, 5, 9).forEach(printer);
語句
object::instanceMethod
物件實例方法 ex: System.out::println
Class::staticMethod
類別靜態方法 ex: Math::max
Class::instanceMethod
類別實例方法 ex: String::length
@FunctionalInterface
註釋該介面目標為 lambda expression or method reference.
只能有一個抽象成員
Default Methods
在介面中提供實做
public interface Collection<E> extends Iterable<E> {
boolean isEmpty();
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
}
// usage
boolean removed = Arrays.asList(3, 1, 4, 1, 5, 9).removeIf(n -> n <= 0);
CHAPTER 2 The java.util.function Package
p.s. ReferencePipeline內的 downstream 為 Consumer 類
Consumer
將欲使用方法傳入 Consumer 內
// implement
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
// usage
Arrays.asList("this", "is", "a", "list", "of", "strings").forEach(System.out::println);
Suppliers
將產生的值載入 supplier 內 並透過 get 方法取得值
Supplier<T> sippiler = () -> T;
DoubleSupplier randomSupplier = Math::random;
// 此時才呼叫 Math.random()
System.out.println(randomSupplier.getDouble());
附加 Suppliers 介面
Interface | abstract method |
---|---|
Supplier | T get() |
IntSupplier | int getAsInt() |
DoubleSupplier | double getAsDouble() |
LongSupplier | long getAsLong() |
BooleanSupplier | boolean getAsBoolean() |
Predicates
透過 Predicates 判斷是否有符合 filter
// implement
public static boolean predicateTest(int value, Predicate<Integer> predicate) {
return predicate.test(value);
}
// usage
predicateTest(3, (x) -> x == 3);
// Stream implement
Stream<T> filter(Predicate<? super T> predicate);
abstract class IntPipeline<E_IN>
extends AbstractPipeline<E_IN, Integer, IntStream>
implements IntStream {
@Override
public final IntStream filter(IntPredicate predicate) {
Objects.requireNonNull(predicate);
return new StatelessOp<Integer>(this, StreamShape.INT_VALUE,
StreamOpFlag.NOT_SIZED) {
@Override
Sink<Integer> opWrapSink(int flags, Sink<Integer> sink) {
return new Sink.ChainedInt<Integer>(sink) {
@Override
public void begin(long size) {
downstream.begin(-1);
}
@Override
public void accept(int t) {
// 檢查是否通過測試
if (predicate.test(t))
downstream.accept(t);
}
};
}
};
}
}
// usage
Arrays.asList("this", "is", "a", "list", "of", "strings")
.stream()
.filter(s -> s.length() == 2)
.collect(Collectors.joining(", "));
Functions
呼叫特定方法,並回傳所需內容
// implement
static int modifyTheValue(int valueToBeOperated, Function<Integer, Integer> function) {
return function.apply(valueToBeOperated);
}
// usage
modifyTheValue(10, (x)-> x + 20);
// Stream implement
abstract class ReferencePipeline<P_IN, P_OUT>
extends AbstractPipeline<P_IN, P_OUT, Stream<P_OUT>>
implements Stream<P_OUT> {
@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
}
// usage
Arrays.asList("this", "is", "a", "list", "of", "strings")
.stream()
.map(String::length)
.collect(Collectors.toList());
附加 Functions 介面
Interface | abstract method |
---|---|
Function | R apply(t value) |
IntFunction | R apply(int value) |
DoubleFunction | R apply(double value) |
LongFunction | R apply(long value) |
ToIntFunction | int applyAsInt(T value) |
ToDoubleFunction | double applyAsDouble(T value) |
ToLongFunction | long applyAsLong(T value) |
DoubleToIntFunction | int applyAsInt(double value) |
DoubleToLongFunction | long applyAsLong(double value) |
IntToDoubleFunction | double applyAsDouble(int value) |
IntToLongFunction | long applyAsLong(int value) |
LongToDoubleFunction | double applyAsDouble(long value) |
LongToIntFunction | int applyAsInt(long value) |
BiFunction | void accept(T t, U u) |
ToIntBiFunction | int applyAsInt(T t, U u) |
ToDoubleBiFunction | double applyAsDouble(T t, U u) |
ToLongBiFunction | long applyAsLong(T t, U u) |
CHAPTER 3 Streams
java 8 新串流 API 支援 functional programming,串流只能被使用一次,如需要再次處理數據必須再重新建立。
Stream 運行原理
// 呼叫流程
Arrays.stream -> StreamSupport.stream -> return ReferencePipeline.Head -> filter (or the method you need)
CHAPTER 4 Comparators and Collectors
java7 使用 collections
public List<String> defaultSort() {
Collections.sort(list);
return sampleStrings;
}
java8 透過 streams 使用 collections
public List<String> defaultSortUsingStreams() {
return list.stream()
.sorted()
.collect(Collectors.toList());
}
collect 方法 兩種實做
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
CHAPTER 5 Issues with Streams, Lambdas, and Method References
使用靜態方法確認空值與比較
// 去除空值資料
List<String> nonNullStrings = Arrays.asList(
"this", null, "is", "a", null, "list", "of", "strings", null)
.stream().filter(Objects::nonNull).collect(Collectors.toList());
在 lambdas 內存取在外 local 變數
int total = 0;
// 錯誤 lambdas 無法存取 local var
Arrays.asList(3, 1, 4, 1, 5, 9).forEach(n -> total += n);
// 使用 function 傳入屬性進行存取
Arrays.asList(3, 1, 4, 1, 5, 9)
.stream().mapToInt(Integer::valueOf).sum()
使用 forEach 來進行迭代
List<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);
// 匿名方法
integers.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
// 完整敘述式 lambda
integers.forEach((Integer n) -> {System.out.println(n);});
// 表達式 lambda
integers.forEach(n -> System.out.println(n));
// 方法參照
integers.forEach(System.out::println);
檢查 lambda 錯誤 exception
// 使用 encode 必須處理 UnsupportedEncodingException
Arrays.stream(values).map(s -> URLEncoder.encode(s, "UTF-8"))).collect(Collectors.toList());
// solution
Arrays.stream(values)
.map(s -> {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "";
}
})
.collect(Collectors.toList());
透過 @FunctionalInterface 改寫
@FunctionalInterface
public interface FunctionWithException<T, R, E extends Exception> {
R apply(T t) throws E;
}
private static <T, R, E extends Exception> Function<T, R> wrapper(FunctionWithException<T, R, E> fe) {
return arg -> {
try {
return fe.apply(arg);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
Arrays.stream(values).map(wrapper(s -> URLEncoder.encode(s, "UTF-8"))).collect(Collectors.toList());
CHAPTER 6 The Optional Type
Java 8 新的 java.util.Optional<T>
類,目的是解決許多開發者困擾的 NullPointerExceptions,他被設計以溝通的方式告知回傳值為不合法的空值。
Optional 結合 AtomicInteger 範例
AtomicInteger counter = new AtomicInteger();
Optional<AtomicInteger> opt = Optional.ofNullable(counter);
System.out.println(optional); // Optional[0]
counter.incrementAndGet();
System.out.println(optional); // Optional[1]
optional.get().incrementAndGet();
// 重新指定內容
optional = Optional.ofNullable(new AtomicInteger());
Optional 例外範例
Optional<String> firstOdd =
Stream.of("five", "even", "length", "string", "values")
.filter(s -> s.length() % 2 != 0)
.findFirst();
// throws NoSuchElementException
System.out.println(firstOdd.get());
// 使用 isPresent 檢查空值
System.out.println(
firstOdd.isPresent() ? firstOdd.get() : "No even length strings");
// 如值為空回傳預設值
firstOdd.orElse("No even length strings");
// 如值為空執行Methods並回傳值
firstOdd.orElseGet(() -> new String());
CHAPTER 7 File I/O
使用 stream 處理檔案內文
找出檔案內最長的單字前10名
try (Stream<String> lines = Files.lines(Paths.get("/usr/share/dict/web2")) {
lines.filter(s -> s.length() > 20)
.sorted(Comparator.comparingInt(String::length).reversed()).limit(10)
.forEach(w -> System.out.printf("%s (%d)%n", w, w.length()));
} catch (IOException e) {
e.printStackTrace();
}
列出目錄檔案清單
try (Stream<Path> paths = Files.walk(Paths.get("src/main/java"))) {
paths.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
找出含有特定目錄的檔案
try (Stream<Path> paths =
Files.find(Paths.get("src/main/java"), Integer.MAX_VALUE, (path, attributes) -> !attributes.isDirectory() && path.toString().contains("fileio"))) {
paths.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
CHAPTER 8 The java.time Package
從 JDK1.0 開始就包含的程式庫,java.util.Date , 大多數 java.util.Date 方法都被棄用並使用 java.util.Calendar 來取代,但也並不是那麼方便取用。
終於在 Java 8 後導入了 Joda-Time library 並建議之後的使用者使用它做為開發。
基本使用
// Instant.now(): 2017-06-20T17:27:08.184Z
System.out.println("Instant.now(): " + Instant.now());
// LocalDate.now(): 2017-06-20
System.out.println("LocalDate.now(): " + LocalDate.now());
// LocalTime.now(): 13:27:08.318
System.out.println("LocalTime.now(): " + LocalTime.now());
// LocalDateTime.now(): 2017-06-20T13:27:08.319
System.out.println("LocalDateTime.now(): " + LocalDateTime.now());
// ZonedDateTime.now(): 2017-06-20T13:27:08.319-04:00[America/New_York]
System.out.println("ZonedDateTime.now(): " + ZonedDateTime.now());
util.Date, util.Calendar, java.sql.Date 與 util.time 互換
package datetime;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
public class ConvertDate {
public LocalDate convertFromSqlDatetoLD(java.sql.Date sqlDate) {
return sqlDate.toLocalDate();
}
public java.sql.Date convertToSqlDateFromLD(LocalDate localDate) {
return java.sql.Date.valueOf(localDate);
}
public LocalDateTime convertFromTimestampToLDT(Timestamp timestamp) {
return timestamp.toLocalDateTime();
}
public Timestamp convertToTimestampFromLDT(LocalDateTime localDateTime) {
return Timestamp.valueOf(localDateTime);
}
}
CHAPTER 9 Parallelism and Concurrency
parallelStream 是透過 ForkJoinPool Thread 來進行並發任務
並行範例
// 下列陣列將不會按順訊印出
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)
.parallelStream().forEach(out::println);
// 使用 forEachOrdered 強制以順序印出
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)
.parallelStream().forEachOrdered(out::println);
Parallelism 與其他方式比較
class Person {
int id;
String name;
String sex;
float height;
}
// 傳統迴圈
public List<Person> constructPersons() {
List<Person> persons = new ArrayList<Person>();
for (int i = 0; i < 5; i++) {
Person p = new Person(i, "name" + i, "sex" + i, i);
persons.add(p);
}
return persons;
}
// 迴圈
public void doFor(List<Person> persons) {
long start = System.currentTimeMillis();
for (Person p : persons) {
System.out.println(p.name);
}
long end = System.currentTimeMillis();
System.out.println("doFor cost:" + (end - start));
}
// 串流
public void doStream(List<Person> persons) {
long start = System.currentTimeMillis();
persons.stream().forEach(x -> System.out.println(x.name));
long end = System.currentTimeMillis();
System.out.println("doStream cost:" + (end - start));
}
// 並行
public void doParallelStream(List<Person> persons) {
long start = System.currentTimeMillis();
persons.parallelStream().forEach(x -> System.out.println(x.name));
long end = System.currentTimeMillis();
System.out.println("doParallelStream cost:" + (end - start));
}
使用 openjdk (Java Micro-benchmark Harness) 計算效能
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms4G", "-Xmx4G"})
public class DoublingDemo {
public int doubleIt(int n) {
try {
Thread.sleep(100);
} catch (InterruptedException ignored) { }
return n * 2;
}
@Benchmark
public int doubleAndSumSequential() {
return IntStream.of(3, 1, 4, 1, 5, 9).map(this::doubleIt).sum();
}
@Benchmark
public int doubleAndSumParallel() {
return IntStream.of(3, 1, 4, 1, 5, 9)
.parallel().map(this::doubleIt).sum();
}
}
ForkJoinPool
改變 ForkJoinPool 參數
// 改變 pool 大小
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
// 使用自定的 ForkJoinPool 執行 parallel
ForkJoinPool pool = new ForkJoinPool(15);
ForkJoinTask<Long> task = pool.submit(() -> LongStream.rangeClosed(1, 3_000_000).parallel().sum());
Future
等待未來的結果
public void getIfNotCancelled(Future<String> future) {
if (!future.isCancelled()) {
System.out.println(future.get());
} else {
System.out.println("Cancelled");
}
}
ExecutorService service = Executors.newCachedThreadPool();
Future<String> future = service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(100);
return "Hello, World!";
}
});
// 取消 service submit
future.cancel(true);
System.out.println("Processing...");
getIfNotCancelled(future);
CHAPTER 10 Java 9 Additions
Java 9 新功能
Jigsaw
Suppliers Module
package com.oreilly.suppliers;
public class NamesSupplier implements Supplier<Stream<String>> {
private Path namesPath = Paths.get("server/src/main/resources/names.txt");
@Override
public Stream<String> get() {
try {
return Files.lines(namesPath);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
// module-info.java
module com.oreilly.suppliers {
exports com.oreilly.suppliers;
}
package com.kousenit.clients;
import com.oreilly.suppliers.NamesSupplier;
public class Main {
public static void main(String[] args) throws IOException {
NamesSupplier supplier = new NamesSupplier();
try (Stream<String> lines = supplier.get()) {
lines.forEach(line -> System.out.printf("Hello, %s!%n", line));
}
}
}
// module-info.java
module com.kousenit.clients {
requires com.oreilly.suppliers;
}
介面私有成員
public interface SumNumbers {
default int addEvens(int... nums) {
return add(n -> n % 2 == 0, nums);
}
default int addOdds(int... nums) {
return add(n -> n % 2 != 0, nums);
}
private int add(IntPredicate predicate, int... nums) {
return IntStream.of(nums)
.filter(predicate)
.sum();
}
}
class PrivateDemo implements SumNumbers {}
public class SumNumbersTest {
private SumNumbers demo = new PrivateDemo();
@Test
public void addEvens() throws Exception {
assertEquals(2 + 4 + 6, demo.addEvens(1, 2, 3, 4, 5, 6));
}
@Test
public void addOdds() throws Exception {
assertEquals(1 + 3 + 5, demo.addOdds(1, 2, 3, 4, 5, 6));
}
}
不可變動集合
靜態工廠
不可變動集合 使用 add, addAll, clear, remove, removeAll, replaceAll, set 將會拋出 UnsupportedOperationException.
@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityAdd() throws Exception {
List<Integer> intList = List.of(1, 2, 3);
intList.add(99);
}
@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityClear() throws Exception {
List<Integer> intList = List.of(1, 2, 3);
intList.clear();
}
@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityRemove() throws Exception {
List<Integer> intList = List.of(1, 2, 3);
intList.remove(0);
}
@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityReplace() throws Exception {
List<Integer> intList = List.of(1, 2, 3);
intList.replaceAll(n -> -n);
}
@Test(expected = UnsupportedOperationException.class)
public void showImmutabilitySet() throws Exception {
List<Integer> intList = List.of(1, 2, 3);
intList.set(0, 99);
}
// 如果將不可變動集合塞入物件,將可改變物件內容
@Test
public void areWeImmutableOrArentWe() throws Exception {
List<Holder> holders = List.of(new Holder(1), new Holder(2));
assertEquals(1, holders.get(0).getX());
holders.get(0).setX(4);
assertEquals(4, holders.get(0).getX());
}
迭代使用Predicate
// Java8 方法
List<BigDecimal> bigDecimals =
Stream.iterate(BigDecimal.ZERO, bd -> bd.add(BigDecimal.ONE)).limit(10).collect(Collectors.toList());
// Java9 方法
bigDecimals = Stream.iterate(BigDecimal.ZERO,
bd -> bd.longValue() < 10L,
bd -> bd.add(BigDecimal.ONE))
.collect(Collectors.toList());
日期計算
// Java8 用法
public List<LocalDate> getDays_java8(LocalDate start, LocalDate end) {
Period period = start.until(end);
return LongStream.range(0, ChronoUnit.DAYS.between(start, end))
.mapToObj(start:plusDays)
.collect(Collectors.toList());
}
LocalDate start = LocalDate.of(2017, Month.JUNE, 10);
LocalDate end = LocalDate.of(2017, Month.JUNE, 17);
System.out.println(dateRange.getDays_java8(start, end));
// Java9 用法
public List<LocalDate> getDays_java9(LocalDate start, LocalDate end) {
return start.datesUntil(end).collect(Collectors.toList());
}
public List<LocalDate> getMonths_java9(LocalDate start, LocalDate end) {
return start.datesUntil(end, Period.ofMonths(1)).collect(Collectors.toList());
}
Extra Information
Try-With-Resources
Try with Resources 會自動幫開發者關閉 try(closeable; ... ) 內實做 java.lang.AutoCloseable 的物件
// Java8 用法
// inputstream
try(InputStream stream1 = new InputStream(....); InputStream stream2 = new InputStream(....)) { ... }
// connection
try (Connection con = DriverManager.getConnection(myConnectionURL);
PreparedStatement ps = createPreparedStatement(con, userId);
ResultSet rs = ps.executeQuery()) {
// process the resultset here, all resources will be cleaned up
} catch (Exception e) {
e.printStackTrace();
}
// Java9 用法
InputStream stream1 = new InputStream(....); InputStream stream2 = new InputStream(....); .... try(stream1;stream2){ .... }
APPENDIX A Generics and Java 8
關於泛型的能力大多數的 Java 開發者都只認識到他們所需用於工作上的範圍,跟著 Java 8 的來到 javadoc 文件已經充斥著像是以下的代碼 :
// java.util.Map.Entry
static <K extends Comparable<? super K>,V> Comparator<Map.Entry<K,V>>
comparingByKey()
// or this one from java.util.Comparator:
static <T,U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T,? extends U> keyExtractor)
// or even this monster from java.util.stream.Collectors:
static <T,K,D,A,M extends Map<K, D>> Collector<T,?,M> groupingBy(
Function<? super T,? extends K> classifier, Supplier<M> mapFactory,
Collector<? super T,A,D> downstream)
理解基礎的泛型已不再夠用。
簡易泛型代碼
List<String> strings = new ArrayList<>();
strings.add("Hello");
strings.add("World");
// strings.add(new Date());
// Integer i = strings.get(0); 編譯不過
for (String s : strings) {
System.out.printf("%s has length %d%n", s, s.length());
}
List 所定義的方法
boolean add(E e)
boolean addAll(Collection<? extends E> c)
void clear()
boolean contains(Object o)
boolean containsAll(Collection<?> c)
E get(int index)
有些開發者所不理解的
許多開發者對於 ArrayList<String>
跟 ArrayList<Object>
並沒有關連
List<String> strings = new ArrayList<>();
String s = "abc";
String s = "abc";
Object o = s;
// 這樣的代碼是不被允許的
strings.add(o);
未只定 List 的通配符
List<?> stuff = new ArrayList<>();
// 未指定泛型的 list 不能被寫入
stuff.add("abc");
stuff.add(new Object());
stuff.add(3);
// 但是能被讀取
int numElements = stuff.size();
Upper Bounded Wildcards
使用向上通配符一樣不能被寫入
List<? extends Number> numbers = new ArrayList<>();
// numbers.add(3);
// numbers.add(3.14159);
// numbers.add(new BigDecimal("3"));
使用 Upper Bounded
private static double sumList(List<? extends Number> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5);
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
List<BigDecimal> bigDecimals = Arrays.asList(
new BigDecimal("1.0"),new BigDecimal("2.0"),new BigDecimal("3.0"),new BigDecimal("4.0"),new BigDecimal("5.0"));
System.out.printf("ints sum is %s%n", sumList(ints));
System.out.printf("doubles sum is %s%n", sumList(doubles));
System.out.printf("big decimals sum is %s%n", sumList(bigDecimals));
使用 Lower Bounded
向下通配符一樣不能被寫入
public void numsUpTo(Integer num, List<? super Integer> output) {
IntStream.rangeClosed(1, num).forEach(output::add);
}
ArrayList<Integer> integerList = new ArrayList<>();
ArrayList<Number> numberList = new ArrayList<>();
ArrayList<Object> objectList = new ArrayList<>();
numsUpTo(5, integerList);
numsUpTo(5, numberList);
numsUpTo(5, objectList);
多重分配符
T extends Runnable & AutoCloseable
Top comments (0)