Java编程思想(一)

this关键字

  1. 成员变量与参数同名时,使用 this 指定为成员变量
  2. 除构造器外,编译器禁止从其他任何地方使用 this 调用构造器
  3. 使用this调用构造器:
    1. 可以使用 this 调用一个构造器(不能调用两个)
    2. 调用构造器必须位于起始处

初始化顺序

  1. 数组初始化:
    1. 最后的一个逗号可以保留
    2. 基本数据类型值会自动初始化为空值(数字:0,布尔:false)
  2. 代码块:
    1. 静态代码块:与成员静态变量完全相同 ==> 仅会在必要执行一次(类首次加载或静态方法被调用)
    2. 非静态代码块:用来初始化每一个对象的非静态变量(与非静态成员变量同,确保一定会在每一次构造方法前执行运行
  3. 成员变量:
    1. 静态成员变量仅在必要时初始化(对象创建或第一次访问静态数据时才会初始化;且只会初始化一次)
    2. 非静态成员变量每次创建对象时都会进行初始化,且都在构造方法前顺序执行
// 编译正常
Integer[] a = {1, 2, 3,};
Integer[] b = new Integer[]{1, 2, 3,};
Integer[] c = new Integer[]{1,};

// 静态代码块
static {
    cup1 = new Cup("A");
    cup2 = new Cup("B");
}

// 实例化初始化
{
    cup1 = new Cup("A");
    cup2 = new Cup("B");
}

// 编译顺序
public class Window {
    static {
        // 1、静态代码块最先编译(类加载即执行)
    }
    // 静态成员变量仅在必要时初始化(对象创建或第一次访问静态数据时才会初始化;且只会初始化一次)
    static Window sta = new Window();
    // 非静态成员变量
    Window w = new Window();
    {
        // 2、非静态代码块与非静态成员变量顺序执行,都在构造函数之前执行
    }
    Window(){
        // 3、构造函数在静态代码块及非静态代码块后执行
    }
}

// 方法重载
char ch = 1;

访问权限

··同一个类同一个包不同包的子类不同包的非子类
public
protected
default
private

复用类

  1. @Override 注解是 JavaSE5 加入的
  2. 除了内存以外,不能依赖垃圾回收器做任何事情;如果需要清理,最好编写自己的清理方法,但不要使用 finalize() 方法
  3. 子类初始化之前,一定会保证基类先行初始化(必须隐式|显式进行调用);Java 会自动在子类(导出类)的构造器中插入对父类(基类)构造器的调用
  4. main方法访问:
    1. 即使是一个程序中含有多个类,只有命令行所调用的那个类的main()方法会被调用
    2. 即使Clazz类不是一个public类,如果命令行是java Clazz,那么Clazz.main()仍然会被调用
    3. 即使一个类只具有包访问权限,其public main()仍然是可以访问的
  5. 继承:is-a,组合:has-a【大多数情况下,子类会有自己的特有域,新的可以被称为:is-like-a像一个】
  6. 继承与组合的选择:优先使用组合,尤其是不能十分确定应该使用哪一种方式时
  7. 向上转型:将导出类(子类)引用转换为基类(父类)引用
  8. 继承其实不太常用,如何确认?确认是否需要从导出类向基类进行向上转型
  9. final关键字:数据、方法、类
    1. 一个既是static又是final的域只占据一段不能被改变的存储空间 ==> 常量
    2. 基本类型:final使数字恒定不变;对象引用:final使引用不变(一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象;然而,对象其自身却是可以改变的,Java并未提供使任何对象恒定不变的路径
    3. 空白final:声明为final但又未给定初始值(编译器能确保空白final在使用前必须被初始化) ==> Spring依赖中使用构造函数进行依赖组装
    4. final参数:无法在方法中更改参数引用所指向的对象(主要用来向匿名内部类传递数据
    5. final方法:只有在想要明确禁止覆盖时,才将方法设置为final的;所有private方法都隐式的指定为finalfinal方法对程序的执行效率没有明确的提升
    6. final类:禁止被继承
  10. 数组也是引用
  11. java构造方法

有两个构造方法,分别是实例化构造方法(instance初始化方法)和Class对象构造方法

实例化构造方法:负责完成实例字段的初始化

Class对象构造方法:可以理解为Class(静态)构造方法,负责静态字段的初始化(Java编译器自动生成)

每个对象都会对应有且仅有一个Class对象,即Class对象是单例的。所以在第一次创建对象时,需要先创建Class对象(类加载过程完成,调用的就是Class对象构造方法);后续继续创建该Class的实例对象时,就不需要再创建Class对象了。

初次使用(类加载)之处也是static初始化发生之处。所有的static对象和static代码段都会在类加载时依定义顺序执行;static域只会被初始化一次(类仅会被加载一次)

构造函数

多态

  1. 动态绑定:在运行时根据对象的实际类型进行绑定(Java中除了staticprivate方法【private方法同属于final方法】之外,其他所有方法都是动态|后期|运行时绑定 ==> 自动进行
  2. 只有非private方法才可以被覆盖(private方法默认是final的);导出类中,对于基类中的private方法最好采用不同的名字
  3. 编写构造器时有一条有效的准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法,(在构造器内唯一能够安全调用的那些方法是基类中的final方法【含private方法】)
  4. Java语言中,所有类型转换都会得到检查;即使是一次普通的加括弧类型转换,进入运行期时仍然会对其进行检查;如果转换失败,会返回ClassCaseException异常。在运行期对类型进行检查的行为被称为运行时类型识别(RTTI)
  5. 协变返回类型:子类(导出类)中的被覆盖方法可以返回父类(基类)方法的返回类型的某种导出类型

接口

  1. 抽象类:如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。
  2. interface关键字产生了一个完全抽象的类,不提供任何具体实现
  3. 接口被用来建立类与类之间的协议
  4. 接口也可以包含域,这些域隐式的都是staticfinal
  5. 使用接口的原因:
    1. 为了能够向上转型为多个基类型(以及由此而带来的灵活性)
    2. 防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口
  6. 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类(如果知道某事物应该成为一个基类,第一选择应该定义为接口)
  7. 接口可以嵌套在类或其他接口中
    1. 接口嵌套在类中:可以将接口定义为private
    2. 接口嵌套在接口中:自动声明为public
  8. 当实现某个接口时,并不需要实现嵌套在内部的任何接口。且private接口不能在定义它的类之外被实现
  9. 接口可以多重继承
  10. 恰当的原则应该是优先选择类而不是接口

内部类

  1. JDK11+中,从所在的外部类静态方法创建某个非静态内部类的对象,可以不用按照OuterClassName.InnerClassName书写;在其他独立的类中必须遵从此规则

  2. 生成一个非静态内部类对象

    1. 能访问其外围对象的所有成员
    2. 拥有其外围类的所有元素的访问权
    public class Outer{
        public class Inner{
            public Outer outer(){
             	// 通过 OuterClassName.this 可以在内部类中返回当前的外部类对象   
                return Outer.this;
            }
        }
        public static void main(String[] args) {
            Outer ou = new Outer();
            // 非静态内部类依赖于外部类对象使用 .new 生成内部类对象
            // 在当前外部类或其他独立类中均生效
            Inner inner = ou.new Inner();
        }
    }
    
  3. .this:在非静态内部类中正确返回当前所属外部类对象,可利用OuterClassName.this实现

  4. .new:通过外部类对象创建非静态内部类对象,可利用OuterClassInstance.new InnerClassName()实现

    // 为匿名内部类实现构造器行为
    abstract class Base{
        public Base(int i){
            System.out.println("Base constructor, i= " + i);
        }
        public abstract void f();
    }
    
    public class Outer{
        public static Base getBase(int i){
            return new Base(i){
                
                {
                    // Java的类加载顺序保证了优先调用父类构造,此处子类构造被替换为实例初始化
                    // 效果上等同于当前匿名内部类拥有了自己的构造
                    System.out.println("Inside instance initializer");
                }
                
                @Override
                public void f() {
                    System.out.println("In anonymous f()");
                }
            };
        }
    }
    
  5. 分类(按照书写位置分类)

    1. 非静态内部类(局部|匿名)对象隐式的保存了一个指向创建它的外围类对象引用,在此作用域内,内部类有权操作所有的成员,包括private成员
    2. 静态内部类(嵌套类)
      1. 要创建嵌套类对象,并不需要其外围类的对象
      2. 不能从嵌套类的对象中访问非静态的外围类对象
      3. 嵌套类可以包含static的数据和字段
      4. 嵌套类可以作为接口的一部分
    3. 如果想要创建某些公共代码,使得它们可以被该接口的所有实现公用,推荐使用接口内的嵌套类实现
    4. 一个内部类(非静态)被嵌套多少层并不重要 – 它能透明的访问所有它所嵌入的外围类的所有成员
    1. 局部内部类:定义在方法的作用域内(此内部类除作用域不同,其他与一般类完全相同)。局部内部类不能有访问说明符
    2. 匿名内部类:与正规的继承类相比有些受限,虽既可以扩展类,也可以实现接口,但二者不能兼备;如果为实现接口,也只能实现一个接口
    3. 成员内部类:(非)静态内部类
  6. 为什么需要内部类?每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响

  7. 内部类继承

    // 内部类继承
    class WithInner{
        class Inner{
            // 内部类
        }
    }
    
    // 直接继承内部类
    public class InheritClass extends WithInner.Inner{
       // 无法使用默认的构造器实现,必须提供外围类的引用
       InheritClass(WithInner wi) {
           // 提供外围类对象的引用
           wi.super();
       }
    }
    
    // 非静态内部类任意继承不影响
    
  8. 内部类可以被覆盖吗?

    // 1、当继承了某个外围类的时候,内部类并没有什么变化
    // 2、不同的内部类是完全独立的,各自在自己的命令空间内
    // 3、可以明确的继承某个内部类
    class Egg{
        protected class Yolk{
            // 这是一个内部类
        }
    }
    
    // 4、当前外围类必须同时继承被继承内部类的外围类
    class BigEgg extends Egg{
        public class Yolk extends Egg.Yolk{
            // 继承自另一个内部类
        }
    }
    
  9. 局部内部类匿名内部类

    1. 局部内部类可以拥有具名构造器或重载构造器;匿名内部类只能用于实例初始化
    2. 局部内部类可以创建多个对象
  10. 如果内部类是匿名的,编译器会简单的产生一个数字作为类名标识符,结果:OuterClassName$Num.class

持有对象

  1. 使用@SuppressWarnings注解及其参数表示只抑制有关“不受检查的异常”的警告信息
  2. 如果一个类没有显式的声明继承自哪个类,那么它自动的继承自Object
  3. Java容器类库的用途是“保存对象”,划分为两个不同的概念
    1. Collection:元素序列,List按照插入顺序保存元素、Set不能有重复元素、Queue按照排队规则确定对象产生顺序
    2. Map:一组成对的“键值对”对象,允许使用键来查找值;也可以叫做映射表字典
  4. Set中只有元素不存在的情况下才会添加:TreeSet默认按照字典序升序保存对象,LinkedHashSet保留了添加顺序(TreeMapLinkedHashMap同)
  5. 推荐使用的工具类:java.util.Arraysjava.util.Collections
  6. Arrays.asList说明:
    1. 充当了一个基于数组的API和Collection API的桥梁,该方法仅仅是为了让数组能够用上Collection API,但其本质(底层)还是数组
    2. 返回的 ArrayList 是一个内部类,不是标准的 java.util.ArrayList
    3. 泛型指定:Arrays.<T>asList(T... t)
  7. Collections.addAll执行效率很高The behavior of this convenience method is identical to that of c.addAll(Arrays.asList(elements)), but this method is likely to run significantly faster under most implementations
  8. List
    1. ArrayList:长于元素随机访问,但是在List中间插入和移除元素较慢 – 数组
    2. LinkedList:在List中插入和移除元素代价较低,提供了优化的顺序访问;随机访问速度较慢 – 链表;可以作为队列使用
  9. 迭代器能够将遍历序列的操作与序列底层的结果分离,迭代器统一了对容器的访问方式
  10. 迭代器Iterator只能向前移动;ListIterator只能用于各种List的访问,可以实现双向移动
  11. Stack,"栈"通常指后进先出 LIFO的容器,有时也称为叠加栈
  12. Queue队列是一个典型的先进先出 FIFO容器;先进先出是最典型的队列规则PriorityQueue优先级队列,通过默认的自然顺序或自定义Comparator实现插入元素时同步维护元素优先级
  13. 创建任何实现了Iterable的类,都可以用于foreach语法中(数组除外)
  14. 各种Queue的行为,由LinkedList提供支持

异常

public static void f() throws Exception {
    System.out.println("Originating the exception in f()");
    throw new Exception("Throw from f()");
}

public static void g() throws Exception {
    try {
        f();
    } catch (Exception e) {
        System.out.println("Inside g().e, e.printStackTrace()");
        e.printStackTrace(System.out);
        // 重新抛出异常
        throw e;
    }
}

public static void h() throws Exception {
    try {
        f();
    } catch (Exception e) {
        System.out.println("Inside.h(), e.printStackTrace()");
        e.printStackTrace(System.out);
        // 把当前调用栈信息填入原来的异常对象(有关原来的异常发生点信息会丢失)
        // todo 调用 fillInStackTrace 的这一行就成了异常的新发生地点了
        throw (Exception) e.fillInStackTrace();
    }
}

// 异常丢失
public static void i(){
    try{
        System.out.println("异常丢失");
        try{
            // 这个方法抛出异常1
            a.occurException1();
        }finally{
            // 这个方法抛出异常2
            a.occurException2();
        }
    }catch(Exception e){
        // 这里最后只能捕捉到 finally 中产生的异常2,异常1被丢掉了
        System.out.println(e);
    }
}

// 构造器中发生异常时 finally 的使用规则
// 在创建需要清理的对象之后,立即进入一个 try-finally 语句块
public static void main(String[] args) {
    try {
        InputFile in = new InputFile("Cleanup.java");
        try {
            String s;
            int i = 1;
            while ((s = in.getLine()) != null) {
                System.out.println("对文件的一些操作");
            }
        } catch (Exception e) {
            System.out.println("Caught exception in main()");
            e.printStackTrace(System.err);
        } finally {
            // 构造阶段可能抛出异常,且要求清理资源,强烈建议使用嵌套try-catch-finally子句
            in.dispose();
        }
    } catch (Exception e) {
        System.out.println("InputFile construction failed!");
    }
}
  1. 能够抛出任意类型的Throwable对象,分为两种类型:Error表示编译时和系统错误,一般不用关心;Exception是可以被抛出的基本类型

  2. 通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容(最重要的部分就是异常类型

  3. 异常处理理论上有两种基本类型:

    1. 终止模型:错误无法挽回,不能继续正常执行程序
    2. 恢复模型:异常处理程序修正错误,然后重新尝试调用出问题的方法,并认为第二次调用能成功(不实用
  4. 分类:虚拟机能处理的 – 不受检查异常;虚拟机不能处理的 – 被检查异常???

    1. 被检查的异常:编译时被强制检查的异常,直接extends Exception实现;必须要在方法上主动throws指明
    2. 不受检查异常:即运行时异常RuntimeExcpetion及其子类,会自动被Java虚拟机抛出,不必在异常说明将它们列出来;这种异常属于编程错误,通常不用在代码中捕获,但是可以抛
  5. 可以声明方法将抛出的异常,实际上却不抛出,为异常先占个位置;定义抽象基类和接口时可以进行异常预留

  6. 栈轨迹printStackTrace()方法所提供的信息可以通过getStackTrace()方法访问,将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用;数组中的最后一个元素和栈底是调用序列中的第一个方法调用

  7. 重新抛出异常:

    1. 调用fillInStackTrace()的代码行会成为新的异常发生处
    2. 若在捕获异常后抛出另一种异常,得到的效果类似于使用fillInStackTrace(),且有关原来的异常发生点的信息会丢失
    3. 如果希望新的异常能保留原始的异常信息(异常链),使用含*cause(因由)*的构造函数即可实现

    Throwable的子类中,只有三种基本的异常类提供了带cause的构造器:ErrorExceptionRuntimeException。如果要把其他类型的异常链接起来,应该使用initCause方法而不是构造器

  8. Exception对象都是使用new在堆上创建的对象,垃圾回收器会自动执行清理

  9. 无论try块中的异常是否抛出,finally中的语句总能被执行(可用于实现重试机制释放资源

  10. 当涉及breakcontinue语句的时候,finally子句也会得到执行(如果finally子句中存在return语句,那么此返回会覆盖所有其他的return – 不建议在finally中使用return

  11. 异常丢失

  12. 当覆盖方法时,只能抛出在基类方法的异常说明里列出的异常(也可以不抛出异常或抛出更小的异常

  13. 一个方法同时存在于抽象基类和接口中(两种方法定义抛出不同类型的异常),子类继承并实现时,接口方法抛出的异常不能改变抽象基类中的异常(两者的异常必须相同或子类不抛出任何异常

  14. 对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的try-catch-finally子句

  15. 异常匹配:抛出异常时,异常处理系统会按照代码编写顺序找出“最近”的处理程序(匹配成功即不再继续查找)【派生类的对象也可以匹配其基类的异常处理程序】

  16. 被检查异常处理方式;

    1. 被检查异常转换为不检查的异常(封装RuntimeException(e)
    2. 创建自己的extends RuntimeException异常

字符串

public class TestClazz{
    
    private Formatter formatter;
    
    private String name;
    
    public void testFormat(int x, int y){
        // 内容输出格式化
        formatter.format("The turtle %s is moving to (%d, %d)", x, y);
        // 更复杂的格式化
        formatter.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
    }
    
    public String toString(){
        // 无意识的递归:编译器尝试将this转换为String进行拼接(转换方式为 this.toString())
        //return " TestClazz address: " + this + "\n";
        // 输出当前对象的内存地址,正确的方式是调用 Object.toString()
        return " TestClazz address: " + super.toString() + "\n";
    }
    
    public static void main(String[] args){
        // Formatter构造时接受一个输出内容目的地的参数;存在多种重载
		Formatter f = new Formatter(System.out);
    }
}
  1. 不可变的StringString对象是不可变的,String类中每一个看起来会修改String值的方法,实际上都会创建一个全新的String对象,以包含修改后的字符串内容,只读特性

  2. 每当把String对象作为方法的参数时,都会复制一份引用,而盖引用所指的对象其实一直都在单一的物理位置上,从未改变

  3. 用于String++=Java中仅有的两个重载过的操作符,Java并不允许程序员重载任何操作符

    1. ++=会被重载为使用StringBuilder实现
    2. 如果可以预知最终的字符串有多长,预先指定StringBuiler的大小可以避免多次重新分配缓冲
  4. StringBuffer:线程安全,开销更大

  5. 如果要在toString()方法中使用循环,最好自己创建一个StringBuilder对象

  6. 如果希望toString()方法打印对象内存地址,可以考虑使用this关键字

  7. 无意识的递归

  8. 格式化输出:

    1. System.out.format()
    2. System.out.printf()
  9. Formatter:在Java中所有新的格式化功能都由java.util.Formatter类处理,接受一个参数表明内容的输出目的地,常用的包括PrintStreamOutputStreamFile

  10. 格式化符说明:%[argument_index$][flags][width][.precision]conversion

    1. 默认情况下数据右对齐,可以通过-改变对齐方向
    2. width应用于各种数据类型的数据转换,且行为方式都相同
    3. precision不是所有类型数据都支持;对于不同的数据类型,其意义也不相同;对整数应用precision会触发异常
  11. 正则表达式

    1. 匹配一个反斜杠需要四个反斜杠

      \\\\,第一个:转义符,第二个:斜杠本身,第三个:转义符,第四个:斜杠本身

      \Java和正则表达式中都是转义字符,所以表示斜杠时都需要书写为:\\

      理解:正则中的\,使用字符串表示即\\,而每一个斜杠都需要一个转义符,结果即\\\\表示一个斜杠

    2. :用括号划分的表达式,组号0表示整个表达式,组号1表示被第一队括号括起的组,以此类推

      A(B(C))D:有三个组,组0是ABCD;组1是BC;组2是C

      java.util.Matcher提供了方法获取组相关信息,groupCount(),不含组0group()。。。

    3. 几个特殊符号

      字符作用
      \n换行
      \r回车
      \t制表(相当于tab)
      \f换页
    4. Scanner定界符:默认情况下,Scanner使用空白字符对输入进行分词,方法useDelimiter()可以自定义定界符

类型信息

// 初始化的时机
public class Initialization{
    // static final 的编译期常量读取时不会触发初始化
    static final int staticFinal = 47;
    // 非编译器常量,读取时会触发初始化
    static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
    // static非final域,读取时需要执行
    static int staticNonFinal = 147;
    
    // JDK11+会产生告警
    Class<Integer> intClass1 int.class;
    Class<Integer> intClass2= int.class;
    // Integer extends Number,但Number Class域Integer Class之间没关系
    // Class<Number> numberClass = int.class;
    // 使用通配符?,好处是实现选择非具体的类引用(不会产生任何的告警)
    Class<?> numberClass = int.class;
    // 创建类型子类型,? extends T 创建一个范围(规定上限)
    Class<? extends Number> bounded = int.class;

    // ? super T 也是创建一个范围(规定下限)
    // FancyToy extends Toy
    Class<FancyToy> fancyToyClass = FancyToy.class;
    // 可以直接获取到具体类型
    FancyToy fancyToy = fancyToyClass.getDeclaredConstructor().newInstance();
    Class<? super FancyToy> superClass = fancyToyClass.getSuperclass();
    // todo 只允许将超类声明为“某个类,是FancyToy的超类(非接口)”
    // Class<Toy> superclass2 = fancyToyClass.getSuperclass();
    // 这里就只能是 Object 了
    Object object = superClass.getDeclaredConstructor().newInstance();
}
  1. 运行时类型识别RTTI,Run-Time Type Identification):在运行时,识别一个对象的类型。(在Java中,所有的类型转换都是在运行时进行正确性检查的)

    Java中每个对象都有相应的Class对象,因此,可通过Class对象知道某个对象‘真正’所属的类;无论我们对引用进行怎样的类型转换,*对象本身所对应的Class*对象都是同一个。当我们通过某个引用调用方法时,Java总能找到正确的Class类中所定义的方法,并执行该Class中的代码。由于Class对象的存在,Java不会因为类型的向上转型而迷失,这也是多态的原理

  2. Java使用Class对象来执行其RTTI,即使正在执行的是类似转型的操作

  3. 对于基本数据类型的包装器类,有一个标准字段TYPE,是一个引用,指向对应的基本数据类型的Class对象

    基本数据类型包装器类
    boolean.classBoolean.TYPE
    char.classCharacter.TYPE

    以上两种表示方式是等效的。(建议使用.class的形式,保持与普通类的一致性

  4. 加载类的准备工作包含三个步骤:

    1. 加载:由类加载器执行,查找字节码并从字节码创建一个Class对象
    2. 链接验证类中的字节码,为静态域分配存储空间,必须的话,将解析这个类创建的对其他类的所有引用
    3. 初始化:若该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
  5. 初始化触发

    1. 使用.class来创建Class对象的引用时,不会自动的初始化该Class对象
    2. 使用Class.forName会立即就触发初始化
    3. 读取一个static final的值(必须是编译期常量),不会对所在类进行初始化
    4. 如果一个static域为非final,读取之前需要进行链接(分配存储空间)和初始化(初始化该存储空间)
  6. 泛型语法应用于Class对象时(参考上面的代码说明)

    1. 如果是? extends TnewInstance()将返回该对象的真实类型
    2. 如果是? super TnewInstance()仍旧只能返回Object
  7. 新的类型转换语法:java.lang.Class#cast

  8. RTTI表现形式

    1. 传统的强制类型转换
    2. 代表对象的类型的Class对象。通过查询Class对象获取运行时所需的信息
    3. 关键字instanceof
  9. instanceofisInstance结果完全等效

  10. 在动态代理上所作的所有调用都会被重定向到单一的调用处理器(InvocationHandler)上,它的工作是揭示调用的类型并确定响应的策略

  11. interface实现为私有内部类、匿名类都无法阻止通过反射在运行过程中的修改操作

泛型

// 泛型类定义
public class GenericClazz<T>{
    
    public static <K, V> Map<K, V> map() {
        return new HashMap<>();
    }
    
    // 泛型数组
	// 使用类型标记 Class<T> 执行数组的创建以便从擦除中进行类型恢复
    // 泛型数组的运行时类型只能是 Object[]
    private T[] array;
    public GenericClazz(Class<T> type, int sz) {
        array = (T[])Array.newInstance(type, sz);
    }
    
    public static void main(String[] args) {
        // 自动类型推断
        Map<String, List<? extends Pet>> map = New.map();
    }
}

// 泛型方法
public <T> void f(T t){
    // 泛型方法定义:需要将泛型参数列表置于返回值之前
}

// 泛型多边界定义
class MultiBounded<T extends ConcreteOrAbstractClazzOrInterface & Interface1 & Interface2>{
    // 泛型边界支持多边界
    // 1、必须具体类在前,接口在后
    // 2、具体类可以是普通类或抽象类或接口
    // 3、具体类只能存在一个,接口可以多个
}

class Fruit{}
class Apple extends Fruit{}
class Plate<T>{
    private T item;
    public Plate(T item) {
        this.item = item;
    }
    public T get() {
        return this.item;
    }
    public void set(T item) {
        this.item = item;
    }
    public static void main(String[] args) {
        // p是协变的
        // JVM会使用一个占位符 CAP#1 来表示p接受一个Fruit或子类,运行时通过 CAP#1 把类型擦除了
        Plate<? extends Fruit> p = new Plate<>(new Apple());
        p.set(null);
        // 无论如何都不能赋值一个 CAP#1 类型  ==> Java类不存在一个公共的子类😪
        // p.set(new Fruit());
        // p.set(new Apple());
        // CAP#1 表示的是 Fruit及其子类
        Fruit fruit = p.get();
        Object object = p.get();
        // Apple apple = p.get();
    }
}

// 1、Payable<Employee>与Payable<Hourly>泛型移除后简化为相同的类Payable,下面的代码编译报错
// 2、如果移除Payable<T>的泛型,编译正常(有趣。。。)
interface Payable<T>{}
class Employee implements Payable<Employee>{}
//class Hourly extends Employee implements Payable<Hourly>{}

// CRG
class BasicHolder<T> {}
class Subtype extends BasicHolder<Subtype> {
    // 基类用导出类替代其参数(泛型基类变成了一种其所有导出类的公共功能的模板)
}
// 自限定
class SelfBounded<T extends SelfBounded<T>>{
    // 强制泛型当作其自己的边界参数来使用;只能强制作用于继承关系
    // 自限定希望的用法:class A extends SelfBounded<A>{}
}

// 参数协变
interface SelfBoundSetter<T extends SelfBoundSetter<T>>{
    void set(T arg);
}
interface Setter extends SelfBoundSetter<Setter>{}
void test(Setter s1,Setter s2,SelfBoundSetter sbs){
    s1.set(s2);
    // 使用了自限定类型,编译器不能识别将基类型当作参数传递目标方法的尝试。
    // 不使用自限定类型,普通的继承机制会介入,能够实现重载
    // s1.set(sbs);
}
  1. 元组(tuple):与List同,都可用于数据存储,包含多个数据。不同的是:列表只能存储相同的数据类型,元组可以存储不同的数据类型,如同时存储intstring,list等,并且可以根据需求无限扩展

  2. 泛型可以使用在类或方法上

  3. 应该尽量使用泛型方法

  4. static的方法无法访问泛型类的类型参数;如果static方法需要使用泛型能力,就必须使其称为泛型方法

  5. 类型参数推断:使用泛型类时,必须在创建对象时指定类型参数的值;使用泛型方法时,编译器会自动推导出具体类型

  6. 类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法调用结果作为参数传递给另一个方法,此时编译器只会认为接受的是一个Object类型的变量

  7. 在泛型代码内部,无法获得任何有骨干泛型参数类型的信息。Java泛型是使用擦除来实现的,任何具体的类型信息在运行时都被擦除了,都会称为原生类型泛型类型参数将擦除到它的第一个边界

  8. 泛型并不是Java语言的一种特性,它是Java泛型实现中的一种折中;泛型类型只有在静态类型检查期间才出现

  9. 泛型兼容性:泛型化的代码必须能够与非泛型化的代码共存。使用擦除是目前唯一可行的解决方案(从非泛化代码到泛化代码的转变过程;在不破坏现有类库的情况下将泛型融入Java语言)

  10. 在泛型中创建数组,推荐使用Array.newInstance()

  11. 即使擦除在方法或类内部移除了有关实际类型的信息,编译器仍然可以保证在方法或类中使用的类型的内部一致性

  12. 不能创建泛型数组。一般的解决方案为在任何想要使用数组的地方都使用ArrayList

  13. 成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其进行转型(转型后生成的数组并不能做任何操作;不管怎么转换,实际的运行时类型始终是Object[]。数组在运行时类型只能是Object[]

  14. 泛型数组在运行时只能是Ojbect[];直接获取其数组同时会触发编译器警告及运行时ClassCastException,原因是运行时无法获取其真实类型。使用类型标记Class<T>创建数组不能改变运行时类型,但可以从类型擦除中恢复

  15. 泛型边界:重用了extends关键字;可以定义多边界通配符(?)被限定为单一边界

  16. 通配符**?与类型参数T**区别:

    1. <? extends T>不能往里存,只能能往外取(被称作协变

      • 往里存:不能调用<? extends T>泛型类的以T为形参的方法

      • 往外取:可以调用<? extends T>泛型类的以T为返回值的方法

        不能往里存的根本原因是因为,Java没有一个共同的子类,却有一个共同的父类Object

        所以永远可以向上转型取数据,却不能向下转型存数据

    2. <? super T>不影响往里存,但是往外取却只能放在Object(被称为逆变

      • 容器中存放的是T的任意基类,但是不确定是哪一个基类

      • 往里存:往容器中放T的子类一定成立(这些类一定是<? super T>的子类)

      • 往外取:编译器不知道具体存放的类,所以取出来的是所有类的共同父类Object

    3. PECS(Producer Extends Consumer Super)

      1. Producer Extends:当前类主要作为生产者向外提供数据,使用extends
      2. Consumer Super:当前类主要作为消费者,需要消费(吃进)数据,使用super
  17. List<?>List

    1. List实际上表示“持有任何Object类型的原生List
    2. List<?>表示“具有某种特定类型的非原生List,只是不知道那种类型是什么”
  18. 任何基本类型都不能作为类型参数<T>;自动包装机制不能应用于数组

  19. 古怪的循环泛型(CRG):基类用导出类替代其参数(泛型基类变成了一种其所有导出类的公共功能的模板)

  20. 自限定:强制泛型当作其自己的边界参数来使用;只能强制作用于继承关系

  21. 参数协变:使用了自限定类型,编译器不能识别将基类型当作参数传递目标方法的尝试(此方法接收基类作为参数)

  22. 类型参数可能会在一个方法的throws子句中用到

  23. 混型:混合多个类的能力,以产生一个可以表示混型中所有类型的类。价值之一是它们可以将特性和行为一致的应用于多个类之上。基于继承实现。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值