目录
1.3.3 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
1.3.4 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
1.3.5 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
1.3.6 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
2.3.2 接口当中只能存在抽象方法,除了被 static 或 default 修饰的方法
2.3.3 接口当中的抽象方法默认都是public abstract 修饰的
2.3.4 接口中的成员变量默认都是public static final修饰的
2.3.6 类和接口之间的关系可以使用 implements 来
2.3.8 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
2.3.9 jdk8中:接口中还可以包含default方法。
一、抽象类
1.1 抽象类的概念
1.2 抽象类语法
abstract public class Shape {
abstract void draw();
}
1.3 抽象类特性
1.3.1 抽象类不能实例化对象
1.3.2 抽象方法不能是被private修饰的
1.3.3 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
1.3.4 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
1.3.5 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
1.3.6 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
1.4 抽象类的作用
二、接口
2.1 接口的概念
2.2 语法规则
publicinterface接口名称{
//抽象方法
publicabstractvoidmethod1(); //public abstract是固定搭配,可以不写
publicvoidmethod2();
abstractvoidmethod3();
voidmethod4();
//注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
2.3 接口特性
2.3.1接口是使用interface修饰的
2.3.2 接口当中只能存在抽象方法,除了被 static 或 default 修饰的方法
2.3.3 接口当中的抽象方法默认都是public abstract 修饰的
2.3.4 接口中的成员变量默认都是public static final修饰的
2.3.5 接口不能直接实例化
2.3.6 类和接口之间的关系可以使用 implements 来
interface Shape {
void draw();
}
public class Triangle implements Shape{
public void draw(){
System.out.println("画了一个三角形");
}
}
public class Rect implements Shape {
public void draw(){
System.out.println("画了一个矩形");
}
}
public class Circle implements Shape{
public void draw(){
System.out.println("画了一个圆形");
}
}
public class Main {
public static void main(String[] args) {
Shape[] shapes = new Shape[]{new Circle(),new Triangle(),new Rect()};
for (Shape shape: shapes){
shape.draw();
}
}
}
2.3.7 接口也有对应的字节码文件
2.3.8 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
2.3.9 jdk8中:接口中还可以包含default方法。
2.4 实现多个接口
package Demo2;
abstract public class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void eat();
}
interface IRUN{
void run();
}
interface ISWIM{
void swim();
}
interface IFLY{
void fly();
}
package Demo2;
public class Dog extends Animal implements IRUN {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
@Override
public void run() {
System.out.println(this.name + "正在飞速地跑");
}
}
package Demo2;
public class Cat extends Animal implements IRUN {
public Cat(String name, int age) {
super(name, age);
}
public void eat(){
System.out.println(this.name + "正在吃猫粮");
}
public void run(){
System.out.println(this.name + "正在轻快地跑");
}
}
package Demo2;
public class Duck extends Animal implements IRUN,IFLY,ISWIM{
public Duck(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃虫子");
}
@Override
public void run() {
System.out.println(this.name + "正在用鸭脚跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在用鸭脚游泳");
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
}
需要注意的是
顺序不能改变
不能说是具备了某个功能是某个事物
而是是某个事物才具备某个功能
package Demo2;
public class Main {
public static void func(Animal animal) {
animal.eat();
}
public static void running(IRUN irun) {
irun.run();
}
public static void flying(IFLY ifly) {
ifly.fly();
}
public static void swimming(ISWIM iswim) {
iswim.swim();
}
public static void main(String[] args) {
func(new Duck("小黄", 2));
func(new Dog("大黄", 2));
func(new Cat("花花", 2));
swimming(new Duck("小黄", 2));
running(new Duck("小黄", 2));
running(new Dog("大黄", 2));
running(new Cat("花花", 2));
flying(new Duck("小黄", 2));
}
}
甚至只要是任意一个类就可以实现接口就可以使用
package Demo2;
public class Robot implements IRUN{
public void run(){
System.out.println("机器人正在跑!");
}
}
public static void main(String[] args) {
running(new Robot());
}
2.5 接口间的继承
interface IRUN {
void run();
}
interface ISWIM {
void swim();
}
interface IFLY {
void fly();
}
interface IALL extends ISWIM, IRUN, IFLY {
}
package Demo2;
public class Main {
public static void running(IRUN irun) {
irun.run();
}
public static void flying(IFLY ifly) {
ifly.fly();
}
public static void swimming(ISWIM iswim) {
iswim.swim();
}
public static void main(String[] args) {
swimming(new Duck("小黄", 2));
running(new Duck("小黄", 2));
flying(new Duck("小黄", 2));
}
}
2.6 使用接口实现对象的比较
如果直接对对象进行大小比较
是不允许进行比较的
因为这里的 dog1 dog2 属于引用数据类型,是地址,无法进行比较
而如果想要进行比较,需要通过他们的属性进行比较
Java中提供了 Comparable 这个接口
那么这里可以看到的是需要重写 compareTo 这个方法
abstract public class Animal implements Comparable<Animal> {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Animal o) {
return this.age-o.age;
}
public abstract void eat();
}
interface IRUN {
void run();
}
interface ISWIM {
void swim();
}
interface IFLY {
void fly();
}
interface IALL extends ISWIM, IRUN, IFLY {
}
这里假设要按年龄进行比较
那么就可以通过这个方法进行比较
public static void main(String[] args) {
Dog dog1 = new Dog("1",2);
Dog dog2 = new Dog("2",2);
System.out.println(dog1.compareTo(dog2));
}
这说明 在一个类中实现 Comparable 就说明这个类是可以进行比较的
但是这样写确实可以进行比较,但是对类的入侵性比较强
如果需求更改了,不按照这一属性进行比较
通俗点来说,就是写死了
那么可以采用比较器(Comparator)来进行比较
package Demo2;
import java.util.Comparator;
public class AgeComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.age - o2.age;
}
}
public static void main(String[] args) {
AgeComparator ageComparator = new AgeComparator();
Dog dog1 = new Dog("1",2);
Dog dog2 = new Dog("2",3);
System.out.println(ageComparator.compare(dog1, dog2));
}
对于name 来说就不能这样 ,因为他是引用数据类型
直接使用 compareTo 这个方法,那么这里并没有重写这个方法,为什么可以直接使用呢
是因为这里name属于String类,而String类中实现了 compareTo
public static void main(String[] args) {
AgeComparator ageComparator = new AgeComparator();
NameComparator nameComparator = new NameComparator();
Dog dog1 = new Dog("1", 2);
Dog dog2 = new Dog("2", 3);
System.out.println(ageComparator.compare(dog1, dog2));
System.out.println(nameComparator.compare(dog1,dog2));
}
Animal中的 compareTo 方法实现后也可以让Array.sort 方法成功实现
public static void main(String[] args) {
Animal[] animals = new Animal[3];
animals[0] = new Dog("cc",3);
animals[1] = new Dog("bb",2);
animals[2] = new Dog("aa",1);
System.out.println(Arrays.toString(animals));
Arrays.sort(animals);
System.out.println("======================");
System.out.println(Arrays.toString(animals));
}
还可以使用比较器模拟 sort 排序
public static void main(String[] args) {
Animal[] animals = new Animal[3];
animals[0] = new Dog("cc", 3);
animals[1] = new Dog("bb", 2);
animals[2] = new Dog("aa", 1);
System.out.println(Arrays.toString(animals));
for (int i = 0; i < animals.length - 1; i++) {
AgeComparator ageComparator = new AgeComparator();
for (int j = 0; j < animals.length - 1 - i; j++) {
if (ageComparator.compare(animals[j], animals[j + 1]) >= 1) {
Animal temp = animals[j];
animals[j] = animals[j + 1];
animals[j + 1] = temp;
}
}
}
System.out.println(Arrays.toString(animals));
}
2.7 Clonable 和 深拷贝
这里有一个 Person 类
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
想要将一个 Person 对象拷贝给另一个 Person 对象可以使用 clone 方法
而在这个 Person 类 中并没有这个方法,需要自己写吗
在之前的文章中说到,Java中所有类的父类都是 Object 类
而 Object 类中 正好实现了 clone 方法
那么 Person 类中肯定也继承了这个方法,就可以使用
但是直接使用的时候编译器报错了
在 Object 类中该方法是被 protected 修饰的,需要通过 super 调用
那么这里在 Person 类中重写一下这个方法
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
需要注意的是
此时这里的返回类型是 Object
在进行对象拷贝的时候 , 返回值类型需要进行类型转换
public class Main {
public static void main(String[] args) throws CloneNotSupportedException{
Person person1 = new Person("张三",18);
Person person2 = (Person) person1.clone();
}
}
为什么会加上这个会在以后的文章中讲到
但是这里在代码跑起来后,编译器仍然报错了
需要在 Person 类这里加上 implements Cloneable
public class Person implements Cloneable {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
此时就可以跑了
2.7.1 浅拷贝
在2.7的例子上做些改动
public class Money {
double m = 20.0;
}
public class Person implements Cloneable {
public String name;
public int age;
Money money = new Money();
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException{
Person person1 = new Person("张三",18);
Person person2 = (Person) person1.clone();
System.out.println(person1);
System.out.println(person2);
System.out.println("========================");
System.out.println("person1:"+person1.money.m);
System.out.println("person2:"+person2.money.m);
person1.money.m = 99.99;
System.out.println("========================");
System.out.println("person1:"+person1.money.m);
System.out.println("person2:"+person2.money.m);
}
}
这里可以看到的是,只对 person1的 money 进行了修改
但是 person2 中 money 的值也被修改了
原因是
clone 方法只是将 person1 指向的对象进行克隆
money 这个对象的地址并没有改变,就说明 person1 和 person2 中的 money 对象是同一个
这种情况就成为浅拷贝
2.7.2 深拷贝
那么原因分析出来了,想避免这种情况其实就很简单了,把 money 变成两个对象不就好了
即对 money 再单独克隆一份
public class Money implements Cloneable{
double m = 20.0;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
// return super.clone();
Person temp = (Person) super.clone();
temp.money = (Money) this.money.clone();
return temp;
}
此时运行之前的代码
public class Main {
public static void main(String[] args) throws CloneNotSupportedException{
Person person1 = new Person("张三",18);
Person person2 = (Person) person1.clone();
System.out.println(person1);
System.out.println(person2);
System.out.println("========================");
System.out.println("person1:"+person1.money.m);
System.out.println("person2:"+person2.money.m);
person1.money.m = 99.99;
System.out.println("========================");
System.out.println("person1:"+person1.money.m);
System.out.println("person2:"+person2.money.m);
}
}
对 person1的 money 的修改就不会影响到 person2 中 money 的值
总而言之是将每一个对象都进行拷贝
这种拷贝方式就叫做深拷贝
2.8 抽象类和接口的区别
区别 | 抽象类(abstract) | 接口(intertface) | |
1 | 结构组成 | 普通类 + 抽象方法 | 抽象方法+全局常量 |
2 | 权限 | 各种权限 | public |
3 | 子类使用 | extends关键字 | implements关键字 |
4 | 关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,可以使用extends关键字继承多个父接口 |
5 |
3.内部类
3.1 实例内部类
public class Out {
class Inner{
public int a ;
public int b ;
public void test(){
System.out.println("调用了内部类方法");
}
}
public void test(){
System.out.println("调用了外部类方法");
}
}
这样就在一个类当中创建了一个内部类
这个内部类不能直接进行实例化
原因是内部类本质上属于外部类的成员
需要依赖外部类对象进行调用
首先需要实例化外部类对象,再通过外部类对象的引用来实例化内部类
public class Main {
public static void main(String[] args) {
Out out = new Out();
Out.Inner inner = out.new Inner();
// 通过外部类对象调用内部类实例
inner.test();
}
}
一般情况下,内部类中不能使用 static 进行修饰
原因是静态的变量不依赖实例化
这就与内部类的实例相冲突了
如果想使用 static 修饰 就还需要加上 final 修饰
直接让内部类的该成员变量成为常量
public class Main {
public static void main(String[] args) {
Out out = new Out();
Out.Inner inner = out.new Inner();
// 通过外部类对象调用内部类实例
inner.test();
System.out.println(Out.Inner.c);
}
}
存在
public class Out {
public int a = 1;
class Inner{
public int a = 2;
public int b ;
public static final int c = 5;
public void test(){
System.out.println("调用了内部类方法");
System.out.println(a);
}
}
public void test(){
System.out.println("调用了外部类方法");
}
}
若存在同名成员变量,在内部类中调用成员变量优先调用内部类自己的成员变量
public static void main(String[] args) {
Out out = new Out();
Out.Inner inner = out.new Inner();
// 通过外部类对象调用内部类实例
inner.test();
}
如果想使用外部类的同名成员变量
就得使用外部类类名.this.成员变量
public void test(){
System.out.println("调用了内部类方法");
System.out.println(a);
System.out.println(Out.this.a);
}
3.2 静态内部类
被 static 修饰的内部类被称为静态内部类
public class Out2 {
public int a;
static class Inner2{
public int b;
public int c;
public static int d = 10;
public static void test(){
System.out.println("调用了静态内部类方法");
}
}
public void test(){
System.out.println("调用了外部类方法");
}
}
静态内部类不需要依赖外部类对象的引用就可以使用
即不需要实例话一个外部类对象就可以直接使用外部类名.内部类名 类名
实例化一个内部类对象
public class Out2 {
public int a;
static class Inner2{
public int b;
public int c;
public static int d = 10;
public void test(){
System.out.println("调用了静态内部类方法");
}
}
public void test(){
System.out.println("调用了外部类方法");
}
}
public class Main {
public static void main(String[] args) {
Out2.Inner2 inner2 = new Out2.Inner2();
inner2.test();
}
而在静态类中想要调用外部的非静态成员变量
则需要外部类对象的引用
public class Out2 {
public int a = 1;
static class Inner2{
public int b;
public int c;
public static int d = 10;
public void test(){
Out2 out2 = new Out2();
System.out.println("调用了静态内部类方法");
System.out.println(out2.a);
}
}
public void test(){
System.out.println("调用了外部类方法");
}
}
public static void main(String[] args) {
Out2.Inner2 inner2 = new Out2.Inner2();
inner2.test();
}
总的来说
静态内部类相比于实例内部类
不需要外部类对象的引用,使用起来更加方便
3.3 局部内部类
public class Test2 {
public void fun(){
class Demo{
public int a = 2;
}
Demo demo = new Demo();
System.out.println("调用了局部内部类!");
System.out.println(demo.a);
}
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.fun();
}
}
3.4 匿名内部类
与接口有关
interface Demo{
void test();
}
public class Test3 {
public static void main(String[] args) {
new Demo(){
@Override
public void test() {
System.out.println("匿名内部类实现Demo接口");
}
};
}
}
接口是不能进行实例化的
但是此处就可以理解为有一个类实现了Demo这个接口,而这个类的名字是被隐藏起来的
调用方法有两种
public static void main(String[] args) {
Demo demo = new Demo(){
@Override
public void test() {
System.out.println("匿名内部类实现Demo接口");
}
};
demo.test();
System.out.println("===========================");
new Demo(){
@Override
public void test() {
System.out.println("匿名内部类实现Demo接口");
}
}.test();
}