一、抽象类
1.1 简介
由于继承这个显著特点,我们可以将子类设计的更加具体,而父类更加一般化,通用化。父类可以封装不同子类的共同特征或者共同行为。而有的时候,父类中封装的方法无法具体完成子类中需要的逻辑,因此我们可以将此方法设计成抽象方法,即使用关键字abstract进行修饰。而有抽象方法的类,也必须使用abstract关键字进行修饰,因此我们称之为抽象类。
1.2 抽象类的特点
1.由abstract修饰的方法为抽象方法,抽象方法没有方法体,需要使用使用分号结尾。
2.方法不提供{},那就必须使用abstract修饰。
3.有抽象方法的类,一定是抽象类,需要使用abstract来修饰。
4.如果子类继承了抽象父类,一定要实现抽象父类里的所有方法(重写),或者子类也声明为抽象类。
5. 抽象类就是用来被继承的,所有不能使用final修饰。
6. 抽象类里可以没有抽象方法。
7. 抽象类不能实例化,因此不能使用new关键字调用构造器,虽然可以提供构造器。
1.3 抽象类的意义
1)为其子类提供一个公共的父类型。
2)封装子类中重复的内容,如成员变量和方法。
3)定义抽象方法,子类虽然有不同不同的实现逻辑,但该方法的定义却是一致的。
1.4 案例
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{
public Dog() {
}
public Dog(String name, int age, String color) {
super(name, age, color);
}
@Override
public void noise(){
System.out.println("~~汪汪汪~~");
}
public void lookHouse(){
System.out.println("~~看家~~");
}
}
class Cat extends Animal{
public void getMouse(){
System.out.println("~~抓老鼠~~");
}
public void noise(){
System.out.println("~~喵喵喵~~");
}
}
//如果不想实现抽象类里的抽象方法,那么该类需要使用abstract修饰
abstract class Duck extends Animal{
public void swim(){
System.out.println("~~游泳~~");
}
}
public class AnimalTest {
public static void main(String[] args) {
//直接定义一个cat类型,调用cat里的功能。
Cat cat = new Cat();
cat.noise();
cat.getMouse();
//使用多态的向上造型
Animal a = new Dog();
a.noise();
//下面代码编译错误,因为抽象类不能使用new关键字实例化。
//Animal b = new Duck();
}
}
~~喵喵喵~~
~~抓老鼠~~
~~汪汪汪~~
二、接口
2.1 简介
java没有多继承的语法,而有些时候需要使用这种形式,比如一个子类想要拥有两个父类的属性或者方法时,可以使用另一个知识点来达到这种目的,就是接口。接口也可以理解为是特殊的抽象类,也可以理解为是一种规范。
2.2 接口的特点
1. 接口的关键字interface。(public interface 接口名)
2. 接口里可以提供成员变量,默认提供public static final进行修饰,即常量。
3. 接口里可以提供成员方法,默认使用public abstract进行修饰,即抽象方法。
4. 接口里不能提供构造器,更不能使用new实例化,没有意义。
5. 与继承不同,一个类可以实现多个接口,接口之间用逗号隔开。
6. 一个类实现接口,需要使用implements进行实现。
7. 一个类实现接口,必须实现接口中的所有抽象方法,否则该类需要使用abstract声明为抽象类。
public interface InterfaceA {
double PI = 3.14;
public static final double NUM = 0.618;
void showInfo();
int sum(int a, int b);
}
class ClassA implements InterfaceA {
public void showInfo() {}
public int sum(int a, int b) {
return a + b;
}
}
abstract class ClassB implements InterfaceA {
public void showInfo() {}
}
2.3 接口间的继承
接口与接口之间的继承:
1.接口可以继承多个接口,使用extends关键字。
2. 子接口里面拥有父接口里面的所有抽象方法,也可以提供自己独有的抽象方法。
3. 类实现子类接口时,要重写里面的所有抽象方法,包括其实现的子接口所继承的父类接口里的抽象方法。
public interface InterfaceB {
void methodB();
}
interface InterfaceC{
void methodC();
}
interface InterfaceD extends InterfaceC,InterfaceB{
void methodD();
}
class classT implements InterfaceD{
//如果不重写下面所有的抽象方法,类就要用abstract修饰。
//即abstract class classT implements InterfaceD{}
@Override
public void methodD() {
}
@Override
public void methodB() {
}
@Override
public void methodC() {
}
}
2.4 接口在1.8之后的新特性
1)提供了默认方法default
1. 就是使用default修饰词修饰的具有方法体的方法。
2. 该方法默认使用public修饰。
3. 该方法,逻辑不能满足子类时,子类可以重写。
4. 该方法,可以在类中重写。
2)提供了静态方法static
1. 就是使用static修饰的具有方法体的方法。
2. 该方法默认使用public修饰。
3. 这个方法,在类中不能重写。
public interface InterfaceM {
public default void print(){
System.out.println("--欢迎来到中国,我的家--");
}
static void print2(){
System.out.println("--地球只有一个,人人有责--");
}
}
class classU implements InterfaceM{
//重写接口里的默认方法
@Override
public void print(){
System.out.println("--欢迎来到长春,我的家--");
}
//@Override 添加注解报错,因此print2方法不是重写,此时是自己独有的静态方法
static void print2(){
System.out.println("--地球只有一个,人人有责--");
}
}
2.5 演示
public interface InterA {
void showInfo();
}
interface InterB {
void showInfo();
int calculate(int a, int b);
}
class ClassA implements InterA,InterB{
@Override
public void showInfo() {
System.out.println("--ClassA--");
}
@Override
public int calculate(int a, int b) {
return a+b;
}
}
public class InterATest {
public static void main(String[] args) {
//因为ClassA是InterA和InterB的子类型,因此可以向上造型。
InterA x = new ClassA();
x.showInfo();
//此时,想要使用x指向的对象,调用其里面的计算功能。
//针对于这道题来说,可以向下转型成ClassA,也可以强制转换成InterB.
//instanceof用于检验x是否属于ClassA类和InterB类
//第一种方式
if(x instanceof ClassA){
ClassA c = (ClassA)x;
int result = c.calculate(3,5);
//第二种方式
if(x instanceof InterB){
InterB b = (InterB)x;
b.calculate(3,5);
System.out.println("result:"+result);
}
}
}
}
2.6 常用接口
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的相关属性
public class Person implements Comparable<Person>{
private String name;
private int age;
private int height;
private int weight;
public Person(String name, int age, int height, int weight) {
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
}
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;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
", weight=" + weight +
'}';
}
/**
* 如果想要进行比较,那么除了要实现comparable接口,还要实现里面的比较方法compareTo
* @param o the object to be compared.
* @return
* 升序:this的相关属性-传入的0的相关属性
* 降序:传入的o的相关属性-this的相关属性
*/
@Override
public int compareTo(Person o) {
//按照年龄升序: 返回负数,证明this小,返回0,证明相等,返回正数,说明this大。
//return this.age - o.age;
//按照身高降序
//return o.height - this.height;
//先按照年龄升序,如果年龄相同,按照身高降序
int r = this.age - o.age;
if(r == 0){
r = o.height - this.height;
}
return r;
}
}
3)Comparator接口
比较器、比较仪的含义。用于在compareble的基础上去修改比较guize。
public class PersonTest {
public static void main(String[] args) {
Person[] ps = new Person[3];
ps[0] = new Person("小明",18,176,70);
ps[1] = new Person("小黑",18,175,65);
ps[2] = new Person("小张",19,177,64);
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
//现在想要修改比较规则,按照体重进行升序。不能修改源代码,因为Person类可能有人已经使用了,并不是自己一个人再用。
//此时,可以使用comparator重新自定义比较规则。
//使用匿名内部类创建一个比较器对象
Comparator c1 = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getWeight()-p2.getWeight();
}
};
//数组工具类sort方法,重载了很多个方法,包含一个sort(Object[] a,Comparator c);
Arrays.sort(ps, c1);
System.out.println(Arrays.toString(ps));
}
}
三、枚举
3.1 简介
在Java中,枚举是一种特殊的引用数据类型 ,是一个被命名的整型常数的集合,用于声明一组带标识符的常数,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
枚举的主要用途:将一组常量,也可以说成是一组离散值组织起来。
3.2 枚举的定义
1)自定义类实现枚举
1.提供一堆可以像外界暴露的该类型的对象,通常使用public static final关键字共同修饰。
2.构造器私有化。
3.可以提供属性,用来描述对象信息,使用private final修饰。
4.给属性提供getXXX方法,向外界暴露,不需要提供setXXX方法,因为只读。
public class Season {
//声明Season对象的属性:private final修饰
private final String name;//对象的名字
private final String desc;//对象的描述
//构造器私有化。
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
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("冬天","白雪皑皑");
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return name;
}
public static void main(String[] args) {
Season s = Season.AUTUMN;
//直接输出。打印的是对象的地址
System.out.println(s);
//打印这个季节的名字
System.out.println(s.getName());
//获取这个季节的描述信息
System.out.println(s.getDesc());
}
}
秋天
秋天
落叶归根
2)enum关键字实现枚举
1.使用enum关键字自定义一个枚举,默认会继承java.lang.Enum类,而且是一个final类,因此不能再继承其他类。
2. 第一行,必须是枚举类的对象,有多个枚举对象时,使用逗号隔开,最后一个用分号结尾, 名称自定义,应该符合常量的命名规则。
3. 内部系统提供了一个无参构造器,因为创建枚举对象时,调用的是无参构造器。 因此,对象后面的小括号可以省略。
注意:构造器是私有的。
4. 可以提供私有的属性。
5. 可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须显式调用构造器。
6. 枚举类和普通类一样,可以实现接口。
案例一:
public enum Week {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
public static void main(String[] args) {
System.out.println(Week.FRIDAY);
}
}
案例二:
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 String getName(){
return name;
}
public String getDesc(){
return desc;
}
public static void main(String[] args) {
Season season = Season.SUMMER;
System.out.println(season.name);
System.out.println(season.desc);
}
}
案例三:
public enum Direction implements InterA {
BEFORE("前"){
public void showInfo(){
System.out.println("向前进,如箭离弦,永不回头");
}
},AFTER("后") {
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("--方向--");
}
public static void main(String[] args) {
Direction direction = Direction.BEFORE;
System.out.println(direction);
direction.showInfo();
}
}
interface InterA{
void showInfo();
}
四、内部类
定义在一个类内部的类,就是内部类。内部类可以分为:成员内部类、静态内部类、局部内部类、匿名内部类。
4.1 成员内部类
1. 定义在一个类里面,与类的其他成员是平级关系。并且没有用static修饰的类。
2. 该内部类的访问权限可以是private,默认的,protected,public。
3. 需要先实例化外部类对象,再使用外部类对象进行内部类的实例化。
4. 内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
public class Outer {
private String name;
private int age;
public Outer(String name, int age) {
this.name = name;
this.age = age;
}
public void showInfo(){
System.out.println(name+","+age);
}
//定义一个成员内部类
class Inner{
private String name;
private int age;
public Inner(String name, int age) {
this.name = name;
this.age = age;
}
public void showInfo(){
System.out.println(name+","+age);
System.out.println(Outer.this.name+","+Outer.this.age);
}
}
public static void main(String[] args) {
//先创建一个外部类对象
Outer outer = new Outer("妈妈",29);
//然后通过外部类对象来实例化一个内部类对象
Inner inner = outer.new Inner("儿子",1);
inner.showInfo();
}
}
4.2 静态内部类
1. 定义在一个类的内部,与这个类的成员(属性、方法)平级,并且使用static修饰的类。
2. 访问权限可以是任意的权限,类似于一个类中的成员。
3. 实例化的过程中,直接使用 new实例化一个外部类 . 内部类对象即可。
4. 内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
public void showInfo() {
System.out.println(name);
}
static class Inner {
private String name;
public Inner(String name) {
this.name = name;
}
public void showInfo() {
System.out.println(name);
//不能直接访问外部类成员
//System.out.println(Outer.this.name);
}
public static void main(String[] args) {
//创建内部类的对象:new 外部类名.内部类构造器
Inner i = new Outer.Inner("儿子");
i.showInfo();
}
}
}
4.3 局部内部类
1. 在方法中定义的内部类,和局部变量的用法一样。出了作用域就失效了。
2. 没有访问权限修饰词。
3. 在当前方法中,直接实例化即可。
4. 内部类编译后,也会生成.class字节码文件。格式:外部类$序号内部类 .class
public class Outer {
public static void main(String[] args) {
int a = 10;
System.out.println(a);
class Inner {
private String name;
public Inner(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Inner inner = new Inner("小明");
System.out.println(inner.getName());
}
4.4 匿名内部类
就是没有名字的子类型对象。
语法:
new 接口名|抽象类名|父类名(){
方法的重写
};
在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。
public class Outer {
public static void main(String[] args) {
A a =new A(){
//匿名内部类提供成员属性
private String name = "--你好,欢迎来到中国--";
public void showInfo(){
System.out.println(name);
}
//子类提供了独有的getXXX方法
public String getName(){
return name;
}
};
a.showInfo();
}
}
interface A{
public void showInfo();
}