Java核心技术(第8版) – 读书笔记 – 第6章

1、接口:用于描述类应当实现什么功能,但接口不包含实现。一个类可以实现多个接口。比如用于比较的接口:

public interface Comparable
{
    int compareTo()(Object other);
}

上面的Comparable接口中,返回值定义如下:
(1) x < y,返回负数
(2) x > y,返回正数
(3) x = y,返回0

2、接口中所有的方法,默认为public,无需再定义。

3、接口中可以包含N多方法,常量,单不能有任何实例域!

4、实际上,现在很多接口都支持泛型,因此,上面Comparable的Object这种别扭的写法不复存在了,注意实现接口用的是关键字implements:

class AAA implements Comparable<AAA>
{
    //....

    public int compareTo(AAA other)
    {
        //....
    }

    //....
}

5、我们来看一下Java中如何将自定义的对象进行排序sort。我们知道Arrays.sort可以,看看它的一个重载:

public static void sort(Object[] a)

再看Java Docs里面的解释:

Sorts the specified array of objects into ascending order, according to the natural ordering of its elements. All elements in the array must implement the Comparable interface. Furthermore, all elements in the array must be mutually comparable (that is, e1.compareTo(e2) must not throw a ClassCastException for any elements e1 and e2 in the array).

即:这个Object[]里面所有元素都要实现Comparable接口。

下面我们实现一个自定义的类,它实现了Comparable接口,然后用Arrays.sort排序:

import java.util.Arrays;
public class ComparableTest implements Comparable<ComparableTest>
{
    public ComparableTest(int aid, double ascore)
    {
        id = aid;
        score = ascore;
    }   

    public int compareTo(ComparableTest other)
    {
        return this.id - other.id;
    }   

    public String toString()
    {
        return id + " " + score;
    }   

    private int id;
    private double score;

    public static void main(String [] args)
    {
        ComparableTest [] objs = {new ComparableTest(10, 8.0), new ComparableTest(2,9.0), new ComparableTest(3, 10.0)};
        Arrays.sort(objs);
        for(ComparableTest obj: objs)
        {
            System.out.println(obj);
        }
    }
}

输出结果:

2 9.0
3 10.0
10 8.0

6、不能new一个接口出来。

7、obj instanceof intf,是返回true的,只要obj实现了接口intf

8、接口也可以相互继承,如:

public interface Movable
{
    void move()(double x, double y);
}

public interface Powered extends Movable
{
    double xxx();
}

10、接口中可以包含常量,但不能又类实例域。

public interface Powered extends Movable
{
    double xxx();
    //常量是允许的!
    double SPEED_LIMIT = 95;
}

11、为什么选择接口而不是抽象类?因为Java规定只能继承一个类,不能多继承!而接口可以实现多个:

public class extends XXX implements XXXIntf1, XXXIntf2
{
//....
}

为什么会如此设计?因为多继承会显著降低语言的运行效率,比如C++。

12、对象克隆:这时深度克隆即完全拷贝新副本,而不是指针指向同一个对象。这需要使用.clone()方法,同时,要求对象实现Cloneable接口。

Employee copy  = original.clone();
//Won't affect origna l!!
copy.raiseSalary(10);

13、在Object类中,clone()被定义为protected,因此,只有子类显示的重新以public定义clone()方法,才能让该类对象使用clone()方法。实现clone的途径:
(1)如果浅拷贝能满足要求,直接调用super.clone() // 对于直接(默认)继承自Object的类来说。
(2)至少一个成员域需要深度拷贝(是非基础类型),则要对每个需要递归调用的子域的调用clone()

见下面的例子,对于int/double,可以直接类的super.clone()(实际是Object.clone())但返回的Object要强转。对于非基本类型,要用成员域上操纵clone(),同样要强制转换。最后,注意一定要把clone()声明为public,并抛出CloneNot-SuppourtException。

代码如下:

import java.util.Date;

public class Li implements Cloneable
{
	public Li(int aid, double ascore)
	{
		id = aid;
		score = ascore;
		day = new Date();
	}

	public Li clone() throws CloneNotSupportedException
	{
		Li cloned = (Li)super.clone();
		cloned.day = (Date)this.day.clone();

		return cloned;
	}

	public void clearAll()
	{
		day.setTime(0);
		score = 0;
	}

	public String toString()
	{
		return "id="+id+", score="+score+", day="+day;
	}

	//Basic type
	private int id;
	private double score;

	//Class
	private Date day;

	public static void main(String [] args) throws Exception
	{
		//Clone, same output
		Li li1 = new Li(1, 1.0);
		Li li2 = li1.clone();
		System.out.println("li1:");
		System.out.println(li1);
		System.out.println("li2:");
		System.out.println(li2);

		//Change the li1
		li1.clearAll();
		System.out.println("After...");
		System.out.println("li1:");
		System.out.println(li1);
		System.out.println("li2:");
		System.out.println(li2);
	}
}

14、对于没有实现Cloneable接口的,调用clone(),会抛出CloneNot-SuppourtException异常。

15、clone机制一致存在,但是由的非常少,只有不到5%的java类实现了clone(),因为它容易出错。如果性能可以接受的话,Java序列化是另一种更好的克隆方式(安全但效率略低)!

16、定时回调的一种方式:用javax.swing.Timer类和java.awt.event.ActionLisner实现,

这里用swing和awt实现回调,只是为下面几章GUI准备,这种简单功能的实现方法还有很多很多。

代码如下:

import javax.swing.Timer;
import java.awt.event.*;
public class MyTimer implements ActionListener
{
    public void actionPerformed(ActionEvent e)
    {
        System.out.println("I was called...");
    }   

    public static void main(String [] args)
    {
        Timer timer  = new Timer(2*1000, new MyTimer());
        timer.start();

        try
        {
            Thread.sleep(1000*1000*10);
            timer.stop();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

17、内部类(inner class):定义在一个类中的另一个类。
(1)内部类可访问所在作用域的数据,包括私有数据(作用同C++的friend)
(2)内部类对外部类之外的其他类(包括同包下其他类)都不可见,便于隐藏细节。
(3)只是为了定义回调函数,而不想编写大量代码,可直接使用匿名内部类(anonymous class)。

18、内部类可以通过隐式引用或者OutClass.this.引用访问所在类外面那个类的成员变量!

代码如下:

public class AClass
{
    public AClass(int aid)
    {
        id = aid;
    }   

    public String toString()
    {
        return (new INClass()).toString();
    }   

    int id; 

    class INClass
    {
        public String toString()
        {
            //Can access AClass's id!!!
            return id+"";
            //or
            //return AClass.this.id;
        }
    }   

    public static void main(String [] args)
    {
        AClass ac = new AClass(188);
        System.out.println(ac);
    }
}

19、内部类引用外部类的域:直接隐式写.field or OutClass.this.field

20、在外部,可以这样引用内部类OuterClass.InnerClass

21、实际上,内部类在破坏访问的同时,也为黑客留下了漏洞,如果一个类包含内部类,则可以模仿内部类,侵入内存中改类的private域。

22、局部内部类:直接在类的方法中声明内部类,不能用public或者private声明,出了}的生命周期,就没发再访问到所有改类的对象了!

public class AClass
{
    public AClass(int aid)
    {
        id = aid;
    }

    public String toString()
    {
        class INClass
        {
            public String toString()
            {
                //Can access AClass's id!!!
                return id+"";
                //or
                //return AClass.this.id+"";
            }
        }
        return (new INClass()).toString();
    }

    int id;

    public static void main(String [] args)
    {
        AClass ac = new AClass(188);
        System.out.println(ac);
    }
}

23、内部类如果有自己的域,必须声明为final!!否则编译过不去。这个是有原因的。

24、如果上面这个约定必须打破。。我们可以用长度为1的数组代替基本类型。。对对象引用,这个final是没影响的,你懂的。。

25、匿名内部类:这个在GUI里非常常见:

import java.awt.event.*;
import javax.swing.*;
public class AnonyClass
{
    public AnonyClass()
    {   

    }   

    public void start()
    {
        Timer timer = new Timer(1*1000, new ActionListener()
                {
                public void actionPerformed(ActionEvent event)
                {
                System.out.println("Called");
                }
                });
        timer.start();
    }   

    public static void main(String args [])
    {
        AnonyClass ac = new AnonyClass();
        ac.start();
        try
        {
            Thread.sleep(1000*1000*10);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

26、静态内部类:只有内部类可以声明为static,此时,类中不能又方法调用outerSpace的域成员。一般是为了防止命名冲突,而把它以static class的方式,放在类内部!一般是为了解决命名冲突。

public class StaticInnerTest {

    public static void main(String[] args) {

        double[] d=new double[20];
        for(int i=0;i<d.length;i++)
        {
            d[i]=100*Math.random();
            ArrayAlg.Pair p=ArrayAlg.minmax(d);
            System.out.println("min= "+p.getFirst());
            System.out.println("max= "+p.getSecond());
        }

    }

}

class ArrayAlg
{
    public static class Pair
    {
        public Pair(double f,double s)
        {
            first=f;
            second=s;
        }   

        private double first;
        private double second;
        public double getFirst() {
            return first;
        }
        public void setFirst(double first) {
            this.first = first;
        }
        public double getSecond() {
            return second;
        }
        public void setSecond(double second) {
            this.second = second;
        }   

    }   

    public static Pair minmax(double[] values)
    {
        double min=Double.MAX_VALUE;
        double max=Double.MIN_VALUE;
        for(double v:values)
        {
            if(min>v) min=v;
            if(max<v) max=v;
        }
        return new Pair(min,max);

    }
}

27、代理(Proxy):在运行时动态创建一组给定接口的新类。在运行时创建全新的类。它是Java 1.3后添加的新功能。

但是,不能在运行时指定新类的代码,因此,代码的填充需要借助调用处理器(invocation handler)完成,它是实现了InvocationHandler接口的类对象。

28、如何创建一个代理的对象?调用Proxy的newProxyInstance,如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

29、代理常用于类的包装:批量对若干类包装,在被包装(被代理)类调用前、后打印Log、关闭DB链接等等这种。

30、代理流程

(1)实现接口InvocationHandler
(2)将被代理的类包装到(1)中的代理类中 handler= new XXHandler(obj),obj为被代理对象
(3)Proxy.newProxyInstance(null, new Class[]{XXInterface,..}, handler)

下面的代码实现了一个在调用前打印被调用方法(和参数)的代理类:

class PrintHandler implements InvocationHandler
{
    public PrintHandler(Object atarget)
    {   
        this.target = atarget;
    }   

    public Object invoke(Object proxy, Method method,
            Object[] args) throws Throwable
    {   
        System.out.print(method.getName());
        System.out.print("(");
        boolean first = true;
        for(Object arg:args)
        {   
            if(first)
            {   
                first = false;  
            }   
            else
            {   
                System.out.print(",");
            }   
            System.out.print(arg);
        }   
        System.out.println(")");
        return method.invoke(target, args);
    }   

    private Object target;
}
import java.lang.reflect.*;

public class TestProxy
{
    public static void main(String [] args)
    {   
        String str = "abc";
        PrintHandler handler = new PrintHandler(str);
        Object str_obj = Proxy.newProxyInstance(null, new 
                Class[]{Comparable.class}, handler);
        String str2 = "ee";
        System.out.println(str_obj.equals(str2));
    }   
}

31、在JVM虚拟机中,代理类以$Proxy开头。

32、代理类必须是public和final的。

第六章完。

 

2 thoughts on “Java核心技术(第8版) – 读书笔记 – 第6章

  1. vanxining

    多继承会显著降低语言的运行效率,比如C++。
    ===================
    这个应该是不正确的,多继承假如没有虚函数的话,效率和单继承是一样的,即使有也是很轻微的影响。
    多继承的复杂性主要体现在编译器的实现上而不是程序的运行效率上。

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *