1.抽象类
1.1 简介
由于继承这个显著特点,我们可以将子类设计的更加具体,而父类更加一般化,通用化。父类可以封装不同子类的共同特征或者共同行为。而有的时候,父类中封装的方法无法具体完成子类中需要的逻辑,因此我们可以将此方法设计成抽象方法,即使用关键字abstract进行修饰。而有抽象方法的类,也必须使用abstract关键字进行修饰,因此我们称之为抽象类。
1.2 特点
abstract方法
1.方法不提供花括号{},那就必须使用abstract修饰,必须以分号结尾。
2.有抽象方法的类,必须是抽象类。需要使用abstract修饰类。
3.子类需要实现抽象父类里的所有抽象方法,除非子类也声明为抽象类。
4.抽象类就是用来被继承的,所以不能使用final修饰。(final修饰的类不能被继承)
5.抽象类里面可以没有抽象方法(就如Duck类)
6.抽象类不能实例化,因此不能使用new关键字调用构造器,虽然可以提供构造器。
1.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 {
public Dog() {}
public Dog(String name, int age, String color) {
super(name, age, color);
}
public void noise(){
System.out.println("汪汪汪汪");
}
public void lookHouse(){
System.out.println("看家");
}
}
class Cat extends Animal{
public Cat() {}
public Cat(String name, int age, String color) {
super(name, age, color);
}
public void noise(){
System.out.println("喵喵喵喵");
}
public void getMouse(){
System.out.println("抓老鼠。");
}
}
//如果不想实现抽象类里的抽象方法,该类需要使用abstract修饰。
abstract class Duck extends Animal{
public Duck(){};
public Duck(String name, int age, String color) {
super(name, age, color);
}
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();
// a.lookHouse();//报错,父类没有该方法。
//下面代码编译错误,因为抽象类不能使用new关键字实例化。
// Animal b = new Animal();
}
}
2.接口
有的时候,我们需要从几个不相关的类中派生出一个子类,继承他们的所有成员变量和方法,但是java不支持多继承。此时,我们可以使用接口,来达到多继承的效果。
2.1 概念
概念:
java没有多继承的语法,而有些时候需要使用这种形式,比如一个类想要(需要)两个类的属性或者方法时,可以使用另一个知识点来达到这个目的,就是接口。接口也可以理解为是一个特殊的抽象类,也可以理解为是一种规范。
2.2 类调用接口的特点
- 接口的关键字:interface
- 接口里可以提供成员属性,默认使用public static final 修饰的,即常量。
- 接口里不能提供构造器,更不能使用new实例化,没有意义。
- 接口里可以提供成员方法,默认使用public abstract修饰
- 与类的继承不同,一个类可以实现多个接口。接口间使用逗号分开。
- 一个类实现接口,需要使用关键字implements调用。
- 一个类实现接口时,需要实现里面的所有抽象方法,否则需要使用abstract修饰class。
代码测试:
接口代码:
public interface InterfaceA {
double PI = 3.14159265358979323846;
double NIM = 0.618;
// public InterfaceA() {}//不允许构造
void showInfo();
public abstract int sum(int a, int b);
}
类调用接口:
class A implements InterfaceA {
@Override
public void showInfo() {}
public int sum(int a, int b) {
return a + b;
}
}
abstract class B implements InterfaceA {
@Override
public void showInfo() {
}
}
2.3接口调用接口的特点
接口与接口之间:
1.接口可以继承多个接口,使用extends,多继承使用逗号隔开。
2.子接口拥有了父接口里的所有抽象方法。
3.子接口可以提供自己独有的抽象方法
4.类实现子接口时,要重写里面所有的抽象方法。
代码测试:(C,D接口调用InterfeceB,ClassT继承接口D)
public interface InterfaceB {
void methodB();
}
interface C{
void methodC();
}
interface D extends C,InterfaceB{
void methodD();
}
class ClassT implements D{
@Override
public void methodD() {
}
@Override
public void methodC() {
}
@Override
public void methodB() {
}
}
2.4 接口在1.8之后的新特性(default 、 static)
JDK1.8以后的接口新特性
1.提供了默认方法:使用default 修饰词修饰的具有方法体的方法。
--1)该方法,默认使用public修饰。
--2)该方法,逻辑不能满足子类时,子类可以重写。
2.提供了静态方法:使用static修饰的具有方法体的方法。
--1)该方法,默认使用public修饰--2) 该方法不可重写。
测试代码:
public interface InterfaceM {
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 常用接口
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的相关属性
降序: -(就使用this的相关属性-传入的对象o的相关属性) 拆开括号 :传入的对象o的相关属性-this的相关属性
测试代码(类):
package com.oop.day03.eInterface;
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) {}
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 getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
", weight=" + weight +
'}';
}
/*
如果想要进行比较,那么除了要是实现comparable接口,还要实现里面的比较方法compareTo
升序:this的相关属性-传入的o的相关属性
降序:传入的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;
// if(this.age == o.age){
// return o.height - this.height;
// }
// return this.age-o.age;
//
}
}
测试代码:(测试类)
package com.oop.day03.eInterface;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Comparator;
public class PersonTest {
public static void main(String[] args) {
Person[] ps =new Person[3];
ps[0] = new Person("小明",19,166,70);
ps[1] = new Person("小黑",18,176,65);
ps[2] = new Person("小张",19,186,64);
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
//现在想要修改比较规则,按照体重进行升序排序。不能修改源代码,因为Person类可能有人已经使用了,并不是自己一个人在用。
//此时,就可以使用Comparator比较器进行重新自定义比较规则。
//使用匿名内部类创建一个比较器对象,
Comparator c1 = new Comparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
return o1.getWeight()-o2.getWeight();
}
};
//数组工具类sort方法,重载了很多个方法,包含一个sort(Object[] a,Comparator c);
Arrays.sort(ps,c1);
System.out.println(Arrays.toString(ps));
}
}
3) Comparator接口
汉语翻译成:比较器,比较仪。用于在compareble的基础上去修改比较规则。
3.枚举
3.1 简介
在Java中,枚举是一种特殊的引用数据类型,是一个被命名的整型常数的集合,用于声明一组带标识符的常数,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
背景:枚举是在JDK1.5以后引入的。
主要用途是:将一组常量,也可以说成是一组离散值组织起来。
3.2 枚举的定义
1)自定义类实现枚举 的规则:
类内部创建一组对象,通常使用public static final关键字共同修饰,对外进行暴露
枚举对象名通常全部都会大写,这是常量的命名规范
可以提供属性,属性应使用private final共同修饰
将构造器私有化
属性,可以提供getXXX方法,但是不需要提供setXxx方法,属性应该是只读的。
测试代码:
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 final String desc;
private final String name;
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getDesc() {
return desc;
}
public String getName() {
return name;
}
public String toString() {
return name+","+desc;
}
}
2) enum关键字实现枚举
enum关键字实现枚举的规则
使用enum关键字定义一个枚举,默认会继承java.lang.Enum类,而且是一个final类,因此不能再继承其他类
必须在枚举类的第一行声明枚举类对象。有多个枚举对象时,使用逗号隔开,最后一个用分号结尾
可以提供私有的属性
可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须显式调用构造器
如果使用无参构造器创建枚举对象,则定义对象时,小括号可以省略
/**
* 案例1:简单定义一个枚举
* 注意:
* 1. 第一行,必须是枚举类的对象. 名称自定义,应该符合常量的命名规则。
* 2. 内部系统提供了一个无参构造器,因为创建枚举对象时,调用的是无参构造器,因此
* 对象后面的小括号是可以省略的。
* 注意:构造器是私有的。
*/
public enum Week {
//小括号可以省略
MONDAY("周一"),TUESDAY(),WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
private String a;
private Week(){}//无参构造器也是私有的
private Week(String a){
this.a=a;
}
public void showInfo(){
System.out.println(a);
}
public static void main(String[] args) {
System.out.println(Week.MONDAY);
Week.MONDAY.showInfo();
}
}
使用enum关键字后,就不能再继承其它类,因为enum会隐式继承Enum,而Java是单继承机制
枚举类和普通类一样,可以实现接口
/*
方向的枚举
*/
public enum Direction implements interA{
BEFORE("前"){
@Override
public void showInfo() {
System.out.println("向前进,如箭离弦,永不回头");
}
},
AFTER("后"){
public void showInfo() {
System.out.println("向后退");
}
},
LEFT("左"){
public void showInfo() {
System.out.println("left");
}
},
RIGHT("right"){
public void showInfo() {
System.out.println("right");
}
};
private String name;
private Direction(String name){
this.name = name;
}
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();
}
3.3 enum的常用方法
说明:使用关键字enum时,会隐式继承Enum类,这样我们就可以使用Enum类相关的方法
方法名 | 详细描述 |
---|---|
toString | 得到当前枚举常量的名称。子类可以通过重写这个方法来使结果更易读 |
name | 返回当前对象名(常量名),子类中不能重写 |
ordinal | 返回当前对象的位置号(编号),默认从0开始 |
values | 返回当前枚举类中所有的常量 |
valueOf | 将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常! |
compareTo | 比较两个枚举常量,比较的就是位置号(编号) |
hashCode | Enum实现了hashCode()来和equals()保持一致,它也是不可变的 |
getDeclaningClass | 得到枚举常量所属枚举类型的Class对象。可以用它来判断两个枚举常量是否属于同一个枚举类型 |
clone | 枚举类型不能被Clone。为了防止子类实现克隆方法Enum实现了一个仅抛出Clone Not Supported Exception异常的不变clone() |
4.内部类
4.1 成员内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且没有用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程,需要先实例化外部类对象,再使用外部类对象进行内部类的实例化
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
/*
成员内部类:
1.定义在一个类的里面,与类的其他成员是平级关系。没有static修饰
2.该内部类的访问权限可以是private,默认的,protected,public
*/
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 int age;
private String name;
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);
}
}
public static void main(String[] args) {
// 先创建一个外部类对象
Outer outer = new Outer("妈妈",29);
//然后通过外部类对象,来实例化一个内部类对象。
Inner inner =outer.new Inner("儿子",0);
inner.showInfo();
}
}
4.2 静态内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且使用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程中,直接使用 new实例化一个外部类 .内部类对象即可。
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
/*
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程中,直接使用 new实例化一个外部类.内部类对象即可
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类.class
*/
public class Outer {
private String name;
public Outer() {
}
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("Inner showInfo");
//不能直接访问外部类的成员
// System.out.println(Outer.this.name);
}
}
public static void main(String[] args) {
//创建内部类的对象:new 外部类名.内部类构造器。
Inner inner = new Outer.Inner("Inner showInfo");
}
}
4.3局部内部类
定义在某一个代码段中的中。
1、没有访问权限修饰符。
2、在当前方法中,直接实例化即可
3、内部类编译后,也会生成.class字节码文件。格式:外部类$序号内部类 .class4、 出了作用域就失效了。
代码:
/*
局部内部类:
再方法中定义的内部类,和局部变量用法一样。出了作用域就失效了。
*/
public class Outer {
public static void main(String[] args) {
int a = 10;
System.out.println(a);
class Inner {
private int a;
public Inner(int a) {
this.a = a;
}
public int getA() {
return a;
}
}
Inner inner = new Inner(10);
System.out.println(inner.getA());
}
}
4.4匿名内部类(重点)
没有名字的内部类,匿名内部类,通常是需要配合其他的类或者接口一块使用的。
用法:
接口名|抽象类名|父类名 变量=new 接口名|抽象类名|父类名(){
方法的重写;
};
代码:
/*
匿名内部类:
就是没有名字的子类型对象
接口名|抽象类名|父类名 变量=new 接口名|抽象类名|父类名(){
方法的重写
};
*/
public class Outer {
public static void main(String[] args) {
A a = new A(){
//匿名内部类提供成员属性
public String name ="asdasad";
//重写接口A里的抽象方法
@Override
public void showInfo(){
System.out.println("Hello World");
}
//子类提供了独有的getXXX方法
public String getName(){
return name;
}
};
//编译期间,看变量类型,因此能调用到showInfo
a.showInfo();
// a.getName();//编译期间,a里根本没有getName方法。
}
}
interface A{
public void showInfo();
}
//一般A的子类写法
//class B implements A{
// public void showInfo() {
// System.out.println("B");
// }
//}
在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。
匿名内部类也会生成 .class字节码文件,命名格式 : 外部类$序号 .class