1、面向对象的特征二:继承性
1.1、继承性的规定
- 一个类可以被多个类继承
- Java 中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类,称为:间接父类。
- 子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法。
- 如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类
- 所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类;
- 意味着,所有的 java 类具有 java.lang.Object 类声明的功能。
class A{
public A() {
System.out.println("A、A类的构造方法");
}
}
class B extends A{
public B() {
System.out.println("B、B类的构造方法");
}
}
public class Demo04 {
public static void main(String[] args) {
new B();
}
为什么要有继承?
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,
- 那么多个类无需再定义这些属性和行为,只要继承那个类即可。
- 继承性的好处
- 减少了代码的冗余,提高了代码的复用性;
- 便于功能的扩展;
- 为之后多态性的使用,提供了前提。
继承性的格式
class A extends B{}
- A:子类、派生类、subclass
- B:父类、超类、基类、superclass
- 体现:一旦子类 A 继承父类以后,子类 A 中就获取了父类 B 中声明的结构:属性、方法
特别的,父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。
只有因为封装性的影响,使得子类不能直接调用父类的结构而已。 - 子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。
子类和父类的关系:不同于子集与集合的关系。
extends:延展、扩展
2、方法的重写(override/overwrite)
2.1、重写的规定
方法的声明
权限修饰符 返回值类型 方法名(形参列表){
//方法体
}
- 重写:子类继承父类以后,可以对父类中的方法进行覆盖操作。
- 应用:重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法。
即在程序执行时,子类的方法将覆盖父类的方法。 - 面试题:
- 问:区分方法的重载与重写(有的书也叫做“覆盖”)
- 答:方法的重写Overriding和重载Overloading是Java多态性的不同表现。
重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。
如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。
子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。
如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}
重写的细节
- 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法。
- 子类重写的方法的方法名和形参列表必须和父类被重写的方法的方法名、形参列表相同;
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限,
特殊情况: 子类不能重写父类中声明为private权限的方法; - 返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void;
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;
- 父类被重写的方法的返回值类型如果是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须是:double)。
- 子类方法抛出的异常不能大于父类被重写的方法抛出的异常;
- 注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。
因为static方法是属于类的,子类无法覆盖父类的方法。
3、四种访问权限修饰符
4、关键字:super
- super理解为:父类的
- super可以用来调用:属性、方法、构造器
- super的使用
- 我们可以在子类的方法或构造器中,通过"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯去省略这个"super."
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法后,我们想在子类的方法中调用父类中被重写的方法时,必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
- super调用构造器
- 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- "super(形参列表)"的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现。
- 在构造器的首行,既没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认的调用的是父类中的空参构造器。super()
- 在类的多个构造器中,至少有一个类的构造器使用了"super(形参列表)",调用父类中的构造器。
class Person {
public static void prt(String s) {
System.out.println(s);
}
Person() {
prt("父类·无参数构造方法: "+"A Person.");
}//构造方法(1)
Person(String name) {
prt("父类·含一个参数的构造方法: "+"A person's name is " + name);
}//构造方法(2)
}
public class Chinese extends Person {
Chinese() {
super(); // 调用父类构造方法(1)
prt("子类·调用父类"无参数构造方法": "+"A chinese coder.");
}
Chinese(String name) {
super(name);// 调用父类具有相同形参的构造方法(2)
prt("子类·调用父类"含一个参数的构造方法": "+"his name is " + name);
}
Chinese(String name, int age) {
this(name);// 调用具有相同形参的构造方法(3)
prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
}
public static void main(String[] args) {
Chinese cn = new Chinese();
cn = new Chinese("codersai");
cn = new Chinese("codersai", 18);
}
}
运行结果
父类·无参数构造方法: A Person.
子类·调用父类”无参数构造方法“: A chinese coder.
父类·含一个参数的构造方法: A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
父类·含一个参数的构造方法: A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
子类:调用子类具有相同形参的构造方法:his age is 18
5、子类对象实例化过程
- 从结果上看:
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象中,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类结构,所以才可以看到内存中有父类中的结构,子类对象可以考虑进行调用。
- 明确:虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象。
6、面向对象特征之三:多态性
- 理解多态性:可以理解为一个事物的多种态性。
- 何为多态性:
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
- 多态的使用:虚拟方法调用
- 有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法
简称:编译时,看左边;运行时,看右边。 - 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,
- “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
- 有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法
- 多态性的使用前提:
- 类的继承关系
- 方法的重写
- 对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
运行结果
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
6.1、虚拟方法的补充
-
从编译和运行的角度看:
- 重载,是指允许存在多个同名方法,而这些方法的参数不同。
- 编译器根据方法不同的参数表,对同名方法的名称做修饰。
- 对于编译器而言,这些同名方法就成了不同的方法。
- 它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
- 所以:对于重载而言,在方法调用之前,
- 编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
- 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
-
“不要犯傻,如果它不是晚绑定,它就不是多态。”——Bruce Eckel
-
面试题:
- 问:多态是编译时行为还是运行时行为?
- 答:运行时行为。可利用random随机数随机生成对象,在运行时产生对象,来证明多态试运行时行为。
-
证明如下:
import java.util.Random;
/*
*
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
6.2、向下转型的使用
7、Object 类的使用
java.lang.Object类
- Object类是所有Java类的根父类;
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
- Object类中的功能(属性、方法)就具有通用性。
- 属性:无
- 方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
- Object类只声明了一个空参的构造器。
- 面试题:
- 问:final、finally、finalize的区别?
- 答:
- final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
- finally是异常处理语句结构的一部分,表示总是执行。
- finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
7.1、Object类中的主要结构
7.2、==操作符与equals方法
- 面试题:
- 问:==和equals的区别
- 答:
- ==的使用
- ==:运算符
- 可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
- 补充:== 符号使用时,必须保证符号左右两边的变量类型一致。
- equals()方法的使用
- 是一个方法,而非运算符
- 只能适用于引用数据类型。
- Object类中equals()的定义:
- ==的使用
public boolean equals(Object obj){
return (this == obj);
}
-
说明:Object类中定义的equals()和==的作用是相同的,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
-
像String、Date、File、包装类等都重写了Object类中的equals()方法。两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同。
-
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。
-
重写的原则:比较两个对象的实体内容是否相同。
重写equals()方法的原则
- 对称性:如果
x.equals(y)
返回是“true”,那么y.equals(x)
也应该返回是“true”。 - 自反性:
x.equals(x)
必须返回是“true”。 - 传递性:如果
x.equals(y)
返回是“true”,而且y.equals(z)
返回是“true”,那么z.equals(x)
也应该返回是“true”。 - 一致性:如果
x.equals(y)
返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)
多少次,返回都是“true”。 - 任何情况下,
x.equals(null)
,永远返回是“false”;x.equals
(和x不同类型的对象)永远返回是“false”。
int it = 65;
float fl= 65.0f;
System.out.println("65和65.0f是否相等?" + (it == fl)); //true
char ch1 = 'A';
char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println("12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2));//false
System.out.println("str1是否equals str2?"+(str1.equals(str2)));//true
System.out.println("hello" == new java.util.Date()); //编译不通过
7.3、toString的使用
- Object类中toString()的使用
- 当我们输出一个引用对象时,实际上就是调用当前对象的toString()
- Object类中toString的定义方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用toString()时,返回"实体内容"信息.
- 自定义类如果重写toString()方法,当调用此方法时,返回对象的"实体内容"。
8、包装类(Wrapper)的使用
8.1、单元测试方法的使用
import java.util.Date;
import org.junit.Test;
/*
* java中的JUnit单元测试
*
* 步骤:
* 1.选中当前项目工程 --》 右键:build path --》 add libraries --》 JUnit 4 --》 下一步
* 2.创建一个Java类进行单元测试。
* 此时的Java类要求:①此类是公共的 ②此类提供一个公共的无参构造器
* 3.此类中声明单元测试方法。
* 此时的单元测试方法:方法的权限是public,没有返回值,没有形参。
*
* 4.此单元测试方法上需要声明注解:@Test并在单元测试类中调用:import org.junit.Test;
* 5.声明好单元测试方法以后,就可以在方法体内测试代码。
* 6.写好代码后,左键双击单元测试方法名:右键 --》 run as --》 JUnit Test
*
* 说明:如果执行结果无错误,则显示是一个绿色进度条,反之,错误即为红色进度条。
*/
public class JUnit {
int num = 10;
//第一个单元测试方法
@Test
public void testEquals(){
String s1 = "MM";
String s2 = "MM";
System.out.println(s1.equals(s2));
//ClassCastException的异常
// Object obj = new String("GG");
// Date date = (Date)obj;
System.out.println(num);
show();
}
public void show(){
num = 20;
System.out.println("show()...");
}
//第二个单元测试方法
@Test
public void testToString(){
String s2 = "MM";
System.out.println(s2.toString());
}
}
8.2、包装类的使用
- java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
- 注意:其中Byte、Short、Integer、Long、Float、Double的父类是:Number
8.3、包装类与基本数据类型相互转换
import org.junit.Test;
/*
* 2.基本数据类型、包装类、String三者之间的相互转换。
*
*/
public class WrapperTest {
//String类型---> 基本数据类型、包装类,调用包装类的parseXxx()
@Test
public void test5(){
String str1 = "123";
// String str1 = "123a";
//错误的情况,可能会报错
// int num1 = (int)str1;
// Integer in1 = (Integer)str1;
int num2 = Integer.parseInt(str1);
System.out.println(num2 + 1); //124
String str2 = "true";
Boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1); //true
}
//基本数据类型、包装类---》String类型,调用String重载的valueOf(Xxx xxx)
@Test
public void test4(){
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1); //"12.3"
Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
System.out.println(str2);
System.out.println(str3); //"12.4"
}
/*
* JDK 5.0 新特性:自动装箱与自动拆箱
*/
@Test
public void test3(){
// int num1 = 10;
// //基本数据类型 --》 包装类的对象
// method(num1); //Object obj = num1
//自动装箱:基本数据类型 --》 包装类
int num2 = 10;
Integer in1 = num2;//自动装箱
boolean b1 = true;
Boolean b2 = b1;//自动装箱
//自动拆箱:包装类 --》 基本数据类型
System.out.println(in1.toString());
int num3 = in1;
}
public void method(Object obj){
System.out.println(obj);
}
//包装类 --》 基本数据类型:调用包装类的xxxValue()
@Test
public void test2() {
Integer in1 = new Integer(12);
int i1 = in1.intValue();
System.out.println(i1 + 1);
Float f1 = new Float(12.3f);
float f2 = f1.floatValue();
System.out.println(f2 + 1);
}
//基本数据类型--》包装类,调用包装类的构造器
@Test
public void test1() {
int num1 = 10;
// System.out.println(num1.toString());
Integer in1 = new Integer(num1);
System.out.println(in1.toString());
Integer in2 = new Integer("123");
System.out.println(in2.toString());
//报异常
// Integer in3 = new Integer("123abc");
// System.out.println(in3.toString());
Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
System.out.println(f1);
System.out.println(f2);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("true");
Boolean b3 = new Boolean("true123");
System.out.println(b3); //false
Order order = new Order();
System.out.println(order.isMale); //false
System.out.println(order.isFemale); //null
}
}
class Order{
boolean isMale;
Boolean isFemale;
}