1.抽象类
(1).简介
父类可以封装不同子类的共同特征或者共同行为.而有的时候,父类中封装的方法无法具体完成子类中需要的逻辑,因此我们可以将此方法设计成抽象方法,即使用关键字abstract进行修饰。而有抽象方法的类,也必须使用abstract关键字进行修饰,因此我们称之为抽象类
(2)抽象类的特点
1)由abstract修饰的方法为抽象方法,抽象方法没有方法体,即没有{ },需要使用分号结尾
public abstract void noise();
2)若类中包含抽象方法,那么该类必须使用关键字abstract声明成抽象类。final不能修饰抽象类,因为抽象类就是用来被继承的,而final修饰的类是不能被继承的。
public abstract class Animal {
private String name;
private int age;
private String color;
// 抽象方法
public abstract void noise();
}
3)抽象类里,可以没有抽象方法
public abstract class Animal {
private String name;
private int age;
private String color;
// 抽象方法
public abstract void noise();
public void zouLu(){
System.out.println("正在走路");
}
}
4)抽象类里可以提供构造器,但是不能实例化,没有意义。因为不能使用new关键字调用构造器
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 static void main(String[] args) {
//抽象类不能实例化,因此不能使用new关键字调用构造器,虽然可以提供构造器
//Animal animal = new Animal() ;//会报错
}
}
5)一个类继承了抽象类,那么必须重写里面的所有抽象方法,除非该子类也声明为抽象类。
Dog继承了Animal类,不重写父类的抽象方法noise(),就会出现如下错误
第一种解决方法重写父类的抽象方法noise()
class Dog extends Animal{
public Dog() {
}
public Dog(String name, int age, String color) {
super(name, age, color);
}
//子类自己的方法
public void lookHouse(){
System.out.println("~~看家~~");
}
// 重写父类里的抽象方法
public void noise(){
System.out.println("~~汪汪汪~~");
}
}
第二种是:把子类也声明成抽象类
abstract class Dog extends Animal{
}
6)抽象类里可以没有抽象方法
(3)抽象类的意义
1)为其子类提供一个公共的父类型
2) 封装子类中重复的内容,如成员变量和方法
3) 定义抽象方法,子类虽然有不同的实现逻辑,但该方法的定义却是一致的。
2.接口
(1)接口的特点
有的时候,我们需要从几个不相关的类中派生出一个子类,继承他们的所有成员变量和方法,但是java不支持多继承。此时,我们可以使用接口,来达到多继承的效果
java没有多继承的语法,而有些时候需要使用这种形式, 比如一个类想要拥有两个类的属性或者方法时,可以使用另一个知识点来达到这种目的,就是接口
接口也可以理解为是特殊的抽象类, 也可以理解为是一种规范。
1.接口的关键字:interface
2. 接口里可以提供成员属性,默认使用public static final修饰的,即常量
public interface IntefaceA {
double PI = 3.14;
public static final double NUM = 0.618;
}
3. 接口里不能提供构造器,更不能使用new实例化,没有意义。
4. 接口里可以提供成员方法,默认使用public abstract修饰,(抽象方法),所以可以省略不写
public interface IntefaceA {
void showInfo();
public abstract int sum(int a, int b);
}
(2)实现接口
1) 使用关键字implements进行实现,必须实现接口中的所有抽象方法
class ClassB implements IntefaceA {
@Override
public void showInfo() {}
@Override
public int sum(int a, int b) {
return 0;
}
}
2)若一个类中没有全部实现接口中的抽象方法,那么该类需要使用abstract声明成抽象类
abstract class ClassB implements IntefaceA {
public void showInfo() {}
}
3) 1.与继承不同,一个类可以实现多个接口。接口间使用逗号分开。并且子类里要重写两个接口里的抽象方法。
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指向的对象,调用其里面的计算功能。编译时看变量类型,x里没有计算功能。
//所以针对于这道题来说: 可以向下转型成ClassA,也可以强制转换成InterB
if(x instanceof ClassA){
ClassA c = (ClassA)x;
int result = c.calculate(3,5);
System.out.println("result="+result);
}
//第二种方式:
if(x instanceof InterB){
InterB b = (InterB)x;
int result = b.calculate(3,5);
System.out.println("result="+result);
}
}
}
2.一个类可以在继承一个父类的情况下,同时实现多个接口。也可以达到多继承的效果。
父类中有抽象方法,子类必须重写该方法,同时也还要重写实现的接口里的抽象方法。
interface InterM{
void showInfo();
}
interface InterN{
void sum(int a, int b);
}
class ClassX{
public abstract void sub(int a,int b);
public void run(){
System.out.println("--跑步中--");
}
}
//设计一个子类型,同时具备上面三个引用类型的功能
// 第二种方式:一个类可以在继承一个父类的情况下,同时实现多个接口。也可以达到多继承的效果。
class ClassY extends ClassX implements InterM,InterN{
@Override
public void showInfo() {
System.out.println("--ClassY--");
}
@Override
public void sum(int a, int b) {
System.out.println("两个数的和:"+(a+b));
}
@Override
public void sub(int a, int b) {
System.out.println("两个数的差:"+(a-b));
}
}
进行方法调用测试:
public class Program {
public static void main(String[] args) {
//因为ClassY,同时继承和实现了ClassX,InterM,InterN. 因此可以理解为有多个父亲。
ClassY y = new ClassY();
y.run();
y.showInfo();
y.sum(10,20);
//向上造型成不同的父亲
ClassX x = y; //只能调用父类里的方法
x.run();
InterM m = y;//只能调用接口里的抽象方法
m.showInfo();
InterN n = y;
n.sum(100,200);
}
}
(3)接口间的继承
1. 接口可以继承多个接口,使用extends关键字 ,
2. 子接口拥有了父接口里的所有抽象方法,子接口不用重写父接口里的抽象方法方法。
3.如果继承多个接口,且父类接口里有重名的带有方法体的方法,子接口必须也写一个相同的方法。
4. 子接口可以提供自己独有的抽象方法
5. 类实现子接口时,要重写里面所有的抽象方法。
public interface InterfaceB {
void methodB();
}
interface InterfaceC{
void methodC();
}
interface InterfaceD extends InterfaceC,InterfaceB{//继承InterfaceB、InterfaceC,所以继承两个接口里的所有抽象方法,并且提供了自己的抽象方法。
void methodD();
}
class classT implements InterfaceD{ //实现InterfaceD接口,要重写InterfaceD接口继承的所有抽象方法,也要重写他自己的抽象方法。
@Override
public void methodD() {
}
@Override
public void methodB() {
}
@Override
public void methodC() {
}
}
(4)接口在1.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("--地球只有一个,人人有责---");
}
}
(5)常用接口
1.Serializable序列化接口
系统类库提供好的一个接口。当涉及到数据传输时,比如将内存的对象保存到磁盘,磁盘上的文件变成内存中的对象,或者对象在两台电脑之间传输。那么该对象的类必须实现序列化接口。否则传输失败。
源码:
public interface Serializable {
}
比如String类就是实现了Serializable序列化接口
String类
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
}
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);
}
实现Comparable接口,然后自定义比较规则,以Person类为例
如果想要进行比较,那么除了要实现comparable接口,还要实现里面的比较方法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的相关属性-传入的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;
}
}
3)Comparator接口
用于在compareble的基础上去修改比较规则。
现在想要修改比较规则,按照体重进行升序。不能修改源代码,因为Person类可能有人已经使用了。并不是自己一个人用。
此时就可以使用Comparator比较器进行重新自定义比较规则
使用匿名内部类创建一个比较器对象
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);
//现在想要修改比较规则,按照体重进行升序。不能修改源代码,因为Person类可能有人已经使用了。并不是自己一个人用。
//此时就可以使用Comparator比较器进行重新自定义比较规则
//使用匿名内部类创建一个比较器对象
Comparator c1 = new Comparator<Person>(){
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就是一个枚举。
背景:枚举是在JDK1.5以后引入的。
主要用途是:将一组常量,也可以说成是一组离散值组织起来。
(2)枚举的定义
1)自定义类实现枚举
-
类内部创建一组对象,通常使用public static final关键字共同修饰,对外进行暴露
-
枚举对象名通常全部都会大写,这是常量的命名规范
-
可以提供属性,属性应使用private final共同修饰
-
将构造器私有化
-
属性,可以提供getXXX方法,但是不需要提供setXxx方法,属性应该是只读的。
public class Season {
//可以提供属性,用来描述对象信息, 使用private final修饰
private final String name; //对象的名字
private final String desc; //对象的描述
//构造器私有化 ,使外界不能使用关键字new对象
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("冬天","白雪皑皑");
//给属性提供getXXX方法,向外界暴露,不要提供setXXX方法,因为只读。
public String getName(){
return name;
}
public String getDesc(){
return desc;
}
@Override
public String toString(){
return name+","+desc;
}
}
测试:
public class SeasonTest {
public static void main(String[] args) {
Season s = Season.AUTUMN;
//直接输出。 如果没有重写toString()方法打印的是对象的地址 ,提供toString()方法打印的是里面的内容。
System.out.println(s);
//打印这个季节的名字
System.out.println(s.getName());
//获取这个季节的描述信息
System.out.println(s.getDesc());
}
}
2)enum关键字实现枚举
-
使用enum关键字定义一个枚举,默认会继承java.lang.Enum类,而且是一个final类,因此不能再继承其他类
-
必须在枚举类的第一行声明枚举类对象。有多个枚举对象时,使用逗号隔开,最后一个用分号结尾,没有枚举对象也必须添加一个分号才不会报错。
-
可以提供私有的属性
-
可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须显式调用构造器
public enum Season {
//枚举类里的第一行,必须是枚举的对象,提供了构造器,枚举对象必须显式调用构造器
SPRING("春天","春暖花开"),SUMMER("夏天","烈日炎炎"),
AOTUMN("秋天","落叶归根"),WINTER("冬天","白雪皑皑");
//提供属性,必须私有化。
private String name;
private String desc;
//提供了构造器,构造器必须私有化。
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
//提供get方法
public String getName(){
return name;
}
public String getDesc(){
return desc;
}
public static void main(String[] args) {
Season season = Season.SUMMER;
System.out.println(season);
System.out.println(season.name);
System.out.println(season.desc);
}
}
-
如果使用无参构造器创建枚举对象,则定义对象时,小括号可以省略,调用时,直接使用
类名.枚举类的对象
public enum Week {
//第一行,必须是枚举类的对象. 名称自定义,应该符合常量的命名规则。
//MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
周一,周二,周三,周四,周五,周六,周日;
//内部系统提供了一个无参构造器,因为创建枚举对象时,调用的是无参构造器(构造器是私有的。),因此对象后面的小括号是可以省略的。
public static void main(String[] args) {
System.out.println(Week.周五); //直接用类名.调用
}
}
重写方法形式
-
1.使用enum关键字后,就不能再继承其它类,因为enum会隐式继承Enum,而Java是单继承机制
-
2.枚举类和普通类一样,可以实现接口,必须重写接口里的抽象方法。可以在枚举对象里重写
interface InterA{
void showInfo();
}
public enum Direction implements InterA {
BEFORE("前"){
@Override
public void showInfo() {
System.out.println("向前进,如箭离弦,永不回头");
}
},
AFTER("后"){
@Override
public void showInfo() {
System.out.println("向后退,悬崖勒马");
}
},
LEFT("左"){
@Override
public void showInfo() {
System.out.println("向左看齐");
}
},
RIGHT("右"){
@Override
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);//BEFORE
direction.showInfo();//输出语句
}
}
3)enum的常用方法
toString | 得到当前枚举常量的名称。子类可以通过重写这个方法来使结果更易读 |
name | 返回当前对象名(常量名),子类中不能重写 |
ordinal | 返回当前对象的位置号(编号),默认从0开始 |
values | 返回当前枚举类中所有的常量 |
valueOf | 将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常! |
compareTo | 比较两个枚举常量,比较的就是位置号(编号) |
代码演示:
public static void main(String[] args) {
Season season = Season.SUMMER;
System.out.println(season);
System.out.println(season.name);
System.out.println(season.desc);
//enum的常用方法
Season season1 = Season.AOTUMN;
//输出枚举对象的名字
System.out.println(season1.name());//AOTUMN
2.ordinal()输出的是该枚举对象的次序(编号),从0开始编号
System.out.println(season1.ordinal());//2
//从反编译可以看出values方法,返回的是Season1[],该数组含有定义的所有枚举对象
Season[] values = Season.values();//使用类名调用values()方法可以获取所有的枚举对象
//遍历取出枚举对象
for (Season season2:values){
System.out.println(season2);
}
//4.valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//执行逻辑:根据输入的常量到Season2的枚举对象去查找,如果找到了就返回,如果没有找到就报错
Season aotumn1 = Season.AOTUMN;
Season aotumn = Season.valueOf("AOTUMN");//AOTUMN,找到就返回
System.out.println(aotumn);
System.out.println(aotumn1==aotumn);//true 地址相同
//compareTo:比较两个枚举常量,比较的就是编号
/**
* 源码:public final int compareTo(E o) {
* return self.ordinal - other.ordinal;
* }
*/
System.out.println(Season.SPRING.compareTo(Season.SUMMER));//-1
}
4.内部类
定义在一个类内部的类,就是内部类。内部类可以分为:成员内部类、静态内部类、局部内部类、匿名内部类。
1)成员,静态,局部内部类(了解)
1.成员内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且没有用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程,需要先实例化外部类对象,再使用外部类对象进行内部类的实例化
外部类名 外部变量名 = new 外部类名()
内部类名 内部变量名 = 外部变量名.new 内部类名()
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
4.可以访问外部类的成员: 外部类名.this.成员
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);
//访问外部类的成员: 外部类名.this.成员
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();
}
}
2)静态内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且使用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程中,直接使用 new实例化一个外部类 .内部类对象即可。
内部类名 变量 = new 外部类名.内部类构造器
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class
4.不能直接访问外部类的成员
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();
}
}
3)局部内部类
定义在某一个代码段中的中。
1、没有访问权限修饰符。
2、在当前方法中,直接实例化即可,直接使用关键字new对象(只能在当前方法中)
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)匿名内部类 (重点)
匿名内部类: 就是没有名字的子类型对象
接口名|抽象类名|父类名 变量 = new 接口名|抽象类名|父类名(){
* 方法的重写 * }; (分号不能丢)
注意:要重写接口或者抽象类里面的抽象方法
public class Outer {
public static void main(String[] args) {
A a = new A(){
//匿名内部类,提供成员属性
private String name = "--你好,欢迎来到中国--";
//重写接口A里的抽象方法
public void showInfo(){
System.out.println(name);
}
//子类提供了独有的getXXX方法
public String getName(){
return name;
}
};
// 编译期间,看变量类型,因此能调用到showInfo. 运行期间看对象类型
a.showInfo();
//a.getName(); //编译期间,a里根本没有getName方法,编译都不会通过的
}
}
interface A{
public void showInfo();
}
在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向 上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。
匿名内部类也会生成 .class字节码文件,命名格式 : 外部类$序号 .class
匿名内部类可以访问外部类的成员,但需要将外部类成员声明为final。
如下书写是正确的 ,输出test
interface TestA {
String toString();
}
public class Test {
public static void main(String[] args) {
System.out.println(new TestA() {
public String toString() {
return "test";
}
});
}
}