第十五讲 抽象类与接口
1 final关键字
- final 意为:最终的
- 用在什么地方:
可以修饰类:被final修饰的类,是无法被继承的。因为final意为最终的。既然是最终的类,那么它就不会有子类了。
可以修饰变量:(只要是变量)它被final修饰以后,值不能被改变。也就是说,只能给final修饰的变量赋一次值,不能二次赋值。被final修饰的局部变量,生命周期结束在方法结束时,也就是说被final修饰的局部变量,仍然是一个局部变量。
final 修饰的成员变量,一定要赋初值。且只能赋一次值。(重要)
final修饰的引用类型的成员变量,也要赋初始值,且只能赋值一次。
final修饰的局部变量(方法中定义的),可以先声明,再赋值,但是只能赋一次值。
成员变量被final修饰为什么一定要初始化?成员变量是成员的,是实例对象的,如果被final修饰以后,意为该成员变量是最终的,也就是说任何一个对象看到的都是一样的东西,不能跟随对象的改变而改变了,因为它是最终的。
对象的成员属性,是对象所有的,对象不同,变量值也不一样。如果被final修饰以后,不赋初始值,那么其他的对象都有能力将该值修改一下。那么这就不是最终的了。十年前,一个对象给final修饰的成员属性赋值了,十年后又有对象修改了final修饰的成员属性值,这样的成员属性就不叫做最终的了。final失去了意义。
可以修饰方法:被final修饰的方法是不能被覆盖的
- 常量:常量,是一个常数,比如圆周率π,它不随任何东西的改变而改变,是客观存在的。因为常数,不是变量,所以不会被改变。这种不会被改变的量,我们在定义的时候要不要每个对象都给一份?我们希望全局只有一份,而且这一份不能被改变。常量在java中应该是这个样子,只分配一次内存,且只能赋值一次。
- 全局只有一份,在java中怎么做?static关键字修饰。因为static关键字修饰的变量在方法区内存中存在,在类加载的时候执行(分配空间),且只执行(分配空间)一次。但是被static修饰的变量,值是可以变得,内存地址只能被分配一次。我们发现final只能赋值一次,不能二次赋值。
- 所以,java中的常量是:final + static修饰的。
- final static double PI = 3.1415;
- PI在方法区内存中存在,因为static修饰的在类加载的时候执行,方法区的变量生命周期是整个程序结束。
- PI只能被赋值一次,因为被final修饰,不可能再进行二次赋值,即不能被更改
- 常量书写的规则:单词字母全部大写,如果两个以上的单词连写,用”_”隔开
- 常量可以被定义为private的吗?语法上是过得去的,但是情理上是过不去的,因为常量就是要被其他类访问的,因此我们不建议定义为私有的,一般情况下都定义为public的。常量是很安全的,是不可能被改变的。
- 被final修饰的成员变量什么时候被赋值?
- 定义的时候赋值
- 也可以在构造方法中赋值
- 它到底是怎样被赋值的呢?它其实是在构造方法中被赋值。如果定义的时候给了一个初始值(不是默认值),那么会在构造方法中隐式的执行这句话。
- final int id = 10;可以被认为是这样的
- final int id;构造方法() {this.id = 10}
- 成员变量中被final修饰的成员变量,在有参构造中赋初始值的时候,无参构造如果存在,且未给变量赋值,编译出错。如果有无参构造且在无参构造中赋初始值,则编译通过。如果没有无参,当然是可以的。
- final修饰的成员变量在有参构造中赋值,也只能赋值一次,这代表着构造出来的对象中被final修饰的变量是不能再次被改变的,只能赋值一次。每个对象的这个被final修饰的成员属性的值永远不能变了。好比一个人的身份证号,一旦产生就不会改变。这样的成员属性,就要加上final。每个人都有一个身份证号,只不过身份证号是不能改变的。身份证号是 一个人的成员属性,唯一且不能被改变。
public class FinalTest
{
public static void main(String[] args) {
//Dog d = new Dog();
//d.id = 20;
// 错误: 无法为最终变量id分配值
//System.out.println(d.id);
Animal dog = new Dog();
// 编译时,编译器会看等号右边的引用类型,
// 发现是Animal,那么它会去Animal中绑定Animal中所有的方法。
// 编译器编译到dog.eat()的时候,会在Animal类中查找是否有该方法,
// 如果有就编译通过,
// 如果没有会向上查找,去Animal的父类Object类中找,如果找不到,编译报错
// 这就是静态绑定
dog.eat();
// 运行时,JVM执行到dog.eat()的时候,
// 会首先在子类Dog中查找是否有该方法,如果有,调用该方法。
// 如果没有,调用父类中的该方法,如果父类中也没有,再向上找。
// 运行时,一定找得到该方法。
// 这就是动态绑定
// 以上构成了多态。
}
}
// 错误: 无法从最终Animal进行继承
//final class Animal
//{
//}
class Animal
{
// final void eat() {};
/*
FinalTest.java:40: 错误: Dog中的eat()无法覆盖Animal中的eat()
void eat() {
^
被覆盖的方法为final
*/
// 也就是说,当final修饰成员方法的时候,该方法不能被重写。
}
class Dog extends Animal
{
final int id = 10;
void eat() {
System.out.println("小狗吃骨头");
}
/*
void test() {
final int k =10;
// 错误: 无法为最终变量k分配值
}
void test01() {
System.out.println(k);//错误: 找不到符号
// 也就是说,被final修饰的局部变量,生命周期结束在方法结束时。
// 也就是说被final修饰的局部变量,仍然是一个局部变量。
}
*/
}
2 权限修饰符
- public
- protected
- private
- 缺省的
public: 公开的
protected :受保护的
缺省的: 没有加上修饰符
private : 私有的
权限修饰符用在什么地方:
修饰类:只能是public或者是却省的,不能使用private和protected来修饰
修饰成员变量:protected修饰的成员变量,在类外是可以被访问的 (前提是在同一个包中),protected修饰的成员变量可以在子类中访问。默认的也可以在子类中被访问。
修饰成员方法
package的概念:包,文件夹、目录
在java中,我们在写代码的时候,不能将所有的代码都写在一起,这样的话后期的工作量很大。于是就有了包的概念。
同类型的代码我们放在同一个包里。不同类型的代码放在不同的包里。这样业务上就更加清晰了。
E:\java基础\day08-1-29\com\tj>java MyTest
错误: 找不到或无法加载主类 MyTest
原因: java.lang.NoClassDefFoundError: com/tj/MyTest (wrong name: MyTest)
这个因为,package的概念是指,一个包名+类名 才是一个完整的类名。
也就是说一个类的真正的完整的名字,不是class后面所带的那个标识符,而是整个包名+类名。
包名的书写:com.xxx.xxxx.xxx这种方式一般是公司的域名倒着写
www.baidu.com
com/baidu/www 包的目录结构
com.baidu.www 这是包名
完整的类名:com.baidu.www.Test;
在java中package的语法如下:package跟上全路径名,然后以";"结尾
package com.tj.pojo;
package 这个关键字一定要出现在java源文件的第一行(注释不算)
import com.tj.pojo.User;
import 关键字:导包
它的作用是可以在不同的包中引用类。
import关键字的语法:import 全路径名.类名;
import com.tj.pojo.*;
"*"代表该包下所有的类
访问控制修饰符 | 本类 | 子类 | 同包 | 任意位置 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
默认 | Y | N(Y) | Y | N |
private | Y | N | N | N |
- 今天讲的内容不多,引导你们学习的方法,就是遇到了一些关键字、语法的时候,要自己去测试,变着法儿的测。
3 抽象类
-
什么是抽象类
-
类:是对对象进行抽象的结果,是一个抽象的概念,一类事物共有的属性和方法的集合。
-
抽象类:对类进行抽象的结果。抽象之上的抽象。
-
比如说:大学生是一个类,xjf是一个大学生,xjf是一个对象,我们大家都要是大学生这个类下的对象。大学生、中学生、小学生共同抽象出一个类叫做学生类。学生这个类就可以被看成是抽象类。
-
怎么定义抽象类:
- 在类定义之前,class之前加上abstract
- 抽象类无法创建对象;
- 抽象类有构造方法吗?有
- 构造方法有什么用呢?首先要回答抽象类有什么用?抽象类可以被继承。那么它的构造方法就是为了给子类对象构造父类型的特征。
- 一个类没有办法创建对象,那么这个类只能有一个作用了,这个作用就是只能被继承,只能当父类啊!!!
- 抽象类是无法实例化的。抽象类中,就有抽象的方法。抽象的方法就是没有方法体。如果有方法体就是具体的方法了。
- 抽象方法的定义:[访问权限修饰符] abstract [返回值类型] 方法名();
- private与abstract是无法结合使用的。抽象出来的方法一定要在其他地方具体化(具体化是要有对象的,对象只能在子类中存在。这是对象相关的方法。),如果把这个抽象的东西变成了私有的,请问如何私有化?所以不能这么用。
- abstract不能与private、static、final等组合使用。final修饰的方法是最终的方法,是不能被继承的方法。抽象方法就是用来被子类继承且被重写。其实抽象方法是完完全全要交给子类去定义的。因为抽象方法没有方法体,只有方法头(返回值类型 方法名(参数列表);)
- abstract修饰的方法一定要子类重写。子类一定要覆盖。否则编译报错。为什么报错?猜!!!
- 1.继承的本质是为了多态服务的
- 2.如果子类不去重写抽象父类中的抽象方法,多态是怎样的?动态绑定的时候就要去执行父类中的方法,父类中的方法没有方法体,怎么执行呢?静态绑定的时候,它会在父类中找可以执行的方法,发现没有方法体啊!报错。
- 抽象方法不能有方法体。也就是说方法名()后面不能带有{}
- 抽象方法一定要被子类重写。。。。
-
不是抽象类中能否有抽象方法?不能。抽象方法只能存在于抽象类中。
-
一个抽象类中,可以没有抽象方法。有抽象的方法类一定要被定义为抽象类。
-
-
总结:
- 抽象类无法实例化对象。
- 抽象类中的抽象方法一定要被子类重写。
- 抽象方法和空方法的区别,抽象方法没有方法体{};空方法是有{}只不过{}中没有内容,也不被abstract修饰。
4 接口(interface)
- 什么是接口?
- 接口就是方法抽象的集合。抽象类是类的抽象,类是对象的抽象。对象包含属性和方法。
- 接口就是对方法的抽象
- 语法:
- interface 接口名(){}
- 接口也是一个java文件,编译后生成的也是.class文件
- 接口中的方法写成:返回值类型 方法名();
- 如果在接口中的方法前加上abstract修饰,也没问题。因为本身接口中的方法都是抽象的。
- 接口抽象方法不能带有主体。也就是收接口中的方法本身就是抽象的,所以不能带主体。
- 接口中的方法可以用哪些访问权限修饰符来修饰?只能用public或者是默认的。其实默认的在这里可以被理解问public的。因为它都是公开的,没必要写。
- 接口中还可以定义其他的属性吗?可以,但必须要给属性赋值。什么情况下必须要给属性赋值啊?????接口中没有构造方法,成员属性一定要赋值,怎么办?static。static根本就不需要构造方法,它是在类加载的时候赋值的。必须要赋值,又是static的,请问这是什么东西?常量。
- 所以:接口中只能定义常量。不能定义其他成员变量。常量前面修饰可以省略,类型不能省。final static是可以省略的。
- 接口怎么用
- 普通的类无法继承接口:因为接口没有构造方法,所以不能成为父类,子类无法通过父类的构造器构造父类的特征。
- 接口之间是可以继承的,而且可以多继承。多继承之后,还不能重写父接口中的方法。假如要重写,就不再是接口了,而是子类了。
- 接口怎么用呢?implements
- class ABC implements A:类ABC实现接口A
- 既然是实现,实现什么?只能实现其中的方法吧。因为接口中的方法都是没有实现的,只是纯抽象的方法。必须要有类去实现它,才有意义。
- 实现类实现接口的时候,一定要加上public访问权限修饰符
- 类ABC一但实现接口A,就必须要实现A中所有的抽象方法。否则编译报错。
- 接口和抽象类,都是引用类型。也就是说:abstract 还是 interface限定的,他们都是不能实例化对象的,但是他们都是引用类型。
public interface A
{
static int i = 10; // 编译通过,这只有一个解释,省略了final
//final static String COUNTRY = "CHINA";// 这是常量的定义。
String COUNTRY = "CHINA";// 这里省略了final static关键字。只有接口中可以这么做。
void a1();
public abstract void a2();
}
interface B
{
}
interface C extends A,B
{
void a1();
}
class ABC implements A,B,C
{
public void a1() {}
public void a2() {
System.out.println("ABC------");
}
}
class Test
{
public static void main(String[] args){
A a = new ABC();// 父类型引用指向子类型对象。静态绑定是不是通过了?
// 我们以前说过,多态的发生依赖于继承和重写。有了继承才有了父类型引用指向子类型对象
// 有了重写,才让动态绑定发生。才有了多态,编译时一种形态,运行时另一种形态
// 我们再看继承:extends,意为继承吗????扩展!!!
// 什么扩展,以前很窄咯,现在要扩宽咯。以前的功能不够强大咯,现在要变得更强大咯。
// 继承的本质是不是就是扩展啊!!子类要拥有父类所有的东西,又要比父类强大。
// 这才是我说继承时候,说过的“青出于蓝而胜于蓝”的真正含义。
// 父类中的方法,不够强大,不能满足业务要求,子类就有权力去重写它。
// 接口中的方法就很强大吗?接口中定义的都是抽象方法,说白了就是一个名字而已,要啥没啥。
// 接口中的方法就是一个简单的规定啊!它没有太大的作用。它真正的意义就是告诉实现类
// 你要按照我接口中的方法的标准去写。这样使得大家都遵守接口的规定。
// 接口就是一种规范。具体的实现交给实现类。实现类对接口中方法的实现就是扩展。
// 所以,我们也将实现类实现接口中的方法看成是某种意义上的继承。
// SUN公司java开发人员把多态的机制也引入到了接口中。
// 所以接口也是引用类型,我们也可以将它用于多态
// 实现类可以多实现接口,也就是说一个实现类可以实现多个接口
// 接口的总结:它是一种规范,它可以用于多态,它是引用类型,它的引用可以指向实现类的对象。
a.a2();
// 以上两句话是什么?多态!!!!!!
// 多态才是面向对象的真正的核心。
}
}