资讯专栏INFORMATION COLUMN

<java核心技术>读书笔记2

jimhs / 3120人阅读

摘要:如果需要收集参数化类型对象,只有使用警告这节讨论,向参数可变的方法传递一个泛型类型的实例。异常不能抛出或捕获泛型类的实例实际上,泛型类扩展也是不合法的。

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 list=new ArrayList();
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 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 {
    ...
}

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

泛型方法

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

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

类型变量放在修饰符(这里是public)的后面,返回类型的前面
调用class.getMethod()
如果是上面情况,大多数时候,方法调用可以省略类型参数。编译器有足够的信息推断出调用的方法。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 min(T[] a);

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

泛型的局限

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

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

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

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

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

Pair stringPair=...;
Pair employeePair=...;
if(stringPair.getClass()==employeePair.getClass())...//they are equal

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

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

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

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

Object[] objarray=pairs;

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

Pair[] pairs=(Pair[]) new Pair[10];

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

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

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

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

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

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

@SafeVarargs
static  void addAll(Collection coll,T... ts)

不能实例化类型变量
不能使用像new T(),new T[],T.class这样的表达式中的类型变量。

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

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

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

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

Class Problem extends Exception{...}//error,can"t extend Throwable

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

     void dowork(T t) throws T{
        try{
            ...
        }catch(T t){//error
            ...
        }
    }

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

     void dowork(T t) throws T{
        try{
            ...
        }catch(Throwable realCause){//error
            t.initCause(realCause);
            throw t;
        }
    }

注意擦除后的冲突
类型擦除后,无法创建引发冲突的条件。

public class Pair {
    ...
    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不是Pair的子类。
注意泛型和数组间的重要区别,可以将一个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是原始类型Pair的一个子类型。

public class Pair {
    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 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可以转换为List.
但是如前面所见,ArrayList不是一个ArrayList或List

通配符类型

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

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

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

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

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

通配符的超类型限定

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

无限定通配符

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64680.html

相关文章

  • &lt;java核心技术&gt;读书笔记1

    摘要:关键字作用调用超类方法调用超类构造器关键字作用引用隐式参数如调用该类的其他构造器在覆盖一个方法时,子类方法可见性不能低于超类方法阻止继承类和方法目的确保它们不会在子类中改变语义。但是如果将一个类声明为后面可以改变类变量的值了。 数据类型 整型 int 存储要求:4byte 取值范围:-2147483648 -- 2147483647(超过20亿) short 存储要求:2byte 取...

    William_Sang 评论0 收藏0
  • &lt;jdk7学习笔记&gt;读书笔记-并行api

    摘要:然而,这两个方法都只是读取对象状态,如果只是读取操作,就可以允许线程并行,这样读取效率将会提高。分配线程执行子任务执行子任务获得子任务进行完成的结果 Lock Lock接口主要操作类是ReentrantLock,可以起到synchronized的作用,另外也提供额外的功能。用Lock重写上一篇中的死锁例子 import java.util.concurrent.locks.Lock; ...

    bovenson 评论0 收藏0
  • &lt;&lt;深入PHP面向对象、模式与实践&gt;&gt;读书笔记:面向对象设计和过程式编程

    摘要:注本文内容来深入面向对象模式与实践中节。面向对象设计与过程式编程面向对象设计和过程式编程有什么不同呢可能有些人认为最大的不同在于面向对象编程中包含对象。面向对象编程和过程式编程的一个核心区别是如何分配职责。 注:本文内容来中6.2节。 6.2 面向对象设计与过程式编程   面向对象设计和过程式编程有什么不同呢?可能有些人认为最大的不同在于面向对象编程中包含对象。事实上,这种说法不准确。...

    xiao7cn 评论0 收藏0
  • 《HTML与CSS 第一章 认识HTML》读书笔记

    摘要:一让广播明星黯然失色要建立页面,需要创建用超文本标记语言,编写的文件,把它们放在一个服务器上二服务器能做什么服务器在互联网上有一份全天候的工作。一、Web让广播明星黯然失色    要建立Web页面,需要创建用超文本标记语言(HyperText Markup Language,HTML)编写的文件,把它们放在一个Web服务器上二、Web服务器能做什么?  Web服务器在互联网上有一份全天候的工...

    番茄西红柿 评论0 收藏0
  • &lt;javascript高级程序设计&gt;第十二章读书笔记----偏移量

    摘要:包括元素的高度上下内边距上下边框值,如果元素的的值为那么该值为。该值为元素的包含元素。最后,所有这些偏移量都是只读的,而且每次访问他们都需要重新计算。为了避免重复计算,可以将计算的值保存起来,以提高性能。 offsetHeight 包括元素的高度、上下内边距、上下边框值,如果元素的style.display的值为none,那么该值为0。offsetWidth 包括元素的宽度、左...

    dayday_up 评论0 收藏0

发表评论

0条评论

jimhs

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<