面向对象
1 继承
1.1 继承概述
面向对象的三大特征之一:封装、继承、多态
如果一些类中的属性和方法是重复的,可以将这些重复的属性和代码提取到一个新的类中,利用extends关键字让原来的类和新的类产生联系。这种联系称之为继承。提取出来的新的类是父类(超类/基类),原来的类是子类(派生类)。子类通过继承父类可以使用父类中的一部分的方法和属性。
注意:子类通过继承可以继承父类全部的属性和方法,但是只有一部分属性和方法对子类可见。
继承的好处:提高代码的复用率
注意:Java是单继承的。有多层继承,但是没有多继承。
单继承和多继承的比较:多继承在代码的复用性上要优于单继承,但是在调用方法的时候会产生混淆。
Java中的顶级父类是Object
1.2 权限修饰符
权限修饰符就是来限定成员的使用的范围。
本类中 | 子类中 | 同包类中 | 其他类中 | |
---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
default | 可以 | 同包子类可以 | 可以 | 不可以 |
private | 可以 | 不可以 | 不可以 | 不可以 |
1.3 super
super可以在子类中调用父类的方法和属性
super() 访问父类对象的构造方法 必须在方法体中的有效代码第一行
super.成员方法() 访问父类对象的方法
super.成员变量 访问父类对象的成员变量
public Dog(){
// 调用父类的无参构造方法
super();
System.out.println("dog构造方法被执行了");
}
public void dogEat(){
// 调用父类对象的eat方法 super相当于父类对象
super.eat();
System.out.println("小狗啃骨头");
super.name = "";
}
在构造方法中默认会调用父类的无参构造,如果父类没有无参构造,就会编译不通过。父类可以提供无参构造,或者手动调用父类的有参构造。
public Dog(){
// 如果没有写super(参数),系统会默认提供一个super(),调用父类的无参构造,如果父类没有无参构造,编译不通过
super("佩奇");
// 调用父类的无参构造方法 必须在方法体中的有效代码第一行
System.out.println("dog构造方法被执行了");
}
super()是调用父类对象的构造方法,this()是调用当前对象的构造方法,它们都要求在方法体的有效代码第一行。所有不能共存。
public Dog(){
// 如果没有写super(参数),系统会默认提供一个super(),调用父类的无参构造,如果父类没有无参构造,编译不通过
super("佩奇");
// 调用父类的无参构造方法 必须在方法体中的有效代码第一行
System.out.println("dog构造方法被执行了");
}
public Dog(String age){
this();
}
1.4 重写
- 概述
在父子类中存在方法签名(方法的名字和参数列表类型)相同的非静态方法,称之为方法的重写,也称之为覆盖。
调用时执行的是子类的方法。
-
方法的重写遵循五个原则
- 方法签名一致
- 如果父类中的方法的返回值类型是基本数据类型和void,那么子类在重写方法的时候返回值类型必须要保持一致。
- 子类重写的方法的权限修饰符的范围要大于等于父类对应方法的权限修饰符范围
public class Proffession { protected void work(){ System.out.println("在工作中..."); } } class Doctor extends Proffession{ // 方法的重写/覆盖 public void work(){ System.out.println("医生在治病救人"); } }
- 如果父类方法的返回值类型是引用数据类型,那么子类重写的方法的返回值类型要么与父类方法返回值类型一致,要么子类方法的返回值类型是父类方法返回值类型的子类
class A{}
class B extends A{}
class C{
public A m(){
return null;
}
}
class D extends C{
public B m(){
return null;
}
}
- 以后将异常时再说
2 多态
多态是继封装、继承之后,面向对象的第三大特性。
Java作为面向对象的语言,可以描述一个事物的多种形态。比如Student类继承Person类,一个Student对象既是Student,也是Person.
多态的体现形式:
编译时多态:方法的重载
运行时多态:方法的重写、向上转型
向上转型的使用
public class Pet {
String name;
public void eat(){
System.out.println("mixi");
}
}
public class Cat extends Pet {
String color;
public void miao(){
System.out.println("一起学猫叫~");
}
public void eat() {
System.out.println("猫吃小鱼");
}
}
public static void main(String[] args) {
// 向上转型 父类 对象名 = new 子类();
/**
* 用父类来声明对象,用子类来创建对象
* 在使用向上造型创建对象的时候
* 编译期间只会检查声明类和创建类之间是否有继承关系
* 而并不关心具体是哪一个子类
* 到了运行的时候才会确定具体的子类
* 编译期不确定子类,所以只能按照父类的规定来编译
* 等到运行期间确定子类,使用子类的对象来运行
*
* 编译看左边,运行看右边
*/
Pet pet = new Cat();
pet.eat();
// ClassCastException 类型不匹配异常
Dog cat = (Dog) pet;
}
public static void test(Pet pet){
Cat cat = (Cat) pet;
cat.color = "红色";
System.out.println(cat.color);
}
如果左边类型是右边类型的子类,那么可以进行强转。这样就可以使用子类中的属性和方法
public static void test(Pet pet){
// 子类 = (子类)父类;
Cat cat = (Cat) pet;
cat.color = "红色";
System.out.println(cat.color);
}
思考:
- 为什么子类重写的方法的权限修饰符的范围要大于等于父类对应方法的权限修饰符范围?
public static void main(String[] args) {
/**
* a的对象的声明类是A类,所以a对象能够干什么看的是A类,A类
* 告诉a对象有一个m方法可以使用,而且m方法可以在任何地方使用
*/
A a = new B();
/**
* a对象本质上是B类产生的对象,所以m方法执行时候要看B类,B类的m方法不能在任何地方使用 --产生了冲突
*/
a.m();
}
class A{
public void m(){}
}
class B extends A{
public void m() {
}
}
- 为什么 父类方法的返回值类型是引用数据类型,那么子类重写的方法的返回值类型要么与父类方法返回值类型一致,要么子类方法的返回值类型是父类方法返回值类型的子类?
3 static关键字
static是一个修饰符,可以修饰变量、方法、代码块、内部类
3.1 静态变量
static修饰的变量称之为静态变量。静态变量是随着类的加载而加载到方法区的,在方法区中被赋予了默认值。静态变量是先于对象而出现的。静态变量是属于类的,而不是属于对象的,是被多个对象共享的。该类产生的所有的对象都是使用同一个静态变量,每一个对象存储的都是静态变量的地址。
静态变量也称之为类变量,可以通过类名来调用。
使用静态变量的格式
类名.静态变量 推荐
对象名.静态变量 不推荐
public class Person {
// 成员变量
// 姓名
String name;
// 年龄
int age;
// 功夫 -- 静态变量
static String kongfu;
public String toStr(){
return name + "\t" + age + "\t" + kongfu;
}
}
public static void main(String[] args) {
// 使用静态变量
Person.kongfu = "葵花宝典";
}
注意点:
- 类是加载到方法区的
- 类是在第一次被使用的时候才会放到方法区中
- 类只加载一次
思考:
- 静态变量能否定义到构造方法中?
不能.静态变量在类加载时加载到方法区并且初始化,构造方法在创建对象时执行。静态变量存储在方法区,构造方法在栈内存中执行,对应创建出来的对象在堆内存。
静态变量能否定义在成员方法中?
不能
3.2 静态方法
static修饰的方法称之为静态方法。静态方法在类加载的时候加载到方法区,并没有执行而是存储在方法区。在方法被调用的时候到栈内存中执行。静态方法本身也是先于对象存在的,所以习惯上是通过类名来调用静态方法。静态方法一般用于后面使用的工具类中。
格式:
类名.静态方法();
思考:
- 静态方法能够使用this/super?
不可以。this代表当前对象,super代表父类对象,都是晚于静态方法出现的。当使用类名.静态方法时,对象还没有出现。
- 静态方法中能否直接使用本来中的非静态方法?
不可以,因为非静态方法需要对象调用. e
eg:
eat()相当于 this.eat();
- 静态可以被继承吗?
可以
public class StaticDemo {
public static void main(String[] args) {
B.m();
}
}
class A{
public static void m(){
System.out.println("A m...");
}
}
class B extends A{
}
- 静态方法可以被重写吗?
不可以.
父子类中可以存在方法签名一致的静态方法,这种情况称之为隐藏(hide)
隐藏也适用于重写的五个原则。
public class StaticDemo {
public static void main(String[] args) {
// 向上转型
A a = new B();
a.m();
}
}
class A{
public static void m(){
System.out.println("A m...");
}
}
class B extends A{
public static void m(){
System.out.println("B m...");
}
}
课堂练习:统计一个类所创建的对象的个数
public class A {
static int i = 0;
// 构造代码块 在每一次创建对象时都会先于构造方法执行
{
i++;
}
}
3.3 静态代码块
用static修饰的{}就称之为静态代码块,在类中方法外定义。这个代码块只会在类加载的时候执行一次。
经常在静态代码块中做一些数据的初始化操作。静态代码块在栈内存中执行。
public class B {
// 静态代码块 在类加载到方法区时执行 因为类只加载一次,所以静态代码块只会执行一次
static {
System.out.println("静态代码块执行了");
}
}
静态代码块执行顺序先于构造代码块,构造代码块的执行顺序先于构造方法
3.4 执行顺序问题
public class TestDemo {
public static void main(String[] args) {
new SA();
}
}
class SA{
static {
System.out.println("A1");
}
{
System.out.println("A2");
}
public SA(){
System.out.println("A3");
}
}
顺序是A1 A2 A3
public class TestDemo {
public static void main(String[] args) {
new SA();
new SA();// A1 A2 A3 A2 A3
}
}
顺序是A1 A2 A3 A2 A3
public static void main(String[] args) {
// 先加载父类,然后加载子类
// 调用父类构造方法,再去创建子类对象
new SB();// A1 B1 A2 A3 B2 B3
}
}
class SA{
static {
System.out.println("A1");
}
{
System.out.println("A2");
}
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
public class TestDemo {
public static void main(String[] args) {
// 先加载父类,然后加载子类
// 调用父类构造方法,再去创建子类对象
new SB();// A1 C B1 A2 A3 B2 B3
}
}
class SA{
static {
System.out.println("A1");
}
{
System.out.println("A2");
}
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println("C");
}
}
public class TestDemo {
public static void main(String[] args) {
// 先加载父类,然后加载子类
// 调用父类构造方法,再去创建子类对象
// 这个情况SD d只是声明,根本没有创建对象,d的默认值就是null
new SB();// A1 C B1 A2 A3 B2 B3
}
}
class SA{
SD d;
static {
System.out.println("A1");
}
{
System.out.println("A2");
}
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println("C");
}
}
class SD extends SC{
public SD(){
System.out.println("D");
}
}
public class TestDemo {
public static void main(String[] args) {
// 先加载父类,然后加载子类
// 调用父类构造方法,再去创建子类对象
new SB();// A1 C B1 A2 C D A3 B2 B3
}
}
class SA{
SD d;
static {
System.out.println("A1");
}
{
System.out.println("A2");
d = new SD();
}
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println("C");
}
}
class SD extends SC{
public SD(){
System.out.println("D");
}
}
总结:执行顺序 父类静态 -->子类静态 -->父类非静态 -->子类非静态
1 C B1 A2 C D A3 B2 B3
}
}
class SA{
SD d;
static {
System.out.println(“A1”);
}
{
System.out.println("A2");
d = new SD();
}
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println(“B1”);
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println(“C”);
}
}
class SD extends SC{
public SD(){
System.out.println(“D”);
}
}
总结:执行顺序 父类静态 -->子类静态 -->父类非静态 -->子类非静态