抽象类
由于继承这个显著特点,我们可以将子类设计的更加具体,而父类更加一般化,通用化。
抽象类的特点:
1.作为父类,里面的方法不能满足任何一个子类的需,那么提供的逻辑根本就用不上,那么就不添加方法体 ,此时这个方法需要使用关键字abstract来修饰,表示为抽象方法,而抽象方法也要使用abstract来修饰,所以,该类就是抽象类。
2.抽象类中可以没有抽象方法
3.抽象类里面可以提供构造方法,但是不能被调用,即不能new对象,因为没有意义。
4.抽象类可以被继承,但是子类必须重写所有的抽象方法,否则子类也必须是抽象的。
public class 子类型名 extends 抽象类名{}
抽象类的意义:
1) 为其子类提供一个公共的父类型
2) 封装子类中重复的内容,如成员变量和方法
3) 定义抽象方法,子类虽然有不同的实现逻辑,但该方法的定义却是一致的。
public abstract class Person { private String name; private int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public void sleep(){ System.out.println(name+"-正在睡觉中------"); } public void eat(){ System.out.println("都可以吃饭"); } public abstract void work(); }
public abstract class Student extends Person { private String sid; public Student(){} // public void work(){ // System.out.println("---学生的工作是学习---"); // } public abstract void doHomeWork(); }
public class Teacher extends Person { private String tid; public Teacher(){} public void work(){ System.out.println("---老师的工作是教学---"); } }
public class Test { public static void main(String[] args) { Teacher teacher = new Teacher(); //Student student = new Student(); testWork(teacher); //testWork(student); //测试抽象类的构造器可以new对象吗? 不能 //Person p1 = new Person("小红",23); } public static void testWork(Person p){ p.work(); } }
接口
接口,可以理解为是特殊的抽象类,里面的方法全都是抽象方法,里面的成员变量都是常量。
因为JAVA是单继承的,而JAVA为了完善多继承的概念,引用了接口。类可以实现多个接口,来达到多继承的效果。
接口的特点:
- 接口的关键字,是interface,不再是class
- 子类是实现接口的,关键字是implements
public class 子类型名 implements 接口名,接口名...{
}
实现接口时,所有的抽象方法都应该重写,否则该类是抽象类。
- 子接口与父接口是继承关系:
pubilc interface 子接口名 extends 父接口名{
//接口里的变量就是常量,默认使用public static final修饰,可省略。常量注意赋值。
//接口里的抽象方法,默认使用public abstract来修饰,可以省略
}
- 接口不能提供构造器,没有意义。
实现接口:
- 1) 与继承不同,一个类可以实现多个接口。接口间使用逗号分开。
- 2) 使用关键字implements进行实现,必须实现接口中的所有抽象方法
- 3) 若一个类中没有全部实现接口中的抽象方法,那么该类需要使用abstract声明成抽象类
接口间的继承:
- 1)接口之间可以存在继承关系,即一个接口通过关键字extends可以继承另一个接口。
- 2)子接口继承了父接口中的所有抽象方法
接口在1.8之后的新特性:
在 jdk 1.8 之后,给接口添加了若干个新的特性。
1)default
给接口中的方法,添加默认的实现方式。此时,这个方法,可以在实现类中实现,也可以不在实现类 中实现。如果,实现类没有重写实现这个方法,以接口中的实现为准。
public default修饰的成员方法
给子类型的对象来使用的,不需要每一种子类进行重写。
interface Calculate { int calculate(int a, int b); public default void testMethod(int a, int b) { System.out.println("calculate: " + (a + b)); } }
2)static
⽤static修饰的接口中的方法,表示是静态的方法。此时,这个方法必须要添加一个实现。这个方法,在实现类中不能重写实现。只能通过接口来调用。
在接口中可以提供静态方法,作为工具方法来使用。
不同的子类都能继承过去,子类名调用。
interface Calculate { int calculate(int a, int b); public static void show() { System.out.println("Calculate Show"); } }
两道练习题:
代码:
public class Computer { private USB usb1; private USB usb2; public void setUsb1(USB usb1){ this.usb1 = usb1; usb1.charge(); } public void setUsb2(USB usb2){ this.usb2 = usb2; usb2.charge(); } }
public class KeyBoard implements USB{ private String brand; @Override public void charge() { System.out.println("---键盘正在充电中---"); } @Override public String getInfo() { return brand; } }
public class Mouse implements USB { private String color; @Override public void charge() { System.out.println("---鼠标正在充电中---"); } @Override public String getInfo() { return "鼠标的颜色:"+color; } }
/** * 接口 */ public interface USB { /** * 充电功能 */ void charge(); /** * 获取信息功能 * @return */ String getInfo(); }
public class Program { public static void main(String[] args) { //创建一个电脑对象 Computer computer = new Computer(); //创建一个键盘对象 KeyBoard keyBoard = new KeyBoard(); //创建一个鼠标对象 Mouse mouse = new Mouse(); //将鼠标插入到电脑上 computer.setUsb1(mouse); //将键盘插入到电脑上 computer.setUsb2(keyBoard); } }
代码:
public interface Employee { void work(); void punchCard(); }
public interface Family { void cook(); void wash(); }
public class Person implements Employee,Family,Comparable{ private String name; private int age; public Person(){} public Person(String name, int age) { this.name = name; this.age = age; } public void play(){ System.out.println("---人喜欢娱乐-----"); } public String toString(){ return name+","+age; } @Override public void work() { System.out.println("---喜欢工作----"); } @Override public void punchCard() { System.out.println("---上下班需要打卡----"); } @Override public void cook() { System.out.println("---正在做饭---"); } @Override public void wash() { System.out.println("---正在洗涮---"); } /** * 默认是升序: * 返回值 是负数 说明前面的小,后面的大 不需要交换 * 返回值 0 相等 不需要交换 * 返回值 是正数 说明前面的大,后面的小 需要交换 * 想要降序: * * 升序: this-other * 降序: other-this * @param o the object to be compared. * @return */ @Override public int compareTo(Object o) { // Person p = (Person)o; // return p.name.compareTo(this.name); return 0; } }
import java.util.Arrays; public class Pragram { public static void main(String[] args) { Person p = new Person("xiaoming", 23); Employee e = p; e.punchCard(); e.work(); Family f = p; f.cook(); f.wash(); System.out.println("-------------------"); testPlay(e); Person p2 = new Person("xiaohong", 24); //将这两个人放进数组中 Person[] ps = new Person[]{p,p2}; Arrays.sort(ps); System.out.println(Arrays.toString(ps)); } public static void testPlay(Employee e){ //调用cook方法 if(e instanceof Family){ Family f = (Family)e; f.cook(); } //调用play方法 if(e instanceof Person){ Person p = (Person)e; p.play(); } } }
常用接口:
Serializable序列化接口:
系统类库提供好的一个接口。当涉及到数据传输时,比如将内存的对象保存到磁盘,磁盘上的文件变成内存中的对象,或者对象在两台电脑之间传输。那么该对象的类必须实现序列化接口。否则传输失败。
该接口就是一个规范,里面没有任何东西,源码如下:
public interface Serializable {
}
比如Person类的对象想要进行传输:
public class Person implements Serializable {
//.....person的代码
}
Comparable接口:
汉语翻译成: 可比的,可比较的,是一个形容词。 当一个类的多个对象之间想要进行比较时,比如排序等操作,那么类必须实现该接口,然后自定义比较规则。否则不能比较,会报如下错误:
Exception in thread "main" java.lang.ClassCastException: xxx.类型名 cannot be cast to java.lang.Comparable
源码如下:
public interface Comparable<T> {
public int compareTo(T o);
}
如何自定义比较规则? 即重写接口里提供好的compareTo方法:
默认升序: 就使用this的相关属性-传入的对象o的相关属性
降序: -(就使用this的相关属性-传入的对象o的相关属性) 拆开括号 :传入的对象o的相关属性-this的相关属性返回值时负数:前面小后面大
返回值0:相等
返回值正数:前面大后面小
Comparator接口:
汉语翻译成:比较器,比较仪。用于在compareble的基础上去修改比较规则。
内部类
定义在一个类内部的类,就是内部类。内部类可以分为:成员内部类、静态内部类、局部内部类、匿名内部类。
成员内部类:
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且没有用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程,需要先实例化外部类对象,再使用外部类对象进行内部类的实例化
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
/** * 成员内部类 */ public class Program { public static void main(String[] args) { // 1. 实例化一个外部类的对象 Outer outer = new Outer(); outer.name = "outer"; // 2. 通过外部类的对象,实例化内部类对象 Outer.Inner inner = outer.new Inner(); inner.name = "inner"; inner.show("hello"); } } /** * 外部类 */ class Outer { public String name; public int age1; // 这个类,由于是书写在一个类的内部,因此这个类被称为--内部类 // 这个类,由于是写在Outer类中,和类中的属性、方法平级,可以称为是一个类的成员。 // 并且,这个类没有使用 static 修饰,这样的类,被称为 -- 成员内部类 class Inner { Inner() { System.out.println("实例化了一个Inner的对象"); } public String name; public int age2; public void show(int age3) { System.out.println("参数age3: " + age3); System.out.println("内部类属性age2: " + age2); System.out.println("外部类属性age1: " + age1); } public void show(String name) { System.out.println("参数name: " + name); System.out.println("内部类属性name: " + this.name); System.out.println("外部类属性name: " + Outer.this.name); } } }
静态内部类:
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且使用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程中,直接使用 new实例化一个外部类 .内部类对象即可。
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
/** * 静态内部类 */ public class Program { public static void main(String[] args) { // 1. 实例化静态内部类对象的时候,不需要借助外部类对象的。 // Outer.Inner inner = new Outer.Inner(); // 2. 如果已经事先导包了,还可以直接进行实例化 Inner inner = new Inner(); } } class Outer { public String name; // 因为这个类,书写与Outer类内,并且是用static修饰的类 // 这样的类,被称为 -- 静态内部类 static class Inner { Inner() { System.out.println("实例化了一个Inner对象"); } public String name; public void show(String name) { System.out.println("参数name: " + name); System.out.println("内部类属性name: " + this.name); System.out.println("外部类属性name,此时无法访问"); } } }
局部内部类:
定义在某一个代码段中的中。
1、没有访问权限修饰符。
2、在当前方法中,直接实例化即可
3、内部类编译后,也会生成.class字节码文件。格式:外部类$序号内部类 .class
方法里,可重名,有作用范围
public class Program { public static void main(String[] args) { int a; // 写在某一个局部代码段中(例如:方法中) // 这个类,只能在当前的方法中使用 class Inner { } Inner inner = new Inner(); test(); } static void test() { class Inner { } } }
匿名内部类:
- 没有名字的内部类,内部类,通常是需要配合其他的类或者接口一块使用的
- 在方法中定义的没有名字的子类型对象,然后将对象的地址存到子类型的父类型的变量中
- 通过变量来操作这个没有名字的子类型对象
格式:
父类型名 变量名=new 父类型名(){
//重写父类里的方法逻辑
};
- 匿名内部类可以对接口,抽象类,普通父类进行定义
// 实例化了一个匿名子类对象,并向上转型为父类类型 Person xiaoming = new Person() { // 这里,其实就是一个内部类的类体 // 这个类,因为没有名字,因此是一个匿名内部类 // 这个类,是继承自 Person类的 // 因此,这个类是Person类的匿名子类 @Override public void work() { System.out.println("搬砖 "); } };
在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向 上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。
匿名内部类也会生成 .class字节码文件,命名格式 : 外部类$序号 .class
枚举
枚举是一种特殊的引用数据类型,是一个被命名的整型常数的集合,用于声明一组带标识符的常数。
背景:枚举是在JDK1.5以后引入的。
主要用途是:将一组常量,也可以说成是一组离散值组织起来。
枚举定义:
1.自定义类实现枚举
类内部创建一组对象,通常使用public static final关键字共同修饰,对外进行暴露
枚举对象名通常全部都会大写,这是常量的命名规范
可以提供属性,属性应使用private final共同修饰
将构造器私有化
属性,可以提供getXXX方法,但是不需要提供setXxx方法,属性应该是只读的。
//自定义枚举类 public class Season{ //1.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //2.私有化类的构造器,并给对象属性赋值 private Season(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //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("冬天","冰天雪地"); //4.其他诉求1:获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } //4.其他诉求1:提供toString() @Override public String toString() { return "Season{" + "seasonName='" + seasonName + '\'' + ", seasonDesc='" + seasonDesc + '\'' + '}'; } } public class SeasonTest { public static void main(String[] args) { Season spring = Season.SPRING; System.out.println(spring); } }
2.enum关键字实现枚举
使用enum关键字定义一个枚举,默认会继承java.lang.Enum类,而且是一个final类,因此不能再继承其他类
必须在枚举类的第一行声明枚举类对象。有多个枚举对象时,使用逗号隔开,最后一个用分号结尾
可以提供私有的属性
可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须显式调用构造器
如果使用无参构造器创建枚举对象,则定义对象时,小括号可以省略
public enum Week { //MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; 周一,周二,周三,周四,周五,周六,周日; public static void main(String[] args) { System.out.println(Week.周五); } }
使用构造器:
public enum Week { 周一,周二,周三,周四,周五,周六,周日; public static void main(String[] args) { Person p = new Person(); p.age = 23; p.name = "小物"; //获取季节这个枚举中的一个值 Season a = Season.AUTUMN; System.out.println(a); } } class Person{ String name; int age; Week[] week; } enum Season{ SPRING("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天"); private String describe; private Season(String describe){ this.describe =describe; } }
重写方法形式:
public enum Demo1 { LEFT{ @Override public void show() { System.out.println("左:重写了show方法"); } },RIGHT{ @Override public void show() { System.out.println("右:重写了show方法"); } },FRONT{ @Override public void show() { System.out.println("前:重写了show方法"); } },BEHIND{ @Override public void show() { System.out.println("后:重写了show方法"); } }; public abstract void show(); }
public class Test { public static void main(String[] args) { Demo d=Demo.BEHIND; System.out.println(d);//BEHIND System.out.println("-------------------"); String name = Demo.BEHIND.getName(); System.out.println(name); String name1 = Demo.FRONT.getName(); System.out.println(name1); System.out.println("-------------------"); Demo1 d1=Demo1.RIGHT; d1.show();//右:重写了show方法 Demo1 d2=Demo1.BEHIND; d2.show();//后:重写了show方法 } }
enum常用方法:
说明:使用关键字enum时,会隐式继承Enum类,这样我们就可以使用Enum类相关的方法
/*
演示Enum类的各种方法的使用
*/
public class EnumMethod {
public static void main(String[] args) {
//使用Season2枚举类,来演示各种方法
Season2 autumn = Season2.AUTUMN;
//1.输出枚举对象的名字
System.out.println(autumn.name());
//2.ordinal()输出的是该枚举对象的次序(编号),从0开始编号
//AUTUMN枚举对象是第三个,因此输出2
System.out.println(autumn.ordinal());
//3.从反编译可以看出values方法,返回的是Season2[],该数组含有定义的所有枚举对象
Season2[] values = Season2.values();
//遍历取出枚举对象
for(Season2 season: values){ //增强for循环
System.out.println(season);
}
//4.valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//执行流程
//1.根据输入的"AUTUMN"到Season2的枚举对象去查找
//2.如果找到了就返回,如果没有找到就报错
Season2 autumn1 = Season2.valueOf("AUTUMN");
System.out.println("autumn1" + autumn1);
System.out.println(autumn == autumn1);
//5.compareTo:比较两个枚举常量,比较的就是编号
//解读
//1.就是把Season2.AUTUMN枚举对象的编号 和 Season2.SUMMER枚举对象的编号进行比较
//2.就是Season2.AUTUMN的编号 - Season2.SUMMER的编号
/*
public final int compareTo(E o) {
return self.ordinal - other.ordinal;
}
*/
System.out.println(Season2.AUTUMN.compareTo(Season2.SUMMER));
}
}