美文网首页
JAVA 第三章 —— 继承

JAVA 第三章 —— 继承

作者: 定格r | 来源:发表于2019-03-22 17:57 被阅读0次

1. 继承

1.1继承的概念

继承是 java 面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

1.2类的继承格式
class 父类 {
}
 
class 子类 extends 父类 {
}

公共父类:

public class Animal { 
    private String name;  
    private int id; 
    public Animal(String myName, int myid) { 
        name = myName; 
        id = myid;
    } 
    public void eat(){ 
        System.out.println(name+"正在吃"); 
    }
    public void sleep(){
        System.out.println(name+"正在睡");
    }
    public void introduction() { 
        System.out.println("大家好!我是"         + id + "号" + name + "."); 
    } 
}

这个Animal类就可以作为一个父类,然后企鹅类和老鼠类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:

企鹅类:

public class Penguin extends Animal { 
    public Penguin(String myName, int myid) { 
        super(myName, myid);   //super 调用父类构造方法
    } 
}

老鼠类:

public class Mouse extends Animal { 
    public Mouse(String myName, int myid) { 
        super(myName, myid); //super 调用父类构造方法,并向其传参数
    } 
}
1.3继承类型

注意:需要注意的是 Java 不支持多继承,但支持多重继承。

image.png
1.4继承的特性
  • 子类拥有父类非 private 的属性、方法。

  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

  • 子类可以用自己的方式实现父类的方法。

  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。

  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

1.5 继承关键字

继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。

extends关键字

在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

public class Animal { 
    private String name;   
    private int id; 
    public Animal(String myName, String myid) { 
        //初始化属性值
    } 
    public void eat() {  //吃东西方法的具体实现  } 
    public void sleep() { //睡觉方法的具体实现  } 
} 
 
public class Penguin  extends  Animal{ 
}

implements关键字

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

public interface A {
    public void eat();
    public void sleep();
}
 
public interface B {
    public void show();
}
 
public class C implements A,B {
}

super 与 this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用。

class Animal {
  void eat() {
    System.out.println("animal : eat");
  }
}
class Dog extends Animal {
  void eat() {
    System.out.println("dog : eat");
  }
  void eatTest() {
    this.eat();   // this 调用自己的方法
    super.eat();  // super 调用父类方法
  }
}

final关键字

final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:
声明类:

final class 类名 {//类体}

声明方法:

修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}

注:实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final

2. Object:所有类的超类

2.1 概念

Obje类是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。
在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类。
可以使用类型为Object的变量指向任意类型的对象。
Object类是Java中唯一没有父类的类
Object类有一个默认构造方法pubilc Object(),在构造子类实例时,都会先调用这个默认构造方法。

2.2 Object中几个重要的方法:
  • equals() (判断两个对象是否相等,用于比较两个对象的内存地址)

1)基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 他们之间的比较,用双等号(==),比较的是他们的值。

2)复合数据类型(类) 当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。

  • hashCode()( 返回该对象的哈希码值)

hashCode 的常规协定是(重写equals()时,必须重写hashcode()并保证此协定):

    如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等
    如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同
    如果两个对象的hashcode值不等,则equals方法得到的结果必定为false
    如果两个对象的hashcode值相等,则equals方法得到的结果未知
  • toString()(返回该对象的字符串表示)

    Object类中的toString()方法会打印出类名和对象的内存位置。几乎每个类都会覆盖该方法,以便打印对该对象当前状态的表示。大多数(非全部)toString()方法都遵循如下格式:类名[字段名=值,字段名=值...],当然,子类应该定义自己的toString()方法。该方法是非常重要的调试工具,很多标准类库中的类都定义了toString()方法,以便程序员获得有用的调试信息

public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.toString());  //complete.Main@7852e922
        System.out.println(m);   //complete.Main@7852e922
    }

注意: java 是开源,源代码公开,,,

查看源代码方式:

方式一:按住 Ctrl 键,单机你需要看的源代码。
方式二:把光标移动到你需要查看代码处,按下 F3

为什么要查看源代码?

1.查看源代码可以了解别人是如何写出这个技术的,让我们了解的更加的深入。
2.吸收大牛思想。

toString() 方法有何作用?
重写 toString() 之后,我们直接输出一个对象的时候,就会输出符合我们所需求的格式数据


class person {
    String name;
    int age;

    public person(String name, int age) {
        this.name = name;
        this.age = age;
    }
//重写 toString() 方法
    public String toString() {             
        return "姓名:" + this.name + " 年龄:" + this.age;
    }
}
public class Main {

    public static void main(String[] args) {
        person p = new person("haha", 18);
        System.out.println(p);
    }
}

结果:

image.png

3. 泛型

3.1 泛型定义:

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

例如:

需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

答案:是可以使用 Java 泛型。

3.2 泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
public class Main {
    // 泛型方法 printArray
    public static <E> void printArray(E[] arry) {
        // 输出数组元素
        for (int i = 0; i < arry.length; i++) {
            System.out.printf("%s ", arry[i]);
        }
        System.out.println();
    }
    public static void main(String args[]) {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
        
        System.out.println("整型数组元素为:");
        printArray(intArray); // 传递一个整型数组

        System.out.println("\n双精度型数组元素为:");
        printArray(doubleArray); // 传递一个双精度型数组

        System.out.println("\n字符型数组元素为:");
        printArray(charArray); // 传递一个字符型数组
    }
}

结果:

image.png
3.3 泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

举例:

public class Box<T> {
   
  private T t;
 
  public void add(T t) {
    this.t = t;
  }
 
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

结果:


image.png

4. 对象包装器与自动打包

4.1 对象包装器

所有基本类型都有一个与之对应的类,例如,Integer类对应基本类型int。通常,这些类称为包装器(wrapping)

这些对象包装器拥有很鲜明的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前面六个类派生于公共的超类Number)。对象包装器类是不可变的,一旦构造了包装器,就不允许更改包装器里面的值。同时,对象包装器还是final,因此不能定义他们的子类。

4.2 自动打包

定义一个整形数组列表,尖括号里面的参数类型不允许是基本类型,也就是说不允许写成ArrayList<int>。这里就用到了Integer对象包装器类。我们可以声明一个Integer对象的数组列表。

         ArrayList<Integer> list = new ArrayList<Integer>;

当调用:

         list.add(3);

将自动变换成:

         list.add(new Integer(3));

这种变换称之自动打包(autoboxing)

相反的如果将Integer对象赋值给一个int值时,将会 自动第拆包 ,也就是说编译器将下列语句:

int n = list.get(i);

翻译成:

int n = list.get(i).intValue();

甚至在算术表达式中也能够自动的打包和拆包。例如,可以将自增操作符应用于一个包装器引用:

Integer n = 3;
n++;

编译器将自动第插入一条拆开对象包的指令,然后进行自增运算,最后再将结果打入对象包内。
很多情况下,容易有一种假象,即基本类型和他们的对象包装类是一样的,只是他们的相等性不同,==运算符也可以用于包装器对象,只不过检测的是对象是否指向同一个存储区域。

先看下面一段代码:

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出结果为:true,false。结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。下面这段代码是Integer的valueOf方法的具体实现:

public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

自动打包要求规范要求boolean、byte、char<=127,介于-128~127之间的short和int被包装到固定的对象中,否则创建一个新的对象。如上面的i1和i2初始化为100,对他们的比较的结果一定是成立的。

所以在比较两个包装器对象的值是否相等时调用equals方法就能避免出现false的情况。

最后说明一下,打包和拆包是编译器认可的,而不是虚拟机。编译器在生成类的字节码时插入必要的方法调用。虚拟机只是执行这些字节码。

5.参数数量可变的方法

可变的参数类型,也称为不定参数类型。英文缩写是varargus,还原一下就是variable argument type。通过它的名字可以很直接地看出来,这个方法在接收参数的时候,个数是不定的。
Java开始支持可变参数的方法,比如printf方法就是一个可变参数的方法。

我们也可以自定义可变参数的方法,具体语法为:

  返回类型 函数名(参数类型...参数名字)

原理:就是将接收的参数组装成一个临时数组,然后再处理临时数组中的数据。

示例代码:

public class Main {

    public static double max(double... values) { // //定义可变参数 values
        double largest = Double.MIN_VALUE;  ////次数为 Double 能表示的最小值
        
        for (double v : values) { // 相当于把values数组里的每一个值取出赋于v,直到结束
            
           largest = v > largest ? v : largest; // 取大值
        }
        
        return largest;
    }

    public static void main(String[] args) {
        double m = max(1.2, 3.4, 56.7, 345.45, 2, 5);
        System.out.println(m);
    }

}

结果:


image.png

这段代码接受任意个double类型的参数,从中寻找其中的最大值并在控制台输出。

要点:

  1. 在方法中定义可变参数后,我们可以像操作数组一样操作该参数;

  2. 如果该方法除了可变参数还有其它的参数,可变参数必须放到最后;

  3. 调用使用了可变参数的方法时:

    a. 可以不写参数,即传入空参;
    b. 可以直接在里边写入参数,参数间用逗号隔开;
    c. 可以传入一个数组;

6.枚举类

6.1 枚举的定义

这是在没有枚举类型时定义常量常见的方式

//使用普通方式定义日期常量
 
public class DayDemo {

    public static final int MONDAY =1;

    public static final int TUESDAY=2;

    public static final int WEDNESDAY=3;

    public static final int THURSDAY=4;

    public static final int FRIDAY=5;

    public static final int SATURDAY=6;

    public static final int SUNDAY=7;

}

上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足,如在类型安全和使用方便性上并没有多少好处,如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告,因此这种方式在枚举出现后并不提倡,现在我们利用枚举类型来重新定义上述的常量,同时也感受一把枚举定义的方式,如下定义周一到周日的常量

//枚举类型,使用关键字enum
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

相当简洁,在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。

枚举类型Day中分别定义了从周一到周日的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天。那么该如何使用呢?如下:

public class EnumDemo {

    public static void main(String[] args){
        //直接引用
        Day day =Day.MONDAY;
    }

}
//定义枚举类型
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

就像上述代码那样,直接引用枚举的值即可,这便是枚举类型的最简单模型。

6.2 枚举类的定义

枚举类和普通类有以下几个不同点:

1、枚举类不能指定继承的父类(因为继承了java.lang.Enum类),但是可以实现多个接口, 枚举类默认实现了Comparable接口和Serializable接口

2、枚举类的构造方法的访问权限只可为private

3、枚举类的实例必须显式列出。

6.3 枚举类的真正实现

举例:

public enum Season {
    SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
    
    private final String chinese;
    
    private Season(String chinese) {
        this.chinese = chinese;
    }

    public String getChinese() {
        return chinese;
    }
}

其实上述代码相当于:

public final class Season extends Enum<Season> {
    public static final Season SPRING;
    public static final Season SUMMER;
    public static final Season AUTUMN;
    public static final Season WINTER;
    private static final Season[] ENUM$VALUES;
    static {
        SPRING = new Season("SPRING", 0, "春天");
        SUMMER = new Season("SUMMER", 1, "夏天");
        AUTUMN = new Season("AUTUMN", 2, "秋天");
        WINTER = new Season("WINTER", 3, "冬天");
        ENUM$VALUES = new Season[]{SPRING, SUMMER, AUTUMN, WINTER}
    }
    
    private final String chinese;
    
    private Season(String name, int ordinal, String chinese) {
        super(name, ordinal);
        this.chinese = chinese;
    }

    public String getChinese() {
        return chinese;
    }
    
    public static Season[] values() {
        Season[] arr = new Session[ENUM$VALUES.length];
        System.arraycopy(ENUM$VALUES, 0, arr, 0, arr.length);
        return arr;
    }
    
    public static Season valueOf(String name) {
        return Enum.valueOf(Season.class, name);
    }
}

在编译过程中,编译器会对枚举类进行以下处理:

1、增加一个静态初始化块和一个静态数组,静态初始化块负责构造枚举类的实例并将其保存到数组中。
2、修改当前枚举类的构造方法,向构造方法前面增加两个参数:name, ordinal。分别代表枚举类的名称和初始化顺序,并且使用super调用父类java.lang.Enum的构造方法。
3、添加两个静态方法:
public static Season[] values()
该方法返回一个数组,包含了该枚举类所有的实例(并不是返回枚举类自己保存实例的数组,而是通过对其复制,以确保实例不被篡改)。
public static Season valueOf(String name)
该方法通过当前枚举类的名称返回对应的实例。比如valueOf(“SPRING”)返回Season.SPRING)。

7.反射

7.1 反射的概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

反射就是把java类中的各种成分映射成一个个的Java对象

例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

image.png

相关文章

  • Java继承

    一、Java继承详解 Java继承目录:1.为什么要”继承“?2.继承的类型2.1 单继承2.2 多重继承2.3 ...

  • JAVA 第三章 —— 继承

    1. 继承 1.1继承的概念 继承是 java 面向对象编程技术的一块基石,因为它允许创建分等级层次的类。 继...

  • Java继承

    Java继承 一、继承类型 Java不支持多继承,但是支持多重继承 二、继承的特性 子类拥有父类非 private...

  • JAVA语言第二课

    JAVA面向对象——四大特征 继承篇——extendsJava 继承继承的概念继承是java面向对象编程技术的...

  • java培训专家介绍java的特性之:继承

    java培训专家介绍java的特性之:继承 什么是继承?java特性继承又是什么?在线教育平台专家为你解答! 多个...

  • JAVA 核心笔记 || [7] 继承

    继承 被继承的类为 基类 或者 父类 类 只能单继承 不可同时继承多类 Animal.java Dog.java ...

  • 继承

    java中对于继承,java只支持单继承。java虽然不直接支持多继承,但是可实现多接口。 1:成员变量。当子父类...

  • 2017-12-29

    Java学习日记(4) 主要谈一下——继承extends 个 Tips : Java不像c++,Java是单继承(...

  • JAVA核心技术总结(五六章)继承&接口与内部类

    第五章 继承 在Java中,所有的继承都是公有继承,没有C++的私有继承和保护继承 Java同C++一样,子类不能...

  • Java初级笔记No.3(更新中)

    2018.7.26 1、Java中的继承 Java中使用extends关键字来说明继承关系。 2、继承的特性 ja...

网友评论

      本文标题:JAVA 第三章 —— 继承

      本文链接:https://www.haomeiwen.com/subject/rvwrvqtx.html