<java核心技术>读书笔记2
Object:所有类的超类
java中每个类都是由它扩展而来,但是并不需要这样写:class Employee extends Object
.如果没有明确指出超类,Object类就被认为是这个的超类。
可以使用Object类型的变量引用任何类型的对象Object obj=new Employee()
.
在java中,只有基本类型(int,boolean,...)不是对象
,所有的数组类型,不管是对象数组还是基本类型的数组都扩展自Object类。
Employee[] staff=new Employee[10];Object obj=staff;//okobj=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对象,然后寻找这些类的共同超类型,最终找到两个这样的超类Number
和Comparable
,其本身也是泛型类型,这种情况下,只有将所有的参数写成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>)...//errorif(a instanceof Pair<T>)...//errorPair<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>数组,这就违反了上一个泛型局限。
不过,对于这种情况,规则有所放松,只会得到一个警告,而不是错误。
有两种方法抑制这个警告
为包含addAll调用的方法添加标注
@SuppressWarnings("unchecked")
.如果是java7或其后面版本,用
@SafeVarargs
直接标注addAll方法。
@SafeVarargsstatic <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){//errort.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)