Java 总结二

Java核心类

字符串和编码

  • Java字符串String是不可变对象;

  • 字符串操作不改变原字符串内容,而是返回新字符串

  • 常用的字符串操作:提取子串、查找、替换、大小写转换等;

  • Java使用Unicode编码表示Stringchar

  • 转换编码就是将**Stringbyte[]转换,需要指定编码**;转换为byte[]时,始终优先考虑UTF-8编码。

  • 在Java中,String是一个引用类型,它本身也是一个class

  • Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。

  • 结论:两个字符串比较,必须总是使用equals()方法。要忽略大小写比较,使用equalsIgnoreCase()方法。==是用来比较两个实例是否指向相同的内存地址

  • new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组。从String的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用

  • 为了统一全球所有语言的编码,全球统一码联盟发布了Unicode编码,它把世界上主要语言都纳入同一个编码

  • 因为英文字符的Unicode编码高字节总是00,包含大量英文的文本会浪费空间,所以,出现了UTF-8编码,它是一种变长编码,用来把固定长度的Unicode编码变成1~4字节的变长编码。通过UTF-8编码,英文字符'A'UTF-8编码变为0x41,正好和ASCII码一致,而中文'中'UTF-8编码为3字节0xe4b8ad

// 实际上字符串在String内部是通过一个char[]数组表示的
String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

// 是否包含字串
"Hello".contains("ll"); // true
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true

// 提取子串
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); // "ll"

// 使用trim()方法可以移除字符串首尾空白字符
"  \tHello\r\n ".trim(); // "Hello"
"\u3000Hello\u3000".strip(); // "Hello"

// 替换子串
String s = "hello";
s.replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"

// 分隔字符串
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

// 	拼接字符串
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

// 格式化字符串
public class Main {
    public static void main(String[] args) {
        String s = "Hi %s, your score is %d!";
        System.out.println(s.formatted("Alice", 80));
        System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
    }
}

// 类型转换 重要 要把任意基本类型或引用类型转换为字符串
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
// 要把字符串转换为其他类型,就需要根据情况
int n1 = Integer.parseInt("123");
boolean b1 = Boolean.parseBoolean("true"); 
// String 和 char[] 转换
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs);


// 注意浅拷贝
public class Main {
    public static void main(String[] args) {
        int[] scores = new int[] { 88, 77, 51, 66 };
        Score s = new Score(scores);
        s.printScores(); // [88, 77, 51, 66]
        scores[2] = 99;
        s.printScores(); // [88, 77, 99, 66]
    }
}

class Score {
    private int[] scores;
    public Score(int[] scores) {
        this.scores = scores;
    }
   /*
   	深拷贝
   	public Score(int[] scores) {
        this.scores = Arrays.copyOf(scores, scores.length);
        this.scores = scores.clone();
    }
    */
    public void printScores() {
        System.out.println(Arrays.toString(scores));
    }
}

//  转换字符编码
byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换

StringBuilder

  • String添加字符在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。这样,绝大部分字符串都是临时对象,不但浪费内存,还会影响GC效率。

  • 为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象

  • 定义的append()方法会返回this,这样,就可以不断调用自身的其他方法。可以链式的添加

  • 对于普通的字符串+操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。

  • StringBuffer,这是Java早期的一个StringBuilder的线程安全版本,它通过同步来保证多个线程操作StringBuffer也是安全的,但是同步会带来执行速度的下降。

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',');
    sb.append(i);
}
String s = sb.toString();

StringJoiner

  • 主要用来用分隔符拼接数组,可以指定开头和结尾
  • String还提供了一个静态方法join(),这个方法在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()更方便:
import java.util.StringJoiner;
public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sj = new StringJoiner(", ", "Hello ", "!");
        for (String name : names) {
            sj.add(name);
        }
        System.out.println(sj.toString());// Hello Bob, Alice, Grace!
    }
}

String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);

包装类型

  • Java的数据类型分两种:

    • 基本类型:byteshortintlongbooleanfloatdoublechar
    • 引用类型:所有classinterface类型
  • 引用类型可以赋值为null,表示空,但基本类型不能赋值为null

  • 这种直接把int变为Integer的赋值写法,称为自动装箱(Auto Boxing),反过来,把Integer变为int的赋值写法,称为自动拆箱(Auto Unboxing)。注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException

  • 对两个Integer实例进行比较要特别注意:绝对不能用==比较,因为Integer是引用类型,必须使用equals()比较:

  • 整数和浮点数的包装类型都继承自Number;包装类型提供了大量实用方法。

booleanjava.lang.Boolean
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
charjava.lang.Character
// 互相转换
public class Main {
    public static void main(String[] args) {
        int i = 100;
        // 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
        Integer n1 = new Integer(i);
        // 通过静态方法valueOf(int)创建Integer实例:
        Integer n2 = Integer.valueOf(i);
        // 通过静态方法valueOf(String)创建Integer实例:
        Integer n3 = Integer.valueOf("100");
        // Integer 转为 int
        System.out.println(n3.intValue());
    }
}

// 自动装箱
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

/*
因为Integer.valueOf()可能始终返回同一个Integer实例,因此,在我们自己创建Integer的时候,以下两种方法:
方法1:Integer n = new Integer(100);
方法2:Integer n = Integer.valueOf(100);
方法2更好,因为方法1总是创建新的Integer实例,方法2把内部优化留给Integer的实现者去做,即使在当前版本没有优化,也有可能在下一个版本进行优化。
我们把能创建“新”对象的静态方法称为静态工厂方法。Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。
*/


public class Main {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true
    }
}
/*
==比较,较小的两个相同的Integer返回true,较大的两个相同的Integer返回false,
这是因为Integer是不变类,编译器把Integer x = 127;自动变为Integer x = Integer.valueOf(127);,
为了节省内存,Integer.valueOf()对于较小的数,始终返回相同的实例,因此,==比较“恰好”为true,
但我们绝不能因为Java标准库的Integer内部有缓存优化就用==比较,必须用equals()方法比较两个Integer。
*/


JavaBean

  • 有很多class的定义都符合这样的规范:

    • 若干private实例字段;
    • 通过public方法来读写实例字段。
  • JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中。

  • 点击右键,在弹出的菜单中选择“Source”,“Generate Getters and Setters”,在弹出的对话框中选中需要生成gettersetter方法的字段,点击确定即可由IDE自动完成所有方法代码。

  • 使用Introspector.getBeanInfo()可以获取属性列表。

// 如果读写方法符合以下这种命名规范: 那么这种class被称为JavaBean:
// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)
/*
上面的字段是xyz,那么读写方法名分别以get和set开头,并且后接大写字母开头的字段名Xyz,
因此两个读写方法名分别是getXyz()和setXyz()。
boolean字段比较特殊,它的读方法一般命名为isXyz():
*/
  
  
public class Main {
    public static void main(String[] args) throws Exception {
        BeanInfo info = Introspector.getBeanInfo(Person.class);
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println(pd.getName());
            System.out.println("  " + pd.getReadMethod());
            System.out.println("  " + pd.getWriteMethod());
        }
    }
}
/*
age
  public int Person.getAge()
  public void Person.setAge(int)
class
  public final native java.lang.Class java.lang.Object.getClass()
  null
name
  public java.lang.String Person.getName()
  public void Person.setName(java.lang.String)
*/
class Person {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

枚举类

  • 因为enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较
  • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
  • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … }
  • 通过name()获取常量定义的字符串,注意不要使用toString()
  • 通过ordinal()返回常量定义的顺序(无实质意义);
  • 可以为enum编写构造方法、字段和方法
  • enum的构造方法要声明为private,字段强烈建议声明为final
  • enum适合用在switch语句中。
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day == Weekday.SAT || day == Weekday.SUN) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}

String s = Weekday.SUN.name(); // "SUN"
int n = Weekday.MON.ordinal(); // 1

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}
enum Weekday {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);
    public final int dayValue;
    private Weekday(int dayValue) {
        this.dayValue = dayValue;
    }
}


public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Today is " + day + ". Work at home!");
        } else {
            System.out.println("Today is " + day + ". Work at office!");
        }
    }
}

enum Weekday {
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");
    public final int dayValue;
    private final String chinese;
    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }
    // 可重写
    @Override
    public String toString() {
        return this.chinese;
    }
}

记录类

  • record类
public record Point(int x, int y) {}
// 和下面的一样
public final class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int x() {
        return this.x;
    }
    public int y() {
        return this.y;
    }
     public String toString() {
        return String.format("Point[x=%s, y=%s]", x, y);
    }
    public boolean equals(Object o) {
        ...
    }
    public int hashCode() {
        ...
    }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point(123, 456);
        System.out.println(p.x());
        System.out.println(p.y());
        System.out.println(p);
    }
}

BigInteger 和 BigDecimal

  • BigInteger用于表示任意大小的整数;BigInteger是不变类,并且继承自Number;将BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确。

  • BigDecimal用于表示精确的小数,常用于财务计算;BigDecimalscale()表示小数位数

  • 调用divideAndRemainder()方法时,返回的数组包含两个BigDecimal,分别是商和余数,其中商总是整数,余数不会大于除数

  • 比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()

  • Java提供的常用工具类有:

    • Math:数学计算 Math.random() 0<= x < 1
    • Random:生成伪随机数
    • SecureRandom:生成安全的随机数
BigInteger bi = new BigInteger("1234567890");
System.out.println(bi.pow(5)); // 2867971860299718107233761438093672048294900000

BigDecimal n = new BigDecimal("12.75");
BigDecimal m = new BigDecimal("0.15");
BigDecimal[] dr = n.divideAndRemainder(m);
if (dr[1].signum() == 0) {
    // n是m的整数倍
}

//必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。s
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
System.out.println(d1.equals(d2)); // false,因为scale不同
System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2
System.out.println(d1.compareTo(d2)); // 0

Math.random()  // 0<= x < 1
Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int

集合

Collection

  • Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口。Java的java.util包主要提供了以下三种类型的集合:

    • List:一种有序列表的集合,例如,按索引排列的StudentList
    • Set:一种保证没有重复元素的集合,例如,所有无重复名称的StudentSet
    • Map:一种通过键值(key-value)查找的映射表集合,例如,根据Studentname查找对应StudentMap
  • Java集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List,具体的实现类有ArrayListLinkedList等,二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如: List list = new ArrayList<>(); // 只能放入String类型

  • 最后,Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。

List

  • 实现List接口并非只能通过数组(即ArrayList的实现方式)来实现,另一种LinkedList通过“链表”也实现了List接口。

  • List允许添加重复值、允许添加null、

  • 我们考察List<E>接口,可以看到几个主要的接口方法:

    • 在末尾添加一个元素:boolean add(E e)
    • 在指定索引添加一个元素:boolean add(int index, E e)
    • 删除指定索引的元素:E remove(int index)
    • 删除某个元素:boolean remove(Object e)
    • 获取指定索引的元素:E get(int index)
    • 获取链表大小(包含元素的个数):int size()
  • ArrayListLinkedList
    获取指定元素速度很快需要从头开始查找元素
    添加元素到末尾速度很快速度很快
    在指定位置添加/删除需要移动元素不需要移动元素
    内存占用较大
  • 遍历

import java.util.Iterator;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        // 给定元素快速创建List
        List<String> list = List.of("apple", "pear", "banana");
        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s);
        }
        for (String s : list) {
              System.out.println(s);
        }
    }
}

  • List和Array的转换

    • 如果传入的数组不够大,那么List内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List元素还要多,那么填充完元素后,剩下的数组元素一律填充null

    • // 传入一个恰好大小的数组
      Integer[] array = list.toArray(Integer[]::new);
      
    • toArray()方法直接返回一个Object[]数组,使用比较少会丢失类型信息

    • 是给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array

      • //List 转 Array
        public class Main {
            public static void main(String[] args) {
                List<Integer> list = List.of(12, 34, 56);
                Integer[] array = list.toArray(new Integer[3]);
                for (Integer n : array) {
                    System.out.println(n);
                }
            }
        }
        
        // Array转List的
        Integer[] array = { 1, 2, 3 };
        // 返回的只是一个只读List
        List<Integer> list = List.of(array);
        
  • List还提供了boolean contains(Object o)方法来判断List是否包含某个指定元素。此外,int indexOf(Object o)方法可以返回某个元素的索引,如果元素不存在,就返回-1

    • 因为List内部并不是通过==判断两个元素是否相等,而是使用equals()方法判断两个元素是否相等,

    • 正确使用Listcontains()indexOf()这些方法,放入的实例必须正确覆写equals()方法,否则,放进去的实例,查找不到。我们之所以能正常放入StringInteger这些对象,是因为Java标准库定义的这些类已经正确实现了equals()方法

    • equals()方法的正确编写方法:

      • 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
      • instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false
      • 对引用类型用Objects.equals()比较,对基本类型直接用==比较
    •  public boolean equals(Object o) {
          if (o instanceof Person) {
              Person p = (Person) o;
              return Objects.equals(this.name, p.name) && this.age == p.age;
          }
          return false;
      }
      

Map

  • Map<K, V>是一种键-值映射表,当我们调用put(K key, V value)方法时,就把keyvalue做了映射并放入Map。当我们调用V get(K key)时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的实现类是HashMap

  • 如果只是想查询某个key是否存在,可以调用boolean containsKey(K key)方法。

  • Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。

  • 不保证顺序,存储的是key-value的映射关系

  • 遍历

    • public class Main {
          public static void main(String[] args) {
              Map<String, Integer> map = new HashMap<>();
              map.put("apple", 123);
              map.put("pear", 456);
              map.put("banana", 789);
              for (String key : map.keySet()) {
                  Integer value = map.get(key);
                  System.out.println(key + " = " + value);
              }
              
              for (Map.Entry<String, Integer> entry : map.entrySet()) {
                  String key = entry.getKey();
                  Integer value = entry.getValue();
                  System.out.println(key + " = " + value);
              }
          }
      }
      
      
      
  • equals 和 hashcode

    • 因为在Map的内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的,即正确使用Map必须保证:作为key的对象必须正确覆写equals()方法。

    • 我们经常使用String作为key,因为String已经正确覆写了equals()方法。但如果我们放入的key是一个自己写的类,就必须保证正确覆写了equals()方法。

    • 通过key计算索引的方式就是调用key对象的hashCode()方法,它返回一个int整数。HashMap正是通过这个方法直接定位key对应的value的索引,继而直接返回value

    • 一个类如果覆写了equals(),就必须覆写hashCode(),并且覆写规则是:

      • 如果equals()返回true,则hashCode()返回值必须相等;
      • 如果equals()返回false,则hashCode()返回值尽量不要相等。
      • 实现hashCode()方法可以通过Objects.hashCode()辅助方法实现。
    • int hashCode() {
          return Objects.hash(firstName, lastName, age);
      }
      

TreeMap

  • HashMap是一种以空间换时间的映射表,它的实现原理决定了内部的Key是无序的,即遍历HashMap的Key时,其顺序是不可预测的(但每个Key都会遍历一次且仅遍历一次)。还有一种Map,它在内部会对Key进行排序,这种Map就是SortedMap。注意到SortedMap是接口,它的实现类是TreeMap

  • SortedMap保证遍历时以Key的顺序来进行排序。

  • 使用TreeMap时,放入的Key必须实现Comparable接口。StringInteger这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:

  • TreeMap不使用equals()hashCode()

  • public class Main {
        public static void main(String[] args) {
            Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
                public int compare(Person p1, Person p2) {
                    return p1.name.compareTo(p2.name);
                }
            });
            map.put(new Person("Tom"), 1);
            map.put(new Person("Bob"), 2);
            map.put(new Person("Lily"), 3);
            for (Person key : map.keySet()) {
                System.out.println(key);
            }
            // {Person: Bob}, {Person: Lily}, {Person: Tom}
            System.out.println(map.get(new Person("Bob"))); // 2
        }
    }
    
    class Person {
        public String name;
        Person(String name) {
            this.name = name;
        }
        public String toString() {
            return "{Person: " + name + "}";
        }
    }
    

Properties

  • 配置文件,它的Key-Value一般都是String-String类型的,因此我们完全可以用Map<String, String>来表示它,Properties内部本质上是一个Hashtable,但我们只需要用到Properties自身关于读写配置的接口

  • Properties读取配置文件,一共有三步:

    1. 创建Properties实例;
    2. 调用load()读取文件;
    3. 调用getProperty()获取配置。
  • // 典型的配置文件
    # setting.properties
    
    last_open_file=/data/hello.txt
    auto_save_interval=60
      
    // 读取文件
    String f = "setting.properties";
    Properties props = new Properties();
    props.load(new java.io.FileInputStream(f));
    
    String filepath = props.getProperty("last_open_file");
    String interval = props.getProperty("auto_save_interval", "120");
    
    // 解决中文乱码的问题
    Properties props = new Properties();
    props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
    
    
    // 写入配置文件 
    Properties props = new Properties();
    props.setProperty("url", "http://www.liaoxuefeng.com");
    props.setProperty("language", "Java");
    props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
    
    
    

Set

  • Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素。

  • 因为放入Set的元素和Map的key类似,都要正确实现equals()hashCode()方法,否则该元素无法正确地放入Set

  • 最常用的Set实现类是HashSet,实际上,HashSet仅仅是对HashMap的一个简单封装,它的核心代码如下:

  • Set用于存储不重复的元素集合,它主要提供以下几个方法:

    • 将元素添加进Set<E>boolean add(E e)
    • 将元素从Set<E>删除:boolean remove(Object e)
    • 判断是否包含元素:boolean contains(Object e)
  • Set接口并不保证有序,而SortedSet接口则保证元素是有序的:

    • HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
    • TreeSet是有序的,因为它实现了SortedSet接口。
  • public class Main {
        public static void main(String[] args) {
            Set<String> set = new HashSet<>();
            System.out.println(set.add("abc")); // true
            System.out.println(set.add("xyz")); // true
            System.out.println(set.add("xyz")); // false,添加失败,因为元素已存在
            System.out.println(set.contains("xyz")); // true,元素存在
            System.out.println(set.size()); // 2,一共两个元素
        }
    }
    

Queue

  • 队列Queue实现了一个先进先出(FIFO)的数据结构:

    • 通过add()/offer()方法将元素添加到队尾;
    • 通过remove()/poll()从队首获取元素并删除;
    • 通过element()/peek()从队首获取元素但不删除。
  • 要避免把null添加到队列。

  • throw Exception返回false或null
    添加元素到队尾add(E e)boolean offer(E e)
    取队首元素并删除E remove()E poll()
    取队首元素但不删除E element()E peek()
  • public class Main {
        public static void main(String[] args) {
            Queue<String> q = new LinkedList<>();
            // 添加3个元素到队列:
            q.offer("apple");
            q.offer("pear");
            q.offer("banana");
            // 从队列取出元素:
            System.out.println(q.poll()); // apple
            System.out.println(q.poll()); // pear
            System.out.println(q.poll()); // banana
            System.out.println(q.poll()); // null,因为队列是空的
        }
    }
    
  • PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。

  • public class Main {
        public static void main(String[] args) {
            Queue<User> q = new PriorityQueue<>(new UserComparator());
            // 添加3个元素到队列:
            q.offer(new User("Bob", "A1"));
            q.offer(new User("Alice", "A2"));
            q.offer(new User("Boss", "V1"));
            System.out.println(q.poll()); // Boss/V1
            System.out.println(q.poll()); // Bob/A1
            System.out.println(q.poll()); // Alice/A2
            System.out.println(q.poll()); // null,因为队列为空
        }
    }
    class UserComparator implements Comparator<User> {
        public int compare(User u1, User u2) {
            if (u1.number.charAt(0) == u2.number.charAt(0)) {
                // 如果两人的号都是A开头或者都是V开头,比较号的大小:
                return u1.number.compareTo(u2.number);
            }
            if (u1.number.charAt(0) == 'V') {
                // u1的号码是V开头,优先级高:
                return -1;
            } else {
                return 1;
            }
        }
    }
    
    class User {
        public final String name;
        public final String number;
        public User(String name, String number) {
            this.name = name;
            this.number = number;
        }
        public String toString() {
            return name + "/" + number;
        }
    }
    
    

Deque

  • QueueDeque
    添加元素到队尾add(E e) / offer(E e)addLast(E e) / offerLast(E e)
    取队首元素并删除E remove() / E poll()E removeFirst() / E pollFirst()
    取队首元素但不删除E element() / E peek()E getFirst() / E peekFirst()
    添加元素到队首addFirst(E e) / offerFirst(E e)
    取队尾元素并删除E removeLast() / E pollLast()
    取队尾元素但不删除E getLast() / E peekLast()
  • // 推荐的写法:
    Deque<String> d2 = new LinkedList<>();
    d2.offerLast("z");
    

Stack

  • 在Java中,我们用Deque可以实现Stack的功能:
    • 把元素压栈:push(E)/addFirst(E)
    • 把栈顶的元素“弹出”:pop()/removeFirst()
    • 取栈顶元素但不弹出:peek()/peekFirst()

Collections

  • Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合
  • 创建空集合,Collections提供了一系列方法来创建空集合:
    • 创建空List:List<T> emptyList()
    • 创建空Map:Map<K, V> emptyMap()
    • 创建空Set:Set<T> emptySet()
  • Collections可以对List进行排序。因为排序会直接修改List元素的位置,因此必须传入可变List
  • Collections提供了洗牌算法,即传入一个有序的List,可以随机打乱List内部元素的顺序,效果相当于让计算机洗牌 Collections.shuffle(list)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值