面向对象4
1 多态
1.1 概述
是指同一行为,具有多个不同表现形式。
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
1.2 概念及语法
概念
多态性,是面向对象中最重要的概念,在Java中的体现:父类的引用指向子类的对象
-
可以直接应用在抽象类和接口上
-
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
Java编译器将. java文件编译成. class文件时,变量的类型
Java虚拟机将,class文件加载进内存,真正创建的对象所属的类型,运行时类型
语法格式
父类类型 引用 = new 子类对象;
引用.方法名();
示例代码
public abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的
eat a1.eat();
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的
eat a2.eat();
}
}
1.3 实现原理
1需要存在继承或者实现关系
2有方法的重写
多态最重要的特性:发生多态时,父类引用调用的方法是子类重写的方法。
虚拟方法调用(virtual method invocation)
正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
多态情况下
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。这也叫做方法的动态绑定
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法 //虚拟方法的调用
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
内存图
属性不具有多态性
1.4 多态的好处
-
可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
-
可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
-
接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
-
灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
-
简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
1.5 instanceof
操作符
Animal animal=new Cat();
System.out.println(anilmal instanceof Cat); //true
System.out.println(anilmal instanceof Dog); //flase
System.out.println(anilmal instanceof Animal ); //true
x instanceof A:检验x是否为类A的对象,返回值为boolean型。 //X对象是否是A类型
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果x属于类A的子类B,x instanceof A值也为true。
1.6 对象类型转换
int b;
double a= b;
byte c=(byte) b; //精度丢失,强制类型转换。
(Cat) Animal.run();//强制转换
(Gog) Animal.rin();//强制转换
对Java对象的强制类型转换称为造型(Casting)
-
从子类到父类的类型转换可以自动进行(多态)
-
从父类到子类的类型转换必须通过造型(强制类型转换)实现
-
无继承关系的引用类型间的转换是非法的
-
在造型前可以使用instanceof操作符测试一个对象的类型
示例代码
public class Test {
public void method(Person e) {
// 设Person类中没有getschool() 方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if (e instanceof Student) {
Student me = (Student) e; // 将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(String[] args){
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
课堂练习
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
}
}
课堂练习-class01
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;
}
}
建立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”
package Stage1_JavaBasics.Chapter02_ObjectOriented.ObjectOriented04.class01;
public class InstanceTest {
public void method(Person e){
if (e instanceof Graduate){
System.out.println("a graduated student \na student\na person\n");
}
else if (e instanceof Student){
System.out.println("a student \na person\n");
}
else if(e instanceof Person){
System.out.println("a person\n");
}
}
public static void main(String[] args) {
Person person=new Person();
person.getInfo();
Student student=new Student();
student.getInfo();
Graduate graduate=new Graduate();
graduate.getInfo();
InstanceTest text=new InstanceTest();
text.method(person);
text.method(student);
text.method(graduate);
}
}
课堂练习-class02
定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆形,MyRectangle代表矩形。定义一个测试类GeometricTest,编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
练习题
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();
}
}
2 代码块
-
代码块(或初始化块)的作用:
- 对Java类或对象进行初始化
-
代码块(或初始化块)的分类:
- 一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block),没有使用static修饰的,为非静态代码块。
-
static代码块通常用于初始化static的属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
-
静态代码块:用static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
-
非静态代码块:没有static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
示例代码
class Person {
public static int total = 100;
static {
total = total + 50;
System.out.println("in static block!");
}
}
public class PersonTest {
public static void main(String[] args) {
System.out.println("total = " + Person.total);
System.out.println("total = " + Person.total);
}
}
- 变量赋值总结-面试
3 Object
类
3.1 介绍
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为
java.lang.Object
类
public class Person {
...
}
等价于:
public class Person extends Object {
...
}
3.2 主要结构
3.3 equals()方法
如果要自定义对象相等的判断逻辑,就需要重写equals()方法
public boolean equals(object obj){
if (obj instanceof Student) {
return this.name == ( (Student) obj ) . name;
} else {
return false;
}
}
-
equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。
- 只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
- 格式:obj1.equals(obj2)
-
特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
- 原因:在这些类中重写了Object类的equals()方法。
-
当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等
-
重写equals()方法的原则
- 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
- 自反性:x.equals(x)必须返回是“true”。
- 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
- 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
面试题:==和equals的区别
-
==既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
-
equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中** 用的比较多,久而久之,形成了equals是比较值的错误观点。
-
具体要看自定义类里有没有重写Object的equals方法来判断。
-
通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
课堂练习
int it = 65;
float fl = 65.0f;
System.out.println(“65和65.0f是否相等?” + (it == fl));//true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println(“12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2));//false
System.out.println("str1是否equals str2?"+(str1.equals(str2)));//true
System.out.println(“hello” == new java.util.Date());
课堂练习-clas03
编写Order类,有int型的orderId,String型的orderName,相应的getter()和setter()方法,两个参数的构造器,重写父类的equals()方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。
3.4 toString()方法
-
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
-
在进行String与其它类型数据的连接操作时,自动调用toString()方法
Date now=new Date(); System.out.println(“now=”+now);相当于System.out.println(“now=”+now.toString());
-
可以根据需要在用户自定义类型中重写toString()方法。如String类重写了toString()方法,返回字符串的值。
s1=“hello”; System.out.println(s1);//相当于System.out.println(s1.toString());
-
基本类型数据转换为String类型时,调用了对应包装类的toString()方法
int a=10; System.out.println(“a=”+a);
.getClass:是toString前面的那一部分
4.包装类
(基本数据类型-字符串-包装类 三者类之间的相互转换)
基本数据类型
//每一个基本数据类型提供个对应的包装类
public class WrapperTest {
public static void main(String[] args) {
int i=10; //Ruby int i=10;i.
Integer integer = new
Integer(i);
// xxxVaLue 方法根据要返回的基本数据类型将value转化成该类型返回,返回的是基本数据类型。
long l = integer. longValue();
//paresXxx方法将字符串解析为对应的值。
long il=Integer .paresInt(“67”);
//getXxx方法将字符串解析为对应的对象。
Integer integer 2 =Integer .paresInt(“67”);
//常用 //valueof方法将字符串或者是基本数据类型解析然后包装成对应的包装类对象
Integer integer2 = Integer . value0f(78);
//
int bytes = Integer .BYTES;
System. out . println(bytes);
}
基本数据类型和其对应的包装类的转换
1.1基本转包装
Double aDouble=new Double(“23.9f”)
Float aFloat =Float . va lue0f (45.7f)
1.2包装转基本
double d = aDouble . doubleValue():
1.3自动装箱和自动拆箱
Java的编译器会根据需要自动地在基本数据类型和其对应的包装类之问进行转化
Integer integerl = new Integer( value: 10);
Integer integer2 = new Integer( value: 20);
//add(20, 67); //自动装箱,将基本数据类型转换成包装类型
int sum = integerl + integer2; //自动拆箱,将包装类型拆成基本数据类型
System. out. println(sum);
2字符串与包装类之间的转换
2.1字符串->包装类
Double aDouble = new
Double( S: “23.9”);
FLoat aFloat = Float . va Lue0f(“45.7f”);
2.2包装类->字符串
(1)String s = aDouble . toString();
3字符串与基本数据类型的转换
3.1字符串–>基本数据类型parseXxxx
(1)int i = Integer. parseInt( “36F”);
3.2基本数据类型–>字符串
(1)String s = String. value0f(i);
System. out . printLn(s);
(2)String s1=i+“ ”;
System. out. println(s1);
思考题
1.0bject ol = true ? new Integer( value: 1) : new Double( value: 2.0) ;
// Integer对象 Double对象
// int 1 double 2.0
// double 1. 0 double 2. 0
问题:System. out. println(o1);?
A 1 B 2 C 1.0 D 其他
选c1.0
Integer i =new Integer( value: 1);
Integer j = new Integer( value: 1) ;
System. out.println(i == j);//falues
Integer m = 1;
Integer n = 1;
System.out. println(m == n);//true,放到缓存中-128~127
Integer x = 128;
Integer y = 128;
System. out .println(x == y);//falues,放到缓存中-128~127,超出,只能在内存中new对象
5 接口
5.1 概述
-
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都 支持USB连接。
-
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
-
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
5.2 语法
命名:表示什么能力时:xxxable
-
接口(interface—>传统的Java8之前的接口)是抽象方法和常量值定义的集合。
-
接口的特点:
- 用interface来定义。 //–>定义接口:常量+抽象方法
- 接口中的所有成员变量都默认是由public static final修饰的。//–>可以省略不写
- 接口中的所有抽象方法都默认是由public abstract修饰的。 //–>抽象方法
- 接口中没有构造器。 //–>说明接口中不需要初始化的成员变量–>只能是常量
- 接口采用多继承机制。 //FlyAndWalk extends Flyable,Walkable
-
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
-
一个类可以实现多个接口,接口也可以继承其它接口。 //–>implements InterfaceA,InterfaceB
-
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
-
接口的主要用途就是被实现类实现。(面向接口编程)
-
与继承关系类似,接口与实现类之间存在多态性
-
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
5.3 接口与抽象类
9.都可以使用:没有成员变量
思考题-class04
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x);//对x的引用不明确 B和A都匹配
}
public static void main(String[] args) {
new C().pX();
} }
思考题采纳-class05
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable,
Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
private String name;
public String getName() {
return name; }
public Ball(String name) {
this.name = name; }
public void play() {
ball = new Ball("Football"); //无法为最终变量球赋值
System.out.println(ball.getName());
} }
课堂练习-clas06
定义一个接口用来实现两个对象的比较。
interface CompareObject{
public int compareTo(Object o); //若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
}
定义一个Circle类,声明redius属性,提供getter和setter方法
定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
思 考 : 参 照 上 述 做 法 定 义 矩 形 类 Rectangle 和 ComparableRectangle 类 , 在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小。
5.4 接口的改进
Java 8中,可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
-
**静态方法:**使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
-
**默认方法:**默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
6 内部类
内部类:在一个类的内部定义得类
6.1 概述
-
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
-
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
-
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
- Inner class的名字不能与包含它的外部类类名相同;
-
分类:
- 成员内部类(static成员内部类和非static成员内部类)
- 局部内部类(不谈修饰符)、匿名内部类
-
成员内部类作为类的成员的角色:
- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
-
成员内部类作为类的角色:
- 可以在内部定义属性、方法、构造器等结构
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意
-
非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
-
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
-
成员内部类可以直接使用外部类的所有成员,包括私有的数据
-
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
示例代码
class Outer {
private int s;
public class Inner {
public void mb() {
s = 100;
System.out.println("在内部类Inner中s=" + s);
}
}
public void ma() {
Inner i = new Inner();
i.mb();
}
}
public class InnerTest {
public static void main(String args[]) {
Outer o = new Outer();
o.ma();
}
}
6.2 内部类的使用语法
如何实例化成员内部类
如何在成员内部类中调用外部类的结构
//创建Dog实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类):
//Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
1.在外部类的内部结构中使用 //在外部方法中new()
Engine engine = car.new Engine();
2.在外部类 //在外部main中new();
//如果内部类是一个非静态的结构,那么就不能直接在外部直接实例化
Car car = new Car() ;
Engine engine = car.new Engine();
//如果内部类是一个静态(static)的结构,那么就不能直接在外部直接实例化
Car.Engine engine = new Car.Engine( );
engine . start();
6.3 匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}
new Animal(){
// 方法重写
}
匿名内部类的特点
-
匿名内部类必须继承父类或实现接口
-
匿名内部类只能有一个对象
-
匿名内部类对象只能使用多态形式引用
示例代码
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
1.具名类的具名对象
Cat cat=new Cat();
2.具名类的匿名对象
new Cat(); //作用使用一次就完了
3.匿名类的具名对象(一般是抽象类或者是接口的实现类) //Geometrit0bject几何类是抽象类,不能创建对象
Geometrit0bject gb=new Geometrit0bject( ){
@0verride
public double findArea() {
return 0;
}
};
4.匿名类的匿名对象
new Geometrit0bject( ){
@0verride
public double findArea() {
return 0;
}
}.findArea();