# static
叫静态,可以修饰成员变量、成员方法
# 修饰成员变量
成员变量按照有无 static 修饰,分为两种:
类变量(静态成员变量):有 static 修饰,属于类,在计算机里只有一份,会被类的全部对象共享
格式:
类名.类变量
(推荐)、对象名.类变量
(不推荐)适用场景:数据只需要一份,且需要被共享时(访问,修改)
实例变量(对象变量):无 static 修饰,属于每个对象的
格式:
对象名.实例变量
适用场景:每个对象都要有一份,数据各不同(如:name、score、age)
成员变量的执行原理:
应用场景
在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住
// 🌰:要求用户类记住创建了几个用户对象 | |
public class User { | |
public static int number; | |
public User() { | |
// 写法 1: | |
User.number++; | |
// 写法 2:访问自己类中的类变量,才可以省略类名 | |
number++; | |
} | |
} |
# 修饰成员方法
类方法(静态方法):有 static 修饰的成员方法,属于类
格式:
类名.类方法
(推荐)、对象名.类方法
(不推荐)实例方法(对象的方法):无 static 修饰的成员方法,属于对象
格式:
对象名.实例方法
成员方法的执行原理:
🔖 补充:main 方法是一个类方法
应用场景
可以用来设计工具类
工具类:工具类中的方法都是类方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的
好处:提高了代码复用;方便调用,提高了开发效率
不用实例方法的原因:实例方法需要创建对象来调用,会浪费内存
// 🔖 多学一招:工具类不需要创建对象,建议将工具类的构造器私有化 | |
public class MyUtil { | |
private MyUtil() { | |
} | |
} | |
MyUtil t = new MyUtil(); // 无法创建对象 |
# 注意事项
- 类方法中可以直接访问类成员,不可以直接访问实例成员
- 实例方法中可以直接访问类成员、实例成员
- 实例方法中可以出现
this
关键字,类方法中不可以出现this
关键字的
# static 应用(代码块)
代码块是类的 5 大成分之一(成员变量、构造器、方法、代码块、内部类)
静态代码块
格式:
static {}
特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
作用:完成类的初始化,例如:对类变量的初始化赋值
实例代码块
格式:
{}
特点:每次创建对象时,执行实例代码块,并在构造器前执行
作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值
案例
public class Student { | |
static int number = 80; | |
static String schoolName = "呆呆"; | |
// 静态代码块 | |
static { | |
System.out.println("静态代码块执行了~~"); | |
schoolName = "呆呆"; | |
} | |
} | |
//------------------------------------------------ | |
public class Test { | |
public static void main(String[] args) { | |
System.out.println(Student.number); | |
System.out.println(Student.number); | |
System.out.println(Student.schoolName); | |
} | |
} | |
运行结果: | |
静态代码块执行了~~ | |
80 | |
80 | |
呆呆 |
public class Student { | |
int age; | |
{ | |
System.out.println("实例代码块执行了~~"); | |
age = 18; | |
System.out.println("有人创建了对象:" + this); | |
} | |
public Student() { | |
System.out.println("无参数构造器执行了~~"); | |
} | |
public Student(String name) { | |
System.out.println("有参数构造器执行了~~"); | |
} | |
} | |
//------------------------------------------------ | |
public class Test { | |
public static void main(String[] args) { | |
Student s1 = new Student(); | |
Student s2 = new Student("呆呆"); | |
System.out.println(s1.age); | |
System.out.println(s2.age); | |
} | |
} | |
运行结果: | |
实例代码块执行了~~ | |
有人创建了对象: | |
无参数构造器执行了~~ | |
实例代码块执行了~~ | |
有人创建了对象: | |
有参数构造器执行了~~ | |
18 | |
18 |
# static 应用(单例设计模式)
设计模式:设计模式就是具体问题的最优解决方案
单例设计模式:确保一个类只有一个对象
写法:
- 把类的构造器私有
- 定义一个类变量存储类的一个对象
- 定义一个类方法返回对象
应用场景:任务管理器对象、获取运行时对象,使用单例模式可以避免浪费内存
# 饿汉式单例
特点:拿对象时,对象早就创建好了
public class A { | |
private static A a = new A(); | |
private A() { | |
} | |
public static A getInstance() { | |
return a; | |
} | |
} | |
-------------------------------- | |
A a1 = A.getInstance(); | |
A a2 = A.getInstance(); | |
System.out.println(a1); // com.itheima.A@4eec7777 | |
System.out.println(a2); // com.itheima.A@4eec7777 |
# 懒汉式单例
特点:拿对象时,才开始创建对象
public class B { | |
private static B b; | |
private B() { | |
} | |
public static B getInstance() { | |
if (b == null) { | |
System.out.println("第一次创建对象"); | |
b = new B(); | |
} | |
return b; | |
} | |
} | |
-------------------------------- | |
B b1 = B.getInstance(); | |
B b2 = B.getInstance(); | |
System.out.println(b1 == b2); | |
// 第一次创建对象 | |
// true |
# 继承
用 extends
关键字,让一个类和另一个类建立起一种父子关系
特点:子类能继承父类的非私有成员(成员变量、成员方法)
好处:减少了重复代码的编写,提高了代码的复用性
继承后对象的创建:子类的对象是由子类、父类共同完成的
对象能直接访问什么成员,是由子父类这多张设计图共同决定的,这多张设计图对外暴露了什么成员,对象就可以访问什么成员
public class B extends A {} | |
➡ A类:父类(基类或超类) | |
➡ B类:子类(派生类) |
继承的执行原理:
# 权限修饰符
用来限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围
修饰符 | 在本类里 | 同一个包下中的其他类里 | 任意包下的子类里 | 任意包下的任意类里 |
---|---|---|---|---|
private | 🗸 | |||
缺省 | 🗸 | 🗸 | ||
protected | 🗸 | 🗸 | 🗸 | |
public | 🗸 | 🗸 | 🗸 | 🗸 |
private < 缺省 < protected < public
# 单继承、Object
Java 是单继承的:一个类只能继承一个直接父类
Java 中的类不支持多继承,但是支持多层继承
object 类是 java 所有类的祖宗类。我们写的任何一个类,其实都是 object 的子类或子孙类
# 方法重写
子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法
注意事项:
- 重写后,方法的访问,Java 会遵循就近原则
- 建议加上:
@Override
注解,可以校验重写是否正确,同时可读性好 - 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限( public > protected > 缺省 )
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写
应用场景:当子类觉得父类的方法不好用,或者不满足自己的需求时,就可以用方法重写
子类重写 Object 类的 toString () 方法,以便返回对象的内容
🌰
public class Student extends Object {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{name=" + name + ", age=" + age + "}";
}
//idea 会自动生成:鼠标右键 ➡ 生成(Generate)➡ toString () ➡ 按住 shift 全选中
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student("呆呆", 18);
// System.out.println(s.toString());
System.out.println(s); // Student {name=' 呆呆 ', age=18}
}
}
# 子类中访问成员的特点
在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的
先子类局部范围找
然后子类成员范围找
然后父类成员范围找,如果父类范围还没有找到则报错
如果子父类中,出现了重名的成员,会优先使用子类的
可以通过
super
关键字,指定访问父类的成员:super.父类成员变量/父类成员方法
public class Test { | |
public static void main(String[] args) { | |
B b = new B(); | |
b.showName(); | |
b.showMethod(); | |
} | |
} | |
public class A { | |
String name = "父类成员变量"; | |
public void print1() { | |
System.out.println("父类成员方法"); | |
} | |
} | |
public class B extends A { | |
String name = "子类成员变量"; | |
public void showName() { | |
String name = "局部名称"; | |
System.out.println(name); // 局部名称 | |
System.out.println(this.name); // 子类成员变量 | |
System.out.println(super.name); // 父类成员变量 | |
} | |
@Override | |
public void print1() { | |
System.out.println("子类成员方法"); | |
} | |
public void showMethod() { | |
print1(); // 子类成员方法 | |
super.print1(); // 父类成员方法 | |
} | |
} |
# 子类构造器的特点
子类的全部构造器,都会先调用父类的构造器,再执行自己
# super(...)
子类构造器如何实现调用父类构造器的:
默认情况下,子类全部构造器的第一行代码都是
super();
(写不写都有) ,它会调用父类的无参数构造器如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写
super(...);
,指定去调用父类的有参数构造器
应用场景
# this(...)
在构造器中调用本类的其他构造器
this(): 调用本类的无参数构造器 | |
this(参数): 调用本类的有参数构造器 |
⚠️ 注:
this(…) 、super(…)
都只能放在构造器的第一行,因此,有了this(…)
就不能写super(…)
了,反之亦然
案例 🌰
# 总结
访问本类成员: | |
this.成员变量 // 访问本类成员变量 | |
this.成员方法 // 调用本类成员方法 | |
this() // 调用本类无参数构造器 | |
this(参数) // 调用本类有参数构造器 | |
访问父类成员: | |
super.成员变量 // 访问父类成员变量 | |
super.成员方法 // 调用父类成员方法 | |
super() // 调用父类无参数构造器 | |
super(参数) // 调用父类有参数构造器 | |
注意:this 和 super 访问构造方法,只能用到构造方法的第一句,否则会报错 |
# 多态
在继承 / 实现情况下的一种现象,表现为:对象多态、行为多态
前提:
- 有继承 / 实现关系
- 存在父类引用子类对象
- 存在方法重写
⚠️ 注:多态是对象、行为的多态,Java 中的属性(成员变量)不谈多态
// 对象多态 | |
People p1 = new Student(); // 编译看左边,运行看右边 | |
People p2 = new Teacher(); | |
// 行为多态 | |
p1.run(); | |
p2.run(); |
好处:
在多态形式下,右边对象是解耦合的,更便于扩展和维护
定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利
public class Test2 {
public static void main(String[] args) {
Teacher t = new Teacher();
go(t);
Student s = new Student();
go(s);
}
public static void go(People p) {
p.run();
}
}
多态下不能直接调用子类的独有方法,需要类型转换
# 类型转换
自动类型转换:
父类 变量名 = new 子类();
People p = new Teacher();
强制类型转换:
子类 变量名 = (子类) 父类变量
Teacher t = (Teacher) p;
⚠️ 注:
存在继承 / 实现时,就可以进行强制类型转换,编译阶段不会报错
运行时,如果发现对象的真实类型与强转后的类型不同会报错(ClassCastException)
People p = new Teacher(); Student s = (Student) p; // java.lang.ClassCastException
使用 instanceof
关键字,判断当前对象的真实类型,再进行强转
格式:对象 instanceof 类型 | |
p instanceof Student |
# final
final 关键字是最终的意思,可以修饰(类、方法、变量)
- 修饰类:该类被称为最终类,不能被继承
- 修饰方法:该方法被称为最终方法,不能被重写
- 修饰变量:该变量只能被赋值一次
⚠️ 注:
- 修饰基本类型的变量:变量存储的数据不能被改变
- 修饰引用类型的变量:变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的
# 常量
使用 static final
修饰的成员变量,通常用于记录系统的配置信息
命名规范:建议使用大写英文单词,多个单词使用下划线连接起来
public class Constant { | |
public static final String SCHOOL_NAME = "xxx大学"; | |
} |
优势、执行原理:
- 代码可读性更好,可维护性也更好
- 程序编译后,常量会被 “宏替换”:出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的
# 抽象
用 abstract
关键字修饰类、成员方法
// 抽象类 | |
修饰符 abstract class 类名 { | |
// 抽象方法:只有方法签名,不能有方法体 | |
修饰符 abstract 返回值类型 方法名称(形参列表); | |
} |
注意事项、特点:
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 类该有的成员(成员变量、方法、构造器)抽象类都可以有
- 抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
应用场景:
- 用抽象类可以把子类中相同的代码,包括方法签名都抽上来,这样能更好地支持多态,以提高代码的灵活性
- 反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去继承实现,以方便系统的扩展
案例 🌰
// 父类:Animal 类 | |
public abstract class Animal { | |
private String name; | |
public abstract void cry(); | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
} | |
// 子类:Dog 类 | |
public class Dog extends Animal { | |
public void cry() { | |
System.out.println(getName() + "汪汪汪的叫~~"); | |
} | |
} | |
// 子类:Cat 类 | |
public class Cat extends Animal { | |
public void cry(){ | |
System.out.println(getName() + "喵喵喵的叫~~"); | |
} | |
} | |
// 测试类 | |
public class Test { | |
public static void main(String[] args) { | |
Animal a = new Cat("叮当猫"); | |
a.cry(); // 这时执行的是 Cat 类的 cry 方法 | |
} | |
} |
# 模板方法设计模式
解决方法中存在重复代码的问题
写法:
定义一个抽象类
在里面定义 2 个方法
模板方法:把相同代码放里面去
抽象方法:具体实现交给子类完成
建议使用 final
关键字修饰模板方法
- 模板方法是给对象直接使用的,不能被子类重写
- 一旦子类重写了模板方法,模板方法就失效了
案例 🌰
public abstract class C { | |
// 模板方法 | |
public final void sing() { | |
System.out.println("唱一首你喜欢的歌:"); | |
doSing(); | |
System.out.println("唱完了!"); | |
} | |
public abstract void doSing(); | |
} | |
public class A extends C { | |
@Override | |
public void doSing() { | |
System.out.println("《海盗船长》"); | |
} | |
} | |
public class B extends C { | |
@Override | |
public void doSing() { | |
System.out.println("《原本住在北极的人选择了冒险》"); | |
} | |
} | |
// 测试类 | |
public class Test { | |
public static void main(String[] args) { | |
B b = new B(); | |
b.sing(); | |
} | |
} |
# 接口
使用 interface
关键字定义出一种特殊的结构,JDK 8 之前,接口中只能定义成员变量和成员方法
public interface 接口名 { | |
成员变量(常量) | |
成员方法(抽象方法) | |
} |
接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类称为实现类
修饰符 class 实现类 implements 接口1, 接口2, 接口3, ... {
}
一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类
好处:
- 弥补了类单继承的不足,类可以同时实现多个接口
- 让程序可以面向接口编程,这样既不用关心实现的细节,也可以灵活方便的切换各种实现
写法案例
// 定义 B 接口 | |
public interface B { | |
void testb1(); | |
void testb2(); | |
} | |
// 定义 C 接口 | |
public interface C { | |
void testc1(); | |
void testc2(); | |
} | |
// 实现类 D | |
public class D implements B, C{ | |
@Override | |
public void testb1() { | |
} | |
@Override | |
public void testb2() { | |
} | |
@Override | |
public void testc1() { | |
} | |
@Override | |
public void testc2() { | |
} | |
} | |
// 测试类 | |
public class Test { | |
public static void main(String[] args) { | |
D d = new D(); | |
d.testb1(); | |
d.testc1(); | |
} | |
} |
# 接口案例
# JDK8 的新特性
新增了三种形式的方法,增强了接口的能力,更便于项目的扩展和维护
- 默认方法(实例方法):
default
修饰,使用实现类的对象调用 - 静态方法(类方法):
static
修饰,必须用当前接口名调用 - 私有方法:
private
修饰,jdk9 开始才支持,只能在接口内部被调用 - 都默认被
public
修饰
public interface A { | |
default void test1() { | |
... | |
} | |
private void test2() { | |
... | |
} | |
static void test3(){ | |
... | |
} | |
} |
# 接口的多继承
一个接口可以同时继承多个接口,作用:便于实现类去实现
public interface C extends B, A { | |
} |
注意事项(了解):
一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承
一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现
interface A {
void test();
}
interface B {
String test();
}
// 报错
interface C implements A, B {
}
class C implements A, B {
}
一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的
一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可
interface A {
default void test() {
System.out.println("A接口");
}
}
interface B {
default void test() {
System.out.println("B接口");
}
}
class C implements A, B {
@Override
public void test() {
// 重写方法
}
}
# 内部类
类中的五大成分之一,如果一个类定义在另一个类的内部,这个类就是内部类
应用场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类
public class Car { | |
// 内部类 | |
public class Engine { | |
} | |
} |
# 成员内部类
类中的一个普通成员,类似普通的成员变量和成员方法
public class Outer { | |
public class Inner { | |
} | |
} | |
// 创建对象的格式 | |
外部类名.内部类名 对象名 = new 外部类(...).new 内部类(...); | |
Outer.Inner in = new Outer().new Inner(); |
⚠️ 注:JDK 16 之前,成员内部类中不能定义静态成员,JDK 16 开始才支持定义静态成员
成员内部类的实例方法中,访问其他成员的特点:
- 可以直接访问外部类的实例成员、静态成员
- 可以拿到当前外部类对象,格式是:
外部类名.this
# 静态内部类
有 static
修饰的内部类,属于外部类自己持有
public class Outer { | |
public static class Inner { | |
} | |
} | |
// 创建对象的格式 | |
外部类名.内部类名 对象名 = new 外部类.内部类(...); | |
Outer.Inner in = new Outer.Inner(); |
特点:可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员(因为属于对象的成员)
# 局部内部类
定义在在方法中、代码块中、构造器等执行体中
鸡肋语法,仅作了解
public class Test { | |
public static void main(String[] args) { | |
} | |
public static void go() { | |
class A {} | |
abstract class B {} | |
interface C {} | |
} | |
} |
# 匿名内部类
一种特殊的局部内部类
匿名:指的是程序员不需要为这个类声明名字
new 父类/接口(参数值...) { | |
类体(一般是方法重写); | |
}; |
特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象
作用:用于更方便地创建一个子类对象
应用场景:通常作为一个参数传输给方法
public class Test { | |
public static void main(String[] args) { | |
// 方法一 | |
Swimming s = new Swimming() { | |
@Override | |
public void swim() { | |
System.out.println("游泳~~"); | |
} | |
}; | |
// 方法二 | |
go(new Swimming() { | |
@Override | |
public void swim() { | |
System.out.println("游泳~~"); | |
} | |
}); | |
} | |
public static void go(Swimming s) { | |
System.out.println("--- start ---"); | |
s.swim(); | |
System.out.println("--- end ---"); | |
} | |
} | |
interface Swimming { | |
void swim(); | |
} |
import javax.swing.*; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
public class Test2 { | |
public static void main(String[] args) { | |
// GUI 编程 | |
JFrame win = new JFrame("登录界面"); | |
JPanel panel = new JPanel(); // 桌布 | |
win.add(panel); | |
JButton btn = new JButton("登录"); | |
panel.add(btn); | |
// 绑定事件监听器 | |
btn.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
JOptionPane.showMessageDialog(win, "登录成功"); | |
} | |
}); | |
// 简化写法 | |
btn.addActionListener(e -> JOptionPane.showMessageDialog(win, "登录成功")); | |
win.setSize(400, 400); // 窗口大小 | |
win.setLocationRelativeTo(null); // 居中 | |
win.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // 关闭窗口退出程序 | |
win.setVisible(true); // 可见 | |
} | |
} |
# 枚举
枚举是一种特殊类
修饰符 enum 枚举类名 { | |
名称1, 名称2, ... ; | |
其他成员... | |
} |
- 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象
- 枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象
- 枚举都是最终类,不可以被继承
- 枚举类中,从第二行开始,可以定义类的其他各种成员
- 编译器为枚举类新增了几个方法,并且枚举类都是继承:
java.lang.Enum 类
的,从 enum 类也会继承到一些方法
public enum A { | |
X, Y, Z; | |
} | |
public class Test { | |
public static void main(String[] args) { | |
// 获取枚举 A 类的枚举项 | |
A a1 = A.X; | |
A a2 = A.Y; | |
A a3 = A.Z; | |
A[] as = A.values(); // 拿到全部对象 | |
A a4 = A.valueOf("Z"); | |
System.out.println(a4.name()); // Z | |
System.out.println(a4.ordinal()); // 2,索引值 | |
} | |
} |
反编译来验证:枚举项实际上是枚举类的对象
// 反编译命令 | |
// javap ./A.class | |
Compiled from "A.java" | |
public final class A extends java.lang.Enum<A> { | |
public static final A X = new A(); | |
public static final A Y = new A(); | |
public static final A Z = new A(); | |
public static A[] values(); | |
public static A valueOf(java.lang.String); | |
static {}; | |
} |
// 使用枚举类实现单例设计模式 | |
public enum B {} |
枚举深入
可以在枚举类中定义构造器、成员变量、成员方法
public enum A { | |
// 定义枚举项 | |
X, Y, Z("呆呆"); // 执行枚举类的带参数构造方法 | |
// 成员变量 | |
private String name; | |
// 定义空构造器 | |
public A() { | |
} | |
// 定义带参数构造器 | |
public A(String name) { | |
this.name = name; | |
} | |
// 成员方法 | |
public String getName() { | |
return name; | |
} | |
... | |
} |
# 应用场景
用来表示一组信息作为参数进行传输,做信息标志和分类
案例
需求:根据用户不同的身份选择,推荐不同的信息给用户
方式一:选择定义一个一个的常量来表示一组信息,并作为参数传输
优点:参数值不受约束
public class Constant { | |
public static final int GRIL = 0; | |
public static final int BOY = 1; | |
} | |
public class Test { | |
public static void main(String[] args) { | |
check(Constant.GRIL); | |
// check(1); | |
// check(11); | |
} | |
public static void check(int sex) { | |
switch (sex) { | |
case Constant.GRIL: | |
System.out.println("言情小说"); | |
break; | |
case Constant.BOY: | |
System.out.println("武侠小说"); | |
break; | |
} | |
} | |
} |
方式二:选择定义枚举表示一组信息,并作为参数传输
优点:代码可读性好,参数值得到了约束,对使用者更友好,建议使用
public enum Constant2 { | |
GRIL, BOY; | |
} | |
public class Test { | |
public static void main(String[] args) { | |
check(Constant2.GRIL); | |
} | |
public static void check(Constant2 sex) { | |
switch (sex) { | |
case GRIL: | |
System.out.println("言情小说"); | |
break; | |
case BOY: | |
System.out.println("武侠小说"); | |
break; | |
} | |
} | |
} |
# 泛型
定义类、接口、方法时,同时声明了一个或者多个类型变量(如: <E>
) ,称为泛型类、泛型接口,泛型方法、它们统称为泛型
作用:提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常
本质:把具体的数据类型作为参数传给类型变量
public class ArrayList<E> { | |
... | |
} |
# 自定义泛型类
修饰符 class 类名<类型变量, 类型变量, ...> { | |
} |
⚠️ 注:类型变量建议用大写的英文字母,常用的有:
E、T、K、V
等
自定义 MyArrayList 类
public class MyArrayList<E> { | |
private Object[] arr = new Object[10]; | |
private int size; // 记录当前位置 | |
public boolean add(E e) { | |
arr[size++] = e; | |
return true; | |
} | |
public E get(int index) { | |
return (E) arr[index]; | |
} | |
} |
# 自定义泛型接口
修饰符 interface 接口名<类型变量, 类型变量, ...> { | |
} |
案例
需求:一个系统要处理学生和老师的数据,需要提供 2 个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象
1、要有学生类和老师类
public class Teacher {} | |
public class Student {} |
2、定义一个 Data<T>
泛型接口
import java.util.ArrayList; | |
public interface Data<T> { | |
public void add(T t); | |
public ArrayList<T> getByName(String name); | |
} |
3、Teacher 对象的接口实现类
import java.util.ArrayList; | |
public class TeacherData implements Data<Teacher> { | |
@Override | |
public void add(Teacher teacher) { | |
} | |
@Override | |
public ArrayList<Teacher> getByName(String name) { | |
return null; | |
} | |
} |
4、Student 对象的接口实现类同理
# 泛型方法
修饰符 <类型变量, 类型变量, ...> 返回值类型 方法名(形参列表) { | |
} | |
// 例子 | |
public static <T> void test(T t) { | |
return t; | |
} | |
// 不是泛型方法 | |
public E get(int index) { | |
return (E) arr[index]; | |
} |
# 泛型限定
通配符:就是 ?
,使用泛型的时候代表一切类型
对泛型的数据类型进行范围的限制
<?>
表示任意类型- 上限:
<? extends 数据类型>
表示指定类型或者指定类型的子类 - 下限:
<? super 数据类型>
表示指定类型或者指定类型的父类
案例
class Animal {} | |
class Cat extends Animal {} | |
class Dog extends Animal {} | |
public class Test { | |
public static void main(String[] args) { | |
//test1 方法接收:任意类型 | |
ArrayList<Cat> list1 = new ArrayList<>(); | |
ArrayList<Dog> list2 = new ArrayList<>(); | |
ArrayList<String> list3 = new ArrayList<>(); | |
test1(list1); | |
test1(list2); | |
test1(list3); | |
//test2 方法接收:Animal 或者 Animal 的子类类型 | |
ArrayList<Animal> list4 = new ArrayList<>(); | |
ArrayList<Cat> list5 = new ArrayList<>(); | |
test2(list4); | |
test2(list5); | |
//test3 方法接收:Animal 或者 Animal 的父类类型 | |
ArrayList<Animal> list6 = new ArrayList<>(); | |
ArrayList<Object> list7 = new ArrayList<>(); | |
test3(list6); | |
test3(list7); | |
} | |
public static void test1(ArrayList<?> list) {} | |
public static void test2(ArrayList<? extends Animal> list) {} | |
public static void test3(ArrayList<? super Animal> list) {} | |
} |
# 泛型擦除
泛型是工作在编译阶段的,一旦程序编译成 class 文件,class 文件中就不存在泛型了,这就是泛型擦除
泛型不支持基本数据类型,只支持对象类型(引用数据类型)
// 若要添加 int 或 double 类型 | |
ArrayList<Integer> list1 = new ArrayList<>(); | |
list1.add(1); | |
ArrayList<Double> list2 = new ArrayList<>(); | |
list2.add(1.0); |