比较器、枚举、注解、泛型(Java学习笔记八)

比较器

在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。

Java实现对象排序的方式有两种:

  • 自然排序:java.lang.Comparable
  • 定制排序:java.util.Comparator

Comparable接口

Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。

实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即 通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。

实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或 Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有 序集合中的元素,无需指定比较器。

public class Day08 {
    public static void main(String[] args) {
        Goods[] all = new Goods[4];
        all[0] = new Goods("《红楼梦》", 100);
        all[1] = new Goods("《西游记》", 80);
        all[2] = new Goods("《三国演义》", 140);
        all[3] = new Goods("《水浒传》", 120);
        Arrays.sort(all); 
        System.out.println(Arrays.toString(all));
    }
}

class Goods implements Comparable {
    private String name;
    private double price;

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    //按照价格,比较商品的大小
    @Override
    public int compareTo(Object o) {
        if (o instanceof Goods) {
            Goods other = (Goods) o;
            if (this.price > other.price) {//默认升序,若需要降序,-1和1互换位置即可
                return 1;
            } else if (this.price < other.price) {
                return -1;
            }
            return 0;
        }
        throw new RuntimeException("输入的数据类型不一致"); //getter、setter、toString()方法略
    }
}

Comparable 的典型实现:(默认都是从小到大排列的)

  • String:按照字符串中字符的Unicode值进行比较
  • Character:按照字符的Unicode值来进行比较
  • 数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值 大小进行比较
  • Boolean:true 对应的包装类实例大于 false 对应的包装类实例
  • Date、Time等:后面的日期时间比前面的日期时间大

Comparator

当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。

重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。

可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort), 从而允许在排序顺序上实现精确控制。

还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。

@Test
public void test1() {
    Goods[] all = new Goods[4];
    all[0] = new Goods("War and Peace", 100);
    all[1] = new Goods("Childhood", 80);
    all[2] = new Goods("Scarlet and Black", 140);
    all[3] = new Goods("Notre Dame de Paris", 120);
    Arrays.sort(all, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {//默认升序,若需要降序,改变形参位置即可
            Goods g1 = (Goods) o1;
            Goods g2 = (Goods) o2;
            return g1.getName().compareTo(g2.getName());
        }
    });
    System.out.println(Arrays.toString(all));
}

System类

System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。

由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。

System类内部包含in、out和err三个成员变量,分别代表标准输入流 (键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。

常用方法:

方法 描述
long currentTimeMillis(): 返回当前的计算机时间,距离1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status): 退出程序。其中status的值为0代表正常退出,非零代表 异常退出。
void gc(): 请求系统进行垃圾回收。
String getProperty(String key): 获得系统中属性名为key的属性对应的值。

Math类

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回 值类型一般为double型。

常用方法:

方法 描述
abs 绝对值
asin、atan、acos、tan、sin、cos 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)、min(double a,double b) 求最大值、最小值
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度

注:Math.random()*(n-m+1)+m,指定范围的随机数(m-n之间)。


BigInteger与BigDecimal

BigInteger

Integer类作为int的包装类,能存储的最大整型值为2^31-1,Long类也是有限的, 最大为2^63-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。

java.math包的BigInteger可以表示不可变的任意精度的整数。

BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。 另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。

构造器:BigInteger(String val):根据字符串构建BigInteger对象。

方法 描述
public BigInteger abs(): 返回此 BigInteger 的绝对值的 BigInteger。
BigInteger add(BigInteger val) : 返回其值为 (this + val) 的 BigInteger。
BigInteger subtract(BigInteger val) : 返回其值为 (this - val) 的 BigInteger。
BigInteger multiply(BigInteger val) : 返回其值为 (this * val) 的 BigInteger。
BigInteger divide(BigInteger val) : 返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
BigInteger remainder(BigInteger val) : 返回其值为 (this % val) 的 BigInteger。
BigInteger[] divideAndRemainder(BigInteger val): 返回包含 (this / val) 后跟 (this % val) 的两个 BigInteger 的数组。
BigInteger pow(int exponent) : 返回其值为 (this^exponent) 的 BigInteger。

BigDecimal

一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中, 要求数字精度比较高,故用到java.math.BigDecimal类。

BigDecimal类支持不可变的、任意精度的有符号十进制定点数。

构造器 :

  • public BigDecimal(double val)
  • public BigDecimal(String val)
方法 描述
public BigDecimal add(BigDecimal augend) 返回其值为 (this + augend) 的 BigDecimal。
public BigDecimal subtract(BigDecimal subtrahend) 返回其值为 (this - augend) 的 BigDecimal。
public BigDecimal multiply(BigDecimal multiplicand) 返回其值为 (this * augend) 的 BigDecimal。
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 返回其值为 (this / augend) 的 BigDecimal。(scale:精度、roundingMode:小数的保留模式)
BigInteger bi = new BigInteger("12433241123"); 
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11"); 
System.out.println(bi); // 12433241123
//System.out.println(bd.divide(bd2)); 
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP)); //1130.486
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));//1130.486454545454545,BigDecimal.ROUND_HALF_UP表示的是4舍5入。

枚举

类的对象只有有限个,确定的,关键字enum。——枚举类

当需要定义一组常量时,强烈建议使用枚举类。例如,星期:Monday(星期一)、…、Sunday(星期天);季节:Spring(春节)…Winter(冬天);等等。

若枚举只有一个对象,则可以作e为一种单例模式的实现方式。

枚举类的属性:

enum类中的常用方法:

  • values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  • valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
  • toString():返回当前枚举类对象常量的名称

使用enum关键字创建枚举类

  • 默认继承于java.lang.Enum类(故不能再继承于其他类)。
  • 枚举类的构造器只能使用private修饰符。
  • 枚举类的所有实例,必须在枚举类中显式列出,用逗号分隔分号结尾。列出的实例系统会自动添加public static final修饰。
  • 必须在枚举类的第一行声明枚举类对象。
public enum Season {
    SPRING("春天","春暖花开"),
    SUMMER("夏天","烈日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","冰天雪地");

    private final String seasonName;
    private final String seasonDesc;

    //2.私有化类的构造器,并给对象属性赋值
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
}

常用方法:

方法 描述
values() 返回枚举类型的对象数组。该方法可以用于遍历所有的枚举值。
valueOf(String str) 把字符串转为对应的枚举类对象,要求字符串必须是枚举类对象的名字,否则会报错。
toString() 返回当前枚举类对象常量的名称

实现接口的枚举类

interface Info{
    void show();
}

//使用enum关键字枚举类
enum Season1 implements Info{
    //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
    SPRING("春天","春暖花开"){
        @Override
        public void show() {
            System.out.println("春天在哪里?");
        }
    },
    SUMMER("夏天","夏日炎炎"){
        @Override
        public void show() {
            System.out.println("宁夏");
        }
    },
    AUTUMN("秋天","秋高气爽"){
        @Override
        public void show() {
            System.out.println("秋天不回来");
        }
    },
    WINTER("冬天","冰天雪地"){
        @Override
        public void show() {
            System.out.println("大约在冬季");
        }
    };

    //2.声明Season对象的属性:private final修饰
    private final String seasonName;
    private final String seasonDesc;

    //2.私有化类的构造器,并给对象属性赋值
    private Season1(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    
    //4.其他诉求1:获取枚举类对象的属性
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
}

注解(Annotation)

注解(Annotation)是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者部署。

注解(Annotation)可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在注解的“name=value”对中。

开发模式都是基于注解的。一定程度上可以说,框架=注解+反射+设计模式。

使用注解时,前面要增加**@符号**。示例如下:

  • 文档信息相关的@author、@version等。
  • 在编译时进行格式检查的@Deprecated、@Override、@SuppressWarnings等。
  • 跟踪代码依赖性,实现替代配置文件功能。例如Servlet、Spring中的一些注解。

自定义注解:使用@interface关键字,自动继承java.lang.annotation.Annotation接口。

JDK的四种元注解:用于修饰其他Annotation定义:

**1.@Retention:**用于修饰一个Annotation定义,指定该Annotation的生命周期;@Retention包含一个RetentionPolicy类型的成员变量,使用时必须指定值。

  • RetentionPolicy.RUNTIME:运行时有效。当运行java程序时,JVM会保留注释,程序可以通过反射获得该注释。
  • RetentionPolicy.SOURCE:在源文件中有效。编译器直接丢弃这种策略的注释。
  • RetentionPolicy.CLASS:在class文件中有效。当运行java程序时,JVM不会保留注释,这是默认值

**2.@Target:**用于修饰一个Annotation定义,指定该Annotation能用于修饰哪些元素。3.@Retention也包含一个名为value的成员变量。

  • CONSTRUCTOR:用于描述构造器。
  • FIELD:用于描述域。
  • LOCAL_VARIABLE:用于描述局部变量。
  • METHOD:用于描述方法。
  • PACKAGE:用于描述包。
  • PARAMETER:用于描述参数。
  • TYPE:用于描述类、接口(包括注解类型)或enum声明。
  • TYPE_PARAMETER:类型变量的声明语句中(如泛型声明)。
  • TYPE_USE:使用类型的任何语句中。

**3.@Inherited:**被它修饰的Annotation将具有继承性。如果某个类使用了@Inherited修饰的Annotation,其子类自动具有该Annotation。

**4.@Documented:**指定该元Annotation修饰的Annotation类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。(定义为Documented的Annotation必须设置Retention的值为RUNTIME)

@MyAnnotation(value="刘星宇")
public class MyAnnotationTest {
    public static void main(String[] args) {
        Class clazz = MyAnnotationTest.class;
        Annotation a = clazz.getAnnotation(MyAnnotation.class);
        MyAnnotation m = (MyAnnotation) a;
        String info = m.value(); 
        System.out.println(info);//刘星宇
} }

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation{
    String value() default "张三";
}

JDK5.0之后,在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。程序可以调用AnnotatedElement对象的如下方法来访问Annotation 信息:

  • getAnnotation(Class annotationClass)
  • getAnnotations()
  • getDeclaredAnnotations()
  • isAnnotationPresent(Class annotationClass)

JDK8.0对注解处理提供了改进:可重复的注解@Repeated(Annotation.class)和可用于类型的注解。JDK8之前注解只能是在声明的地方使用,JDK8之后注解可以使用在任何地方。


泛型

泛型背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就 是类型参数,即泛型。

泛型概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。

从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念, 允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持, 从而可以在声明集合变量、创建集合对象时传入类型实参。

泛型作用

1.解决元素存储的安全性问题,好比商品、药品标签,不会弄错。

2.解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。

3.使用泛型的主要优点是能够在编译时而不是在运行时检测错误。

集合中使用举例:

ArrayList<Integer> list = new ArrayList<>();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);

Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
     System.out.println(iterator.next());
}
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
// map.put(33, "Tom");//添加失败
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String,Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
    Map.Entry<String,Integer> entry = iterator.next();
    System.out.println(entry.getKey() + "--->" + entry.getValue());
}

自定义范型结构

自定义泛型类/接口:

声明方式:interface List 和 class GenTest(T只能是类,不能用基本数据类型填充。但可以使用包装类填充)

如果定义了泛型类,实例化没有指定类的泛型,则认为此类泛型类型为Object类型。建议在实例化时指定泛型:

public class Order<T> {
    String orderName;
    int orderId;
    T orderT;
    public Order(){

    }
    public Order(String orderName, int orderId, T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }
}//get/set省略

Order<String> order = new Order<String>();
order.setOrderT("abc");

如果涉及到继承,由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不需要在指定。如果未指定泛型类型,则该子类仍是泛型,实例化子类对象时,仍需指定类型。

class SubOrder1 extends Order<String>{
    ......
}
SubOrder1 sub1 = new SubOrder1();
sub1.setOrderId("abc");

class SubOrder2<T> extends Order<T>{
	......
}
SubOrder2<String> sub2 = new SubOrder2<String>();
sub2.setOrderId("abc");

注意点:

  • 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: 。上述举例只写了一个参数。

  • 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

  • 泛型不同的引用不能相互赋值。尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有 一个ArrayList被加载到JVM中。

  • //list1和list2不能相互赋值
    ArrayList<Integer> list1 = null;
    ArrayList<String> list2 = null;
    
  • 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。**经验:**泛型要使用一路都用。要不用,一路都不要用。

  • 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

  • jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();

  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  • 在静态方法中不能使用类的泛型。

  • 异常类不能是泛型的。

  • 不能使用new T[]。但是可以:T[] elements = (T[]) new Object[10]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。

  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    • 子类不保留父类的泛型:按需实现
      • 没有类型 擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留
  • 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

自定义泛型方法

方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

泛型方法可以声明为静态,因为其泛型参数是在调用方法时确定,而不是实例化时确定。

[访问权限] <泛型> 返回类型 方法名([泛型标识参数名称]) 抛出的异常

public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o);
	} 
}
public static void main(String[] args) {
    Object[] ao = new Object[100];
    Collection<Object> co = new ArrayList<Object>();
    fromArrayToCollection(ao, co);
    
    String[] sa = new String[20];
    Collection<String> cs = new ArrayList<>();
    fromArrayToCollection(sa, cs);
    
    Collection<Double> cd = new ArrayList<>();
    // 下面代码中T是Double类,但sa是String类型,编译错误。
    fromArrayToCollection(sa, cd);
    
    // 下面代码中T是Object类型,sa是String类型,可以赋值成功。
    fromArrayToCollection(sa, co);
}

泛型在继承方面的体现

虽然类A是类B的父类,但是G和G不具备子父类关系,是并列关系,不可相互赋值。

Object o1 = null;
String o2 = null;
o1 = o2;//编译通过

ArrayList<Object> list1 = null;
ArrayList<String> list2 = null;
list1 == list2;//编译错误

补充:A 和B 是子父类关系。

通配符

使用类型通配符: 比如:List ,Map

List< ? >是List、List等各种泛型List的父类。即类A是类B的父类,G和G是没有关系的,二者共同的父类是G< ? >。

读取List的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。

写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。

有限制的通配符

通配符指定上限——上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=。

通配符指定下限——下限super:使用时指定的类型不能小于操作的类,即>=。

举例:

  • (无穷小 , Number] 只允许泛型为Number及Number子类的引用调用
  • [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用
  • 只允许泛型为实现Comparable接口的实现类的引用调用
List<? extends Order> list1 = null;
List<? super Order> list2 = null;
List<SubOrder> list3 = null;
List<Order> list4 = null;
List<Object> list5 = null;

list1 = list3;//编译通过
list1 = list4;//编译通过
list1 = list5;//编译错误

list2 = list3;//编译错误
list2 = list4;//编译通过
list2 = list5;//编译通过

你可能感兴趣的:(Java)