目录
Java面向对象
包
(1)Java内建的package机制是为了避免class命名冲突
(2)JDK的核心类使用java.lang包,编译器会自动导入
(3)JDK的其它常用类定义在java.util.*,java.math.*,java.text.*等
(4)包名推荐使用倒置的域名,例如:org.apache
类结构
(1)实例变量(instance variable)
或叫实例域、实例字段(instance field)、成员变量(member variable)。实例的变量,每个实例的变量可能不同。
实例字段的特点:每个实例都有独立的字段,各个实例的同名字段互不影响。
(2)实例方法(instance method)
或叫成员方法(member method)。供实例用的方法,必须要先有实例,才能通过此实例调用实例方法
(3)类变量(class variable)
或叫静态域、静态字段(static field)、静态变量(static variable)
1.创建类
class 类名称 {
// 类的成员变量
// 类的成员方法
}
类名要求:
(1)类名必须以英文字母开头,后接字母,数字和下划线的组合
(2)习惯以大写字母开头
2.构造方法
(1)没有返回类型,也不能定义为void
(2)方法名与类名一致
(3)主要作用完成对象的初始化工作,把定义对象的参数传给对象成员
public class Book {
public Book(){} // 构造方法名与类名一致
}
有参数的为有参构造,无参数的为无参构造
public可换为private;表示无法实例化,也就是无法new
3.成员方法
[权限修饰符] [返回值类型] 方法名 ([参数类型 参数名]) [throws 异常类型] {
// 方法体
return 返回值;
}
4.成员方法中的参数
调用方法时可以给该方法传递一个或者多个值,传给方法的值叫做实参。在方法内部,接受实参的变量叫做形参,形参的声明语法与变量的声明语法一样
(1)值参数(修改形参不会影响实参)
值参数(基本数据类型)表面是实参与形参之间按值传递。当使用值参数的方法被调用时,编译器为形参分配存储单元,然后将对应的实参的值复制到形参中,由于值类型的传递方式。所以,在方法中对值类型的形参的修改不会影响实参。
(2)引用参数(修改形参影响实参)
在方法传递参数时,参数的类型是数组或者其他引用类型。在方法对参数的修改会反映到原有的数组或者其他引用类型上,这种类型的方法参数。称之为引用参数(对形参的修改,会改变实参的值,并映射到原有的引用类型上,实参也是一样)
public void fun(Person person, int i) {
Person(形参) one = person(实参);
int a = i;
}
虽然String确实是引用类型,但基本把他看成是值类型,因为他引用的内存中的值并不会改变,也应该不会说当另外一个应用改变了内存中的值,从而会导致这个值的改变。但StringBuffer却可以,还有数组等。
(3)不定长参数
声明方法时,如果有若干个相同类型的参数,可以定义为不定长参数
权限修饰符 返回值类型 方法名(参数类型… 类型名)
5.局部变量
作用域:范围从该变量的声明开始到结束为止{}
成员方法内定义的变量
this关键字
调用成员方法和成员变量
static关键字
由static修饰的变量、常量和方法被称为静态变量、静态常量、静态方法,也被称为类的静态成员(归类所有)
1.静态代码块(static{})
类加载的时候运行
static {
// 方法体
}
2.静态字段
对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例
不推荐用“实例对象.静态字段”去访问静态字段,因为在Java程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段只是因为编译器可以根据实例类型自动转换为“类名.静态字段”来访问静态对象
推荐用类名来访问静态字段。可以把静态字段理解为描述class本身的字段(非实例字段)
3.静态方法
调用静态方法则不需要实例变量,通过类名就可以调用
(1)静态字段属于所有实例“共享”的字段,实际上是属于class的字段
(2)调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法
(3)静态方法常用于工具类和辅助方法
类的主方法
public static void main(String[] args) {
}
(1)主方法是静态的,如要直接在主方法中调用其他方法,那么该方法必须也是静态的
(2)没有返回值
(3)形参为数值
作用域
(1)Java内建的访问权限包括public、protected、private和default(默认)
(2)Java在方法内部定义的变量是局部变量,作用域从变量声明开始,到一个块结束
(3)final修饰符不是访问权限,它可以修饰class、field和method
(4)一个.java文件只能包含一个public类,但可以包含多个非public类
访问权限表
访问修饰符&访问权限 | 本类 | 同包 | 子类 | 父类 |
---|---|---|---|---|
private | √ | |||
默认(default) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
对象
1.对象的创建
Test test = new Test(); // 实例化
Test test = new Test("");
Test :类名
test :创建Test的对象。为引用变量名,其类型为Test
new :创建对象操作符
访问对象的属性和方法:“对象.类成员”
总体意思:定义一个test变量。类型为:Test。值为:一个Test对象
2.对象的销毁
每个对象都有生命周期,当对象的生命周期结束时,分配给该对象的内存地址会被回收;Java拥有一套完整的垃圾回收机制,用户不必担心废弃的对象占用内存,垃圾回收器将回收无用的但占内存的资源
抽象类与接口
抽象类和抽象方法(abstract)(利于代码重用)
[权限修饰符] abstract class 类名 {
// 抽象方法
public abstract void print();
// 类体
}
抽象方法本身没有任何意义,除非被继承重写,而承载这个抽象方法的抽象类必须被继承,除了被继承之外,没有任何意义。
抽象类特点(abstract)
(1)抽象类和抽象方法都通过abstract关键字来修饰,方法默认被public abstract修饰
(2)抽象类不能实例化,抽象类中可以没有、一个、多个抽象方法,甚至全部方法都可以是抽象方法
(3)抽象方法只有声明定义,没有实现。有抽象方法的类必须声明为抽象类。从抽象类继承的子类必须实现抽象方法,如果不实现抽象方法,则该子类仍是一个抽象类
(4)面向抽象编程使得调用者只关心抽象方法的定义,不关心子类的具体实现
抽象类不一定包含抽象方法,但抽象方法所在的类一定是抽象类
abstract不能修饰
abstract不能和private、static、final同用
(1)abstract不能和private同时修饰一个方法
(2)abstract不能和static同时修饰一个方法
(3)abstract不能和final同时修饰一个方法或类
final修饰符(不能继承、不能重写、只能赋值一次)
(1)final修饰的类,不能再被继承
(2)final修饰的方法,不能被子类重写
(3)final修饰的变量(包括成员变量和局部变量),只能赋值一次
接口特点(interface)
(1)接口命名与类相同,如果是public,则在整个项目可见,如果省略,则该接口只能在当前包可见
(2)可以定义常量,不能定义变量,接口中的属性都是全局静态常量(都会用public static final修饰),必须指定初始值
(3)所有方法都是抽象方法,接口中方法都会自动用public abstract修饰,即接口中只有全局抽象方法
(4)和抽象类一样,不能实例化,并且当中不能有构造方法
(5)接口之间可以通过extends实现继承关系,一个接口可以继承多个接口,但接口不能继承类
(6)接口的实现类必须实现接口的全部方法,否则必须定义为抽象类
一个类只能有一个直接父类,但可以通过implements实现多个接口。当类在继承父类的同时又实现了多个接口时,extends关键字必须位于implements关键字之前
Java的接口特指interface的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范。如:方法签名,数据格式,网络协议等
抽象类和接口对比
abstract class | Interface | |
---|---|---|
继承 | 只能extends一个class | 可以implements多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
(1)Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口
(2)接口也是数据类型,适用于向上转型和向下转型
(3)接口的所有方法都是抽象方法,接口不能定义实例字段(实例字段属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例字段)
(4)接口可以定义default方法(JDK >= 1.8)
面向对象
(1)封装:隐藏实现细节,使得代码模块化
(2)继承:扩展已存在的代码模块(类),它们的目的都是为了代码重用
(3)多态:除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性
耦合(避免蝴蝶效应)
耦合度(Coupling)是对模块间关联程度的度量。耦合的强弱取决与模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少
模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。降低模块间的耦合度能减少模块间的影响,防止对某一模块修改所引起的“牵一发动全身”的水波效应,保证系统设计顺利进行
聚合(内部关联程度)
聚合(Cohesion)是一个模块内部各个成分之间相互关联的程度
内部:说明聚合是一个模块内部的概念,所以可以称为内聚
模块:这个模块的概念可大可小,可以是一个系统,一个功能模块,一个类等。模块的成分,包括数据与方法
一个模块只完成一个独立的功能模块内部不存在与该功能无关的操作和数据
描述 | |
---|---|
耦合 | 模块之间关联的程度 |
耦合 | 模块内部的内向性,耦合关注模块之间的问题 |
封装(getter和setter)
(1)用private修饰符把类的细节与外界隔离起来,从而实现数据项和方法的隐藏,而要访问这些数据项和方法唯一的途径就是通过类本身,类才有资格调用它所拥有的资源(方法,数据项属性等等)。提高数据的安全性
(2)通过隐藏隔离,只允许外部对类做有限的访问,开发者可以自由的改变类的内部实现,而无需修改使用该类的那些程序。只要那些在类外部就能被调用的方法保持其外部特征不变,内部代码就可以自由改变,各取所需,利于分工
(3)提高了代码的重用性,封装成工具类以后能够减少很多繁琐的步骤
JavaBean
JavaBean是一种规范,也就是对类的要求。它要求Java类的成员变量提供getter/setter方法,这样的成员变量被称之为JavaBean属性
JavaBean还要求类必须提供仅有的无参构造器
public User() {
//…
}
主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中
(1)JavaBean是一种符合命名规范的class,它通过getter/setter来定义属性
(2)属性是一种通用的叫法,并非Java语法规定
(3)可以利用IDE快速生成getter和setter
(4)使用Introspector.getBeanInfo()可以获取属性列表
JavaBean规范(2点)
(1)若干private实例字段
(2)通过public方法来读写实例字段
通常把一组对应的读方法(getter)和写方法(setter)称为属性(property)
(1)只有getter的属性称为只读属性(read-only)
(2)只有setter的属性称为只写属性(write-only)
很明显,只读属性很常见,只写属性不常见,getter和setter也是一种数据封装的方法
读写方法名
(1)字段是xyz,那么读写方法名分别以get和set开头,并且后接大写字母开头的字段名Xyz,因此两个读写方法名分别是getXyz()和setXyz()
(2)boolean字段比较特殊,它的读方法一般命名为isXyz()、setChild()
封装实现
1.重写toString()方法:Shift+Alt+S+S(toString)
object类里的toString只是把字符串的直接打印,数字的要转化成字符再打印,而对象,则直接打印该对象的hash码。 如果在类中没有重写toString方法,默认使用的是Object的toString方法输出格式。这个时候,对象就会直接显示hash码。
如果在类中重写toString方法,那么输出格式就会按照我们定义的方式输出这个对象,因此可以这样理解:
重写toString是对对象在打印输出时候的一种格式化。这样做符合业务逻辑,显示结果人性化。
当print一个对象是需要这个对象在打印时展现的是什么形态就把toString重写成需要的返回形态
2.生成无参有参:Alt+/、Shift+Alt+S+O (生成有参)
3.生成getter/settter方法:Shift+Alt+S+R
4.重载(Overloading)
重载注意:
(1)在同一个类里、方法名相同
(2)参数列表(方法参数的个数或参数类型)不同
(3)与方法返回值和方法修饰符没有任何关系
(4)使用this调用自身的其他构造方法时,只能作为第一条语句
(5)构造方法没有返回值类型。如果有,则不是构造方法,而是和构造方法同名的成员方法
举个例子,String类提供了多个重载方法indexOf(),可以查找子串
方法 | 描述 |
---|---|
int indexOf(int ch) | 根据字符的Unicode码查找 |
int indexOf(String str) | 根据字符串查找 |
int indexOf(int ch, int formIndex) | 根据字符查找,但指定起始位置 |
int indexOf(String str, int formIndex) | 根据字符串查找,但指定起始位置 |
继承(extends)
继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力
Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类的功能
修饰符 子类 extends 父类() {
}
(1)修饰符如果是public,则该类在整个项目可见
(2)如果无public修饰符,则该类只能在当前包可见
(3)不可以使用private和protected修饰符
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
子类可以从父类继承哪些
(1)继承public和protected修饰的属性和方法,无论子类和父类是否在同一个包里
(2)继承默认权限修饰符(friendly)修饰的属性和方法,但子类和父类必须在同一个包里
(3)无法继承private修饰的属性和方法
(4)无法继承父类的构造方法
protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问
继承类构造方法问题
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
this.score = score;
}
}
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
运行上面的代码,会得到一个编译错误,大意是在Student的构造方法中,无法调用Person的构造方法。
这是因为在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();,所以,Student类的构造方法实际上是这样:编译器自动加上了super()。但是,Person类并没有无参数的构造方法,因此,编译失败。
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(); //自动调用父类的构造方法
this.score = score;
}
}
解决方法是调用Person类存在的某个构造方法
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); //调用父类的构造方法Person(String, int)
this.score = score;
}
}
如果父类没有默认的无参构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法
子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的
运行顺序
父类属性 --> 父类构造方法 --> 子类属性 --> 子类构造方法
祖类(Object类)
在Java中,所有的Java类都直接或间接地继承了java.lang.Object类,Object类是所有Java类的祖先,如果没使用extends,这个类直接继承Object类。否则间接继承Object类
Object类中的getClass()、notify()、notifyAll()、wait()等方法不能重写,因为这些方法定义为finally,其中重要方法getClass()、toString()、equals()
方法 | 描述 |
---|---|
protected Object clone() | 创建并返回此对象的副本 |
boolean equals(Object obj) | 指示一些其他对象是否等于此对象(内存地址) |
protected void finalize() | 当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用该对象 |
Class<?> getClass() | 返回此 Object 的运行时类 |
int hashCode() | 返回对象的哈希码值 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
String toString() | 返回对象的字符串表示形式 |
void wait(long timeout, int nanos) | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法,或者某些其他线程中断当前线程,或一定量的实时时间 |
重写或覆盖(overriding)
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)
(1)重写方法和被重写方法必须具有相同的方法名,具有相同的参数列表(相同方法名、参数)
(2)重写方法的返回值类型必须和被重写方法的返回值类型相同或是其子类(相同返回值)
(3)重写方法不能缩小被重写方法的访问权限(不能缩小访问权限)
Override和Overload不同的是,如果方法签名如果不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。
注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错
加上@Override可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错。但是@Override不是必需的
继承关系中的构造方法
(1)super代表对当前对象的直接父类对象的默认引用
(2)super必须出现在子类(子类的方法和构造方法)
(3)可以访问父类的成员。如:父类的属性、方法、构造方法
(4)注意访问权限的限制(无法通过super和private成员)
构造方法调用规则
(1)子类构造方法没有用super显示调用有参构造方法,也没有this显示调用自身的其他构造方法,系统会默认先调用父类的无参数构造方法
(2)子类的构造方法中有super显示调用父类的有参数过程方法,则执行父类的相应的构造方法,不执行父类无参数构造方法
(3)如果子类的构造方法通过this显示调用自身的其他构造方法,则在相应构造方法中应用以上两条规则
(4)如果存在多级继承关系,则在创建一个子类对象时,以上规则会多次项更高一级父类应用,一直到Object类
this和super使用规则
①构造方法中有(this和super)则只能是第一条
②一个构造方法不能同时有this和super
③类方法中不能出现this和super关键字
④在实例方法中,this和super语句不要求是第一条语句,可以共存
调用super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用
覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
方法 | 描述 |
---|---|
toString() | 把instance输出为String; |
equals() | 判断两个instance是否逻辑相等; |
hashCode() | 计算一个instance的哈希值。 |
在必要的情况下,可以覆写Object的这几个方法
class Person {
private String name;
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}
// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
区分继承和组合
在使用继承时,要注意逻辑一致性
class Book {
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这个Book类也有name字段,那么,能不能让Student继承自Book呢?
class Student extends Book {
protected int score;
}
显然,从逻辑上讲,这是不合理的,Student不应该从Book继承,而应该从Person继承
究其原因,是因为Student是Person的一种,它们是is关系,而Student并不是Book。实际上Student和Book的关系是has关系。
具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例:
class Student extends Person {
protected Book book;
protected int score;
}
继承是is关系,组合是has关系
多态(Polymorphic)
什么是多态?
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态
(1)多态的特性:针对某个类型的方法调用,其真正执行的方法取决于运行时期对象的实际类型的方法
(2)多态具有一个非常强大的功能:允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码
(3)当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法
为什么使用多态?
(1)实现代码的复用,避免代码的冗余
(2)减少代码之间的关联性,即耦合度,方便后期对代码的修改,功能的改善,不必牵一发而动全身,减少不必要的麻烦
特点
变量:编译看父类
方法:运行看子类
多态的好处
(1)可替换性(substitutability)
多态对已存在代码具有可替换性
例如:多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作
(2)可扩充性(extensibility)
多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能
例如:在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性
(3)接口性(interface-ability)
多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现
(4)灵活性(flexibility)
它在应用中体现了灵活多样的操作,提高了使用效率
(5)简化性(simplicity)
多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要
多态的作用?
Java多态性增加了软件的灵活性,可以为程序提供更好的可扩展性;同时多态也增加了代码的可重用性。Java实现运行时多态性主要通过重载、重写和接口三种主要实现方式
多态的实际意义:屏蔽不同子类的差异性实现通用的编程
多态存在的三个必要条件
(1)继承
(2)重写
(3)向上转型(父类引用指向子类对象)
多态表现形式
1.编译多态
主要是体现在方法重载,重载指的是同一个类中有多个同名方法,当方法的参数不同时,编译时就可以确定调用哪个方法,是同一个类中多态性的表现方式
2.运行多态
主要体现在继承性上的方法重写,Java中子类可以重写父类的方法,同样的方法在父类与子类中有着不同的表现形式。父类的引用可以指向子类对象,程序调用的方法在运行期才动态绑定,运行时才可以确定调用哪个方法,因此称之为运行时多态,是父类与子类之间多态性的表现方式
多态要注意的细节
- 子父类存在同名的成员变量时,访问的是父类的成员变量
- 子父类存在同名的非静态的成员函数时,访问的是子类的成员函数(重写)
- 子父类存在同名的静态的成员函数时,访问的是父类的成员函数
- 不能访问子类特有的成员
总结:多态情况下,子父类存在同名的成员时,访问的都是父类成员,除了在同名非静态函数时才是访问子类的
多态的效果
1.当父类的引用指向子类的对象时,该引用可以直接调用父类的成员方法,但是不可以直接调用子类的成员变量
2.对于父子类都拥有的非静态成员方法来说,编译阶段调用父类的,运行阶段调用子类的
3.对于父子类都拥有的静态成员方法来说,编译和运行阶段都调用父类的版本,与对象无关
虚函数
虚函数的存在是为了多态(final修饰)
Java中没有虚函数的概念,普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果Java中不希望某个函数具有虚函数特性,可以加上final关键字变成非虚函数
引用数据类型之间的转换
1.引用数据类型之间的转换分为:自动转换、强制转换
自动转换:主要指从小范围到大范围之间的转换,也就是子类向父类的转换。(Pet pc = new Cat() 这是自动类型转换)
强制转换:主要指从大范围到小范围之间的转换,也就是父类向子类的转换。((Cat)pc).getColor();父类到子类的转换
2.引用数据类型之间的转换必须发生在父子类之间,否则编译报错
3.拥有父子类关系后可以发生强制类型转换,若该引用真正指向的对象并不是目标类型的对象时,编译阶段ok,运行阶段发生类型转换异常
4.为了避免上述错误的发生,每次强转之前,应该使用instanceof进行判断
if(引用变量名 instanceof 目标类型){
// 判断引用变量指向的对象是否为目标类型
}
如果用多态,就是父类的引用指向子类,而父类的引用能直接访问父类的方法,若想访问子类的,需要加个强转
向上转型(upcasting)
子类创建的对象赋值给父类声明变量,则该对象称为上转型(upcasting),这个过程称为对象上转型,对应于数据类型转换中的自动类型转换
Person p = new Student();
注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object
向下转型(downcasting)
向上转型对象再强制转换为创建该对象的子类类型的对象称为向下转型(downcasting),即将上转型对象还原为子类对象,对应于数据类型转换中的强制类型转换
Person p1 = new Student(); // upcasting(向上), ok
Person p2 = new Person();
Student s1 = (Student) p1; // downcasting(向下),ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
Person类型p1实际指向Student实例,Person类型变量p2实际指向Person实例。在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException
注意:向下转型的时候必须先向上转型
①向上转型的对象才能够对象向下转型
②向上转型对象不能操作子类新增的成员变量,不能调用子类新增的方法,因此如果要使用子类新增的属性必须向下转型
instanceof关键字
instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类,返回boolean类型的值。如果一个引用变量为null,那么对任何instanceof的判断都为false
myobject instanceof exampleClass;
myobject:某个类的对象引用
exampleClass:某个类
判断myobject是否为exampleClass的类型或者其子类
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
// 利用instanceof,在向下转型前可以先判断
Person p = new Student();
if (p instanceof Student) { //判断成功才会向下转型
Student s = (Student ) p; // 一定会成功
}
多态实现
接口实现、抽象类、继承父类进行方法重写、同一个类中进行方法重载
1.同一个类中进行方法重载
方法的重写需参数不同、参数个数不同、返回值不同
public int init(){ }
public void init(String name, int i){ }
public void init(int i, String name){ }
2.接口回调
接口回调描述的是一种现象:接口声明的变量指向其实现类实例化的对象,那么该接口变量就可以调用接口中的抽象方法,编译时方法行为表现的是接口中的抽象方法,运行时方法行为表现的是接口实现类中重写该方法的行为特征,即为多态。
如果接口中新增加了方法,则需要修改所有实现此接口的实现方法
(1)接口中的变量会被隐式地指定为public static final变量
(2)接口中的方法都是抽象方法
(3)一个类可继承多个接口
(4)实现接口需实现接口中的所有方法
接口回调实例
// 接口
public interface IMammal {
void move();
}
public class Whale implements IMammal{
public void move(){
System.out.println("靠鱼鳍移动");
}
}
public class Test {
public static void main(String[] args) {
IMammal mammal = new Whale();
mammal.move();
}
}
运行结果:编译时是接口的行为特征,运行时是接口实现类的行为特征
3.子类继承实现抽象类方法
如果在抽象类中增加方法,底层所有继承此类的子类都不需要修改,如果需调用,则直接调用父类方法即可。
抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象,所以抽象类一个是在项目开始就得确定好的公共类
抽象类是实现多态的方法之一,一个类被abstract标记的类叫抽象类。当父类的方法不知道如何去实现或者要把某个类抽象出来的时候,可以考虑将父类写成抽象类,将方法写成抽象方法,抽象类的成员也必须标记abstract,即抽象类不一定包含抽象方法,但抽象方法所在的类一定是抽象类,并且不能有任何实现,也就是没有方法体,抽象成员的访问修饰符可以不写,如果写,不能是private,实现父类抽象方法的方法的参数和返回值都要一样;抽象类虽然有构造器,但不能实例化
abstract class Animal {
public abstract void bark();
public void test(){
System.out.println("抽象类的普通方法");
}
}
class Dog extends Animal {
@Override
public void bark(){
System.out.println()"狗狗会叫");
}
}
class Cat extends Animal{
@Override
public void bark() {
System.out.println("猫咪会叫");
}
}
public class Demo {
public static void main(String[] args) {
Animal a = new Dog();
a.bark();
Animal b = new Cat();
b.bark();
}
}
抽象类可以继承普通类,抽象类也可以继承抽象类,抽象类在继承抽象类的时候,可以实现父类的抽象方法,如果实现了父类的抽象方法,在子类的普通方法中就不用再实现该抽象方法了,如果没有实现,在普通类继承父类的抽象方法时,就要实现父类的所有抽象方法
public class Common{
public void commonMethod(){
System.out.println("普通类普通方法");
}
}
abstract class Animal extends Common {
public abstract void bark();
public void Test(){
System.out.println("抽象类的普通方法");
}
}
abstract class Dog extends Animal {
public abstract int run();
public void dogMethod(){
System.out.println("小狗的方法");
}
}
class Cat extends Dog {
@Override
public void bark() {
System.out.println("猫咪会叫");
}
@Override
public int run() {
System.out.println("跑");
return 0;
}
}
4.子类继承父类进行方法重写
子类继承父类重写父类方法
public class Parent {
public void fun() {
System.out.println("我是父母!");
}
}
public class Child extends Parent {
@Override
public void fun() {
//子类的重写
System.out.println("我是孩子!");
}
}
父类Pet做参数,子类Dog做实参
// Master主人类:
public void feed(Pet pet){
pet.eat();
}
// 测试类:
Dog dog = new Dog();
Master ms = new Master();
ms.feed(dog);
父类做类型,子类实例化(向上转型)
Parent p = new Child();
// 运行子类fun();方法
p.fun();
静态绑定和动态绑定
JVM方法调用的静态(static binding)和动态绑定机制(auto binding)
静态绑定
在编译阶段就能够确定调用哪个方法的方式,叫做静态绑定机制,new类调用方法就是静态绑定
动态绑定
在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,叫做动态绑定机制也是多态的体现
下面代码有三个概念多态、方法覆盖、方法重载
//被调用的父类
class Father{
public void f1(){
System.out.println("father-f1()");
}
public void f1(int i){
System.out.println("father-f1() para-int "+i);
}
}
//被调用的子类
class Son extends Father{
public void f1(){ //覆盖父类的方法
System.out.println("Son-f1()");
}
public void f1(char c){
System.out.println("Son-s1() para-char "+c);
}
}
public class AutoCall{
public static void main(String[] args){
Father father=new Son(); //多态
father.f1(); //打印结果: Son-f1()
}
}
应用场景
1.多态用于形式参数类型的时候,可以接受更多类型的数据
2.多态用于返回值类型的时候,可以返回更多类型的数据
多态实例
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return income * 0.1; // 税率10%
}
}
class Salary extends Income {
public Salary(double income) {
super(income);
}
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}
@Override
public double getTax() {
return 0;
}
}
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}
模块(Java 9)
主要是为了解决“依赖”这个问题。如果a.jar必须依赖另一个b.jar才能运行,那么应该给a.jar加点说明啥的,让程序在编译和运行的时候能自动定位到b.jar,这种自带“依赖关系”的class容器就是模块
为了表明Java模块化的决心,从Java 9开始,原有的Java标准库已经由一个单一巨大的rt.jar分拆成了几十个模块,这些模块以.jmod扩展名标识,可以在$JAVA_HOME/jmods目录下找到它们
这些.jmod文件每一个都是一个模块,模块名就是文件名
例如:模块java.base对应的文件就是java.base.jmod。模块之间的依赖关系已经被写入到模块内的module-info.class文件了。所有的模块都直接或间接地依赖java.base模块,只有java.base模块不依赖任何模块,它可以被看作是“根模块”,好比所有的类都是从Object直接或间接继承而来