发新话题
打印

[开发] 《Java教程》廖雪峰

异常处理-使用SLF4J和Logback

SLF4J相当于Commons Logging的改进版
Logback相当于Log4j的改进版,但同时是SLF4J的原生实现,一般与SLF4J一起使用

目前推荐的组合式SLF4J+Logback

导入第三方库SLF4J和Logback,配置logback.xml,然后使用
代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class Main {
    final Logger logger = LoggerFactory.getLogger(getClass());

    void foo(){
        int score = 99;
        p.setScore(score);
        logger.info("Set score {} for Person {} ok.", score, p.getName()); //占位符方式记录日志
    }
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

反射-Class类

反射是指在程序运行期间拿到一个实例的所有信息
一般用于解决在运行期间,对一个对象一无所知的情况下,如何调用其方法

除了基本类型以外,Java中所有类型全部都是class
每个class被动态加载时,JVM会自动为其创建一个Class类的实例,包含了该类的所有信息
代码:
Class cls = new Class(String);
通过Class类的实例获取到对应类的所有信息,就叫反射(Reflection)
获取一个class的Class实例,有三种方法:
代码:
//方法1:获取静态变量class
Class cls = String.class;

//方法2:调用getClass()方法
String s = "Hello";
Class cls = s.getClass();

//方法3:使用完整类名,调用forName()静态方法
Class cls = Class.forName("java.lang.String");
拿到Class类的实例后,就可以使用newInstance()方法创建该类的实例(不推荐,不如new高效)
代码:
// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();
动态加载
JVM在程序执行时,会根据需要动态加载class,因此可以根据条件选择性的加载不同的class
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

反射-访问字段

通过class对象获取字段信息

  • Field getField(name) - 获取某个public字段(含父类)
  • Field getDeclaredField(name) - 获取某个字段(不含父类,含private)
  • Field getFields() - 获取所有pulbic字段(含父类)
  • field getDeclaredFields() - 获取所有字段(不含父类,含private)

通过filed对象,获取具体信息

  • getName() - 返回字段名
  • getType() - 返回字段类型
  • getModifiers() - 返回字段修饰符(int,表示不同的类型)

获取字段值
先拿到字段对应的Filed实例,然后通过Field拿到对应实例获取该字段的值

  • get(object) - 获取字段对应的实例中的字段值
代码:
public class Main {

    public static void main(String[] args) throws Exception {
        Object p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
设置字段值
同样先拿到Field实例,然后通过Field实例设置对应实例的字段值

  • set(object,value) - 设置字段对应的实例的中的字段值


读取和修改非public字段,需要首先调用setAccessible(true)
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

反射-调用方法

通过Class实例获取所有的Method信息

  • Method getMethod(name, Class...) - 获取某个Public方法,后面是参数的Class实例(含父类)
  • Method getDeclareMethod(name, Class...) - 获取某个方法,后面是参数的Class实例(不含父类,含private)
  • Method getMethods() - 获取所有Public方法(含父类)
  • Method getDeclareMethods() - 获取所有方法(不含父类,含private)

通过Method实例获取Method的信息

  • getName() - 返回方法名称
  • getReturnType() - 返回方法的返回值类型,是一个Class实例,比如:String.class
  • getParameterTypes() - 返回方法的参数类型,是一个Class数组比如,比如:{String.class, int.class}
  • getModifiers() - 返回方法的修饰符

调用方法
先获取类的Method实例,然后通过Method的invoke方法,调用指定对象的方法

  • invoke(Object, param) - 后面是参数值

以下是反射机制调用方法
代码:
// 普通调用
String s = "Hello world";
String r = s.substring(6); // "world"

// 反射调用
Method m = String.class.getMethod("substring", int.class);
r = (String) m.invoke(s, 6); // "world"
调用静态方法
调用静态方法时,无需指定对象,invoke方法传入第一个参数永远为null
代码:
Method m = Integer.class.getMethod("parseInt", String.class);
Integer n = (Integer) m.invoke(null, "12345");
调用非public方法
使用Method.setAccessible(true)
受SecrityManger规则的影响,setAccessible(true)可能会失败

多态
如果子类覆写了父类的方法,那么从父类获取的Method,作用于子类实例时,会遵循多态原则:总是调用实际类型的覆写方法(如果存在)
代码:
// 普通调用 
Person p = new Student();
p.hello(); // 调用Student的hello方法

// 反射调用
Method m = Person.class.getMethod("hello");
m.invoke(new Student()); // 调用Student的hello方法
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

反射-调用构造方法

通过反射创建实例,只能调用public无参数构造方法
代码:
Person p = new Person(); //普通创建实例
Person p = Person.class.newInstance(); //反射创建实例
调用任意构造方法,使用Constructor对象
代码:
//调用构造方法Integer(int)
Constructor cons1 = Integer.class.getConstructor(int.class);
Integer n1 = (Integer) cons1.newInstance(123);

//调用构造方法Integer(string)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");

  • getConstructor(Class...) - 获取某个public的Constructor对象(不含父类)
  • getDeclaredConstructor(Class...) - 获取某个Constructor(不含父类)
  • getConstructors() - 获取所有public的Constructor(不含父类)
  • getDeclaredConstructors() - 获取所有的Constructor(不含父类)

同样调用非public的Constructor时,必须先setAccessible(true)
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

反射-获取继承关系

获取某个Class对象时,就获取到了一个类的类型
代码:
Class cls = String.class; //获取到String的Class

String s = "abc";
Class cls = s.getClass(); //s是String,因此获取到String的Class

Class s = Class.forName("java.lang.String"); //通过类名获取到String的class
这三种方法获取的都是同一个实例,因为JVM对每个加载的Class只创建一个Class实例来表示它的类型

获取父类的Class

  • getSuperclass()
代码:
Class i = Integer.class;
Class n = i.getSuperclass(); //class java.lang.Number
Class o = n.getSuperclass(); //class java.lang.Object
Class u = o.getSuperclass(); //null
获取interface

  • getInterfaces() - 返回当前类直接实现的接口(不含父类)
代码:
Class s = Integer.class;
Class[] is = s.getInterfaces();
for (Class i : is) {
    System.out.println(i);
}
对接口获取父接口,使用getinterfaces(),而不是getSuperclass()

继承关系
判断一个实例是否时某个类型时,一般使用instanceof操作符
代码:
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
如果两个Class实例,判断一个向上转型是否可以实现,用isAssignableFrom()
代码:
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

注解-使用注解

注解分三类:

  • 编译器用注解 - 如@Override,@SuppressWarnings
  • 工具处理用注解 - 底层库处理.class文件时用于实现一些特殊功能
  • 程序运行注解 - 常用的注解,加载后存在于JVM中

我们这里说的是第三种

注解配置参数必须是常量,同时可以有默认值
代码:
public class Hello {
    @Check(min=0, max=100, value=55)
    public int n;

    @Check(value=99)
    public int p;

    @Check(99) // @Check(value=99)
    public int x;

    @Check
    public int y;
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

注解-定义注解

@interface语法定义注解(Annotation)
代码:
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}
元注解
可以修饰其他注解的注解叫做元注解。Java标准库已经定义好了一些元注解,可以直接使用

@Target
定义注解能够被应用于源码的哪些位置

  • ElementType.TYPE - 类或接口
  • ElementType.FIELD - 字段
  • ElementType.METHOD - 方法
  • ElementType.CONSTRUCTOR - 构造方法
  • ElementType.PARAMETER - 方法参数
代码:
@Target({
    ElementType.METHOD,
    ElementType.FIELD
})
public @interface Report {
    ...
}
@Retention
定义注解的生命周期

  • RetentionPolicy.SOURCE - 仅编译期
  • RetentionPolicy.CLASS - 仅class文件
  • RetentionPolicy.RUNTIME - 运行期
代码:
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}
@Repeatable
定义注解是否可以重复(不常用)。经过Repeatable修饰后,某个类的声明处可以添加多个注解

@Inherited
定义子类是否可继承父类定义的注解。

如何定义注解(Annotation)

  • 使用@interface定义
  • 添加参数和默认值(常用参数定义为value(),所有参数尽量设置默认值)
  • 用元注解配置注解(必须设置@Target和@Retention)
代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}
@Retention一般设置为RUNTIME,因为只有运行期注解对我们来说比较常用
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

注解-处理注解

注解定义后也是一种class,因此需要使用反射API读取注解

使用isAnnotationPresent来判断莫格注解是否粗壮乃与Class、Field、Method或Constructor

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)
代码:
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
使用getAnnotation(Class)读取注解

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)
代码:
Class cls = Person.class;

//先判断注解是否存在,再读取
if (cls.isAnnotationPresent(Report.class)) {
    Report report = cls.getAnnotation(Report.class);
    ...
}

//先读取,不存在返回null
Report report = cls.getAnnotation(Report.class);
if (report != null) {
   ...
}
使用getParameterAnnotations获取参数注解

  • Method.getParameterAnnotations()
代码:
// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range) { // @Range注解
        Range r = (Range) anno;
    }
    if (anno instanceof NotNull) { // @NotNull注解
        NotNull n = (NotNull) anno;
    }
}
使用注解
例如:定义@Range注解检查字段长度
定义
代码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
    int min() default 0;
    int max() default 255;
}
使用
代码:
public class Person {
    @Range(min=1, max=20)
    public String name;

    @Range(max=10)
    public String city;
}
检查
代码:
void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
    // 遍历所有Field:
    for (Field field : person.getClass().getFields()) {
        // 获取Field定义的@Range:
        Range range = field.getAnnotation(Range.class);
        // 如果@Range存在:
        if (range != null) {
            // 获取Field的值:
            Object value = field.get(person);
            // 如果值是String:
            if (value instanceof String) {
                String s = (String) value;
                // 判断值是否满足@Range的min/max:
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-什么是泛型

泛型是一种“代码模板”,可以用一套代码套用各种类型
代码:
public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}
T可以是任何class
代码:
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();

strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!
向上转型
代码:
public class ArrayList<T> implements List<T> {
    ...
}

List<String> list = new ArrayList<String>();
注意:ArrayList<Integer>不能向上转型为ArrayList<Number>
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-使用泛型

代码:
List<Number> list = new ArrayList<Number>();
list.add(new Integer(123));
list.add(new Double(12.34));
Number first = list.get(0);
Number second = list.get(1);
泛型赋值时,可以省略后面的泛型类型(JVM自动推断)
代码:
//普通写法
List<Number> list = new ArrayList<Number>();
//简写
List<Number> list = new ArrayList<>();
泛型接口
接口中也可以定义泛型
代码:
public interface Comparable<T> {
    /**
     * 返回负数: 当前实例比参数o小
     * 返回0: 当前实例与参数o相等
     * 返回正数: 当前实例比参数o大
     */
    int compareTo(T o);
}
实现这个接口的类,必须实现正确的泛型类型
代码:
class Person implements Comparable<Person> {
    String name;
    int score;
    Person(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public int compareTo(Person other) {
        return (other.score - this.score);//this.name.compareTo(other.name);
    }
    public String toString() {
        return this.name + "," + this.score;
    }
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-编写泛型

按照某种类型(比如String)来编写类,
代码:
public class Pair {
    private String first;
    private String last;
    public Pair(String first, String last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {
        return first;
    }
    public String getLast() {
        return last;
    }
}
编写完成之后替换为T,并申明T
代码:
public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}
静态方法
泛型类型<T>不能用于静态方法。
如果要对静态方法使用泛型,需要在static修饰符后加<T>,但这里的<T>与泛型类中声明的<T>不是同一个类型,因此最好用<K>
代码:
public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }

    // 静态泛型方法应该使用其他类型区分:
    public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
    }
}
多个泛型类型
泛型类可以定义多个不同的泛型类型
代码:
public class Pair<T, K> {
    private T first;
    private K last;
    public Pair(T first, K last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public K getLast() { ... }
}
使用时需指出两种类型
代码:
Pair<String, Integer> p = new Pair<>("test", 123);
比如Java标准库中的Map<K, V>
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-擦拭法

Java实现泛型的方式是擦拭法(Type Erasure)
擦拭法:在编译时强制执行类型约束,并在运行时丢弃元素类型信息的过程

Java中

  • 编译器会把类型<T>替换为Object
  • 编译器根据<T>实现安全的强制转型

因此存在4个局限:

  • <T>不能是基本类型
    • <T>运行时会转为Object,Object类型无法持有基本类型

  • 无法取得带泛型的Class
    • 因为擦拭法会将<T>替换为Object,因此无论T是什么类型,获得的结果都是同一个Class

  • 无法判断带泛型的类型
    • 例如判断p instanceof Pair<String>,由于只有唯一的Pair.class,并不存在Pair<T>.class

  • 无法实例化T类型
代码:
public class Pair<T> {
    private T first;
    private T last;
    public Pair() {
        // Compile error:
        first = new T();
        last = new T();
    }
}
因为T最终转为Object,对于new Pair<String>()和new Pair<Integer>()中的T全部会转为Object,编译器不允许这样

为了实例化T类型,可以借助额外的Class<T>参数
代码:
public class Pair<T> {
    private T first;
    private T last;
    public Pair(Class<T> clazz) {
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}
使用时需要传入指定的Class<String>实例
代码:
Pair<String> pair = new Pair<>(String.class);
不恰当的覆写方法
如果在带泛型的类中,定义的方法与Object的方法重名(即在编译时,T转为Object时无意间覆写了Object的方法),编译会无法通过
代码:
public class Pair<T> {
    public boolean equals(T t) { //equals(Object t)继承自Object,产生冲突,编译失败
        return this == t;
    }
}
这种情况改个名就行

泛型继承
子类可以继承自一个泛型父类

  • 正常继承
    • class Son1<T> extends Father<T>

  • 子类新增泛型参数
    • class Son2<K,T> extends Father<T>

  • 子类继承时指定父类泛型参数
    • class Son3 extends Father<Integer>

  • 子类继承时指定父类泛型参数,同时自己新增泛型参数
    • class Son4<T> exntends Father<Integer>


对于上面的第3个情况,子类可以获取父类的泛型类型,如下(比较复杂)
代码:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Main {
    public static void main(String[] args) {
        Class<IntPair> clazz = IntPair.class;
        Type t = clazz.getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) t;
            Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型
            Type firstType = types[0]; // 取第一个泛型类型
            Class<?> typeClass = (Class<?>) firstType;
            System.out.println(typeClass); // Integer
        }
    }
}

class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

class IntPair extends Pair<Integer> {
    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-extends通配符

假设我们定义了Pair<T>,针对Pair<Integer>实例,我们定义新的Pair<Number>并作为参数传入
代码:
public static void main(String[] args) {
    Pair<Integer> p = new Pair<>(123, 456);
    int n = add(p);
    System.out.println(n);
}

static int add(Pair<Number> p) {
    Number first = p.getFirst(); //传入的Pare<Integer>
    Number last = p.getLast();
    return first.intValue() + last.intValue();
}
上述静态方法add中,传入的Integer给Number对象,虽然逻辑是正确的,但受到泛型约束,无法通过编译。只要做以下修改:
代码:
static int add(Pair<? extends Number> p) {
    Number first = p.getFirst();
    Number last = p.getLast();
    return first.intValue() + last.intValue();
}
即可通过编译

给方法传入Pair<Integer>符合Pair<? extends Number>类型,这种使用<? extends Number>的泛型定义,叫做上界通配符(Upper Bounds Wildcards)

  • 对于Number first = p.getFirst(),返回值Integer是Number的子类,可以安全的赋值
  • 对于Integer first = p.getFirst(),则无法通过编译,因为返回值可能是double等其他类型,它们不是Integer的子类,无法安全赋值

同时对于set方法,由于无法知道Pair<T>的具体类型,假设传入的p是Pair<Double>,而返回的是Integer类型,是无法set的
代码:
static int add(Pair<? extends Number> p) {
    Number first = p.getFirst();
    Number last = p.getLast();
    p.setFirst(new Integer(first.intValue() + 100)); //返回值是Number的子类,但不知道p的类型,无法写入
    p.setLast(new Integer(last.intValue() + 100));
    return p.getFirst().intValue() + p.getFirst().intValue();
}
方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)
唯一例外的是null

extends通配符的作用
对于一个方法内部来说,传入参数List<Integer>和List<? extends Integer>是完全一样的。但传入<? extends Integer>会有以下特点:

  • 允许调用get()方法获取Integer的引用
  • 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)

即对参数List<? extends Integer>进行只读的方法(恶意set(null)除外)

使用extends限定T类型
定义Pair<T>时,也可以使用extends限定T的类型
代码:
public class Pair<T extends Number> { ... }
这样就限制了定义类型:
代码:
Pair<Number> p1 = null;
Pair<Integer> p2 = new Pair<>(1, 2);
Pair<Double> p3 = null;
Pair<String> p4 = null; // 编译错误,因为String不符合T extends number
Pair<Object> p5 = null; // 编译错误,同上
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-super通配符

假设我们定义的set方法,对于传入参数p,允许Pair<Integer>,但不允许其父类Pair<Number>
代码:
void set(Pair<Integer> p, Integer first, Integer last) {
    p.setFirst(first);
    p.setLast(last);
}
为了实现允许父类Pair<Number>或Pair<Object>,需要使用super通配符
代码:
void set(Pair<? super Integer> p, Integer first, Integer last) {
    p.setFirst(first);
    p.setLast(last);
}
super通配符和extends相反,用于表示参数接受泛型类型为Integer或Integer父类的Pair类型

使用<? super Integer>作为方法参数,表示只能写,不能读:

  • 允许调用set(? super Integer)方法传入Integer的引用
  • 不允许调用get()方法获得Integer的引用

唯一例外的是可以获取Object的引用:Object o = p.getFirst()

对比extends和super通配符

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外)
  • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)

PECS原则
Producer Extends Consumer Super

  • 生产者(Producer,返回T)使用extends通配符
  • 消费者(Consumer,写入T)使用super通配符

无限定通配符
无限定通配符(Unbounded Wildcard Type)既不能读,也不能写,只能做一些null判断:

  • 不允许调用set(T)方法并传入引用(null除外)
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)
代码:
static boolean isNull(Pair<?> p) {
    return p.getFirst() == null || p.getLast() == null;
}
无限定通配符很少用,一般用T来替换
代码:
static <T> boolean isNull(Pair<T> p) {
    return p.getFirst() == null || p.getLast() == null;
}
Pair<?>是所有Pair<T>的超类:
代码:
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全地向上转型
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

泛型-泛型与反射

Class<T>是泛型,调用Class的getSuperclass()方法,返回的Class类型是Class<? super T>
代码:
Class<String> clazz = String.class;
String str = clazz.newInstance();

Class<? super String> sup = clazz.getSuperclass();
构造方法Constructor<T>也是泛型
代码:
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
声明泛型数组,不能使用new
代码:
Pair<String>[] ps = null; // ok
Pair<String>[] ps = new Pair<String>[2]; // compile error!

@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
由于泛型数组在运行期间实际上指向的是同一个数组,因此泛型数组的操作不当可能导致其他数组报错
代码:
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;

ps[0] = new Pair<String>("a", "b");
arr[0] = new Pair<Integer>(1, 2);

// ClassCastException:
Pair<String> p = ps[0];
String s = p.getFirst();
为了安全的使用泛型数组,上面的例子中必须丢弃arr的引用
代码:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
而创建泛型数组T[],需要借助Class<T>
代码:
public class Abc<T> {
    // 错误
    T[] createArray() {
        return new T[5];
    }

    // 正确,借助Class<T>
    T[] createArray(Class<T> cls) {
        return (T[]) Array.newInstance(cls, 5);
    }
}
或者借助可变参数
代码:
public class ArrayHelper {
    @SafeVarargs
    static <T> T[] asArray(T... objs) {
        return objs;
    }
}

String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
谨慎使用泛型可变参数
代码:
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] arr = asArray("one", "two", "three");
        System.out.println(Arrays.toString(arr));
        // ClassCastException:
        String[] firstTwo = pickTwo("one", "two", "three");
        System.out.println(Arrays.toString(firstTwo));
    }

    static <K> K[] pickTwo(K k1, K k2, K k3) {
        return asArray(k1, k2); // 无法检测K[]的类型,因此返回Object[],对外部来说,Object无法赋值给String类型
    }

    static <T> T[] asArray(T... objs) {
        return objs;
    }
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

集合-Java集合简介

集合就是“由若干个确定的元素所构成的整体”
一个对象可以再内部持有若干其他对象,并对外提供访问接口,就称为集合。
数组就是一种集合
代码:
String[] ss = new String[10]; // 可以持有10个String对象
ss[0] = "Hello"; // 可以放入String对象
String first = ss[0]; // 可以获取String对象
数组有以下限制:

  • 数组初始化后大小不可变
  • 数组只能按索引顺序存取


Collection
为了满足其他需求,java.util提供了集合类Collection,它是除Map外所有其他集合类的根接口。主要包含以下三种集合:

  • List - 有序列表的集合
  • Set - 保证没有重复元素的集合
  • Map - 通过键值查找的引射表集合

集合的设计包含以下特点:

  • 接口与实现类分离
  • 支持泛型
代码:
List<String> list = new ArrayList<>(); // 只能放入String类型
Java访问集合总是通过统一的方式迭代器(Iterator)来实现,好处是无需知道集合内部元素是按什么方式存储的

以下为历史遗留的集合,尽量不要使用:

  • Hashtable - 线程安全的Map实现
  • Vector - 线程安全的List实现
  • Stack - 基于Vector实现的LIFO的栈
  • Enumeration<E> - 被Iterator<E>取代
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

集合-使用List

List比数组方便的是

  • List在添加或删除元素后,会自动移动元素到指定的位置
  • List可以变更空间大小

List<E>接口有以下主要方法:

  • boolean add(E e) - 末尾添加一个元素
  • boolaen add(int index, E e) - 在指定索引添加一个元素
  • E remove(int index) - 删除指定索引的元素
  • boolean remove(Object e) - 删除某个元素
  • E get(int index) - 获得指定索引的元素
  • int size() - 获取链表大小(包含元素的个数)

List接口常见的两种实现方式ArrayList和LinkedList区别:

  • ArrayList获取指定元素比LinkedList快
  • ArrayList在指定位置添加/删除元素,需要移动位置,而LinkedList不需要
  • ArrayList内存占用比LinkedList少


List的特点

  • List允许添加重复的元素
  • List允许添加null


创建List
可以通过new一个ArrayList或LinkedList来创建
也可以使用List接口提供的of()方法(该方法不接受null)
代码:
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);

List<Integer> list2 = List.of(1, 2, 5);
遍历List
遍历List最好使用迭代器Iterator,迭代器对象有两个方法:

  • boolean hasNext() - 判断是否有下一个元素
  • E next() - 返回下一个元素

这样可以使用for循环遍历
代码:
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 each提供的简写方法来遍历
代码:
List<String> list = List.of("apple", "pear", "banana");
for (String s : list) {
    System.out.println(s);
}
编译器在对集合进行for each时,会自动变成对Iterator的调用,所有实现了Iterator<E> iterator()方法的集合都可以用for each遍历。

List和Array转换
List变为Array:
1. toArray() - 返回Object[]数组,会丢失类型信息
代码:
List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
2. toArray(T[])传入一个相同类型的Array
代码:
List<Integer> list = List.of(12, 34, 56);
Integer[] array1 = list.toArray(new Integer[3]);
Number[] array2 = list.toArray(new Number[3]); //toArray(T[])中的泛型参数T不是List接口定义的泛型参数E,因此可以不同
String[] array3 = list.toArray(new String[3]); //List元素是Integer,无法放入String[]数组,报ArrayStoreException
Integer[] array4 = list.toArray(new Integer[1]); //传入数组过小,List内部创建一个刚好够大的数组,填充后返回
Integer[] array5 = list.toArray(new Integer[5]); //传入数组过大,填充结束后,剩余元素填充null
Integer[] array6 = list.toArray(new Integer[list.size()]); //传入大小刚好的数组
3. T[] toArray(IntFunction<t[]> generator))方法
代码:
Integer[] array = list.toArray(Integer[]::new);
Array变为List:
List.of(T...)方法(JDK 11之前的版本用Arrays.asList(T...)方法)
代码:
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);
list.add(999); // UnsupportedOperationException,List.of返回的是只读List
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

集合-编写equals方法

List提供以下方法判断某个元素与集合的关系:

  • boolean contains(Object o) - 判断集合是否包含某个元素
  • int indexOf(Object o) - 判断集合中某个元素的索引

使用上述方法时,传入的元素(上面的o)与集合内部元素的对比不是通过==,而是通过equals()方法对比
所以传入的元素内部必须正确的覆写equals()方法(Integer、String等内部已经实现了equals()方法)

编写euqals
equals()方法需要满足:

  • 自反性(Reflexive):x.equals(x)必须为true(x非null)
  • 对称性(Symmetric):x.equals(y)为true,则y.equals(x)也必须为true(x,y非null)
  • 传递性(Transitive):x.equals(y)为true,y.equals(z)为true,则x.equals(z)必须为true(x,y,z非null)
  • 一致性(Consistent):只要x,y状态不变,则x.equals(y)总是一致的返回true或false(x,y非null)
  • 对null比较:x.equals(null)永远返回false
代码:
public boolean equals(Object o) {
    if (o instanceof Person) {
        Person p = (Person) o;
        boolean nameEquals = false;
        if (this.name == null && p.name == null) {
            nameEquals = true;
        }
        if (this.name != null) {
            nameEquals = this.name.equals(p.name);
        }
        return nameEquals && this.age == p.age;
    }
    return false;
}
使用Objects.equals()方法可以简化写法
代码:
public boolean equals(Object o) {
    if (o instanceof Person p) {
        return Objects.equals(this.name, p.name) && this.age == p.age;
    }
    return false;
}
equals()方法的编写思路如下:

  • 先确定实例“相等”逻辑(即确定哪些字段相等,就认为实例相等)
  • 用instanceof判断传入的待比较Object是不是当前类型
  • 对引用类型用Objects.equals()方法比较,对基本类型用==比较
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

集合-使用Map

Map<K,V>时一种键值映射表,常用的方法如下:

  • V put(K key, V value)
  • V get(Object Key)
  • boolean containsKey(K key)

其常用的实现类时HashMap,在HashMap中,重复放入key-value没有问题,但一个key只能关联一个value
put()方法在放入对象时,如果key对象已经存在,则会返回被删除的旧value,否则返回null
Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。

遍历Map
遍历key,用for each遍历Map实例的keySet()方法返回的Set合集,它包含不重复的key集合
代码:
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println(key + " = " + value);
}
遍历key和value,用for each遍历Map实例的entrySet()方法返回的集合,它包含每一个key-value映射
代码:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " = " + value);
}
遍历Map时,不可假设输出的key是有序的!
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

发新话题