第五章面向对象(中)
继承性
一、继承性的好处
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的拓展
- 为之后多态性的使用,提供了前提
//父类
public class Person {
//属性
String name;
int age;
int sex;
//方法
public void study(){
System.out.println("studying");
}
}
//子类
public class Teacher extends Person{
// String name;
// int age;
// int sex;
double high;
/* public void study(){
System.out.println("studying");
}*/
public void teach(){
System.out.println("正在上课");
}
}
public class TeacherTest {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.age = 25;
teacher.high = 180.0;
teacher.study();
teacher.teach();
}
}
二、继承的格式:class A extends B{ }
A:子类、派生类、subclass
B:父类、超类、基类、superclass
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。子类的功能更多一些。extends:延展、扩展
//父类中私有
private int age;
//子类中调用属性就需要使用get、set方法
System.out.println(getAge());
//调用方法同理
三、Java中关于继承性的规定
- 一个类可以被多个子类继承。
- Java中类的单继承性:一个类只能有一个父类。(接口是多继承的,注意区别)
- 子父类是相对的概念(多层继承)
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
四、
- 如果没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着所有的java类具有java.lang.Object类声明的功能
积累完成项目的过程中的注意点
常见的bug的调试
方式一:”硬“看,必要时,添加输出语句
方式二:Debug
在类前,方法前,方法内具体逻辑的实现步骤等添加必要的注释
偏向于:
- 类前、方法前、属性前:文档注释
- 逻辑步骤:单行、多行注释
继承练习:
public class ManKind {
private int sex;
private int salary;
public ManKind(int sex, int salary) {
this.sex = sex;
this.salary = salary;
}
public ManKind() {
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public void manOrWoman(){
if (sex == 1){
System.out.println("man");
}else if (sex == 0){
System.out.println("woman");
}
}
public void employeed(){
String isJob = (salary == 0) ? "no job" : "job";
System.out.println(isJob);
}
}
public class Kids extends ManKind{
private int yearsOld;
public Kids() {
}
public Kids(int yearsOld) {
this.yearsOld = yearsOld;
}
public int getYearsOld() {
return yearsOld;
}
public void setYearsOld(int yearsOld) {
this.yearsOld = yearsOld;
}
public void printAge(){
System.out.println(yearsOld);
}
}
public class KidsTest {
public static void main(String[] args) {
Kids kids = new Kids(12);
kids.getYearsOld();
kids.setSex(1);
kids.manOrWoman();
kids.setSalary(0);
kids.employeed();
}
}
Debug的使用
//测试1
public class DebugTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
System.out.println("i = " + i + "," + "j = " + j);
DebugTest debugTest = new DebugTest();
int max = debugTest.getMax(i,j);//10
System.out.println("max = " + max);
}
private int getMax(int k,int m){
int max = 0;
if (k < m){
max = k;
}else {
max = m;
}
return max;
}
}
//测试2
public class DebugTest1 {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5};
System.out.println(arr);
char[] arr1 = new char[]{'a','b','c'};
System.out.println(arr1);
}
}
打断点
常用的操作
操作 | 作用 |
---|---|
step into 跳入 | 进入当前行所调用的方法中 |
step over 跳过 | 执行完当前行的语句,进入下一行 |
step return 跳回 | 执行完当前行所在的方法,进入下一行 |
drop to frame | 回到当前行所在方法的第一行 |
resume 恢复 | 执行完当前行所在断点的所有代码,进入下一个断点,如果没有就结束 |
方法的重写(override / overwrite)
- 重写:子类继承父类以后,可以对父类中同名同参数的方法进行覆盖操作
- 应用:重写以后,当创建子类对象以后,通过子类对象调用父类中的**同名同参数**的方法时,实际执行的是子类重写父类的方法。
面试题:区分方法的重载与重写
-
重写的规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法。
①子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
②子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符(不小于,可以等于或大于) 特殊情况:子类不能重写父类中声明为private权限的方法。可以这样理解父类中private权限的方法的权限太小了,子类看不到,当然不能重写。
③返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
//父类
public Object kind(){
return null;
}
//子类
//这里是重写,String是Object的子类
public String kind(){
return null;
}
- 父类被重写的方法的返回值类型是基本数据类型(比如:double,),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。因为基本数据类型没有谁是谁的子类一说,只是有自动类型提升而已,int、double这些都是同一等级。
④方法体肯定不同。(要是相同重写方法干啥)
⑤子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理的时候讲)
开发中写重写方法时直接将要被重写的方法从父类复制到子类(这样即快又不容易出错)
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(此时不是重写)。
学了子类就可以测试protected权限修饰符了
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
super关键字
super关键字的使用
- super理解为:父类的
- super可以用来调用:属性、方法、构造器
- super的使用:调用属性和方法
①可以在子类的方法或构造器中,通过使用 “super.属性” 或"super.方法" 的方式,显式的调用父类中声明的属性或方法。但是通常情况下习惯于省略"super."
②特殊情况:当子类和父类中定义了同名的属性时,要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性" 的方式,表明调用的是父类中声明的属性。因为如果不使用"super.属性" 则会优先在本类中找。
对于属性来说不会存在像方法一样的覆盖之说,属性是不会被覆盖的
③特殊情况,当子类重写了父类中的方法以后,想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法" 的方式,表明调用的时父类中被重写的方法。
"this."结构意思是优先在本类中找,找不到再去父类中找;”super.“结构是直接去父类中找(而且是先去直接父类找,找不到再往上一级去间接父类中找)
- super调用构造器
①可以在子类的构造器中显式的使用"super(形参列表)" 的方式,调用父类中声明的指定的构造器
②"super(形参列表)" 的使用,必须声明在子类构造器的首行!
③在类的构造器中,针对于"this(形参列表)" 或 “super(形参列表)” 只能二选一,不能同时出现
④在构造器的首行,没有显式的声明"this(形参列表)" 或 “super(形参列表)” ,则默认调用的是父类中空参的构造器:super()
正是因为这个原因,如果在父类中没有定义空参的构造器,而直接只定义了带参的构造器,那么在子类中将可能会报错,因为子类中可能有的构造器默认使用了super(),而父类中没有定义空参构造器,肯定要报错。因此我们都习惯于每个类都先定义一个空参的构造器,来避免出错
⑤在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)" ,调用父类中的构造器
一个类的构造器可能有多个,构造器的首行不是this就是super,只能是二选一。n个构造器中至少有一个使用了super关键字,因为前面讲this关键字的时候提到了n个关键字最多有n - 1 个使用了this关键字
public class People {
String name;
int age;
int id = 1001;//身份证号
public People() {
}
public People(String name) {
this.name = name;
}
public People(String name, int age) {
this(name);
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public void walk(int distance){
System.out.println("走了" + distance + "公里");
sleep();
}
private void sleep(){
System.out.println("睡觉");
}
public Object kind(){
return null;
}
}
public class Student extends People{
String major;
int id = 1002;//学号
public Student() {
}
public Student(String major) {
this.major = major;
}
public Student(String name, int age, String major) {
super(name, age);
this.major = major;
}
//重写
public void eat(){
System.out.println("吃有营养的食物");
}
public void study(){
System.out.println("学习,专业是:" + major);
eat();//省略了this,其实是this.eat() 先在本类中找,发现了重写的方法
super.eat();//直接去父类中找,还可以再往上去间接父类中找
}
//这里不是重写,子类不能重写父类private权限的方法
public void sleep(){
System.out.println("白天睡觉");
}
//这里是重写,String是Object的子类
public String kind(){
return null;
}
public void show(){
System.out.println("name = " + name + "," + "age = " + age);
System.out.println("id = " + id);//1002,省略了this.id,会先在本类中找
System.out.println("id = " + super.id);//1001,直接去父类中找
}
}
public class PeopleTest {
public static void main(String[] args) {
Student student = new Student("计科");
student.eat();
student.walk(20);//输出的还是父类的sleep()方法
student.study();
student.show();
Student student1 = new Student("tom",21,"it");
student1.show();
}
}
public class Circle {
private double radius;//半径
public Circle() {
radius = 1.0;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea(){
return Math.PI * radius * radius;
}
}
public class Cylinder extends Circle{
private double length;//圆柱体的高
public Cylinder() {
// super();
length = 1;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double findVolume(){
//计算圆柱的体积
return super.findArea() * length;
}
//计算表面积
public double findArea(){
return super.findArea() * 2 + 2 * Math.PI * getRadius();
}
}
子类对象实例化的全过程
- 从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
- 从过程上来看:
当通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…,直到调用了java.lang.Obejct类中空参的构造器为止。正因为加载过所有的父类的结构,所以才看到内存中有父类中的结构,子类对象才可以考虑进行调用。
**明确:**虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new 的子类对象。
类的继承练习:
public class Account {
private int id;//账户
private double balance;//余额
private double annualInterestRate;//年利率
public Account(int id, double balance, double annualInterestRate) {
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;
}
//实际开发中银行是不会设置setBalance方法,只能通过whitdraw、deposit方法改变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 (balance <= amount){
System.out.println("余额不足!");
}
balance -= amount;
}
//存钱
public void deposit(double amount){
balance += amount;
}
}
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;
}
//重写withdraw方法
@Override
public void withdraw(double amount) {
if (getBalance() >= amount){
//方式一,但是实际开发中银行并不会提供setBalance方法
// setBalance(getBalance() - amount);
//方式二,调用父类withdraw方法
super.withdraw(amount);
//方式三讲balance属性设置为protected,不常用
}else if (overdraft >= (amount - getBalance())){
//下面的顺序错误,如果先设置余额,那么下一步获取到的余额将是0,所以应该把二者换一下顺序
//实际开发中写错只能排错
/* super.withdraw(getBalance());
overdraft -= (amount - getBalance());*/
overdraft -= (amount - getBalance());
super.withdraw(getBalance());
}else {
System.out.println("超过可透支限额!");
}
}
}
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.getBalance());
System.out.println("您的可透支额度:" + checkAccount.getOverdraft());
checkAccount.withdraw(18000);
System.out.println("您的账户余额:" + checkAccount.getBalance());
System.out.println("您的可透支额度:" + checkAccount.getOverdraft());
checkAccount.withdraw(3000);
System.out.println("您的账户余额:" + checkAccount.getBalance());
System.out.println("您的可透支额度:" + checkAccount.getOverdraft());
}
}
多态性
对象的多态性:父类的引用指向子类的对象
//Man.java, Woman.java是Person的子类
Person p2 = new Man();
Person p3 = new Woman();
//父类的引用指向子类的对象
多态的使用,当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---- 虚拟方法调用。
面向对象特征之三:多态性
- 理解多态性:可以理解为一个事物的多种形态
- 何为多态性
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
-
多态的使用:虚拟方法调用
有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。
总结:编译,看左边;运行,看右边
-
多态性使用前提:①类的继承关系(没有继承就没有多态)②方法的重写(使用多态都是子类中对父类方法进行了重写,实际执行的时候执行的是子类中重写的方法)
-
对象的多态性:只适用于方法,不适用于属性(属性的编译和运行都看左边)
多态性使用举例一:
public class Animal {
int id = 1001;
public void eat(){
System.out.println("吃饭");
}
public void shout(){
System.out.println("叫");
}
public void fnuc(Animal animal){
animal.eat();
animal.shout();
}
}
public class Dog extends Animal{
int id = 1002;
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void shout() {
System.out.println("汪!汪!汪!");
}
// //如果没有多态还需要写如下方法
// public void func(Dog dog){
// dog.eat();
// dog.shout();
// }
}
public class Cat extends Animal{
int id = 1003;
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void shout() {
System.out.println("喵~喵~喵~");
}
//如果没有多态还需要写如下方法
// public void func(Dog dog){
// dog.eat();
// dog.shout();
// }
}
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
animal.fnuc(new Dog());//执行Dog重写的方法
animal.fnuc(new Cat());//执行Cat重写的方法
Animal a1 = new Dog();
a1.eat();
a1.shout();//编译看左边,运行看右边
System.out.println(a1.id);//1001,看左边
Animal a2 = new Cat();
a2.eat();
a2.shout();//编译看左边,运行看右边
System.out.println(a2.id);//1001,看左边
}
}
虚拟方法调用(多态情况下):
子类中定义了与父类同名同参数的方法(即重写),在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
所以我们说多态是运行时行为。(虚拟方法调用在编译期是无法确定的)
证明多态是运行时行为:
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();
}
}
面试题:方法的重载与重写
- 二者的定义细节:略
- 从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要 调用的具体方法,这称为“晚绑定”或“动态绑定”。
重载不表现为多态性,重写表现为多态性
判断是否需要继承:A is B 是否为ture
比如student is person 为ture ,则可以让student继承person类
dog is person 为false,所以dog不能继承person
向下转型的使用
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
//Animal.java有如下属性
String name;
//Dog.java继承Animal类,且有如下特有属性
boolean isMale;
Animal a1 = new Dog();
a1.name = "大黄";//父类中的可以调用
//a1.isMale = true;//子类中的报错
如何才能调用子类特有的属性和方法?
向下转型:使用强制类型转换符。
Dog dog = (Dog) a1;
dog.isMale = true;
//使用强制类型转换符之后便可以使用子类特有的属性及方法
类比之前int、double等的强制类型转换理解
注意:使用强转时,可能出现ClassCastException异常
Cat cat = (Cat) a1;
//a1这个对象是new Dog(),转换成Cat救出报这个异常
为了避免出现这种异常情况,引入instanceof关键字
instanceof关键字的使用
instanceof关键字的使用:
a instanceof A :判断对象a是否是类A的实例,如果是,则返回true;如果不是,则返回false。
使用情境:为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,就不进行向下转型。
如果a instanceof A 返回true,则a instanceof B也返回true,其中,类B是类A的父类。
使用a instanceof A进行判断时,要求a所属的类与类A必须是子类和父类的关系,**否则编译错误。**编译就过不去更不用说运行了。
if (a1 instanceof Dog){
Dog dog = (Dog) a1;
System.out.println("转Dog成功");
}
if (a1 instanceof Cat){
Cat cat = (Cat) a1;
System.out.println("转Cat成功");
}
if (a1 instanceof Animal){
System.out.println("是Animal");
}
if (a1 instanceof Object){
System.out.println("是Object");
}
练习:
//练习:
//问题一:编译时通过,运行时不通过
//下面两个不正确
//举例一
Animal a3 = new Dog();
Cat cat = (Cat) a3;
//举例二
Animal a4 = new Animal();
Dog dog = (Dog) a4;
//问题二:编译通过,运行时也通过
//正确
Object a5 = new Dog();
Animal animal1 = (Animal) a5;
//问题三:编译不通过
Dog dog1 = new Cat();//二者没有关系,不是多态
向上转型即多态用的比较多。
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。编译看左边,运行看右边
- 对于实例变量(即属性)则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。总结:对于属性,编译运行都看左边。(实际开发中不要定义同名的属性)
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,方法编译看左边,运行看右边
}
}
对于引用数据类型," == " 比较的是地址值
练习:建立InstanceTest 类,在类中定义方法method(Person e);
在method中:(1) 根据e的类型调用相应类的getInfo()方法。
(2)根据e的类型执行:如果e为Person类的对象,输出: “a person”;
如果e为Student类的对象,输出: “a student”“a person ” ,如果e为Graduate类的对象,输出: “a graduated student”“a student”
“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;
}
}
public class InstanceTest{
public static void main(String[] args) {
InstanceTest instance = new InstanceTest();
instance.method(new Student());
}
public void method(Person e){
e.getInfo();
//方式一
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 graduated student");
System.out.println("a student");
}else if(e instanceof Person){
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");
}
}
}
练习:
public class GeometricObject {
protected String color;
protected double weight;
public GeometricObject(String color, double weight) {
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;
}
}
public class Circle extends GeometricObject{
private double radius;
public Circle(String color, double weight, double radius) {
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;
}
}
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;
}
}
public class GeometricTest {
public static void main(String[] args) {
GeometricTest geometricTest = new GeometricTest();
Circle c1 = new Circle("blue",1.0,3.0);
Circle c2 = new Circle("white",2.0,4.0);
boolean isEquals = geometricTest.equalsArea(c1,c2);
System.out.println("二者面积是否相等:" + isEquals);
double area = geometricTest.displayGeometricObject(c1);
System.out.println("面积是:" + area);
MyRectangle m1 = new MyRectangle("green",1.0,3.0,4.0);
System.out.println(geometricTest.displayGeometricObject(m1));
System.out.println(geometricTest.equalsArea(c1,m1));
}
public boolean equalsArea(GeometricObject o1,GeometricObject o2){
return o1.findArea() == o2.findArea();
}
public double displayGeometricObject(GeometricObject o){
return o.findArea();
}
}
考察多态的笔试题目:
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub();
base.add(1, 2, 3);//输出sub_1,因为int... arr与int[] arr不能同时存在,认为二者是相同的,
//所以这个方法是重写,就调用重写的方法
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 {
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");
}
}
Object类
垃圾回收机制关键点:
垃圾回收机制只回收JVM堆内存里的对象空间。
对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
现在的JVM有多种垃圾回收实现算法,表现各异。
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。
垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象)。
注意是回收这个对象之前,这个对象会调用一次finalize方法,可以类比Android开发中的destroy方法,在销毁之前调用一次destroy方法
永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
java.lang.Object类
-
Object类是所有Java类的根父类,数组也作为Object类的子类出现,可以调用Object类中声明的方法。
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
-
Object类中的功能(属性、方法)就具有通用性。
属性:无
方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
-
Object类只声明了一个空参的构造器
面试题:final、finally、finalize的区别?
三者只是长得像,一点关系也没有,只需要解释清楚即可。
面试题: == 和equals() 区别
一、回顾 == 的使用
== : 运算符
- 可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等。(不一定类型要相同,因为存在自动类型提升),这里指的类型不一定要相同说的是满足自动类型提升可以自己转换成相同类型,如果转换不成相同类型则编译就不通过,比如Boolean与int、double等不能够使用“ == ”,不是相同类型,编译期间就会报错。 总结:== 符号使用时,必须保证符号左右两边的变量类型一致。(一致指的是可以自动转换成相同类型)
//基本数据类型变量测试
int i = 10;
double j = 10.0;
System.out.println(i == j);//true
char k = 10;
System.out.println(i == k);//true,此时k存的是10,相当于字符A对应的是65
如果比较的是引用数据类型变量,比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体。
//引用数据类型测试
Circle c1 = new Circle("blue",1.0,3.0);
Circle c2 = new Circle("white",2.0,4.0);
System.out.println(c1 == c2);//false
//String 也是一个类
String str1 = new String("123");
String str2 = new String("123");
System.out.println(str1 == str2);//false
二、equals()方法的使用:
-
是一个方法、而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的定义:
public boolean equals(Object obj) { return (this == obj); }
说明:Object类中定义的equals()和 == 作用是相同的,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
-
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同。
//没有重写相当于 ==
System.out.println(c1.equals(c2));//false
//重写了equals方法
System.out.println(str1.equals(str2));//true
- 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的“实体内容”是否相同,那么就需要对Object类中的equals()进行重写。重写的原则:比较两个对象的实体内容是否相同。
手动重写equals() 方法,借鉴String重写的equals()方法
//手动重写equals方法
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Circle){
Circle circle = (Circle) obj;
/*if (radius == circle.getRadius() && this.equals(circle)){
return true;
}else {
return false;
}*/
//等同于
return radius == circle.getRadius() && this.equals(circle);
}
return false;
}
自动生成的equals()方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Circle circle = (Circle) o;
return Double.compare(circle.radius, radius) == 0 && name.equals(circle.name);
}
重写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是null时也永远返回false;x.equals(和x不同类型的对象)永远返回是“false”。
String s1 = "AA";
String s2 = "AA";
System.out.println(s1 == s2);//true
//用这种方式给string赋值然后用“ == ”会发现相等,这是因为string变量存储在常量池中,当发现有相同变量的时候会将地址直接赋过去,等往后学到string的存储结构会更理解。但我们比较字符串还是用equals()方法,不用 ==
自己写的equals() 方法有漏洞,在开发中都用自动生成的。
toString()的使用
Object类中toString() 的使用:
- 当输出一个对象的引用时,实际上就是调用当前对象的toString(),前提是这个对象不是null,如果是null,则二者有区别
String s= "abc";
s = null;
System.out.println(s);//null
System.out.println(s.toString());//空指针异常
Circle c3 = new Circle("blue",1.0,2.1);
System.out.println(c3.toString());//com.exer.Circle@1b6d3586
System.out.println(c3);//com.exer.Circle@1b6d3586
//输出的都是地址值,那是因为pringln方法中当是一个对象是会调用该对象的toString()方法,故二者一样
- Object类中toString() 的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString() 时,返回“ 实体内容 ” 信息。(这个实体内容,指的是我们想要的,需要的)
- 自定义类也可以重写toString() 方法,当调用此方法时,返回对象的 ”实体内容“
手动重写之后:
Circle c3 = new Circle("blue",1.0,2.1);
System.out.println(c3.toString());//com.exer.Circle@1b6d3586--->Circle[blue,1.0,2.1]
System.out.println(c3);//com.exer.Circle@1b6d3586--->Circle[blue,1.0,2.1]
自动生成的toString()
//自动生成的
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
", color='" + color + '\'' +
", weight=" + weight +
'}';
}
练习:
public class GeometricObject {
protected String color;
protected double weight;
protected GeometricObject() {
color = "white";
weight = 1.0;
}
protected GeometricObject(String color, double weight) {
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 class Circle extends GeometricObject{
private double radius;//半径
public Circle() {
super();
radius = 1.0;
}
public Circle(double radius) {
super();
this.radius = radius;
}
public Circle(String color, double weight, double radius) {
super(color, weight);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea(){
return Math.PI * radius * radius;
}
//比较圆的半径是否相等
@Override
public boolean equals(Object obj) {
if (this == obj){
return true;
}
if (obj instanceof Circle){
Circle circle = (Circle) obj;
return this.getRadius() == circle.getRadius();
}
return false;
}
//重写toString()输出半径
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle(2.0);
Circle c2 = new Circle("blue",2.0,2.0);
boolean isColor = c1.getColor().equals(c2.getColor());
System.out.println("isColor = " + isColor);
System.out.println(c1.equals(c2));//判断半径是否相等
System.out.println(c1.toString());
//二者都是调用toString()方法
System.out.println(c1);
}
}
JUnit单元测试
以下内容是从其他课程看的,不属于本课程内容(分隔符中间的内容)
测试分类:
- 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值
- 白盒测试:需要写代码的。关注程序具体的执行流程。
JUnit属于白盒测试。
JUnit使用步骤:
- 定义一个测试类(测试用例)
建议:测试类名:被测试的类名Test 如CalculatorTest
包名:xxx.xxx.xx.test
- 定义测试方法:可以独立运行
建议:方法名:test测试的方法名 如testAdd()
返回值:void
参数列表:空参
- 给方法加@Test
- 导入依赖环境Add ‘JUnit4’ to classpath
然后就会在工程下出现JUnit4的包说明导入完成
- 判定结果:绿色为成功,红色为失败
一般会使用断言操作来处理结果,方式
Assert.assertEquals(期望的结果,运算的结果);
如以下例子
@Test
public void testEquals(){
System.out.println("执行了");
int i = 4;
//断言,我断言这个结果是3
Assert.assertEquals(3,i);
}
@Before和@After
@Before:修饰的方法会在测试方法之前被自动执行
@After:修饰的方法会在测试方法执行之后自动被执行
/**
* 初始化方法:
* 用于资源申请,所有测试方法在执行之前都会先执行该方法
*/
public void init(){
}
/**
* 释放资源方法:
* 在所有测试方法执行完后,都会自动执行该方法
*/
public void close(){
}
以下是本课程关于JUnit的内容
JUnit作用:可以单独的测试方法
Java中的JUnit单元测试
步骤:
-
(第一步是Eclipse中的步骤)选中当前工程 – 右键选择:build path – add libraries – JUnit4 – 下一步
开发中直接在满足条件的类里面想要测试的方法添加@Test注释,IDE会提示添加JUnit4,不必这么麻烦去设置
-
创建Java类,进行单元测试。
此时的Java类要求:①此类是public 的 ②此类提供无参的构造器
-
此类中声明单元测试方法:
此时的单元测试方法:方法的权限是public,没有返回值,没有形参
-
此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:
import org.junit.Test;
- 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 运行
说明:
- 如果执行结果没有任何异常:绿色
- 如果执行结构出现异常:红条
之前类中程序的入口都是main方法,在单元测试中也可以运行程序,把单元测试方法看作一个普通的方法,不用创建对象便可以调用属性和其他方法。
单元测试练习:
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().....");
}
}
//需要再测试其他方法在创建其他测试方法即可
包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Interger |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
前六种包装类有一个共同的父类Number
针对八种基本数据类型定义相应的引用类型----包装类(封装类)
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
包装类的使用:
- Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
- 掌握的:基本数据类型、包装类、String三者之间的相互转换
- 基本数据类型----->包装类:调用包装类的构造器
@Test
public void test1(){
int num = 10;
System.out.println(num);
Integer integer = new Integer(num);
System.out.println(integer.toString());
Integer integer1 = new Integer("123");
System.out.println(integer1.toString());
//NumberFormatException报异常,必须是一个纯粹的数
// Integer integer2 = new Integer("123abc");
// System.out.println(integer2.toString());
Float f1 = new Float(12.3);
Float f2 = new Float("12.3");
System.out.println(f1);//没有写toString(),println也是调用了toString()方法
System.out.println(f2);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("true");
Boolean b3 = new Boolean("true123");
System.out.println(b1);
System.out.println(b2);
System.out.println(b3); //false,当不清楚为什么时可以点进去看源码
}
/*
parseBoolean(String s)的源码
意思是如果不是true(不区分大小写),其他的任何值都返回false
public static boolean parseBoolean(String s) {
return ((s != null) && s.equalsIgnoreCase("true"));
}
*/
- 包装类 ----- > 基本数据类型:调用类Xxx的xxxValue()
@Test
public void test2(){
Integer i1 = new Integer(10);
Integer i2 = new Integer("20");
int num1 = i1.intValue();
int num2 = i2.intValue();
//转换为基本数据类型就可以做加减乘除运算
System.out.println(num1 + 1);
System.out.println(num2 + 1);
Float f1 = new Float(11.2);
Float f2 = new Float("12.3");
float num3 = f1.floatValue();
float num4 = f2.floatValue();
System.out.println(num3 + 1);
System.out.println(num4 + 1);
}
以上方法可以实现基本数据类型和包装类之间的转换,使用频繁,这样还是有些麻烦,所以JDK 5.0 新特性增加了自动装箱与自动拆箱
-
自动装箱:基本数据类型------ >包装类
-
自动拆箱:包装类----> 基本数据类型
可以实现快速转换
/*
JDK 5.0 新特性:自动装箱与自动拆箱
*/
@Test
public void test3(){
//自动装箱:基本数据类型------ >包装类
int i1 = 10;
Integer integer = i1;//自动装箱
boolean b1 = true;
Boolean b2 = b1;//自动装箱
//自动拆箱:包装类----> 基本数据类型
System.out.println(integer.toString());//自动拆箱
System.out.println(b2.toString());
int i2 = integer;//自动拆箱
}
由于自动装箱与自动拆箱的存在,我们可以看作基本数据类型和包装类是同一种类型,可以相互直接赋值。(其实并不是,只是因为自动装箱与自动拆箱帮我们做了)
所以二者一块看怎么转换成String类型
- 基本数据类型、包装类 ---- > String类型:调用String重载的valueOf(Xxx xxx)
@Test
public void test4(){
int num1 = 10;
//方式一:连接运算
String str1 = num1 + "";
//方式二,调用String的valueOf(Xxx xxx)
String str2 = String.valueOf(num1);
System.out.println(str2);//“10”
double d1 = 12.3;
String str3 = String.valueOf(d1);
System.out.println(str3);//输出的是字符串形式的“12.3”
Float f1 = 11.2f;
String str4 = String.valueOf(f1);
System.out.println(str4);
}
- String类型 ---- >基本数据类型、包装类:调用包装类的parseXxx(String s)
@Test
public void test5(){
String str1 = "123";
//如果字符串里面不是一个纯粹的数就会报NumberFormatException异常
int i1 = Integer.parseInt(str1);
System.out.println("i1 = " + i1);
System.out.println(i1 + 1);
//boolean则不会报异常,只要不是ture,其他任何字符组合都是false
String str2 = "true";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);
}
这一部分只需要掌握基本数据类型与包装类转换用到自动装箱与自动拆箱;
基本数据类型和包装类转换为String用valueOf()方法;反过来转换用parseXxx(String s)方法。记忆方法:要转换成什么就调用什么方法,转换为String类型,就调用String.valueOf()
面试题:如下两个题目输出结果相同吗?各是什么:
Object o1 = true ? new Integer(1) : new Double(2.0); System.out.println(o1);//1.0
//隐含知识点,三目运算符在编译时需要两个表达式的类型相同,现在一个是int,一个是double就是自动类型提升为全是double
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);//1
//这个不用考虑那么多
如下题目的输出结果是什么?
public void method1() {
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了。目的:提高效率,因为发现-128~127范围内的使用频率最高
*/
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
}
练习题:
public class Wrapper {
public static void main(String[] args) {
//从键盘获取学生成绩
Scanner scanner = new Scanner(System.in);
int maxValue = 0;
Vector v = new Vector();
for (;;){
//创建Vector对象
System.out.print("请输入学生成绩(以负数代表输入结束):");
int score = scanner.nextInt();
if (score < 0){
//负数跳出循环
break;
}
if (score > 100){
System.out.println("成绩输入不合理!请重新输入");
//当输入非法时开始下一次循环重新输入,用continue实现
continue;
}
/*
JDK 5.0 之前
Integer integer = new Integer(score);
*/
//JDK 5.0之后:自动装箱
Integer integer = score;
//添加学生成绩
v.addElement(integer);
//找到最大值
if (score > maxValue){
maxValue = score;
}
}
//取出对象.比较成绩
for (int i = 0; i < v.size(); i++) {
Object obj = v.elementAt(i);
/*
JDK 5.0之前
Integer integer = (Integer) obj;
int score = integer.intValue();
*/
//JDK 5.0之后自动拆箱
int score = (int) obj;
char level = 0;
if (maxValue - score <= 10){
level = 'A';
}else if (maxValue - score <= 20){
level = 'B';
}else if (maxValue - score <= 30){
level = 'C';
}else {
level = 'D';
}
System.out.println("Student " + i + " score is " + score + "," + "level is " + level);
}
}
}
面试题:谈谈你对多态性的理解?
主要目的(作用)①实现代码的通用性。
举例②Object类中定义的public boolean equals(Object obj){}
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
面试题:谈谈你对多态性的理解?
主要目的(作用)①实现代码的通用性。
举例②Object类中定义的public boolean equals(Object obj){}
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
③抽象类、接口的使用肯定体现了多态性。(原因:抽象类、接口不能实例化,也就是不能new 对象,只能new 子类的对象,这就体现了多态性)