目录
abstract抽象类
- 对于父类方法不确定性问题,父类有方法要声明,但不需实现,由其子类实现,可将方法定义为抽象方法,有抽象方法的类需修饰为抽象类
- 用abstract修饰一个类,这个类就是抽象类:访问修饰符 abstract class 类名{}
- 用abstract修饰一个方法,这个类方法是抽象方法,抽象方法没有方法体:访问修饰符 abstract 返回类型 方法名(参数列表);
- 抽象类的价值更多作用在于设计,设计者设计好后,让子类继承并实现抽象类
- 抽象类在框架和设计模式中应用较多(考官爱考!)
抽象类细节
- 抽象类不能被实例化
- 抽象类可以没有抽象方法 ,有抽象方法的类一定是抽象类
- abstract只能修饰类和方法
- 抽象类本质还是类,可以有任意成员(非抽象方法、构造器、静态属性等)
- 抽象方法没有方法体
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它也是一个抽象类
- 抽象方法不能用private, final, static来修饰,因为这些关键字都和重写相矛盾(私有方法不能被子类覆盖)⭐⭐
抽象类小练习
很简单,试试吧~
package com.lili.abstract_;
public class AbstractExercise01 {
public static void main(String[] args) {
CommonEmployee lulu = new CommonEmployee("lulu", 11, 6000);
lulu.work();
Manager lili = new Manager("lili", 42, 10000, 6000);
lili.work();
}
}
package com.lili.abstract_;
public abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public abstract void work();
}
package com.lili.abstract_;
public class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工" + getName() + "工作中...");
}
}
package com.lili.abstract_;
public class Manager extends Employee{
private double bonus;
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理" + getName() + "工作中...");
}
}
抽象类最佳实践——模板设计模式
job() 方法有动态绑定机制(绑定运行类型,见java入门第七天)
package com.lili.abstract_;
public abstract class Template {
public abstract void job();
public void calculateTime() {
long start = System.currentTimeMillis();
job(); //方法有动态绑定机制(绑定运行类型)
long end = System.currentTimeMillis();
System.out.println("任务执行时间:" + (end - start));
}
}
package com.lili.abstract_;
public class CalSum extends Template{
@Override
public void job() {
long sum = 0;
for (long i = 0; i < 800000; i++) {
sum += i;
}
}
}
package com.lili.abstract_;
public class CalMul extends Template{
@Override
public void job() {
long res = 1;
for (long i = 1; i <= 100000; i++) {
res *= i;
}
}
}
package com.lili.abstract_;
public class TestTemplate {
public static void main(String[] args) {
CalSum cal1 = new CalSum();
cal1.calculateTime();
CalMul cal2 = new CalMul();
cal2.calculateTime();
}
}
接口
- 接口就是给出一些没有实现的方法,封装起来,某个类需要使用时,再具体实现。
- 在接口中,抽象方法可以省略abstract关键字。
interface 接口名{
//属性
//方法
}
class 类名 implements 接口名{
//属性
//方法
实现接口的抽象方法
}
在jdk7.0之前,接口中所以方法都没有方法体;
jdk8.0之后,接口中可以有静态方法、默认方法(需要用default关键字修饰),既可以具体实现方法。
接口细节
- 接口不能被实例化
- 接口中的所有方法都是public(可省略),接口中的抽象方法可省略abstract修饰
- 一个普通类实现接口,必须将接口中的所有方法都实现 (快捷键alt+enter)
- 抽象类实现接口时,可以不用实现接口的抽象方法
- 一个类可以实现多个接口
- 接口中的属性只能是final的,接口属性都是public static final修饰
- 接口名.属性名
- 接口不能继承类,但能继承多个别的接口
- 接口的修饰符只能是public或默认,与类一样
answer:
接口属性修饰符为public static final
语法正确
输出:
23 23 23
接口与继承⭐
- 当子类继承了父类,子类就自动拥有了父类的功能,如果子类需要扩展功能,可以通过接口的方式扩展
- 可以理解为实现接口是对java单继承机制的一种补充
- 继承的价值在于解决代码的复用性和可维护性
- 接口的价值在于设计好各种规范,让其他类去实现这些方法
- 接口比继承更加灵活
- 接口在一定程度上实现代码解耦(接口规范性+动态绑定)
接口的多态特性
- 接口的多态体现在接口类型的变量可以指向实现了接口的类的对象实例(类的多态体现在父类类型的变量可以指向继承父类的子类的对象实例)
- 接口多态参数(接口类型的形参可以接收实现接口的类实参)
- 接口多态数组(接口类型的数组可以存储实现接口的类型实例)
package com.lili.interface_;
public interface Usb {
void start();
void end();
}
package com.lili.interface_;
public class Phone implements Usb {
@Override
public void start() {
System.out.println("手机连接成功...");
}
@Override
public void end() {
System.out.println("手机断开连接...");
}
public void call() {
System.out.println("电话呼叫中...");
}
}
package com.lili.interface_;
public class Camera implements Usb{
@Override
public void start() {
System.out.println("相机连接成功...");
}
@Override
public void end() {
System.out.println("相机断开连接...");
}
}
package com.lili.interface_;
import com.lili.override_.Person;
public class InterfacePolyArr {
public static void main(String[] args) {
Phone phone = new Phone();
Camera camera = new Camera();
Usb[] usbs = {phone, camera};
for (int i = 0; i < usbs.length; i++) {
usbs[i].start();
usbs[i].end();
if(usbs[i] instanceof Phone) { //判断运行类型是否是Phone
((Phone) usbs[i]).call();
}
}
}
}
- 接口多态传递现象
接口小练习
answer:
有错误
错误在于x不明确,编译报错
想调用父类的x就使用super.x,想调用接口的x就使用A.x(接口的属性修饰符为public static final)!
内部类⭐
- 一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)
- 类的五大成员:属性、方法、构造器、代码块、内部类
- 内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
内部类分类 (4种)
- 定义在外部类局部位置上(比如方法中、代码块内)
- 局部内部类(有类名)
- 匿名内部类(没有类名,非常非常非常非常之重要!)
- 定义在外部类的成员位置上
- 成员内部类(没有static修饰)
- 静态内部类(用static修饰)
局部内部类
- 定义在外部类的局部位置上,如方法或代码块内
- 局部内部类可以直接访问外部类的所有成员,包括私有的
- 外部类要访问局部内部类方式:在其作用域内创建对象再访问
- 局部内部类地位与局部变量相同,不能用访问修饰符修饰,可以使用final修饰
- 局部内部类的作用域为定义它的方法体或代码块内
- 如果外部类和局部内部类的成员重名时,遵循就近原则,若想访问外部类成员,就用(外部类名.this.成员名)去访问。理解:外部类.this就是外部类的对象,哪个对象在调用就是哪个对象。
package com.lili.innerclass;
public class LocalInnerClass {
public static void main(String[] args) {
Outer01 outer01 = new Outer01();
outer01.m1();
}
}
class Outer01 {
private int n1 = 100;
public void m1() {
System.out.println("外部类的m1()被调用...");
class Inner01 {
private int n1 = 800;
public void m2() {
System.out.println("局部内部类的m2()被调用...");
//局部内部类可以直接访问外部类的所有成员,包括私有的
//外部类和局部内部类的成员重名时,遵循就近原则
System.out.println("局部内部类的n1 = " + n1);
//若想访问重名外部类成员,就用(外部类名.this.成员名)访问
System.out.println("外部类的n1 = " + Outer01.this.n1);
}
}
//外部类要访问局部内部类方式:在其作用域内创建对象再访问
Inner01 inner01 = new Inner01();
inner01.m2();
}
public Outer01() {
}
{
System.out.println("代码块被执行...");
class Inner02 {
public int n3 = 600;
}
}
}
匿名内部类⭐⭐
- 定义在外部类局部位置上,没有类名
- 匿名内部类本质是一个类,同时还是一个对象⭐
基本语法:
new 类或接口(参数列表) {
类体
};
annoymous--匿名的
- 当需要定义一个类来继承父类或实现接口,而这个类又只想使用一次时,可以使用匿名内部类
- 匿名内部类的类名由系统给出,对象.getClass()可以看到运行类型(系统给出的匿名内部类的类名)
- 匿名内部类只能使用一次
匿名内部类使用细节
1. 匿名内部类本质是一个类,同时还是一个对象,调用匿名内部类的方法有2种
2. 匿名内部类可以直接访问外部类的所有成员,包括私有的
3. 不能直接添加访问修饰符,地位是局部变量
4. 作用域:定义它的方法或代码块中
5. 外部其他类不能访问匿名内部类
6. 如果外部类和匿名内部类的成员重名时,遵循就近原则,若想访问外部类成员,就用(外部类名.this.成员名)去访问。
匿名内部类的最佳实践
当作实参直接传递
package com.lili.innerclass;
public class AnnoymousInnerClass {
public static void main(String[] args) {
f1(new IA() {
@Override
public void sayHello() {
System.out.println("你好!");
}
}
);
}
public static void f1(IA ia) {
ia.sayHello();
}
}
interface IA {
void sayHello();
}
匿名内部类小练习
package com.lili.innerclass;
public class InnerClassExercise02 {
public static void main(String[] args) {
CellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
CellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell {
void ring();
}
class CellPhone {
public static void alarmClock(Bell bell) {
bell.ring();
}
}
成员内部类
- 定义在外部类的成员位置上 ,且没有static修饰
- 可以直接访问外部类的所有成员,包括私有的
- 可以添加任意访问修饰符,因为其地位是成员
package com.lili.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
new Outer().m1();
}
}
class Outer {
private int age = 21;
public String name = "lulu";
class Inner { //成员内部类
public void f1(){
//可以直接访问外部类的所有成员,包括私有的
System.out.println("name = " + name + " age = " + age);
}
}
public void m1(){
Inner inner = new Inner();
inner.f1();
}
}
外部其他类使用成员内部类的两种方式:
①Outer.Inner inner1 = outer1.new Inner();等号右边相当于对象.属性
②在外部类中编写一个方法返回内部类的实例
如果外部类和成员内部类的成员重名时,遵循就近原则,若想访问外部类成员,就用(外部类名.this.成员名)去访问。
静态内部类
- 定义在外部类的成员位置上 ,有static修饰
- 可以直接访问外部类的所有静态成员,包括私有的,但不能访问非静态成员
- 可以添加任意访问修饰符
- 作用域为整个类
外部其他类访问静态内部类的2种方式:
①Outer2.Inner2 inner2 = new Outer2.Inner2(); 等号右边相当于类名.属性
②在外部类中编写一个方法返回内部类的实例 Outer2.Inner2 inner21 = Outer2.getInner2();
- 如果外部类和静态内部类成员重名,就近原则,访问外部类成员就用(外部类名.成员名)去访问。注意,没有this了!
内部类练习题
看看下段代码会输出什么?
new Test()会执行构造器,s1指向一个对象,s1的a = 10,s2指向又一个对象,s2的a = 5,并输出一个5 ;r也指向一个新的Inner对象,r的a = 5,输出一个5;
所以答案是输出2个5
枚举类(enumeration)
- 枚举是一组常量的集合
- 常量一般全大写
- 枚举有2种:
①自定义类实现枚举
实现步骤:
1.构造器私有化,防止直接new
2.不定义setter方法,只定义getter方法,防止枚举值被修改
3.对枚举对象使用public final static修饰,实现底层优化
②使用enum关键字实现枚举
1.使用enum替代class
2.如果有多个常量对象,用逗号间隔
3.枚举对象必须放在枚举类的行首
4.使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类(javap反编译)
5.如果使用无参构造器创建常量对象,则还可以省略实参列表和小括号(),只写常量名
- 因为用enum关键字时,会隐式的继承Enum类 ,因此可以使用Enum类的相关方法。
枚举小练习
package com.lili.enumeration_;
public class EnumExercise {
public static void main(String[] args) {
Week[] weeks = Week.values();
System.out.println("==所有的星期信息如下==");
for (Week week:weeks){
System.out.println(week);
}
}
}
enum Week {
MONDAY("星期一"),TUESDAY("星期二"),WEDNESDAY("星期三"),
THURSDAY("星期四"),FRIDAY("星期五"),SATURDAY("星期六"),
SUNDAY("星期日");
private String desc;
Week(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return desc;
}
}
enum细节
- 使用enum关键字后,就不能再继承其他类了,因为它已经隐式继承了Enum类(java是单继承机制)
- enum实现的枚举类,本质还是一个类,可以实现接口
package com.lili.enumeration_;
public class EnumExercise02 {
public static void main(String[] args) {
Music.DAOXIANG.playing();
Music.CRAZYOVERYOU.playing();
}
}
class A{
}
//1.使用enum关键字后,就不能再继承其他类了
//enum Music extends A{
//}
interface IPlaying{
void playing();
}
//2.enum实现的枚举类,本质还是一个类,可以实现接口
enum Music implements IPlaying{
DAOXIANG("稻香"),CRAZYOVERYOU("Crazy Over You");
private String name;
Music(String name) {
this.name = name;
}
@Override
public void playing() {
System.out.println("播放音乐" + this.toString() + "...");
}
@Override
public String toString() {
return name;
}
}