java基础知识—面向对象和继承
一、变量
- 静态变量
- 成员变量
- 局部变量
详情可见 浅谈Java变量。
二、修饰符
详情可见 梳理Java修饰符。
三、Java方法参数——按值传递
首先回顾两个概念:
1.按值传递:表示方法接受的是调用者提供的值
2.按引用传递:表示方法接受的是调用者提供的变量的地址
其中:方法是可以修改按引用传递的变量的值
Java语言总是采用按照按值传递,也就是说java 的方法不能改变传递给它的任何参数变量的内容
举个栗子:
double percent = 10;
tripleValue(percent);
public static void tripleValue(double x) {
x = 3 * x;
}
运行如上代码你会发现:percent的值依旧是10。下面我们详细分解一下具体执行的过程:
1.x初始化为percent值得一个副本(也就是10);
2.x乘以3后等于30,但是percent仍然是10.(如下图所示)
3.方法结束后,参数变量x不再使用。
上面的例子我们可以看到是使用了基本类型,然而方法的参数除了基本类型之外,还有对象引用,我们已经通过上面的例子已经知道了,方法是不可能改变基本类型的,而对象引用作为参数就不同了。我们看下面这个例子:
harry = new Employee(...);
tripleValue(harry);
public static void tripleValue(Employee x) {
x.raiseSalary(200);
}
运行上面的代码,我们可以看到harry的工资确实增长了200;这是为什么呢?我们来看一下具体的执行过程:
1.x初始化为harry值得一个副本,注意:这里是一个对象的引用
2.raiseSalary方法应用于这个对象引用。x和harry同时引用的都是同一个Employee的对象。(如下图所示)
3.方法结束后,x不再使用。但是harry依旧引用了那个Employee的对象
由此可以看到,实现一个改变对象参数状态的方法是完全可以的,所以有人会有疑问,这样不就跟上面所述的java 的方法不能改变传递给它的任何参数变量的内容
自相矛盾了吗?其实不然,我们可以看到第二个例子中,方法中传递的是对象的引用,也就是指向这个对象的地址,依旧是一个值。所以由此可以说明java方法中,是采用按值传递的原则,对象的引用实际上也是按值传递
四、Java中的三大特点:封装、继承和多态
1.什么是封装
封装:从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方法。简言之,把对象的属性私有化,通过提供对外可用的方法来访问属性。封装给对象赋予了“黑盒”的特性。
2.什么是继承
继承:继承的基本思想就是可以基于已有的类创建新的类。继承已经存在的类就是继承这些类的方法。我们可以用“is - a”的关系来描述继承。
关于继承,可以详情可见 。
3.什么是多态
多态:一个对象变量可指示多个实际类型的现象称为多态。在程序运行时,才能够自动地选择适当的方法。由此可以明白,java程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。这种在运行时才决定使用哪儿中方法的现象称之为“动态绑定”。
4.方法调用
由于多态性的存在,准确理解如何在对象上应用方法调用肥肠重要:),下面根据一个例子详细介绍调用过程的步骤:
例:假设需要调用 x.f(String)
,隐式参数x声明为类C的一个对象。
1.编译器查看对象的声明类型和方法名。需要注意,有可能存在多个名字为f但参数类型不一样的方法。例如:f(int)
,f(String)
,编译器会一一列举C类中所有名为f的方法和超类中所有名为f且可以访问的方法。。至此,编译器已经知道所有可能被调用的候选方法。
2.编译器需要确定方法调用中提供的参数类型。如果所有候选列表中存在一个与所提供的参数类型完全匹配的方法,则选择此方法。这个过程叫做重载解析。至此编译器已经知道需要调用的方法名和参数类型。
3.如果是private
方法,static
方法,final
方法或者构造器,那么编译器可以准确的知道应调用哪儿个方法。这个称为“静态绑定”。与之对应,如果要调用的方法依赖于隐式参数的实际类型,那么必选在运行时使用动态绑定。在该例子中会利用动态绑定生成一个调用f(String)的指令。
4.程序运行且采用动态绑定机制,虚拟机必须调用与x所引用对象的实际类型对应的那个方法。假设x实际类型是D,D是C的子类。如果D中声明了f(String)
则调用,反之去超类中找。
五、重载(overloading)与覆盖(override)的区别
1.什么是重载(overloading)
在一个类中,如果多个方法有相同的签名(签名:方法名和参数类型,不包含返回类型),便出现了重载。无参构造函数和有参构造函数是最明显的例子了。
2.什么是覆盖(override)
覆盖(override)适用于继承当中的,当子类中的某个方法的签名和超类的方法签名一样,则出现了覆盖。出现覆盖的原因在于,有的时候父类中的部分方法不适用与子类。
例如:父类为Employee,子类为Manager,父类中有个getSalary的方法,但是由于子类Manager中的getSalary的方法想返回基本工资和奖金(普通员工没有),则需要在子类中重新实现getSalary的方法,即覆盖,格式如下(使用super
的关键字):
public double getSalary() {
double baseSalary = super.getSalary(); // 此处调用了父类的getSalary的方法
return baseSalary + bonus;
}
**注意:在覆盖中,返回值的范围要小于父类,抛出的异常范围小于等于父类,访问修饰符不能低于父类,如果父类中的方法修饰符为private,则子类就不能重写此方法。
六、自动装箱和拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
七、equals和hashCode
在我们coding过程中,经常碰到重写equals方法,但是我们为什么重写equals的时候必须重写hashCode()方法呢?
1)、如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
2)、如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。
此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。