6、面向对象(下)

159 阅读14分钟

继承基础使用

class Person{}

class Student extends Person{}

子类可以通过继承,直接把父类中的方法拿到子类中使用,也可以定义自己的属性和方法

Java中只允许单继承,即一个子类一次只能继承一个父类

不过Java支持多层继承,不能多重继承

C extends B, B extends A √

C extends B, A  ×

父类又称为基类

子类又称为派生类

继承,更准确的含义是扩展,子类不仅可以保留父类原有的功能,还可以拥有更多的功能

在使用继承的时候,需要注意的是,子类无法直接访问父类中的私有成员

注意,子类对象的实例化,会先调用父类中的构造方法,再调用子类自己的构造方法

之所以先调用父类中的构造方法,就是要用父类的构造方法为父类中的属性初始化,表示先有父类实例,然后才能产生子类实例,实际上在子类的构造方法中隐含了一个super()的语法

方法的重写与重载

  • 方法重载 Overloading

    1)方法名称相同,参数的类型和个数不同

    2)对重载方法和原方法的权限没有要求

    3)发生在一个类中

  • 方法重写 Overriding

    1)方法名称、参数的类型、返回类型全部相同,方法体不同

    2)被重写的方法权限扩大

    3)发生在父子类中

super与this关键字

在子类中使用super.属性的形式访问父类中的属性,super.方法()的形式访问父类中的方法

  • super

    1)访问父类中的属性、方法

    2)调用父类的构造方法时,必须放在子类构造方法的首行(因为构造方法优先调用)

  • this

    1)访问本类中的属性、方法如果本类中没有该属性或方法,则会从父类中继续查找

    2)调用本类构造方法时,必须放在构造方法的首行(因为构造方法优先调用)

注意,this和super都可以调用构造方法,但是两者是不能同时使用,因为两者在调用构造方法时都必须放在首行

final关键字(终结器)

1)使用final声明的类不能有子类

2)使用final声明的方法不能被子类所重写

3)使用final声明的变量即成为常量,常量不可以修改

class A{
    private final String str = "AAA";

    str = "BBB";// 此处会报错 Cannot assign a value to final variable 'str'
}

另外,如果使用public static final声明一个变量,则此变量称为全局常量(公共、静态、不可改变)

抽象类 abstract

抽象类的作用是用作模板,其中的抽象方法由子类去实现

1)抽象类中至少有一个抽象方法

2)抽象类和抽象方法都要使用abstract关键字声明

3)抽象方法只用声明不需要实现,交给子类重写实现

4)抽象类必须被子类继承,子类如果不是抽象类,必须重写抽象类中全部的抽象方法

abstrcat class A{
    public abstract String getInfo();
}

// 抽象类只能被继承
class B extends A{
    @override
    public String getInfo(){

    }
}

可以这么理解,抽象类就是比普通方法多了一些抽象方法,且不能直接对象实例化,其他部分是一样的

注意,抽象类不能使用final关键字,因为抽象类必须由子类继承,由子类去重写其中的抽象方法

注意,抽象方法不要用private来修饰,否则子类无法重写

另外,抽象类中其实可以存在构造方法,用来初始化抽象类的属性,虽然抽象方法不能被实例化,但是子类在实例化时依旧会先对父类进行实例化

抽象内部类

如果抽象类的子类只有一个时,可以将它封装到抽象类的内部类中,从而隐藏这个无需知道的类,这在开发时很常用

abstract class A       //抽象类
{
    // abstract修饰的方法为抽象方法,无需实现方法体.但是必须被子类覆写
    abstract public void printData();
    
    // 需要在抽象类里隐藏的内部类
    static private class B extends A{
        private int Data=12;     
        public void printData(){
            System.out.println("B Data:"+this.Data); //打印内部类的Data值
        }
    }

    // 获取实例对象
    static public A getInstance(){
        //通过静态方法来获取要隐藏的静态抽象内部类  (静态方法里只能使用静态变量)
        return new B(); 
    }
}

public class Test{
    public static void main(String args[]){
        A a = A.getInstance();
        a.printData(); //等价于: A.getInstance().printData();
    }
}

接口类 interface

接口属于一种特殊的类,这个类里面只允许有抽象方法和全局常量public static final(该限制在JDK1.8之后被打破)

接口中也可以定义普通方法和静态方法

接口实际上只是表示一种操作标准,接口本身并没有操作能力,需要子类去实现这个操作能力

接口的几个原则

1)接口通过interface关键字来定义

2)一个子类如果需要实现接口的话,需要通过implements关键字,从而实现多继承

3)接口的子类如果不是个抽象类,则必须重写接口中的所有抽象方法

4)接口的子对象,可以通过向上转型进行实例化操作

5)规定接口由抽象方法和全局常量组成,所有在接口中方法和常量不写,默认也是abstract和public

interface A{
    public static final String HA = "hello";
    public abstract String getInfo();
}

// 开发时经常简写,但是仍然是全局常量和公共的抽象方法
interface A{
    String HA = "hello";
    String getInfo();
}

// 接口可以被实现可以被继承
class B implements A{
    String getInfo(){
        System.out.println("Hello");
    }
}

Java中限制单继承,但是可以多实现

class C implements A,B

如果一个类既要实现接口又要继承父类,则写法如下,先继承后实现

class C extends B implements A

另外,接口也可以继承

interface B
interface C extend B

多态

多态是面向对象中最重要的概念

主要有2种体现方式:

1、方法的多态性:重载、重写

2、对象的多态性:向上转型、向下转型

下面主要介绍对象的多态性

  • 向上转型(自动完成)

  • 向下转型(需要明确指出要转型的子类类型)

对象的多态(上下转型)

www.cnblogs.com/lifexy/p/10…

父子对象之间的转换分为了向上转型和向下转型

区别在于

向上转型,通过子类对象实例化父类对象,这种属于自动转换

向下转型,通过父类对象实例化子类对象,这种属于强制转换

向上转型

向上转型时,父类对象只能调用父类方法或者被子类重写后的方法,无法调用子类中单独的方法

class A {
         public void print() {
                  System.out.println("A:print");
         }
}

class B extends A {
         public void print() {        
                  System.out.println("B:print");
         }
}

public class Test{
         public static void main(String args[])
         {
                  A a = new B();          //通过子类去实例化父类
                  a.print();
         }
}

这里的a.print()调用的是子类B中的重写方法

因为我们通过子类B去实例化的,所以父类A中的print方法被子类B中的print重写了

这样做的意义是

当我们需要相同父类的多个对象调用某个方法时,通过向上转换后,则可以确定参数的统一

class A {
         public void print() {
                  System.out.println("A:print");
         }
}

class B extends A {
         public void print() {        
                  System.out.println("B:print");
         }
}

class C extends B {
         public void print() {        
                  System.out.println("C:print");
         }
}

public class Test{
        // 因为向上转型,这里方法参数可以统一为父类类型
         public static void func(A a)
         {
                  a.print();
         }

         public static void main(String args[])
         {
                  func(new B());  //等价于 A a =new B();
                  func(new C());  //等价于 A a =new C();
         }
}

输出结果

B:print

C:print

但是注意,两种不同的对象有相同的父类,作为统一的参数

向下转型

向下转型是为了通过父类强制转换为子类,从而来调用子类独有的方法(向下转型一般不会用)

class A {
         public void print() {
                System.out.println("A:print");
         }
}

class B extends A {
         public void print() {        
                System.out.println("B:print");
         }
         public void talk(){
         		System.out.println("B:talk");
         }
}

public class Test{
         public static void main(String args[])
         {
                  A a = new A();
                  B b = (B)a; // 向下转型
                  b.talk(); // 向上转型,调用子类独有的方法
         }
}

输出结果

B:talk

instanceof关键字

instanceof用于判断一个对象到底属于哪个类的实例,返回boolean

A a = new B();                 // 向上转型 (B类是A的子类)

a instanceof A;                // true
a instanceof B;                // true
a instanceof C;                // false

工厂设计模式(3种)

1、简单工厂

  • 实现类的共同接口
  • 具体类的实现类
  • 工厂类的实现

2、工厂方法

  • 实现类的共同接口
  • 具体的实现类
  • 抽象工厂(工厂的共有接口)
  • 具体工厂

3、抽象工厂(了解)

Object 类的9个常用方法,共12个

www.cnblogs.com/wsming/p/13…

Object是Java中所有类的基类,任何类都默认继承Object,其提供的方法主要有:

1、registerNatives() 向JVM注册native方法,仅了解

    private static native void registerNatives();
    static {
        registerNatives();
    }

2、getClass() 反射的一种方式

public final native Class<?> getClass();

类加载的第一阶段是将.class文件加载到内存中,并生成一个java.lang.Class对象的过程

getClass()方法就是获取这个对象,这是当前类的对象在运行时类的所有信息的集合

反射共有3种方式

  • 对象的getClass()
  • 类名.class
  • Class.forName()

3、 hashCode() 返回当前对象的hashCode值

public native int hashCode();

4、 equals() 比较对象的堆地址,String类中重写equals用于比较内容值

public boolean equals(Object obj) {
        return (this == obj);
    }

5、 clone() 此方法返回当前对象的一个副本(浅拷贝),使用前需要实现Cloneable接口

protected native Object clone() throws CloneNotSupportedException;

浅拷贝,拷贝的是引用

深拷贝,新开辟内存空间,进行值拷贝

6、toString() 默认输出当前类的全限定类名+@+十六进制的hashCode值(建议所有子类都重写toString方法,返回值为String字符串)

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

7、wait(long time) 阻塞当前线程,等待其他线程调用notify()将其唤醒(public final不可重写的方法)

    public final native void wait(long timeout) throws InterruptedException;

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    public final void wait() throws InterruptedException {
        wait(0);
    }

wait()、wait(long)、wait(long, int) 重载方法

8、notify()、notifyAll() 唤醒线程

    public final native void notify();

    public final native void notifyAll();

9、finalize() 此方法是在GC垃圾回收前,JVM会调用此方法来清理资源

    protected void finalize() throws Throwable { }

包装类以及包装类的应用

Java中的数据类型分为基本数据类型和引用数据类型

但是基本数据类型如何成为一个对象呢

为解决这个问题,需要将这8种基本类型包装成一个类的形式,即包装类

byte  --  Byte 

int -- Integer

float --  Float

double -- Double

short --  Short

long  --  Long

char -- Character

boolean  --  Boolean

上述的包装类中,其中Byte、Integer、Float、Double、Short、Long都属于Number类的子类

Number类本身提供一系列返回上述6种基本数据类型的操作

Character和Boolean属于Object的直接子类

装箱与拆箱

装箱:将一个基本数据类型变为包装类

拆箱:将一个包装类变为基本数据类型

装箱
Integer a = 5;
Integer a = Integer.valueOf(5);

拆箱
Integer b = new Integer(6);
int c = b.intValue();

valueOf() 和 xxxValue() 方法均来自于Number类

在JDK1.5之后,装箱和拆箱都自动实现

String类与Integer、Float类型互相转换

1、 String类转为Integer、Float类

在Integer和Float类中提供方法parseXX()

    public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s,10);
    }
    public static float parseFloat(String s) throws NumberFormatException {
        return FloatingDecimal.parseFloat(s);
    }
    String str1 = "111";

    int a = Integer.parseInt(str1);

    String str2 = "11.1";

    float b = Float.parseFloat(str2);

2、 Integer、Float类转为String类

    int a = 11;

    float b = 11.1;

    String str3 = String.valueOf(a);

    String str4 = String.valueOf(b);

注意这种转换,要求字符串必须是数字类型,否则转换报错

匿名内部类

就是没有名字的内部类

优点:不用创建子类

缺点:只能使用一次,若要多次使用还是老老实实创建子类

使用条件:必须存在继承或实现关系的时候才会使用,且匿名内部类没有名字,只能使用一次,必须重写父类或接口中的方法

作用:实现接口或重写父类方法,如果只用一次就不需要再写子类了

只能通过继承它的父类或实现一个接口来达到这一目的

public class AnnoInner {

    public static void main(String[] args) {
        // 匿名内部类重写父类方法
        new Animals(){
            public void eat(){
                System.out.println("我是匿名内部类");
            }
        }.eat();

        // 匿名内部类实现接口方法
        new Car(){
            public void price(){
                System.out.println("12万");
            }
        }.price();
    }
}


class Animals {
    public void eat() {
        System.out.println("动物也要吃饭");
    }
}

interface Car{
    public void price();
}

匿名内部类最常用的是作为实参传递

public class AnnoInner2 {

    public static void main(String[] args) {
        // 将匿名内部类作为参数传递
        say(new P(){
            public void talk(){
                System.out.println("不说话了");
            }
        });
    }

    public static void say(P p){
        p.talk();
    }
}

class P{
    public void talk(){
        System.out.println("人在说话");
    }
}

匿名内部类常见格式

new 父类构造器(参数列表)|实现接口()  
    {  
     //匿名内部类的类体部分  
    }

在使用匿名内部类的过程中,我们需要注意如下几点:

1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。

2、匿名内部类中是不能定义构造函数的。

3、匿名内部类中不能存在任何的静态成员变量和静态方法。

4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。

5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。


问题

重写与重载区别

方法重载 Overloading

1)方法名称相同,参数的类型和个数不同

2)对重载方法和原方法的权限没有要求

3)发生在一个类中

方法重写 Overriding

1)方法名称、参数的类型、返回类型全部相同,方法体不同

2)被重写的方法权限扩大

3)发生在父子类中

this与super区别

this

1)指向当前对象本身

2)形参与成员名字重名,用this来区分

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

3)引用本类的构造方法

class Person{
    private String name;
    private int age;
    
    public Person() {
    }
 
    public Person(String name) {
        this.name = name;
    }
    public Person(String name, int age) {
        this(name);
        this.age = age;
    }
}

super

super可以理解为指向自己父类对象的一个指针

1)指向当前对象的父类的引用(与this相似)

2)子类的成员变量和方法与父类中的同名时,用super区分

class Person{
    protected String name;
 
    public Person(String name) {
        this.name = name;
    }
 
}
 
class Student extends Person{
    private String name;
 
    public Student(String name, String name1) {
        super(name);
        this.name = name1;
    }
 
    public void getInfo(){
        System.out.println(this.name);      //Child
        System.out.println(super.name);     //Father
    }
 
}

3)引用父类的构造方法

super 调用父类中的某一个构造方法,一般是无参构造方法

this与super的区分:

1)this和super不能出现在同一个构造方法中

2)this()和super()必须放在构造方法的第一行

3)this和super类似,super()是调用父类构造方法,this()是调用本类自己的构造方法

抽象类与接口的区别

接口是一种行为约束,只能控制行为的有无,但是对如何实现没有限制

抽象是一种模板,为了代码复用,当不同的类有很多相同的成员

向上转型、向下转型

向上转型,子类实例化父类对象,自动转换,只能调用父类中被子类重写的方法,而子类中的单独方法则是无法调用的

作用:统一参数,当一个方法的参数为父类对象时,其子类对象都可以作为参数

向下转型,父类强制转换为子类,只能调用子类独有的方法(实际开发很少使用)

native

native是与C++联合开发的时候用的!java自己开发不用的!