1.面向对象(高级)-抽象类与抽象方法的使用

abstract: 抽象的

1.可以用来修饰:类、方法

2.具体的:

abstract修饰类:抽象类

  • 此类不能实例化
  • 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
  • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
  • 抽象的使用前提:继承性

abstract修饰方法:抽象方法

  • 抽象方法只有方法的声明,没方法体
  • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
  • 若子类重写了父类中的所的抽象方法后,此子类方可实例化
  • 若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰

3.注意点:

3.1.abstract不能用来修饰:属性、构造器等结构
3.2.abstract不能用来修饰私有方法、静态方法、final的方法、final的类

4.abstract的应用举例:

举例一:

1
2
3
4
5
6
7
8
9
abstract class GeometricObject{
public abstract double findArea();
}
class Circle extends GeometricObject{
private double radius;
public double findArea(){
return 3.14 * radius * radius;
};
}

举例二:IO流中设计到的抽象类:InputStream/OutputStream / Reader /Writer。在其内部定义了抽象的read()、write()方法。

2.面向对象(高级)-模板方法设计模式

1 解决的问题

在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

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
39
40
41
1. abstract class Template{
//计算某段代码执行所需要花费的时间
public void spendTime(){

long start = System.currentTimeMillis();

this.code();//不确定的部分、易变的部分

long end = System.currentTimeMillis();

System.out.println("花费的时间为:" + (end - start));

}

public abstract void code();

}

class SubTemplate extends Template{

@Override
public void code() {


for(int i = 2;i <= 1000;i++){
boolean isFlag = true;
for(int j = 2;j <= Math.sqrt(i);j++){

if(i % j == 0){
isFlag = false;
break;
}
}
if(isFlag){
System.out.println(i);
}
}

}

}

3.面向对象(高级)-接口的使用

1.接口的定义

1 接口概述

接口使用interface来定义

Java中,接口和类是并列的两个结构

2 接口的定义

如何定义接口:定义接口中的成员

1 JDK7及以前:只能定义全局常量和抽象方法
			>全局常量:public static final的.但是书写时,可以省略不写
			>抽象方法:public abstract的
2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
3 接口中不能定义构造器!意味着接口不可以实例化
4. Java开发中,接口通过让类去实现(implements)的方式来使用.

​ 如果实现类覆盖了接口中所有的抽象方法,则此实现类就可以实例化
​ 如果实现类没覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类

5. Java类可以实现多个接口 —>弥补了Java单继承性的局限性

​ 格式:class AA extends BB implements CC,DD,EE

6. 接口与接口之间可以继承,而且可以多继承
7. 接口的具体使用体现了多态性
8. 接口,实际上可以看做是一种规范

2.举例:

image-20250614235518398

1
2
3
4
5
6
7
8
9
class Computer{
public void transferData(USB usb){//USB usb = new Flash();
usb.start();

System.out.println("具体传输数据的细节");

usb.stop();
}
}
1
2
3
4
5
6
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();

void stop();
}
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
class Flash implements USB{

@Override
public void start() {
System.out.println("U盘开启工作");
}

@Override
public void stop() {
System.out.println("U盘结束工作");
}

}

class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}

@Override
public void stop() {
System.out.println("打印机结束工作");
}

}

体会:
①接口使用上也满足多态性
②接口,实际上就是定义了一种规范
③开发中,体会面向接口编程!

3.体会面向接口编程的思想

image.png
面向接口编程:我们在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个数据库厂商的API。

4.面试题:

抽象类和接口的异同?
相同点:不能实例化;都可以包含抽象方法的。
不同点:
①把抽象类和接口(java7,java8,java9)的定义、内部结构解释说明
②类: 单继承 接口:多继承
类与接口: 多实现

4.面向对象(高级)-JDK8 和 JDK9 中接口的新特性

1.Java8中关于接口的新规范

知识点1:接口中定义的静态方法,只能通过接口来调用。
知识点2:通过实现类的对象,可以调用接口中的默认方法。
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。–>接口冲突。这就需要我们必须在实现类中重写此方法。
知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法

1
2
3
4
5
6
7
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}

5.面向对象(高级)-枚举类的两种定义方式

1.枚举类的说明:

1.1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
1.2当需要定义一组常量时,强烈建议使用枚举类
1.3如果枚举类中只有一个对象,则可以作为单例模式的实现方式。

2.如何自定义枚举类?步骤:

自定义枚举类
class Season{

2.1声明Season对象的属性:private final修饰

private final String seasonName;
private final String seasonDesc;

2.2私化类的构造器,并给对象属性赋值

private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

2.3提供当前枚举类的多个对象:public static final的

public static final Season SPRING = new Season(“春天”,”春暖花开”);
public static final Season SUMMER = new Season(“夏天”,”夏日炎炎”);
public static final Season AUTUMN = new Season(“秋天”,”秋高气爽”);
public static final Season WINTER = new Season(“冬天”,”冰天雪地”);

2.4.其他诉求1:获取枚举类对象的属性

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}

2.5其他诉求1:提供toString()

@Override
public String toString() {
return “Season{“ +
“seasonName=’” + seasonName + ‘'‘ +
“, seasonDesc=’” + seasonDesc + ‘'‘ +
‘}’;
}
}

3.jdk 5.0 新增使用enum定义枚举类。步骤:

使用enum关键字枚举类
enum Season1 {

3.1提供当前枚举类的对象,多个对象之间用”,”隔开,末尾对象”;”结束

SPRING(“春天”,”春暖花开”),
SUMMER(“夏天”,”夏日炎炎”),
AUTUMN(“秋天”,”秋高气爽”),
WINTER(“冬天”,”冰天雪地”);

3.2声明Season对象的属性:private final修饰

private final String seasonName;
private final String seasonDesc;

3.3私化类的构造器,并给对象属性赋值

private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

3.4其他诉求1:获取枚举类对象的属性

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}

}

3.5开发使用实例,了解(每个项目都要用到)

使用lombok注解简化了开发

1
2
3
4
5
6
7
8
9
10
11
12
13
@Getter
@AllArgsConstructor
public enum ResultCodeEnum {

SUCCESS(200, "成功"),

FAIL(201, "失败");

private Integer code;

private String message;

}

4.使用enum定义枚举类之后,枚举类常用方法:(继承于java.lang.Enum类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
System.out.println(Season1.class.getSuperclass());
System.out.println("---------------");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
}
System.out.println("-------------------");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}

//valueOf(String objName):返回枚举类中对象名是objName的对象。

Season1 winter = Season1.valueOf("WINTER");
//如果没objName的枚举类对象,则抛异常:IllegalArgumentException
Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);

5.使用enum定义枚举类之后,如何让枚举类对象分别实现接口:

1
2
3
interface Info{
void show();
}
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
//使用enum关键字枚举类
enum Season1 implements Info{

//提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束

SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
}

6.面向对象(高级)-Annotation 注解、单元测试的使用

1.注解的理解

①Jdk5.0 新增的功能

② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射机制 + 设计模式

1.1注解的使用示例

示例一:生成文档相关的注解
示例二:在编译时进行格式检查(JDK内置的个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告

  • 示例:跟踪代码依赖性,实现替代配置文件功能

2.如何自定义注解:参照@SuppressWarnings定义

​ ① 注解声明为:@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
​ ④ 如果自定义注解没成员,表明是一个标识作用。
//要结合反射,先感受一下

1
2
3
4
5
6
7
8
public @interface MyAnnototaion {
String name() default "hello";
}

@MyAnnototaion(name = "yupengtao")
class myannototionTest{

}

说明:
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才意义。
自定义注解通常都会指明两个元注解:Retention、Target

代码举例:

1
2
3
4
5
6
7
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}

3.元注解 :对现有的注解进行解释说明的注解。

jdk 提供的4种元注解:
Retention:指定所修饰的 Annotation 的生命周期:
SOURCE\CLASS(默认行为\RUNTIME)
只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR,LOCAL_VARIABLE})
下面注解出现的频率较低:
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具继承性。
—>类比:元数据的概念:String name = “Tom”;

4.如何获取注解信息:通过反射来进行获取、调用。

前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME.

5.JDK8中注解的新特性:可重复注解、类型注解

5.1 可重复注解:

从 Java 8 开始,所有包含 value() 方法且返回值类型为数组的注解,都被隐式地视为可重复注解。 而不用显示的使用@Repeatable注解

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
public @interface Authors {
Author[] value();
}
------------------------------------------------

@Author(name = "Alice")
@Author(name = "Bob")
public class MyClass {
// ...
}
5.2 类型注解:

ElementType.TYPE_PARAMETER 表示该注解能写在什么类型变量的声明语句中(如:泛型声明。

ElementType.TYPE_USE 表示该注解能写在任何使用类型的语句中。

6.Java中的JUnit单元测试

步骤:
6.1中当前工程 - 右键择:build path - add libraries - JUnit 4 - 下一步
6.2.创建Java类,进行单元测试。
此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
如果测试类需要进行依赖注入等复杂操作,则可以使用带参数的构造函数,并在测试方法上使用注解来指定运行器应该使用哪个构造函数进行实例化。例如,使用JUnit 4框架时,可以使用@RunWith(Parameterized.class)注解来指定带参数的构造函数。
6.3.此类中声明单元测试方法。
此时的单元测试方法:方法的权限是public,没返回值,没形参

​ 6.4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
​ 6.5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
比如带参数的方法,放单元测试里面
​ 6.6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
​ 6.7.自定义单元测试模板 test
image.png
说明:
①如果执行结果没任何异常:绿条
②如果执行结果出现异常:红条

7.面向对象(高级)-包装类的理解_基本数据类型与包装类间的转换

1.为什么要有包装类(或封装类)

为了使基本数据类型的变量具有类的特征,引入包装类。

2.基本数据类型与对应的包装类:

image.png

3.需要掌握的类型间的转换:(基本数据类型、包装类)

基本数据类型<—>包装类。JDK 5.0 新特性:自动装箱与自动拆箱

1 装箱
1
2
3
4
5
6
7
int i = 10;
Integer j = i; // 自动装箱
Integer i = new Integer(10);//手动装箱法1

或者

Integer i = Integer.valueOf(10);//手动装箱法2
2 拆箱
1
2
3
int j = i; // 自动拆箱
Integer integerValue = 10;
int intValue = integerValue.intValue(); //手动拆箱

8.面向对象(高级)-String 与基本数据类型、包装类间的转换

1.基本数据类型、包装类—>String:调用String重载的valueOf(Xxx xxx)

1
2
//基本数据类型、包装类-->String
String s = String.valueOf(a);

2.String—>基本数据类型、包装类:调用包装类的parseXxx(String s)

1
2
3
4
//        String-->基本数据类型
byte b1 = Byte.parseByte(s);
// String-->包装类
Byte aByte = Byte.valueOf(s);

注意:转换时,可能会报NumberFormatException

image.png
应用场景举例:
① Vector类中关于添加元素,只定义了形参为Object类型的方法:
v.addElement(Object obj); //基本数据类型 --->包装类 --->使用多态

9.128陷阱

原文链接:https://blog.csdn.net/qq_62636650/article/details/137029063

​ 128陷阱 是指在 Java 中,当比较 Integer 类型的数据时,有时候会发现两个明明相同的值,最后比较的结果为 false 。这是因为在 Java 中, Integer 是数据类型 int 的封装类。Java 设计者认为大家对数的使用大多在100以内,因此规定在-128至127之间的 Integer 类型的变量,直接指向常量池中的缓存地址,不会 new 开辟出新的空间。

下面提供一个示例,展示 128陷阱 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IntegerComparisonExample {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
Integer cc = 1000;
Integer dd = 1000;
System.out.println((a == b));
System.out.println((c == d));
System.out.println((cc == dd));
System.out.println("----------------");
System.out.println((a.equals(b)));
System.out.println((c.equals(d)));
System.out.println((cc.equals(dd)));
}
}

​ 在上述代码中,定义了六个 Integer 类型的对象,分别是 a 、 b 、 c 、 d 、 cc 和 dd 。通过 == 运算符比较它们的值,可以发现 a 和 b 的值相等,返回 true 。而 c 和 d 的值相等,返回 false 。这是因为 c 和 d 的值为128,自动装箱时超出了常量池的范围,创建了新的对象,因此它们的地址不同。使用 equals() 方法比较它们的值,结果都是 true ,因为 equals() 方法比较的是对象的值,而不是对象的地址。

    这是因为,在Integer的valueOf()方中,当数值在-128-127之间时,数值都存储在有一个catch数组当中,该数组相当于一个缓存,当我们在-128-127之间进行自动装箱的时候,我们就直接返回该值在内存当中的地址,所以在-128-127之间的数值用==进行比较是相等的。

如何避免128陷阱
为了避免 Integer 128陷阱 ,推荐使用 equals 方法进行比较,因为 equals 方法比较的是对象的内容,而不是引用。