6.1 OOP特征二:继承(Inheritance)
6.1.1 问题的引入
对于如下Person、Student两个类,其中有很多重复的代码。
public class Person{ String name; int age; public Person(){} public Person(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println("吃饭"); } public void sleep() { System.out.println("睡觉"); } } //======================================= public class Student{ String name; int age; public Student(){} public Student(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println("吃饭"); } public void sleep() { System.out.println("睡觉"); } public void study() { System.out.println("学习"); } }
对于Student类可以改写如下:
public class Student extends Person { public Student(){} public Student(String name, int age) { this.name = name; this.age = age; } public void study() { System.out.println("学习"); } public static void main(String[] args) { Student student = new Student(); student.eat(); } }
Java SE中的例子
6.1.2 继承的好处
- 减少了代码的冗余,提高了代码的复用性。
- 便于功能的扩展。
- 为之后多态性的使用,提供了前提。
6.1.3 继承性的体现
1、格式
class A extends B { }
- A:子类、派生类、subclass。
- B:父类、超类、基类、superclass。
- extends:延展、扩展。
2、体现
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性、方法。
- 特别地,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有结构。只是因为封装性的影响,使得子类不可以直接调用父类的结构。
3、拓展
- 子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。
4、关系
- 子类和父类的关系,不同于子集和集合的关系。
6.1.4 Java中关于继承的规定
- 一个类可以被多个子类继承。
- Java中类的单继承性:一个类只能有一个父类。
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
- 子类继承父类以后,就获取了直接父类以及所有间接父类声明的属性和方法。
6.1.5 Object
- 如果没有显式的声明一个类的父类的话,则此类继承java.lang.Object类。
- 所有的Java类(除java.lang.Object类之外),都直接或间接继承java.lang.Object类。
- 所有的Java类都具有java.lang.Object类声明的功能。
练习
1、MandKind、Kids
public class MandKind { private int sex; private int salary; public MandKind() { } public MandKind(int sex, int salary) { this.sex = sex; this.salary = salary; } public void manOrWoman() { if (sex == 1) { System.out.println("man"); } else if (sex == 0) { System.out.println("woman"); } } public void employeed() { if (salary == 0) { System.out.println("no job"); } else { System.out.println("job"); } // String jobInfo = (salary == 0) ? "no job" : "job"; // System.out.println(jobInfo); } public void setSex(int sex) { this.sex = sex; } public int getSex() { return sex; } public void setSalary(int salary) { this.salary = salary; } public int getSalary() { return salary; } } //=================== public class Kids extends MandKind { private int yearsOld; public Kids() { } public Kids(int yearsOld) { this.yearsOld = yearsOld; } public void setYearsOld(int yearsOld) { this.yearsOld = yearsOld; } public int getYearsOld() { return yearsOld; } public void printAge() { System.out.println("I am " + yearsOld + " years old."); } public static void main(String[] args) { Kids someKid = new Kids(12); someKid.printAge(); someKid.setSalary(0); someKid.setSex(1); someKid.employeed(); someKid.manOrWoman(); } }
2、Circle、Cylinder
public class Circle { private double radius; public Circle() { radius = 1.0; } public void setRadius(double radius) { this.radius = radius; } public double getRadius() { return radius; } public double findArea() { return Math.PI * radius * radius; } } //======================= public class Cylinder extends Circle { private double length; // 高 public Cylinder() { length = 1.0; } public void setLength(double length) { this.length = length; } public double getLength() { return length; } public double findVolume() { // return Math.PI * getRadius() * getRadius() * getLength(); return findArea() * getLength(); } public static void main(String[] args) { Cylinder cylinder = new Cylinder(); cylinder.setRadius(2.1); cylinder.setLength(3.4); double volume = cylinder.findVolume(); System.out.println("圆柱的体积为:" + volume); double area = cylinder.findArea(); System.out.println("底面圆的面积:" + area); } }
6.2 方法的重写(Override/Overwrite)
1、定义
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
public class Person { String name; int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println("吃饭"); } public void walk(int distance) { System.out.println("走路,走的距离是:" + distance + "公里"); } } //============================ public class Student extends Person { String major; public Student() { } public Student(String major) { this.major = major; } public void study() { System.out.println("学习,专业是:" + major); } @Override public void eat() { System.out.println("学生应该多吃有营养的食物"); } public static void main(String[] args) { Student student = new Student("计算机科学与技术"); student.eat(); // 学生应该多吃有营养的食物 student.walk(10); student.study(); } }
2、要求
方法的声明:
权限修饰符 返回值类型 方法名(参数列表) throws 异常的类型 { 方法体 }
子类中的方法叫重写的方法,父类中的叫被重写的方法。
要求:
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
public class Person { @Override public double info() { return 1.1; } } //===== public class Student extends Person { // The return type is incompatible with Person.info() // @Override // public int info() { // return 1; // } }
子类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限。
public class Person { public void eat() { System.out.println("吃饭"); } } //===== public class Student extends Person { // 报错:Cannot reduce the visibility of the inherited method from Person // @Override // private void eat() { // System.out.println("学生应该多吃有营养的食物"); // } }
- 子类不能重写父类中声明为private权限的方法。
public class Person { private void eat() { System.out.println("吃饭"); } } //===== public class Student extends Person { // 报错:The method eat() of type Student must override or implement a supertype method // @Override // public void eat() { // System.out.println("学生应该多吃有营养的食物"); // } }
- 子类不能重写父类中声明为private权限的方法。
子类方法抛出的异常不能大于父类被重写方法的异常。
3、注意
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的静态方法。
4、应用
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写的方法。
5、面试题:区分方法的重载与重写
二者的定义细节:略
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以,对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”。
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
6.3 四种访问权限修饰符
- 4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类。
- 具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类。
- 修饰类的话,只能用:缺省、public。
6.4 关键字:super
super:父类的……。
super可以用来调用:属性、方法、构造器。
public class Person { String name; int age; int id = 1001; // 身份证号 public Person() { System.out.println("我无处不在!"); } public Person(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println("吃饭"); } public void walk(int distance) { System.out.println("走路,走的距离是:" + distance + "公里"); } } //====================== public class Student extends Person { String major; int id = 1002; // 学号 public Student(){} public Student(String major) { // super(); 默认 this.major = major; } public Student(String name, int age, String major) { super(name, age); this.major = major; } @Override public void eat() { System.out.println("学生,多吃有营养的食物"); } public void study() { System.out.println("学生,学习"); eat(); this.eat(); super.eat(); } public void show() { System.out.println("name = " + this.name + ", age = " + super.age); System.out.println("id = " + id); System.out.println("id = " + this.id); System.out.println("id = " + super.id); } public static void main(String[] args) { Student student = new Student(); student.show(); student.study(); Student student2 = new Student("Tom", 21, "IT"); student2.show(); } }
1、super的使用:调用属性和方法
- 我们可以在子类的方法或构造器中,通过“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略。
- 特殊情况:当子类和父类中定义了同名的属性时,我们要向在子类中调用父类中声明的属性,则必须显式的使用“super.属性”的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用“super.方法”的方式,表明调用的是父类中声明的被重写的方法。
2、super的使用:调用构造器
- 我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器。
- “super(形参列表)”的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现。
- 在构造器的首行,没有显式的声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中无参的构造器。
- 如果父类没有无参构造器,并且在子类中没有显示调用“super(形参列表)”,可能会报错。
- 在类的构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器。
6.5 子类对象实例化过程
- 从结果上来看(继承性):
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上来看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器……直到调用了java.lang.Object类中无参构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 明确:创建子类对象时,虽然调用了父类的构造器,但是至始至终就创建过一个对象,即为new的子类对象。
练习
public class Account { private int id; // 账号 private double balance; // 余额 private double annualInterestRate; // 年利率 public Account() { } public Account(int id, double balance, double annualInterestRate) { this.id = id; this.balance = balance; this.annualInterestRate = annualInterestRate; } public void setId(int id) { this.id = id; } public int getId() { return id; } public void setBalance(double balance) { this.balance = balance; } public double getBalance() { return balance; } public void setAnnualInterestRate(double annualInterestRate) { this.annualInterestRate = annualInterestRate; } public double getAnnualInterestRate() { return annualInterestRate; } /** * 返回月利率 */ public double getMonthlyInterest() { return annualInterestRate / 12; } /** * 取钱 * * @param amount */ public void withdraw(double amount) { if (balance >= amount) { balance -= amount; } else { System.out.println("余额不足"); } } /** * 存钱 */ public void deposit(double amount) { if (amount > 0) { balance += amount; } } public static void main(String[] args) { Account account = new Account(1122, 20000, 0.045); account.withdraw(30000); System.out.println("您的账户余额为:" + account.getBalance()); account.deposit(3000); System.out.println("您的账户余额为:" + account.getBalance()); System.out.println("月利率为:" + (account.getMonthlyInterest() * 100) + "%"); } } //===================== 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 void setOverdraft(double overdraft) { this.overdraft = overdraft; } public double getOverdraft() { return overdraft; } @Override public void withdraw(double amount) { if (getBalance() >= amount) { // 余额足够 // 方式一:setBalance(getBalance() - amount); // 方式二: super.withdraw(amount); } else if (overdraft >= amount - getBalance()) { // 透支额度+余额足够消费 overdraft -= (amount - getBalance()); super.withdraw(getBalance()); // 或 setBalance(0); } else { System.out.println("超过可透支限额!"); } } public static void main(String[] args) { CheckAccount checkAccount = new CheckAccount(1122, 20000, 0.0045, 5000); checkAccount.withdraw(5000); System.out.println("账户余额:" + checkAccount.getBalance() + ",可透支额度:" + checkAccount.getOverdraft()); checkAccount.withdraw(18000); System.out.println("账户余额:" + checkAccount.getBalance() + ",可透支额度:" + checkAccount.getOverdraft()); checkAccount.withdraw(3000); System.out.println("账户余额:" + checkAccount.getBalance() + ",可透支额度:" + checkAccount.getOverdraft()); } }
6.6 OOP特征三:多态
public class Person { String name; int age; int id = 1001; public void eat() { System.out.println("人,吃饭"); } public void walk() { System.out.println("人,走路"); } } //===================== public class Man extends Person { boolean isSmoking; int id = 1002; public void earnMoney() { System.out.println("男人,负责挣钱养家"); } @Override public void eat() { System.out.println("男人多吃肉,长肌肉"); } @Override public void walk() { System.out.println("男人霸气的走路"); } } //===================== public class Woman extends Person { boolean isBeauty; public void goShopping() { System.out.println("女人喜欢购物"); } @Override public void eat() { System.out.println("女人少吃,为了减肥"); } @Override public void walk() { System.out.println("女人窈窕的走路"); } } //======================= /** * 面向对象特征之三:多态性 */ 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(); // Man的方法 p2.walk(); // p2.earnMoney(); The method earnMoney() is undefined for the type Person p2.id; // 1001 父类的id } }
1、理解多态性
可以理解为一个事物的多种形态。
2、何为多态性
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)。
Person p2 = new Man();
3、多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写的父类的方法。
- 总结:编译:看左边;运行:看右边。
4、多态性的使用前提
- 类的继承关系
- 方法的重写
5、多态性的应用场景(为什么要有多态性?)
public class AnimalTest { public void func(Animal animal) { animal.eat(); animal.shout(); } // public void func(Dog dog) { // dog.eat(); // dog.shout(); // } // public void func(Cat cat) { // cat.eat(); // cat.shout(); // } public static void main(String[] args) { AnimalTest test = new AnimalTest(); test.func(new Dog()); test.func(new Cat()); } } class Animal { public void eat() { System.out.println("动物,进食"); } public void shout() { System.out.println("动物,叫"); } } class Dog extends Animal { @Override public void eat() { System.out.println("狗,吃骨头"); } @Override public void shout() { System.out.println("汪汪汪"); } } class Cat extends Animal { @Override public void eat() { System.out.println("猫,吃鱼"); } @Override public void shout() { System.out.println("喵喵喵"); } } // 举例二: class Order { public void method(Object obj) { } }
5、多态性不适用于属性
对象的多态性只适用于方法,不适用于属性(编译和运行都看左边)。
6、虚拟方法调用(Virtual Method Invocation)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
多态是运行时行为!
7、instanceof操作符
- 检验对象a是否为类A的对象,返回值为boolean型。
a instanceof A
- 使用情景:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
Person p = new Man(); if (p instanceof Woman) { Woman w = (Woman)p; // 使用 w.goShopping(); }
- 类B是类A的父类,如果a instanceof A返回true,则a instanceof B也返回true。
8、向下转型
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何才能调用子类特有的属性和方法?
Person p = new Man(); // p.earnMoney(); 不能调用子类特有的方法 Man m = (Man)p; // 向下转型 m.earnMoney(); // Woman w = (Woman)p; 类型转换异常
使用强转时,可能出现ClassCastException的异常。
在向下转型时,需要使用instanceof事先进行判断。
问题一:编译时通过,运行时不通过
Person p = new Woman(); Man m = (Man)p; // 报错:ClassCastException Person p2 = new Person(); Man m2 = (Man)p2; // 报错:ClassCastException
问题二:编译通过,运行也通过
Object obj = new Woman(); Person p = (Person)obj;
问题三:编译不通过
Man m = new Woman(); // Type mismatch,类型不匹配
9、多态练习题
例题1:
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 } } 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); } }
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。【编译看左边,运行看右边】
对于实力变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。【编译运行看左边】
例题2:
public class InstanceTest { public void method(Person e) { String info = e.getInfo(); System.out.println(info); 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"); } } public static void main(String[] args) { InstanceTest test = new InstanceTest(); test.method(new Student()); } } 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; } }
例题3:
public class InterviewTest { public static void main(String[] args) { Base base = new Sub(); base.add(1, 2, 3); // sub Sub s = (Sub)base; // s.add(1,2,3); The method add(int, int[]) in the type Sub is not applicable for the arguments (int, int, int) } } class Base { public void add(int a, int... arr) { System.out.println("base"); } } class Sub extends Base { // Varargs methods should only override or be overridden by other varargs methods unlike Sub.add(int, int[]) and Base.add(int, int...) public void add(int a, int[] arr) { System.out.println("sub"); } // public void add(int a, int b, int c) { // System.out.println("sub"); // } } //============================================================= public class InterviewTest { public static void main(String[] args) { Base base = new Sub(); base.add(1, 2, 3); // sub } } class Base { public void add(int a, int... arr) { System.out.println("base"); } } class Sub extends Base { // Varargs methods should only override or be overridden by other varargs methods unlike Sub.add(int, int[]) and Base.add(int, int...) public void add(int a, int[] arr) { System.out.println("sub"); } public void add(int a, int b, int c) { System.out.println("sub_2"); } } //=================================== public class InterviewTest { public static void main(String[] args) { Base base = new Sub(); base.add(1, 2, 3); // sub Sub s = (Sub)base; s.add(1,2,3); // sub_2 } } class Base { public void add(int a, int... arr) { System.out.println("base"); } } class Sub extends Base { // Varargs methods should only override or be overridden by other varargs methods unlike Sub.add(int, int[]) and Base.add(int, int...) public void add(int a, int[] arr) { System.out.println("sub"); } public void add(int a, int b, int c) { System.out.println("sub_2"); } }
小结
1、什么是多态性?什么是虚拟方法调用?
对象的多态性:父类的引用指向子类的对象。
Person p = new Man(); p.eat();
虚拟方法调用:调用方法时,编译时看左边,运行时看右边。
2、一个类可以有几个直接父类?一个父类可有多少个子类?子类能获取直接父类的父类中的结构吗?子类能否获取父类中private权限的属性或方法?
①只有一个;②多个;③可以;④可以(私有属性或方法,放到公共方法中)。
3、方法的重写(override/overwrite)的具体规则有哪些。
- 子类重写的方法与父类被重写的方法的方法名、形参列表相同。
- 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。
- 子类重写的方法的返回值的类型不大于父类被重写的方法的返回值。
- void ←→ void
- 基本数据类型(如double) ←→ 基本数据类型(必须也是double)
- 引用数据类型(比如A) ←→ 引用数据类型(A或A的子类)
- 子类重写的方法抛出的异常的类型不大于父类被重写的方法的抛出的异常。
- 重写的方法都是非static的。
4、super调用构造器,有哪些具体的注意点。
- this(形参列表):本类重载的其他的构造器。
- super(形参列表):调用父类中指定的构造器。
- 构造器中,如果没有显式调用“this(形参列表)”和“super(形参列表)”,则默认调用无参的“super()”。
6.7 Object类
- Object类是所有Java类的根父类。
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object。
- Object类中的功能(属性、方法)具有通用性。
- Object类没有属性,只声明了一个无参构造器(Object())。
- 方法:clone()、equals(Object obj)、finalize()、getClass()、hashCode()、notify()、notifyAll()、wait()、wait(long timeout)、wait(long timeout, int nanos)、toString()。
1、==
运算符。
操作数的类型必须匹配,否则编译不通过。
可以使用在基本数据类型变量和引用数据类型变量中。
如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
示例:
public class EqualsTest { public static void main(String[] args) { // 基本数据类型: int i = 10; int j = 10; System.out.println(i == j); // true double d = 10.0; System.out.println(i == d); // true boolean b = true; // System.out.println(i == b); The operator == is undefined for the argument // type(s) int, boolean char c = 10; System.out.println(i == c); // true char c1 = 'A'; char c2 = 65; System.out.println(c1 == c2); // true // 引用数据类型: Customer customer1 = new Customer("Tom", 21); Customer customer2 = new Customer("Tom", 21); System.out.println(customer1 == customer2); // false String str1 =new String("Xianhuii"); String str2 = new String("Xianhuii"); System.out.println(str1 == str2); // false } } class Customer { private String name; private int age; public Customer() { } public Customer(String name, int age) { this.name = name; this.age = age; } }
2、equals()
是一个方法,而非运算符。
只能适用于引用数据类型。
Object类中equals()方法的定义:
- Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
public boolean equals(Object obj) { return (this == obj); }
- Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容“是否相同。
String类中重写的equals():
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String aString = (String)anObject; if (coder() == aString.coder()) { return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value); } } return false; }
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的”实体内容“是否相同。那么,我们就需要对Object类中的equals()进行重写:
class Customer { private String name; private int age; public Customer() { } public Customer(String name, int age) { this.name = name; this.age = age; } // 重写的原则:比较两个对象的实体内容(即:name、age)是否相同 @Override public boolean equals(Object obj) { if(obj == null) { return false; } if(this == obj) { return true; } if(obj instanceof Customer) { Customer that = (Customer)obj; // 比较两个对象的每个属性是否相同 // if(this.age == that.age && this.name.equals(that.name)) { // return true; // } else { // return false; // } return this.age == that.age && this.name.equals(that.name); } return false; } }
示例:
public class EqualsTest { public static void main(String[] args) { // 引用数据类型: Customer customer1 = new Customer("Tom", 21); Customer customer2 = new Customer("Tom", 21); System.out.println(customer1 == customer2); // false String str1 = new String("Xianhuii"); String str2 = new String("Xianhuii"); System.out.println(str1 == str2); // false System.out.println(customer1.equals(customer2)); // 重写前:false;重写后:true System.out.println(str1.equals(str2)); // true,String已经重写了equals()方法 } } class Customer { private String name; private int age; public Customer() { } public Customer(String name, int age) { this.name = name; this.age = age; } // 重写的原则:比较两个对象的实体内容(即:name、age)是否相同 @Override public boolean equals(Object obj) { if(obj == null) { return false; } if (this == obj) { return true; } if (obj instanceof Customer) { Customer that = (Customer) obj; // 比较两个对象的每个属性是否相同 // if(this.age == that.age && this.name.equals(that.name)) { // return true; // } else { // return false; // } return this.age == that.age && this.name.equals(that.name); } return false; } }
3、toString()
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()。
Object类中toString()的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回“实体内容”信息。
自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”:
class Customer { private String name; private int age; public Customer() { } public Customer(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Customer[name = " + name + ", age = " + age + "]"; } }
示例:
public class ToStringTest { public static void main(String[] args) { Customer customer = new Customer("Tom", 21); System.out.println(customer); // 重写toString()前:Customer@5caf905d;重写后:Customer[name = Tom, age = 21] System.out.println(customer.toString()); // 重写toString()前:Customer@5caf905d;重写后:Customer[name = Tom, age = 21] String str = new String("MM"); System.out.println(str); // MM,String重写过toString()方法 } } class Customer { private String name; private int age; public Customer() { } public Customer(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Customer[name = " + name + ", age = " + age + "]"; } }
中场休息:单元测试
Java中的JUnit单元测试
- 步骤:
- 选中当前工程 → 右键选择:build path → add libraries → JUnit → 下一步
- 创建Java类,进行单元测试。
- 此时的Java类要求:①此类是public的;②此类提供公共的无参构造器。
- 在此类中声明单元测试方法。
- 此时的单元测试方法:方法的权限是public,没有返回值,没有形参。
- 此单元测试方法上需要声明一个注解:@Test,并在单元测试类当中添加:import org.junit.Test;。
- 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 下完代码以后,左键双击单元测试方法名,右键:run as → JUnit Test。
- 说明:
- 如果执行结果没有任何异常:绿条。
- 如果执行结果出现异常:红条。
6.8 包装类(Wrapper)
- 针对八种基本数据类型定义相应的引用类型——包装类(封装类)。
- 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
1、包装类的使用
Java提供了8种基本数据类型对应的包装类,使得基本数据类型的白能量具有类的特征。
2、基本数据类型、包装类、String三者之间的相互转换
基本数据类型 ---> 包装类:调用包装类的构造器。
包装类 ---> 基本数据类型:调用包装类的xxxValue()。
JDK 5.0新特性:自动装箱、自动拆箱。
基本数据类型、包装类 ---> String类型:
- 连接运算:+ ""
- 调用String重载的valueOf()。
String类型 ---> 基本数据类型、包装类:调用包装类的parseXxx()。
示例:
public class WrapperTest { // 基本数据类型 ---> 包装类:调用包装类的构造器 public void test1() { int num1 = 10; 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.toString()); System.out.println(f2.toString()); Boolean b1 = new Boolean(true); Boolean b2 = new Boolean("TRue"); Boolean b3 = new Boolean("true123"); System.out.println(b3); // false } // 包装类 ---> 基本数据类型:调用包装类的xxxValue() 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); } // JDK 5.0新特性:自动装箱与自动拆箱 public void test3() { int num1 = 10; method(num1); // 自动装箱 int num2 = 10; Integer in1 = num2; boolean b1 = true; Boolean b2 = b1; // 自动拆箱 int num3 = in1; } public void method(Object obj) { System.out.println(obj); } // 基本数据类型、包装类 ---> String类型:调用String重载的valueOf()。 public void test4() { int num1 = 10; // 方式1:连接运算 String str1 = num1 + ""; // 方式2:String.valueOf() float f1 = 12.3f; String str2 = String.valueOf(f1); Double d1 = new Double(12.4); String str3 = String.valueOf(d1); } // String类型 ---> 基本数据类型、包装类:调用包装类的parseXxx()。 public void test5() { String str1 = "123"; int num1 = Integer.parseInt(str1); // 可能会报NumberFormatException String str2 = "true"; boolean b1 = Boolean.parseBoolean(str2); // true String str3 = "true1"; boolean b2 = Boolean.parseBoolean(str3); // false } }
3、面试题
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(02); // 1 } @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 } }
package com.xianhuii; import java.util.Scanner; import java.util.Vector; public class ScoreTest { public static void main(String[] args) { // 1、实例化Scanner,用于从键盘获取学生成绩 Scanner scanner = new Scanner(System.in); // 2、创建Vector对象:Vector v = new Vector(); Vector v = new Vector(); // 3、通过for(;;)或while(true)方式,给Vector中添加数组 int maxScore = 0; for (;;) { System.out.println("请输入学生成绩(以负数代表输入结束):"); int score = scanner.nextInt(); // 3.2、当输入是负数时,跳出循环 if (score < 0) { break; } if (score > 100) { System.out.println("输入的数据非法,请重新输入:"); continue; } // 3.1、添加操作:v.addElement(Object obj) v.add(score); // 4、获取学生成绩的最大值 if (maxScore < score) { maxScore = score; } } // 5、遍历Vector,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级。 char level; for (int i = 0; i < v.size(); i++) { Object obj = v.elementAt(i); int score = (Integer)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); } } }