前言 越來越多的項目已經使用 "Java 8" 了,毫無疑問, "Java 8" 是Java自Java 5(發佈於2004年)之後的最重要的版本。這個版本包含語言、編譯器、庫、工具和 JVM 等方面的十多個新特性。在本文中我們將學習這些新特性,並用實際的例子說明在什麼場景下適合使用。 引用: 本文參 ...
前言
越來越多的項目已經使用 Java 8 了,毫無疑問,Java 8 是Java自Java 5(發佈於2004年)之後的最重要的版本。這個版本包含語言、編譯器、庫、工具和 JVM 等方面的十多個新特性。在本文中我們將學習這些新特性,並用實際的例子說明在什麼場景下適合使用。
引用:本文參考了這兩篇文章,加以自己的理解,整理成一份最容易理解的 Java8 新特性文章,有少部分章節可能內容一致,但絕對不是抄襲,只是為了文章的完整性,大部分常用的地方加了我自己的理解和示例。
https://blog.csdn.net/yczz/article/details/50896975
https://blog.csdn.net/maosijunzi/article/details/38658095
適合讀者及目標
目標人群
- 適合有用過 lambda 表達式的同學,想徹底瞭解清楚
- 學習 Java8 的新特定
目標
- 瞭解 java8 的函數式介面和 Lambda 表達式
- 方法引用的使用
- 介面的靜態方法和預設方法
- Date/Time Api 的使用
- Stream API 的使用
1. Java 語言的新特性
Java8 的 lambda 的使用確實方便了許多,但也使初次瞭解的人感覺到難以閱讀,其實是你不習慣的原因。很多語言從一開始就支持了 Lambda 表達式,像 Groovy,Scala 等。
1.1 Lambda 表達式和函數式介面
在 Java8 以前,我們想要讓一個方法可以與用戶進行交互,比如說使用方法內的局部變數;這時候就只能使用介面做為參數,讓用戶實現這個介面或使用匿名內部類的形式,把局部變數通過介面方法傳給用戶。
傳統匿名內部類缺點:代碼臃腫,難以閱讀
Lambda 表達式
Lambda 表達式將函數當成參數傳遞給某個方法,或者把代碼本身當作數據處理;
語法格式:
- 用逗號分隔的參數列表
->
符號- 和 語句塊 組成
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
等價於
List<String> list = Arrays.asList( "a", "b", "d" );
for(String e:list){
System.out.println(e);
}
如果語句塊比較複雜,使用 {}
包起來
Arrays.asList( "a", "b", "d" ).forEach( e -> {
String m = "9420 "+e;
System.out.print( m );
});
Lambda 本質上是匿名內部類的改裝,所以它使用到的變數都會隱式的轉成 final
的
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
e -> System.out.print( e + separator ) );
等價於
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
e -> System.out.print( e + separator ) );
Lambda 的返回值和參數類型由編譯器推理得出,不需要顯示定義,如果只有一行代碼可以不寫 return 語句
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
等價於
List<String> list = Arrays.asList("a", "b", "c");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
函數式介面
- 介面中只能有一個介面方法
- 可以有靜態方法和預設方法
- 使用
@FunctionalInterface
標記 - 預設方法可以被覆寫
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
static void staticMethod(){
}
}
private interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
}
private static class DefaultableImpl implements Defaulable {
}
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
// 也可以由介面覆蓋
public interface OverridableInterface extends Defaulable{
@Override
public String notRequired() {
return "interface Overridden implementation";
}
}
由於JVM上的預設方法的實現在位元組碼層面提供了支持,因此效率非常高。預設方法允許在不打破現有繼承體系的基礎上改進介面。該特性在官方庫中的應用是:給 java.util.Collection介面添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。
已經存在的 Java8 定義的函數式介面
我們基本不需要定義自己的函數式介面,Java8 已經給我們提供了大量的預設函數式介面,基本夠用,在 rt.jar
包的 java.util.function
目錄下可以看到所有預設的函數式介面,大致分為幾類
Function<T,R>
T 作為輸入,返回的 R 作為輸出Predicate<T>
T 作為輸入 ,返回 boolean 值的輸出Consumer<T>
T 作為輸入 ,沒有輸出Supplier<R>
沒有輸入 , R 作為輸出BinaryOperator<T>
兩個 T 作為輸入 ,T 同樣是輸出UnaryOperator<T>
是Function
的變種 ,輸入輸出者是 T
其它的都是上面幾種的各種擴展,只為更方便的使用,下麵演示示例,你可以把其當成正常的介面使用,由用戶使用 Lambda 傳入。
// hello world 示例
Function<String,String> function = (x) -> {return x+"Function";};
System.out.println(function.apply("hello world")); // hello world Function
UnaryOperator<String> unaryOperator = x -> x + 2;
System.out.println(unaryOperator.apply("9420-")); // 9420-2
// 判斷輸入值是否為偶數示例
Predicate<Integer> predicate = (x) ->{return x % 2 == 0 ;};
System.out.println(predicate.test(1)); // false
// 這個沒有返回值
Consumer<String> consumer = (x) -> {System.out.println(x);};
consumer.accept("hello world "); // hello world
// 這個沒有輸入
Supplier<String> supplier = () -> {return "Supplier";};
System.out.println(supplier.get()); // Supplier
// 找出大數
BinaryOperator<Integer> bina = (x, y) ->{return x > y ? x : y;};
bina.apply(1,2); // 2
1.2 方法引用
方法引用使得開發者可以直接引用現存的方法、Java類的構造方法或者實例對象。方法引用和Lambda表達式配合使用,使得java類的構造方法看起來緊湊而簡潔,沒有很多複雜的模板代碼。
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一種方法引用的類型是構造器引用,語法是Class::new,或者更一般的形式:Class
final Car car = Car.create( Car::new );
等價於
Car car = Car.create(() -> new Car());
第二種方法引用的類型是靜態方法引用,語法是Class::static_method。註意:這個方法接受一個Car類型的參數。
cars.forEach( Car::collide );
forEach
原型為 forEach(Consumer<? super T> action)
使用的是 Consumer 只有參數,沒有返回值;這個參數 T 就是 car 類型,因為是 cars.forEach
嘛,所以上面的方法引用等價於
cars.forEach(car -> Car.collide(car));
第三種方法引用的類型是某個類的成員方法的引用,語法是Class::method,註意,這個方法沒有定義入參:
cars.forEach( Car::repair );
它等價於
cars.forEach(car -> car.repair());
1.3 重覆註解
自從Java 5中引入註解以來,這個特性開始變得非常流行,併在各個框架和項目中被廣泛使用。不過,註解有一個很大的限制是:在同一個地方不能多次使用同一個註解。Java 8打破了這個限制,引入了重覆註解的概念,允許在同一個地方多次使用同一個註解。
在Java 8中使用 @Repeatable 註解定義重覆註解,實際上,這並不是語言層面的改進,而是編譯器做的一個trick,底層的技術仍然相同。可以利用下麵的代碼說明:
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
public static void main(String[] args) {
for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
System.out.println( filter.value() );
}
}
正如我們所見,這裡的Filter類使用 @Repeatable(Filters.class)
註解修飾,而Filters是存放Filter註解的容器,編譯器儘量對開發者屏蔽這些細節。這樣,Filterable介面可以用兩個Filter註解註釋(這裡並沒有提到任何關於Filters的信息)。
另外,反射API提供了一個新的方法:getAnnotationsByType(),可以返回某個類型的重覆註解,例如Filterable.class.getAnnoation(Filters.class)
將返回兩個Filter實例。
1.4 更好的類型推斷
Java 8編譯器在類型推斷方面有很大的提升,在很多場景下編譯器可以推導出某個參數的數據類型,從而使得代碼更為簡潔。例子代碼如下:
public class Value< T > {
public static< T > T defaultValue() {
return null;
}
public T getOrDefault( T value, T defaultValue ) {
return ( value != null ) ? value : defaultValue;
}
}
public class TypeInference {
public static void main(String[] args) {
final Value< String > value = new Value<>();
value.getOrDefault( "22", Value.defaultValue() );
}
}
參數 Value.defaultValue() 的類型由編譯器推導得出,不需要顯式指明。在Java 7中這段代碼會有編譯錯誤,除非使用Value.<String>defaultValue()
1.5 拓寬註解的應用場景
Java 8拓寬了註解的應用場景。現在,註解幾乎可以使用在任何元素上:局部變數、介面類型、超類和介面實現類,甚至可以用在函數的異常定義上。下麵是一些例子:
package com.javacodegeeks.java8.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
ElementType.TYPE_USER 和 ElementType.TYPE_PARAMETER 是Java 8新增的兩個註解,用於描述註解的使用場景。Java 語言也做了對應的改變,以識別這些新增的註解。
2. Java 編譯器的新特性
Java 8 開始正式支持參數名稱,終於不需要讀 class 位元組碼來獲取參數名稱了,這對於經常使用反射的人特別有用。
在 Java8 這個特性預設是關閉的,需要開啟參數才能獲取參數名稱:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
3. JVM 的新特性
使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM參數方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原來的-XX:PermSize和-XX:MaxPermSize。
4. Java 官方庫的新特性
Java 8增加了很多新的工具類(date/time類),並擴展了現存的工具類,以支持現代的併發編程、函數式編程等,本章節參考原文,並提取出常用功能。
4.1 Streams
Streams 操作分為中間操作和晚期操作,中間操作會返回一個新的 Stream ,只是把要做的操作記錄起來而已,並不會真的執行,晚期操作才會真的遍歷列表並執行所有操作
Stream 的另一個價值就是支持了並行處理 parallel
方法。
Stream API 簡化了集合的操作,並擴展了集合的分組,求和,mapReduce,flatMap ,排序等功能,下麵列出項目中經常用到的功能,會以使用頻率排序。
- 準備一個用於下麵例子測試的對象
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Vehicle {
//車架號
private String vin;
// 車主手機號
private String phone;
// 車主姓名
private String name;
// 所屬車租車公司
private Integer companyId;
// 個人評分
private Double score;
//安裝的設備列表imei,使用逗號分隔
private String deviceNos;
}
- 準備一些車輛數據
static List<Vehicle> vehicles = new ArrayList<>();
@Before
public void init(){
List<String> imeis = new ArrayList<>();
for (int i = 0; i <5 ; i++) {
List<String> singleVehicleDevices = new ArrayList<>();
for (int j = 0; j < 3; j++) {
String imei = RandomStringUtils.randomAlphanumeric(15);
singleVehicleDevices.add(imei);
}
imeis.add(StringUtils.join(singleVehicleDevices,','));
}
vehicles.add(new Vehicle("KPTSOA1K67P081452","17620411498","9420",1,4.5,imeis.get(0)));
vehicles.add(new Vehicle("KPTCOB1K18P057071","15073030945","張玲",2,1.4,imeis.get(1)));
vehicles.add(new Vehicle("KPTS0A1K87P080237","19645871598","sanri1993",1,3.0,imeis.get(2)));
vehicles.add(new Vehicle("KNAJC526975740490","15879146974","李種",1,3.9,imeis.get(3)));
vehicles.add(new Vehicle("KNAJC521395884849","13520184976","袁紹",2,4.9,imeis.get(4)));
}
4.1.1 forEach 遍歷Collection 數據
vehicles.forEach(vehicle -> System.out.println(vehicle));
//這樣就可以遍歷列印
vehicles.forEach(System.out::println);
4.1.2 forEach 遍歷 Map 數據
Map<String,Integer> map = new HashMap<>();
map.put("a",1);map.put("b",2);map.put("c",3);
map.forEach((k,v) -> System.out.println("key:"+k+",value:"+v));
4.1.3 filter 數據過濾
// 去掉評分為 3 分以下的車
List<Vehicle> collect = vehicles.stream().filter(vehicle -> vehicle.getScore() >= 3).collect(Collectors.toList());
4.1.4 map 對象映射
對一個 List<Object>
大部分情況下,我們只需要列表中的某一列,或者需要把裡面的每一個對象轉換成其它的對象,這時候可以使用 map 映射,示例:
// 取出所有的車架號列表
List<String> vins = vehicles.stream().map(Vehicle::getVin).collect(Collectors.toList());
4.1.5 groupBy 按照某個屬性進行分組
// 按照公司 Id 進行分組
Map<Integer, List<Vehicle>> companyVehicles = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getCompanyId));
// 按照公司分組求司機打分和
Map<Integer, Double> collect = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getCompanyId, Collectors.summingDouble(Vehicle::getScore)));
4.1.6 sort 按照某個屬性排序 ,及多列排序
// 單列排序
vehicles.sort((v1,v2) -> v2.getScore().compareTo(v1.getScore()));
// 或使用 Comparator 類來構建比較器,流處理不會改變原列表,需要接收返回值才能得到預期結果
List<Vehicle> collect = vehicles.stream().sorted(Comparator.comparing(Vehicle::getScore).reversed()).collect(Collectors.toList());
// 多列排序,score 降序,companyId 升序排列
List<Vehicle> collect = vehicles.stream().sorted(Comparator.comparing(Vehicle::getScore).reversed()
.thenComparing(Comparator.comparing(Vehicle::getCompanyId)))
.collect(Collectors.toList());
4.1.7 flatMap 扁平化數據處理
// 查出所有車綁定的所有設備
List<String> collect = vehicles.stream().map(vehicle -> {
String deviceNos = vehicle.getDeviceNos();
return StringUtils.split(deviceNos,',');
}).flatMap(Arrays::stream).collect(Collectors.toList());
flatMap 很適合 List<List>
或 List<object []>
這種結構,可以當成一個列表來處理;像上面的設備列表,在資料庫中存儲的結構就是以逗號分隔的數據,而車輛列表又是一個列表數據。
4.1.8 mapReduce 數據處理
// 對所有司機的總分求和
Double reduce = vehicles.stream().parallel().map(Vehicle::getScore).reduce(0d, Double::sum);
4.1.9 綜合處理示例
// 總的分值
Double totalScore = vehicles.stream().parallel().map(Vehicle::getScore).reduce(0d, Double::sum);
// 查看每一個司機占的分值比重
List<String> collect = vehicles.stream()
.mapToDouble(vehicle -> vehicle.getScore() / totalScore)
.mapToLong(weight -> (long) (weight * 100))
.mapToObj(percentage -> percentage + "%")
.collect(Collectors.toList());
原文的 boxed 不知道是什麼意思,希望有大神能幫忙解答下,不用 boxed 也是可以的
4.2 Optional
Optional 用來解決 Java 中經常出現的 NullPointerException ,從而避免源碼被各種空檢查污染,使源碼更加簡潔和更加容易閱讀
// 假設有一個對象 obj ,你不知道它是不是為空的,但是你想用它的方法,可以這麼玩
Optional<T> canUseObj = Optional.ofNullable(obj);
canUseObj.ifPresent(System.out::println); //如果 obj 不為空,則可以使用 obj 的方法,這裡做個簡單輸出
4.3 Date/Time API(JSR 310)
新的日期時間工具全部都在 java.time
及其子包中。
4.3.1 新 Date/Time API 設計原則
Java 8日期/時間API是 JSR-310 規範的實現,它的目標是剋服舊的日期/時間API實現中所有的缺陷,新的日期/時間API的一些設計原則如下:
- 不變性:新的日期/時間API中,所有的類都是不可變的,這種設計有利於併發編程。
- 關註點分離:新的API將人可讀的日期時間和機器時間(unix timestamp)明確分離,它為日期(Date)、時間(Time)、日期時間(DateTime)、時間戳(unix timestamp)以及時區定義了不同的類。
- 清晰:在所有的類中,方法都被明確定義用以完成相同的行為。舉個例子,要拿到當前實例我們可以使用now()方法,在所有的類中都定義了format()和parse()方法,而不是像以前那樣專門有一個獨立的類。為了更好的處理問題,所有的類都使用了工廠模式和策略模式,一旦你使用了其中某個類的方法,與其他類協同工作並不困難。
- 實用操作:所有新的日期/時間API類都實現了一系列方法用以完成通用的任務,如:加、減、格式化、解析、從日期/時間中提取單獨部分等操作。
- 可擴展性:新的日期/時間API是工作在ISO-8601日曆系統上的,但我們也可以將其應用在非IOS的日曆上。
4.3.2 常用類及其使用
時間大致可以分為三個部分:日期、時間、時區
其中日期又細分為年、月、日;時間又細分為時、分、秒
一般機器時間用從 1970-01-01T00:00 到現在的秒數來表示時間; 這裡糾正大部分人犯的一個錯誤概念,時間戳指的是秒數,而不是毫秒數。
幾乎所有的時間對象都實現了 Temporal
介面,所以介面參數一般都是 Temporal
Instant: 表示時間線上的一個點,參考點是標準的Java紀元(epoch),即1970-01-01T00:00:00Z(1970年1月1日00:00 GMT)
LocalDate: 日期值對象如 2019-09-22
LocalTime:時間值對象如 21:25:36
LocalDateTime:日期+時間值對象
ZoneId:時區
ZonedDateTime:日期+時間+時區值對象
DateTimeFormatter:用於日期時間的格式化
Period:用於計算日期間隔
Duration:用於計算時間間隔
4.3.2.1 Instant
表示時間線上的一個點(瞬時)
// 測試執行一個 new 操作使用的時間(納秒值)
Instant begin = Instant.now();
StreamMain streamMain = new StreamMain();
Instant end = Instant.now();
System.out.println(Duration.between(begin,end).toNanos());
4.3.2.2 LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
可以規為一組,用於表示時間的
// 可以使用 of 方法構建它們的實例,如下麵創建了一個 2019-9-22 21:42:59 東八區 的時間對象
LocalDate localDate = LocalDate.of(2019, Month.SEPTEMBER, 22);
LocalTime localTime = LocalTime.of(21, 42, 59);
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
// 獲取現在的時間,這是一個靜態方法
LocalDate now = LocalDate.now();
// 每個實例可以獲取它們的 part 信息,如獲取年
int year = localDate.getYear();
// 可以修改 part 信息,這將返回一個新對象,如增加一年
LocalDate localDatePlus = localDate.plusYears(1);
// 設置 part 信息,也會返回新的對象,如設置為 2017 年
LocalDate localDateWithYear = localDate.withYear(2017);
// 比較兩個日期 isAfter,isBefore
boolean after = localDate.isAfter(LocalDate.now());
// 格式化日期時間
// yyyy-MM-dd
System.out.println(now.format(DateTimeFormatter.ISO_DATE));
// yyyy-MM-ddTHH:mm:ss
System.out.println(now.format(DateTimeFormatter.ISO_DATE_TIME));
// yyyy-MM-dd HH:mm:ss
System.out.println(now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
// 日期解析
System.out.println(LocalDate.parse("2019-09-22"));
System.out.println(LocalDateTime.parse("2019-09-22T21:05:22"));
System.out.println(LocalDateTime.parse("2019-09-22 21:05:22",DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
4.3.2.3 ZoneId
用來操作時區,它提供了獲取所有時區和本地時區的方法
ZoneId zoneId = ZoneId.systemDefault();
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
4.3.2.4 Period
,Duration
可以視為一組,用於計算時間間隔
// 創建一個兩周的間隔
Period periodWeeks = Period.ofWeeks(2);
// 一年三個月零二天的間隔
Period custom = Period.of(1, 3, 2);
// 一天的時長
Duration duration = Duration.ofDays(1);
// 計算2015/6/16 號到現在 2019/09/22 過了多久,它這個把間隔分到每個 part 了
LocalDate now = LocalDate.now();
LocalDate customDate = LocalDate.of(2015, 6, 16);
Period between = Period.between(customDate, now);
// 結果為 4:3:6 即過去了 4年3個月6天了
System.out.println(between.getYears()+":"+between.getMonths()+":"+between.getDays());
// 比較兩個瞬時的時間間隔
Instant begin = Instant.now();
Instant end = Instant.now();
Duration.between(begin,end);
// 同樣可以修改 part 信息和設置 part 信息,都是返回新的對象來表示設置過的值,原來的對象不變
Period plusDays = between.plusDays(1);
Period withDays = between.withDays(4);
4.4 Base64
對於 Base64 終於不用引用第三方包了,使用 java 庫就可以完成
// 編碼
final String encoded = Base64.getEncoder().encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
// 解碼
final String decoded = new String( Base64.getDecoder().decode( encoded ),StandardCharsets.UTF_8 );
4.5 JUC 工具包擴充
基於新增的lambda表達式和steam特性,為Java 8中為java.util.concurrent.ConcurrentHashMap類添加了新的方法來支持聚焦操作;另外,也為java.util.concurrentForkJoinPool類添加了新的方法來支持通用線程池操作(更多內容可以參考我們的併發編程課程)。
Java 8還添加了新的java.util.concurrent.locks.StampedLock類,用於支持基於容量的鎖——該鎖有三個模型用於支持讀寫操作(可以把這個鎖當做是java.util.concurrent.locks.ReadWriteLock的替代者)。
在java.util.concurrent.atomic包中也新增了不少工具類,列舉如下:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
5. 新的工具
Java 8提供了一些新的命令行工具,這部分會講解一些對開發者最有用的工具。
5.1 類依賴分析器:jdeps
deps是一個相當棒的命令行工具,它可以展示包層級和類層級的Java類依賴關係,它以.class文件、目錄或者Jar文件為輸入,然後會把依賴關係輸出到控制台。
我們可以利用jedps分析下Spring Framework庫,為了讓結果少一點,僅僅分析一個JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
jdeps org.springframework.core-3.0.5.RELEASE.jar
這個命令會輸出很多結果,我們僅看下其中的一部分:依賴關係按照包分組,如果在classpath上找不到依賴,則顯示"not found".
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
-> java.io
-> java.lang
-> java.lang.annotation
-> java.lang.ref
-> java.lang.reflect
-> java.util
-> java.util.concurrent
-> org.apache.commons.logging not found
-> org.springframework.asm not found
-> org.springframework.asm.commons not found
org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
-> java.lang
-> java.lang.annotation
-> java.lang.reflect
-> java.util
一點小推廣
創作不易,希望可以支持下我的開源軟體,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。
Excel 通用導入導出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代碼 ,從資料庫生成代碼 ,及一些項目中經常可以用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven