问题导入:
在开发中,一个父类的子类里往往对于该父类的某一个方法实现,结果与过程都是不一样的,这时候,原先父类里定义的该方法就显得有些多余(不满足实现该方法的子类就必须强制重写该方法)
抽象方法&抽象类的引入
抽象类
概念 : 继承中,将多个共同的内容,提取到一个类中,但是有些共同方法,方法声明是一样的,具体的实现不一样(每个具体的对象在做具体的操作的时候,功能实现不同),如果有这种情况,这个方法就需要被定义为抽象方法
抽象方法就是具有方法名,但是没有具体的方法实现(没有大括号)
如果一个类中存在抽象方法,那么该类 就是抽象类
抽象类特点 :
写法 : 抽象方法或者抽象类通过abstract 关键字 修饰
特点 :
抽象类不能实例化
抽象类中,可以没有抽象方法,但是有抽象方法的类一定是抽象类
抽象类的子类,必须是一个抽象类 或者 是个普通类,普通类必须重写抽象类中的所有抽象方法
抽象类的成员特点:
成员属性 : 可以有变量,也可以有常量
成员方法 :有抽象方法,普通方法,静态方法
构造方法 :有构造方法,抽象类不能实例化,所以构造方法一般提供给子类访问使用
注意:abstract关键字和static关键字不可以同时作用于一个方法中,代码会报错
原因:静态方法一般都需要进行具体的实现,需要有方法体{实现代码},而抽象关键字的作用恰恰相反,不允许方法有具体的实现方法体(实现代码)两者是相违背的,因此不能同时写,写了代码会报错 即 修饰符 static abstract 返回值类型 方法名(); 是错误写法
public abstract class Animal {
private String name;
final int age = 20;
public Animal(String name) {
this.name = name;
}
public Animal() {
}
//抽象方法,声明为抽象方法的类,必须是抽象类
// 加上abstract关键字,完成方法和类的声明
public abstract void eat();
public void sleep(){
System.out.println("动物会睡觉!");
}
public static void show(){
System.out.println("动物会说话!");
}
}
下面是具体案例,定义一个Person抽象类,其中有对应的抽象方法,定义它的子类,实现抽象方法的重写等等。
抽象类Person
package com.day06.AbstractDemo;
public abstract class Person {//抽象类可以被定义成里面没有抽象方法,但是
//有抽象方法的类必须是抽象类
private String name;//抽象类可以定义属性但是这些对象属性不能被赋值(实例化)
private int age;
public Person() {//抽象类的构造方法可以写,但是不能被实例化(对象赋值)
//提供是为了让子类进行访问(提供构造方法构造对象)
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void work();//抽象方法
//父类作为抽象类,里面的抽象方法同样被子类继承,而且子类必须重写这些抽象方法
public void sleep(){//普通方法
System.out.println("人类会睡觉");
//父类的普通方法也可以被子类继承并调用
}
public static void show(){//静态方法
System.out.println("父类的show是静态方法");
}
}
子类Student
package com.day06.AbstractDemo;
public class Student extends Person{
@Override
public void work() {//子类一但继承了抽象类,就强制要重写其所有的抽象方法
System.out.println("学生的工作是学习");
}
}
子类Teacher
package com.day06.AbstractDemo;
public class Teacher extends Person{
@Override
public void work() {
System.out.println("老师的工作是教书");
}
}
测试类
package com.day06.AbstractDemo;
public class PersonTest {
public static void main(String[] args) {
Student student1 = new Student();
Teacher teacher1 = new Teacher();
student1.work();
teacher1.work();
student1.sleep();//普通方法的实现
Person.show();//抽象类的静态方法可以执行
}
}
面向对象三大特征:封装、继承 、多态
多态特性必须要有 继承 extends 或者实现 implements (实现接口 )
多态
父类的引用指向子类的对象
Person person =new Student();
父类 对象名 =new 子类();
public class Person {
public void say(){
System.out.println("人类会说话!");
}
public void show(){
System.out.println("这是父类的show方法");
}
}
public class Student extends Person {
@Override
public void show(){
System.out.println("这是子类的show方法");
}
public void show1(){
System.out.println("这是子类特有的show1方法");
}
}
public class PersonTest {
public static void main(String[] args) {
//多态创建Person对象
//父类的引用指向子类的对象
Person person = new Student();
person.say(); //作为父类形态,可以调用父类特有的方法
// person.show1(); //不能调用子类特有的方法
person.show(); // 作为子类形态,可以调用子类重写父类的方法
}
}
多态的特点
前提条件:
1.存在继承关系或者实现关系,
2.必须有方法重写(多态体现)
3.有父类或者父接口的引用指向子类对象
多态的分类
1.具体类的多态
class Fu{}
class Zi extends Fu{}
Fu f =new Zi();
2.抽象类的多态
abstract class Fu{}
class Zi extends Fu{}
Fu f =new Zi();
3.接口多态
interface Fu{}
class Zi implements Fu{}
Fu f =new Zi();
多态关系中成员访问特点 :
成员变量 : 直接看等号的左边,左边是谁,优先找谁,没有向上找,找不到就报错
编译看左边,运行看左边
成员方法 :先从等号的左边找,左边不存在,就报错,运行的时候看new的是谁,就找谁
编译看左边,运行看右边
构造方法 :子类的构造默认会访问父类构造
多态的好处:
多态可以提高代码的维护性(继承)
多态可以提高代码的扩展性(多态体现)
public abstract class Emp {
public abstract void work();
}
public class Teacher extends Emp{
@Override
public void work() {
System.out.println("老师在讲课!");
}
}
public class Assistant extends Emp {
@Override
public void work() {
System.out.println("助教在辅导");
}
}
public class EmpTest {
public static void main(String[] args) {
//没有多态,创建对象,关心的是通过哪个类创建的对象调用的work方法
Teacher emp1 = new Teacher();
emp1.work();
Assistant emp2 = new Assistant();
emp2.work();
//多态的方式创建,关注点是work方法的调用,其他的东西我不关心
Emp emp3 = new Teacher();
emp3.work();
Emp emp4 = new Assistant();
emp4.work();
}
}
多态的缺点 :
父类引用不能使用子类特有功能,子类可以当做父类使用,父类不能当做子类使用
多态的转型:(面试问题)
多态中,父类引用不能使用子类特有功能,所以,要将父类对象做转型。
多态的转型,分为向上转型和向下转型
多态的分类 :(面试问题)
面向对象中,根据代码执行时刻的不同,将多态分为 编译时多态 和 运行时多态
编译时多态是静态的,一般指的是方法的重载,根据参数列表来区分不同的方法,通过编译之后就会变成不同的方法。
运行时多态是动态的,它是通过动态绑定来实现的,通过方法的重写来体现的,也就是之前讲解的面向对象的多态性
final关键字
概念:final表示最终的意思,可以用来修饰类,方法,变量
特点:
1.被final修饰的类不能被继承(String类就被final修饰,不能被继承)
2.final修饰的方法,不能被重写
3.被final修饰的变量,会变成常量,必须一开始就被赋予初始值(不会因为程序的运行而发生值的改变),如果被final修饰了还不赋值,代码会报错
public int num1 =10;//普通的变量
public final int num2=20;//常量20,无法被重新赋值(数值不能发生改变)
被final修饰的方法内的局部变量也无法再方法内被重新赋值(因为你执行方法时已经往局部变量里传了初值了,这个时候再进入到方法体内进行二次赋值会报错)
public class Father {
public int num = 10; //普通的变量
public final int num2 = 20; //常量
public final void test01(){
System.out.println("父类的final方法");
}
}
public class Son extends Father {
String name;
public void show(){
num = 100;
//num2是常量,无法重新赋值
//num2 = 1000;
System.out.println(num);
System.out.println(num2);
}
//子类不能重新父类的final方法
//public void test01(){
// System.out.println("子类的方法");
//}
}
public class Test {
public static void main(String[] args) {
Son son = new Son();
son.show();
final int a = 10; //final修饰的局部变量也是常量
// a = 100; //不能被重新赋值
System.out.println(a);
Son son1 = new Son();
son1.name = "张三";
System.out.println(son1.name);
final Son son2 = new Son();
son2.name = "李四";
System.out.println(son2.name);
//final修饰的引用类型变量,变量中的属性值是可以被改变的
son2.name = "王五";
System.out.println(son2.name);
//final修饰的引用的类型的变量,引用地址无法重新被赋值
//son2 = new Son();
}
//final能不能修饰局部变量?修饰基本类型和引用类型的区别
//final修饰的局部变量也是常量
//final修饰的引用类型变量,变量中的属性值是可以被改变的
//final修饰的引用的类型的变量,引用地址无法重新被赋值
}
接口
接口里的方法都是抽象方法(不需要加abstract关键字,因为接口里的方法默认自己加上abstract)
实现类实现接口时需要实现父类接口里的所有的抽象方法(解决单继承的局限性)
接口的概念:接口在java中就是一种规则也是一种约束,实现类必须遵守接口里的规则。
接口和类属于统一级别(接口可以看作是特殊的类)
接口的特点:
写法:使用interface关键字,其实就是将类中的class换成interface
public interface 接口名()
子类实现接口
class 类名 implements 接口名{}
接口和接口之间可以继承
interface 子接口 extends 父接口{
}
其他特点:接口不能被实例化:不能new 接口名;
接口的子类(实现类)可以是一个具体的普通类(具体类中必须要实现接口内的全部方法)
也可以是一个抽象类,抽象类可以不用实现全部抽象方法
Java中的接口 :
概念 :接口就是一种规则,也是一种约束,实现类必须要遵守接口中定义的信息,并且还可以解决单继承的局限性
接口的特点:
写法 : 使用interface关键字,其实就是将类的class换成interface
public interface 接口名{}
子类实现接口:
class 类名 implements 接口名{}
interface 子接口 extends 父接口{}
其他特点 :
1,接口不能被实例化
2,接口的实现类 ,
可以是一个具体类,具体类中,必须要实现父接口的全部抽象方法
可以是一个抽象类,抽象类可以不用实现全部抽象方法
3,成员特点:
成员变量
成员变量只能是常量,默认的修饰符是 : public static final
构造方法
接口中没有构造方法
成员方法
默认只能存在抽象方法,默认修饰符就是 public abstract
jdk8针对接口的不容易扩展现象,增加了static 和 default方法,如果定义这两个方法,方法中必须要写方法体用来实现某些功能,主要用来增加接口的灵活性
jdk9之后,在接口中又增加了私有private修饰的方法和private修饰的静态方法,这些私有的方法,主要可以改善接口内部代码的可重用性
因为接口方法都是抽象方法,没有办法实例化new,因此都是通过多态写法,利用父类(接口)引用指向子类对象(实现类对象)
父类接口指定一个方法,子类(即实现类)去实现这个方法。
接口 接口名 =new 实现类();
运行的时候直接
接口名.方法名();
注意:接口中不能定义属性(只能定义常量,没有意义)
接口与抽象类的区别
区别主要在设计区别上(日常使用中的区别不是很大)
接口和抽象类的区别 · 语雀 《接口和抽象类的区别》
内部类
概念:把类定义在其他类的内部,这个类就被成为内部类
特点:内部类作为外部类的一部分,它可以直接访问外部类的属性和方法(包括私有的)
相反,外部类如果要访问内部类的成员则需要创建内部类对象
分类:
定义在成员位置:成员内部类
定义在局部位置(一般指外部类的方法里):局部内部类
public class Outer {
private int num = 100; //外部类的私有属性
//成员内部类
class Inner{
}
//外部类的成员方法
public void method(){
//局部内部类
class Inner{
}
}
}
在同一个类中,成员内部类可以跟定义在方法里的局部内部类同名(类比成员变量和局部变量),它们的作用范围不一样
创建普通成员内部类对象的写法(固定写法)
public class Outer {
private int num = 100; //外部类的私有属性
private static int num1 = 200;
//成员内部类
class Inner{
//内部类可以使用的修饰符
//private static
//内部类的普通方法
public void show(){
//内部类加了static之后,只能访问外部类中加了static的属性
System.out.println(num);
System.out.println(num1);
}
//如果内部类没有static,内部不能定义static方法
//public static void show2(){
// //System.out.println(num);
// System.out.println(num1);
//}
}
}
public class InnerDemo {
public static void main(String[] args) {
//创建内部类的对象的写法 :
//外部类名.内部类名 对象名 = 外部类对象.内部类对象
Outer.Inner inner = new Outer().new Inner();
inner.show();
}
}
外部类名.内部类名 对象名 = 外部类对象.内部类对象
Outer.Inner inner = new Outer().new Inner();
(外面多了一层外部类的创建)
内部类的调用:
inner.方法名();
内部类可以使用的修饰符
//private static
静态成员内部类
public class Outer {
private int num = 100; //外部类的私有属性
private static int num1 = 200;
//成员内部类
static class Inner{
//内部类可以使用的修饰符
//private static
//内部类的普通方法
public void show(){
//内部类加了static之后,只能访问外部类中加了static的属性
//System.out.println(num);
System.out.println(num1);
}
//如果内部类没有static,内部不能定义static方法
public static void show2(){
//System.out.println(num);
System.out.println(num1);
}
}
}
public class InnerDemo {
public static void main(String[] args) {
//静态的内部类对象创建方式:
//外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner inner1 = new Outer.Inner();
inner1.show();
inner1.show2();
//静态内部类中的静态方法,可以通过类名直接访问
Outer.Inner.show2();
//Outer.Inner.show();
}
}
内部类加了static之后(变成静态内部类),就只能访问外部类中的静态成员变量
同样的如果内部类没有被static修饰,它的内部不能定义静态的方法与变量
局部内部类:
1,可以直接访问外部类的成员
2,可以在局部位置,创建内部类的对象,通过对象调用内部类的方法,从而来使用局部内部类的功能
public class Outer {
private String str = "hello";
public void method(String a){
//a = "world"; //变量a重新赋值会报错
//局部内部类
class Inner{
//内部类的成员方法
public void show(){
System.out.println(str); //直接访问外部类的成员属性
System.out.println(a); //访问局部变量
}
}
//在局部位置,直接创建内部类的对象
Inner inner = new Inner();
inner.show();
}
}
public class InnerTest {
public static void main(String[] args) {
//创建外部类对象,通过外部类对象直接调用成员方法
//从而调用到成员方法中的局部内部类
Outer outer = new Outer();
outer.method("你好");
}
}
局部内部类访问局部变量的时候的问题
1,局部内部类访问的局部变量,必须是用final修饰的
为什么?
局部变量是随着方法的调用而存在,方法调用完毕而消失
局部内部类对象是在堆中,堆中的内容不会马上消失,所以如果不加final修饰,可能会出现一个没有消失的对象,在调用已经消失的局部变量,这种情况是不合理的。
加了final修饰后,变量就变为常量了,消失之后内存中的数据还是存在
jdk8之后,局部内部类的局部变量,即使没有加上final,系统也会默认变量是final的
匿名内部类
匿名内部类是内部类的简化写法
需要前提 :需要存在一个类或者接口,这个类可以是具体类,也可以是抽象类
new 类名或者接口名(){
重写方法;
}
public interface Inner {
void show1();
void show2();
}
public class Outer {
//外部类的成员方法
public void method(){
//这个操作,相当于在创建接口的实现类
//这个实现类,没有名字,称为匿名内部类
//相当于把实现Inner接口的实现类,和实现类的对象都创建了
//完成了4个动作 :
// 1,Inner接口实现类的创建 2,接口方法的重写 3,实现类对象的创建 4,重写方法的调用
/* new Inner(){
//实现接口中的方法
@Override
public void show1() {
System.out.println("匿名内部类重写的show1方法~");
}
@Override
public void show2() {
System.out.println("匿名内部类重写的show2方法~");
}
}.show1();
//接口中有多个方法的话,写实现就得写多次,比较麻烦
new Inner(){
//实现接口中的方法
@Override
public void show1() {
System.out.println("匿名内部类重写的show1方法~");
}
@Override
public void show2() {
System.out.println("匿名内部类重写的show2方法~");
}
}.show2();
*/
//使用多态来改进写法,父接口的引用指向子类的对象
Inner inner = new Inner(){
//实现接口中的方法
@Override
public void show1() {
System.out.println("匿名内部类重写的show1方法~");
}
@Override
public void show2() {
System.out.println("匿名内部类重写的show2方法~");
}
};
inner.show1();
inner.show2();
}
}
public class OuterTest {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
匿名内部类的笔试题:
public interface Inner {
void show();
}
public class Outer {
//等待补齐代码
}
public class OuterTest {
public static void main(String[] args) {
Outer.method().show();
//在后台输出 :"HelloWord!"
}
}
补充代码:
//Outer.method()
//说明存在静态的method()方法
//Outer.method().show() 说明method方法应该返回Inner对象
public static Inner method(){
return new Inner() {
@Override
public void show() {
System.out.println("HelloWorld!");
}
};
}
通过设计模式的完成计算器的使用:
基本代码实现
/*
基本代码实现计算器
*/
public class Demo01 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入计算的第一个数字:");
int a = scanner.nextInt();
System.out.println("请输入运算符号:");
String b = scanner.next();
System.out.println("请输入计算的第二个数字:");
int c = scanner.nextInt();
int result = 0;
switch (b){
case "+":
result = a + c;
break;
case "-":
result = a - c;
break;
case "*":
result = a * c;
break;
case "/":
result = a / c;
break;
default:
System.out.println("运算符输入错误!");
return;
}
System.out.println( a + b + c + "=" +result);
}
}
面向对象实现
//面向对象实现
public class Operation {
public static int getResult(int a,int b,String operate){
int result = 0;
switch (operate){
case "+":
result = a + b;
break;
case "-":
result = a - b;
break;
case "*":
result = a * b;
break;
case "/":
result = a / b;
break;
default:
System.out.println("运算符输入错误!");
break;
}
return result;
}
}
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入计算的第一个数字:");
int a = scanner.nextInt();
System.out.println("请输入运算符号:");
String b = scanner.next();
System.out.println("请输入计算的第二个数字:");
int c = scanner.nextInt();
int result = Operation.getResult(a, c, b);
System.out.println(result);
}
}
利用设计模式的工厂方法模式:
//计算类抽象类
public abstract class Operation {
private double num1;
private double num2;
public abstract double getResult();
public double getNum1() {
return num1;
}
public void setNum1(double num1) {
this.num1 = num1;
}
public double getNum2() {
return num2;
}
public void setNum2(double num2) {
this.num2 = num2;
}
}
//加法类
public class Add extends Operation {
@Override
public double getResult() {
return getNum1() + getNum2();
}
}
//减法类
public class Minus extends Operation {
@Override
public double getResult() {
return getNum1() - getNum2();
}
}
利用设计模式的抽象工厂模式:
//计算类抽象类
public abstract class Operation {
private double num1;
private double num2;
public abstract double getResult();
public double getNum1() {
return num1;
}
public void setNum1(double num1) {
this.num1 = num1;
}
public double getNum2() {
return num2;
}
public void setNum2(double num2) {
this.num2 = num2;
}
}
//加法类
public class Add extends Operation {
@Override
public double getResult() {
return getNum1() + getNum2();
}
}
//减法类
public class Minus extends Operation {
@Override
public double getResult() {
return getNum1() - getNum2();
}
}
//计算工厂抽象类
public abstract class OperationFactory {
public abstract Operation createOperation();
}
//加法工厂
public class AddFactory extends OperationFactory {
@Override
public Operation createOperation() {
return new Add();
}
}
//减法工厂
public class MinusFactory extends OperationFactory {
@Override
public Operation createOperation() {
return new Minus();
}
}
//测试类
public class Test {
public static void main(String[] args) {
AddFactory addFactory = new AddFactory();
Operation operation = addFactory.createOperation();
operation.setNum1(10);
operation.setNum2(20);
System.out.println(operation.getResult());
}
}
使用抽象工厂之后,添加一个运算,只要在写子类继承即可:
比如,加个乘法类:
//乘法类
public class ChengFa extends Operation {
@Override
public double getResult() {
return getNum1() * getNum2();
}
}
//乘法工厂
public class ChengFaFactory extends OperationFactory {
@Override
public Operation createOperation() {
return new ChengFa();
}
}
//测试类
ChengFaFactory chengFaFactory = new ChengFaFactory();
Operation operation1 = chengFaFactory.createOperation();
operation1.setNum2(10);
operation1.setNum1(20);
System.out.println(operation1.getResult());