目录
1.7实例代码块与静态代码块 和构造方法的(在继承环境下的运行顺序)
1.继承
在现实生活中继承有继承财产,而在java代码中的继承就是继承代码。
现在看一个例子
package 继承;
public class Dog {
public String name;
public int age;
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
package 继承;
public class Cat {
public String name;
public int age;
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
会发现出现了代码重复,虽然在不同类中
而我们如果把相同的代码放进一个单独的类中,在使用继承的方法,就完美的解决了这个问题。
1.1继承的概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性 的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。
上述图示中,
Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的 子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
1.2继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字
父类
package 继承;
public class Animal {
public String name ;
public int age;
}
子类
package 继承;
public class Dog extends Animal {
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
package 继承;
public class Cat extends Animal{
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
测试类
package 继承;
public class Test {
public static void main(String[] args) {
Dog dog =new Dog();
dog.name="旺财";
dog.age=11;
dog.eat();
Cat cat =new Cat();
cat.name="咪咪";
cat.age=12;
cat.eat();
}
}
结果
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
1.3父类成员访问
在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?
1.3.1子类中访问父类的成员变量
1. 子类和父类不存在同名成员变量
public class Base {
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a = 10; // 访问从父类中继承下来的a
b = 20; // 访问从父类中继承下来的b
c = 30; // 访问子类自己的c
}
}
2. 子类和父类成员变量同名
public class Base {
int a;
int b;
int c;
}
/
public class Derived extends Base{
int a; // 与父类中成员a同名,且类型相同
char b; // 与父类中成员b同名,但类型不同
public void method(){
a = 100; // 访问父类继承的a,还是子类自己新增的a?
b = 101; // 访问父类继承的b,还是子类自己新增的b?
c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
}
}
在子类方法中 或者 通过子类对象访问成员时
1.如果访问的成员变量子类中有,优先访问自己的成员变量。
2.如果访问的成员变量子类中无,则访问父类继承下来的,
3.如果父类也没有定义,则编译报错。 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
1.3.2子类中访问父类的成员方法
1.成员方法名字不同
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
}
public class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodB(); // 访问子类自己的methodB()
methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
}
}
2.成员方法名相同的时候
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
public void methodA(int a) {
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
}
}
1,通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。
2,通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错
1.4super关键字
super关键字,该关键字主要作用:在子类方法中访问父 类的成员
public class Base {
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
// 与父类中methodA()构成重载
public void methodA(int a) {
System.out.println("Derived中的method()方法");
}
// 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
a = 100; // 等价于: this.a = 100;
b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从基类继承下来的部分
super.a = 200;
super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
super.methodB(); // 访问基类的methodB()
【注意事项】
1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法。
1.5子类构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base{
public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
结果打印:
Base()
Derived()
子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构 造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
1.6 super与this
相同点
1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成 员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
1.7实例代码块与静态代码块 和构造方法的(在继承环境下的运行顺序)
1.7.1回顾之前没有继承环境下的运行顺序
package 继承;
class Person {
public String name;
public int age;
public Person (String name,int age){
this.name=name;
this.age=age;
System.out.println("这个是构造方法的执行");
}
{
System.out.println("这是实例代码块的执行");
}
static
{
System.out.println("这个是静态代码块的执行");
}
}
public class Test {
public static void main(String[] args) {
// Dog dog =new Dog();
// dog.name="旺财";
// dog.age=11;
// dog.eat();
// Cat cat =new Cat();
// cat.name="咪咪";
// cat.age=12;
// cat.eat();
Person person=new Person("wo",55);
System.out.println("----------------------");
Person person1=new Person("fskd",5);
}
}
执行结果
注意;
1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
1.7.2继承环境下的运行顺序
父类
package 继承;
public class Animal {
public String name ;
public int age;
public Animal (String name,int age){
this.name=name;
this.age=age;
System.out.println("父类 这个是构造方法的执行");
}
{
System.out.println("父类 这是实例代码块的执行");
}
static
{
System.out.println("父类 这个是静态代码块的执行");
}
}
子类
package 继承;
class Person extends Animal {
public String name;
public int age;
public Person (String name,int age){
super(name,age);
System.out.println("子类 这个是构造方法的执行");
}
{
System.out.println("子类 这是实例代码块的执行");
}
static
{
System.out.println("子类 这个是静态代码块的执行");
}
}
public class Test {
public static void main(String[] args) {
// Dog dog =new Dog();
// dog.name="旺财";
// dog.age=11;
// dog.eat();
// Cat cat =new Cat();
// cat.name="咪咪";
// cat.age=12;
// cat.eat();
Person person=new Person("wo",55);
System.out.println("----------------------");
Person person1=new Person("fskd",5);
}
}
执行结果
通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,子类和父类的
1.8protected关键字
// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一个包中的子类
public class D extends B{
public void method(){
// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
super.c = 30; // 父类中public成员在相同包子类中可以直接访问
super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
// extend02包中
// 不同包中的子类
public class C extends B {
public void method(){
// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
// extend02包中
// 不同包中的类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}
1.9final关键字
final关键可以用来修饰变量、成员方法以及类。
1.9.1修饰字段或变量,表示常量(即不可以被修改)
1.9.2修饰类(即类就不可以被继承)
我们平时用的字符串就是用Sreing修饰的,所以不能被改变
1.9.3修饰方法(既不能进行方法的重写)
1.10继承方式
1.单继承
2.多层继承
3.不同类继承一个类
4.多继承·(不支持)
2.多态
2.1多态实现的条件
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
父类
package 继承;
public class Animal {
public String name ;
public int age;
public Animal (String name,int age){
this.name=name;
this.age=age;
// System.out.println("父类 这个是构造方法的执行");
}
// {
// System.out.println("父类 这是实例代码块的执行");
// }
// static
// {
// System.out.println("父类 这个是静态代码块的执行");
// }
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
Dog子类
package 继承;
//package 继承;
public class Dog extends Animal {
public Dog (String name,int age){
super(name,age);
}
public void eat(){
System.out.println(this.name+"汪汪叫");
}
}
// int a;
// public void eat() {
// System.out.println(this.name + "正在吃饭");
// this.a=77;
// super.a=88;
// }
// //被static修饰的方法,是属于静态方法
// //this 和super一样都是依赖于对象,但是static修饰后属于类
// public static void add () {
// //this.a;
// //super.a;
//
// }
//
//
//}
Cat子类
package 继承;
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println(this.name + "喵喵叫");
}
}
// public void eat(){
// System.out.println(this.name+"正在吃饭");
//
// }
//}
teat测试类
package 继承;
class Person extends Animal {
public String name;
public int age;
public Person(String name, int age) {
super(name, age);
// System.out.println("子类 这个是构造方法的执行");
}
// {
// System.out.println("子类 这是实例代码块的执行");
// }
// static
// {
// System.out.println("子类 这个是静态代码块的执行");
// }
}
public class Test {
// public static Animal fun(){
// Cat cat=new Cat("s",5);
// return cat;
// }
// public static void fun(Animal animal){
//
// }
public static void main(String[] args) {
//先进行向上转型
Animal animal=new Dog("旺财",5);
animal.eat();
Animal animal1=new Cat("喵喵",8);
animal1.eat();
}
}
执行结果
2.2方法的重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。
方法重写的规则
1.子类在重写父类的方法时一般必须跟父类的方法一样即(方法名 参数列表 返回值类型与父类的方法一样)
2.被重写的方法返回值类型可以不一样,但一定要有父子关系
3.访问权限不能比被重写的父类的方法的访问权限还要低,例如父类中的访问权限为public时
子类中的访问权限就不能是protected
4.父类被static,private,声明和构造方法都不能被重写
5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.
重载于重写的区别
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
2.3向上转型与向下转型
2.3.1向上转型
三种方式
1.直接赋值
2.方法传参
3.方法返回
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
2.3.2向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象即可,即向下转换
package 继承;
class Person extends Animal {
public String name;
public int age;
public Person(String name, int age) {
super(name, age);
// System.out.println("子类 这个是构造方法的执行");
}
// {
// System.out.println("子类 这是实例代码块的执行");
// }
// static
// {
// System.out.println("子类 这个是静态代码块的执行");
// }
}
public class Test {
// public static Animal fun(){
// Cat cat=new Cat("s",5);
// return cat;
// }
// public static void fun(Animal animal){
//
// }
public static void main(String[] args) {
//先进行向上转型
Animal animal=new Dog("旺财",5);
animal.eat();
Animal animal1=new Cat("喵喵",8);
animal1.eat();
//这是调用子类中特有的方法就会报错
// animal.brea();
//这时就可以使用向下转型
Dog dog=(Dog)animal;
dog.brea();
//但是向下转型不能乱转 例如
//无法正常还原,因为向上转型的时候时由子类Dog;
// Cat cat=(Cat)animal;
//向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入
//了 instanceof ,如果该表达式为true,则可以安全转换。
}
}
运行结果
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。
package 继承;
class Person extends Animal {
public String name;
public int age;
public Person(String name, int age) {
super(name, age);
// System.out.println("子类 这个是构造方法的执行");
}
// {
// System.out.println("子类 这是实例代码块的执行");
// }
// static
// {
// System.out.println("子类 这个是静态代码块的执行");
// }
}
public class Test {
// public static Animal fun(){
// Cat cat=new Cat("s",5);
// return cat;
// }
// public static void fun(Animal animal){
//
// }
public static void main(String[] args) {
//先进行向上转型
Animal animal=new Dog("旺财",5);
animal.eat();
Animal animal1=new Cat("喵喵",8);
animal1.eat();
//这是调用子类中特有的方法就会报错
// animal.brea();
//这时就可以使用向下转型
//Dog dog=(Dog)animal;
//dog.brea();
//但是向下转型不能乱转 例如
//无法正常还原,因为向上转型的时候时由子类Dog;
// Cat cat=(Cat)animal;
//向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入
//了 instanceof ,如果该表达式为true,则可以安全转换。
if(animal instanceof Dog){
Dog dog=(Dog)animal;
dog.brea();
}
if(animal instanceof Cat){
Cat cat=(Cat)animal;
cat.fun();
}
}
}
2.4避免在构造方法中调用方法的重写
一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果
D.func() 0
构造 D 对象的同时, 会调用 B 的构造方法.
B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func 此时 D 对象自身还没有构造,
此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
3.导入包中的类
3.1java中自带的包
Java 中已经提供了很多现成的类供我们使用. 例如Date类:
1.可以使用 java.util.Date 导入 java.util 这个包中的 Date 类.
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
2.但是这种写法比较麻烦一些, 可以使用 import语句导入包
mport java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
3.如果需要使用 java.util 中的其他类, 可以使用 import java.util.*
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
更建议显式的指定要导入的类名. 否则还是容易出现冲突
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
改成如下情况
在这种情况下需要使用完整的类名
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
可以使用import static导入包中静态的方法和字段
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
3.2自定义包
Computer类位于com.bit.demo1包中,TestComputer位置com.bit.demo2包中:
package com.bit.demo1;
public class Computer {
private String cpu; // cpu
private String memory; // 内存
public String screen; // 屏幕
String brand; // 品牌
public Computer(String brand, String cpu, String memory, String screen) {
this.brand = brand;
this.cpu = cpu;
this.memory = memory;
this.screen = screen;
}
public void Boot(){
System.out.println("开机~~~");
}
public void PowerOff(){
System.out.println("关机~~~");
}
public void SurfInternet(){
System.out.println("上网~~~");
}
///
package com.bite.demo2;
import com.bite.demo1.Computer;
public class TestComputer {
public static void main(String[] args) {
Computer p = new Computer("HW", "i7", "8G", "13*14");
System.out.println(p.screen);
// System.out.println(p.cpu); // 报错:cup是私有的,不允许被其他类访问
// System.out.println(p.brand); // 报错:brand是default,不允许被其他包中的类访问
}
}
// 注意:如果去掉Computer类之前的public修饰符,代码也会编译失败
注意:导入自定义包中的类是不同的