Object:所有类的超类

java中每个类都是由它扩展而来,但是并不需要这样写:class Employee extends Object.如果没有明确指出超类,Object类就被认为是这个的超类。
可以使用Object类型的变量引用任何类型的对象Object obj=new Employee().
在java中,只有基本类型(int,boolean,...)不是对象所有的数组类型,不管是对象数组还是基本类型的数组都扩展自Object类

Employee[] staff=new Employee[10];
Object obj=staff;//ok
obj=new int[10];//ok

对象包装器与自动装箱

有时需要将int这样的基本类型转换为对象,所有基本对象都有一个与之对应的类。如int->Integer.这些类称为包装类(wrapper).具体的,包括Integer,Long,Float,Double,Short,Byte,Character,Void,Boolean,前6个派生于它们公共的超类Number.
对象包装类是不可变的,一旦创建,就不允许更改包装在其中的值。
对象包装类还是final,不能定义它们的子类。
java5之后,调用list.add(3)将自动变换成list.add(Integer.valueOf(3)),这种变换称为自动装箱。相反的,将一个Integer对象赋值给一个int值时,会自动拆箱

List<Integer> list=new ArrayList<Integer>();
list.add(3);
int n=list.get(0);//等同于list.get(0).intValue();

在算术表达式中,也可以自动装箱,自动拆箱。

Integer n=3;
n++;//拆箱->自增->装箱

==运算符用于比较包装器对象,只不过检测的是对象是否指向同一个存储区域。因此,下面比较不会成立

        Integer a=1000;
        Integer b=1000;
        System.out.println(a==b);//false

但是如果包装的值经常出现,==比较就有可能相等

        Integer a=100;
        Integer b=100;
        System.out.println(a==b);//true

这个和python类似

python并不是对创建的所有对象都会重新申请新的一块内存空间。作为一种优化,python会缓存不变的对象(如数值较小的数字,字符串,元组等)并对其进行复用。

因此,java设定了自动装箱规范

  • boolean

  • byte

  • char<=127

  • 介于-128和127之间的short,int

被包装到固定的对象中。
上面例子a,b是100,第二次赋值时,java不会重新申请新的一块内存空间,存储100这个值。

接口

接口特性

  • 接口不是类,不能使用new运算符实例化一个接口

  • 可以声明接口变量,但必须引用实现了接口的类对象

  • 可以使用instanceof检查一个对象是否实现了某个特定的接口

  • 接口可以继承接口InterfaceA extends InterfaceB

  • 接口中不能包含实例变量或静态方法,却可以包含常量

public interface Interface {
    String name="aaa";//a public static final constant
}

常量会自动被设为public static final

  • 尽管每个类只允许拥有一个超类,却可以实现多个接口class Concrete implements InterfaceA,InterfaceB

内部类

  • 内部类方法可以访问该类所在的作用域中的数据,包括私有数据

  • 内部类可以对同一个包中的其他类隐藏起来

  • 当想要定义一个回调函数且不想写大量代码时,使用匿名内部类比较方便

泛型

泛型类

泛型类:具有一个或多个类型变量的类。类型变量可以指定方法的返回类型以及类变量和局部变量的类型

public class Pair<T> {
    T first;
    T second;
    Pair(){
        this.first=null;
        this.second=null;
    }
    Pair(T first,T second){
        this.first=first;
        this.second=second;
    }
    public T getFirst() {
        return first;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public T getSecond() {
        return second;
    }
    public void setSecond(T second) {
        this.second = second;
    }
}

Pair类引入一个类型变量T,用尖括号(<>)括起来,并放在类名的后面。
泛型可以有多个类型变量``

public class Pair<T,U> {
    ...
}

实例化Pair<String> pair=new Pair<String>(),这时类型变量T就会被替换成String.这时,泛型类就可以看做普通类。

泛型方法

泛型方法可以定义在普通类中。

public <T> T getMethod(T... a){}

类型变量放在修饰符(这里是public)的后面,返回类型的前面
调用class.<String>getMethod()
如果是上面情况,大多数时候,方法调用可以省略<String>类型参数。编译器有足够的信息推断出调用的方法。String[]与泛型类型T[]进行匹配,并推断出T一定是String.
但是,如果是下面代码

double result=class.getMethod(1.23,123,0);

编译器会自动将参数打包为1个Double和2个Integer对象,然后寻找这些类的共同超类型,最终找到两个这样的超类NumberComparable,其本身也是泛型类型,这种情况下,只有将所有的参数写成double值。
如果想知道一个泛型方法最终推断出哪种类型,可以有目的的引入一个错误,看产生的错误信息,比如

class.getMethod("aaa",0,null);
//found:java.lang.Object&java.io.Serializable&java.util.Comparable...

意思是可以将结果赋给Object,Serializable或Comparable.

类型变量的限定

<T extends Comparable> T min(T[] a);

上面代码将T限制为实现了Comparable接口的类,这样min方法只能被实现了实现了Comparable接口的类(如String,Date类)的数组调用。
注意这里用的是extends关键字
<T> extends BoundingType表示T应该是绑定类型的子类型(subtype),T和绑定类型可以是类,也可以是接口.选择extends关键字的原因是跟接近子类型的概念。
一个类型变量或通配符可以有多个限定,如T extends Serializable&Comparable.&隔开限定类型,,隔开类型变量。
由于对类是单继承,对接口多重继承,如果限定类型中有类又有接口,必须将类作为限定列表中的第一个

泛型的局限

大多数限制都是由类型擦除引起的,类型擦除可以参见Java泛型:类型擦除

  • 不能用基本类型实例化类型参数。
    没有Pair<double>,只有Pair<Double>,因为类型擦除之后,类型参数变为Pair<Object>,而Object不能存储double值。

  • 运行时类型查询只试用于原始类型。
    虚拟机中的对象总有一个特定的非泛型类型,因此所有的类型查询只产生原始类型。

if(a instanceof Pair<String>)...//error
if(a instanceof Pair<T>)...//error
Pair<String> p=(Pair<String>) a;//warning,can only test that a is a Pair

同样的道理,getClass方法总是返回原始类型。

Pair<String> stringPair=...;
Pair<Employee> employeePair=...;
if(stringPair.getClass()==employeePair.getClass())...//they are equal

上面代码两次调用getClass都将返回Pair.class.

  • 不能创建参数化类型的数组

Pair<String>[] paris=new Pair<String>[10];
//error,The type of the expression must be an array type but it resolved to Pair<String>

上面代码的问题在于,类型擦除后,paris的类型是Pair[],可以转换为Object[]

Object[] objarray=pairs;

数组会记住它的元素类型,如果试图存储其他类型的元素,就会抛出Array-StoreException.
只是不允许这样创建数组,而声明类型为Pair<String>[]的变量仍然是合法的。不过不能用new Pair<String>[10]初始化这个变量。
可以声明通配类型的数组,然后类型转换

Pair<String>[] pairs=(Pair<String>[]) new Pair<?>[10];

但这样类型是不安全的。
如果需要收集参数化类型对象,只有使用ArrayList<Pair<String>>.

  • Varargs警告
    这节讨论,向参数可变的方法传递一个泛型类型的实例。

static <T> void addAll(Collection<T> coll,T... ts){
    for(T t:ts)
        coll.add(t);
}
Collection<Pair<String>> coll=...;
Pair<String> pair1=...;
Pair<String> pair2=...;
addAll(coll,pair1,pair2);

为了调用addAll方法,java虚拟机必须建立一个Pair<String>数组,这就违反了上一个泛型局限。
不过,对于这种情况,规则有所放松,只会得到一个警告,而不是错误。
有两种方法抑制这个警告

  1. 为包含addAll调用的方法添加标注@SuppressWarnings("unchecked").

  2. 如果是java7或其后面版本,用@SafeVarargs直接标注addAll方法。

@SafeVarargs
static <T> void addAll(Collection<T> coll,T... ts)
  • 不能实例化类型变量
    不能使用像new T(),new T[],T.class这样的表达式中的类型变量。

  • 泛型类中静态类型变量无效
    不能在静态域或方法中引用类型变量

public class singleton<T>{
    static T instance;//error
    static T getInstance(){//error
        ...
    }
}

类型擦除后,Singleton类只包含一个instance域变量。

  • 异常不能抛出或捕获泛型类的实例
    实际上,泛型类扩展Throwable也是不合法的。

Class Problem<T> extends Exception{...}//error,can't extend Throwable

catch子句中不能使用类型变量。

    <T extends Throwable> void dowork(T t) throws T{
        try{
            ...
        }catch(T t){//error
            ...
        }
    }

不过在其中使用类型变量是允许的。

    <T extends Throwable> void dowork(T t) throws T{
        try{
            ...
        }catch(Throwable realCause){//error
            t.initCause(realCause);
            throw t;
        }
    }
  • 注意擦除后的冲突
    类型擦除后,无法创建引发冲突的条件。

public class Pair<T> {
    ...
    boolean equals(T value){
        return this.first.equals(value)&&this.second.equals(value);
    }
}

上面代码类型擦除后boolean equals(T)变成boolean equals(Object),与Object类本身的equals方法发生冲突。所以编译器会发出警告,equals(T)方法没有override Object类中的equals(Object).

泛型类型的继承规则

考虑一个类和一个子类,如Employee和Manager.显然Pair<Manager>不是Pair<Employee>的子类。
注意泛型和数组间的重要区别,可以将一个Manager[]数组赋值给一个类型为Employee[]的变量。

public class Manager extends Employee{
    public static void main(String[] args){
        Manager ceo=new Manager();
        Manager cto=new Manager();
        Manager[] managers=new Manager[]{ceo,cto};
        Employee[] employs=managers;
        System.out.println(employs.length);//2
    }
}

另外,可以参数化类型转换为原始类型,如Pair<Manager >是原始类型Pair的一个子类型。

public class Pair<T> {
    T first;
    T second;
    Pair(T first,T second){
        this.first=first;
        this.second=second;
    }
    public T getFirst() {
        return first;
    }
    public void setFirst(T first) {
        this.first = first;
    }
}
public class Manager{
    public static void main(String[] args){
        Manager ceo=new Manager();
        Manager cto=new Manager();
        Pair<Manager> pair=new Pair<>(ceo,cto);
        Pair pair1=pair;//ok
        pair1.setFirst(new File(""));//类型警告,但是可运行
        Manager manager=(Manager)pair1.getFirst();
        //ClassCastException,java.io.File cannot be cast to com.Manager
        Manager manager1=pair.getFirst();
        //ClassCastException,java.io.File cannot be cast to com.Manager
    }
}

最后,泛型类可以扩展或实现其他泛型类,这和普通类没什么区别。如ArrayList类实现List接口,这意味着ArrayList<Manager>可以转换为List<Manager>.
但是如前面所见,ArrayList<Manager>不是一个ArrayList<Employee>或List<Manager>

通配符类型

Pair<? extends Employee>表示类型参数是Employee类的子类.
看下面代码

    void print(Pair<Employee> p){
        Employee first=p.getFirst();
        Employee second=p.getSecond();
        ...
    }

正如前面所说,这里不能将Pair<Manager>传给方法。解决方法:
void print(Pair<? extends Employee> p)
类型Pair<Manager>是Pair<? extends Employee>的子类型
下面考虑

        Pair<Manager> manager=new Pair<>();
        Pair<? extends Employee> pair=manager;//ok
        pair.setFirst(manager);
        //编译错误,The method setFirst(capture#1-of ? extends Employee) in the type 
        //Pair<capture#1-of ? extends Employee> is not applicable for the arguments (Pair<Manager>)

不过将getFirst方法的返回值赋值给一个Employee引用就不存在问题。

通配符的超类型限定

? super Manager,这个通配符限制类型变量为Manager的所有超类型。
上面代码<? extends Manager>如果换成<? super Manager>,将会表现为可以为方法通过参数(set),但不能使用返回值(get).
带有超类型限定的通配符(<? super Manager>)可以向泛型对象写入,带有子类型限定的(<? extends Manager>)可以从泛型对象读取

无限定通配符

如Pair<?>有方法
? getFirst()
void setFirst(?)
getFirst的返回值只能赋值给一个Object,而setFirst方法不能被调用,甚至用Object做参数也不能。
可以调用setFirst(null)


TheViper
465 声望16 粉丝