java 核心技术卷I学习记录(五)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhao__zhen/article/details/83245121

java 核心技术卷I学习记录(五)

本次记录内容只要包括java核心技术卷I第五章 继承 的相关内容。

##类、超类和子类

  1. 继承
    在java中所有的继承都是公有继承,而没有c++中的私有继承和保护继承。

  2. 覆盖方法

  • 在子类中使用super关键字时,因为super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是, 如果超类方法是public, 子类方法一定要声明为public。
  1. 子类构造器
public Decedent(String name,double salary){
        super(name,salary);
        this.bonus = 0;
    }

super 调用构造器的语句必须是子类构造器的第一条语句。如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器, 并且在子类的构造器中又没有显式地调用超类的其他构造器则Java 编译器将报告错误。

  1. 多态
    有一个用来判断是否应该设计为继承关系的简单规则, 就是“ is-a” 规则, 它表明子类的每个对象也是超类的对象。在java中,对象变量是多态的。一个employee变量既可以引用一个Employee类对象,也可以引用一个Employee类的任何一个子类队形。但是不能将一个超类的引用赋予子类变量。
		Decedent boss = new Decedent("zhaozhen",40000);
        boss.setBonus(800);

        Emp[] staff = new Emp[3];
        staff[0] = boss;

但是要注意boss.setBonus(800)是正确的,但是staff[0].setBonus(800)是错误的。这是因为staff[0] 声明类型是Employee,而setBonus不是employee类的方法。同时子类数组的引用可以转换为超类数组的引用,不需要进行强制类型转换。
5. 方法调用的流程。

假设有要调用x.f(args).

  • 编译器查看对象的声明类型和方法名。假设调用x.f(param),且隐式参数x ,声明为C类的对编译器将会一一列举所有C 类中名为f 的方法和其超类中访问属性为public 且名为f 的方法。
  • 编译器查看调用方法时提供的参数类型。如果在所有名为f 的方法中存在一个与提供的参数类型完全匹配, 就选择这个方法。这个过程被称为重栽解析( overloading resolution )。在这个过程可能出现类型转换。
  • 如果是private 方法、static 方法、final 方法(有关final 修饰符的含义将在下一节讲述)或者构造器, 那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称为静态绑定( static binding )。与此对应的是,调用的方法依赖于隐式参数的实际类型, 并且在运行时实现动态绑定。当程序运行,并且采用动态绑定调用方法时, 虚拟机一定调用与x 所引用对象的实际类型最合适的那个类的方法

每次调用方法都要进行搜索,时间开销相当大。因此, 虚拟机预先为每个类创建了一个
方法表( method table), 其中列出了所有方法的签名和实际调用的方法。这样一来,在真正
调用方法的时候, 虚拟机仅查找这个表就行了,该表示例如下:

Employee:
getName() -> Employee.getName()
getSalary() -> Employee.getSalary()
raiseSalary(double) -> Employee. raiseSalary(double)

Manager:
getName() -> Employee.getName()
getSalary() -> Manager.getSalary()
raiseSalary(double) -> Employee. raiseSalary(double)
setBonus(double) -> Manager.setBonus(double)

在真正运行时,调用e.getSalary()的解析过程为:

  • 首先是提取e的方法表。既可能是Employee、Manager 的方法表,也可能是Employee 类的其他子类的方法表
  • 接下来,虚拟机搜索定义getSalary 签名的类。此时, 虚拟机已经知道应该调用哪个方法。
  • 最后, 虚拟机调用方法。
  1. final类和final方法
    若在定义类时在类名前面使用final关键字时,那么其他类就不能继承该类。final也可在方法名前面使用,此时该方法就变为final方法,子类就不能对该方法进行重写(final类中的所有方法默认为final方法)。

如果将一个类声明为final, 只有其中的方法自动地成为final ,而不包括域.

虚拟机中的即时编译器可以准确地知道类之间的继承关系, 并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖, 那么即时编译器就会将这个方法进行内联处理。

  1. 强制类型转换
    在强制类型转换时,要先查看一下能否成功的转化。这个过程简单的使用instanceof操作符就可以实现。

只能在继承层次内进行类型转换。

  1. 抽象类
    包含一个或多个抽象方法的类本身必须被声明为抽象类。

扩展抽象类可以有两种选择。一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
类即使不含抽象方法,也可以将类声明为抽象类,抽象类不能被实例化。也就是说, 如果将一个类声明为abstract , 就不能创建这个类的对象。

//抽象基类
public abstract class Person {
    private String name;

    public Person(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract String getDescription();
}

//子类1
class Student extends  Person{
    private String major;

    public Student(String name,String major){
        super(name);
        this.major = major;
    }

    public String getMajor() {
        return major;
    }

    public void setMajor(String major) {
        this.major = major;
    }
    @Override
    public String getDescription() {
        return "a student majoring in" + major;
    }
    }
//子类2
class Staff extends Person {

    private  String job;

    public Staff(String name, String job) {
        super(name);
        this.job = job;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String getDescription() {
        return "his job IS " + job;
    }
}

//测试
class PersonTest{
    public static void main(String[] args) {
        Person[] people = new Person[2];
        people[0] = new Student("zhaozhen","computer");
        people[1] = new Staff("zhaozhen","teacher");
        for (Person a : people){
            System.out.println(a.getName()+ a.getDescription());
        }
    }
}

Java 用于控制可见性的4 个访问修饰符:

  • 仅对本类可见 --private
  • 对所有类可见 --public
  • 对本包和所有子类可见 --protected
  • 对本包可见 – (默认)不需要修饰符

object 类

1 .equals()方法与==的区别

java中的数据类型可分为两类:
基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean。
基本数据类型的比较,应使用==来比较他们的值

数据类型(类):
当这些复合数据类型使用== 来进行比较时,==是比较的他们存放在内存的地址(所以除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false)。

JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法(该equals方法也在定义时也是使用== 来比较的),这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被重写了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了

所以说,对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被复写,按照复写的要求来。

  1. 下面可以从两个截然不同的情况看一下这个问题:

如果子类能够拥有自己的相等概念, 则对称性需求将强制采用getClass 进行检测
如果由超类决定相等的概念,那么就可以使用instanceof 进行检测, 这样可以在不同子类的对象之间进行相等的比较

  1. 下面给出编写一个完美的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.getCIassO) return false;
    如果所有的子类都拥有统一的语义,就使用instanceof 检测:
    ​ if (!(otherObject instanceof ClassName)) return false;

  5. 将otherObject 转换为相应的类类型变量:
    ​ ClassName other = (ClassName) otherObject

  6. 现在开始对所有需要比较的域进行比较了。使用= 比较基本类型域,使用equals 比较对象域。如果所有的域都匹配, 就返回true; 否则返回false。

  1. toString()

int[] luckyNumbers = { 2, 3, 5, 7,9,11 13 } ;

String s = “” + luckyNumbers; 将生成[I@la46e30],修正的方式是使用Arrays.toString.

要打印多维数组时,应调用arrays.deepToString方法。

泛型数组列表

  1. ArrayList

ArrayList 是一个采用类型参数( type parameter ) 的泛型类( generic class )。为了指定数组列表保存的元素对象类型, 需要用一对尖括号将类名括起来加在后面.

ArrayList<Person> staff = new ArrayList<>();

这被称为“ 菱形” 语法, 因为空尖括号<>就像是一个菱形。可以结合new 操作符使用菱形语法.

ArrayList管理着对象引用的一个内部数组。若数组的全部空间被用尽是,可以使用add方法进行继续添加元素。

empList.add(new Student("zane","computer"));

对象包装器与自动装箱

需要将int 这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。例如,Integer 类对应基本类型int。通常, 这些类称为包装器( wrapper ),这些对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character 、Void 和Boolean ( 前6 个类派生于公共的超类Number)。对象包装器类是不可变的, 即一旦构造了包装器, 就不允许更改包装在其中的值。对象包装器类还是final , 因此不能定义它们的子类。

在泛型数组列表尖括号中的类型参数不允许是基本类型, 也就是说,不允许写成ArrayList 。里就用到了Integer 对象包装器类。我们可以声明一个Integer 对象的数组列表。

ArrayList<Integer> list = new ArrayList<>()

注意: 泛型数组列表中的每个值都包装在对象中国,所以类似ArrayList 的效率远远低于int[] 数组。因此,应该用它构造小型集合。

自动装箱

list.add(3);//该语句在执行时将自动变换为以下语句

list.add(Integer.valueOf(3)); //这种变换就被称为自动装箱

自动拆箱

int n = list .get(i);//该语句在执行时将自动变换为以下语句
int n = list.get(i).intValue();//这种操作就是自动拆箱

Integer n = 3;
n++//也涉及到自动装箱和自动拆箱,编译器将自动地插人一条对象拆箱的指令, 然后进行自增计算,最后再将结果装箱。

注意 :自动装箱规范要求boolean、byte、char 127, 介于-128 ~ 127 之间的short 和int 被包装到固定的对象中。如果在一个条件表达式中混合使用Integer 和Double 类型, Integer 值就会拆箱,提升为double, 再装箱为Double;Integer 对象是不可变的, 包含在包装器中的内容不会改变: 不能使用这些包装器类创建修改数值参数的方法。

参数数量可变的方法

参数数量可变的方法的典型例子就是 printf 方法。 例如system.out.printf("%d",n);和System.out.printf("Xd %s\ n, “widgets”) ;

printf 方法的定义是:

public class PrintStream
{
	public PrintStream printf(String fmt , Object . .. args) { return format(fmt, args) ; }
}

这里的省略号. . . 是Java 代码的一部分,它表明这个方法可以接收任意数量的对象(除fmt参数之外)。printf方法接收两个参数,一个是格式字符串,另一个就是Object[]数组。

枚举类

语法格式:

public enum Size{
    SMALL,
    MEDIUM,
    LARGE,
    EXTRA_LARGE
};

上面声明定义枚举实际上是一个类,它刚好有四个实例,因此在比较两个枚举类型的值时,不要使用equals,而是直接使用‘==’就行!

反射

反射:能够分析类能力的程序

功能:

  1. 在运行时分析类的能力,能够检查类的结构。
  2. 在运行时查看对象。
  3. 实现通用的数组操作代码
  4. 利用Method对象,和c++中的函数指针类似。

反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者, 而不是应用程序
员。

class类

在java程序运行期间,Java 虚拟机始终为所有的对象维护一个被称为运行时的类型标识。
这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。可以通过专门的Java 类访问这些信息,保存这些信息的类就是Class类。

继承的设计技巧

  1. 将公共操作和域放在超类。

  2. 不使用受保护的域。

    ①子类集合是无限的,任何人都可以派生出子类,并访问或修改实例域的值,这样就破坏了封装性

    ②在同一个包中的任何类都可以访问protected域。

  3. 使用继承实现“is-a”关系

  4. 除非所有继承的方法都有意义, 否则不要使用继承

  5. 在覆盖方法时, 不要改变原方法的预期的行为。

  6. 使用多态,而非类型信息

  7. 不要过多的使用反射

    反射机制使我们可以在运行时查看域和方法。这种功能对编写系统程序来说极其使用,但是通常不适于编写应用程序。另外编译器很难帮助我们找出程序中的错误,只有在运行时才能发现错误并且导致异常。

展开阅读全文

没有更多推荐了,返回首页