文章目录
接口
- 在jdk1.8之后允许定义带有default修饰的方法
- default修饰的方法带有方法体
- 需要创建实现类的对象有实现类对象调用
- 允许实现类重写接口中的default方法,但不强制必须重写
- 不重写则体现接口中方法的特点,重写则体现实现类重写之后的特点
- jdk1.8之后,允许接口中出现带有static修饰的方法
- 这种方法有方法体,这种方法由接口名调用,不能由对象名调用
1、接口的多继承 (extends)
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
定义父接口:
public interface Chargeable {
void charge();
void in();
void out();
}
定义子接口:
public interface UsbC extends Chargeable,Usb3 {
void reverse();
}
定义子接口的实现类:
public class TypeCConverter implements UsbC {
@Override
public void reverse() {
System.out.println("正反面都支持");
}
@Override
public void charge() {
System.out.println("可充电");
}
@Override
public void in() {
System.out.println("接收数据");
}
@Override
public void out() {
System.out.println("输出数据");
}
}
所有父接口的抽象方法都有重写。
方法签名相同的抽象方法只需要实现一次。
2、接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口的不同实现类:
public class Mouse implements Usb3 {
@Override
public void out() {
System.out.println("发送脉冲信号");
}
@Override
public void in() {
System.out.println("不接收信号");
}
}
public class KeyBoard implements Usb3{
@Override
public void in() {
System.out.println("不接收信号");
}
@Override
public void out() {
System.out.println("发送按键信号");
}
}
测试类
public class TestComputer {
public static void main(String[] args) {
Computer computer = new Computer();
Usb3 usb = new Mouse();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new KeyBoard();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new MobileHDD();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
}
}
3、接口中的default修饰方法
1、概念
- 接口提供一个默认实现的方法,并且不强制实现类重写此方法
- 默认方法使用default关键字来修饰
2、引入背景
- 当一个接口添加新方法时,需要所有的实现类都重写新方法,影响到了已有的实现类,可能导致应用崩溃
- 默认方法可以不强制重写,也不会影响到已有的实现类
3、使用
-
当一个实现类实现了多个接口,多个接口里都有相同的默认方法时,实现类必须重写该默认方法,否则编译错误
- 实现类自己重写逻辑
- 实现类使用super关键字指定使用哪个接口的默认方法
-
接口静态方法
接口中支持定义静态方法,将默认方法的default关键字换成static即可
-
继承类可以直接使用接口中的static方法,也可以创建对象后使用接口中的default方法
4、接口的特点总结
-
接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
-
声明接口用interface,接口的成员声明有限制:
(1)公共的静态常量
(2)公共的抽象方法
(3)公共的默认方法
(4)公共的静态方法
(5)私有方法(JDK1.9以上)
-
类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
-
接口可以继承接口,关键字是extends,而且支持多继承。
-
接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
-
接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过“接口名.静态方法名”进行调用。
小结:继昨天的所学习的借口后面的部分,本次介绍了借口可以多继承,何为多继承,就是一个类可以有多个接口,也就是说可以同时拥有多个接口的属性与方法,当然也有弊端,那就是接口的抽象构造方法都需要我们去进行一个重写。
枚举
概述
某些类型的对象是有限的几个,这样的例子举不胜举:
- 星期:Monday(星期一)…Sunday(星期天)
- 性别:Man(男)、Woman(女)
- 月份:January(1月)…December(12月)
- 季节:Spring(春节)…Winter(冬天)
- 支付方式:Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银行卡)、CreditCard(信用卡)
- 员工工作状态:Busy(忙)、Free(闲)、Vocation(休假)
- 订单状态:Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配货)、Delivered(已发货)、Checked(已确认收货)、Return(退货)、Exchange(换货)、Cancel(取消)
枚举类型本质上也是一种类,只不过是这个类的对象是固定的几个,而不能随意让用户创建。
在JDK1.5之前,需要程序员自己通过特殊的方式来定义枚举类型。
在JDK1.5之后,Java支持enum关键字来快速的定义枚举类型。
1、 jdk1.5之前的枚举
在JDK1.5之前如何声明枚举类呢?
- 构造器加private私有化
- 本类内部创建一组常量对象,并添加public static修饰符,对外暴露这些常量对象
示例代码:
public class Season{
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();
private Season(){
}
public String toString(){
if(this == SPRING){
return "春";
}else if(this == SUMMER){
return "夏";
}else if(this == AUTUMN){
return "秋";
}else{
return "冬";
}
}
}
public class TestSeason {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
2、jdk1.5之后的枚举
1、enum关键字声明枚举
【修饰符】 enum 枚举类名{
常量对象列表
}
【修饰符】 enum 枚举类名{
常量对象列表;
其他成员列表;
}
示例代码:
public enum Week {
MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
}
public class TestEnum {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
3、枚举类的要求和特点
枚举类的要求和特点:
- 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
- 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
- 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数,
- 如果枚举类需要的是有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
- 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
- JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
- 枚举类型如有其它属性,建议(不是必须)这些属性也声明为final的,因为常量对象在逻辑意义上应该不可变。
4、枚举类型常用方法
1.String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
2.String name():返回的是常量名(对象名)
3.int ordinal():返回常量的次序号,默认从0开始
4.枚举类型[] values():返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型,是一个静态方法
5.枚举类型 valueOf(String name):根据枚举常量对象名称获取枚举对象
小结:枚举的方法诞生,是因为我们在给一个值定义一个取值区间比如,春夏秋冬,月份,星期……
枚举的使用 ==》
提供若干公开静态常量的本类对象属性
私有化所有的构造方法
enum声明的枚举类的构造方法默认且只能是private
包装类
Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),那么基本数据类型的数据就需要用包装类来包装。
序号 | 基本数据类型 | 包装类(java.lang包) |
---|---|---|
1 | byte | Byte |
2 | short | Short |
3 | int | Integer |
4 | long | Long |
5 | float | Float |
6 | double | Double |
7 | char | Character |
8 | boolean | Boolean |
9 | void | Void |
1、装箱与拆箱
装箱:把基本数据类型转为包装类对象。
转为包装类的对象,是为了使用专门为对象设计的API和特性
拆箱:把包装类对象拆为基本数据类型。
转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等
基本数值---->包装对象
Integer obj1 = new Integer(4);//使用构造函数函数
Integer obj2 = Integer.valueOf(4);//使用包装类中的valueOf方法
包装对象---->基本数值
Integer obj = new Integer(4);
int num1 = obj.intValue();
JDK1.5之后,可以自动装箱与拆箱。
注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。
Integer i = 1;
Double d = 1;//错误的,1是int类型
2、包装类的特点和使用方式
1、包装类缓存对象
包装类 | 缓存对象 |
---|---|
Byte | -128~127 |
Short | -128~127 |
Integer | -128~127 |
Long | -128~127 |
Float | 没有 |
Double | 没有 |
Character | 0~127 |
Boolean | true和false |
Integer a = 1;
Integer b = 1;
System.out.println(a == b);//true
Integer i = 128;
Integer j = 128;
System.out.println(i == j);//false
Integer m = new Integer(1);//新new的在堆中
Integer n = 1;//这个用的是缓冲的常量对象,在方法区
System.out.println(m == n);//false
Integer x = new Integer(1);//新new的在堆中
Integer y = new Integer(1);//另一个新new的在堆中
System.out.println(x == y);//false
Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1==d2);//false 比较地址,没有缓存对象,每一个都是新new的
2、类型转换问题
Integer i = 1000;
double j = 1000;
System.out.println(i==j);//true 会先将i自动拆箱为int,然后根据基本数据类型“自动类型转换”规则,转为double比较
Integer i = 1000;
int j = 1000;
System.out.println(i==j);//true 会自动拆箱,按照基本数据类型进行比较
Integer i = 1;
Double d = 1.0
System.out.println(i==d);//编译报错
3、包装类对象不可变
public class TestExam {
public static void main(String[] args) {
int i = 1;
Integer j = new Integer(2);
Circle c = new Circle();
change(i,j,c);
System.out.println("i = " + i);//1
System.out.println("j = " + j);//2
System.out.println("c.radius = " + c.radius);//10.0
}
/*
* 方法的参数传递机制:
* (1)基本数据类型:形参的修改完全不影响实参
* (2)引用数据类型:通过形参修改对象的属性值,会影响实参的属性值
* 这类Integer等包装类对象是“不可变”对象,即一旦修改,就是新对象,和实参就无关了
*/
public static void change(int a ,Integer b,Circle c ){
a += 10;
// b += 10;//等价于 b = new Integer(b+10);
c.radius += 10;
/*c = new Circle();
c.radius+=10;*/
}
}
class Circle{
double radius;
}
小结:在我的理解中就是,包装类的出现是让类型进行一个转换,让基础类型转换引用类型,引用类型转换基础。而基础类型是可以在计算机底层进行一个计算的,那我们就可以让我们的引用类型返回的数值与基础类型进行一个比较。同理,我们可以让基础类型与引用类型进行判断大小,相等。甚至我们可以进行传参,让一个需要引用类型的参数传入包装后的基础类型。
内部类
概述
1、什么是内部类?
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
2、为什么要声明内部类呢?
总的来说,遵循高内聚低耦合的面向对象开发总原则。便于代码维护和扩展。
具体来说,当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员。
3、内部类都有哪些形式?
根据内部类声明的位置(如同变量的分类),我们可以分为:
(1)成员内部类:
- 静态成员内部类
- 非静态成员内部类
(2)局部内部类
- 有名字的局部内部类
- 匿名的内部类
1、成员内部类
如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。
语法格式:
【修饰符】 class 外部类{
【其他修饰符】 【static】 class 内部类{
}
}
1、静态内部类
有static修饰的成员内部类叫做静态内部类。它的特点:
- 和其他类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
- 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
- 只可以在静态内部类中使用外部类的静态成员
- 在静态内部类中不能使用外部类的非静态成员哦
- 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别
- 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象(通常应该避免这样使用)
其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。
2、非静态成员内部类
没有static修饰的成员内部类叫做非静态内部类。非静态内部类的特点:
- 和其他类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量。
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
- 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
- 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
- 在外部类的静态成员中不可以使用非静态内部类哦
- 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
- 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象(通常应该避免这样使用)
- 如果要在外部类的外面使用非静态内部类的对象,通常在外部类中提供一个方法来返回这个非静态内部类的对象比较合适
- 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象
public class TestMemberInnerClass {
public static void main(String[] args) {
Outer.outMethod();
System.out.println("-----------------------");
Outer out = new Outer();
out.outFun();
System.out.println("####################################");
Outer.Inner.inMethod();
System.out.println("------------------------");
Outer.Inner inner = new Outer.Inner();
inner.inFun();
System.out.println("####################################");
Outer outer = new Outer();
// Outer.Nei nei = outer.new Nei();
Outer.Nei nei = out.getNei();
nei.inFun();
}
}
class Outer{
private static String a = "外部类的静态a";
private static String b = "外部类的静态b";
private String c = "外部类对象的非静态c";
private String d = "外部类对象的非静态d";
static class Inner{
private static String a ="静态内部类的静态a";
private String c = "静态内部类对象的非静态c";
public static void inMethod(){
System.out.println("Inner.inMethod");
System.out.println("Outer.a = " + Outer.a);
System.out.println("Inner.a = " + a);
System.out.println("b = " + b);
// System.out.println("c = " + c);//不能访问外部类和自己的非静态成员
// System.out.println("d = " + d);//不能访问外部类的非静态成员
}
public void inFun(){
System.out.println("Inner.inFun");
System.out.println("Outer.a = " + Outer.a);
System.out.println("Inner.a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
// System.out.println("d = " + d);//不能访问外部类的非静态成员
}
}
class Nei{
private String a = "非静态内部类对象的非静态a";
private String c = "非静态内部类对象的非静态c";
public void inFun(){
System.out.println("Nei.inFun");
System.out.println("Outer.a = " + Outer.a);
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("Outer.c = " + Outer.this.c);
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}
public static void outMethod(){
System.out.println("Outer.outMethod");
System.out.println("a = " + a);
System.out.println("Inner.a = " + Inner.a);
System.out.println("b = " + b);
// System.out.println("c = " + c);
// System.out.println("d = " + d);
Inner in = new Inner();
System.out.println("in.c = " + in.c);
}
public void outFun(){
System.out.println("Outer.outFun");
System.out.println("a = " + a);
System.out.println("Inner.a = " + Inner.a);
System.out.println("b = " + b);
System.out.println("c = " + c);
System.out.println("d = " + d);
Inner in = new Inner();
System.out.println("in.c = " + in.c);
}
public Nei getNei(){
return new Nei();
}
}
2、局部内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
局部内部类的特点:
- 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量
- 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法
- 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
- JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
- 为什么在局部内部类中使用外部类方法的局部变量要加final呢?考虑生命周期问题。
public class TestLocalInner {
public static void main(String[] args) {
Runner runner = Outer.getRunner();
runner.run();
System.out.println("-------------------");
Outer.outMethod();
System.out.println("-------------------");
Outer out = new Outer();
out.outTest();
}
}
class Outer{
private static String a = "外部类的静态a";
private String b = "外部类对象的非静态b";
public static void outMethod(){
System.out.println("Outer.outMethod");
final String c = "局部变量c";
class Inner{
public void inMethod(){
System.out.println("Inner.inMethod");
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
System.out.println("out.local.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + b);//可以,因为outTest是非静态的
}
}
Inner in = new Inner();
in.inMethod();
}
public static Runner getRunner(){
class LocalRunner implements Runner{
@Override
public void run() {
System.out.println("LocalRunner.run");
}
}
return new LocalRunner();
}
}
interface Runner{
void run();
}
3、匿名内部类
当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?
(1)编写类,继承这个父类或实现这个接口
(2)重写父类或父接口的方法
(3)创建这个子类或实现类的对象
这里,因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。
new 父类(【实参列表】){
重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。
注意:
匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:
- 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
- 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final
思考:这个对象能做什么呢?
(1)使用匿名内部类的对象直接调用方法
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
(2)通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
(3)匿名内部类的对象作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}
小结:内部类是指在原有类内再声明类,申明出来的类也会产生一个单独的class结尾的文件。成员内部类是指与该类的属性与方法一般,在类内直接申明去使用,该类可分为静态与普通,与我们的抽象类差不多,静态的只能调用静态,而普通则能调用静态与普通。接着我们来到了局部内部类,这则是与我们的参数方法一般,只不过是参数换成了类,同意该参数能调取本类的方法,但是,在其中也有分静态与普通,同理它们也如内部类一样,静态只能调取静态,普通能调取静态与普通。最后我们来到了匿名内部类,匿名内部类我理解的不是很清晰,能知道的就是当我们只需要使用到一次时,即后面的代码块里我们无需用到,这时我们使用匿名类就会使我们免去起名字,减少代码的作用
总结:今天的内容还是我们的面向对象,当然到这里也即将结束了。今天的内容分为接口的完结,枚举,包装类,内部类。接口就是让多个类使用,枚举则是限制区间,包装类则是进行一个类型转换,最后的内部类则是告诉我们可以在内部进行一个申明,当然使用就是进行一个打点。
每日金句:
吾生也有涯,而知也无涯。 —————庄子·养生主