类,超类,子类

super

  1. super与this
    有些人认为 super 与 this 引用是类似的概念, 实际上, 这样比较并不太恰当。这是因为super 不是一个对象的引用, 不能将 super 赋给另一个对象变量, 它只是一个指示编译器调用超类方法的特殊关键字。

  2. super与构造
    当子类构造的时候,由于不能对其超类的私有成员进行访问,只能调用超类的构造器。如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数)的构造器。

超类与子类引用关系

超类可以引用子类,但是,不能将一个超类的引用赋给子类变量。在下面的例子中,超类虽然引用了子类,变量staff[0]与boss引用同一个对象。但编译器将staff[0]看成Employee对象。

1
2
3
4
5
Manager boss = new Manager(. . .);
Employee[] staff = new Employee[3];
staff[0] = boss;//Correct Employee超类可以引用其子类Manger
boss = staff[0];//Error
staff[0].setBonus(5000);//Error

在Java 中,子类数组的引用可以转换成超类数组的引用, 而不需要采用强制类型转换。例如,下面是一个经理数组

1
Manager[] managers = new Manager[10];

将它转换成Employee[]数组完全是合法的:

1
Employee[] staff = managers; // OK

这样做肯定不会有问题,请思考一下其中的缘由。毕竟,如果manager[i] 是一个Manager, 也一定是一个Employee。然而, 实际上,将会发生一些令人惊讶的事情。要切记managers和staff引用的是同一个数组。现在看一下这条语句:

1
staff[0] = new Employee("Harry Hacker", . . .) ;

编译器竟然接纳了这个赋值操作。但在这里, staff[0]与manager[0]引用的是同一个对象,似乎我们把一个普通雇员擅自归入经理行列中了。这是一种很忌讳发生的情形,当调用managers[0].setBonus(1000) 的时候,将会导致调用一个不存在的实例域, 进而搅乱相邻存储空间的内容。
为了确保不发生这类错误, 所有数组都要牢记创建它们的元素类型,并负责监督仅将类型兼容的引用存储到数组中。例如,使用new managers[10]创建的数组是一个经理数组。如果试图存储一个Employee 类型的引用就会引发ArrayStoreException异常

类型转换

  • 只能在继承层次内进行类型转换。
  • 在将超类转换成子类之前,应该使用instanceof 进行检查。

在一般情况下,应该尽量少用类型转换和instanceof 运算符。

抽象类abstract

提示:许多程序员认为,在抽象类中不能包含具体方法。建议尽量将通用的域和方法(不管是否是抽象的)放在超类(不管是否是抽象类)中。

继承抽象类可以有两种选择。
一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽
象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
抽象类不能被实例化。可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象,这个时候,利用这个对象变量调用抽象类的抽象方法,会自动向下寻找,也就是实际调用的是其子类的方法。

抽象与接口

抽象类和接口的区别:

  1. 接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提供具体的实现。抽象类可以为普通方法提供具体的实现。所以,当一个子类继承抽象类的时候,并不一定要覆盖抽象类的方法(抽象方法除外),而一个类继承一个接口时,必须要重写接口的所有方法(毕竟接口中的方法只是个声明)
  2. 接口里只能定义静态常量,不能定义普通成员变量。抽象类可以定义普通成员变量和静态变量。在接口中定义成员变量会被自动转化成静态 final 变量,也就是在接口中,下面两个语句等价

    1
    int i = 10;
    1
    public static final int i = 10;
  3. 接口里不包含构造器。抽象类可以包含构造器(抽象类里的构造器不是用于创建对象的,而是让其子类调用这些构造器来完成类的初始化)。

  4. 接口里不能包含初始化块,而抽象类里可以包含初始化块。
  5. 一个类最多只能有一个直接父类,包括抽象类,而一个类可以实现多个接口,通过实现多个接口来弥补Java单继承的不足。
    抽象类中的成员可以是 private、默认、protectedpublic 的,而接口中的成员全都是 public 的。

Object所有类的超类

equals

在Object 类中,这个方法将判断两个对象是否具有相同的引用。

泛型数组列表

ArrayList

  1. 构造形式

    1
    ArrayList<Employee> staff = new ArrayList<Employee>();

    Java SE 7 中, 可以省去右边的类型参数:

    1
    ArrayList<Employee> staff = new ArrayList<>();
  2. 添加元素
    add方法
    如果调用add且内部数组已经满了, 数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
    警告:分配数组列表, 如下所示:

    1
    new ArrayList<>(lOO) // capacity is 100

    它与为新数组分配空间有所不同:

    1
    new Employee[100] // size is 100

    数组列表的容量与数组的大小有一个非常重要的区别。如果为数组分配100 个元素的存储空间,数组就有100 个空位置可以使用。而容量为100 个元素的数组列表只是拥有保存100 个元素的潜力( 实际上,重新分配空间的话,将会超过100), 但是在最初,甚至完成初始化构造之后,数组列表根本就不含有任何元素。

    C++ 注释: ArrayList类似于C++ 的vector 模板。ArrayList 与vector 都是泛型类型。但是C++ 的vector 模板为了便于访问元素重栽了[ ] 运算符。由于Java 没有运算符重栽,所以必须调用显式的方法。此外,C++ 向量是值拷贝。如果a 和b 是两个向量,赋值操作a = b 将会构造一个与b 长度相同的新向量a,并将所有的元素由b 拷贝到a, 而在Java 中,这条赋值语句的操作结果是让a和b引用同一个数组列表。

  3. 总结

    • 不必指出数组的大小。
    • 使用add 将任意多的元素添加到数组中。
    • 使用size() 替代length 计算元素的数目。
    • 使用a.get(i) 替代a[i] 访问元素。

这就是Java 中不尽如人意的参数化类型的限制所带来的结果。鉴于兼容性的考虑, 编译
器在对类型转换进行检査之后, 如果没有发现违反规则的现象, 就将所有的类型化数组列表
转换成原始ArrayList 对象。在程序运行时, 所有的数组列表都是一样的, 即没有虚拟机中
的类型参数。因此,类型转换ArrayListArrayList<Employee> 将执行相同的运行时检查。

对象包装器与自动装箱

  1. 对象包装器
    有时, 需要将int这样的基本类型转换为对象。所有的基本类型都冇一个与之对应的类。通常,这些类称为包装器(wrapper) 。
    这些对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character 、Void 和Boolean
    下面是声明一个ArrayList里面存放int型

    1
    ArrayList<Integer> list = new ArrayList<>();

    这种方式适用于小型集合,因为其效率比整型数组int[]要低得多,但是添加成员的方便程度要比整型数组要高。

  2. 自动装箱
    幸运的是, 有一个很有用的特性, 从而更加便于添加int 类型的元素到ArrayList<lnteger>中。下面这个调用

    1
    list.add(3);

    将自动地变换成

    1
    list.add(Integer.value0f(3)) ;

    这种变换被称为自动装箱(autoboxing)。

枚举类

内部类(2019-7-12更新)

内部类中变量的使用

如果希望访问所在方法的局部变量,那么这个局部变量必须是有效 final 的。

1
2
3
4
5
6
7
8
9
10
11
public class MyOuter{
public void methodOuter{
int num = 10;//所在方法中的局部变量

class MyInner{
public void methodfInner(){
System.out.println(num);
}
}
}
}
  1. new 出来的对象在堆内存中
  2. 局部变量是跟着方法走的,在栈内存当中.
  3. 方法运行结束之后,立即出栈,局部变量就会立即消失.
  4. 但是 new 出来的对象会在堆持续存在,直到垃圾回收消失。
  5. 最终当内部类需要使用局部变量时吗,会复制一份到自己的堆内存中,如果这个变量是不确定的,与你形式会带来不确定的后果。

匿名内部类

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用匿名内部类。

谈谈反射(2019-7-17更新)

每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。

类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
Constructor :可以用 Constructor 的 newInstance() 创建新的对象。

反射的优点

可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。