JavaBase-2-Object-oriented
继承
关键字:
- extends:java 不支持多继承(但是支持多重继承),即一个类继承两个或以上的父类。
- implements:可以变相使java实现多继承的特性,即一个类继承多个接口
this
访问本身的类方法super
访问父类的方法,父类指针,一般在构造器
中使用用于调用父类的构造器方法,用于初始化构造。构造器方法一般可能会有有参数的,无参数的等等。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
50package 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{
public void eat() {}
public void sleep() {}
public void run() {}
}
}
重写(Override)与重载(Overload)
重写:子类定义了与父类一样名称、参数、返回值的方法。
重载:一个类中,方法名字相同,参数一定不同,返回值可以不同的几个方法。
重写
一般是需要 @override
注解的,但是菜鸟教程里面没有提到。
重写规则:
- 返回类型可以与父类不同,但是必须是父类返回值的派生类
- 访问权限不能比父类中的方法权限更低,总之一定不能做更严格的限制,可以降低限制。
- 声明为
final
的方法不能重写 - 声明为
static
的方法不能重写,但是可以再次声明 - 声明为
private
的方法子类没继承,自然谈不上重写 - ……
- 重写方法能够抛出非强制性异常、不能抛出新的强制性异常、不能抛出更广泛的强制性异常。总之,可以减少或者删除,一定不能抛出更广或者新的异常
重载
没什么好说的,基本上和 C++ 一样。
多态
多态实现方法
- 重写
- 接口
- 抽象类与抽象方法
多态: 同一个方法/接口,在不同的条件下执行不同的操作。
多态优点:消除类型之间的耦合、替换、扩充、接口、灵活、简化
存在的必要条件:继承、重写、父类引用指向子类对象
Parent p = new child();
使用多态调用方法时,先检查父类中是否有这个方法,没有报编译错误,有则调用子类的同名方法。
1 | public class Test { |
上面的代码中,15-20 行为什么不使用 a.work()
代替呢?因为
Animal
父类没有work
方法,这是子类扩充出来的。
所以:
- 我们可以使用 父类指针指向子类对象,并且调用子类中重写父类的方法。
- 但是我们不能直接使用 父类指针指向子类对象后,使用父类指针调用子类中独有的方法。
- 需要向下转型为
子类指针
,这时我们需要使用instanceof
来判断到底是父类的哪个扩展子类,以决定将父类指针向下转型为哪种子类的指针,进而才能调用对应子类中独有的方法。
说明:
- 本类指针调用本类重写方法:编译器在编译时找到方法,执行过程中 JVM 调用本类中的方法
- 父类指针调用子类的重写方法:编译时编译器采用父类中的方法验证语句,运行时 JVM 调用子类中的方法。
虚函数
Java 中没有虚函数的概念,实际上所有函数默认是 C++
中的虚函数,动态绑定属于默认行为。如果需要某个函数不具有虚函数特性,使用
final
抽象类 抽象方法
抽象类;除了不能实例化对象(也就是调用构造函数会报错),必须要继承,其余的没区别。
一个类可以实现多个接口。
采用 public abstract class Xxx
定义抽象类。
抽象方法:
- 抽象类中特别的成员方法,具体实现由子类确定。
- 只有方法名,没有大括号和里面的方法体。
public abstract <type> xxx();
。 - 抽象类中可选的。
- 构造方法、类方法
(
static
修饰的)不能声明为抽象方法。 - 一直继承到不是抽象类的子类时,就必须给出对应实现了。
封装 Encapsulation
优点:减少耦合、安全、精确控制、隐藏细节
总而言之,对于 私有属性提供 set
与 get
方法。
接口 Interface
与类对比
接口像是一种弱化版本+特化版本的类。
接口和接口内的方法是隐式抽象且公有的,意思是可以不用写
abstract
关键字。
相同点:
- 同样存储在
.java
文件中,文件名使用接口名。 - 字节码同样在
.class
文件中。 - 字节码文件必须在与包名相匹配的目录结构中。
不同点:
- 接口不能实例化对象
- 没有构造方法
- 所有方法都是抽象方法 ,
java 8
之后可以使用default
关键字修饰非抽象方法。方法默认为public abstract
- 接口不能包含成员变量,除了
static
和final
变量,成员变量默认为public static final
- 接口要么被继承,要么被实现。
- 支持多继承。
与抽象类对比
- 抽象类可以由方法体
- 抽象类的成员变量类型没有限制
- 抽象类中可以含有静态代码块与静态方法,接口不行。
- 抽象类只能被单继承,接口可以被多继承。换句话说,一个类只能继承一个抽象类,但是可以继承多个接口。
实现
类实现部分接口方法时,必须声明为抽象类。因为你还继承了人家一部分抽象方法,而只要有抽象方法,那至少是个抽象类。【那从这里,我们可以这么理解,抽象类是抽象的类,接口是进一步抽象的抽象类。】
语法
1
MyInterface implements Interface1, Interface2[,……]
继承与多继承
除了能多继承之外,几乎没有其他区别了。同样使用关键字
extends
。
标记接口
标记接口:没有任何方法和属性的接口。一般仅仅表明它的类属于一个特定的类型。
作用:
- 建立一个公共的父接口
- 向一个类添加数据类型(这个没看懂)
以下是 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)是一种没有任何方法声明的接口。它通常用于表示一个类属于某个类别或者具备某种属性,而不需要提供任何方法实现。标记接口可以被用来实现以下功能:
- 类型安全:确保某个类具有特定的属性或行为。
- 过滤和筛选:在集合类中,如
List
或Set
,可以使用标记接口来过滤和筛选对象。- 注解和反射:标记接口可以与注解一起使用,通过反射来检查对象是否实现了特定的标记接口。
- 设计模式:在某些设计模式中,如策略模式或状态模式,标记接口可以用来区分不同的策略或状态.
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;
// 只能用于类、接口等类型
public 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 的第二个例子做详细解读。
对于
@Override
的使用:@Override
是一个注解(annotation),用于指示一个方法声明打算重写基类中的另一个方法。它不是重写操作所必需的,但它提供了几个好处:- 编译时检查:当你在方法前加上
@Override
注解时,编译器会检查你声明的方法是否真的重写了父类或接口中的方法。如果父类或接口中没有对应的方法,编译器会报错。这有助于避免因拼写错误或其他原因导致的意外行为。 - 代码清晰:
@Override
注解可以提高代码的可读性,让其他开发者或维护者更容易理解你的意图是重写某个方法。 - 避免错误:如果你不小心更改了父类或接口中的方法签名,而你的子类中的方法没有相应的更改,那么没有
@Override
注解的情况下,这个错误可能不会立即被发现。有了@Override
注解,编译器会立即报错,提示你检查方法签名。 - 总而言之,不是必须,但是作为规范,是必要的。
- 编译时检查:当你在方法前加上
上述代码出现了反射的概念,我们详细解释【以下代码注释请初学者需要逐句观看】
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
// 指定注解的保留策略为运行时
// 指定注解可以用于类、接口等类型
public TestAnnotation {
// 注解定义,这里没有成员变量,是一个标记注解
// @interface 用于声明一个注解,而 interface 用于声明一个接口
}
// 使用TestAnnotation注解标记MarkerInterface接口
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 | // 定义一个父类 |
所以同时继承接口与类是可行的。
枚举 enum
枚举类是一种特殊的类。可以在类内类外定义、可序列化可比较、可以使用
private
修饰的构造函数,可以有具体方法与抽象方法。一般有三个常用的从基类继承来的方法。常用在迭代与
switch
比较中。
定义方法:
1 | enum color |
可以定义在类内部,也可以在类外部。
一般用在 switch
或者 for 迭代
中:
1 | enum Color |
enum
类默认继承 java.lang.Enum
类与
java.lang.Serializable
接口和
java.lang.Comparable
接口。
基类继承来的三个方法:
<ClassName>.values()
返回枚举类中所有值,返回一个枚举类的数组/列表。<Object>.ordinal()
找到每个枚举常量的索引<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 程序在运行期间可以获取到一个对象的全部信息。
反射的四种应用:
- 开发通用框架
- 动态代理
- JDK 代理
- GGLIB 代理
- 自定义注解
- 访问私有成员
这部分暂时放一下下。我后续过来补充这部分。