Java - 基础语法二

Java - 基础语法二

① 常用 API

Object 类

java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。

常用 API:
public String toString(): 返回该对象的字符串表示
public boolean equals(Object obj) : 与其他对象比较是否与当前对象“相等”

Objects 类

Object 类的 equals 方法容易抛出空指针异常,在 Objects 类中提供了 equals 方法优化这个问题。
public static boolean equals(Object a, Object b):判断两个对象是否相等。

public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}

Date 类

  • public Date():分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
  • public Date(long date):分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。
  • public long getTime() 把日期对象转换成对应的时间毫秒值。

DateFormat 类

java.text.DateFormat 是日期/时间格式化子类的抽象类,通过这个类完成日期和文本之间的转换。

格式规则

标识字母(区分大小写) 含义
y
M
d
H
m
s

Demo:

   /*从出生到现在经历多少天*/
    private static void daysFormBirthday() throws ParseException {
        System.out.println("输入生日:(格式 yyyy-MM-dd)");
        Scanner scanner = new Scanner(System.in);
        String birthday = scanner.next();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date birthdayDate = simpleDateFormat.parse(birthday);

        long birthdayDateTime = birthdayDate.getTime();
        long nowTime = new Date().getTime();
        long interval = nowTime - birthdayDateTime;
        long days = interval / 1000 / 60 / 60 / 24;
        System.out.println("从出生到现在已过" + days + "天!");
    }

Calendar 类

日历类。
Calendar类中提供很多成员常量,代表给定的日历字段:

字段值 含义
YEAR
MONTH 月(从0开始,可以+1使用)
DAY_OF_MONTH 月中的天(几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(周几,周日为1,可以-1使用)

System 类

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间。
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

数组的拷贝动作是系统级的,性能很高。System.arraycopy方法具有5个参数,含义分别为:

参数序号 参数名称 参数类型 参数含义
1 src Object 源数组
2 srcPos int 源数组索引起始位置
3 dest Object 目标数组
4 destPos int 目标数组索引起始位置
5 length int 复制元素个数

② Collection 集合

数组的长度是固定的,集合的长度是可变的
数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象,而且对象的类型可以不一致,在开发中一般当对象多的时候,使用集合存储。

Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。

迭代器 (Iterator)

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

泛型

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

受限泛型
泛型的上限

  • 格式类型名称 <? extends 类 > 对象名称
  • 意义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 <? super 类 > 对象名称
  • 意义只能接收该类型及其父类型

List

java.util.ArrayList 集合数据存储的结构是数组结构,元素增删满,查找快。日常开发常用的功能是查询数据和遍历数据,所以ArrayList是最常用的集合。
java.until.LinkedList集合数据存储的结构是链表结构,方便元素添加和删除的集合,但是查询慢。

Set

java.until.Set 接口和 java.util.List 接口一样,同样继承于 Collection 接口,它与 Collection 接口中的方法基本一致。Set接口中的元素无序,并且都会以某种规则保证存入的元素不会出现重复。
HashSet 能保证存储的元素唯一,但是无序。想要即唯一,又有顺序就需要用 java.util.LinkedHashSet

Collections

java.utils.Collections是集合工具类,用来对集合进行操作。部分方法:

  • public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。
  • public static void shuffle(List<?> list) 打乱顺序:打乱集合顺序。
  • public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
  • public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。

Comparator 接口代表一个比较器,其中:
public int compare(String o1, String o2); 比较其两个参数的顺序。
若按照升序排序,则 o1 < o2 (负数)
若按照降序排序,则 o1 > o1 (正数)

Demo:

private static void sortDemo() {
    ArrayList<Student> arrayList = new ArrayList<Student>();
    arrayList.add(new Student("邱学伟",18));
    arrayList.add(new Student("梁朝伟",30));
    arrayList.add(new Student("周星驰",24));
    arrayList.add(new Student("刘德华",28));
    arrayList.add(new Student("a",22));
    arrayList.add(new Student("b",22));
    // 按类内定义排序
//        Collections.sort(arrayList); 

    // 年龄降序
//        arrayList.sort(new Comparator<Student>() {
//            @Override
//            public int compare(Student o1, Student o2) {
//                return o2.getAge() - o1.getAge();//年龄降序
//            }
//        });

    // 年龄升序,相同年龄 按首字母
    arrayList.sort(new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            int result = o1.getAge() - o2.getAge();
            if (result == 0) {
                result = o1.getName().charAt(0) - o2.getName().charAt(0);
            }
            return result;
        }
    });

    for (Student student : arrayList) {
        System.out.println(student);
    }
}

Map

Map 中的集合,元素是成对存在的,每个元素由键和值两部分组成,通过键可以找到所对应的值;
Collection 中的集合称为单列集合,Map 中的集合称为双列集合;
Map 中的集合不能包含重复的键,值可以重复。每个键对应一个值。
Map接口中定义了很多方法,常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • boolean containsKey(Object key) 判断集合中是否包含指定的键。
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

Entry 键值对对象

Entry 将键值对的对应关系封装成了对象,即键值对对象,这样我们在遍历 Map 集合时,就可以从每一个键值对(Entry)对象中获取对应的键与其对应的值。

  • public K getKey():获取Entry对象中的键。
  • public V getValue():获取Entry对象中的值。

在Map集合中也提供了获取所有Entry对象的方法:

  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

当给 HashMap 中存放自定义对象时,如果自定义对象作为 Key 存在,这时要保证对象的唯一,则必须重写对象的 hashCodeequals 方法。
如果要保证 map 中存在的 key 和取出的顺序一致,可以使用 java.util.LinkedHashMap集合存放。

③ 线程

并发:指两个或多个事件在同一时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。

进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中的程序的执行,一个进程中至少有一个线程。一个进程是可以有多个线程的,这个应用程序也可称之为多线程程序。

简而言之:一个程序运行后至少有一个进程,一个进程可以包含多个线程。

线程的调度方式:

  1. 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  2. 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java 使用的是抢占式调度。
    1. CPU 使用抢占式调度模式在多个线程间进行着高速的切换,对于 CPU 的一个核而言,某个时刻,只能执行一个线程,而 CPU 在多个线程间切换速度相对我们感觉要快,看上去就像在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序的运行效率,让 CPU 的使用率更高。

线程同步的三种方式

  1. 同步代码块
  2. 同步方法
  3. 锁机制

同步代码块:

synchronized(同步锁) 
{
    /// 需要同步执行的代码
}

其中同步锁:

  1. 锁对象,可以是任意类型
  2. 多个线程对象,要使用同一把锁

线程之间的通信

wait/notify 就是线程间的一种协作机制。

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

线程池

一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。

④ Lambda

面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情;
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果不重视过程。

Lambda 的标准格式:

  • 一些参数
  • 一个箭头
  • 一段代码
(参数类型 参数名称) -> { 代码语句 }

小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
-> 是新引入的语法,代表指向动作
大括号内的语法与传统方法体要求基本一致

// Lambda , 年龄升序,相同年龄,首字母降序
arrayList.sort((Student s1, Student s2)->{
    int result = s1.getAge() - s2.getAge();
    if (result == 0) {
        result = s2.getName().charAt(0) - s1.getName().charAt(0);
    }
    return result;
});

省略规则:
小括号内参数的类型可以省略;
如果小括号内有且仅有一个参数,则小括号可以省略
如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return 关键字及语句分号。

使用 Lambda 注意:

  1. 使用 Lambda 必须有接口,且要求接口中有且仅有一个抽象方法
  2. 使用 Lambda 必须具有上下文推断。

Java 中的 Lambda 可以被当做是匿名内部类的替代品。
如果函数的参数是一个函数式接口类型,就可以使用 Lambda 表达式进行替代,使用 Lambda 表达式作为方法参数,其实就是使用函数式接口作为方法参数。
类似的,如果一个方法的返回值是一个函数式接口,那么也可以直接返回一个 Lambda 表达式。

常用函数式接口

1. Supplier 接口

java.util.function.Supplier<T> 接口仅包含一个无参方法 T get()。用以获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的 Lambda 表达式需要“对外提供”一个符合泛型类型的对象数据。

private static void demo2() {
    String s1 = "极客学伟";
    String s2 = "科技有限公司";
    String s3 = getString(()-> s1 + s2 );
    System.out.print(s3);
}
private static String getString(Supplier<String> function) {
    return function.get();
}

2. Consumer 接口

java.util.function.Consumer<T> 接口与 Supplier 相反,它不是产生一个数据,而是消费一个数据,其数据类型由泛型决定。

抽象方法 accept
private static void demo3() {
    consumerString((num)-> System.out.println("打印:" + num));
}
private static void consumerString(Consumer<Integer> function) {
    function.accept(1024);
}
默认方法 andThen

消费数据时,首先做一个操作,然后再做一个操作,实现组合。

private static void demo4() {
    consumerAndThenString(s -> System.out.println(s.toLowerCase()) ,s -> System.out.println(s.toUpperCase()));
}
private static void consumerAndThenString(Consumer<String> f1, Consumer<String> f2) {
    f1.andThen(f2).accept("Hello World!");
}
private static void demo5() {
    ArrayList<Student> students = new ArrayList<Student>(Arrays.asList(new Student("极客",18) , new Student("学伟",28)));
    userInfoLog(students,student -> System.out.print("姓名:" + student.getName()) , student -> System.out.print(" 年龄:" + student.getAge() + ";\n"));
}
private static void userInfoLog(ArrayList<Student> s, Consumer<Student> s1, Consumer<Student> s2) {
    for (Student student : s) {
        s1.andThen(s2).accept(student);
    }
}

3. Predicate 接口

对某种类型的数据进行判断,从而得到一个 boolean 值的结果,可以使用 java.util.function.Predicate<T> 接口。

抽象方法:test

用于条件判断的场景:

private static void demo6() {
    Student s = new Student("极客学伟",27);
    adjustMethod(student -> student.getName().equals("极客学伟") , s);
}
private static void adjustMethod(Predicate<Student> predicate, Student s) {
    boolean isMe = predicate.test(s);
    System.out.println("名字叫" + s.getName() + "的" + (isMe ? "很帅" : "一点点丑"));
}
默认方法:and

逻辑判断中将两个 Predicate 条件使用“与”逻辑连接起来实现 “并且”的效果

默认方法:or

逻辑判断中将两个 Predicate 条件使用“或”逻辑连接起来实现 “或者”的效果

默认方法:negate

逻辑判断中将两个 Predicate 条件使用“非”逻辑连接起来实现 “取反”的效果

Demo:

private static void demo7() {
    String[] girls = {"邓紫棋,26", "韩红,46", "韩雪,36", "杨紫,29", "慧慧,28", "古力娜扎,32", "AngelaBaby,28"};
    /// 筛选 二十来岁名字是两个字的女神
    ArrayList<String> result = predicateDemo(girls, g -> Integer.parseInt(g.split(",")[1]) < 30, g -> g.split(",")[0].length() < 3);
    result.forEach(System.out::println);
}
// 筛选满足两个条件的数组
private static ArrayList<String> predicateDemo(String[] girls, Predicate<String> p1, Predicate<String> p2) {
    ArrayList<String> list = new ArrayList<>(girls.length);
    for (String girl : girls) {
        if (p1.and(p2).test(girl)) {
            list.add(girl);
        }
    }
    return list;
}

4. Function 接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者成为前置条件,后者称为后置条件。

抽象方法:apply

R apply(T t) 根据类型 T 的参数获取类型 R 的结果。

⑤ Stream

Java 中的流思想类似于工厂车间的“生产流水线”。
Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。Java 中的 Stream 并不会存储元素,而是按需计算。
  • 数据源 流的来源,可以是集合数组等。

基础特性:

  • Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。
  • 内部迭代:以前对集合遍历都是通过 Iterator 或者增强 for 的方式,显式的在集合外部进行迭代,这叫外部迭代。Stream 提供内部迭代的方式,流可以直接调用遍历方法。

使用步骤:
获取一个数据源 -> 数据转换 -> 执行操作获取想要的结果。每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

demo:


/// 筛选 二十来岁名字是两个字的女神
private static void streamDemo1() {
    String[] girls = {"邓紫棋,26", "韩红,46", "韩雪,36", "杨紫,29", "慧慧,28", "古力娜扎,32", "AngelaBaby,28"};
    Arrays.stream(girls).filter(s -> parseInt(s.split(",")[1]) < 30).filter(s -> s.split(",")[0].length() < 3).forEach(s -> System.out.println(s));
}

获取 Stream

Collection

java.util.Collection 接口中加入了 default 方法 stream 用来获取流,所以其所有实现类均可直接获取流。

Map

java.util.Map 接口不是 Collection 的子接口,且其 K-V 数据结构不符合流元素的单一特征,所以获取对应的流需要 Key、Value 或 entry 等情况。

数组

数组对象不可能添加默认方法,Stream 接口中提供了静态方法 of,用于生成数组的 stream

private static void arrayStreamDemo() {
    String[] array = {"张三", "李四", "王五", "赵六"};
    Stream<String> arrayStream = Stream.of(array);
    arrayStream.forEach(System.out::println);
}

Stream 常用方法

逐一处理:forEach

接收一个 Consumer 接口函数,会将每一个流元素交给该函数处理。

过滤:filter

将一个流转换成另一个子集流

映射:map

将流中的元素映射到另一个流中

统计个数:count

获取流中的元素个数

提取前几个:limit

对流进行截取,只取前 n 个

跳过前几个:skip

跳过流中的前 n 个元素

组合:concat

将两个流合并成为一个流