Java 核心技术读书笔记

Java 核心技术读书笔记

第五章

动态绑定

一个对象变量可以指示多种实际类型的现象被称为多态,在运行时能够自动的选择调用哪个方法的现象被称为动态绑定。

对象方法的调用过程:

编译期

  • 编译器查看对象的声明类型和方法名。假设x.f(param),且隐式参数x声明为C类的对象。编译器将会一一列举C类中名为f的方法和超类中访问属性为public且名为f的方法(超类的私有方法不可访问)
  • 接下来,编译器将会查看调用方法提供的参数类型,如果编译器没有找到与参数类型匹配的方法,或者发现多个方法与之匹配,就会报错

  • 如果是private方法、static方法、final方法或者构造器,那么编译器将会知道调用哪个方法,这种调用称为静态绑定。于此对应的是,调用方法依赖隐式参数的实际类型,并且在运行时实现动态绑定。

运行

  • 当程序运行时,并且采用动态绑定调用方法时,虚拟机一定调用与X所引用对象的实际类型最合适的那个类的方法。如果每次调用方法都要进行搜索,时间开销特别大。因此,虚拟机预先为每个类创建一个方法表,其中列出了所有方法的签名和实际调用的方法。这样效率更高

Object

只有基本类型不是对象。所有的数组类型,不管是对象数组还是基本类型数组都拓展于Object类

ArrayList

  • list.ensureCapacity(100);提前分配空间大小
  • list.trimToSize();将存储区大小调整为当前实际元素个数,并回收多余的存储空间
  • list.set(i,element);对集合索引为i的元素进行赋值,前提是索引为i的元素必须存在
  • list.add(i,element);向后移动元素,以便插入元素

包装类都是不可变类

  • 自动装箱 和 自动拆箱 是编译器行为,编译器在生成码字节文件时插入了必要的方法调用,虚拟机知识执行这些码字节
  • static int parseInt(String s)
  • static Integer valueof(String s)

枚举类

枚举类的构造函数一般私有,在比较枚举类型的值时,永远不要调用equals ,而是直接调用“==”

所有枚举类型都是Enum类的子类,可以在枚举类中添加构造器、方法和域,构造器只是在构造枚举常量时被调用

Size.SMALL.toString();        //将返回字符串SMALL
Size s = Enum.valueOf(Size.class, "SMALL");            //toString()的逆方法,返回Size.SMALL
Size[] values = Size.values();             //返回包含全部枚举值的数组
Size.SMALL.ordinal();          //将返回枚举常量的位置,这里返回0
Size.SMALL.compareTo(Size MEDIUM)            //如果SMALL出现在枚举常量MEDIUM前,则返回一个负值,相等返回0,否则返回正值

public enum Size {
    SMALL("small"),Large("large");

    private String abbr;

    private Size(String abbr){
        this.abbr = abbr;
    }
    public String getAbbr(){
        return abbr;
    }
}

使用反射编写泛型数组代码

java数组会记住每个元素的类型,即创建数组时new表达式中使用的元素类型。将一个Employee[]临时地转换成Object[]数组,然后再把它转换回来是可以的,但一个从开始就是Object[]的数组却永远不能转换成Employee[]数组,故下例不能达到拓展数组大小的效果。

static Object[] badArrayGrow(Object[] a) //not useful
{

    Object[] newArray = new Object[newLength];//限定死数组实际类型为object[]
    System.arraycopy(a, 0, newArray, 0, a.length);
    return newArray;
}

使用Object newArray = Array.newInstance(componentType, newLength);来构造新数组。如下例。其中,整形数组类型int[]可以被转换成Object,但不能转换为对象数组,如Integer[]。故传入参数为Object类型。

扫描二维码关注公众号,回复: 2199030 查看本文章
static Object goodArrayGrow(Object a) //useful
{
    Class cl = a.getClass();
    if(!cl.isArray())
        return null;
    Class componentType = cl.getComponentType();
    int length = Array.getLength(a);
    int newLength = length * 11 / 10 + 10;
    Object newArray = Array.newInstance(componentType, new Length);//object类是数组的超类
    System.arraycopy(a, 0, newArray, 0, length);
    return newArray;
}

方法指针

Method类有一个invoke方法,允许调用包装在当前Method对象中的方法。其签名为Object invode(Object obj, Object… args);第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以被忽略,即传入null。如果返回类型是基本类型,invoke方法会返回包装器类型,可使用

double s = (Double) m2.invoke(harry);

通过自动拆包来完成类型转换。

getMethod的方法签名是Method getMethod(String name, Class… parameterTypes)对于Java SE 5.0前的版本,需要传入数组或null作为显式参数。

invoke的参数和返回类型都是Object型,这意味着进行多次类型转换,这会使编译器错过检查代码的机会,错误会到测试阶段才能被发现,找到并改正它们会更加困难。使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。因此,建议仅在必要的时候才使用Method对象,最好使用接口和内部类。

第六章

对象复制

Object类的克隆方法是对各个域进行对应的拷贝。如果对象中包含了对子对象的引用,拷贝的结果会使这两个域共同引用同一个子对象。Object类 中,clone方法被声明为protected,因此无法直接调用anObject.clone(),只可以在anObject所属类内部调用clone方法。

Cloneable接口的出现与接口的正常使用没有任何关系,它没有指定clone方法,这个方法使从Object类中继承而来的。接口在这里只是作为一个标记接口,表明类设计者知道要进行克隆处理。通常使用接口的目的是为了确保类实现某些特定的方法,而标记接口没有方法,使用它的唯一目的是可以用instanceof进行类型检查

需要明确一点是,clone的默认实现是签复制,对于基本类型没有问题,但是如果类中有引用成员,就会导致引用全都指向同一对象,导致不是真正意义上的复制,需要斟酌一下,如果想要实现深层复制,可以自己实现clone方法的实现,或者利用I/O来实现深层复制,但效率较低

即使clone的默认实现(浅拷贝)能够满足需求,也应该实现Cloneable接口,将clone重定义为public,并调用super.clone()。

class Employee implements Cloneable
{
    //raise visibility level to public, change return type
    public Employee clone() throws CloneNotSupportedException
    {
        return (Employee)super.clone();
    }
}

所有的数组类型均包含一个clone方法,这个方法被设为public,而不是protected,可以利用这个方法创建一个包含所有数组元素拷贝的一个新数组。

int[] luckyNumbers = {2, 3, 4, 5, 7, 11, 13};
int[] cloned = (int[]) luckNumbers.clone();
cloned[5] = 12;    //doesn't change luckyNumbers[5]

内部类

使用内部类的原因:

  • 内部类方法访问该类定义所在的作用域中的数据,包括私有数据
  • 内部类可以对同一个包中的其它类隐藏起来
  • 当想要定义一个回调函数,使用匿名内部类不叫便捷

1)使用内部类访问对象状态
内部类既可以访问自身的数据域,也可以访问创建他的外围类对象的数据域。

 public class TalkingClock
{
    public TalkingClock(int interval, boolean beep{...} 
    public void start(){...}

    private int interval;
    private boolean beep;

    public class TimerPrinter implements ActionListener
    {
        ...
    }
}

内部类对象总有一个隐式引用,它指向创建它的外部类对象

内部类的特殊语法规则

在内部类中使用 OuterClass.this 引用外围类。使用outerObject.new InnerClass() 构造内部类的对象。在外围类的作用域之外,使用OuterClass.InnerClas引用内部类。

class TimerTest
{
    void fun()
    {
        TimeWorker.TimePrinter aTimePrinter = aTimeWorker.new TimePrinter();
    }
}

class TimeWorker
{
    public class TimePrinter
    {
    }
}

内部类是一种编译器现象,与虚拟机无关,编译器将会把内部类翻译成$分隔外部类名与内部类名的常规类文件,而虚拟机对此一无所知

局部内部类

将一个方法中定义的类,不能用public和private 访问说明符进行声明。它的作用域被限定在声明这个局部类的块中,该类的优势是,对外部世界完全隐藏,外部类的其他方法都不能访问他.他们不仅能够访问包含他们的外部类,还可以访问局部变量,但是必须用final声明,实际上是编译器在内部类给局部变量生成了一个副本

public void start(){

    class TimePrinter implements ActionLister{

        public void actionPerformed(ActionEvent event){
            xxxxxxxxxxxxxxx
        }
    }
} 

匿名内部类

因为没有类名,所以没有构造器

静态内部类

使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外部类对象。为此,可以将内部类声明为static ,以便取消产生的引用。**静态内部类的对象除了,诶呦对生成它的外围类对象引用的特权外,与其他内部类完全一致。

猜你喜欢

转载自blog.csdn.net/whp404/article/details/79548409