目录
1.面向对象的特征之二:继承性
1.1继承性的优点
减少了代码的冗余,提高代码的复用性。
便于功能的扩展。
为之后的多态性的使用提供了前提。
1.2继承性的体现
格式:
class A extends B{}
//A:子类(派生类),subclass
//B:父类(超类、基类)、superclass
一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性、方法,包括私有的。此处的获取指堆内存中有。
父类声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。
子类和父类的关系,不同于子类和集合的关系。
1.3 java中关于继承性的规定
1.一个类可以被多个子类继承
2.一个类只能有一个父类(与c++多继承不同,java是单继承)
3.子父类是相对的概念,体现在多层继承,一个类既可以是父类,也可以是子类。
4.子类直接继承的父类称为直接父类,间接继承的父类称为间接父类
5.子类继承父类以后,就获得了直接父类和所有间接父类中声明的属性和方法
A<–B<–C
C继承B,B继承A,B是C的直接父类,A是C的间接父类,则C继承了A和B的所有属性和结构。
/**
* @description:父类
* @author: d-linlin
* @time: 2021/7/29
*/
public class ManKind {
private int sex;
private int salary;
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 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");
}
public ManKind(){
}
public ManKind(int sex,int salary){
this.sex=sex;
this.salary=salary;
}
}
/**
* @description:子类
* @author: d-linlin
* @time: 2021/7/29
*/
public class Kids extends ManKind{
int yearsOld;
public void printAge(){
System.out.println("yearsOld="+yearsOld);
}
public Kids(){
}
public Kids(int yearsOld){
this.yearsOld=yearsOld;
}
}
/**
* @description:测试
* @author: d-linlin
* @time: 2021/7/29
*/
public class KidsTest {
public static void main(String[] args) {
Kids somekids=new Kids();
somekids.setSex(0);
somekids.setSalary(0);
somekids.yearsOld=10;
somekids.manOrWoman();
somekids.employeed();
somekids.printAge();
}
}
1.4 Object类
如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
所有的java类(除java.lang.Object类外),都直接或间接继承java.lang.Object类
意味着所有java类,都具有java.lang.Object类声明的功能
2.方法的重写(override/overwrite)
/**
* @description:父类
* @author: d-linlin
* @time: 2021/7/29
*/
public class ManKind {
private int sex;
private int salary;
public void manOrWoman(){
if(sex==1) System.out.println("man");
else if(sex==0) System.out.println("woman");
}
public ManKind(){}
public ManKind(int sex,int salary){
this.sex=sex;
this.salary=salary;
}
}
/**
* @description:子类
* @author: d-linlin
* @time: 2021/7/29
*/
public class Kids extends ManKind{
int yearsOld;
//方法的重写
public void manOrWoman(){
System.out.println("man");
}
//错误,原因见重写的规定中第二条
void manOrWoman(){
System.out.println("man");
}
public Kids(){}
public Kids(int yearsOld){
this.yearsOld=yearsOld;
}
}
方法的重写:子类继承父类后,可以对父类中同名同形参列表的方法进行覆盖操作。
应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同形参列表的方法时,实际执行的是子类重写父类的方法。
重写的规定:
方法的声明:
权限修饰符 返回值类型 方法名(形参列表)throws 异常的类型{
//方法体
}
约定:子类中的叫重写的方法,父类中的叫被重写的方法
1.子类重写的方法的方法名和形参列表,与父类被重写的方法的方法名和形参列表相同
2.子类重写的方法的权限修饰符 >= 父类中被重写的方法的权限修饰符
特殊情况:子类不能重写父类中private的方法。因为子类根本就看不到父类的private方法
如果:
//父类:
private void show(){}
//子类:
public void show(){}//此时不认为这个方法是重写的方法
3.返回值类型
①父类被重写的返回值类型是void,则子类重写的方法的返回值类型只能是void
②父类被重写的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型。
③父类被重写的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类,或者A类的子类。
//父类:
public Object info(){}
//子类:
public String info(){}
④子类重写的方法抛出的异常类型 <= 父类被重写的方法抛出的异常类型。(具体放到异常处理的时候讲)
子类和父类中的同名同形参列表的方法,要么都生命为非static的(可以考虑重写),要么都声明为static的(不是重写,不可以被重写)。
3.super关键字
1.super可理解为:父类的
2.super可以用来调用属性、方法、构造器
3.1 super的使用:调用属性和方法
1.我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性或方法。但是通常情况下,我们习惯省略super。
2.当子类和父类当中定义了同名的属性时,我们要想在子类中调用父类声明的属性时,则必须显式的使用“super.属性”的方式,表示调用的是在父类中声明的属性。(通常在开发中是不会写子父类同名属性的)
public class Person {
String name;
int age;
int id;//身份证号
}
public class Student extends Person{
int id;
public void show(){
System.out.println("name="+name+",age="+age);//此时在属性前省略了this或super
System.out.println(id);//Student类的id
System.out.println(super.id);//父类Person的id
}
}
3.当子类重写的父类的方法以后,我们想在子类中调用父类中被重写的方法时,必须显式的使用“super.方法”的方式,表明调用的时父类中被重写的方法。
public class Circle {
private double radius;
public double getRadius() {
return radius;
}
//父类被重写的方法findArea()
public double findArea(){
return Math.PI*radius*radius;
}
}
public class Cylinder extends Circle{
private double length;
public double findV(){
//return Math.PI*getRadius()*getRadius()*length;
//return findArea()*getLength();此句调用了子类中重写的方法findArea()
return super.findArea()*getLength();此句调用了父类中被重写的方法findArea()
}
//子类重写的方法findArea()
public double findArea(){
return Math.PI*getRadius()*getRadius()*2
+2*Math.PI*getRadius()*getLength();
}
}
3.2 super的使用:调用构造器
- 我们可以在子类构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器。
- super(形参列表)的使用,必须声明在子类构造器的首行。
意味着,“super(形参列表)”和“this(形参列表)”不能同时在一个构造器中出现。 - 如果构造器的首行没有显式的声明“super(形参列表)”或“this(形参列表)”,则首行默认调用父类中的空参构造器(super(形参列表))
- 如果一个类中共有N个构造器,那么最少有1个构造器中使用super(形参列表)
因为:如果一个类中共有N个构造器,那么最多有N-1个构造器中使用this(形参列表),而构造器首行必须是“super(形参列表)”或“this(形参列表)”。
4.子类对象实例化的全过程
1.从结果上看:(继承性)
子类继承父类以后,就获取了父类中声明的属性和方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
2.从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器…直到调用了java.lang.Object类中空参的构造器为止,所以才可以看到内存中有父类中的结构,子类对象才可以考虑调用。
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
5.面向对象的特征之三:多态性
5.1 多态性理解
一个事物的多种形态。
5.2 什么是多态性
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
//Person类(父类)有两个子类:Man类和Woman类
//对象的多态性:父类的引用指向子类的对象
Person p1=new Man();
Person p2=new Woman();
5.3 多态的使用(即虚拟方法调用):
有了对象的多态性以后,
在编译期,只能调用父类中声明的方法
在运行期,实际执行子类重写父类的方法
当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法。
//Person类中有eat()方法,子类Man重写了eat()方法,有特有的方法aa()
Person p=new Man();
//即编译看左边,执行看右边
p.eat();//此时调用的是子类Man中的重写方法
p.aa();//错误,不可以调用子类特有方法,因为声明的是Person类型
对象的多态性只适用于方法,不适用于属性
虚拟方法调用(动态绑定):
子类定义了与父类同名同参数的方法,在多态情况下,此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Person p=new Student();
p.getInfo();//调用Student类的getInfo()方法
5.4 多态性的使用前提
①有继承关系
②要有方法的重写,否则没有必要使用多态性
5.5 多态性使用举例
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test=new AnimalTest();
test.func(new Dog());//相当于Animal animal=new Dog();
}
public void func(Animal animal){//声明的Animal类型
animal.eat();
animal.shout();
}
}
class Animal{
public void eat(){
System.out.println("动物,进食");
}
public void shout(){
System.out.println("动物,叫");
}
}
class Dog extends Animal{
@Override
public void shout(){
System.out.println("汪");
}
}
class Cat extends Animal{
@Override
public void shout() {
System.out.println("喵");
}
}
运行结果:
5.6 面试题:多态性是编译时行为还是运行时行为?如何证明?
答:运行时行为。
Animal为父类,有三个子类Cat、Dog、Sheep,都重写了父类的eat()方法。
此时并不能看出来eat()调用的是哪个方法,只有运行后才知道,因此多态性是一个运行时行为。
5.7重载与重写
从编译和运行角度来看
重载:重载的方法的调用地址在编译期就绑定了。java重载是可以包括父类和子类的,即子类子类可以重载父类的同名不同参数的方法。所以对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为**“早绑定”或“静态绑定”**。
多态:只有等到方法调用的那一刻,解释运行器才会确定所要调用的方法,这称为**“晚绑定”或“动态绑定”**。
所以重写也就是多态。
5.8多态练习题
1.输出结果?
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
}
}
2.有坑!
public class Test {
public static void main(String[] args) {
Base1 base1=new Sub1();
base1.add(1,2,3);//sub1
Sub1 s=(Sub1)base1;
s.add(1,2,3);//sub2
}
}
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("sub1");
}
public void add(int a,int b,int c){
System.out.println("sub2");
}
}
**base1.add(1,2,3)**调用了Sub1类中的第一个add(),因为在函数声明的参数中,int[]arr与int…arr是一样的,也就是Sub1类的第一个add()函数与父类的add()函数构成了重写,所以多态调用了子类的这个重写的函数。Sub1类的第二个add()函数并不与父类构成重写。
**s.add(1,2,3)**调用了Sub1类中的第二个add(),因为s此时被转为Sub1类,当一个类中既有可变形参函数,又有固定形参函数时,优先调用固定形参函数。
6. 向下转型
应用背景:
Person p1=new Man();
有了对象的多态性以后,内存中实际上加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用。
那么如何才能调用子类特有的属性和方法?
Man m=(Man)p1;
答:使用强制类型转换符
父类–>子类:强制类型转换(向下转型)
子类–>父类:多态(向上转型)
问题一:如何写使得编译时通过,运行时不通过?
//Person类为父类,有两个子类Man类和Woman类
Person p1=new Woman();
Man m1=(Man)p1;
//从语法上来看没问题,因此编译错误。
//但是运行时相当于Man m1=new Woman();Man类和Woman类没有继承关系,
//因此不能使用多态,运行错误。
问题二:如何写使得编译时通过,运行时也通过?
Object obj1=new Woman();
Person p2=(Person)obj1;
//相当于Person p2=new Woman();左父右子,正确
但是,强制类型转换有风险,可能出现ClassCastException的异常
因此,为了避免出现异常,引入了instanceof关键字
7. instanceof关键字
a instanceof A
判断对象a是否是类A的实例,如果是,返回true,否则返回false
如果a instanceof A返回true,则a instanceof B也返回true,其中类B是类A的父类。
为了避免出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,则进行向下转型,否则,不进行向下转型。
if(p1 instanceof Man){
Man m1=(Man)p1;
}
即如果p1是Man的类型,则进行强转
8.Object类的使用
1.Object类是所有Java类的根父类。
2.如果在类的声明中未使用extends关键字指明父类,则默认父类为java.lang.Object类
3.Object类中的功能(属性、方法)就具有通用性。
4.Object类中只声明了一个空参构造器
5.Object类中有主要几个方法:equals()、toString();
9.==运算符和equals()的区别
9.1 ==运算符
1.可以使用在基本数据类型变量和引用数据类型变量中。
2.如果比较的是基本数据类型变量,就是比较两个变量保存的数据是否相等。(不一定要类型相同)
3.如果比较的是引用数据类型变量,就是比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体。
4.使用时必须保证==左右两边的变量类型可以进行比较
9.2 Object类中equals()的使用
a.equals(b);//判断ab是否相同
1.是一个方法,而非运算符
2.只适用于引用数据类型
3.在Object类中equals方法的定义:
public boolean equals(Object obj){
return (this==obj);
}
此时和==的作用相同,即比较两个对象的地址值是否相同
Person p1=new Person();
Person p2=new Person();
System.out.println(p1.equals(p2));//false
4.在String类、Date类、File类、包装类等都重写了Object类中的equals()方法。重写以后比较的是两个对象的“实体内容(变量值)”是否相同,而不是比较两个对象的地址值是否相同。
String str1=new String("d-linlin");
String str2=new String("d-linlin");
System.out.println(str1.equals(str2));//true
5.我们自定义的类如果使用equals()的话,也通常比较两个对象的“实体内容”是否相同。那么我们就需要对Object类中的equals()方法进行重写。
那么,自定义类如何重写?
例:Person类中有属性age(int)、name(String),重写equals()使得其比较两个对象的实体内容是否相同,即两个对象的属性是否相同
public boolean equals(Object obj){
if(this==obj)return true;
if(obj instanceof Person){
Person p=(Person)obj;
return this.age==p.age && this.name.equals(p.name);
}
return false;
}
idea可使用alt+insert快捷键自动添加重写的equals()方法。
6.在任何情况下,x.equals(null)返回的永远是false.
10.Object类中toString()的使用
toString() 方法返回此对象本身(它已经是一个字符串)。
public class Test {
public static void main(String args[]) {
String Str = new String("WWW.RUNOOB.COM");
System.out.print("返回值 :" );
System.out.println( Str.toString() );//返回值 :WWW.RUNOOB.COM
}
}
1.当我们输出一个对象的引用时,实际上就是调用当前对象的toString方法。
Person p=new Person();
System.out.println(p);//输出地址值
System.out.println(p.toString());//输出地址值,和上一条语句结果相同
2.Object类中toString()的定义
public String toString(){
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
3.像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回“实体内容”信息。
4.自定义类也可以重写toString()方法,当调用此方法时,返回“实体内容”信息。
11.JUnit单元测试
步骤:
1.导入JUnit
2.创建java类,进行单元测试
此时的java类要求:此类时public的,并且此类提供公共的无参的构造器。
3.此类中声明单元测试的方法
此时的单元测试方法,方法的权限是public,没有返回值,没有形参
4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入import org.junit.Test;
5.声明好单元测试方法以后,就可以在方法体内测试相关代码
6.写完代码以后,左键双击单元测试方法名,右键:run as --> JUnit Test
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));
System.out.println(num);
show();
}
public void show(){
num=20;
}
}
如果执行结果没有任何异常:绿条
如果执行结果出现异常:红条
12.包装类(Wrapper、封装类)
针对八种基本数据类型定义相应的引用类型:包装类
有了类的特点,就可以调用类中的方法
基本数据类型 | 包装类 | 父类 |
---|---|---|
byte | Byte | Number |
short | Short | Number |
int | Integer | Number |
long | Long | Number |
float | Float | Number |
double | Double | Number |
boolean | Boolean | |
char | Character |
12.1 基本数据类型 转换为 包装类
基本数据类型—>包装类:调用包装类的构造器
应用:如调用equals()方法,参数为Object类,就需要将基本数据类型转换为包装类。
import org.junit.Test;
public class WrapperTest {
@Test
public void test1(){
int num1=10;
Integer int1=new Integer(num1);
System.out.println(int1.toString());
Float f1=new Float(12.3f);
Float f1=new Float("12.3f");//12.3,具体原因可查看源码(ctrl+shift+i)
Boolean b1=new Boolean(true);
Boolean b2=new Boolean("TrUe");//true
Order o1=new Order();
System.out.println(o1.isMale);//false,由于未赋初值,显示boolean类型的默认值
System.out.println(o1.isFemale);//null,由于未赋初值,显示Boolean类的默认值
}
}
class Order(){
boolean isMale;
Boolean isFemale;
}
12.2 包装类 转换为 基本数据类型
包装类—>基本数据类型:调用包装类的xxxValue()
应用:当包装类需要进行比较时
xxxValue()的返回值类型为xxx
import org.junit.Test;
public class WrapperTest {
@Test
public void test2(){
Integer i1=new Integer(12);
int i=i1.intValue();
System.out.println(i+1);
}
}
12.3 JDK5.0新特性:自动装箱与自动拆箱
正常来讲,当方法形参为类对象时,实参也应为类对象
public class Wrapper{
@Test
public void test3(){
int num1=10;
Integer i1=new Integer(num1);
method(i1);
}
public void method(Object obj){}
}
但是,下面这种写法也是正确的
public class Wrapper{
@Test
public void test3(){
//自动装箱
int num1=10;
method(num1);
//自动装箱
boolean b1=true;
Boolean b2=b1;
//自动拆箱
boolean b1=b2;
}
public void method(Object obj){}
}
这个就是自动装箱,即基本数据类型转为包装类
自动拆箱:包装类转换为基本数据类型
12.4 基本数据类型、包装类 转换为 String类型
public class WrapperTest {
@Test
public void test4(){
//方式一:连接运算
int num=40;
String str1=num+"";
//方式二:调用String的valueOf(Xxx xxx);
float f1=12.3f;
String str2=String.valueOf(f1);
Float F=new Float(12.3f);
String str=String.valueOf(F);
}
}
12.5 String类型 转换为 基本数据类型、包装类
public class WrapperTest {
//String-->基本数据类型、包装类:调用包装类的parseXxx()
@Test
public void test5(){
String str1="123";
int num1=Integer.parseInt(str1);
String str2="truE";
boolean b1=Boolean.parseBoolean(str2);//true
}
}
String类型的变量在转为基本数据类型时,应注意变量值要与基本数据类型一致,否则可能出现NumberFormatException的报错。
对于boolean类型,当变量值为true或true的不分大小写的形式时,返回值才为true
12.6 包装类面试题
1.以下两段代码的输出结果相同吗?分别是什么
Object o1=true?new Integer(1):new Double(2.0);
System.out.println(o1);//1.0
?:符号的后两个表达式在编译时会有自动类型提升,也就是要有同一类型,如果一个是int,一个是String就是错误的。
Object o2;
if(true)
o2=new Integer(1);
else
o2=new Double(2.0);
System.out.println(o2);//1
import org.junit.Test;
public class InterviewTest {
@Test
public void test1(){
Integer i=new Integer(1);
Integer j=new Integer(1);
System.out.println(i==j);//false
Integer m=1;
Integer n=1;
System.out.println(m==n);//true
Integer x=128;//相当于new了一个Integer对象
Integer y=128;
System.out.println(x==y);//false
}
}
分析原因:归结于Java对于Integer 与 int 的自动装箱和拆箱的设计,是一种模式:叫 享元模式(flyweight)。
加大对简单数字的重利用,Java 定义在自动装箱时对于值在 [-128,127] 之间的值,他们被装箱为Integer 对象后,会存在内存中被重用,始终只有一个对象。
Integer内部定义了一个IntegerCache结构,IntegerCache中定义了Integer[],保存了从[-128,127]范围内的整数,如果我们使用自动装箱的方式,给Integer赋值的范围在[-128,127]范围时,可以直接使用数组中的元素,不用再new了。
目的:提高效率
12.7包装类练习
import java.util.Vector;
import java.util.Scanner;
/**
* @description: 利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束) , 找出
* 最高分,并输出学生成绩等级。
* ➢提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的
* 长度。而向量类java.util.Vector可以根据需要动态伸缩。
* ➢创建Vector对象: Vector v=new Vector();
* ➢给向最添加元索: v.addElement(Object obj); //obj必须是对象
* ➢取出向量中的元素: Object obj=v.elementAt(O);
* 注意第一个元素的下标是0,返回值是Object类型的。
* ➢计算向量的长度: v.size();
* ➢若与最高分相差10分内: A等: 20分内: B等;
* 30分内: C等:其它: D等
* @author: d-linlin
* @time: 2021/8/13
*/
public class ScoreTest {
public static void main(String[] args) {
//1.实例化Scanner,用于从键盘获取学生
Scanner scan=new Scanner(System.in);
//2.创建Vector对象
Vector v=new Vector();
//3.通过循环,给Vector中添加数据,当输入是负数时,跳出循环
int maxScore=0;
for(;;){
System.out.println("请输入学生成绩(当输入是负数时输入结束)");
int score=scan.nextInt();
if(score<0)break;
if(score>100){
System.out.println("输入数据非法,请重新输入");
continue;
}
//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);
//jdk5.0之前:
//Integer inScore=(Integer) obj;
//int score=inScore.intValue();
//jdk5.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);
}
}
}