JavaBase-2-Object-oriented

继承

关键字:

  1. extends:java 不支持多继承(但是支持多重继承),即一个类继承两个或以上的父类。
  2. implements:可以变相使java实现多继承的特性,即一个类继承多个接口
  3. this 访问本身的类方法
  4. super 访问父类的方法,父类指针,一般在构造器 中使用用于调用父类的构造器方法,用于初始化构造。构造器方法一般可能会有有参数的,无参数的等等。
  5. final 定义最终类,该类不能被继承,或者用于声明类中的一个方法(类没有用 final,但是类中的方法final)表明这个方法不允许子类重写。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.duanyao.hello;

public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello wolrd");
}

// extends 用法
public class Animal{
private String name;
private int id;
public Animal (String name,int id){}
public void setName(String name){}
public void setId(int id){}
public int getId() {
return id;
}
public String getName() {
return name;
}
}

public class Dog extends Animal{
private String color;
public Dog(String name,int id,String color){
super(name,id);
this.color=color;
}
public void setColor(String color){}
public String getColor(){
return color;
}
}
// implements 用法
public interface A{
void eat();
void sleep();
}
public interface B{
void run();
}
public class C implements A,B{
@Override
public void eat() {}
@Override
public void sleep() {}
@Override
public void run() {}
}
}

重写(Override)与重载(Overload)

重写:子类定义了与父类一样名称、参数、返回值的方法。

重载:一个类中,方法名字相同,参数一定不同,返回值可以不同的几个方法。

重写

一般是需要 @override 注解的,但是菜鸟教程里面没有提到。

重写规则:

  1. 返回类型可以与父类不同,但是必须是父类返回值的派生类
  2. 访问权限不能比父类中的方法权限更低,总之一定不能做更严格的限制,可以降低限制。
  3. 声明为 final的方法不能重写
  4. 声明为 static 的方法不能重写,但是可以再次声明
  5. 声明为 private 的方法子类没继承,自然谈不上重写
  6. ……
  7. 重写方法能够抛出非强制性异常、不能抛出新的强制性异常、不能抛出更广泛的强制性异常。总之,可以减少或者删除,一定不能抛出更广或者新的异常

重载

没什么好说的,基本上和 C++ 一样。

多态

多态实现方法

  1. 重写
  2. 接口
  3. 抽象类与抽象方法

多态: 同一个方法/接口,在不同的条件下执行不同的操作。

多态优点:消除类型之间的耦合、替换、扩充、接口、灵活、简化

存在的必要条件:继承、重写、父类引用指向子类对象 Parent p = new child();

使用多态调用方法时,先检查父类中是否有这个方法,没有报编译错误,有则调用子类的同名方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法

Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}

public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}

abstract class Animal {
abstract void eat();
}

class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}

class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}

上面的代码中,15-20 行为什么不使用 a.work() 代替呢?因为 Animal 父类没有work 方法,这是子类扩充出来的。

所以:

  • 我们可以使用 父类指针指向子类对象,并且调用子类中重写父类的方法。
  • 但是我们不能直接使用 父类指针指向子类对象后,使用父类指针调用子类中独有的方法。
  • 需要向下转型为子类指针,这时我们需要使用 instanceof 来判断到底是父类的哪个扩展子类,以决定将父类指针向下转型为哪种子类的指针,进而才能调用对应子类中独有的方法。

说明:

  1. 本类指针调用本类重写方法:编译器在编译时找到方法,执行过程中 JVM 调用本类中的方法
  2. 父类指针调用子类的重写方法:编译时编译器采用父类中的方法验证语句,运行时 JVM 调用子类中的方法。

虚函数

Java 中没有虚函数的概念,实际上所有函数默认是 C++ 中的虚函数,动态绑定属于默认行为。如果需要某个函数不具有虚函数特性,使用 final

抽象类 抽象方法

抽象类;除了不能实例化对象(也就是调用构造函数会报错),必须要继承,其余的没区别。

一个类可以实现多个接口。

采用 public abstract class Xxx 定义抽象类。

抽象方法:

  1. 抽象类中特别的成员方法,具体实现由子类确定。
  2. 只有方法名,没有大括号和里面的方法体。public abstract <type> xxx();
  3. 抽象类中可选的。
  4. 构造方法、类方法 (static修饰的)不能声明为抽象方法。
  5. 一直继承到不是抽象类的子类时,就必须给出对应实现了。

封装 Encapsulation

优点:减少耦合、安全、精确控制、隐藏细节

总而言之,对于 私有属性提供 setget 方法。

接口 Interface

与类对比

接口像是一种弱化版本+特化版本的类。

接口和接口内的方法是隐式抽象且公有的,意思是可以不用写 abstract 关键字。

相同点:

  1. 同样存储在 .java 文件中,文件名使用接口名。
  2. 字节码同样在 .class 文件中。
  3. 字节码文件必须在与包名相匹配的目录结构中。

不同点:

  1. 接口不能实例化对象
  2. 没有构造方法
  3. 所有方法都是抽象方法 ,java 8 之后可以使用 default 关键字修饰非抽象方法。方法默认为 public abstract
  4. 接口不能包含成员变量,除了 staticfinal 变量,成员变量默认为 public static final
  5. 接口要么被继承,要么被实现。
  6. 支持多继承。

与抽象类对比

  1. 抽象类可以由方法体
  2. 抽象类的成员变量类型没有限制
  3. 抽象类中可以含有静态代码块与静态方法,接口不行。
  4. 抽象类只能被单继承,接口可以被多继承。换句话说,一个类只能继承一个抽象类,但是可以继承多个接口。

实现

  1. 类实现部分接口方法时,必须声明为抽象类。因为你还继承了人家一部分抽象方法,而只要有抽象方法,那至少是个抽象类。【那从这里,我们可以这么理解,抽象类是抽象的类,接口是进一步抽象的抽象类。

  2. 语法

    1
    MyInterface implements Interface1, Interface2[,……]

继承与多继承

除了能多继承之外,几乎没有其他区别了。同样使用关键字 extends

标记接口

标记接口:没有任何方法和属性的接口。一般仅仅表明它的类属于一个特定的类型。

作用:

  1. 建立一个公共的父接口
  2. 向一个类添加数据类型(这个没看懂)

以下是 Wikipedia 对于标记的解释:

The tag/ marker interface pattern is a design pattern in computer science, used with languages that provide run-time type information about objects. It provides a means to associate metadata with a class where the language does not have explicit support for such metadata.

翻译为:

标记接口是计算机科学中的一种设计思路。编程语言本身不支持为类维护元数据。而标记接口则弥补了这个功能上的缺失——一个类实现某个没有任何方法的标记接口,实际上标记接口从某种意义上说就成为了这个类的元数据之一。运行时,通过编程语言的反射机制,我们就可以在代码里拿到这种元数据。

以下是 Kimi 关于标记接口的作用:

在Java中,标记接口(Marker Interface)是一种没有任何方法声明的接口。它通常用于表示一个类属于某个类别或者具备某种属性,而不需要提供任何方法实现。标记接口可以被用来实现以下功能:

  1. 类型安全:确保某个类具有特定的属性或行为。
  2. 过滤和筛选:在集合类中,如ListSet,可以使用标记接口来过滤和筛选对象。
  3. 注解和反射:标记接口可以与注解一起使用,通过反射来检查对象是否实现了特定的标记接口。
  4. 设计模式:在某些设计模式中,如策略模式或状态模式,标记接口可以用来区分不同的策略或状态.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用标记接口进行类型检查
public interface Serializable {}

public class MyClass implements Serializable {
// 类的实现
}

public class TestClass {
public static void main(String[] args) {
MyClass myClass = new MyClass();
if (myClass instanceof Serializable) {
System.out.println("myClass is serializable");
}
}
}

在这个例子中,Serializable是一个标记接口,它没有任何方法。MyClass实现了这个接口,表明它具备序列化(和反序列化)的能力。在TestClass中,我们使用instanceof关键字来检查myClass对象是否实现了Serializable接口。

与之类似的还有 Cloneable 可以克隆的,RandomAccess 表明这个类是可以随机访问的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// 例子2 使用标记接口与注解结合
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 只能用于类、接口等类型
public @interface TestAnnotation {
}

@TestAnnotation
public interface MarkerInterface {}

public class SomeClass implements MarkerInterface {
// 类的实现
}

public class AnnotationProcessor {
public static void main(String[] args) {
Class<?> clazz = SomeClass.class;
if (clazz.isAnnotationPresent(TestAnnotation.class)) {
System.out.println(clazz.getSimpleName() + " is annotated with TestAnnotation");
}
}
}

在这个例子中,我们定义了一个注解TestAnnotation,它被用来标记类型。MarkerInterface是一个标记接口,它被TestAnnotation注解标记。SomeClass实现了MarkerInterface。在AnnotationProcessor类中,我们使用反射来检查SomeClass是否被TestAnnotation注解标记。

我们对上述 Kimi 的第二个例子做详细解读。

  1. 对于 @Override 的使用:

    @Override是一个注解(annotation),用于指示一个方法声明打算重写基类中的另一个方法。它不是重写操作所必需的,但它提供了几个好处:

    1. 编译时检查:当你在方法前加上@Override注解时,编译器会检查你声明的方法是否真的重写了父类或接口中的方法。如果父类或接口中没有对应的方法,编译器会报错。这有助于避免因拼写错误或其他原因导致的意外行为。
    2. 代码清晰@Override注解可以提高代码的可读性,让其他开发者或维护者更容易理解你的意图是重写某个方法。
    3. 避免错误:如果你不小心更改了父类或接口中的方法签名,而你的子类中的方法没有相应的更改,那么没有@Override注解的情况下,这个错误可能不会立即被发现。有了@Override注解,编译器会立即报错,提示你检查方法签名。
    4. 总而言之,不是必须,但是作为规范,是必要的。
  2. 上述代码出现了反射的概念,我们详细解释【以下代码注释请初学者需要逐句观看】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // 导入Java注解相关的类
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    // 定义一个注解TestAnnotation
    @Retention(RetentionPolicy.RUNTIME) // 指定注解的保留策略为运行时
    @Target(ElementType.TYPE) // 指定注解可以用于类、接口等类型
    public @interface TestAnnotation {
    // 注解定义,这里没有成员变量,是一个标记注解
    // @interface 用于声明一个注解,而 interface 用于声明一个接口
    }

    // 使用TestAnnotation注解标记MarkerInterface接口
    @TestAnnotation
    public interface MarkerInterface {
    // 这是一个标记接口,没有定义任何方法
    }

    // 实现MarkerInterface接口的类SomeClass
    public class SomeClass implements MarkerInterface {
    // 类的实现,这里没有具体的方法实现
    }

    // 类AnnotationProcessor 使用反射来检查类是否被注解标记
    public class AnnotationProcessor {
    public static void main(String[] args) {
    // 获取SomeClass类的Class对象
    Class<?> clazz = SomeClass.class; //<?> 是一个类型通配符
    // 使用Class对象的isAnnotationPresent方法 检查SomeClass是否被TestAnnotation注解标记
    if (clazz.isAnnotationPresent(TestAnnotation.class)) {
    // 如果是,打印出SomeClass类名,并说明它被TestAnnotation注解标记
    System.out.println(clazz.getSimpleName() + " is annotated with TestAnnotation");
    }
    // 上述代码中使用的 idAnnotaionPresent 与 getSimpleName 方法是Java所有类的根类Class 实现的。
    }
    }

多继承接口 单继承类 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 定义一个父类
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}

// 定义两个接口
interface Movable {
void move();
}

interface Speakable {
void speak();
}

// 子类继承Animal类,并实现Movable和Speakable接口
class Dog extends Animal implements Movable, Speakable {
public void move() {
System.out.println("Dog is moving");
}

public void speak() {
System.out.println("Dog says woof");
}

// 覆盖父类的eat方法
@Override
void eat() {
System.out.println("Dog is eating");
}
}

public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 调用继承自Animal的方法
dog.move(); // 调用实现自Movable接口的方法
dog.speak(); // 调用实现自Speakable接口的方法
}
}

所以同时继承接口与类是可行的。

枚举 enum

枚举类是一种特殊的类。可以在类内类外定义、可序列化可比较、可以使用 private 修饰的构造函数,可以有具体方法与抽象方法。一般有三个常用的从基类继承来的方法。常用在迭代与 switch比较中。

定义方法:

1
2
3
4
enum color
{
RED, GREEN, BLUE;
}

可以定义在类内部,也可以在类外部。

一般用在 switch 或者 for 迭代 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
enum Color 
{
RED, GREEN, BLUE;
}

// switch
public class MyClass {
public static void main(String[] args) {
Color myVar = Color.BLUE;

switch(myVar) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}


// iteration
public class MyClass {
public static void main(String[] args) {
for (Color myVar : Color.values()) {
System.out.println(myVar);
}
}
}

enum 类默认继承 java.lang.Enum 类与 java.lang.Serializable 接口和 java.lang.Comparable 接口。

基类继承来的三个方法:

  1. <ClassName>.values() 返回枚举类中所有值,返回一个枚举类的数组/列表。
  2. <Object>.ordinal() 找到每个枚举常量的索引
  3. <ClassName>.valueOf("<String>") 返回指定字符串的枚举常量。不存在会报错 IllegalArgumentException

包 Package

package 指定当前文件(一般是类)的路径(所属包)

import 关键字导入包,可用通配符导入包下所有类。

一般一个项目用公司的域名颠倒作为包名,比如 com.duanyao,我可以在下面创建 com.duanyao.hello 这个包,然后在里面创建 com.duanyao.hello.Hello.java 类用于输出 Hello, Java World!

编译使用的命令可以进一步查询 javac 的相关参数。

反射 Reflection

这是个重点。

Java 中的反射(Reflection)机制是指,Java 程序在运行期间可以获取到一个对象的全部信息。

反射的四种应用:

  1. 开发通用框架
  2. 动态代理
    1. JDK 代理
    2. GGLIB 代理
  3. 自定义注解
  4. 访问私有成员

这部分暂时放一下下。我后续过来补充这部分。