jdk8的新特性
- 接口增強,default,static關鍵字
在jdk1.8只前接口中只能使用抽象方法,而不能有任何方法的實現的
jdk1.8裏面則可以聲明default和static修飾的方法
/**
* @Date: 2019/11/16 11:10
* @Description:
* 在jdk1.8以前接⼝⾥⾯是隻能有抽象⽅法,不能有任何⽅法的實現的
* 但是jdk1.8⾥⾯打破了這個規定,引⼊了新的關鍵字default,使⽤default修飾⽅法,可以在接⼝⾥⾯定義具體的⽅法實現
* default修飾的方法稱爲默認⽅法,這個接⼝的實現類實現了這個接⼝之後,可以被重寫,類似像抽象類的方法一樣
* static修飾的方法和普通的方法一樣,可以被直接調用
* jdk 9新增private方法
*/
public interface student {
void study();
default void testMethod(){
System.out.println("接口裏的默認方法");
}
static void staticMethod(){
System.out.println("接口裏的靜態方法");
}
// jdk9 新增private方法
private void privateMethod() {
System.out.println("jdk9 新增私有方法");
};
}
- 日期處理類
- SimpleDateFormate,Calendar舊版的缺點: java.util.Date 是非線程安全的api,涉及的比較差,日期時間的計算,比較都相對的麻煩
- java 8新增日期處理類:LocalDate、LocalTime、Instant、Duration以及Period,這些類都包含在java.time包中
/**
* @Date: 2019/11/16 11:12
* @Description: LocalDate LocalTime LocalDateTime api類似
*/
public class DateTest {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
System.out.println(today);
//獲取年月日
System.out.println(today.getYear());
System.out.println(today.getMonth());
System.out.println(today.getDayOfMonth());
System.out.println(today.getDayOfWeek());
//加減年份,加厚返回的對象纔是修改後的,舊的依舊是舊的
LocalDate changeDate = today.plusYears(1);
System.out.println(changeDate.getYear());
System.out.println(today.getYear());
//日期比較
//是否在之前
System.out.println("isbefore:"+today.isBefore(changeDate));
//是否在之後
System.out.println("isbefore:"+today.isAfter(changeDate));
// jdk8引入 DateTimeFormatter是線程安全的SimpleDateFormat
LocalDateTime lt = LocalDateTime.now();
System.out.println(lt);// 2019-11-07T23:12:29.056
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String ldtStr = dtf.format(lt);
System.out.println(ldtStr);// 2019-11-07 23:12:29
//獲取指定時間對象
LocalDateTime ldt = LocalDateTime.of(2020,10,1,10,40,30);
System.out.println(changeDate);// 2020-10-01T10:40:30
//Duration基於時間值,Period基於日期值,
//Duration類表示秒或納秒時間間隔
//適合處理較短的時間,需要更高的精確性。使用between()方法比較兩個瞬間的差:
Instant start = Instant.parse("2019-11-16T11:15:30.00Z");
Instant end = Instant.parse("2019-11-16T11:16:30.00Z");
Duration duration = Duration.between(start,end);//第⼆個參數減第⼀個參數
System.out.println(duration.toDays());//兩個時間差的天數
System.out.println(duration.toHours());//兩個時間差的⼩時數
System.out.println(duration.toMinutes());//兩個時間差的分鐘數
System.out.println(duration.toMillis());//兩個時間差的毫秒數
System.out.println(duration.toNanos());//兩個時間差的納秒數
//Period 類表示一段時間的年、月、日,使用between()方法獲取兩個日期之間的差
LocalDate startDate = LocalDate.of(2019, 11, 16);
LocalDate endDate = LocalDate.of(2020, 1, 1);
Period period = Period.between(startDate,endDate);
System.out.println("per:"+period.getYears());//兩個時間差的年數
System.out.println("per:"+period.getMonths());//兩個時間差的月
}
}
- base64
新增base64 編解碼api
public static void main(String args[]) throws UnsupportedEncodingException {
Base64.Encoder encoder = Base64.getEncoder();
Base64.Decoder decoder = Base64.getDecoder();
String str = "這是一個測試";
byte[] bytes = str.getBytes("utf-8");
String encodText = encoder.encodeToString(bytes);
System.out.println(encodText);
System.out.println(new String(decoder.decode(encodText),"utf-8"));
}
- Optionnal類
Optional類的作用:
- 主要解決的問題是:空指針異常 NullPointerException
- 本質上是一個包含有可選值的包裝類,Opertional類既可以爲含有對象也可以爲空
1> 創建opertional類
of()
null傳進去會報錯
Optional<Student> opt = Optional.of(user);
ofNullable()
對象可能是null也可能是非null 就應該使用ofNullable()
Optional<Student> opt = Optional.ofNullable(user);
2>訪問Optional對象的值
get()
Optional<Student> opt = Optional.ofNullable(student);
Student s = opt.get();
如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象一般使用get之前需要先驗證是否有值,不然會報錯
public static void main(String[] args) {
Student student = null;
test(student);
}
public static void test(Student student){
Optional<Student> opt = Optional.ofNullable(student);
System.out.println(opt.isPresent());
}
3>兜底方法
使用:當value值爲null時,給予一個默認值:
方法1:orElse(T other)
方法2:orElseGet(Supplier<? extends T> other)
方法3:orElseThrow(Supplier<? extends X> exceptionSupplier)
@Test
public void test() {
Student student= null;
Student= Optional.ofNullable(student).orElse(createStudent());
Student= Optional.ofNullable(student).orElseGet(() -> createStudent());
Student= Optional.ofNullable(student).orElseThrow(()->new Exception("學生不存在"));
}
public User createStudent(){
Studentstudent = new Student();
student.setName("stu");
return user;
}
orElse和orElseGet二者區別:
orElse()方法在Optional值爲非空時,也會計算傳入的參數,而orElseGet()方法只有在Optional值爲空時纔會執行傳入的函數。
由於orElseGet()不是每次都會調用傳入的方法,所以orElseGet()方法在性能上要優於orElse()方法。一般情況下,個人推薦使用orElseGet()方法更好
4>map()和faltMap()
map(Function<? super T, ? extends U> mapper)
如果有值,則對其執行調用mapping函數得到返回值。如果返回值不爲null,則創建包含mapping返回值的Optional作爲map方法返回值,否則返回空Optional。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("name is null"));
這兩個函數,在函數體上沒什麼區別。唯一區別的就是入參,map函數所接受的入參類型爲Function<? super T, ? extends U>,而flapMap的入參類型爲Function<? super T, Optional>
5> filter(Predicate<? super T> predicate)
使用:如果有值並且滿足斷言條件返回包含該值的Optional,否則返回空Optional。
Optional<Student> student= Optional.ofNullable(student).filter(s -> s.getName().length()<6);
- Lambda表達式
lambda表達式 使⽤場景(前提):⼀個接⼝中只包含⼀個⽅法,則可以使⽤Lambda表達式,這樣
的接⼝稱之爲“函數接⼝” 語法: (params) -> expression
第⼀部分爲括號內⽤逗號分隔的形式參數,參數是函數式接⼝⾥⾯⽅法的參數;第⼆部分爲⼀個箭
頭符號:->;第三部分爲⽅法體,可以是表達式和代碼塊
參數列表 :
括號中參數列表的數據類型可以省略不寫
括號中的參數只有⼀個,那麼參數類型和()都可以省略不寫
⽅法體:
如果{}中的代碼只有⼀⾏,⽆論有返回值,可以省略{},return,分號,要⼀起省略,其他
則需要加上
1>Lambda 表達式的實現⽅式在本質是以匿名內部類的⽅式進⾏實現
public class lambdatest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("jdk8以前創建線程");
}
});
//()對應run()沒有一個參數,->後面是方法體內容
//如果{}中的代碼只有⼀行,⽆論有返回值,可以省略{}、return、分號,其他則需要加上
new Thread(()-> System.out.println("lambda表達式創建線程"));
List<String> list = Arrays.asList("a","c","d","b","e");
// jdk8以前排序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
// lambda表達式排序
//,前面的對應接口前面的參數,a b 對應compare裏面的參數
Collections.sort(list,(a,b)->b.compareTo(a));
}
}
2>自定義lambda接口流程
定義⼀個函數式接⼝ 需要標註此接⼝ @FunctionalInterface,接口裏面必須有且只能有一個方法
@FunctionalInterface
public interface OperFunction<R,T> {
// R表示返回值,T表示參數類型,t1 t2是具體參數
R operator(T t1, T t2);
}
public class cacl {
// 步驟:1定義一個函數方法 2 需要傳入a和b兩個參數 3傳入的函數
public static Integer operator(Integer a,Integer b,Test1<Integer,Integer> of){
return of.operator(a,b);
}
}
public static void main(String[] args) throws Exception {
System.out.println(cacl.operator(1,3,(a,b)-> a+b));
System.out.println(cacl.operator(1,3,(a,b)-> a-b));
System.out.println(cacl.operator(1,3,(a,b)-> a*b));
System.out.println(cacl.operator(1,3,(a,b)-> a/b));
}
3>⽅法引⽤與構造函數引⽤
jdk1.8提供了另外⼀種調⽤⽅式 ::
說明:
⽅法引⽤是⼀種更簡潔易懂的lambda表達式,操作符是雙冒號::,⽤來直接訪問類或者實例已經存在的⽅法或構造⽅法
通過⽅法引⽤,可以將⽅法的引⽤賦值給⼀個變量
語法:
左邊是容器(可以是類名,實例名),中間是" :: ",右邊是相應的⽅法名
靜態⽅法,則是ClassName::methodName。如 Object ::equals
實例⽅法,則是Instance::methodName
構造函數,則是 類名::new;
單個參數
Function<⼊參1, 返回類型> func = ⽅法引⽤
應⽤ func.apply(⼊參);
兩個參數
BiFunction<⼊參1,⼊參2, 返回類型> func = ⽅法引⽤
應⽤ func.apply(⼊參1,⼊參2);
- 函數式編程
內置的四⼤核⼼函數式接⼝
Function<T, R> : 函數型接⼝:有⼊參,有返回值
R apply(T t);
Consumer<T> : 消費型接⼝:有⼊參,⽆返回值
void accept(T t);
Supplier<T> : 供給型接⼝:⽆⼊參,有返回值
T get();
Predicate<T> : 斷⾔型接⼝:有⼊參,有返回值,返回值類型確定是boolean
boolean test(T t);
1> Function<T, R>
- 傳⼊⼀個值經過函數的計算返回另⼀個值
- T:⼊參類型,R:出參類型
調⽤⽅法:R apply(T t) - 作⽤:將轉換邏輯提取出來,解耦合
//function源碼
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
//BiFunction源碼
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
public static void main(String[] args) throws Exception {
//一個參數一個返回值
Function<Integer,Integer> func = a->{
return a+1;
};
System.out.println(func.apply(1));
//兩個參數 一個返回值
BiFunction<Integer, Integer,Boolean> func1 = (a,b)->{
return a+b;
};
}
2> Consumer< T >
- Consumer 消費型接⼝:有⼊參,⽆返回值
- 將 T 作爲輸⼊,不返回任何內容
調⽤⽅法:void accept(T t); - 沒有出參,一般用於打印(foreach() ),發送短信等消費動作
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public static void main(String[] args) throws Exception {
//foreach遍歷操作
List<String> list = Arrays.asList("aaa","bbb");
list.forEach(obj->{
System.out.println(obj);
});
Consumer<String> consumer = obj->{
System.out.println(obj);
System.out.println("調用consumer接口");
};
sendMsg("8888888",consumer);
}
public static void sendMsg(String phone,Consumer<String> consumer){
consumer.accept(phone);
}
3> Supplier : 供給型接⼝:⽆⼊參,有返回值
- Supplier: 供給型接⼝:⽆⼊參,有返回值
- T:出參類型;沒有⼊參
- 調⽤⽅法:T get()
- 泛型⼀定和⽅法的返回值類型是⼀種類型,如果需要獲得⼀個數據,並且不需要傳⼊參數,可
以使⽤Supplier接⼝,例如 ⽆參的⼯⼚⽅法,即⼯⼚設計模式創建對象,簡單來說就是 提供者
@FunctionalInterface
public interface Supplier<T> {
T get();
}
public static void main(String[] args) {
Student student = newStudent();
System.out.println(student.getName());
}
public static Student newStudent(){
Supplier<Student> supplier = ()-> {
Student student = new Student();
student.setName("默認名稱");
return student;
};
return supplier.get();
}
class Student{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4> Predicate : 斷⾔型接⼝:有⼊參,有返回值,返回值類型確定是boolean
- Predicate: 斷⾔型接⼝:有⼊參,有返回值,返回值類型確定是boolean
- T:⼊參類型;出參類型是Boolean
- 調⽤⽅法:boolean test(T t);
- ⽤途: 接收⼀個參數,⽤於判斷是否滿⾜⼀定的條件,過濾數據
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public static void main(String[] args) {
List<String> list =Arrays.asList("awewrwe","vdssdsd","aoooo","psdddsd");
List<String> results = filter(list,obj->obj.startsWith("a"));
System.out.println(results);
}
public static List<String> filter(List<String> list,Predicate<String> predicate) {
List<String> results = new ArrayList<>();
for (String str : list) {
if (predicate.test(str)) {
results.add(str);
}
}
return results;
}
- stream
- 通過將集合轉爲一種stream流的元素隊列,通過聲明性⽅式,
能夠對集合中的每個元素進⾏⼀系列並⾏或串⾏的流⽔線操作 - 元素是特定類型的對象,所以元素集合看作⼀種流, 流在管道中傳輸, 且可以在管道的節點上進⾏處理, ⽐如 排序,聚合,過濾等操作
- stream的操作可以分爲兩類,中間操作和結束操作
數據操作:
- 數據元素便是原始集合,如List、Set、Map等
- ⽣成流,可以是串⾏流stream() 或者並⾏流 parallelStream()
- 中間操作,可以是 排序,聚合,過濾,轉換等
- 終端操作,很多流操作本身就會返回⼀個流,所以多個操作可以直接連接起來,最後統⼀進
⾏收集
public static void main(String[] args) {
List<User> list = Arrays.asList(
// name,age
new User("張三", 11),
new User("王五", 20),
new User("王五", 91),
new User("張三", 8),
new User("李四", 44),
new User("李四", 44),
new User("李四", 44)
);
//foreach 迭代
list.forEach(user -> System.out.println(user));
System.out.println("stream:");
list.stream().forEach(user -> System.out.println(user));
//將流中的每⼀個元素 T 映射爲 R(類似類型轉換
List<String> mapUser= list.stream().map(obj->"用戶:"+obj).collect(Collectors.toList());
list.forEach(obj->System.out.println(obj));
mapUser.forEach(obj->System.out.println(obj));
//filter 使用該方法過濾 (Predicate)
list.stream().filter(user -> user.getAge()>50).forEach(user -> System.out.println(user));
//limit() 使用該方法截斷
list.stream().limit(3).forEach(user -> System.out.println(user));
//skip() 與limit互斥,使用該方法跳過元素
list.stream().skip(3).forEach(user-> System.out.println(user));
//sort 排序
List<User> users = list.stream().sorted(Comparator.comparing(User::getAge).reversed()).collect(Collectors.toList());
System.out.println(users);
//allmatch函數 檢查是否匹配所有元素,只有全部符合才返回true
List<String> list = Arrays.asList("springboot", "springcloud","redis","git", "netty", "java", "html", "docker");
boolean flag = list.stream().allMatch(obj->obj.length()>1);
System.out.println(flag);
//anyMatch函數 檢查是否⾄少匹配⼀個元素
List<String> list = Arrays.asList("springboot", "springcloud","redis","git", "netty", "java", "html", "docker");
boolean flag = list.stream().anyMatch(obj->obj.length()>18);
System.out.println(flag);
//Max Min
Optional<User> opu = list.stream().max(Comparator.comparing(User::getAge));
System.out.println(opu.get().getAge());
Optional<User> opu = list.stream().max((s1,s2)->Integer.compare(s1.getAge(),s2.getAge()));
System.out.println(opu.get().getAge());
Optional<User> minopu = list.stream().min((s1,s2)->Integer.compare(s1.getAge(),s2.getAge()));
System.out.println(minopu .get().getAge());
//並⾏流parallelStream
//順序輸出
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.stream().forEach(System.out::println);
//並⾏亂序輸出
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream().forEach(System.out::println);
//reduce 聚合操作
//reduce 聚合操作
Optional<T> reduce(BinaryOperator<T> accumulator);
int result = Stream.of(1,2,3,4,5).reduce(new
BinaryOperator<Integer>() {
@Override
public Integer apply(Integer item1, Integer item2) {
return item1 + item2;
}
}).get();
System.out.println("result:"+result);
//聚合統計collector
list.stream().collect(Collectors.toList());
System.out.println(list);
//Collectors.toMap()
//Collectors.toSet()
//Collectors.toCollection() :⽤⾃定義的實現Collection的數據結構收集
//Collectors.toCollection(LinkedList::new)
//Collectors.toCollection(CopyOnWriteArrayList::new)
//Collectors.toCollection(TreeSet::new)
//joining收集器
String str = Stream.of("springboot","springcloud","dubbo").collect(Collectors.joining(","));
System.out.println(str);
//partitioningBy分組
List<String> strlist = Arrays.asList("java", "springboot", "HTML5","nodejs","CSS3");
Map<Boolean, List<String>> result = strlist.stream().collect(partitioningBy(s -> s.length()>4));
System.out.println(result);
//groupingBy()分組
Map<Integer, List<String>> slist = strlist.stream().collect(Collectors.groupingBy(o -> o.length()));
System.out.println(slist);
//summarizingint 集合統計
IntSummaryStatistics summaryStatistics = strlist.stream().collect(Collectors.summarizingInt(String :: length));
System.out.println(summaryStatistics.getMax());
System.out.println(summaryStatistics.getAverage());
System.out.println(summaryStatistics.getCount());
}
jdk9新特性
- jdk9新增的接⼝私有⽅法
- 增強try-with-resource
//jdk7
OutputStream out = new FileOutputStream(filepath);
try(OutputStream temp = out;) {
temp.write((filepath+"學習jdk新特性").getBytes());
}catch (Exception e){
e.printStackTrace();
}
//jdk9
OutputStream out = new FileOutputStream(filepath);
try (out) {
out.write((filepath + "學習jdk新特性").getBytes());
} catch (Exception e) {
e.printStackTrace();
}
- 增強stream,增加部分方法
- takeWhile
有序的集合:從 Stream 中獲取⼀部分數據, 返回從頭開始的儘可能多的元素, 直到遇到第⼀個false結果,如果第⼀個值不滿⾜斷⾔條件,將返回⼀個空的 Stream
List<String> list = List.of("springboot","java","html","","git").stream().takeWhile(obj->!obj.isEmpty()).collect(Collectors.toList());
//⽆序集合,返回元素不固定,暫⽆⽆實際使⽤場景
Set<String> set =Set.of("springboot","java","html","","git").stream().takeWhile(obj->!obj.isEmpty()).collect(Collectors.toList());
- dropWhile 與 takeWhile相反,返回剩餘的元素
List<String> list =List.of("springboot","java","html","","git").stream().dropWhile(obj->!obj.isEmpty()).collect(Collectors.toList());
- 創建只讀集合
List<String> list = List.of("spring", "springboot", "springmvc");
list2.remove(0);
System.out.println(list);
jdk10新特性
- var作爲局部變量類型推斷標識符
public class Main {
// var作爲局部變量類型推斷標識符
public static void main(String[] args) throws Exception {
var strVar = "springboot";
System.out.println(strVar instanceof String);
//根據10L 推斷long 類型
var longVar = Long.valueOf(10l);
System.out.println(longVar instanceof Long);
//根據 true推斷 boolean 類型
var flag = Boolean.valueOf("true");
System.out.println(flag instanceof Boolean);
// 推斷 ArrayList<String>
var listVar = new ArrayList<String>();
System.out.println(listVar instanceof ArrayList);
// 推斷 Stream<String>
var streamVar = Stream.of("aa", "bb", "cc");
System.out.println(streamVar instanceof Stream);
if(flag){
System.out.println("這個是 flag 變量,值爲true");
}
for (var i = 0; i < 10; i++) {
System.out.println(i);
}
try (var input = new FileInputStream("validation.txt")) {
}
}
}
jdk11新特性
- httpclinet
private static final URI uri = URI.create("https://www.csdn.net/");
public static void main(String[] args) {
testHttp2();
}
/**
* get請求
*/
private static void testGet() {
// 創建連接兩種方式: var httpClient = HttpClient.newHttpClient();
var httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofMillis(5000)).build();
// 封裝請求參數(默認get請求)
HttpRequest request = HttpRequest.newBuilder().timeout(Duration.ofMillis(3000))
.header("key1", "v1")
.uri(uri).build();
try {
var response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* post請求
*/
private static void testPost() {
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(uri)
.POST(HttpRequest.BodyPublishers.ofString("phone=13113777337&pwd=1234567890"))
// from表單要用下面格式發送
//.header("Content-Type", "application/json")
//.POST(HttpRequest.BodyPublishers.ofString("{\"phone\":\"13113777337\",\"pwd\":\"1234567890\"}"))
.build();
try {
var response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 異步GET請求
*/
private static void testAsynGet() {
var httpClient = HttpClient.newBuilder().build();
var request =
HttpRequest.newBuilder().timeout(Duration.ofMillis(3000))
.header("key1", "v1")
.uri(uri).build();
try {
// 異步請求通過CompletableFuture實現
CompletableFuture<String> result = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
System.out.println(result.get());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 發送http2請求
* HTTP2協議的強制要求https,如果目標URI是HTTP的,則無法使用HTTP 2協議
*/
private static void testHttp2() {
var httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofMillis(3000))
.version(HttpClient.Version.HTTP_2)
.build();
var request = HttpRequest.newBuilder().timeout(Duration.ofMillis(3000))
.header("key1", "v1")
.uri(uri)
.build();
try {
var response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
System.out.println(response.version());
} catch (Exception e) {
e.printStackTrace();
}
}
jdk13
- switch增強
switch(i){
case 0 -> {
System.out.println("zero");
System.out.println("這是多⾏行行語句句");
}
case 1,11,111 -> System.out.println("one");
case 2 -> System.out.println("two");
default -> System.out.println("default");
}
- 多行文本塊
String html = "<html>\n" +
"<body>\n" +
"<p>Hello, world</p>\n" +
"</body>\n" +
"</html>\n";
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
"WHERE `CITY` = 'INDIANAPOLIS'\n" +
"ORDER BY `EMP_ID`, `LAST_NAME`;\n";
/**
* 新:不用對轉義字符進行轉義
*/
String html2 = """"
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
String query = """
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
WHERE `CITY` = 'INDIANAPOLIS'
ORDER BY `EMP_ID`, `LAST_NAME`;
""";
}