1.抽象类
由于继承这个显著特点,我们可以将子类设计的更加具体,而父类更加一般化,通用化。父类可以封装不同子类的共同特征或者共同行为.而有的时候,父类中封装的方法无法具体完成子类中需要的逻辑,因此我们可以将此方法设计成抽象方法,即使用关键字abstract进行修饰。而有抽象方法的类,也必须使用abstract关键字进行修饰,因此我们称之为抽象类。
1.1抽象方法的特点
(1)有abstract修饰的方法为抽象方法,抽象方法没有方法体,不需要{},只需要使用;结尾
(2)若类中包含至少一个抽象方法,那么该类必须使用abstract关键字声明为抽象类,因为抽象类是用来继承的,所以final关键字不能修饰抽象类。
(3)在一个抽象类里可以没有抽象方法。
(4)抽象类不能实例化,所以不能使用new关键字调用构造器,所以即使可以提供构造器,但是也没有任何意义。但是子类可以在自己的构造器内用super();来调用父类的构造器。
(5)如果一个类继承了抽象类,那么必须重写里面所有的抽象方法,否则必须声明子类也为抽象类。
1.2抽象类的意义
(1)抽象类为子类提供一个公共的父类。
(2)封装子类中重复的内容,比如成员变量和方法。这样可以被所有子类继承和调用,减少代码的重复性,提高了代码的可维护性。
(3)定义抽象方法,子类虽然有不同的实现逻辑,但该方法的定义却是一致的
案例代码:
public abstract class Animal {
private String name;
private int age;
private String color;
public Animal(){}
public Animal(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
//抽象的方法,由子类实现
public abstract void noise();
}
class Dog extends Animal {
// private String name;
// private int age;
// private String color;
public Dog(){}
public Dog(String name, int age, String color) {
super(name, age, color);
}
public void noise(){
System.out.println("Woof!");
}
public void lookHouse(){
System.out.println("Looking house!");
}
}
class Cat extends Animal{
// private String name;
// private int age;
// private String color;
public Cat(){}
public Cat(String name, int age, String color) {
super(name, age, color);
}
public void noise(){
System.out.println("Meow!");
}
public void getMouse(){
System.out.println("Get a mouse!");
}
}
//如果不想使用抽象方法类里的抽象呢方法,该类需要使用abstrct修饰。
abstract class Duck extends Animal{
public void swim(){
}
}
public class AnimalTest {
public static void main(String[] args) {
//直接定义一个cat类型,调用cat里的功能
Cat cat = new Cat();
cat.noise();
//使用多态的向上造型
Animal a = new Dog();
a.noise();
//下面代码编译错误,因为抽象类不能使用new关键字实例化
// Animal b = new Animal();
}
}
2.接口
有的时候,我们需要从几个不相关的类中派生出一个子类,继承他们的所有成员变量和方法,但是java不支持多继承。此时,我们可以使用接口,来达到多继承的效果
2.1接口特点
(1)使用interface关键字定义接口
(2)接口里面可以提供成员属性,默认使用public static final修饰,并且只能是常量
(3)接口里面不能有构造器,因为接口没办法使用new实例化,提供构造器没有任何意义
(4)接口里面可以提供成员方法,默认使用public abstract修饰,不能有方法体
(5)一个类想要实现接口,需要使用implements关键字,必须实现接口中的所有抽象方法。
(6)一个类实现接口时,需要实现里面所有的抽象方法,否则需要使用abstract关键字修饰class。
(7)一个类可以实现多个接口,接口之间使用逗号分开。
(8)接口可以继承多个接口,使用extends关键字
(9)子接口拥有父接口的所有抽象方法,也可以提供自己独有的抽象方法。
2.2JDK1.8以后的新特性
1.提供了默认方法:使用default关键字修饰的具有方法体的方法 -该方法默认使用public修饰,可以省略public关键字 -该方法逻辑不能满足子类时,子类可以重写该方法 2.提供了静态方法: -使用static关键字修饰的具有方法体的方法 -该方案默认使用public修饰,可以省略public关键字
2.3常用接口
1)Serializable序列化接口
系统类库提供好的一个接口。当涉及到数据传输时,比如将内存的对象保存到磁盘,磁盘上的文件变成内存中的对象,或者对象在两台电脑之间传输。那么该对象的类必须实现序列化接口。否则传输失败。该接口就是一个规范,里面没有任何东西,源码如下:
public interface Serializable {
}
比如Person类的对象想要进行传输:
public class Person implements Serializable {
//.....person的代码
}
2)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的相关属性
降序:传入的对象o的相关属性-this的相关属性
3)Comparator接口
用于在compareble的基础上去修改比较规则。
3.枚举
枚举是一种特殊的引用数据类型,是一个被命名的整型常数的集合,用于声明一组带标识符的常数,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、 WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。枚举的主要用途是将一组常量,也可以说成是一组离散值组织起来。
3.1自定义实现
(1)提供一些可以向外界暴露的该类型的对象,注意枚举对象的名称都会大写。
(2)构造器需要私有化
(3)可以提供属性,用来描述对象的信息,需要使用private final修饰
(4)给属性提供get方法向外界暴露,但是不需要提供set方法,因为只读。
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("冬天","冰天雪地");
3.2enum关键字实现
1)使用enum定义一个枚举,默认继承java.lang.Enum类,这是一个final类,所以不能再继承其他类。
2)必须在枚举类的第一行声明枚举类对象,当有多个对象的时候,使用逗号隔开,最后以分号结尾
3)可以提供属性但是必须私有化
4)可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须显式调用构造器,如果使用无参构造器创建对象时小括号可以省略。
public enum Season {
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
private String name;
private String desc;
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
public static void main(String[] args) {
Season seasons = Season.AUTUMN;
System.out.println(seasons.name);
}
}
还可以重写方法形式
public enum Direction implements InterA {
FRONT("前"){
public void showInfo() {
System.out.println("向前走,如箭离弦,永不回头");
}
},
BACK("后"){
@Override
public void showInfo() {
System.out.println("向后走");
}
},
LEFT("左"){
public void showInfo() {
System.out.println("向左走");
}
},
RIGHT("右"){
public void showInfo() {
System.out.println("向右走");
}
};
private String name;
private Direction(String name){
this.name = name;
}
@Override
public void showInfo() {
System.out.println("方向:");
}
3.3enum常用方法
/*
演示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));
}
}
4.内部类
1)成员内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且没有用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);
}
}
}
2)静态内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且使用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,此时无法访问");
}
}
}
3)局部内部类
定义在某一个代码段中的中。
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 {
}
}
}
4、匿名内部类
没有名字的内部类,内部类,通常是需要配合其他的类或者接口一块使用的
// 实例化了一个匿名子类对象,并向上转型为父类类型
Person xiaoming = new Person() {
// 这里,其实就是一个内部类的类体
// 这个类,因为没有名字,因此是一个匿名内部类 // 这个类,是继承自 Person类的
// 因此,这个类是Person类的匿名子类
@Override
public void work() {
System.out.println("搬砖 "); }
};
注意:在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向 上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。匿名内部类也会生成 .class字节码文件,命名格式 : 外部类$序号 .class