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

 

1、继承(inheritance):利用已存在的类构造一个新类,复用前者的方法和域。

2、反射(reflection):程序运行期间发现更多的类及其属性的能力。

3、继承:is-a关系。

public class Manager extends Emplyoee
{
//......
public void setBounds(double b)
{
    this.bouns = b;
}

private double bouns;
}

已经存在的类(例如上面的Emplyoee)被称为超类(super class)、基类(base class)或者父类(parent class)。派生出来的新类称为子类(sub class)、派生类(derived class)、孩子类(child class)。

4、在上面的例子中,子类新定义了奖金bouns,这个在父类Emplyoee中是不能访问到的。

5、子类也可以覆盖(over-ride)父类的方法,直接定义同名、同参数的函数即可。

class Manager extends Employee
{
    public double getSalary()
    {
        return super.getSalary() + this.bouns;
    }
}

注意在子类中,不能调用getSalary,虽然你想的是能调用Employee,但实际这样会无限次调用自己,导致堆栈溢出。也不能return super.salary+this.bouns。因为前者salary是父类private的。

6、总之,子类可以添加、覆盖父类的方法,但不能删除域名和方法。

7、子类的构造函数中应当主动显示调用父类构造函数,否则,Java将用父类默认的构造函数初始化!

8、如下的代码中子类Manager覆盖了父类的Employee的getSalary()方法,当引用为Employee类型时,也能成功调用Manager的getSalary()方法。这种现象称为多态(polymorphism),这种自动选择哪个函数的称为动态绑定(dynamic binding)。

public class Manager extends Employee
{
    public Manager(String n, double s, int year, int month, int day, double b)
    {
        super(n, s, year, month, day);
        this.bouns = b;
    }   

    public void setBouns(double b)
    {
        this.bouns = b;
    }   

    public double getSalary()
    {
        return super.getSalary() + this.bouns;
    }   

    private double bouns;
}
public class ManagerTest
{
    public static void main(String [] args)
    {
        Employee[] es = new Employee[3];
        //多态
        es[0] = new Manager("Ma", 20000, 1999, 9, 9, 5000);
        es[1] = new Employee("li", 20000, 1999, 9, 9);
        es[2] = new Employee("ME", 20000, 1899, 3, 9);
        for(Employee e: es)
        {
            //动态绑定,识别出Manager对应的getSalary()
            System.out.println("name:"+e.getName()+",salary:"+e.getSalary()+",hire:"+e.getHireDay());
        }
    }
}

9、Java默认都是动态绑定,无需C++声明为virtual才动态绑定。如果希望停用动态绑定,则把方法、类加上final即可。例如String类。

10、Java支持多层继承,但不支持多继承。多继承可以用接口的方法实现。

11、多态不是万能的:用父类引用类型不能访问子类(新增加)的方法,这是显然的。

12、动态绑定依赖于:每个类会生成自己的方法表,JVM通过查找方法表,决定调用父类还是子类的那个方法。

13、声明为final的类、方法可以确保不被继承,也不会有多态和动态绑定。、

14、可以”向上提升“类型(强制转换):

//OK
Manager m  = staff[0];

但假的提升会抛出ClassCastException:

//把实际是Employee的,假提升为Manager,骗不过JVM的。。ClassCastException
Manager m  = (Manager)staff[1];

15、如何测试某个类是不是xx类型:

注意 子类 instanceof 父类 会返回true

System.out.println(es[0] instanceof Employee); // true
System.out.println(es[0] instanceof Manager); //true
System.out.println(es[1] instanceof Employee); //true
System.out.println(es[1] instanceof Manager); //false

16、抽象方法:使用description,无需实现该方法。

abstract class Person
{

    public abstract String getDescription();

    public String getName()
    {
        return name;
    }

    private String name;
}

17、抽象类不能被实例化(不能new对象)。

18、即使不含有抽象方法,也可以标记类为抽象abstract的。

19、希望父类中某些方法、域名被子类访问,又不想对其他类开放:声明为protected。protected使用还是要谨慎,因为很可能破坏OOP的封装原则。

20、4个访问修饰符。
private:仅对本类可见。
public:对所有类可见。
protected:对本包和所有子类可见。
默认(不声明):对本包可见。

21、Java中,所有类都默认派生于Object,但不必加上XXXClass extends Object。

22、Object引用可指向任何变量。

23、除了基本数据类型外,其他都是对象。(包括基本数据类型的数组)

24、Object中定义了equals,用于比较两个类对象是否相等。如果你得类也有类似逻辑,则重写equals即可。

25、检测equals之前,一定要查obj.getClass() == this.getClass(),只有类型相等了,才有必要能继续判断相应数据域是否相等。(书中不建议使用instanceof的方法,因为超类和子类逻辑上就不对等)

26、一个完美的equals方法:

(1)显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。

(2)检测this与otherObject是否引用同一个对象:

if (this == otherObject) return true;

这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。

(3)检测otherObject是否为null,如果为null,返回false。这项检测是很必要的。

if (otherObject == null) return false;

(4)比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测:

if (getClass() != otherObject.getClass()) return false;

如果所有的子类都拥有统一的语义,就使用instanceof检测:

if (! (otherObject instanceof ClassName)) retrun false;

(5)将otherObject转换为相应的类类型变量:

ClassName other = (ClassName)otherObject;

(6)现在开始对所有需要比较的域进行比较了。使用 == 比较

基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回ture;否则返回false。

return field1 == other.field1

&& field2.equals(other.field2)

&& ……;

27、equals,切记为:

public bool equals(Object OtherObject) // 对的
{
}

而不是:

public bool equals(XXClass OtherObject)
{
}

28、Boolean java.util.Arrays.equals(type [] a, type [] b),其中type是int、double、。。等基本数据类型,以及Object(即只要实现了equals方法,就能比较!)。用于比较两个数组的每个元素是否完全相等!

29、HashCode,应用于对象上,产生散列值,用于快速判断两个对象是否重复。可用于map等场景。hashCode方法应该返回一个int(可正负):

class XXX
{
    //overwrite Object's overwrite
    public int hashCode()
    {
        //compute hashCode
        return xxx;
    }
}

30、hashCode必须与equals一致:如果equals返回相等,则hashCode必须一致。

31、java.util.Arrays.hashCode(type[] a)有对数组进行Hashcode的方法:

32、toString()。格式化为:输出字符串时显示。例如System.out.println(a)时,就会调用a的toString()。

33、this.getClass().getName():返回包名.类名

34、Object中默认的toString()是打印类名+hashCode:例如java.io.PrintWriter@2f3381

35、Java提供了可变数组解决方案(类似C++的vector):ArrayList,可以使用它的泛型版本(限定之中存储的类型一致),或者非泛型限定版本(之中存储的元素视为Object)。

public boolean java.util.ArrayList.add(E e)

内部数组用尽后,会自动realloc。

如果一开始就像让他分配大些的空间:

public void java.util.ArrayList.ensureCapacity(int minCapacity)

size返回ArraryList中实际元素个数:

public int java.util.ArrayList.size()

当不再需要add(),即动态数组大小已经确定之后,可以调用。因为add时如果内部空间不够,会预先多分配一些,当确定大小不会变动后,trimtosize可以释放那些多余分配的缓存。

public void java.util.ArrayList.trimToSize()

访问ArrayList需要使用arr.set(i, obj)和arr.get,不能用[]。

//获取第i个位置的obj
public E java.util.ArrayList.get(int index)

//设置第i个位置的obj
public E java.util.ArrayList.set(int index, E element)

删除元素:

public E java.util.ArrayList.remove(int index)

在ArrayList的中部分插入、删除元素的代价非常大,此时应考虑更换为LinkedList。

36、使用非泛型版本的get时,会返回Object,此时需要自己进行强制转换。

37、如果想节省内存、或者想用[]访问数组,且数组在第一次初始化后就不会变化了,可以如下:

//ArrayList
ArrayList<X> list = new ArrayList<X>();

//Got all elem into list
while(..)
{
...
}

//To array
X[] a = new X[list.size()];
list.toArray(a);

38、可以用foreach遍历ArrayList

39、打包(autoboxing):当int、double等基本数据类型需要做为对象,用在ArrayList这种中时,Java会自动将它们转化为Integer、Double等。

相反,int a = new Integer(5);这种场景下,会发生自动拆包。

40、将字符串转化为int:Integer.parseInt(String str)

41、如果某个函数必须要修改参数中的int、double等,即下面代码无效时:

public void update(int n)
{
    //错误,无效的更新!
    n +=1;
}

可以使用org.omg.CORBA的IntHolder、BooleanHolder等。比如IntHolder中,包含public int,因此可以用于参数的修改。

42、从JDK5后,开始支持变参(函数的参数数量是可变的)。例如System.out.printf。

定义方法:

public static xxx(Type... values)

例如一个变参的max函数:

public class TestMax
{
    public static double max(double ... values)
    {
        double largest = Double.MIN_VALUE;
        for(double v: values)
        {
            if(v>largest)
            {
                largest = v;
            }
        }
        return largest;
    }

    public static void main(String [] args)
    {
        System.out.println(max(10.0, -10.0, -100, 9999));
    }
}

43、Enum是定义常量,实际上每个Enum定义出来的常量都是Enum的子类。例如:

Enum Size
{
    SMALL, MEDIUM, LARGE;
};

我们可以用字符串的S、M和L代表对应的常量SMALL、MEDIUM和LARGE,并且Enum提供了内置的转换:

注意,如果如上面定义,那么你得String必须是"SMALL",全称才可以!

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

判断两个常量是否相等,直接==就可以了。

返回常量在Enum中的下标:

public final int ordinal()

一个完整的例子:

import java.util.Scanner;

public class TestEnum
{
    public static void main(String [] args)
    {
        Scanner in = new Scanner(System.in);
        System.out.println("What size do you want?");
        //需要输入enum全名:SMALL !!
        String str = in.next().toUpperCase();
        Size s = Enum.valueOf(Size.class, str);
        System.out.println("ordinal " + s.ordinal());
        //输出缩写域名
        System.out.println("field " + s.getField());
    }
}

enum Size
{
    // 构造函数一个参数String,存缩略名
    SMALL("S"), MEDIUM("M"), LARGE("L");

    private Size(String afield)
    {
        this.field = afield;
    }   

    public String getField()
    {
        return field;
    }   

    private String field;
}

44、反射(Reflection):使程序能够动态操纵Java代码。
(1)在程序运行中动态分析类:获取类信息、函数、内置域等。
(2)动态修改函数,如为所有类设置toString()方法。
(3)实现泛型数组
(4)使用Method类,类似c++中的函数指针。

45、Class类,维护了类的运行时信息。每个类绑定一个Class类。
通过对象.getClass()获得Class类:

Employee e;
......
Class c1 = e.getClass();

也可以直接类名.class获得对应的Class类:

Class c10 = Date.class;
Class c11 = int.class;
Class c12 = Double[].class;

可以用==比较两个Class,判断它们是否实例化自同一个类:

Employee e1;
if(e1.getClass() == Employee.class)
{
    //yes, same class
}

Class.getName()获取包+类名:

Date d = new Date();
Class c2 = d.getClass();
String str = c2.getName(); // str == "java.util.Date"

如果想根据类名动态加载类(例如我们可能加载数据库驱动,但运行时才知道是加载MySQL、Oracle还是DB2。),此时,可以根据类名加载类:

String name = "java.util.Date";
Class c3 = Class.forName(name);

一般forName和newInstance一起使用,可以动态构造任意一个类(从字符串加载任意的Class,然后构造它的一个实例):

String name = "java.util.Date";
Object mdate = Class.forName().newInstance();

如果要动态创建的类使用非默认构造函数(构造函数有其他参数),则需要使用另一个方法构建:

public T newInstance(Object... initargs) throws .....

46、异常:

try
{
    //Code may cause Exception
}
catch(Exception e)
{
    e.printStackTrace();
}

47、获取类信息:java.lang.reflect.Field/Method/Constructor分别用于描述类的域、方法、构造函数。
方法:
Field/Method/Constroctor.getName() 返回名字
Field/Method/Constroctor.getModifiers() 获得是public/protected/private,以及static与否,返回的是int,用Modifiers.toString(int)转化为对应的String。或者用Modifiers.isPublic()/isXXX等判断是否有对应修饰符。
Field.getType() 返回域的类的所属类型
Class.getFields()/getMethods()/getConstructors() 获取类的public的域、方法和构造函数。
Class.getDeclareFields()/getDeclareMethods()/getDeclareConstructors() 获取定义的全部域、方法、函数,不管是否public。

48、关于反射动态获取类信息(从.class文件中提取)的例子如下:

import java.util.Scanner;
import java.lang.reflect.*;

public class ReflectionTest
{
    public static void main(String [] args)
    {
        String name = "java.util.Date";
        try
        {
            Class cls = Class.forName(name);

            printClass(cls);
            System.out.println("\n{");

            System.out.println("\n");
            printConstructor(cls);
            System.out.println("\n");

            System.out.println("\n");
            printMethod(cls);
            System.out.println("\n");

            System.out.println("\n");
            printFields(cls);
            System.out.println("\n}");
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }   

    }   

    public static void printClass(Class cls)
    {
        //Modifier: public/private/protected class
        String modifier = Modifier.toString(cls.getModifiers());
        if(modifier.length()>0)
        {
            System.out.print(modifier+" ");
        }

49、如果某个Method、Field等为private,直接调用它会产生IllegelException。此时可以用 AccessibleObject.setAccessible(Fields[], true)解决。

50、函数指针:

它的好处是:可以动态决定调用哪个函数(仅仅通过名字而不是多态)。

public Object Method.invoke(Object obj, Object... args)

第一个参数obj,对于非static来说,就是被操控的对象,对于static函数来说,应该为null。返回结果,一般要自己强制转化成理论正确的结果。

下面是一个用反射手动invoke开根号sqrt的例子:

import java.lang.reflect.*;
import java.util.Scanner;
public class MethodPointerTest
{
    public static void main(String [] args)
    {
        try
        {
            Method sqrt = Math.class.getMethod("sqrt", double.class);
            Scanner in = new Scanner(System.in);
            while(in.hasNextDouble())
            {
                double d = in.nextDouble();
                double res = (Double)sqrt.invoke(null, d);
                System.out.println(res);
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

51、使用反射机制调用的,比直接调用的要慢,并且更加容易出错。所以要谨慎使用。

52、一些类设计的原则:
(1)公共操作和域放在父类
(2)不要使用受保护的域
(3)使用继承实现is-a
(4)使用多态时候,注意类型信息
(5)尽量少用反射。

本章完

 

Leave a Reply

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