一.抽象类
1.1简介
由于继承这个方法的存在,我们可以把子类设置的更加具体,父类设置的更加一般化。父类可以封装不同的子类的共同特征和共同行为。在某些情况下,父类中封装的方法无法满足子类的需求,因此我们可以把父类中的方法设置为抽象方法,使用abstract关键字进行修饰,而写了抽象方法的类,也必须设置为抽象类。
1.2抽象类的特点
1.abstract修饰的方法是抽象方法,抽象方法没有方法体,需要使用分号结尾;
2.如果类方法中有抽象方法,则必须把类也设置为抽象类,final不能修饰抽象类,因为final修饰词就不能有子类了,而抽象的意义就是为了让子类能更好的继承方法
3.抽象类中,可以没有抽象方法;
4.抽象类中,可以提供构造器,但是没有意义,因为构造器是给子类使用的,不能实例化对象;
5.一个类如果继承了抽象类,那么必须要重写里面的所有方法,除非该子类也声明为抽象类;
我们可以看一个实例
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;
}
在上述代码中,我们定义了一个抽象类Animal,并提供了私有化的静态变量name,age,color,并提供了一个全参构造器,一个无参构造器
public abstract void noise();
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 String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
我们在抽象父类中定义了一个抽象的公有方法noise,并提供了父类各个属性的getter和setter方法
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("看家");
}
}
我们在抽象父类外定义了一个子类Dog,并提供了全参构造器和无参构造器,此时还需要重写父类中的抽象方法noise(),并且在后面提供了一个子类独有的看家的方法;
class Cat extends Animal{
public void noise(){
System.out.println("---喵喵喵---");
}
public void getMouse(){
System.out.println("抓耗子");
}
}
//如果不想实现抽象类里的抽象方法,该类需要使用abstract修饰
abstract class Duck extends Animal{
public void swim(){}
}
在子类外定义了一个新的子类Cat,默认这个子类调用了父类的无参构造器,同样重写了父类中的noise()方法,并在后面定义了一个子类独有的抓耗子的方法
如果定义了一个子类,但是不想使用父类中的抽象方法,就需要把子类也定义为抽象类,使用abstract来修饰
public class AnimalTest {
public static void main(String[] args) {
//直接定义一个Cat类型,调用功能
Cat cat = new Cat();
cat.noise();
cat.getMouse();
//使用多态的向上造型
Animal a = new Dog("小黑",3,"black");
a.noise();
//下面代码编译错误,因为抽象类不能使用new关键字实例化;
//Animal b = new Animal()
System.out.println(a.getName());
System.out.println(a.getAge());
System.out.println(a.getColor());
}
}
我们对上述定义的类进行一个测试,使用main方法,实例化一个Cat类型的对象cat,调用noise和抓耗子的方法;
之后使用了多态的向上造型,父类型的变量引用的是子类型Dog的对象;调用noise方法,并调用getter方法
根据返回的结果,我们可以判断是a是子类型的对象,所以会返回汪汪汪。
二.接口
2.1简介
在Java语言中,没有一个子类继承多个父类的语法,一个类想要同时继承两个类的属性和方法,所以这时我们需要使用接口 interface。
接口可以理解为一个特殊的抽象类,也可以理解成为一种规范;
需要注意:
1.接口的关键字: interface
2.接口里也可以提供成员变量(属性),默认使用public static final 来修饰,即常量。
3.接口里不能提供构造器,更不能使用new关键字实例化对象,没有意义
4.接口里可以提供成员方法,默认使用public abstract来修饰
5.一个类若想实现接口,就需要使用关键字implements
6.一个类在实现接口时,需要重写里面的抽象方法,或者使用abstract来修饰类。
public interface InterfaceA {
double PI = 3.14;
public static final double NUM = 0.618;
void showInfo();
public abstract int sum(int a, int b);
}
首先定义了一个InterfaceA的接口,并在其中设定两个静态变脸PI和MUM,然后再其中继续写出一个抽象的方法showInfo
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() {}
}
接下来定义两个子类实现接口,需要使用implements关键字,然后在类中重写抽象方法showInfo
并在ClassA中提供一个属于子类的返回值类型是int类型的方法sum(),在ClassB中,我们设置为抽象类。
2.2接口与接口间的继承关系
1.接口可以继承多个接口,需要使用extends关键字。
2.子接口继承了父接口里的所有抽象方法
3.子接口可以提供自己独有的抽象方法
4.类具体实现接口时,需要重写接口里的抽象方法
public interface InterfaceB {
void methodB();
}
interface InterfaceC{
void methodC();
}
interface InterfaceD extends InterfaceC,InterfaceB{
void methodD();
}
在上述代码中,我们设计了三个接口,分别是B,C,D其中接口D继承了接口B和接口C;因为我们设置了接口D继承了上面的两个接口,就不需要重写抽象方法了;
class classT implements InterfaceD{
@Override
public void methodD() {
}
@Override
public void methodB() {
}
@Override
public void methodC() {
}
}
我们创建了一个类来实现接口D,需要使用implements关键字,还需要把接口中的抽象方法全部重写,包括父接口B,C的抽象方法
2.3 jdk1.8以后的接口新特性
1.提供了默认的方法:使用default修饰具有方法体的方法; 该方法使用public修饰,如果逻辑不能满足子类,在子类中可以被重写。
2.提供了静态的方法使用static修饰具有方法体的方法,默认使用public方法,该方法不能被重写
public interface InterfaceM {
public default void print(){
System.out.println("---欢迎来到中国,我的家---");
}
static void print2(){
System.out.println("---地球只有一个,人人有责---");
}
}
首先定义了一个接口M,定义了一个default修饰的print()方法,和一个静态的无返回值的方法
class classU implements InterfaceM{
//重写接口里的默认方法,不加default
@Override
public void print(){
System.out.println("---欢迎来到长春---");
}
//@Override 添加注解,报错,因此print2方法不能重写,此时是自己独有的静态方法
static void print2(){
System.out.println("---地球只有一个,人人有责---");
}
}
在上述代码中,我们设计了一个classU类来实现接口,重写了print()方法,只需要去掉default即可,而对于第二个static修饰的方法,我们不能重写,新给出的print2()方法只能是子类独有的方法,该静态方法只能重载。
2.4 案例
1. USB接口
充电功能 charge()
获取信息 getInfo()
2. Computer类型:
属性:
两个USB接口: usb1,usb2
方法:
两个属性的setXXX方法
3. KeyBoard键盘类:
实现USB接口
4. Mouse鼠标类:
实现USB接口
5. Program测试类:
根据上述的需求分析,我们需要设计一个Usb接口,一个父类Computer,一个键盘类,一个鼠标类,一个测试类
首先我们可以设计Usb接口:有两个功能是充电功能和获取信息:
public interface Usb {
//充电
void charge();
//返回信息
String getInfo();
}
充电功能是不需要返回值的,我们打印一个充电语句就可以,返回信息需要一个返回值String,然后我们就可以设计父类了,键盘和鼠标作为子类,共同特征就是通过USB连接,可以作为成员变量
package com.oop.day03.dInterface;
public class Computer {
private Usb usb1;
private Usb usb2;
public void setUsb1(Usb devide) {
this.usb1 = devide ;
System.out.println(devide.getInfo()+"连接到电脑");
}
public void setUsb2(Usb devide) {
this.usb2 = devide;
System.out.println(devide.getInfo()+"连接到电脑");
}
}
在上述代码中,我们提供了两个私有化Usb类型的变量usb1和usb2,并提供了两个set方法,分别设置成员变量u1和u2,通过打印语句输出是哪个设备连接到电脑了;接下来就可以设计键盘子类和鼠标子类了;
public class KeyBoard implements Usb{
@Override
public void charge() {
System.out.println("--电脑可以给键盘充电--");
}
@Override
public String getInfo() {
return "键盘";
}
}
可以发现,该子类实现了usb接口,重写了其中的charge()方法,又重写了getInfo()方法,返回字符串是键盘;
public class Mouse implements Usb{
@Override
public void charge() {
System.out.println("--电脑可以给鼠标充电--");
}
@Override
public String getInfo() {
return "鼠标";
}
}
上述代码与键盘类的格式几乎相同;
public class Program {
public static void main(String[] args) {
//具体电脑对象
Computer c1 = new Computer();
//实例化键盘
KeyBoard kb = new KeyBoard();
//实例化鼠标
Mouse m1 = new Mouse();
//连接到电脑上
c1.setUsb1(m1);
c1.setUsb2(kb);
kb.charge();
m1.charge();
}
}
在上述代码中,我们实现了一个测试类,在main方法中实例化了电脑对象,键盘对象,鼠标对象,连接需要使用电脑类的setUsb1()方法,分别传入实参Usb类型的kb和m1,就完成了连接的需求,后面直接调用两个子类中重写的charge()方法即可实现充电的需求
2.5 常用接口
comparable接口,汉语翻译成: 可比的,可比较的,是一个形容词。 当一个类的多个对象之间想要进行比较时,比如排序等操作,那么类必须实现该接口,然后自定义比较规则。否则不能比较。
我们通过一个案例来理解这一部分的知识:
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 int getAge() {
return age;
}
public int getHeight() {
return height;
}
public int getWeight() {
return weight;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
", weight=" + weight +
'}';
在上述代码中,我们设计了一个一个Person类,继承接口Comparable<Person>,并提供私有属性姓名,年龄,身高,体重。在类体中提供一个全参构造器,并提供get方法,在最后重写一个toString方法,返回字符串的形式
//如果想要进行比较,除了要实现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;
}
}
我们如果想进行比较,还需要重写接口中的compareTo()方法,形参是Person类型的变量;如果需要升序排序,就需要返回this.相关属性-传入o.相关属性,如果需要降序排列,就需要使用o.相关属性-this.相关属性;
比如若是想按照年龄升序排列:就需要return this.age - o.age
若是想按照身高降序排列,就需要使用 return o.height - this.height
如果想先按照年龄升序,如果年龄相同,再按照身高降序的化:就需要先声明一个int 类型的变量r, 让r = this.age - o.age; 然后再使用判断语句,如果r ==0,则另r = o.height - this.height;然后再返回r的值,如果r不等于0,就直接返回r。
public class PersonTest {
public static void main(String[] args) {
Person[] ps = new Person[3];
ps[0] = new Person("小明",19,176,70);
ps[1] = new Person("小黑",18,175,65);
ps[2] = new Person("小张",19,177,64);
//不能比较,会报错:com.oop.day03.eInterface.Person cannot be cast to java.lang.Comparable
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
上述代码作为一个测试类,先实例化了Person类型的数组ps,长度是3,0位置,1位置,2位置分别调用构造器给属性赋值,然后对数组进行比较
返回结果小黑的年龄最小,排在最前面,小张和小明年龄一致,所以要排身高,小明比小张矮,所以排在后面
需求:
现在想要修改比较规则,按照体重进行升序排列,不能修改源代码,
因为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));
在上述的main方法里可以创建一个Comparatpor接口类型的变量c1,在类内部重写一遍compare方法按照体重升序,p1在前,p2在后;
数组工具类sort方法中重载了很多方法,我们选择一个sort(Object[ ] a,Comparator c)的类型,输入实参ps,c1;在排序完之后就可以打印该数组了
三. 枚举
3.1简介
在Java中的一种特殊的引用数据类型,是一个被命名的整型常数的集合,用于声明一组带标识符的常数,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
3.2 枚举的定义
1.自定义类实现枚举
提供一堆可以向外界暴露的该类型的对象,需要是public final static修饰的,并把构造器私有化,不能让外界设置成员变量,可以提供属性,用来描述对象的信息,但是需要使用private final修饰,给属性提供get方法,但是不要提供set方法,因为外界只可以读但是不可以修改。
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 name;//对象的名字
private final String desc;//对象的描述
private Season(String name,String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return name;
}
}
根据上述代码,我们定义了一个季节枚举类,提供了成员变量名字和描述,并提供了全参构造器和get方法,同时重写了toString()方法,以便于返回的是成员变量的名字而不是地址。
之后我们定义了一个枚举类型,使用public static final来修饰 后面加上类名和全部大写的对象名。
public class SeasonTest {
public static void main(String[] args) {
Season s = Season.AUTUMN;
//直接输出,打印的是对象的地址
System.out.println(s);
//打印季节的名字
System.out.println(s.getName());
//获取这个季节的描述信息
System.out.println(s.getDesc());
}
}
根据上述代码,我们先定义一个Season类型的变量s 赋值为AUTUMN枚举,如果直接打印s的值而不重写toString方法的话,就会直接返回地址值,重写之后就可以直接打印name了,我们如果想打印季节的名字的话还可以使用对象调用getname方法,下面的获取信息同理
2.使用enum来定义一个枚举
我们可以先定义一个简单枚举,需要注意的是:
1. 第一行,必须是枚举类的对象. 名称自定义,应该符合常量的命名规则。
2. 内部系统提供了一个无参构造器,因为创建枚举对象时,调用的是无参构造器,因此
对象后面的小括号是可以省略的。
注意:构造器是私有的。
public enum Week {
MONDAY(), TUESDAY(), WEDNESDAY(), THURSDAY(), FRIDAY(), SATURDAY(), SUNDAY();
private Week() {}
public static void main(String[] args) {
System.out.println(Week.FRIDAY);
}
}
根据上述代码我们直接定义了一个枚举类Week,再{ }中创建枚举对象,然后提供一个无参构造器,因为调用的是无参构造器,所以说对象后面的( )可以省略,需要注意:构造器必须定义成私有的,然后创建一个main方法来测试,直接打印Week.FRIDAY即可。
3.使用构造器获取枚举对象:
使用构造器来获取一堆枚举对象:
1.枚举类的第一行必须是枚举的对象
2.如果提供构造器,构造器必须私有化。枚举对象必须显示调用构造器
3.可以提供属性,必须私有化
4.自定义的枚举,默认继承了java.lang.Enum抽象类
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);
}
}
根据上述代码,我们实例化了一个Season类,直接在第一行创建枚举对象,使用了构造器,我们必须再下面提供一个私有化的全参构造器,并提供公有的set方法来获取成员变量name
之后实例化一个season对象,赋值为枚举对象的SUMMER,然后打印名字和描述。返回的结果就是夏天 烈日炎炎。
4.重写方法形式
使用enum关键字后,就不能再继承其它类,因为enum会隐式继承Enum,而Java是单继承机制.
枚举类和普通类一样,可以实现接口
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();
}
我们定义了一个A接口,方向枚举类就继承了A接口,然后我们定义了成员变量name并提供了全参构造器,重写了接口中的抽象方法,在定义枚举时直接调用构造器即可,并且在构造器中直接重写一遍showInfo方法,最后在main方法中实例化一个方向对象,赋值为BEFORE,直接打印方向的值,如果调用方法的话就会打印出向前进。
四.内部类
1.成员内部类
定义在一个类的里面,与类的其他成员是平级关系,没有static修饰
该内部类的访问权限可以是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);
}
我们先创建了一个外部类,提供成员变量年龄,姓名,并提供了全参构造器和方法showInfo()
//定义一个成员内部类
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);
//访问外部类的成员:外部类名.this.成员
System.out.println(Outer.this.name+","+Outer.this.age);
}
}
然后我们定义了一个内部类,也是提供了成员变量姓名和年龄,提供全参构造器,和方法showInfo() ,如果想要访问外部类的成员,需要使用外部类名.this.成员变量名
public static void main(String[] args) {
//先创建一个外部类对象
Outer outer = new Outer("妈妈",29);
//然后通过外部类对象来实例化一个内部类对象
Inner inner = outer.new Inner("儿子",1);
inner.showInfo();
}
}
在外部类中,我们在main方法里测试,先实例化一个外部类对象,调用构造器来赋值,然偶通过外部类对象来实例化一个内部类对象,之后调用内部类的showInfo()方法;
执行打印命令的就是内部类的showInfo方法;
4.2静态内部类
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
public void showInfo(){
System.out.println(name);
首先定义一个外部类,成员变量只有名字,提供构造器和showInfo()方法;
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);
}
}
然后我们定义一个静态内部类,用static修饰,和外部类提供相同的成员变量和方法,但是静态内部类不能直接访问外部类的成员了,即使使用 外部类名.this.成员变量名也不可以;
public static void main(String[] args) {
//创建内部类的对象: new 外部类名.内部类构造器
Inner i = new Outer.Inner("儿子");
i.showInfo();
}
}
此时我们如果想要实例化内部类的对象,就需要使用 new.外部类名.内部类构造器 的方式
之后调用showInfo()方法;
4.3局部内部类
局部内部类:在方法中定义的内部类,和局部变量的用法一样,出了作用域就失效了
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());
}
}
在上述代码中,我们先定义了外部类Outer,然后再main方法中建立了一个内部类,提供成员变量name,全参构造器,和get方法,我们此时如果像实例化内部类的对象,必须在方法中实例化,一旦出了方法体,就会报错。
4.4匿名内部类(重点)
匿名内部类:就是没有名字的子类型对象
接口名|抽象类名|父类名 变量 = new 接口名|抽象类名|父类名( ){
方法的重写
};
public class Outer {
public static void main(String[] args) {
A a = new A(){
//匿名内部类,提供成员属性
private String name;
//重写父类A的抽象方法
@Override
public void showInfo() {
System.out.println("--你好,欢迎你来到中国--");
}
//子类提供了独有的get方法
public String getName(){
return name ;
}
};
//编译期间,看对象变量,能调用showInfo,运行期间看对象类型
a.showInfo();
//a.getName(); 编译期间,A里根本没有get方法,编译都不会通过
}
}
interface A{
public void showInfo();
}
首先定义一个外部类,调用main方法,然后直接创建一个匿名抽象类使用接口名加上变量的形式,可以在里面提供成员属性,也是需要重写父接口A的抽象方法,并提供一个子类独有的get方法,需要在类体后面加上“;”,然后调用showInfo()方法,可以调用,因为对象类型是A接口类型,但是getName()方法会在编译期间出错,因为编译期间父接口A中没有该方法。