Administrator
发布于 2023-01-16 / 92 阅读
0
0

Java Fundemental Feature

hashcode与equals

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:

`public` `native` `int` `hashCode();`

根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现。

为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法。

一.hashCode方法的作用

结论:

​ 1.hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

​ 2.hashCode的值与 存储地址有关。即多次通过new ClassName()得到的多个对象,即使属性一致,hashCode也不相等

​ 3.如果一个对象的某个属性的属性值发生了变化,那么这个对象的hashcode的值可能也会发生改变

	4. 如果两个对象是同一个对象,那么调用equals方法,必然返回true。反过来,一样成立
	5. 通过调用equals方法返回true,那么调用hashcde方法返回的hashcode必然相等。这个结论的逆反命题也成立
            		6. 如果调用equals方法返回false,hashcode可能相等,也可能不等;

对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)

也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是java.util.HashMap的中put方法的具体实现:

public V put(K key, V value) {
		if (key == null)
			return putForNullKey(value);
		int hash = hash(key.hashCode());
		int i = indexFor(hash, table.length);
		for (Entry<K, V> e = table[i]; e != null; e = e.next) {
			Object k;
			if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
				V oldValue = e.value;
				e.value = value;
				e.recordAccess(this);
				return oldValue;
			}
		}
		modCount++;
		addEntry(hash, key, value, i);
		return null;
	}

put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

在有些情况下,程序设计者在设计一个类的时候为需要重写equals方法,比如String类,但是千万要注意,在重写equals方法的同时,必须重写hashCode方法。为什么这么说呢?

下面看一个例子:

package com.cxh.test1;
 
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
 
 
class People{
    private String name;
    private int age;
     
    public People(String name,int age) {
        this.name = name;
        this.age = age;
    }  
     
    public void setAge(int age){
        this.age = age;
    }
         
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}
 
public class Main {
 
    public static void main(String[] args) {
         
        People p1 = new People("Jack", 12);
        System.out.println(p1.hashCode());
             
        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);
         
        System.out.println(hashMap.get(new People("Jack", 12)));
    }
}

在这里我只重写了equals方法,也就说如果两个People对象,如果它的姓名和年龄相等,则认为是同一个人。

这段代码本来的意愿是想这段代码输出结果为“1”,但是事实上它输出的是“null”。为什么呢?原因就在于重写equals方法的同时忘记重写hashCode方法。

虽然通过重写equals方法使得逻辑上姓名和年龄相同的两个对象被判定为相等的对象(跟String类类似),但是要知道默认情况下,hashCode方法是将对象的存储地址进行映射。那么上述代码的输出结果为“null”就不足为奇了。原因很简单,p1指向的对象和

System.out.println(hashMap.get(new People(“Jack”, 12)));这句中的new People(“Jack”, 12)生成的是两个对象,它们的存储地址肯定不同。下面是HashMap的get方法的具体实现:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

所以在hashmap进行get操作时,因为得到的hashcdoe值不同(注意,上述代码也许在某些情况下会得到相同的hashcode值,不过这种概率比较小,因为虽然两个对象的存储地址不同也有可能得到相同的hashcode值),所以导致在get方法中for循环不会执行,直接返回null。

因此如果想上述代码输出结果为“1”,很简单,只需要重写hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性。

package com.cxh.test1;
 
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
 
 
class People{
    private String name;
    private int age;
     
    public People(String name,int age) {
        this.name = name;
        this.age = age;
    }  
     
    public void setAge(int age){
        this.age = age;
    }
     
    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        return name.hashCode()*37+age;
    }
     
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}
 
public class Main {
 
    public static void main(String[] args) {
         
        People p1 = new People("Jack", 12);
        System.out.println(p1.hashCode());
             
        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);
         
        System.out.println(hashMap.get(new People("Jack", 12)));
    }
}

这样一来的话,输出结果就为“1”了。

下面这段话摘自Effective Java一书:

  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的一段话:

“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

下面举个例子:

package com.cxh.test1;
 
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
 
 
class People{
    private String name;
    private int age;
     
    public People(String name,int age) {
        this.name = name;
        this.age = age;
    }  
     
    public void setAge(int age){
        this.age = age;
    }
     
    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        return name.hashCode()*37+age;
    }
     
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}
 
public class Main {
 
    public static void main(String[] args) {
         
        People p1 = new People("Jack", 12);
        System.out.println(p1.hashCode());
         
        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);
         
        p1.setAge(13);
         
        System.out.println(hashMap.get(p1));
    }
}

这段代码输出的结果为“null”,想必其中的原因大家应该都清楚了。

因此,在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。

equals方法编写

However, you will often want to implement state-based equality testing, in which two objects are considered equal when they have the same state.

For example, let us consider two employees equal if they have the same name,
salary, and hire date. (In an actual employee database, it would be more sensible
to compare IDs instead. We use this example to demonstrate the mechanics of
implementing the equals method.)

public class Employee
{
. . .
public boolean equals(Object otherObject)
{
    // a quick test to see if the objects are identical
    if (this == otherObject) return true;
    // must return false if the explicit parameter is null
    if (otherObject == null) return false;
    // if the classes don't match, they can't be equal
    if (getClass() != otherObject.getClass())
    return false;
    // now we know otherObject is a non-null Employee
    Employee other = (Employee) otherObject;
    // test whether the fields have identical values
    return name.equals(other.name)
    && salary == other.salary
    && hireDay.equals(other.hireDay);
}
}

The getClass method returns the class of an object—we discuss this method in detail
later in this chapter. In our test, two objects can only be equal when they belong
to the same class.

还可以使用下面的写法

TIP: To guard against the possibility that name or hireDay are null, use the
Objects.equals method. The call Objects.equals(a, b) returns true if both arguments
are null, false if only one is null, and calls a.equals(b) otherwise.With that method,
the last statement of the Employee.equals method becomes

public boolean equals(Object otherObject)
{
	// a quick test to see if the objects are identical
    if (this == otherObject) return true;
     // must return false if the explicit parameter is null
    if (otherObject == null) return false;
    // if the classes don't match, they can't be equal
    if (getClass() != otherObject.getClass())
    return false;
    // now we know otherObject is a non-null Employee
    Employee other = (Employee) otherObject;
    return Objects.equals(name, other.name)
    && salary == other.salary
    && Object.equals(hireDay, other.hireDay);
  }

上面两种写法的区别在于:下面一种使用了 Objects.equals(name, other.name),避免了name是null的情况出现的异常,下面一种更好一点。

如果新建了一个子类,继承了一个父类,那么子类的equals方法 应该怎么写?

When you defne the equals method for a subclass, frst call equals on the superclass.
If that test doesn’t pass, then the objects can’t be equal. If the superclass felds are
equal, you are ready to compare the instance felds of the subclass.

public class Manager extends Employee
{
. . .
    public boolean equals(Object otherObject)
    {
        if (!super.equals(otherObject)) return false;
        // super.equals checked that this and otherObject belong to the same class
        Manager other = (Manager) otherObject;
        return bonus == other.bonus;
    }
}

hashcode方法编写

That hash code is derived from the object’s memory address

Consider this example:

String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());
String t = new String("Ok");
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode() + " " + tb.hashCode());

1571369343082

Note that the strings s and t have the same hash code because, for strings, the
hash codes are derived from their contents. The string builders sb and tb have
different hash codes because no hashCode method has been defned for the StringBuilder
class and the default hashCode method in the Object class derives the hash code from
the object’s memory address.

If you redefne the equals method, you will also need to redefne the hashCode method
for objects that users might insert into a hash table. (We discuss hash tables in
Chapter 9.)

The hashCode method should return an integer (which can be negative). Just combine
the hash codes of the instance felds so that the hash codes for different objects
are likely to be widely scattered.

注意,不可变类 和 可变类的 hashcode的获取不同。

例如,name是String,不可变类;而salary是double,是可变类

注意,多次new Double(salary) 得到的对象 不是同一个,但是因为Double重写了hashcode方法,返回的是数字值,不是内存地址,所以 多次new Double(salary) .hashcode()的值是相同的。Objects.hashCode(salary) ,返回的也是数字值

For example, here is a hashCode method for the Employee class:

public class Employee
{
    public int hashCode()
    {
        return 7 * name.hashCode()
        + 11 * new Double(salary).hashCode()
        + 13 * hireDay.hashCode();
    }
. . .
}

However, you can do better. First, use the null-safe method Objects.hashCode. It returns
0 if its argument is null and the result of calling hashCode on the argument otherwise.
Also, use the static Double.hashCode method to avoid creating a Double object:

public int hashCode()
{
    return 7 * Objects.hashCode(name)
    + 11 * Double.hashCode(salary)
    + 13 * Objects.hashCode(hireDay);
}

Even better, when you need to combine multiple hash values, call Objects.hash with
all of them. It will call Objects.hashCode for each argument and combine the values.
Then the Employee.hashCode method is simply

public int hashCode()
{
	return Objects.hash(name, salary, hireDay);
}

Your defnitions of equals and hashCode must be compatible: If x.equals(y) is true, then
x.hashCode() must return the same value as y.hashCode().

For example, if you defne Employee.equals to compare employee IDs, then the hashCode method needs to hash the IDs, not employee names or memory addresses.

完整示例:

 @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object == null || getClass() != object.getClass()) {
            return false;
        }
        VisionFaceMessageBean that = (VisionFaceMessageBean)object;
        return faceNum == that.faceNum && Objects.equals(faceList, that.faceList);
    }

    @Override
    public int hashCode() {
        return Objects.hash(faceNum, faceList);
    }

hashcode equal ==的联系/区别

1.首先,默认情况下,Object类型的对象,hashcode的值与 存储地址有关,多次new对象,即使字段、属性一致,hashcode也不一致;默认情况下,equals比较的也是 存储地址

Object:

public boolean equals(Object obj) {
        return (this == obj);
    }

2.String 类型,因为存在常量池,所以多次new String(“zhangsan”),其实得到的是同一个对象的引用,所以hashcode 相同,equals为true

String s = new String("123");
String s1 = "123";
System.out.println(s.hashCode() == s1.hashCode());
System.out.println(s.equals(s1));

3.如果是包装类,例如Integer, hashcode的值与值有关,与存储地址无关,所以多次new Integer(1208),其实hashcode的值相等,equals为true

@Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
Integer m = new Integer(1280);
Integer l = new Integer(1280);
System.out.println(m.hashCode() == l.hashCode()); // true
System.out.println(m.equals(l)); // true

从这里也能看出来,hashcode的影响因子,必须与equals方法中的影响因子相同。如果是新定义一个Person类,直接继承Object类的hashcode方法和equals的方法的话,那么多次new person(),其实hashcode 的值不同,equals为false。

4.== 判断规则如下:

基本数据类型(也称原始数据类型) :byte,short,char,int,long,float,double,boolean。他们之间的比较,应用双等号(==),比较的是他们的值。

复合数据类型(类):当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址(确切的说,是堆内存地址)。

注:对于第二种类型,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。因为每new一次,都会重新开辟堆内存空间。

Optional的使用场景

optional的创造,是为了避免不断判空。optional的所有运算,基本都是,如果值不为空,进行相应运算,否则,返回一个空的optional。optional是一个容器,这个容器比较特殊,可以存放null

实际使用:

  Optional<List<VisionFaceMessageBean.FaceListBean>> optionalFaceListBeans = Optional.of(
                    model.getVisionFaceMessage())
                    .map(VisionFaceMessage::getVisionMessageBean)
                    .map(VisionFaceMessageBean::getFaceList);
                optionalFaceListBeans.ifPresent(faceListBeans::addAll);

在上一段代码中,避免了每一步getxx()之后,都需要判空

optional的map和flatMap的区别

首先,这2个方法的返回值都是一个Optional对象。

如果lambda表达式,本身返回了Optinal对象,那么就使用flatMap,防止导致多次包装optional,产生类似于这种数据结构:Optional<Optional>。

如果lambda表达式,本身返回的不是Optinal对象,那么就使用map.

实例

public static void main(String[] args) {
  Optional<String> s = Optional.of("input");
  System.out.println(s.map(Test::getString));
  System.out.println(s.flatMap(Test::getOptional));
}

static Optional<String> getOptional(String input) {
  return input == null ? Optional.empty() : Optional.of("output for " + input);
}

static String getString(String input) {
  return input == null ? null : "output for " + input;
}

Optional 的of 与 ofNullable区别

of() 和 ofNullable() 方法创建包含值的 Optional。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException

如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:

Optional<User> opt = Optional.ofNullable(user);

Class.forname使用场景和作用

主要功能
Class.forName(xxx.xx.xx)返回的是一个类。
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。
public class Test1 {

	static {
		System.out.println("this is test1 static code");
	}
	
	public Test1() {
		System.out.println("this is test1 construct");
	}
	
	public void test01() {
		System.out.println("this is test1 test01 method");
	}
}

public class Test2 {

	public static void main(String[] args) throws ClassNotFoundException {
        // 调用这段代码的时候,会执行Test1类的静态代码块,Test1的构造方法不会执行
		Class<?> className = Class.forName("com.wp.inner.Test1");
		System.out.println("this is Test2 main method");
		System.out.println(className.getName());
	}
}

执行结果如下:

this is test1 static code
this is Test2 main method
com.wp.inner.Test1

Enum

EnumSet

EnumSet 是一个与枚举类型一起使用的专用 Set 实现。枚举set中所有元素都必须来自单个枚举类型(即必须是同类型,且该类型是Enum的子类)。

基本用法

与TreeSet/HashSet不同,EnumSet是一个抽象类,不能直接通过new新建,也就是说,类似下面代码是错误的:

EnumSet<Size> set = new EnumSet<Size>();

不过,EnumSet提供了若干静态工厂方法,可以创建EnumSet类型的对象,比如:

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)

为方便举例,我们定义一个表示星期几的枚举类Day,值从周一到周日,如下所示:

enum Day {

   MONDAY, TUESDAY, WEDNESDAY,
   THURSDAY, FRIDAY, SATURDAY, SUNDAY

}

noneOf方法会创建一个指定枚举类型的EnumSet,不含任何元素。创建的EnumSet对象的实际类型是EnumSet的子类,待会我们再分析其具体实现。

可以这么用noneOf方法:

Set<Day> weekend = EnumSet.noneOf(Day.class);

weekend.add(Day.SATURDAY);
weekend.add(Day.SUNDAY);
System.out.println(weekend);

// weekend表示休息日,noneOf返回的Set为空,添加了周六和周日,所以输出为:
[SATURDAY, SUNDAY]

EnumSet还有很多其他静态工厂方法,如下所示(省略了修饰public static):

// 初始集合包括指定枚举类型的所有枚举值
<E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)

// 初始集合包括枚举值中指定范围的元素
<E extends Enum<E>> EnumSet<E> range(E from, E to)

// 初始集合包括指定集合的补集
<E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)

// 初始集合包括参数中的所有元素
<E extends Enum<E>> EnumSet<E> of(E e)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
<E extends Enum<E>> EnumSet<E> of(E first, E... rest)

// 初始集合包括参数容器中的所有元素
<E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
<E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)

emumSet的使用场景

假如定义一个类,这个类包含一个set,set中的每个元素都是一个枚举值,这个时候就需要使用enumSet了

下面看一个实例:

关注:1.ResourceSet 这个类的EMPTY_SET是如何定义的,其中用到了Collections.emptySet()

​ 2.ResourceSet 的构造参数是如何定义的,其中用到了EnumSet

public class ResourceSet {

    /**
     * DEFAULT_PRIORITY
     */
    public static final int DEFAULT_PRIORITY = 0;

    /**
     * EMPTY_SET
     */
    public static final ResourceSet EMPTY_SET = new ResourceSet(DEFAULT_PRIORITY, Collections.emptySet());

    /**
     * Comparator for ranking ResourceSets, first descending by priority, then descending by number of resources used.
     * By default more resources are preferred, with the assumption that these will result in more expressive behavior.
     */
    public static final Comparator<ResourceSet> COMPARATOR = (ResourceSet set1, ResourceSet set2) -> {
        if (set1.priority == set2.priority) { // 优先级相等时
            return set2.resources.size() - set1.resources.size(); // 包含资源多的资源集靠前,按照注释所说,是为了选择更具表现力的动作
        }
        return set2.priority - set1.priority; // 优先级高的靠前
    };

    private final int priority;

    private final Set<Resource> resources;

    private ResourceSet(int priority, Set<Resource> resources) {
        this.priority = priority;
        this.resources = resources;
    }

    /**
     * Creates a_ResourceSet with provided Resources at the specified priority level.
     *
     * @param priority x
     * @param resources x
     * @return x
     */
    public static ResourceSet of(int priority, Resource... resources) {
        if (priority == DEFAULT_PRIORITY && resources.length == 0) {
            return EMPTY_SET;
        }
        Set<Resource> setResources = EnumSet.noneOf(Resource.class);
        Collections.addAll(setResources, resources);
        return new ResourceSet(priority, setResources);
    }

    /**
     * Creates a_ResourceSet with the provided Resources at the default priority.
     *
     * @param resources x
     * @return x
     */
    public static ResourceSet of(Resource... resources) {
        if (resources.length == 0) {
            return EMPTY_SET;
        }
        return of(DEFAULT_PRIORITY, resources);
    }

enumMap

EnumMap是一个与枚举类一起使用的Map实现,EnumMap中所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。

EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑、高效。
EnumMap根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护来维护key-value对的次序。当程序通过keySet()、entrySet()、values()等方法来遍历EnumMap时即可看到这种顺序。

EnumMap不允许使用null作为key值,但允许使用null作为value。如果试图使用null作为key将抛出NullPointerException异常。如果仅仅只是查询是否包含值为null的key、或者仅仅只是使用删除值为null的key,都不会抛出异常。

import java.util.*;
 
enum Season
{
	SPRING,SUMMER,FALL,WINTER
}
public class EnumMapTest
{
	public static void main(String[] args)
	{
		// 创建EnumMap对象,该EnumMap的所有key都是Season枚举类的枚举值
		EnumMap enumMap = new EnumMap(Season.class);
		enumMap.put(Season.SUMMER , "小荷才露尖尖角");
		enumMap.put(Season.SPRING , "满园春色关不住");
		System.out.println(enumMap);
	}
}

抽象类的实例变量和构造方法

抽象类 为什么需要构造方法呢?什么时候调用这个构造方法呢?

原因是:通过构造方法,给抽象类的实例变量进行赋值。

当一个子类实现了这个抽象类,在子类的构造方法中,调用这个抽象类的构造方法

abstract class AsyncSequencerRequest {
    final Type mType;

    final AsyncSequencerSession mSession;

    protected AsyncSequencerRequest(Type type, AsyncSequencerSession session) {
        mType = type;
        mSession = session;
    }
class AsyncSequencerPause extends AsyncSequencerRequest {
    AsyncSequencerPause(AsyncSequencerSession session) {
        super(Type.PAUSE, session);
    }

注解

注解分为3个部分:注解声明+注解使用+注解解析

先看一个小demo

注解声明:创建了一个注解MyMessage

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMessage {

	String name() default "sam";

    int num() default 0;

    String desc();
}

注解使用:

public class AnnotationTest {

	@MyMessage(num = 10, desc = "参数b")
    private static int a;


    @MyMessage(name = "Wp test", desc = "wptest method")
    public void test() {
        System.out.println("test");
    }
}

注解解析:

public class MyMessageProcessor {

	public static void main(String[] args) {
		
		try {

            //加载annotationTest.class类
            Class clazz = MyMessageProcessor.class.getClassLoader().loadClass("com.wp.annotation.AnnotationTest");

            //获取属性
            Field[] fields = clazz.getDeclaredFields();
            //遍历属性
            for (Field field : fields) {
                MyMessage myMessage = field.getAnnotation(MyMessage.class);
                // name:sam  num:10  desc:参数b
                System.out.println("name:" + myMessage.name() + "  num:" + myMessage.num() + "  desc:" + myMessage.desc());
            }

            //获取类中的方法
            Method[] methods = clazz.getMethods();
            //遍历方法
            for (Method method : methods) {

                //判断方法是否带有MyMessage注解
                if (method.isAnnotationPresent(MyMessage.class)) {
                    // 获取所有注解 method.getDeclaredAnnotations();
                    // 获取MyMessage注解
                    MyMessage myMessage = method.getAnnotation(MyMessage.class);
                    // name:Wp test  num:0  desc:wptest method
                    System.out.println("name:" + myMessage.name() + "  num:" + myMessage.num() + "  desc:" + myMessage.desc());
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
	}
}

看了上面的demo,下面进入详解

注解声明

public @interface MyMessage表示这是一个注解声明,注解需要以@interface`声明。

Java 中提供了4种元注解:

元注解的作用是:当自定义一个自己的注解的时候,修饰自定义注解,描述自定义注解的一些属性

  • @Documented - 在JavaDoc中提供注解信息
  • @Retention - 注解的生效范围
  • @Target - 注解允许使用的对象
  • @Inherited - 注解是否可以被子类继承

元注解是实现自定义注解的重要工具,最重要的是@Retention@Target

元注解、

@Retention

元注解 @Retention 可以有如下3个属性值:

  • RetentionPolicy.SOURCE – 注解保留在源码中,编译阶段会被丢弃
  • RetentionPolicy.CLASS – 注解保留在.class文件中,但不会在运行时存在
  • RetentionPolicy.RUNTIME – 注解可以在运行时读取、使用反射可以获得

默认是RetentionPolicy.CLASS

@Target

  • @Target({ElementType.TYPE})//用于描述注解的使用范围,超出范围时编译失败。
      取值类型(ElementType):
    
        1.CONSTRUCTOR:用于描述构造器
    
        2.FIELD:用于描述域(成员变量)
    
        3.LOCAL_VARIABLE:用于描述局部变量
    
        4.METHOD:用于描述方法
    
        5.PACKAGE:用于描述包
    
        6.PARAMETER:用于描述参数
    
        7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
    

作用范围看名称基本都能对应上。默认是全部

@Repeatable

Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

(即可以重复在同一个类、方法、属性等上使用)

属性

String name() default "sam";

这个属性的含义是:自定义注解MyMessage 有一个名为name的字符串属性,使用时可以通过@MyMessage (name= "zhangsan")这一形式使用。

自定义注解的属性值只能是基本类型(short/int/long/boolean/char/String/enum/Class等)以及他们这些类型的数组。

例如,定义一个Class类型的属性

Class<?> className() default Void.class;

注意,注解如果有一个value属性的话,如下定义

 String value() default "";

本来,要通过@MyMessage (value= "123")这一形式使用。

但是,value属性比较特殊,注解使用时,如果不声明属性名,说明会赋值到value属性上。

@MyMessage ("123")

注解解析

读取注解

Java通过反射机制解析注解,java在java.lang.reflect包下新增了AnnotatedElement接口, AnnotatedElement是所有注解元素的父接口,所有的注解元素都可以通过某个类反射获取AnnotatedElement对象,该对象有一下4个方法来访问Annotation信息。

(1)<T extends Annotation> T getAnnotation(Class<T> annotationClass)

     返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。 

(2)Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

(3)boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)

    判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

(4)Annotation[] getDeclaredAnnotations()

    返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

注解解析的步骤如下:

1.通过反射,获取被注解修饰的类的Class对象

2.通过步骤1的Class对象,获得该对象的Field和Method等

3.通过该Field对象,获取修饰该Field的注解对象(getAnnotation)

4.通过步骤3的注解对象,获得注解属性值

public class MyMessageProcessor {

	public static void main(String[] args) {
		
		try {

            //加载annotationTest.class类
            Class clazz = MyMessageProcessor.class.getClassLoader().loadClass("com.wp.annotation.AnnotationTest");

            //获取属性
            Field[] fields = clazz.getDeclaredFields();
            //遍历属性
            for (Field field : fields) {
                MyMessage myMessage = field.getAnnotation(MyMessage.class);
                // name:sam  num:10  desc:参数b
                System.out.println("name:" + myMessage.name() + "  num:" + myMessage.num() + "  desc:" + myMessage.desc());
            }

            //获取类中的方法
            Method[] methods = clazz.getMethods();
            //遍历方法
            for (Method method : methods) {

                //判断方法是否带有MyMessage注解
                if (method.isAnnotationPresent(MyMessage.class)) {
                    // 获取所有注解 method.getDeclaredAnnotations();
                    // 获取MyMessage注解
                    MyMessage myMessage = method.getAnnotation(MyMessage.class);
                    // name:Wp test  num:0  desc:wptest method
                    System.out.println("name:" + myMessage.name() + "  num:" + myMessage.num() + "  desc:" + myMessage.desc());
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
	}
}

评论