第六章 面向对象的基本特征
面向对象的基本特征:
1、封装
2、继承
3、多态
6.1 封装
1、好处:
(1)隐藏实现细节,方便使用者使用
(2)安全,可以控制可见范围
2、如何实现封装?
通过权限修饰符
面试题:请按照可见范围从小到大(从大到小)列出权限修饰符?
修饰符 | 本类 | 本包 | 其他包的子类 | 任意位置 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
权限修饰符可以修饰什么?
类(类、接口等)、属性、方法、构造器、内部类
类(外部类):public和缺省
属性:4种
方法:4种
构造器:4种
内部类:4种
3、通常属性的封装是什么样的?
当然属性的权限修饰符可以是private、缺省、protected、public。但是我们大多数时候,见到的都是private,然后给它们配上get/set方法。
示例代码:标准Javabean的写法
public class Student{
//属性私有化
private String name;
private int age;
private boolean marry;
//公共的get/set
public void setName(String n){
name = n;//这里因为还没有学习this等,可能还会优化
}
public String getName(){
return name;
}
public void setAge(int a){
age = a;
}
public int getAge(){
return age;
}
public void setMarry(boolean m){
marry = m;
}
public boolean isMarry(){//boolean类型的属性的get方法,习惯使用把get换成is
return marry;
}
}
6.2 构造器
1、构造器的作用:
(1)和new一起使用创建对象
//调用无参构造创建对象
类名 对象名 = new 类名();
//调用有参构造创建对象
类名 对象名 = new 类名(实参列表);
(2)可以在创建对象的同时为属性赋值
public class Circle{
private double radius;
public Circle(){
}
public Circle(double r){
radius = r;//为radius赋值
}
}
2、声明构造器的语法格式:
【修饰符】 class 类名{
【修饰符】 类名(){//无参构造
}
【修饰符】 类名(形参列表){//有参构造
}
}
3、构造器的特点:
(1)所有的类都有构造器
(2)如果一个类没有显式/明确的声明一个构造器,那么编译器将会自动添加一个默认的无参构造
(3)如果一个类显式/明确的声明了构造器,那么编译器将不再自动添加默认的无参构造,如果需要,那么就需要手动添加
(4)构造器的名称必须与类名相同
(5)构造器没有返回值类型
(6)构造器可以重载
示例代码:
public class Circle{
private double radius;
public Circle(){
}
public Circle(double r){
radius = r;//为radius赋值
}
}
6.3 关键字this
1、this关键字:
意思:当前对象
(1)如果出现在构造器中:表示正在创建的对象
(2)如果出现在成员方法中:表示正在调用这个方法的对象
2、this的用法:
(1)this.属性
当局部变量与成员变量同名时,那么可以在成员变量的而前面加“this.”用于区别
(2)this.方法
调用当前对象的成员方法,完全可以省略“this.”
(3)this()或this(实参列表)
this()表示调用本类的无参构造
this(实参列表)表示调用本类的有参构造
this()或this(实参列表)要么没有,要么必须出现在构造器的首行
示例代码:
public class Student{
private String name;
private int score;
public Student(){
}
public Student(String name){
this.name = name;
}
public Student(String name, int score){
this(name);
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setScore(int score){
this.score = score;
}
public int getScore(){
return score;
}
}
3、成员变量与局部变量的区别?
这里只讨论实例变量(关于类变量见static部分)
(1)声明的位置不同
成员变量:类中方法外
局部变量:方法中或代码中
①方法的形参列表
②方法体中局部变量
③代码块中的局部变量
(2)运行时在内存中的存储位置不同
成员变量:堆
局部变量:栈
基本数据类型的变量在栈中,引用数据类型的变量在堆中:不准确
(3)修饰符
成员变量:有很多修饰符,例如:权限修饰符
局部变量:不能加权限修饰符,唯一的能加的是final
(4)初始化
成员变量:有默认值
局部变量:没有默认值,必须手动初始化
(5)生命周期
成员变量:随着对象的创建而创建,随着对象被回收而消亡,即与对象同生共死。每一个对象都是独立的。
局部变量:方法调用时才分配,方法运行结束就没有了。每一次方法调用,都是独立的
6.4 包
1、包的作用:
(1)可以避免类重名
有了包之后,类的全名称就变为:包.类名
(2)分类组织管理众多的类
例如:java.lang包,java.util包,java.io包…
(3)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用
2、声明包的语法格式:
package 包名;
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个
3、包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置
例如:com.atguigu.xxx;
建议大家取包名时不要使用“java.xx"包
4、使用其他包的类:
前提:被使用的类或成员的权限修饰符是>缺省的
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
5、import语句
import 包.类名;
import 包.*;
注意:当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。
一个使用全名称,一个使用简名称
示例代码:
package com.atguigu.test;
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
}
}
6.5 eclipse的使用
1、eclipse管理项目和代码的结构
workspace --> project --> 包–>类…
一个工作空间可以有多个项目。
2、快捷键
常规快捷键:
Ctrl + S:保存
Ctrl + C:复制
Ctrl + V:粘贴
Ctrl + X:剪切
Ctrl + Y:反撤销
Ctrl + Z:撤销
Ctrl + A:全选
eclipse中默认的快捷键:
Ctrl + 1:快速修复
Alt + /:代码提示
Alt + ?: Alt + Shift + / 方法的形参列表提示
Ctrl + D:删除选中行
Ctrl + Alt + ↓:向下复制行
Ctrl + Alt + ↑:向上复制行
Alt + ↓:与下面的行交换位置
Alt + ↑:与下面的行交换位置
Ctrl + Shift + F:快速格式
Ctrl + /:单行注释,再按一次取消
Ctrl + Shift + /:多行注释
Ctrl + Shift +\:取消多行注释
Shift + 回车:在光标下一行插入新航开始编辑
Ctrl + Shift + 回车:在光标上一行插入新航开始编辑
Alt + Shift + A:多行编辑 再按一次退出多行编辑模式
Alt + Shift + S:弹出自动生成代码的菜单选择,包括自动生成构造器、get/set、equals…
Ctrl + Shift + O:快速导包
Ctrl + Shift + T:打开某个类的源文件
Ctrl + O:打开某个类型的摘要outline
3、快速开发的代码模板
代码模板 + Alt + /
(1)main
public static void main(String[] args){
}
(2)sysout
System.out.println();
(3)for
for(int i=0; i<数组名.lenght; i++){
}
其他详细使用见《JavaSE_柴林燕_相关工具.docx》
6.6 面向对象的基本特征之二:继承
1、为什么要继承?继承的好处?(理解)
(1)代码的复用
(2)代码的扩展
2、如何实现继承?
语法格式:
【修饰符】 class 子类 extends 父类{
}
3、继承的特点
(1)子类会继承父类的所有特征(属性、方法)
但是,私有的在子类中是不能直接使用的
(2)子类不会继承父类的构造器
因为,父类的构造器是用于创建父类的对象的
(3)子类的构造器中又必须去调用父类的构造器
在创建子类对象的同时,为从父类继承的属性进行初始化用,可以借助父类的构造器中的代码为属性赋值。
(4)Java只支持单继承:一个子类只能有一个“直接”父类
(5)Java又支持多层继承:父类还可以有父类,特征会代代相传
(6)一个父类可以同时拥有很多个子类
6.7 关键字super
super关键字:引用父类的,找父类的xx
用法:
(1)super.属性
当子类声明了和父类同名的成员变量时,那么如果要表示某个成员变量是父类的,那么可以加“super.”
(2)super.方法
当子类重写了父类的方法,又需要在子类中调用父类被重写的方法,可以使用"super."
(3)super()或super(实参列表)
super():表示调用父类的无参构造
super(实参列表):表示调用父类的有参构造
注意:
(1)如果要写super()或super(实参列表),必须写在子类构造器的首行
(2)如果子类的构造器中没有写:super()或super(实参列表),那么默认会有 super()
(3)如果父类没有无参构造,那么在子类的构造器的首行“必须”写super(实参列表)
6.8 方法的重写
1、方法的重写(Override)
当子类继承了父类的方法时,又觉得父类的方法体的实现不适合于子类,那么子类可以选择进行重写。
2、方法的重写的要求
(1)方法名:必须相同
(2)形参列表:必须相同
(3)修饰符
权限修饰符: >=
(4)返回值类型
如果是基本数据类型和void:必须相同
如果是引用数据类型:<=
在Java中我们认为,在概念范围上:子类 <父类
3、重载(Overload)与重写(Override)的区别
重载(Overload):在同一个类中,方法名相同,形参列表不同,和返回值类型无关的两个或多个方法。
重写(Override):在父子类之间。对方法签名的要求见上面。
特殊的重载:
public class TestOverload {
public static void main(String[] args) {
B b = new B();
//b对象可以调用几个a方法
b.a();
b.a("");//从b对象同时拥有两个方法名相同,形参不同的角度来说,算是重载
}
}
class A{
public void a(){
//...
}
}
class B extends A{
public void a(String str){
}
}
6.9 非静态代码块
1、语法格式
【修饰符】 class 类名{
{
非静态代码块
}
}
2、作用
目的:在创建的过程中,为对象属性赋值,协助完成实例初始化的过程
3、什么时候执行?
(1)每次创建对象时都会执行
(2)优先于构造器执行
6.10 实例初始化过程
1、概念描述
-
实例初始化过程:实例对象创建的过程
-
实例初始化方法:实例对象创建时要执行的方法
-
实例初始化方法的由来:它是有编译器编译生成的
-
实例初始化方法的形式:()或(形参列表)
-
实例初始化方法的构成:
①属性的显式赋值代码
②非静态代码块的代码
③构造器的代码
其中
①和②按顺序执行,从上往下
③在①和②的后面
因此一个类有几个构造器,就有几个实例初始化方法。
2、单个类实例初始化方法
示例代码:
class Demo{
{
System.out.println("非静态代码块1");
}
private String str = assign();//调用方法,来为str进行显式赋值
public Demo(){
System.out.println("无参构造");
}
public Demo(String str){
this.str = str;
System.out.println("有参构造");
}
{
System.out.println("非静态代码块2");
}
public String assign(){
System.out.println("assign方法");
return "hello";
}
}
图解:
3、父子类的实例初始化
注意:
(1)原先super()和super(实参列表)说是调用父类的构造器,现在就要纠正为调用父类的实例初始化方法了
(2)原先super()和super(实参列表)说是必须在子类构造器的首行,现在要纠正为必须在子类实例初始化方法的首行
结论:
(1)执行顺序是先父类实例初始化方法,再子类实例初始化方法
(2)如果子类重写了方法,通过子类对象调用,一定是执行重写过的方法
示例代码:
class Ba{
private String str = assign();
{
System.out.println("(1)父类的非静态代码块");
}
public Ba(){
System.out.println("(2)父类的无参构造");
}
public String assign(){
System.out.println("(3)父类的assign()");
return "ba";
}
}
class Er extends Ba{
private String str = assign();
{
System.out.println("(4)子类的非静态代码块");
}
public Er(){
//super() ==>调用父类的实例初始化方法,而且它在子类实例初始化方法的首行
System.out.println("(5)子类的无参构造");
}
public String assign(){
System.out.println("(6)子类的assign()");
return "er";
}
}
class Test{
public static void main(String[] args){
new Er();//612645
}
}
图解:
6.11 面向对象的基本特征之三:多态
1、多态:
语法格式:
父类 引用/变量 = 子类的对象;
2、前提:
(1)继承
(2)方法的重写
(3)多态引用
3、现象:
编译时看左边/“父类”,运行时看右边/“子类”。
编译时,因为按父类编译,那么只能父类有的方法,子类扩展的方法是无法调用的;
执行时一定是运行子类重写的过的方法体。
示例代码:
class Person{
public void eat(){
System.out.println("吃饭");
}
public void walk(){
System.out.println("走路");
}
}
class Woman extends Person{
public void eat(){
System.out.println("细嚼慢咽的吃饭");
}
public void walk(){
System.out.println("婀娜多姿走路");
}
public void shop(){
System.out.println("买买买...");
}
}
class Man extends Person{
public void eat(){
System.out.println("狼吞虎咽的吃饭");
}
public void walk(){
System.out.println("大摇大摆的走路");
}
public void smoke(){
System.out.println("吞云吐雾");
}
}
class Test{
public static void main(String[] args){
Person p = new Woman();//多态引用
p.eat();//执行子类重写
p.walk();//执行子类重写
//p.shop();//无法调用
}
}
4、应用:
(1)多态参数:形参是父类,实参是子类对象
(2)多态数组:数组元素类型是父类,元素存储的是子类对象
示例代码:多态参数
class Test{
public static void main(String[] args){
test(new Woman());//实参是子类对象
test(new Man());//实参是子类对象
}
public static void test(Person p){//形参是父类类型
p.eat();
p.walk();
}
}
示例代码:多态数组
class Test{
public static void main(String[] args){
Person[] arr = new Person[2];//多态数组
arr[0] = new Woman();
arr[1] = new Man();
for(int i=0; i<arr.length; i++){
all[i].eat();
all[i].walk();
}
}
}
5、向上转型与向下转型:父子类之间的转换
(1)向上转型:自动类型转换
当把子类的对象赋值给父类的变量时(即多态引用时),在编译时,这个对象就向上转型为父类。此时就看不见子类“特有、扩展”的方法。
(2)向下转型:强制转换。有风险,可能会报ClassCastException异常。
当需要把父类的变量赋值给一个子类的变量时,就需要向下转型。
要想转型成功,必须保证该变量中保存的对象的运行时类型是<=强转的类型
示例代码:
class Person{
//方法代码省略...
}
class Woman extends Person{
//方法代码省略...
}
class ChineseWoman extends Woman{
//方法代码省略...
}
public class Test{
public static void main(String[] args){
//向上转型
Person p1 = new Woman();
//向下转型
Woman m = (Woman)p1;
//p1变量中实际存储的对象就是Woman类型,和强转的Woman类型一样
//向上转型
Person p2 = new ChineseWoman();
//向下转型
Woman w2 = (Woman) p2;
//p2变量中实际存储的对象是ChineseWoman类型,强制的类型是Woman,ChineseWoman<Woman类型
}
}
6、instanceof
表达式语法格式:
对象/变量 instanceof 类型
运算结果:true 或 false
作用:
用来判断这个对象是否属于这个类型,或者说,是否是这个类型的对象或这个类型子类的对象
示例代码:
class Person{
//方法代码省略...
}
class Woman extends Person{
//方法代码省略...
}
class ChineseWoman extends Woman{
//方法代码省略...
}
public class Test{
public static void main(String[] args){
Person p = new Person();
Woman w = new Woman();
ChineseWoman c = new ChineseWoman();
if(p instanceof Woman){//false
}
if(w instanceof Woman){//true
}
if(c instanceof Woman){//true
}
}
}
第七章 面向对象的高级特性
修饰符的学习围绕三个问题:
(1)单词的意思
(2)可以修饰什么?
(3)用它修饰后有什么不同?
7.1 关键字:final
final:最终的
用法:
(1)修饰类(包括外部类、内部类类)
表示这个类不能被继承,没有子类
(2)修饰方法
表示这个方法不能被重写
(3)修饰变量(成员变量(类变量、实例变量),局部变量)
表示这个变量的值不能被修改
注意:如果某个成员变量用final修饰后,也得手动赋值,而且这个值一旦赋完,就不能修改了,即没有set方法
7.2 关键字:native
native:本地的,原生的
用法:
只能修饰方法
表示这个方法的方法体代码不是用Java语言实现的。
但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
JVM内存的管理:
方法区:类的信息、常量、静态变量、动态编译生成的字节码信息
虚拟机栈:Java语言实现的方法的局部变量
本地方法栈:非Java语言实现的方法的局部变量,即native方法执行时的内存区域
堆:new出来的对象
程序计数器:记录每一个线程目前执行到哪一句指令
7.3 关键字:static
static:静态的
用法:
1、成员方法:我们一般称为静态方法或类方法
(1)不能被重写
(2)被使用
本类中:其他方法中可以直接使用它
其他类中:可以使用“类名.方法"进行调用,也可以使用"对象名.方法",推荐使用“类名.方法"
(3)在静态方法中,我们不能出现:this,super,非静态的成员
2、成员变量:我们一般称为静态变量或类变量
(1)静态变量的值是该类所有对象共享的
(2)静态变量存储在方法区
(3)静态变量对应的get/set也是静态的
(4)静态变量与局部变量同名时,就可以使用“类名.静态变量"进行区分
3、内部类:后面讲
4、代码块:静态代码块
5、静态导入(JDK1.5引入)
没有静态导入
package com.atguigu.utils;
public class Utils{
public static final int MAX_VALUE = 1000;
public static void test(){
//...
}
}
package com.atguigu.test;
import com.atguigu.utils;
public class Test{
public static void main(String[] args){
System.out.println(Utils.MAX_VALUE);
Utils.test();
}
}
使用静态导入
package com.atguigu.utils;
public class Utils{
public static final int MAX_VALUE = 1000;
public static void test(){
//...
}
}
package com.atguigu.test;
import static com.atguigu.utils.Utils.*;
public class Test{
public static void main(String[] args){
System.out.println(MAX_VALUE);
test();
}
}
7.4 静态代码块
1、语法格式:
【修饰符】 class 类名{
static{
静态代码块;
}
}
2、作用:
协助完成类初始化,可以为类变量赋值。
3、类初始化()
类的初始化有:
①静态变量的显式赋值代码
②静态代码块中代码
其中①和②按顺序执行
注意:类初始化方法,一个类只有一个
4、类的初始化的执行特点:
(1)每一个类的()只执行一次
(2)如果一个子类在初始化时,发现父类也没有初始化,会先初始化父类
(3)如果既要类初始化又要实例化初始化,那么一定是先完成类初始化的
7.5 变量的分类与区别
1、变量按照数据类型分:
(1)基本数据类型的变量,里面存储数据值
(2)引用数据类型的变量,里面存储对象的地址值
int a = 10;//a中存储的是数据值
Student stu = new Student();//stu存储的是对象的地址值
int[] arr = new int[5];//arr存储的是数组对象的地址值
String str = "hello";//str存储的是"hello"对象的地址值
2、变量按照声明的位置不同:
(1)成员变量
(2)局部变量
3、成员变量与局部变量的区别
(1)声明的位置不同
成员变量:类中方法外
局部变量:(1)方法的()中,即形参(2)方法体的{}的局部变量(3)代码块{}中
(2)存储的位置不同
成员变量:
如果是静态变量(类变量),在方法区中
如果是非静态的变量(实例变量),在堆中
局部变量:栈
(3)修饰符不同
成员变量:4种权限修饰符、static、final。。。。
局部变量:只有final
(4)生命周期
成员变量:
如果是静态变量(类变量),和类相同
如果是非静态的变量(实例变量),和所属的对象相同,每一个对象是独立
局部变量:每次执行都是新的
(5)作用域
成员变量:
如果是静态变量(类变量),在本类中随便用,在其他类中使用“类名.静态变量"
如果是非静态的变量(实例变量),在本类中只能在非静态成员中使用,在其他类中使用“对象名.非静态的变量"
局部变量:有作用域
7.7 根父类
1、java.lang.Object类是类层次结构的根父类。包括数组对象。
(1)Object类中声明的所有的方法都会被继承到子类中,那么即所有的对象,都拥有Object类中的方法
(2)每一个对象的创建,最终都会调用到Object实例初始化方法()
(3)Object类型变量、形参、数组,可以存储任意类型的对象
2、Object类的常用方法
(1)public String toString():
①默认情况下,返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
(2)public final Class<?> getClass():获取对象的运行时类型
(3)protected void finalize():当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法。而且这个方法只会被调用一次。子类可以选择重写。
(4)public int hashCode():返回每个对象的hash值。
规定:①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
主要用于后面当对象存储到哈希表等容中时,为了提高性能用的。
(5)public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:
a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
7.8 关键字:abstract
1、什么时候会用到抽象方法和抽象类?
当声明父类的时候,在父类中某些方法的方法体的实现不能确定,只能由子类决定。但是父类中又要体现子类的共同的特征,即它要包含这个方法,为了统一管理各种子类的对象,即为了多态的应用。
那么此时,就可以选择把这样的方法声明为抽象方法。如果一个类包含了抽象方法,那么这个类就必须是个抽象类。
2、抽象类的语法格式
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
3、抽象方法的语法格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
抽象方法没有方法体
4、抽象类的特点
(1)抽象类不能直接实例化,即不能直接new对象
(2)抽象类就是用来被继承的,那么子类继承了抽象类后,必须重写所有的抽象方法,否则这个子类也得是抽象类
(3)抽象类也有构造器,这个构造的作用不是创建抽象类自己的对象用的,给子类在实例化过程中调用;
(4)抽象类也可以没有抽象方法,那么目的是不让你创建对象,让你创建它子类的对象
(5)抽象类的变量与它子类的对象也构成多态引用
5、不能和abstract一起使用的修饰符?
(1)final:和final不能一起修饰方法和类
(2)static:和static不能一起修饰方法
(3)native:和native不能一起修饰方法
(4)private:和private不能一起修饰方法
7.9 接口
1、接口的概念
接口是一种标准。注意关注行为标准(即方法)。
面向对象的开发原则中有一条:面向接口编程。
2、接口的声明格式
【修饰符】 interface 接口名{
接口的成员列表;
}
3、类实现接口的格式
【修饰符】 class 实现类 implements 父接口们{
}
【修饰符】 class 实现类 extends 父类 implements 父接口们{
}
4、接口继承接口的格式
【修饰符】 interface 接口名 extends 父接口们{
接口的成员列表;
}
5、接口的特点
(1)接口不能直接实例化,即不能直接new对象
(2)只能创建接的实现类对象,那么接口与它的实现类对象之间可以构成多态引用。
(3)实现类在实现接口时,必须重写所有抽象的方法,否则这个实现类也得是抽象类。
(4)Java规定类与类之间,只能是单继承,但是Java的类与接口之间是多实现的关系,即一个类可以同时实现多个接口
(5)Java还支持接口与接口之间的多继承。
6、接口的成员
JDK1.8之前:
(1)全局的静态的常量:public static final,这些修饰符可以省略
(2)公共的抽象方法:public abstract,这些修饰符也可以省略
JDK1.8之后:
(3)公共的静态的方法:public static ,这个就不能省略了
(4)公共的默认的方法:public default,这个就不能省略了
7、默认方法冲突问题
(1) 当一个实现类同时实现了两个或多个接口,这个多个接口的默认方法的签名相同。
解决方案:
方案一:选择保留其中一个
接口名.super.方法名(【实参列表】);
方案二:完全重写
(2)当一个实现类同时继承父类,又实现接口,父类中有一个方法与接口的默认方法签名相同
解决方案:
方案一:默认方案,保留父类的
方案二:选择保留接口的
接口名.super.方法名(【实参列表】);
方案三:完全重写
8、示例代码
public interface Flyable{
long MAX_SPEED = 7900000;
void fly();
}
public class Bird implements Flyable{
public void fly(){
//....
}
}
9、常用的接口
(1)java.lang.Comparable接口:自然排序
抽象方法:int compareTo(Object obj)
(2)java.util.Comparator接口:定制排序
抽象方法:int compare(Object obj1 ,Object obj2)
(3)示例代码
如果员工类型,默认顺序,自然顺序是按照编号升序排列,那么就实现Comparable接口
class Employee implements Comparable{
private int id;
private String name;
private double salary;
//省略了构造器,get/set,toString
@Override
public int compareTo(Object obj){
return id - ((Employee)obj).id;
}
}
如果在后面又发现有新的需求,想要按照薪资排序,那么只能选择用定制排序,实现Comparator接口
class SalaryComparator implements Comparator{
public int compare(Object o1, Object o2){
Employee e1 = (Employee)o1;
Employee e2 = (Employee)o2;
if(e1.getSalary() > e2.getSalary()){
return 1;
}else if(e1.getSalary() < e2.getSalary()){
return -1;
}
return 0;
}
}
7.10 内部类
1、内部类的概念
声明在另外一个类里面的类就是内部类。
2、内部类的4种形式
(1)静态内部类
(2)非静态成员内部类
(3)有名字的局部内部类
(4)匿名内部类
7.10.1 匿名内部类
1、语法格式:
//在匿名子类中调用父类的无参构造
new 父类(){
内部类的成员列表
}
//在匿名子类中调用父类的有参构造
new 父类(实参列表){
内部类的成员列表
}
//接口没有构造器,那么这里表示匿名子类调用自己的无参构造,调用默认父类Object的无参构造
new 父接口名(){
}
2、匿名内部类、匿名对象的区别?
System.out.println(new Student("张三"));//匿名对象
Student stu = new Student("张三");//这个对象有名字,stu
//既有匿名内部类,又是一个匿名的对象
new Object(){
public void test(){
.....
}
}.test();
//这个匿名内部类的对象,使用obj这个名字引用它,既对象有名字,但是这个Object的子类没有名字
Object obj = new Object(){
public void test(){
.....
}
};
3、使用的形式
(1)示例代码:继承式
abstract class Father{
public abstract void test();
}
class Test{
public static void main(String[] args){
//用父类与匿名内部类的对象构成多态引用
Father f = new Father(){
public void test(){
System.out.println("用匿名内部类继承了Father这个抽象类,重写了test抽象方法")
}
};
f.test();
}
}
(2)示例代码:实现式
interface Flyable{
void fly();
}
class Test{
public static void main(String[] args){
//用父接口与匿名内部类的对象构成了多态引用
Flyable f = new Flyable(){
public void fly(){
System.out.println("用匿名内部类实现了Flyable这个接口,重写了抽象方法");
}
};
f.fly();
}
}
(3)示例代码:用匿名内部类的匿名对象直接调用方法
new Object(){
public void test(){
System.out.println("用匿名内部类的匿名对象直接调用方法")
}
}.test();
(4)示例代码:用匿名内部类的匿名对象直接作为实参
Student[] all = new Student[3];
all[0] = new Student("张三",23);
all[1] = new Student("李四",22);
all[2] = new Student("王五",20);
//用匿名内部类的匿名对象直接作为实参
//这个匿名内部类实现了Comparator接口
//这个匿名内部类的对象,是定制比较器的对象
Arrays.sort(all, new Comparator(){
public int compare(Obeject o1, Object o2){
Student s1 = (Student)o1;
Student s2 = (Student)o2;
return s1.getAge() - s2.getAge();
}
});
7.10.2 静态内部类
1、语法格式
【修饰符】 class 外部类名 【extends 外部类的父类】 【implements 外部类的父接口们】{
【其他修饰符】 static class 静态内部类 【extends 静态内部类自己的父类】 【implements 静态内部类的父接口们】{
静态内部类的成员列表;
}
外部类的其他成员列表;
}
2、 使用注意事项
(1)包含成员是否有要求:
可以包含类的所有成员
(2)修饰符要求:
- 权限修饰符:4种
- 其他修饰符:abstract、final
(3)使用外部类的成员上是否有要求
- 只能使用外部类的静态成员
(4)在外部类中使用静态内部类是否有要求
- 正常使用
(5)在外部类的外面使用静态内部类是否有要求
(1)如果使用的是静态内部类的静态成员
外部类名.静态内部类名.静态成员
(2)如果使用的是静态内部类的非静态成员
①先创建静态内部类的对象
外部类名.静态内部类名 对象名 = new 外部类名.静态内部类名(【实参列表】);
②通过对象调用非静态成员
对象名.xxx
(6)字节码文件形式:外部类名$静态内部类名.class
3、示例代码
class Outer{
private static int i = 10;
static class Inner{
public void method(){
//...
System.out.println(i);//可以
}
public static void test(){
//...
System.out.println(i);//可以
}
}
public void outMethod(){
Inner in = new Inner();
in.method();
}
public static void outTest(){
Inner in = new Inner();
in.method();
}
}
class Test{
public static void main(String[] args){
Outer.Inner.test();
Outer.Inner in = new Outer.Inner();
in.method();
}
}
7.10.3 非静态内部类
1、语法格式
【修饰符】 class 外部类名 【extends 外部类的父类】 【implements 外部类的父接口们】{
【修饰符】 class 非静态内部类 【extends 非静态内部类自己的父类】 【implements 非静态内部类的父接口们】{
非静态内部类的成员列表;
}
外部类的其他成员列表;
}
2、 使用注意事项
(1)包含成员是否有要求:
不允许出现静态的成员
(2)修饰符要求
权限修饰符:4种
其他修饰符:abstract,final
(3)使用外部类的成员上是否有要求
都可以使用
(4)在外部类中使用非静态内部类是否有要求
在外部类的静态成员中不能使用非静态内部类
(5)在外部类的外面使用非静态内部类是否有要求
//使用非静态内部类的非静态成员
//(1)创建外部类的对象
外部类名 对象名1 = new 外部类名(【实参列表】);
//(2)通过外部类的对象去创建或获取非静态内部类的对象
//创建
外部类名.非静态内部类名 对象名2 = 对象名1.new 非静态内部类名(【实参列表】);
//获取
外部类名.非静态内部类名 对象名2 = 对象名1.get非静态内部类对象的方法(【实参列表】);
//(3)通过非静态内部类调用它的非静态成员
对象名2.xxx
(6)字节码文件形式:外部类名$非静态内部类名.class
3、示例代码
class Outer{
private static int i = 10;
private int j = 20;
class Inner{
public void method(){
//...
System.out.println(i);//可以
System.out.println(j);//可以
}
}
public void outMethod(){
Inner in = new Inner();
in.method();
}
public static void outTest(){
// Inner in = new Inner();//不可以
}
public Inner getInner(){
return new Inner();
}
}
class Test{
public static void main(String[] args){
Outer out = new Outer();
Outer.Inner in1 = out.new Inner(); //创建
in1.method();
Outer.Inner in2 = out.getInner(); //获取
in2.method();
}
}
7.10.4 局部内部类
1、语法格式
【修饰符】 class 外部类名 【extends 外部类的父类】 【implements 外部类的父接口们】{
【修饰符】 返回值类型 方法名(【形参列表】){
【修饰符】 class 局部内部类 【extends 局部内部类自己的父类】 【implements 局部内部类的父接口们】{
局部内部类的成员列表;
}
}
外部类的其他成员列表;
}
2、 使用注意事项
(1)包含成员是否有要求
不允许出现静态的成员
(2)修饰符要求
权限修饰符:不能
其他修饰符:abstract、final
(3)使用外部类的成员等上是否有要求
①使用外部类的静态成员:随便用
②使用外部类的非静态成员:能不能用要看所在的方法是否是静态的
③使用所在方法的局部变量:必须 final修饰的
(4)在外部类中使用局部内部类是否有要求
有作用域
(5)在外部类的外面使用局部内部类是否有要求
没法使用
(6)字节码文件形式:外部类名$编号局部内部类名.class
3、示例代码
class Outer{
private static int i = 10;
private int j = 20;
public void outMethod(){
class Inner{
public void method(){
//...
System.out.println(i);//可以
System.out.println(j);//可以
}
}
Inner in = new Inner();
in.method();
}
public static void outTest(){
final int k = 30;
class Inner{
public void method(){
//...
System.out.println(i);//可以
System.out.println(j);//不可以
System.out.println(k);//可以
}
}
Inner in = new Inner();
in.method();
}
}