Java基础

Java基础、面向对象编程

Java基础

Java环境变量

1
2
3
4
JAVA_HOME=jdk安装路径
JRE_HOME=jre安装路径
PATH= .;%JAVA_HOME%\bin;%JRE_HOME%\bin
CLASSPATH=.;%JAVA_HOME%\lib;%JRE_HOME%\lib

数组

1
2
3
4
5
6
7
// 创建数组的三种方式
int[] arr1 = new int[3];
int[] arr2 = new int[]{1, 2, 3};
int[] arr3 = {1, 2, 3};

// 获取数组长度
System.out.println(arr1.length);

变量

变量分类

  • 成员变量:类变量(static)、实例变量
  • 局部变量:形参,方法体、代码块中声明的变量

存储位置

  • 局部变量:栈
  • 实例变量:堆
  • 类变量:方法区

变量匹配原则

  • 就近原则

形参传递机制

  • 基本数据类型:传递数据值,即创建一个新的变量,并传递数据值。
  • 引用数据类型:传递引用,即创建一个引用变量,并传递引用的地址。

可变参数

用于传递多个相同类型的参数,其实就是一个数组

如果形参还有其它参数,则可变参数必须放在参数列表的最后

  • 定义:修饰符 返回值类型 方法名(参数类型... 形参名) {}
  • 例子:Arrays类的asList方法,就用到了可变参数。
1
2
3
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}

final 关键字

  • final: 不可改变。可以用于修饰类、方法和变量。
    • 类:被修饰的类,不能被继承。
    • 方法:被修饰的方法,不能被重写。
    • 变量:被final修饰的变量只能被赋值一次,不能更改;若是引用类型则不可修改其引用。
1
2
3
修饰符 final class 类名 {}
修饰符 final 返回值类型 方法名(参数列表){}
final 类型 var = vaule

Jvm内存分区

  1. 栈(stack): 存放局部变量, 方法运行时使用的内存
  2. 堆(Heap): 存储对象或者数组,new来创建的,都存储在堆内存。
    1. 堆内存数据的默认值: 0, 0.0, ‘\u0000’, false, null(引用)
  3. 方法区(Method Area): 存储已被虚拟机加载的类信息、常量、静态变量
  4. 本地方法栈: 为JVM使用native本地方法而准备的
  5. 程序计数器(Program Counter Register):记录下一条jvm指令的执行地址
    • 通过程序计数器,来恢复线程的正确执行位置

面向对象

继承

继承提高了代码的维护性、拓展性,但增加了代码的耦合度(更改父类代码可能会导致子类发生变化)

语法class 子类名 extends 父类名 {}

注意:

  • 私有成员可以会被子类继承,但对子类来说是不可见的

  • 成员变量不能被覆盖:子类定义了父类中存在的变量,则类中会有两个变量,一个是子类的一个是父类

接口与抽象类

定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface 接口名称 {
// 成员变量:是一个常量, 定义时必须赋值
[public static final] 数据类型 常量名 = 数据值;
// 抽象方法
[public abstract] void method();
// 默认方法: 解决接口升级的问题, 可以被实现类重写
[public] default void method() {}
// 静态方法
[public] static void method() {}
// 私有方法:解决多个默认/静态方法中的代码重复的问题,不能被接口的实现类使用
private void void method() {}
private static void method() {}
}

定义抽象类

1
2
3
4
修饰符 abstract class 类名 {
// 抽象方法
修饰符 abstract 返回值类型 方法名 (参数列表);
}

接口与抽象类的区别

  1. 接口的方法默认是 public,所有方法在接口中不能有实现(jdk8 可以有默认方法和静态方法,jdk9可以有私有方法 ),而抽象类可以有非抽象的方法。
  2. 接口中除了 static、final 变量,不能有其他变量,而抽象类中则不一定。
  3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。
  4. 接口方法默认修饰符是 public,抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!)。
  5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。

注意:

  • 接口没有静态代码块和构造方法
  • 类与类之间是单继承的,类与接口之间时多实现的,接口与接口之间是多继承的
  • 实现多个接口,发生默认方法冲突时,必须要实现该默认方法
  • 父类继承的方法与实现的接口的默认方法冲突,子类优先使用父类继承的方法

多态

父类类型作为方法形参,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

  • 多态中的转型

    • 向上转型:子到父(安全)

      • 左边可以对象、也可以是接口
      • 弊端:不能访问子类特有的方法,需要向下转型
    • 向下转型:父到字(加强制转换)

      • 使用instanceof判断对象是由哪个类实例化的

        1
        2
        3
        if (对象 instanceof 类) {
        System.out.println("是");
        }

重写Override

  • 不能被重写的方法
    • final方法
    • 静态方法
    • 子类中不可见方法,如私有方法
  • 对象的多态性
    • 子类重写了父类的方法,通过子类对象调用的一定子类重写过的方法。
    • 非静态方法默认调用对象是this, 而this对象在构造器或者说是<init>方法中指的是正在创建的对象
      • 如:创建子类对象时,调用父类<init>方法,此方法中的this对象就是这个正在的子类对象。

类初始化

  • 创建实例对象前需要先加载并初始化类
    • main方法所在的类需要先加载和初始化
  • 初始化字类前需要初始化父类
  • 类的初始化就是执行<clinit>()方法, 只执行一次
    • <clinit>方法由静态类变量显示赋值代码静态代码块组成,先后顺序由类中代码顺序决定。

实例初始化

  • 实例初始化就是执行<init>()方法
    • 每次创建实例,都会调用构造器对应的<init>方法。
    • <init>方法首行是super方法,即执行父类的<init>方法。
    • 然后是实例变量显示赋值代码非静态块,顺序取决与类中代码的顺序。
    • 最后是对应的构造器代码。

内部类

将一个类定义在另一个类里面。在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。

内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的class文件,文件名外部类$内部类.class

成员内部类

将一个类定义在另一个类里面,方法外

1
2
3
4
class 外部类 {
class 内部类{
}
}
  • 内部类可以直接访问外部类的成员,包括私有成员。

  • 外部类要访问内部类的成员,必须要建立内部类的对象。

    1
    外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
  • 访问外部类同名变量:外部类名.this.外部变量名

静态内部类

将一个静态类定义在另一个类里面,方法外

特点:

  • 不会随着外部类加载而加载,要在用到的时候才会单独加载
  • 外部类中,可以通过内部类名.的方式访问内部类成员,包括私有的

局部内部类

将一个类定义在另一类的方法中。若内部类需要访问方法所在的局部变量,那么这个局部变量必须是常量。因为方法运行结束后,局部变量就会消失,而在方法中new出来的对象不会立刻消亡。

1
2
3
4
5
6
7
8
9
10
11
public class Outer {

public class method() {
int num = 10; // 必须是final的,JDK8后final可省略

class Inner {
System.out.println(num);
};
new Inner().
}
}

匿名内部类

匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的匿名的子类对象。

  • 匿名对象只能在创建对象时使用一次,而 匿名内部类不是。

  • 匿名内部类必须继承一个父类或者实现一个父接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    父类名或者接口名 obj new 父类名或者接口名() {
    // 方法重写
    @Override
    public void method() {}
    };
    obj.method();

    // 匿名对象
    new 父类名或者接口名() {...}.method();

包装类

Byte, Short, Integer, Long, Float, Double, Character, Boolean

装箱拆箱

  • 装箱:从基本类型转换为对应的包装类对象。

  • 拆箱:从包装类对象转换为对应的基本类型。

    • 使用包装类中的valueof方法

      1
      2
      Integer a = new Integer(4);			// 装箱
      Integer b = Integer.valueOf(4); // 拆箱
  • 自动装箱/自动拆箱 (JDK 1.5)

    1
    2
    Integer i = 1		// 自动装箱
    i = i + 1 // 先拆箱,再装箱

缓存机制

当使用自动装箱的时候,就会触发Java的缓存机制。这个时候java虚拟机会创建一系列的整数并且缓存到一个数组中以便直接使用,这就是缓存策略。

Byte,Short,Long 有固定范围: -128 到 127。

对于 Character, 范围是 0 到 127。

  • Long源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}

private static class LongCache {
private LongCache(){}

static final Long cache[] = new Long[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}

字符串转基本类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型

如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

  • public static byte parseByte(String s):string – > byte

  • public static short parseShort(String s):string –> short

  • public static int parseInt(String s):string –> int

  • public static long parseLong(String s):string –> long

  • public static float parseFloat(String s):string –> float

  • public static double parseDouble(String s):string –> double

  • public static boolean parseBoolean(String s):string –> boolean

    1
    2
    3
    String s1 = "123";
    byte b = Byte.parseByte(s1);
    System.out.println(b); // 123

基础类型转字符串

  1. 使用’+’: 基础类型 + “”
  2. 包装类的静态方法tostring
  3. String类的静态方法valueof(), 传入任意类型

常用类

Scanner 类

一个可以解析基本类型和字符串的简单文本扫描器

1
2
3
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
String s = sc.next();

Random 类

此类的实例用于生成伪随机数。

1
2
Random rand = new Random();
int num = rand.nextInt(10); // [0, 10)

ArrayList 类

ArrayList对象不能存储基本类型,只能存储引用类型的数据

  • public boolean add(E e) :将指定的元素添加到此集合的尾部
  • public E remove(int index) :移除此集合中指定位置上的元素
  • public E get(int index) :返回此集合中指定位置上的元素
  • public int size() :返回此集合中的元素数

String 类

String 类代表字符串。字符串是常量,它们的值在创建之后不能更改,底层是一个被final修饰的字节数组。

  • public int length () :返回此字符串的长度。
  • public String concat (String str) :将指定的字符串连接到该字符串的末尾。
  • public char charAt (int index) :返回指定索引处的 char值。
  • public int indexOf (String str) :返回指定字符串第一次出现在该字符串内的索引。
  • public String substring (int beginIndex) :从beginIndex开始截取字符串到字符串结尾。
  • public String substring (int beginIndex, int endIndex) :含beginIndex,不含endIndex。
  • public String[] split(String regex) :将此字符串按照给定的规则拆分为字符串数组。
  • public boolean endsWith(String suffix): 测试此字符串是否以指定的后缀结束。
  • public boolean StartsWith(String prefix): 测试此字符串是否以指定的前缀开始。
  • public boolean matches(String regex) : 正则匹配
  • replace
  • replaceAll
  • intern():从常量池中找与这个字符串相等的串,找到则返回,无则在池中新建

Arrays 类

java.util.Arrays 此类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法,

  • public static String toString(int[] a) :数组转字符串
  • public static void sort(int[] a) :按升序进行排序
  • public static T[] copyOf(T[] original, int newLength):返回一个新数组

Math 类

java.lang.Math 类包含用于执行基本数学运算的方法, 其所有方法均为静态方法

  • public static double abs(double a) :返回 double 值的绝对值。
  • public static double ceil(double a) :返回大于等于参数的最小的整数。
  • public static double floor(double a) :返回小于等于参数最大的整数。
  • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)

Object 类

java.lang.Object类是Java语言中的根类,即所有类的父类。

tostring 方法

  • public String toString():返回该对象的字符串表示。
    • 默认该字符串内容就是对象的类型+@+内存地址值

equals 方法

  • public boolean equals(Object obj):比较其他某个对象是否与此对象“相等”。
    • 不能容忍空指针, 容易抛出空指针异常
  • 默认为地址比较
    • 如果没有覆盖重写equals方法,那么Object类中默认进行==运算符的对象地址比较,只要不是同一个对象,结果必然为false。
  • 对象内容比较
    • 如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。

hashCode 方法

hashCode方法返回对象的哈希值,是一个十进制整数,由系统随机给出(实际上就是对象的逻辑地址值)。

哈希冲突:两个元素不同,但有相同的哈希值。

Objects 类

java.util.Objects类, 是JDK7中添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的)。

Date 类

java.util.Date类 表示特定的瞬间,精确到毫秒。

Date对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。由于我们处于东八区,所以我们的基准时间为1970年1月1日8时0分0秒。

  • public Date():使用无参构造,可以自动设置当前系统时间的毫秒时刻

  • public Date(long date):指定long类型的构造参数,可以自定义毫秒时刻。

  • public long getTime() 把日期对象转换成对应的时间毫秒值。

  • Date类对Object类中的toString方法进行了覆盖重写, 返回一个时间字符串(Thu Jan 01 08:00:00 CST 1970)

DateFormat 类

java.text.DateFormat 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。常用的子类java.text.SimpleDateFormat

  • public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat对象

    • 参数pattern是一个字符串,代表日期时间的自定义格式。

    • 格式规则: 年月日时分秒 – y M d H m s

      1
      DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  • public String format(Date date):将Date对象格式化为字符串。

  • public Date parse(String source):将字符串解析为Date对象。

  • 练习:计算一个人已经出生了多少天

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    System.out.println("请输入出生日期(yyyy-MM-dd): ");
    String birthdayString = new Scanner(System.in).next();

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    Date birthdayDate = df.parse(birthdayString);
    Date currentDate = new Date();

    long delta = currentDate.getTime() - birthdayDate.getTime();
    if (delta < 0) {
    System.out.println("尚未出生!");
    } else {
    System.out.println("已出生" + delta / (1000*60*60*24) + "天");
    }

Calendar类

java.util.Calendar是日历类, 是一个抽象类,在Date后出现,替换掉了许多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。

  • public static Calendar getInstance():使用默认时区和语言环境获得一个日历, 是Calendar子类的一个静态方法

    1
    Calendar cal = Calendar.getInstance();
  • public int get(int field):返回给定日历字段的值。

    日历字段(field) 含义
    YEAR
    MONTH 月(从0开始,一月为0)
    DAY_OF_MONTH / DATE 月中的天(几号)
    HOUR 时(12小时制)
    HOUR_OF_DAY 时(24小时制)
    MINUTE
    SECOND
    DAY_OF_WEEK 周中的天(从周日开始,周日为1)
  • public void set(int field, int value):将给定的日历字段设置为给定值。

  • public abstract void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。

  • public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象

  • 注意:

    • 西方星期的开始为周日,中国为周一。
    • Calendar类中,月份的表示是以0-11代表1-12月
    • 日期是有大小关系的,时间靠后,时间越大。

System 类

java.lang.System类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作。

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。
    • src, dest: 源数组, 目标数组
    • srcPos, destPos: 源数组,目标数组起始位置索引
    • length: 复制元素个数

StringBuilder

java.lang.StringBuilder又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。它的内部拥有一个数组用来存放字符串内容,进行字符a串拼接时,直接在数组中加入新内容。(默认16字符空间,超过自动扩充)

stringBuilder单线程使用, stringBuffer多线程使用

  • public StringBuilder():构造一个空的StringBuilder容器。
  • public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去。
  • public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身。
  • public String toString():将当前StringBuilder对象转换为String对象。

Collection 集合

java.util.Collection是单列集合类的根接口,有两个重要的子接口,分别是java.util.Listjava.util.Set

  • List的特点是元素有序、元素可重复。
    • 实现类有java.util.ArrayListjava.util.LinkedList
  • Set的特点是元素无序,而且不可重复。
    • 主要实现类有java.util.HashSetjava.util.TreeSet

Collection集合通用操作

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

  • 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(): 把集合中的元素,存储到数组中。

List 集合

java.util.List接口继承自Collection接口,是单列集合的一个重要分支。

特点:有序、可重复。

ArrayList 集合

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

LinkedList 集合

java.util.LinkedList集合数据存储的结构是链表结构, 是一个双向链表。元素增删快,查找慢

在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e):将元素推入此列表所表示的堆栈。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

Vector 集合

Vector是最早期的集合,是同步的,现在基本不再使用。

Set 集合

java.util.Set接口和java.util.List接口一样,同样继承自Collection接口。

List不同的是,Set元素无序,不能重复。

HashSet 集合

java.util.HashSetSet接口的一个实现类,HashSet底层的是一个java.util.HashMap

HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。

如何保证元素唯一的:hashCodeequals方法

  • 先比较哈希值,相同再使用equals方法比较。
  • 所以使用哈希表存储自定义类型时,必须要重写hashCodeequals方法

LinkedHashSet

java.util.LinkedHashSet也是Set接口的实现类。

与HashSet的不同在于增加了一个链表,用来存储元素存储的顺序

Iterator迭代器

Iterator接口也是Java集合中的一员,主要用于迭代访问(即遍历)Collection中的元素。

迭代:Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,如果还有就再取出出来。一直把集合中的所有元素全部取出。

操作

  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

  • public E next():返回迭代的下一个元素。

    • 集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException

      1
      2
      3
      4
      5
      6
      7
      8
      9
      Collection<String> coll = new ArrayList<>();
      coll.add("aaa");
      coll.add("bbb");
      coll.add("ccc");

      Iterator iter = coll.iterator();
      while (iter.hasNext()) {
      System.out.println(iter.next());
      }

for-each循环

JDK1.5,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能进行增删操作

格式

1
for(元素的数据类型  变量 : Collection集合or数组) {}

例子

1
2
3
for (String s : coll) {
System.out.println(s);
}

Map 集合

java.util.Map中的集合,元素是成对存在。每个元素由键(key唯一)与值两部分组成,通过键可以找对所对应的值。需要重写键的hashCode()方法、equals()方法

  • public V put(K key, V value): 把指定的键与值添加到Map集合中。
    • key不重复,返回null;
    • key重复更新value,返回原来的value
  • public V remove(Object key): 把指定的键元素删除,返回被删除元素的值。
  • 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集合)。
    • Map.Entry:Map中的一对键和值又称做Map中的一个Entry(项)Entry将键值对的对应关系封装成了对象。即键值对对象
      • getKey():获取键值对的key
      • getValue():获取键值对的value

HashMap / LinkedHashMap

HashMap<k, v>LinkedHashMap<k, v>是Map下重要的两个子类。具体实现可类比Set接口的子类HashSetLinkedHashSet

  • 可以存储<null, null>

Hashtable

Hashtable和Vector集合一样,在jdk1.2后被取代了,但其子类Properties集合依然活跃在历史的舞台。Properies是一个唯一与IO流相结合的集合。

  • 同步单线程, 底层是一个哈希表
  • Hashtable 不能存储<null, null>

遍历Map集合

键找值的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map<Integer, String> map = new HashMap<>();
map.put(1, "张三");
map.put(2, "李四");

// for-each 遍历
for (Integer key : map.keySet()) {
System.out.println("[" + key + ", " + map.get(key) + "]");
}

// 手工遍历
Set<Integer> keySet = map.keySet();
Iterator iter = keySet.iterator();
while (iter.hasNext()) {
Integer key = (Integer) iter.next();
System.out.println("[" + key + ", " + map.get(key) + "]");
}

键值对方法是:使用Map.Entry对象遍历

1
2
3
for (Map.Entry<Integer, String> m : map.entrySet()) {
System.out.println("[" + m.getKey() + ", " + m.getValue() + "]");
}

案例:统计每个字符出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String s = "aaaBBBBBBBCCcccccddd";
// key:字符, value:字符出现的次数
Map<Character, Integer> map = new HashMap<>();

for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (map.containsKey(c))
map.put(c, map.get(c) + 1);
else
map.put(c, 1);
}

for (Character c : map.keySet()) {
System.out.println(c + ": " + map.get(c));
}

泛型

在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。

  • 泛型是一种未知类型。在创建对象时确定具体的类型,没有指定泛型时默认为Object类型。

  • 泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

泛型类

语法:修饰符 class 类名<代表泛型的变量> {}

1
2
3
4
5
6
7
8
class ArrayList<E>{ 
public boolean add(E e){ }
public E get(int index){ }
....
}

// 创建对象时确定泛型
ArrayList<String> list = new ArrayList<String>();

泛型方法

语法:修饰符 interface接口名<代表泛型的变量> {}

1
2
3
4
5
6
7
8
public class Demo {
public <E> void show(E e) {
return e;
}
}

// 调用方法时确认泛型的类型
String s = new Demo().show("hello world")

泛型接口

语法:修饰符 <代表泛型的变量> 返回值类型 方法名(参数) {}

1
2
3
4
5
6
7
8
9
10
11
public interface MyGenericInterface<E>{
public abstract void add(E e);

public abstract E getE();
}

// 1. 定义类时确定泛型的类型
public class MyImp1 implements MyGenericInterface<String> {}
// 2. 始终不确定泛型的类型,直到创建对象时,确定泛型的类型
public class MyImp2<E> implements MyGenericInterface<E> {}
MyImp2<String> obj = new MyImp2<>();

泛型通配符

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

为什么不用<Object>

  • 因为泛型不存在继承关系, Collection<Object> list = new ArrayList<String>()这种是错误的。
1
2
3
4
5
6
7
8
void showList(ArrayList<Object> list) {}	// 只能接受List<Object>类型的
void showList(ArrayList<?> list) {} // 接受任意List

List<String> list1 = new ArrayList<>();
list1.add("hello");

List<?> list2 = list1; // 接受list1, 但不能使用list2往该集合中存放数据
list2.add("world"); // error

高级用法 – 受限泛型

  • 泛型的上限
    • 格式类型名称 <? extends T> coll
    • 意义用于接收T及其子类, 不能添加数据,用于读取操作。如:coll.get(0)
  • 泛型的下限
    • 格式类型名称 <? super T> 对象名称
    • 意义用于取出T及其父类型,可以添加数据。如:coll.add(new T)

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):将集合中元素按照默认规则排序(升)

    • 不可直接进行比较的类型,需要实现comparable接口,重写compareTo方法
  • public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。

    • 需要实现java.util.Comparator接口(比较器), 重写比较方法

      1
      int compare(T o1, T o2) {}
    • 然后再将new出来的比较器对象传给sort方法

Comparable和Comparator的区别

  • Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序。
  • Comparator:强行对某个对象进行整体排序。可以将Comparator 传递给sort方法,从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

jdk9 新特性

Java 9,添加了几种集合工厂方法,更方便创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例

注意

  • of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,比如 HashSet,ArrayList等
  • Set, Map接口在调用of方法时,不能由重复元素
  • 修改of()方法返回的集合,会报java.lang.UnsupportedOperationException异常

例子

1
2
3
List<Integer> list = List.of(1, 2, 3, 4, 5);		// 返回的list是不可变的
Set<Integer> set = Set.of(1, 2, 3, 4, 5);
Map<Integer, String> map = Map.of(1, "a", 2, "b");

 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×