尚硅谷Java入门视频教程第五章——面向对象编程(中)
第5章:面向对象编程(中)
5.1 面向对象特征之二:继承性
面向对象的特征之二:继承性 why?
为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,
那么多个类无需再定义这些属性和行为,只要继承那个类即可。
-
继承性的好处:
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 为之后多态性的使用,提供了前提 -
继承性的格式:
class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。 - 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
子类和父类的关系,不同于子集和集合的关系。
extends:延展、扩展
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
-
Java中关于继承性的规定:
- 一个类可以被多个子类继承。
- Java中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
-
java.lang.Object类初识
- 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着,所有的java类具有java.lang.Object类声明的功能。
-
练习
根据下图实现类。在CylinderTest类中创建Cylinder类的对象,设置圆柱的底面半径和高,并输出圆柱的体积。
Circle类:package com.atguigu.exer; public class Circle { private double radius;//半径 public Circle() { radius = 1.0; } public void setRadiu(double radius) { this.radius = radius; } public double getRadius() { return this.radius; } //返回圆的面积 public double findArea() { return Math.PI * radius * radius; } }
Cylinder类
package com.atguigu.exer; public class Cylinder extends Circle{ private double length; public Cylinder() { length = 1.0; } public double getLength() { return length; } public void setLength(double length) { this.length = length; } //返回圆柱体的体积 public double findVolume() { return Math.PI * getRadius() * getRadius() * getLength(); //或 //return findArea() * getLength(); } }
CylinderTest类
package com.atguigu.exer; public class CylinderTest { public static void main(String[] args) { Cylinder cy = new Cylinder(); cy.setRadiu(2.1); cy.setLength(3.4); double area = cy.findArea(); System.out.println("圆柱体的底面积为:" + area); double volume = cy.findVolume(); System.out.println("圆柱体的体积为:" + volume); } }
5.2 方法的重写(override/overwrite)
- 重写:
子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。 - 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
- 重写的规定:
方法的声明:
约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ //方法体 }
- 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
- 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特殊情况:子类不能重写父类中声明为private权限的方法 - 返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)——与变量类型提升无关 - 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
实际开发中一般直接将父类方法的方法声明与参数列表直接复制粘贴到子类方法中,或者使用Eclipse的提示功能进行重写,防止手打出错。
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
- 面试题:区分方法的重载与重写:
-
二者的概念:
① 重载:
同一个类中,方法名相同但参数列表不同(参数类型与数量)的方法之间构成重载。
构造器也可以重载。
② 重写:
子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。
构造器不可以重写。 -
重载与重写的具体规则:
-
从编译和运行的角度看:
① 重载:
不表现为多态性,编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为 “早绑定”或“静态绑定”
② 重写:
表现为多态性(Person p = new Man;p.eat();),只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为==“晚绑定”或“动态绑定”==
-
- 例题:对5.1节中的例题的Cylinder类进行修改,重写Circle类中的findArea方法,使得其返回圆柱体的表面积。
Cylinder类:package com.atguigu.exer; public class Cylinder extends Circle{ private double length; public Cylinder() { length = 1.0; } public double getLength() { return length; } public void setLength(double length) { this.length = length; } //返回圆柱体的体积 public double findVolume() { return Math.PI * getRadius() * getRadius() * getLength(); } //返回圆柱体的表面积 @Override public double findArea() { return Math.PI * getRadius() * getRadius() * 2 + 2 * Math.PI * getRadius() * getLength(); } }
5.3 四种访问权限修饰符
5.4 关键字:super
- super理解为:父类的
- super可以用来调用:属性、方法、构造器
- super的使用:调用属性和方法
- 可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用
父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super." - 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的
使用"super.属性"的方式,表明调用的是父类中声明的属性。 - 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
使用"super.方法"的方式,表明调用的是父类中被重写的方法。
- 可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用
Cylinder类中求圆柱体的体积时,体积=底面积*高,此时要用的findArea为父类Circle中定义的求圆的面积而不是子类Cylinder类重写的求表面积,因此使用super关键字
package com.atguigu.exer1;
public class Cylinder extends Circle{
private double length;//高
public Cylinder(){
length = 1.0;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
//返回圆柱的体积
public double findVolume(){
// return Math.PI * getRadius() * getRadius() * getLength();
return super.findArea() * getLength();
}
@Override
public double findArea() {//返回圆柱的表面积
return Math.PI * getRadius() * getRadius() * 2 +
2 * Math.PI * getRadius() * getLength();
}
}
- super调用构造器
- 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- "super(形参列表)"的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,且必须二选一,不能同时出现
- 在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
(如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)”,另一个则为"super(形参列表)")
父类Circle:
package com.atguigu.exer;
public class Circle {
private double radius;//半径
// public Circle() {
// radius = 1.0;
// }
public Circle(double radius){
this.radius = radius;
}
public void setRadiu(double radius) {
this.radius = radius;
}
public double getRadius() {
return this.radius;
}
//返回圆的面积
public double findArea() {
return Math.PI * radius * radius;
}
}
子类Cylinder:
package com.atguigu.exer1;
public class Cylinder extends Circle{
private double length;//高
public Cylinder(){
length = 1.0;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
//返回圆柱的体积
public double findVolume(){
// return Math.PI * getRadius() * getRadius() * getLength();
return super.findArea() * getLength();
}
@Override
public double findArea() {//返回圆柱的表面积
return Math.PI * getRadius() * getRadius() * 2 +
2 * Math.PI * getRadius() * getLength();
}
}
当Circle类中的空惨构造器注释时,子类Cylinder的构造器会报错。
原因分析:
在父类Circle中有非空参的构造器,因此系统不再提供空参的构造器,在子类Cylinder的构造器中没有显式的使用的构造器,因此默认使用super(),但是父类中没有空参的,因此报错。
当子类Cylinder的空参构造器注释,系统在进行类的声明时仍然会有一个默认的空参构造器,其中仍然会默认的调用super(),因此也会报错。
5.5 子类对象实例化过程
- 思考:
- 为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
两者在使用时都必须在构造器的第一行 - 为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
- 为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
- 子类对象实例化的全过程:
-
从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。 -
从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…
直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
-
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
- 例题:
package com.atguigu.exer2;
public class Account {
private int id;
protected double balance;
private double annualInterestRate;
// public Account() {
// super();
// }
public Account(int id, double balance, double annualInterestRate) {
super();
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public double getAnnualInterestRate() {
return annualInterestRate;
}
public void setAnnualInterestRate(double annualInterestRate) {
this.annualInterestRate = annualInterestRate;
}
public double getMonthlyInterest() {
return annualInterestRate / 12;
}
public void withdraw (double amount) {
if(amount > balance) {
System.out.println("余额不足!");
}else {
balance -= amount;
}
}
public void deposit (double amount) {
if(amount > 0) {
balance += amount;
}
}
}
package com.atguigu.exer2;
public class CheckAccount extends Account{
private double overdraft;
public CheckAccount(int id, double balance, double annualInterestRate, double overdraft) {
super(id, balance, annualInterestRate);
this.overdraft = overdraft;
}
public double getOverdraft() {
return overdraft;
}
public void setOverdraft(double overdraft) {
this.overdraft = overdraft;
}
@Override
public void withdraw(double amount) {
if(amount <= balance) {
balance -= amount;
}else {
double money = amount - balance;
if(overdraft >= money) {
balance = 0;
overdraft -= money;
}else {
System.out.println("超过可透支额的限额!");
}
}
}
}
package com.atguigu.exer2;
public class CheckAccountTest {
public static void main(String[] args) {
CheckAccount checkAccount = new CheckAccount(1122, 20000, 0.045, 5000);
checkAccount.withdraw(5000);
System.out.println("您的账户余额为:" + checkAccount.balance);
System.out.println("您的可透支额:" + checkAccount.getOverdraft());
System.out.println();
checkAccount.withdraw(18000);
System.out.println("您的账户余额为:" + checkAccount.balance);
System.out.println("您的可透支额:" + checkAccount.getOverdraft());
System.out.println();
checkAccount.withdraw(3000);
System.out.println("您的账户余额为:" + checkAccount.balance);
System.out.println("您的可透支额:" + checkAccount.getOverdraft());
}
}
5.6 面向对象特征之三:多态性
5.6.1 多态性及其理解
-
多态性:
- 理解多态性:可以理解为一个事物的多种形态。
- 何为多态性:
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用) - 多态的使用:虚拟方法调用
- 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。即,方法的调用是在运行时确定的——动态绑定
总结:编译,看左边;运行,看右边。 - 多态性的使用前提: ① 类的继承关系 ② 方法的重写
- 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
定义了一个父类Preson以及它的两个子类Man和Woman:
package com.atguigu.java4; public class Person { String name; int age; int id = 1001; public void eat(){ System.out.println("人:吃饭"); } public void walk(){ System.out.println("人:走路"); } }
package com.atguigu.java4; public class Man extends Person{ boolean isSmoking; int id = 1002; public void earnMoney(){ System.out.println("男人负责挣钱养家"); } public void eat(){ System.out.println("男人多吃肉,长肌肉"); } public void walk(){ System.out.println("男人霸气的走路"); } }
package com.atguigu.java4; public class Woman extends Person{ boolean isBeauty; public void goShopping(){ System.out.println("女人喜欢购物"); } public void eat(){ System.out.println("女人少吃,为了减肥"); } public void walk(){ System.out.println("女人窈窕的走路"); } }
多态性的说明:
package com.atguigu.java4; public class PersonTest { public static void main(String[] args) { Person p1 = new Person(); p1.eat(); Man man = new Man(); man.eat(); man.age = 25; man.earnMoney(); //************************************************* System.out.println("*******************"); //对象的多态性:父类的引用指向子类的对象 Person p2 = new Man(); // Person p3 = new Woman(); //多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用 p2.eat(); p2.walk(); // p2.earnMoney();//不可以调用父类没有声明的方法 System.out.println(p2.id);//1001(与属性无关,属性仍然是父类中的) } }
-
多态性的理解:
- 减少冗余代码(重载方法)
- 增加方法的通用性
- 规范代码操作
- 抽象类、接口的使用肯定体现了多态性(抽象类、接口不能实例化,因此在使用时只能创建其子类的对象)
package com.atguigu.java4;
import java.sql.Connection;
//多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal){//Animal animal = new Dog();
animal.eat();
animal.shout();
if(animal instanceof Dog){
Dog d = (Dog)animal;
d.watchDoor();
}
}
//减少重载方法的编写
// public void func(Dog dog){
// dog.eat();
// dog.shout();
// }
// public void func(Cat cat){
// cat.eat();
// cat.shout();
// }
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
//举例二:
class Order{
//增加方法的通用性:Object中可能定义很多不同类型的对象都需要使用的通用方法
public void method(Object obj){
}
}
//举例三:
class Driver{
//规范操作:不同的数据库类型具体代码不同,但要实现的功能相同,因此使用java.sql.Connection创建连接,写好各个方法的功能,具体实现时调用各个类型数据库自己实现功能的代码
public void doData(Connection conn){//conn = new MySQlConnection(); / conn = new OracleConnection();
//规范的步骤去操作数据
// conn.method1();
// conn.method2();
// conn.method3();
}
}
- 多态小结
- 多态作用:
提高了代码的通用性,常称作接口重用 - 前提:
① 需要存在继承或者实现关系
② 有方法的重写 - 成员方法:
编译时:要查看引用变量所声明的类中是否有所调用的方法。
运行时:调用实际new的对象所属的类中的重写方法。 - 成员变量:
不具备多态性,只看引用变量所声明的类
- 多态作用:
5.6.2 关键字:instanceof
- 问题引入:
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何调用子类的属性和方法?——向下转型:使用强制类型转换符
但是向下转型存在一定风险,可能会导致运行错误(一个父类可能包含多个子类,子类之间进行向下转型编译时不报错,但运行时会报错),因此在转换前应该对变量的具体类型进行判断 -> instanceof关键字 - instanceof关键字的使用
① a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
② 如果 a instanceof A返回true,则 a instanceof B也返回true。 其中,类B是类A的父类
③ 要求a所属的类与类A必须是子父类的关系,否则编译错误。
package com.atguigu.java;
import java.util.Date;
public class PersonTest {
public static void main(String[] args) {
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
//不能调用子类所特有的方法、属性:编译时,p2是Person类型。
// p2.earnMoney();//报错
// p2.isSmoking = true;//报错
//如何才能调用子类特有的属性和方法?
//向下转型:使用强制类型转换符。
Man m1 = (Man)p2;
m1.earnMoney();
m1.isSmoking = true;
//使用强转时,可能出现ClassCastException的异常。Man类型不可以转换为Woman类型
// Woman w1 = (Woman)p2;
// w1.goShopping();
/*
* instanceof关键字的使用:a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
*
* 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
*
* 如果 a instanceof A返回true,则 a instanceof B也返回true.
* 其中,类B是类A的父类。
*/
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("******Woman******");
}
if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.earnMoney();
System.out.println("******Man******");
}
if(p2 instanceof Person){
System.out.println("******Person******");
}
if(p2 instanceof Object){
System.out.println("******Object******");
}
// if(p2 instanceof String){
//
// }
//练习:
//问题一:编译时通过,运行时不通过
//举例一:
// Person p3 = new Woman();
// Man m3 = (Man)p3;
//举例二:
// Person p4 = new Person();
// Man m4 = (Man)p4;
//问题二:编译通过,运行时也通过
Object obj = new Woman();
Person p = (Person)obj;
//问题三:编译不通过
// Man m5 = new Woman();
// String str = new Date();
//编译过,运行不通过
// Object o = new Date();
// String str1 = (String)o;
}
}
- 练习:
-
判断JAVA文件的输出结果:
package com.atguigu.exer; class Base { int count = 10; public void display() { System.out.println(this.count); } } class Sub extends Base { int count = 20; public void display() { System.out.println(this.count); } } public class FieldMethodTest { public static void main(String[] args) { Sub s = new Sub(); System.out.println(s.count);//20 s.display();//20 Base b = s;//多态性 //==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同 System.out.println(b == s);//true System.out.println(b.count);//10 b.display();//20 } }
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
对于实例变量(属性)则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边 -
编写InatanceTest类
package com.atguigu.exer; public class InstanceTest { public static void main(String[] args) { InstanceTest test = new InstanceTest(); test.method(new Student()); } public void method(Person e){ //(1):虚拟方法调用 String info = e.getInfo(); System.out.println(info); //(2):instanceof判断 //方式一 // if(e instanceof Graduate){ // System.out.println("a graduated student"); // System.out.println("a student"); // System.out.println("a person"); // }else if(e instanceof Student){ // System.out.println("a student"); // System.out.println("a person"); // }else{ // System.out.println("a person"); // } //方式二 if(e instanceof Graduate){ System.out.println("a graduated student"); } if(e instanceof Student){ System.out.println("a student"); } if(e instanceof Person){ System.out.println("a person"); } } } class Person { protected String name = "person"; protected int age = 50; public String getInfo() { return "Name: " + name + "\n" + "age: " + age; } } class Student extends Person { protected String school = "pku"; public String getInfo() { return "Name: " + name + "\nage: " + age + "\nschool: " + school; } } class Graduate extends Student { public String major = "IT"; public String getInfo() { return "Name: " + name + "\nage: " + age + "\nschool: " + school + "\nmajor:" + major; } }
-
定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆形,MyRectangle代表矩形。定义一个测试类GeometricTest,编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
父类:package com.atguiau.exer1; public class GeometricObject { protected String color; protected double weight; protected GeometricObject(String color, double weight) { super(); this.color = color; this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public double findArea() { return 0.0; } }
子类1:
package com.atguiau.exer1; public class Circle extends GeometricObject { private double radius; protected Circle(double radius, String color, double weight) { super(color, weight); this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } @Override public double findArea() { return Math.PI * radius * radius; } }
子类2:
package com.atguiau.exer1; public class MyRectangle extends GeometricObject { private double width; private double height; public MyRectangle(String color, double weight, double width, double height) { super(color, weight); this.width = width; this.height = height; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } @Override public double findArea() { return width * height; } }
测试类:
package com.atguiau.exer1; public class GeometricTest { public static void main(String[] args) { GeometricTest geomTest = new GeometricTest(); Circle c1 = new Circle(1, "blue", 2); Circle c2 = new Circle(1, "white", 3); double area1 = geomTest.displayGeometricObject(c1); System.out.println("面积为" + area1); double area2 = geomTest.displayGeometricObject(c2); System.out.println("面积为" + area2); double area3 = geomTest.displayGeometricObject(new MyRectangle("yellow", 2, 1, 2)); System.out.println("面积为" + area3); boolean isEquals = geomTest.equalsArae(c1, c2); System.out.println("面积是否相等:" + isEquals); } public boolean equalsArae(GeometricObject o1, GeometricObject o2) { return o1.findArea() == o2.findArea(); } public double displayGeometricObject(GeometricObject o) { return o.findArea(); } }
-
-
面试题:多态是编译时行为还是运行时行为?如何证明?
运行时行为。
证明:package com.atguigu.java5; 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(); } }
若此段代码不运行起来,我们永远不知道输出结果是什么。
-
笔试题:判断以下程序的输出结果
package com.atguigu.exer; //考查多态的笔试题目: public class InterviewTest1 { public static void main(String[] args) { Base1 base = new Sub1();//多态性,父类只能调用父类中定义过的子类重写的方法 base.add(1, 2, 3);//sub_1 Sub1 s = (Sub1)base; s.add(1,2,3);//sub_2 } } class Base1 { public void add(int a, int... arr) { System.out.println("base1"); } } class Sub1 extends Base1 { public void add(int a, int[] arr) { System.out.println("sub_1"); } public void add(int a, int b, int c) { System.out.println("sub_2"); } }
①
int... arr
与int[] arr
在java看来是相同的,因此子类Sub1中定义的add(int a, int[] arr)是对父类Base1中定义的add(int a, int… arr)的重写(同名同参数的方法)。在调用base.add(1, 2, 3)时,父类的引用指向子类的对象,执行的应为子类的方法(sub_1)。add(int a, int b, int c)
不能与前两个看做同类型的,因此不会改变base.add(1, 2, 3)的执行结果。
② 但是,将base强制转换为Sub1类型后,在执行时,只在Sub1类内部执行,此时,会优先执行参数数量精确匹配的方法(sub_2)。
5.7 Object类的使用
- java.lang.Object类
- Object类是所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
- Object类中的功能(属性、方法)就具有通用性。
属性:无
方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()
wait() 、 notify()、notifyAll()
finalize()是在系统立即回收前通知对象调用的方法,System.gc();//强制性释放空间
,子类重写此方法,可在释放对象前进行某些操作。但一般不提倡程序员主动调用此方法。 - Object类只声明了一个空参的构造器
5.7.1 ==操作符与equals方法
- 面试题: == 和 equals() 区别
-
== 运算符的使用:
- == 是一个运算符
- 可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体 - 补充: == 符号使用时,必须保证符号左右两边的变量类型一致。
-
equals()方法的使用:
-
equals()是一个方法,而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的定义:
public boolean equals(Object obj) { return (this == obj); }
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
-
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
-
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。
重写的原则:比较两个对象的实体内容是否相同。package com.atguigu.java1; import java.util.Date; public class EqualsTest { public static void main(String[] args) { //基本数据类型 int i = 10; int j = 10; double d = 10.0; System.out.println(i == j);//true System.out.println(i == d);//true boolean b = true; // System.out.println(i == b);//编译错误 char c = 10; System.out.println(i == c);//true char c1 = 'A'; char c2 = 65; System.out.println(c1 == c2);//true //引用类型: Customer cust1 = new Customer("Tom",21); Customer cust2 = new Customer("Tom",21); System.out.println(cust1 == cust2);//false //new了两个对象,地址值肯定不同 String str1 = new String("atguigu"); String str2 = new String("atguigu"); System.out.println(str1 == str2);//false//不是想要的结果,需要重写 System.out.println("****************************"); System.out.println(cust1.equals(cust2));//false--->true System.out.println(str1.equals(str2));//true Date date1 = new Date(32432525324L); Date date2 = new Date(32432525324L); System.out.println(date1.equals(date2));//true } }
-
- Customer类中equals()方法的重写:
重写equals()方法的原则:
实际开发中:直接自动生成(Source -> Generate hasCode() and equals())package com.atguigu.java1; public class Customer { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Customer() { super(); } public Customer(String name, int age) { super(); this.name = name; this.age = age; } //重写的原则:比较两个对象的实体内容(即:name和age)是否相同 //手动实现equals()的重写 @Override public boolean equals(Object obj) { System.out.println("Customer equals()...."); //比较地址值是否相等 if (this == obj) { return true; } //判断类型是否一致 if(obj instanceof Customer){ Customer cust = (Customer)obj;//向下转型,为了使用子类的方法 //比较两个对象的每个属性是否都相同 //age是基本数据类型,使用==; name是引用数据类型,用equals return this.age == cust.age && this.name.equals(cust.name); } return false; } }
package com.atguigu.java1; public class Customer { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Customer() { super(); } public Customer(String name, int age) { super(); this.name = name; this.age = age; } //自动生成的equals() @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Customer other = (Customer) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } }
- 例题
5.7.2 toString() 方法
- Object类中toString()的使用:
-
大多数情况下,当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
当对象为null时,打印输出此对象时显示null,但如果使用toString()方法,会出现NullPointerException——println内部有保护机制
-
Object类中toString()的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } ```
-
像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息
-
自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
package com.atguigu.java1; import java.util.Date; public class ToStringTest { public static void main(String[] args) { Customer cust1 = new Customer("Tom",21); System.out.println(cust1.toString());//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21] System.out.println(cust1);//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21] String str = new String("MM"); System.out.println(str);//MM Date date = new Date(4534534534543L); System.out.println(date.toString());//Mon Sep 11 08:55:34 GMT+08:00 2113 } }
-
- toString()方法的重写
package com.atguigu.java1; public class Customer { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Customer() { super(); } public Customer(String name, int age) { super(); this.name = name; this.age = age; } //自动生成的equals() @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Customer other = (Customer) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } //手动实现 // @Override // public String toString() { // return "Customer[name = " + name + ",age = " + age + "]"; // } //自动实现 @Override public String toString() { return "Customer [name=" + name + ", age=" + age + "]"; } }
- 例题
定义两个类,父类GeometricObject代表几何形状,子类Circle代表圆形。
写一个测试类,创建两个Circle对象,判断其颜色是否相等;利用equals方法判断其半径是否相等;利用toString()方法输出其半径。
父类GeometricObject:
子类Circle:package com.atguigu.exer3; public class GeometricObject { protected String color; protected double weight; public GeometricObject() { super(); this.color = "White"; this.weight = 1.0; } public GeometricObject(String color, double weight) { super(); this.color = color; this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } }
测试类 :package com.atguigu.exer3; public class Circle extends GeometricObject { private double radius; public Circle() { super(); this.radius = 1.0; } public double getRadius() { return radius; } public Circle(double radius) { super(); this.radius = radius; } public Circle(double radius, String color, double weight) { super(color, weight); this.radius = radius; } public void setRadius(double radius) { this.radius = radius; } //求圆的面积 public double findArea() { return Math.PI * radius * radius; } //重写equals方法,比较两个圆的半径是否相等,如相等,返回true。 @Override public boolean equals(Object obj) { if(this == obj){ return true; } if(obj instanceof Circle) { Circle cir = (Circle)obj; return this.radius == cir.radius; } return false; } @Override //重写toString方法,输出圆的半径。 public String toString() { return "Circle[radius = " + this.radius + "]"; } }
package com.atguigu.exer3; public class CircleTest { public static void main(String[] args) { Circle c1 = new Circle(3.3); Circle c2 = new Circle(3.3, "White", 2.0); //color是字符串,需要用equals判断是否相等 System.out.println("颜色是否相等:" + c1.getColor().equals(c2.color)); System.out.println("半径是否相等:" + c1.equals(c2)); System.out.println(c1);//Circle[radius = 3.3] System.out.println(c1.toString());//Circle[radius = 3.3] } }
5.8 包装类的使用
5.8.1 单元测试方法
- 步骤:
- 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
(实际开发中只需在需要创建注解@Test后在进行导入即可) - 创建Java类,进行单元测试。
此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器 - 此类中声明单元测试方法。
此时的单元测试方法要求:方法的权限是public,没有返回值,没有形参 - 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
说明:
① 如果执行结果没有任何异常:绿条
② 如果执行结果出现异常:红条
package com.atguigu.java2; import java.util.Date; import org.junit.Test; public class JUnitTest { 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()); } }
- 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
5.8.2 包装类
- 包装类(Wrapper)的使用:
- 为什么要有包装类?
java针对八种基本数据类型提供了相应的引用类型—包装类(封装类),使得基本数据类型的变量具有类的特征,就可以调用类中的方法,Java才是真正的面向对象
- 掌握的:基本数据类型、包装类、String三者之间的相互转换
记住红色部分的方法即可。
简易版:
基本数据类型 <----------> 包装类:JDK 5.0新特性:自动装箱与自动拆箱
基本数据类型、包装类 ------> String:调用String重载的ValueOf(Xxx xxx)
String ------> 基本数据类型、包装类:调用包装类的parseXxx(String s)package com.atguigu.java2; import org.junit.Test; public class WrapperTest { //String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s) @Test public void test5(){ String str1 = "123"; //错误的情况: // int num1 = (int)str1; // Integer in1 = (Integer)str1; //可能会报NumberFormatException(String str1 = "123a") int num2 = Integer.parseInt(str1); System.out.println(num2 + 1); String str2 = "true1"; boolean b1 = Boolean.parseBoolean(str2); System.out.println(b1);//false } //基本数据类型、包装类--->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);//"12.3" System.out.println(str3);//"12.4" } /* * JDK 5.0 新特性:自动装箱 与自动拆箱 */ @Test public void test3(){ // int num1 = 10; // //基本数据类型-->包装类的对象 // method(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); } //包装类--->基本数据类型:调用包装类Xxx的xxxValue() // @Test // public void test2(){ // Integer in1 = new Integer(12); // // int i1 = in1.intValue(); // System.out.println(i1 + 1); // // // Float f1 = new Float(12.3); // 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"); // System.out.println(b2);//true // Boolean b3 = new Boolean("true123"); // System.out.println(b3);//false // Order order = new Order(); System.out.println(order.isMale);//false//boolean的默认值为false System.out.println(order.isFemale);//null//类的默认值为null } } class Order{ boolean isMale; Boolean isFemale; }
- 为什么要有包装类?
面试题:
-
如下两个题目输出结果相同吗?各是什么?
import org.junit.Test; public class InterviewTest { @Test public void test1() { Object o1 = true ? new Integer(1) : new Double(2.0); System.out.println(o1);// 1.0 } @Test public void test2() { Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o2);// 1 }
不同。
test1()中使用到了三元运算符,其在编译时会对表达式1和表达式2的类型进行统一,因此new Integer(1) 被自动类型提升为double类型的1.0。
test2()为if-else结构,不会进行自动类型提升。 -
判断输出结果。
import org.junit.Test; @Test public void test3() { Integer i = new Integer(1); Integer j = new Integer(1); System.out.println(i == j);//false//引用类型的变量使用==比较的是地址值 //Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[], //保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在 //-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率 Integer m = 1; Integer n = 1; System.out.println(m == n);//true Integer x = 128;//相当于new了一个Integer对象 Integer y = 128;//相当于new了一个Integer对象 System.out.println(x == y);//false } }
① 对于i == j的判断:
使用new即为在堆空间中创建新的对象,因此地址值不同。
② 对于m == n的判断:
Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在-128~127范围内时,可以直接使用数组中的元素,不用再去new了(目的:提高效率)。此时不论新创建几个对象,其指向的内存地址值是相同的。
③ 对于x == y的判断:
IntegerCache中定义的Integer[]数组范围为-128~127,而128在范围外,因此不会收到影响。在创建对象时不论是使用new还是自动装箱的方式都需要在堆空间中创建新的对象,因此地址值不同。
- 练习题:
利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。- 提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。
- 创建Vector对象:Vector v=new Vector();
- 给向量添加元素:v.addElement(Object obj); //obj必须是对象
- 取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。 - 计算向量的长度:v.size();
- 若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等
package com.atguigu.exer4; import java.util.Scanner; import java.util.Vector; /* * 利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。 提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。 而向量类java.util.Vector可以根据需要动态伸缩。 创建Vector对象:Vector v=new Vector(); 给向量添加元素:v.addElement(Object obj); //obj必须是对象 取出向量中的元素:Object obj=v.elementAt(0); 注意第一个元素的下标是0,返回值是Object类型的。 计算向量的长度:v.size(); 若与最高分相差10分内:A等;20分内:B等; 30分内:C等;其它:D等 * */ public class ScoreTest { public static void main(String[] args) { //1. 实例化Scanner,用于从键盘获取学生成绩 Scanner scan = new Scanner(System.in); //2. 创建Vector对象:Vector v=new Vector();相当于原来的数组 Vector v = new Vector(); //3.通过for(;;)或while(true)方式,给Vector中添加数组 int maxScore = 0; while(true) { System.out.println("请输入学生的成绩(以负数代表输入结束)"); int score = scan.nextInt(); //3.2 当输入是负数时,跳出循环 if(score < 0) { System.out.println("输入结束"); break; } if(score > 100) { System.out.println("输入的数据非法,请重新输入!"); continue; } //3.1 添加操作:v.addElement(Object obj) //jdk5.0之前: // Integer inScore = new Integer(score); // v.addElement(inScore);//多态 //jdk5.0之后: v.addElement(score);//自动装箱 + 多态 //4.获取学生成绩的最大值 if(maxScore < score) { maxScore = score; } } //5.遍历Vector,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级。 char level; for(int i = 0;i < v.size();i++){ Object obj = v.elementAt(i); //jdk 5.0之前: // Integer inScore = (Integer)obj; // int score = inScore.intValue(); //jdk 5.0之后: int score = (int)obj; if(maxScore - score <= 10){ level = 'A'; }else if(maxScore - score <= 20){ level = 'B'; }else if(maxScore - score <= 30){ level = 'C'; }else{ level = 'D'; } System.out.println("student-" + i + " score is " + score + ",level is " + level); } } }