面向对象思想编程
学习面向对象内容的三条主线
- Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类
- 面向对象的特征:封装、继承、多态、(抽象)
- 其他关键字的使用:this、super、package、import、static、final、interface、abstract等
方法(对功能进行封装,实现代码的复用)
方法
是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数
或过程
。- 将功能封装为方法的目的是,可以
实现代码重用,减少冗余,简化代码
- Java里的方法
不能独立存在
,所有的方法必须定义在类里。
方法必须声明在类中 且不能嵌套使用
类{
方法1(){
}
方法2(){
}
}
定义方法:
修饰符 返回值类型
public static void 方法名([形参列表]){
//方法的内容
}
//public static 是方法的修饰符 公共的 静态的
//返回值类型:表示方法运行的结果的数据类型,方法执行后将结果返回到调用者
//方法名:给方法起一个名字,见名知意,能准确代表该方法功能的名字
//形参列表:表示完成方法体功能时需要外部提供的数据列表
//方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码
方法调用:
public class Method {
public static void main(String[] args) {
System.out.println("调用方法");
eat();
}
public static void eat(){
System.out.println("吃饭了!!!");
}
}
方法的形参和返回值
形参和实参:
方法的形参:
1.方法的声明处
2.规定方法实参的数量和类型
方法的实参:
方法调用者传递的实际的值
例:
public class MethodTest3 {
//方法的声明
public static void sum(int a,int b){//形参
System.out.println(a+b);
}
public static void main(String[] args) {
sum(10,30);//实参
}
}
方法的返回值:
有的时候需要方法执行的结果作为下次执行的条件就需要带返回值的方法
public static 返回值类型 方法名([形参列表]){
方法体的功能代码
}
返回值类型:
void:没有返回值
数据类型:必须通过 return 关键字 返回一个该类型匹配的值
- return语句的作用是结束方法的执行,并将方法的结果返回去
- 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
- 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。
- return语句后面就不能再写其他代码了,否则会报错:Unreachable code
例:
public class MethodTest4 {
//方法的声明
public static int sum(int a,int b){//形参
return a+b;
}
public static void main(String[] args) {
int result = sum(10,30);//实参
System.out.println("result = "+ result);
}
}
注意:
方法只声明不调用 不会执行"
方法与方法是兄弟关系
class 类{
方法a(){}
方法b(){}
}
方法执行完毕 回到方法调用处
方法的重载(在同一类中使用相同的方法名表示不同细节实现的方法)
新:本类或者父子类中有相同的方法名 不同的形参列表(数量 顺序 类型) 就是重载
要求: 两同一不同
两同:同一类中同一方法实现
不同:参数列表不同: 数量、类型、顺序
//找两个数的最大值
public class MethodsOverload {
public static void main(String[] args) {
System.out.println((char)Compare('a','b'));
System.out.println(Compare(10,2));
System.out.println(Compare(6.1,5.1));
}
public static int Compare(int a, int b){
return a>b?a:b;
}
public static int Compare(double a, double b){
return (char)(a>b?a:b);
}
public static int Compare(char a, char b){
return a>b?a:b;
}
}
可变参数
当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变参数
声明可变参数:
public 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){
}
可变参数的特点
(1)一个方法最多只能有一个可变参数(语法规定)
(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个
(3)在声明它的方法中,可变参数当成数组使用
(4)可变参数中实参的数量[0,n]
public class MethodVariableParams {
public static void main(String[] args) {
System.out.println(joins('-',"c","h","s"));
}
//字符于字符串拼接
public static String joins(char c,String ...s){
String temp="";
for (int i = 0; i < s.length; i++) {
if(i==s.length-1) temp+=s[i];
else temp+=s[i]+c;
}
return temp;
}
方法的值传递(画图)
基本类型数据的值:传递的是值的副本
数组作为参数:传递的是地址值
面向对象编程概述
1. 面向过程的程序设计思想(Process-Oriented Programming),简称POP
- 关注的焦点是
过程
:过程就是操作数据的步骤。如果某个过程的实现代码重复出现,那么就可以把这个过程抽取为一个函数
。这样就可以大大简化冗余代码,便于维护。 - 典型的语言:C语言
- 代码结构:以
函数
为组织单位。 - 是一种“
执行者思维
”,适合解决简单问题。扩展能力差、后期维护难度较大。
2. 面向对象的程序设计思想( Object Oriented Programming),简称OOP
- 关注的焦点是
类
:在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。 - 典型的语言:Java、C#、C++、Python、Ruby和PHP等
- 代码结构:以
类
为组织单位。每种事物都具备自己的属性
和行为/功能
。 - 是一种“
设计者思维
”,适合解决复杂问题。代码扩展性强、可维护性高。
类和对象概述
类(Class)
和对象(Object)
是面向对象的核心概念。
1、什么是类
类:具有相同特征的事物的抽象描述,是抽象的
、概念上的定义。
2、什么是对象
对象:实际存在的该类事物的每个个体,是具体的
,因而也称为实例(instance)
。
类的成员概述
- 属性:该类事物的状态信息。对应类中的
成员变量
- 成员变量 <=> 属性 <=> Field
- 行为:该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的
成员方法
- (成员)方法 <=> 函数 <=> Method
面向对象完成功能的三步骤(重要)
步骤1:类的定义
类的定义使用关键字:class。格式如下:
[修饰符] class 类名{
属性声明;
方法声明;
}
步骤2:对象的创建
- 创建对象,使用关键字:new
- 创建对象语法:
//方式1:给创建的对象命名
//把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();
//方式2:
new 类名()//也称为匿名对象
class PersonTest{
public static void main(String[] args){
//创建Person类的对象
Person per = new Person();
//创建Dog类的对象
Dog dog = new Dog();
}
}
步骤3:对象调用属性或方法
- 对象是类的一个实例,必然具备该类事物的属性和行为(即方法)。
- 使用"
对象名.属性
" 或 "对象名.方法
"的方式访问对象成员(包括属性和方法)
匿名对象 (anonymous object)
- 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
- 如:new Person().shout();
- 使用情况
- 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
对象的内存图
成员变量
声明成员变量
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
}
实例变量有默认值
分类 | 数据类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | '\u0000' | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
实例变量的访问
对象.实例变量
成员方法
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
public void 成员方法名{
方法体;
}
}
实例方法的访问
对象.实例方法()
注意:没有static的方法是实例方法:
必须通过方法所在类对象调用
实例方法内有一个隐藏变量----this
this: 谁调用我,我指向谁(代指调用方法的对象)
public class Monkey {
String name;
int age;
public void eat(){
System.out.println(this.name+"吃");
showInfo(); //this.showInfo();
}
public void showInfo(){
System.out.println(this.name+"\t"+this.age);
}
}
class MonkeyTest{
public static void main(String[] args) {
Monkey m=new Monkey();
m.name="孙悟空";
m.age=5000;
m.eat();
}
}
成员变量和方法的定义于访问
实例变量与局部变量的区别
1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈
3、生命周期
(1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。
4、作用域
(1)实例变量(可以在所有的实例方法中使用):
通过对象就可以使用,本类中“this.,没有歧义还可以省略this.”,其他类中“对象.”
(2)局部变量(只能在本方法内使用):出了作用域就不能使用
5、修饰符(后面来讲)
(1)实例变量:public,protected,private,final,volatile,transient等
(2)局部变量:final
6、默认值
(1)实例变量:有默认值
(2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
包
包的作用:
- 包可以包含类和子包,划分
项目层次
,便于管理 - 帮助
管理大型软件
系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式 - 解决
类命名冲突
的问题 - 控制
访问权限
声明包: (公司网站倒序)
例:com.chs.项目名.模块名.子模块名
注意:
1.Java文件的首行是包的声明
2.使用了不同包下的资源,需要导包
import 包名.类名;
import 包名.*; 导入当前包下的所有资源
3.java..lang包下的资源使用时不用导包
4.如果一个java文件中使用了同名资源,一个采用导包,一个采用全路径名/全类名===>com.chs.packge03.Person
封装
所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对象开放,向没必要开放的类或者对象隐藏信息。
面向对象的开发原则要遵循“高内聚、低耦合
”
高内聚
:类的内部数据操作细节自己完成,不允许外部干涉;低耦合
:仅暴露少量的方法给外部使用,尽量方便外部调用。
Java如何实现数据封装
-
实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控制。
-
权限修饰符:
public
、protected
、缺省(什么都不写)
、private
。具体访问范围如下:
修饰符 | 本类内部 | 本包内 | 其他包的子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
- 具体修饰的结构:
- 外部类:public、缺省
- 成员变量、成员方法、构造器、成员内部类:public、protected、缺省、private
封装性的体现
成员变量/属性私有化
概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。
实现步骤:
① 使用 private
修饰成员变量
private 数据类型 变量名 ;
代码如下:
public class Person {
private String name;
private int age;
private boolean marry;
}
② 提供 getXxx
方法 / setXxx
方法,可以访问成员变量,代码如下:
public class Person {
private String name;
private int age;
private boolean marry;
public void setName(String n) {
name = n;
}
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(){
return marry;
}
}
③ 测试:
public class PersonTest {
public static void main(String[] args) {
Person p = new Person();
//实例变量私有化,跨类是无法直接使用的
/* p.name = "张三";
p.age = 23;
p.marry = true;*/
p.setName("张三");
System.out.println("p.name = " + p.getName());
p.setAge(23);
System.out.println("p.age = " + p.getAge());
p.setMarry(true);
System.out.println("p.marry = " + p.isMarry());
}
}
成员变量封装的好处:
- 让使用者只能通过事先预定的方法来
访问数据
,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。 便于修改
,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
注意:
开发中,一般成员实例变量都习惯使用private修饰,再提供相应的public权限的get/set方法访问。
对于final的实例变量,不提供set()方法。(后面final关键字的时候讲)
对于static final的成员变量,习惯上使用public修饰。
构造器(Constructor)
在new对象时,直接为当前对象的某个或所有成员变量直接赋值。Java给我们提供了构造器(Constructor)
,也称为构造方法
。
构造器的语法格式
[修饰符] class 类名{
[修饰符] 构造器名(){
// 实例初始化代码
}
[修饰符] 构造器名(参数列表){
// 实例初始化代码
}
}
说明:
- 构造器名必须与它所在的类名必须相同。
- 它没有返回值,所以不需要返回值类型,也不需要void。
- 构造器的修饰符只能是权限修饰符,不能被其他任何修饰。比如,不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。
使用构造器创建对象
public class TestStudent {
public static void main(String[] args) {
//调用无参构造创建学生对象
Student s1 = new Student();
//调用有参构造创建学生对象
Student s2 = new Student("张三",23);
System.out.println(s1.getInfo());
System.out.println(s2.getInfo());
}
}
注意
当我们没有显式的声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器的修饰符默认与类的修饰符相同。
当我们显式的定义类的构造器以后,系统就不再提供默认的无参的构造器了。
在类中,至少会存在一个构造器。
构造器是可以重载的。
在别的构造器中可以使用this([参数])调用本类中的构造器 。它必须位于构造器的首行,只能有一个this()
JavaBean(domain/entity)
- JavaBean是一种Java语言写成的可重用组件。
- 所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 成员变量私有,且有对应的get、set方法
public class JavaBean {
private String name; // 属性一般定义为private
private int age;
public JavaBean() {
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
}
this:(代指当前对象,谁调用,就是谁)
this:如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量。即:我们可以用this来区分成员变量
和局部变量
,另外,使用this访问属性和方法时,如果在本类中未找到,会从父类中查找。
调用方法:
this.getNum([形参表]){};
调用属性:
this.name;
调用本类中的构造器:
this.Person([形参表]){};
继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 表示类与类之间的is-a关系
继承的语法格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
继承的特点:
1.子类会继承父类所有的实例变量和实例方法
2.Java只支持单继承,不支持多重继承
3.Java支持多层继承(继承体系)
4.一个父类可以同时拥有多个子类
5.java的根父类是Object
6.子类可以继承父类的私有方法,但是不能直接使用需要父类提供get set 方法
查看继承关系快捷键
父-->子:选择A类名,按Ctrl + H就会显示A类的继承树。
子-->父:选择A类名,右键 diagras --> show diagrams (Ctrl+ Alt+U)
权限修饰符限制
权限修饰符:public,protected,缺省,private
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √(本包子类非子类都可见) | × | × |
protected | √ | √(本包子类非子类都可见) | √(其他包仅限于子类中可见) | × |
public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法等:public,protected,缺省,private
public : 本模块下可以使用
protected :本包,以及不同包下的子类中可见
缺省: 本包下使用
private: 私有的 只能在本类中使用
方法重写(快捷键:ctrl+o)
为什么重写:
当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类时
如何重写
在子类中 写父类的方法
父类
package com.atguigu.inherited.method;
public class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
重写
package com.atguigu.inherited.method;
//smartphone:智能手机
public class Smartphone extends Phone{
//重写父类的来电显示功能的方法
public void showNum(){
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
在子类中如何调用父类被重写的方法
super.方法名() //调用父类的方法
package com.atguigu.inherited.method;
//smartphone:智能手机
public class Smartphone extends Phone{
//重写父类的来电显示功能的方法
public void showNum(){
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
//保留父类来电显示号码的功能
super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
}
}
重写方法的要求
1.必须保证父子类之间重写方法的名称和参数列表相同
2.子类方法的《权限修饰符》必须【大于等于】父类方法的权限修饰符。
注意:public > protected > 缺省 > private
父类私有方法不能重写
跨包的父类缺省的方法也不能重写
3.子类方法的《返回值类型》必须【小于等于】父类方法的返回值类型。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
4.抛出的异常 如果父类抛出的是运行时异常 子类没有限制
如果父类抛出的是编译时异常 子类不能抛出比父类更大的异常
//father
public class TestException {
public void arithmetic() throws InterruptedException {
int a=0;
Thread.sleep(1000);
}
}
//son
public class ExcptionTestSon extends TestException {
@Override
public void arithmetic() throws InterruptedException {
Thread.sleep(100);
}
}
重写toString()
方法签名:public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
全类名+@+16进制哈希码
②通常是建议重写可以更加方便展示属性值
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
this和super关键字
this和super的意义
this:当前对象
- 在构造器和非静态代码块中,表示正在new的对象
- 在实例方法中,表示调用当前方法的对象
super:引用父类声明的成员
无论是this和super都是和对象有关的。
this和super的使用格式
-
this
- this.成员变量:表示当前对象的某个成员变量,而不是局部变量
- this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
- this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
-
super
- super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
- super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
- super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
避免子类和父类声明重名的成员变量
因为,子类会继承父类所有的成员变量,所以:
-
如果重名的成员变量表示相同的意义,就无需重复声明
-
如果重名的成员变量表示不同的意义,会引起歧义
解决成员变量重名问题
- 如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
- 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量
- 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
总结:起点不同(就近原则)
-
变量前面没有super.和this.(就近原则)
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量,
- 如果不是局部变量,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
-
变量前面有this.
- 通过this找成员变量时,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
-
变量前面super.
- 通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
- 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
class Father{
int a = 10;
int b = 11;
}
class Son extends Father{
int a = 20;
public void test(){
System.out.println("子类的a:" + a); //20
System.out.println("子类的a:" + this.a); //20
System.out.println("父类的a:" + super.a); //10
System.out.println("b = " + b); //11
System.out.println("b = " + this.b);//11
System.out.println("b = " + super.b);//11
}
public void method(int a, int b){
System.out.println("局部变量的a:" + a); //30
System.out.println("子类的a:" + this.a); //20
System.out.println("父类的a:" + super.a);//10
System.out.println("b = " + b); //13
System.out.println("b = " + this.b); //11
System.out.println("b = " + super.b); //11
}
}
class Test{
public static void main(String[] args){
Son son = new Son();
son.test();
son.method(30,13);
}
}
解决成员方法重写后调用问题
- 如果子类没有重写父类的方法,只有权限修饰符运行,在子类中完全可以直接调用父类的方法;
- 如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法
注意:每个构造器的首行都会有super(), 调用父类的无参构造器。也可以自己修改 super(参数) 调用有参构造器
总结:
-
方法前面没有super.和this.
- 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
-
方法前面有this.
- 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
-
方法前面有super.
- 从当前子类的直接父类找,如果没有,继续往上追溯
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
Daughter d = new Daughter();
d.test();
}
}
class Father{
protected int num = 10;
public int getNum(){
return num;
}
}
class Son extends Father{
private int num = 20;
public void test(){
System.out.println(getNum()); //10
System.out.println(this.getNum()); //10
System.out.println(super.getNum()); //10
}
}
class Daughter extends Father{
private int num = 20;
@Override
public int getNum(){
return num;
}
public void test(){
System.out.println(getNum()); //20
System.out.println(this.getNum()); //20
System.out.println(super.getNum()); //10
}
}
多态(polymorphism)
多态是继封装、继承之后,面向对象的第三大特性。
多态是同一个行为(方法),不同事务(对象),具有多个不同表现形式或形态的能力
1、多态引用
Java规定父类类型的变量可以接收子类类型的对象,这一点从逻辑上也是说得通的。
父类类型 变量名 = 子类对象;
Animal a =new Cat();
父类类型:指子类继承的父类类型,或者实现的父接口类型。
所以说继承是多态的前提
多态的前提
1.有继承关系
2.有方法的重写
3.父类的引用指向子类的实例(对象)
Animal a =new Cat();
2、多态引用方法的表现
表现:编译时类型与运行时类型不一致
编译(写完代码)时看“父类”,运行时看“子类”:
编译:写代码时只能使用父类中声明的方法,
运行:运行时看子类如果子类重写的父类方法就执行子类方法,如果没有重写就执行从父类继承的方法
3、多态引用的好处和弊端
弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;
4、多态演示
public class Animal {
protected void eat(){
System.out.println("动物吃饭");
}
public void sleep(){
System.out.println("动物睡觉");
}
}
class Dog extends Animal{
//子类重写父类的方法
@Override
protected void eat() {
System.out.println("狗吃饭");
}
//子类扩展的方法
public void watchHome(){
System.out.println("狗看家");
}
}
public class Person {
public static void main(String[] args) {
//多态引用
Animal a =new Dog();
//多态的表现形式
/*
编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
*/
a.eat();//运行时执行子类Dog重写的方法
// a.watchHouse();//不能调用Dog子类扩展的方法
}
}
多态的应用
数组:父类类型做数组的类型,可以存储任意子类对象
Cat cat = new Cat();
Dog dog = new Dog();
Animal[] A ={cat,dog};
返回值类型:父类类型做返回值类型,可以返回任意子类对象
public class Person {
public static void main(String[] args) {
Animal animal=getAnimal(1);
if (animal != null) {
animal.eat();
}
}
public static Animal getAnimal(int i){
//1=狗,2=猫 其他=null
if(i==1)return new Dog();
else if (i==2) return new Cat();
return null;
}
}
形参:父类类型做形参,可以接收任意子类对象
public class Person {
public static void main(String[] args) {
active(d);
}
//父类类型做形参,可以接收任意子类对象
public static void active(Animal animal){
animal.eat();
}
}
多态的转型
多态的转型是指将一个父类对象引用转换为其子类对象引用,或者将一个子类对象引用转换为其父类对象引用。
- 向上转型(upcasting)是将子类对象引用转换为其父类对象引用
- 向下转型(downcasting)是将父类对象引用转换为其子类对象引用。在进行向下转型时需要进行类型检查,以确保对象实际上是指向该子类的实例。如果对象不是该子类的实例,则会抛出ClassCastException异常。
- 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是自动完成的
- 向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
如何向上转型与向下转型
向上转型:自动完成
向下转型:(子类类型)父类变量
Animal a = d;
Dog d =(Dog) a;
instanceof关键字
给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
- 变量/匿名对象的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
- 变量/匿名对象的运行时类型<= instanceof后面数据类型,才为true
变量/匿名对象 instanceof 数据类型
if (a instanceof Dog){
Dog d =(Dog) a;
d.mouth();
}
//jdk新特性
if (a instanceof Cat c){
c.mouth();
}
getClass()
public final Class<?> getClass():获取对象的运行时类型对象
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
虚方法和非虚方法:
非虚方法: 不能重写的方法
虚方法调用: 可以重写的方法
静态分派: 编译时类型匹配
动态绑定: 是否有子类重写 如果有重写 就执行重写的
静态关键字(static)
静态变量
语法格式
有static修饰的成员变量就是静态变量。
【修饰符】 class 类{
【其他修饰符】 static 数据类型 静态变量名;
}
静态变量的特点
-
静态变量的默认值规则和实例变量一样。
-
静态变量值是所有对象共享。
-
静态变量的值存储在方法区。
-
静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
-
如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。
类名.静态变量
-
静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。
静态变量内存分析
静态变量vs非静态实例变量
相同点:
1.代码位置相同
类中方法外
2.都具有默认值
基本
整型 0
浮点类型 0.0
布尔类型 false
字符型 \u0000
引用 null
不同点:
1.内存位置不同
静态成员变量:方法区中
普通成员变量:堆中
2.内存中开辟空间的分数不同
静态成员变量: 开辟一份空间
普通成员变量: 每创建一次对象 开辟一份空间
3.调用方式不同:
静态成员变量: 类名.属性名
普通成员变量: 对象名.属性名
4.生命周期不同
开始:
静态成员变量: 在类加载阶段就会开辟空间
普通成员变量: 创建对象后开辟空间
结束:
静态成员变量:在类卸载时销毁
普通成员变量:当没有对象指向堆中内存时 由GC回收
5.修饰符不同
静态成员变量: 被static 修饰
普通成员变量: 没有static 修饰
静态类变量和非静态实例变量、局部变量
- 静态类变量(简称静态变量):存储在方法区,有默认值,所有对象共享,生命周期和类相同,还可以有权限修饰符、final等其他修饰符
- 非静态实例变量(简称实例变量):存储在堆中,有默认值,每一个对象独立,生命周期每一个对象也独立,还可以有权限修饰符、final等其他修饰符
- 局部变量:存储在栈中,没有默认值,每一次方法调用都是独立的,有作用域,只能有final修饰,没有其他修饰符
静态方法
语法格式
有static修饰的成员方法就是静态方法。
【修饰符】 class 类{
【其他修饰符】 static 返回值类型 方法名(形参列表){
方法体
}
}
静态方法的特点
- 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
- 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
类名.静态方法名
- 静态方法可以被子类继承,但不能被子类重写。
- 静态方法的调用都只看编译时类型。
静态和非静态的区别
1.修饰符不同
静态方法:有static 修饰
非静态方法:没有static修饰
2.调用方式不同
静态方法:类名.方法名 本类中可以省略类名
非静态方法:对象名.方法名 本类中可以省略对象名
3.引用资源(属性+方法)不同:***
静态方法:只能【直接】使用静态资源,但可以间接调用非静态资源
非静态方法:可以直按使用所有的资源
- 静态直接访问静态,可以
- 非静态直接访问非静态,可以
- 非静态直接访问静态,可以
- 静态直接访问非静态,不可以
4,this和super的使用
静态方法:不能使用this和super
非静态方法:可以使用this和super
5.方法重写
静态方法:不能重写但可以继承
非静态方法:可以重写可以继承
package com.chs.statictest;
public class test1 {
public static void main(String[] args) {
new test1().t1();
st2(); //静态方法调用静态方法
}
public static void st2(){
System.out.println("st1");
new test1().t1(); //静态方法调用非静态方法
}
public void t1(){
st2();//非静态方法调用静态方法
System.out.println("t1");
}
}
final(修饰符)
final修饰类
表示这个类不能被继承,没有子类
final修饰方法
表示这个方法不能被子类重写
final修饰变量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
修饰局部变量:
基本数据类型: 值不可变
引用数据类型: 地址不可变
修饰成员变量: 必须有赋值操作(直接赋值或构造器赋值)
密封类(sealed)
这个预览功能用于限制超类的使用,密封的类和接口限制其他可能继承或实现它们的其他类或接口。
【修饰符】 sealed class 密封类 【extends 父类】【implements 父接口】 permits 子类{
}
【修饰符】 sealed interface 接口 【extends 父接口们】 permits 实现类{
}
- 密封类用 sealed 修饰符来描述,
- 使用 permits 关键字来指定可以继承或实现该类的类型有哪些
- 一个类继承密封类或实现密封接口,该类必须是sealed、non-sealed、final修饰的。
- sealed修饰的类或接口必须有子类或实现类
import java.io.Serializable;
sealed class Graphic /*extends Object implements Serializable*/ permits Circle,Rectangle, Triangle {
}
final class Triangle extends Graphic{
}
non-sealed class Circle extends Graphic{
}
sealed class Rectangle extends Graphic permits Square{
}
final class Square extends Rectangle{
}
记录类(record)
record是一种全新的类型,它本质上是一个 final类,同时所有的属性都是 final修饰,它会自动编译出get、hashCode 、比较所有属性值的equals、toString 等方法,减少了代码编写量。使用 Record 可以更方便的创建一个常量类。
1.注意:
- Record只会有一个全参构造
- 重写的equals方法比较所有属性值
- 可以在Record声明的类中定义静态字段、静态方法或实例方法。
- 不能在Record声明的类中定义实例字段;
- 类不能声明为abstract;
- 不能显式的声明父类,默认父类是java.lang.Record类
- 因为Record类是一个 final类,所以也没有子类等。
native的语法
native只能修饰方法,表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
public final native Class<?> getClass();
抽象(abstract)
抽象类:被abstract修饰的类。
当前类不能创建对象
抽象方法:被abstract修饰没有方法体的方法。
约束子类的行为
抽象类的语法格式
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
抽象方法的语法格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
注意:抽象方法没有方法体
注意:
1.声明抽象类 abstract class Animal)
2.声明抽象方法 返回值类型前+abstract public abstract void eat();
3.抽象类中可以存在普通的属性方法构造器
抽象类中可以没有抽象方法的
4.如果一个类继承了抽象类则必须重写/实现父类所有的抽象方法 或者此类变为抽象类
5.抽象方法必须在抽象类中
6.抽象方法不能被private final static 修饰
接口(interface)
接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型(引用数据类型:数组,类,枚举,接口,注解。)
接口声明和接口成员
【修饰符】 interface 接口名{
//接口的成员列表:
// 公共的静态常量
public static final int SUM = 10;
// 公共的抽象方法
public abstract void eat(){};
// 公共的默认方法(JDK1.8以上)
public default void eat(){};
// 公共的静态方法(JDK1.8以上)
public static void eat(){};
// 私有方法(JDK1.9以上)
private void eat(){};
}
接口的成员说明
(1)公共的静态的常量:其中public static final可以省略 【默认】
(2)公共的抽象的方法:其中public abstract可以省略 【默认】
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
抽象的方法必须被重写,或把自己变成抽象类
在JDK1.8时,接口中允许声明默认方法和静态方法:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
默认方法可以选择性的重写或不重写
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
静态方法不能重写
(5)私有方法
除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
接口的使用与实现
类实现接口(implements)
接口不能创建对象,但是可以被类实现(implements
,类似于被继承)。
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意:
-
如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。
-
默认方法可以选择保留,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
-
接口中的静态方法不能被继承也不能被重写
public class MobileHDD implements Usb3 {
//重写/实现接口的抽象方法,【必选】
public void out() {
System.out.println("读取数据并发送");
}
public void in(){
System.out.println("接收数据并写入");
}
//重写接口的默认方法,【可选】
//重写默认方法时,default单词去掉
public void end(){
System.out.println("清理硬盘中的隐藏回收站中的东西,再结束");
}
}
使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
package com.atguigu.interfacetype;
public class TestUsb3 {
public static void main(String[] args) {
//通过“接口名.”调用接口的静态方法
Usb3.show();
//通过“接口名.”直接使用接口的静态常量
System.out.println(Usb3.MAX_SPEED);
}
}
使用接口的静态方法和非静态方法
- 对于接口的静态方法,直接使用“接口名.”进行调用即可
- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
接口的多实现
一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
接口的多继承 (extends)
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
所有父接口的抽象方法都有重写。
方法签名相同的抽象方法只需要实现一次。
接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口使用问题
1、默认方法冲突问题
(1)亲爹优先原则
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。
//接口
public interface Friend {
default void date(){//约会
System.out.println("吃喝玩乐");
}
}
//父类
public class Father {
public void date(){//约会
System.out.println("爸爸约吃饭");
}
}
//子类
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重写默认保留父类的
//(2)调用父类被重写的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重写
System.out.println("学Java");
}
}
//test
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
(2)左右为难
如果实现的多个接口内有同名的默认方法则此类必须重写,重写后还想要使用接口内的默认方法 接口名.super.默认方法名();
//接口1
public interface Friend {
default void date(){//约会
System.out.println("吃喝玩乐");
}
}
//接口2
public interface BoyFriend {
default void date(){//约会
System.out.println("神秘约会");
}
}
//类
public class Girl implements Friend,BoyFriend{
@Override
public void date() {
//(1)保留其中一个父接口的
// Friend.super.date();
// BoyFriend.super.date();
//(2)完全重写
System.out.println("学Java");
}
}
//test
public class TestGirl {
public static void main(String[] args) {
Girl girl = new Girl();
girl.date();
}
}
小贴士:
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
常量冲突问题
- 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见。
- 当子类同时继承多个父接口,而多个父接口存在相同同名常量。
此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题。
父类和父接口:
package com.atguigu.interfacetype;
public class SuperClass {
int x = 1;
}
package com.atguigu.interfacetype;
public interface SuperInterface {
int x = 2;
int y = 2;
}
package com.atguigu.interfacetype;
public interface MotherInterface {
int x = 3;
}
子类:
package com.atguigu.interfacetype;
public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
public void method(){
// System.out.println("x = " + x);//模糊不清
System.out.println("super.x = " + super.x);
System.out.println("SuperInterface.x = " + SuperInterface.x);
System.out.println("MotherInterface.x = " + MotherInterface.x);
System.out.println("y = " + y);//没有重名问题,可以直接访问
}
}
接口的特点总结
-
接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
-
声明接口用interface,接口的成员声明有限制:(1)公共的静态常量(2)公共的抽象方法(3)公共的默认方法(4)公共的静态方法(5)私有方法(JDK1.9以上)
-
类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
-
接口可以继承接口,关键字是extends,而且支持多继承。
-
接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
-
如果既有继承也有实现 先继承再实现----如果有同名的方法 则采用继承的类中的方法 亲爹优先
-
如果一个类实现了多个接口 需要重写/实现 接口内所有的抽象方法 否则 此类变为抽象类
-
接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过“接口名.静态方法名”进行调用。
-
如果实现的多个接口内有同名的默认方法则此类必须重写 , 重写后还想要使用接口内的默认方法
接口名.super.默认方法名();
内置接口
Comparable: 内部比较器 (比较对象所在类的内部制定比较规则)
1.让比较对象所在的类实现接口Comparable
2.重写方法制定比较规则
如果前一个对象 > 后一个对象 正整数
如果前一个对象 < 后一个对象 负整数
如果前一个对象 == 后一个对象 0
3. 使用对象调用方法 获取结果
Comparator: 外部比较器(比较对象所在类的外部制定比较规则)
1.创建一个比较规则类 实现接口Comparator
2.重写方法制定比较规则
前一个对象>后一个对象 返回正整数
前一个对象<后一个对象 返回负整数
前一个对象==后一个对象 0
3.需要比较对象的位置 创建比较规则类对象
4.使用比较规则类对象调用方法 获取结果
内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
根据内部类声明的位置(如同变量的分类)
(1)成员内部类:
- 静态成员内部类
- 非静态成员内部类
(2)局部内部类:
- 有名字的局部内部类
- 匿名的内部类
成员内部类
【修饰符】 class 外部类{
【其他修饰符】 【static】 class 内部类{
}
}
静态成员内部类(有static修饰的成员内部类叫做静态内部类)
声明:class A{static class B{}}
特点:
静态成员内部类可以允许四种权限修饰符:public,protected,缺省,private
外部类只允许public或缺省的
1.静态成员内部类使用外部类资源:
静态资源: 直接使用
非静态资源: 创建外部类对象使用
2.外部类使用静态内部类资源
静态资源 :内部类名.资源名
非静态资源 :创建内部类对象使用
3.外部创建静态内部类对象
外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner inner = new Outer.Inner();
4.会产生独立的字节码文件 命名方式:外部类名$内部类名.class
class A {
public void a() {
System.out.println("AAAA");
}
}
class B {
public void b() {
System.out.println("BBBBB");
}
}
class Outer extends A {
String name = "Outer";
public static void show() {
}
public void eat() {
a();
Inner inner = new Inner();
inner.b();
}
public void o2() {
Inner.in2();
Inner inner = new Inner();
inner.in1();
System.out.println("inner.desc = " + inner.desc);
}
class Inner extends B {
String desc = "哈哈 我是内部类";
public void in1() {
System.out.println("in1.........");
}
public static void in2() {
System.out.println("--- in2 ----");
}
}
}
非静态成员内部类(普通成员内部类)
没有static修饰的成员内部类叫做非静态内部类
声明: class A{ class B{} }
特点:
静态成员内部类可以允许四种权限修饰符:public,protected,缺省,private
外部类只允许public或缺省的
2.外部类可以使用内部类资源 内部类也可以使用外部类资源
3.可以打破java的单继承限制
4.外部使用内部类资源
静态资源: 外部类名.内部类名.资源名()
非静态资源: 创建对象类对象.资源;
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
5.内部类会产生独立的字节码文件:外部类名$内部类名.class Outer$Inner.class
6.普通成员内部类 和外部类都有独立的this
外部类的this: 外部类名.this
public class Outer {
public static void o1(){
/* Inner.in1();
Inner inner = new Inner();
inner.in2();*/
}
public void o2(){}
static class Inner{
public static void in1(){
}
public void in2(){
Outer outer = new Outer();
outer.o2();
o1();
}
}
}
局部内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
位置: 方法内
特点
1.局部内部类可以使用外部类资源的类型由所在方法决定
2.局部内部类会产生独立的字节码文件命名方式 :外部类名$序号内部类名.class
3.局部内部类使用了所在方法的局部变量 变量前会 + final
public class Outer {
int a = 10;
static int b = 20;
public static void main(String[] args) {
showB();
}
public static void showB() {
//局部变量
final int num = 66;
// num = 99; 报错
class A {
public void a() {
System.out.println("num = " + num);
}
}
A aaa = new A();
aaa.a();
}
public void showA() {
class A {
public void a() {
System.out.println(a);
System.out.println(b);
}
}
}
}
匿名内部类
当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。
(1)编写类,继承这个父类或实现这个接口
(2)重写父类或父接口的方法
(3)创建这个子类或实现类的对象
声明和意义:
声明:new 类/接口(){}
意义:创建了类/接口的匿名子类
完成了该类或接口的匿名子类的对象创建
new 父类(【实参列表】){
重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
-
注意:
匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:
- 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
- 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final
(1)使用匿名内部类的对象直接调用方法
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
(2)通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
(3)匿名内部类的对象作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}
代码块
非静态代码块(给成员变量赋值)
class 类{
{
非静态代码块
}
}
静态代码块的特点
所有非静态代码块中代码都是在new对象时自动执行。
执行一次构造器 就会执行一次代码块 并且一定是先于构造器的代码执行
格式
【修饰符】 class 类{
{
非静态代码块
}
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
静态成员代码块(给成员变量赋值)
在代码块的前面加static,就是静态代码块
【修饰符】 class 类{
static{
静态代码块
}
}
静态代码块的特点
每一个类的静态代码块只会执行一次。
静态代码块的执行优先于非静态代码块和构造器。
public class Chinese {
// private static String country = "中国";
private static String country;
private String name;
{
System.out.println("非静态代码块,country = " + country);
}
static {
country = "中国";
System.out.println("静态代码块");
}
public Chinese(String name) {
this.name = name;
}
}
静态代码块和非静态代码块
静态代码块在类初始化时执行,只执行一次
非静态代码块在实例初始化时执行,每次new对象都会执行
实例初始化过程
实例初始化的目的
实例初始化的过程其实就是在new对象的过程中为实例变量赋有效初始值的过程
实例初始化相关代码
在new对象的过程中给实例变量赋初始值可以通过以下3个部分的代码完成:
(1)实例变量直接初始化
(2)非静态代码块
(3)构造器
实例初始化方法
实际上我们编写的代码在编译时,会自动处理代码,整理出一个或多个的
实例初始化方法的方法体,由4部分构成:
(1)super()或super(实参列表)
- 这里选择哪个,看原来构造器首行是super()还是super(实参列表)
- 如果原来构造器首行是this()或this(实参列表),那么就取对应构造器首行的super()或super(实参列表)
- 如果原来构造器首行既没写this()或this(实参列表),也没写super()或super(实参列表) ,默认就是super()
(2)非静态实例变量的显示赋值语句
(3)非静态代码块
(4)对应构造器中剩下的的代码
特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面
实例初始化执行特点
- 创建对象时,才会执行
- 每new一个对象,都会完成该对象的实例初始化
- 调用哪个构造器,就是执行它对应的
实例初始化方法 - 子类super()还是super(实参列表)实例初始化方法中的super()或super(实参列表) 不仅仅代表父类的构造器代码了,而是代表父类构造器对应的实例初始化方法。
注意
- 如果存在继承关系 先进行父类实例化再进行子类实例化父子类有单独的
- 如果存在继承关系注意重写的情况
public class Father {
private int a = 1;
public Father(){
System.out.println("Father类的无参构造");
}
public Father(int a, int b){
System.out.println("Father类的有参构造");
this.a = a;
this.b = b;
}
{
System.out.println("Father类的非静态代码块1,a = " + a);
System.out.println("Father类的非静态代码块1,b = " + this.b);
}
private int b = 1;
{
System.out.println("Father类的非静态代码块2,a = " + a);
System.out.println("Father类的非静态代码块2,b = " + b);
}
public String getInfo(){
return "a = " + a + ",b = " + b;
}
}
public class TestFather {
public static void main(String[] args) {
Father f1 = new Father();
System.out.println(f1.getInfo());
System.out.println("-----------------------");
Father f2 = new Father(10,10);
System.out.println(f2.getInfo());
}
}
类初始化过程
1.类的初始化目的
类初始化就是为静态变量赋有效初始值。
2.类初始化方法
类初始化的过程时在调用一个
- 静态类成员变量的显式赋值语句
- 静态代码块中的语句
<clinit>: 1.类中静态成员变量显示赋值语句
2.静态代码块内容
3.类初始化特点
- 每个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。
- 类的初始化一定优先于实例初始化
- 如果存在继续关系会先进行父类初始化再进行子类初始化 父子类共用一个
- 子类使用了从父类继承的静态资源 只会导致父类初始化
4.类初始化执行特点
- 使用了类中的静态资源(属性和方法)
- 创建对象
public class Fu{
static{
System.out.println("Fu静态代码块1,a = " + Fu.a);
}
private static int a = 1;
static{
System.out.println("Fu静态代码块2,a = " + a);
}
public static void method(){
System.out.println("Fu.method");
}
}
public class TestClassInit {
public static void main(String[] args) {
Fu.method();
}
}
既有类初始化也有实例初始化
先类初始化----再---实例初始化 先父类初始化----再----子类初始化
枚举
某些类型的对象是有限的几个,例:星期:Monday(星期一)......Sunday(星期天)
枚举类型本质上也是一种类,只不过是这个类的对象是固定的几个
在JDK1.5之前,需要程序员自己通过特殊的方式来定义枚举类型。
在JDK1.5之前如何声明枚举类呢?
- 构造器加private私有化
- 本类内部创建一组常量对象,并添加public static修饰符,对外暴露这些常量对象
public class Season{
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();
private Season(){
}
public String toString(){
if(this == SPRING){
return "春";
}else if(this == SUMMER){
return "夏";
}else if(this == AUTUMN){
return "秋";
}else{
return "冬";
}
}
}
public class TestSeason {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
在JDK1.5之后,Java支持enum关键字来快速的定义枚举类型。
【修饰符】 enum 枚举类名{
常量对象列表
}
【修饰符】 enum 枚举类名{
常量对象列表;
其他成员列表;
}
示例代码:
public enum Week {
MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
}
public class TestEnum {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
枚举类的要求和特点:
-
枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
-
如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
-
编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数,
-
如果枚举类需要的是有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
-
枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
-
JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
-
枚举类型如有其它属性,建议(不是必须)这些属性也声明为final的,因为常量对象在逻辑意义上应该不可变。
-
枚举可以实现接口既可以统一处理也可以单独处理(枚举自己重写)
-
public enum Gender implements Run{ MAN, WOMAN{ @Override public void running() { System.out.println("女生跑 ......");} }; @Override public void running() { System.out.println("跑起来....."); } } interface Run{ void running(); }
声明枚举:
SPRING("春天"),
SUMMER("夏天"),
AUTUMN("秋天"),
WINTER("冬天")
;
final String des;
Season(String des) {
this.des = des;
}
枚举类型常用方法
1.String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
2.String name():返回的是常量名(对象名)
3.int ordinal():返回常量的次序号,默认从0开始
4.枚举类型[] values():返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型,是一个静态方法
5.枚举类型 valueOf(String name):根据枚举常量对象名称获取枚举对象
6.想使用枚举类中的资源 (可以toString() 重写)
静态资源:类名.属性名/方法名
非静态资源: 对象名.属性名/方法名
public enum Week {
MONDAY("星期一"),
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");
private final String description;
private Week(String description){
this.description = description;
}
@Override
public String toString() {
return super.toString() +":"+ description;
}
}
import java.util.Scanner;
public class TestEnumMethod {
public static void main(String[] args) {
Week[] values = Week.values();
for (int i = 0; i < values.length; i++) {
System.out.println((values[i].ordinal()+1) + "->" + values[i].name());
}
System.out.println("------------------------");
Scanner input = new Scanner(System.in);
System.out.print("请输入星期值:");
int weekValue = input.nextInt();
Week week = values[weekValue-1];
System.out.println(week);
System.out.print("请输入星期名:");
String weekName = input.next();
week = Week.valueOf(weekName);
System.out.println(week);
input.close();
}
}
switch内可以使用枚举
public enum Gender {
MAN,WOMAN
}
@Test
public void test03(){
Gender man = Gender.MAN;
//只需要获取对象就行
switch (man){
case MAN:
System.out.println("男人 顶天立地");
break;
case WOMAN:
System.out.println("貌美如花");
break;
}
}
标签:子类,void,class,面向对象,javaSE,父类,方法,public
From: https://www.cnblogs.com/21CHS/p/18365559