Java面向对象
面对对象概述,类与对象,继承,重写与重载,多态,抽象,封装,包,泛型,异常
面对对象概述
什么是面向对象(OOP)
面向对象(Object Oriented)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向过程和面向对象
面向过程
**优点:**性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
缺点:不易维护、不易复用、不易扩展
面向对象
**优点:**易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
**缺点:**性能比面向过程差
区别
面向对象是相对于面向过程来讲的,指的是把 相关的数据和方法组织为一个整体 来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。
面向对象核心概念
面向对象的三大特性
封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式
将变化隔离,便于使用,提高复用性和安全性。
继承
对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性地继承父类。通过使用继承我们可以非常方便地复用以前的代码。
多态
所谓多态就是指程序中定义的引用变量的指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底就会指向那个类的实例对象,该引用变量发出的方法调用到底是那个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,提高了程序的拓展性。
面向对象三大思想
面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象程序(Object Oriented Programming
五大基本原则
单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的。
开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
接口分离原则ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
小结
抽象会使复杂的问题更加简单化。
从以前面向过程的执行者,变成了张张嘴的指挥者。
面向对象更符合人类的思维,面向过程则是机器的思想
类与对象
概述
类 : 类是具有相同属性和服务的一组对象的集合。为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,应该有一个类名并包括属性说明和服务说明两个主要部分。
对象:对象是系统中用来描述客观事物的一个实体,是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。
类与对象的关系
类:是一组相关属性和行为的集合
对象:该类事物的具体体现
类
Java中用class描述事物也是如此
1.成员变量 就是事物的属性
2.成员方法 就是事物的行为
定义类其实就是定义类的成员(成员变量和成员方法)
成员变量和局部变量的区别
成员变量
类中的属性称为成员变量,该变量定义时 格式为:
数据类型 变量名;
注意:
① 在类中定义的成员变量并不需要有初始值
② 对于成员变量来说,其作用域是在整个类中有效
何为局部变量?
- 局部变量就是方法里定义的变量。
- 局部变量仅作用在局部区域中,从定义开始到大括号或者return结束,生命周期短。
- 局部变量存储:基本数据类型变量放在栈中,引用数据类型放在堆中
- 局部变量可以先定义再初始化,也可以定义的同时初始化,局部变量没有默认初始值。
在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
在内存中的位置不同
成员变量 堆内存
局部变量 栈内存
生命周期不同
成员变量 随着对象的存在而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
初始化值不同
成员变量 有默认的初始化值
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
类的初始化过程
Student s = new Student();在内存中做了哪些事情?
加载Student.class文件进内存
在栈内存为s开辟空间
在堆内存为学生对象开辟空间
对学生对象的成员变量进行默认初始化
.对学生对象的成员变量进行显示初始化
通过构造方法对学生对象的成员变量赋值
学生对象初始化完毕,把对象地址赋值给s变量
对象的内存图
方法的构造
问题:
1.对于创建一个对象格式为:
new 类名() => 格式的解释 => 对于类名()方法的调用 => 类名()方法是什么?
2.对于当前的类对象赋予其属性值,相对较为麻烦,有没有更好的方式编写对应的代码
构造方法:
实际上是一个方法,编写构造方法的格式:
public 类名(){ => 无参构造
}
public 类名(数据类型 参数名,...){ => 无参构造
}
注意:
① 构造方法的方法名需要和类名名称一致
② 构造方法不需要有返回值,并且连void都不需要
编译和反编译:
编辑:将.java文件信息编译成进制文件
反编译:将进制文件 转换成文本文件 idea提供了反编译的功能,对于.class文件可以直接查看其内容
通过idea查看StuConstruct类的编译文件 可以发现有对应的构造方法
public StuConstruct() {
匿名对象
匿名对象:就是没有名字的对象。
是对象的一种简化表示形式
匿名对象的两种使用情况
对象调用方法仅仅一次的时候
作为实际参数传递static关键字
可以修饰成员变量和成员方法
static关键字特点
随着类的加载而加载
优先于对象存在
被类的所有对象共享
这也是我们判断是否使用静态关键字的条件
可以通过类名调用
static关键字注意事项
在静态方法中是没有this关键字的
静态方法只能访问静态的成员变量和静态的成员方法
修饰成员属性
static修饰成员属性:
① 对象可以调用static修饰的成员变量 并可以对其进行赋值
② 通过static修饰的成员变量是所有对象所共有的属性
③ 通过static修饰的成员变量是可以通过类名.属性名进行调用 赋值(赋值过后只要不再次赋值,值为最新一次赋的值)
④ 通过内存展示static修饰的成员变量
修饰成员方法
成员方法是属于对象的 静态方法 => 属于对象也属于类的
静态方法:
① 可以被对象进行调用
② 可以直接使用类名进行调用
③ 在成员方法中可以直接调用静态的方法
④ 在静态方法中不能直接调用成员方法 为什么?
当静态方法被调用时,可能是对象调用 也可能是类进行调用,当是类调用时,
并没有获取到一个对象,所以不能直接进行使用类进行调用成员方法
⑤ 成员方法只能通过对象进行调用而不能通过类名进行调用
⑥ 对于静态方法中可以直接使用静态属性
⑦ 对于静态方法中不能使用成员属性
制作工具类
制作工具类
ArrayTools
制作帮助文档(API)
javadoc -d 目录 -author -version ArrayTool.java
工具类:
将一些常用的方法,该方法可以被static修饰,放置一个类中进行保存,
之后如果使用相关的方法,可以直接使用类名.方法进行调用
代码块
构造代码块
构造代码块是在构造方法之前执行
形式:在类中方法外使用{}进行定义
在每个对象被创建时调用,类在调用时并不会执行构造代码块
静态代码块
会随着类的加载而被执行
形式:在类中方法外使用static{}进行定义
当类被加载时,会默认先执行其静态代码块,智慧再去执行构造代码块,并且静态代码块只会执行一次
原因:当程序运行时,会将当前的.class文件加载到方法区中的class文件区,在整个过程中,只需要加载一次,所以静态代码块只会执行一次
局部代码块
当类中的方法被执行时,才会执行
形式:在方法中使用{}进行定义
作用:限定变量的作用范围,当在{}内定义的变量,不能在{}外进行使用,当{}代码块执行完成,该变量会被销毁,该方式用于代码优化,当{}执行完成,变量的内存空间也会被释放
同步代码块
多线程阶段在讲
为什么会需要有如上类型的代码块?
当使用工具类时,可以直接使用类名,静态方法进行调用,此时对于一些静态变量如果需要做初始化赋值,此时没有一个比较不错的方式,于是可以定义一个静态代码块,当类被加载时,可以执行相对应的代码块内容 于是在该代码块中就可以实现静态变量的初始化
当对象被构建时,在构造方法执行之前可能需要做一些环境的初始化工资,于是可以定义构造代码块,比如要创建对象操作Mysql中的,此时可以直接使用构造代码块,从给定的配置信息中读取连接信息,进行链接,当构造代码块执行完成后,可以获取一个对象
静态代码块、构造代码块、构造函数执行顺序
父类静态代码块 > 子类静态代码块 > main()方法 > 父类(构造)代码块 > 父类构造器(方法)> 子类(构造)代码块 > 子类构造器(方法)
形式参数问题
基本类型作为形式参数 : 基本类型作为参数传递的时候是在一个方法栈中开辟了一块新内存,拷贝了原来的数据值,所以无论我们如何修改,原来的数据值不会受到任何影响。
引用类型作为形式参数 : 首先我们要知道引用的数据存储在栈内存中,而引用指向的对象存储在堆内存中。
当引用作为方法参数传递给方法的时候,是将引用的值拷贝一份给另一个引用,但引用指向的都是同一个堆内存,所以进行的修改操作同样有效。
引用类型中,形参能够改变实参的值,或者一个引用能够改变另一个引用的值,仅仅是因为他们栈内存中存储的值相同,但这个值是随时可以修改的。
实例:
public class Practice2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int a = 5;
System.out.println(a);//5
change(a);
System.out.println(a);//5
}
public static void change(int b) {
b = 500;
}
}
----------------------------------
public class Practice {
static A a = new A(10);
public static void main(String[] args) {
// TODO Auto-generated method stub
Practice practice = new Practice();
System.out.println(practice.a.intData);//10
change(practice.a);
System.out.println(practice.a.intData);//500
}
public static void change(A aa) {
aa.intData = 500;
System.out.println(aa.intData);//500
}
}
class A{
int intData;
public A(int intData) {
this.intData = intData;
}
}
继承
继承关系:
语法:
class 子类名 extends 父类名 {}
说明:
① 对于继承关系,子类可以获取到父类中的所有属性
② 对于子类可以存在有自身独特的属性
③ 对于子类也可以继承父类中的方法
④ 当子类中对于父类继承过来的方法存在有重名,那么在对象调用时,根据就近原则调用自身的成员方法
⑤ 对于一个父类可以存在有多个子类
⑥ Java中不能实现多继承,在子类中只能存在单个的父类
⑦ 对于现实逻辑来看,在类中会存在有多个类之间的关系,如何定义多个类关系?
Java 虽然不支持多继承,但是支持多层继承(单继承)
⑧ 继承关系中的构造方法:
在创建对象时,默认情况下,会先去通过extends关键字找到父类中的构造方法,并执行其构造方法,之后
再去执行子类中的构造方法, => 要想获取子类对象先创建父类对象
当构造代码块和构造方法都存在时,那么会先执行父类中的构造代码块和构造方法再执行子类中的构造代码块和构造方法
静态代码块会先执行(将继承关系中所有的类加载完成) -> 构造代码块
继承的好处
提高了代码的复用性
多个类相同的成员可以放到同一个类中
提高了代码的维护性
如果功能的代码需要修改,修改一处即可
让类与类之间产生了关系,是多态的前提
其实这也是继承的一个弊端:类的耦合性很强
耦合性:
表示代码之间的关系,该关系好处是减少代码量,坏处是代码修改时,
会牵扯过多的子类,导致开发复杂,难度较大
注意:
子类只能继承父类所有非私有的成员(成员方法和成员变量)
什么时候使用继承关系?
当两个类存在有is a的关系 而不是一个类中有少量的方法需要获取时继承
出现同名的变量:
① 变量再方法中有就近原则
② 当子类中有父类同名的变量,那么创建子类对象时,对应变量赋值,会根据就近原则直接赋予给当前对象
查看继承过程中构造方法的相关问题:
① 当子类对象被构建时,需要先创建父类中的对象,才能有子类对象
问题:什么时候创建,如何调用的?
当类被加载后,再去调用构造方法 加载过程是先加载父类,之后再加载子类的类对象
当创建子类对象时,默认先去寻找父类中的无参构造,再由父类无参构造执行,执行完成后,再去执行子类构造
② 当父类中无参构造被有参构造覆盖时,此时不能通过默认的方式创建对象,同时编码不会立即报错,该错误是编译时期的错误
③ 当需要使用父类中的构造方法时,可以使用super关键字,该关键字和this关键字形式一样,this代表当前对象,而super代表父类对象
④ 当子类的构造方法中需要使用super关键字,那么必须要放在第一行 为啥?
当子类运行构造方法时,必须要根据继承关系,先运行父类中的构造方法,此时必须要将super放在构造方法中的第一行
拓展:对于this来说 也可以通过this(参数列表)调用 无参构造或有参构造
类的继承格式
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
super关键字:
(1)super能出现在实例方法和构造方法中。
(2)super的语法是“super.”和“super()”。
(3) super不能出现在静态方法中。
(4) super大部分情况下是可以省略的。
(5)super.什么时候不能省略呢?
别急,我们想一下this指向的是什么,是当前对象自己。super和this类似,它指向了当前对象自己的父类型特征(也就是继承过来的那些东西)。
super和this区别是:this可以看做一个引用变量,保存了该对象的地址,是当前对象整体,而super代表的是父类型特征,是子类局部的一些东西,这些继承过来的东西已经在子类里面了,你可以输出整体this,但不能输出父类型特征super。因为super指向的东西不是一个整体,没法打印输出。
System.out.println(this); //输出this.toString()的值
System.out.println(super); //编译报错,需要'.'
当在子类对象中,子类想访问父类的东西,可以使用“super.”的方式访问。例如:方法覆盖后,子类内部虽然重写了父类的方法,但子类也想使用一下父类的被覆盖的方法,此时可以使用“super.”的方式。当子类中出现和父类一样的属性或者方法,此时,你要想去调用父类的那个属性或者方法,此时“super.”不能省略。
this和super都只能在对象内部使用。
this代表当前对象本身,super代表当前对象的父类型特征。
“this.”是一个实例对象内部为了区分实例变量和局部变量。
而“super.”是一个实例对象为了区分是子类的成员还是父类的成员。
父类有,子类也有,子类想访问父类的,“super.”不能省略。
(6)super()只能出现在构造方法的第一行,通过当前的构造方法去调用“父类”中的对应的构造方法,目的是:创建子类对象时,先初始化父类型特征。
用通俗的话来讲,要想有儿子,得先有父亲。
我问你,当构造方法第一行有"this()"时,你还能手动添加“super()”吗?显然不行,因为“this()”也只能出现在第一行,你不能在它前面写任何代码。所以我们又得出一个结论:构造方法中“this()”和“super()”不能同时出现,也就是“this()”和“super()”都只能出现在构造方法的第一行。
上面谈的都是无参数的“super”方法,我们也可以在构造方法的第一行使用有参数的“super(父类构造函数的参数列表)”,但值得注意的是,当子类构造方法执行有参数的“super(参数列表)”方法,你得确保父类中也有对应的有参数构造方法,不然会编译报错。同样我要提醒一下,当子类构造方法的第一行执行super()无参数方法,那么父类中一定要有无参数构造方法,有的人可能会在父类中写了有参数的构造方法,却忽略了写无参数构造方法,那么在子类构造方法内就会报错,因为当你在一个类中写了有参数的构造方法时,无参数构造方法就会不存在,你需要自己补上无参数的构造方法,这是一个良好的编程习惯。
无论你子类构造方法有没有“this()”和“super()”方法,实例化子类对象一定一定会执行对应的父类构造方法,即不管实例化了一个怎样的孩子,它一定会先实例化一个对应的父亲。
先复习一下this关键字的使用。
(1)this能出现在实例方法和构造方法中;
(2)this的语法是“this.”和“this()”;
(3)this不能出现在静态方法中;
(4)this大部分情况下是可以省略的;
(5)this.什么时候不能省略呢?
在区分局部变量和实例变量时不能省略。例如:
Public void setName(String name){
this.name = name;
}
final关键字
final关键字是最终的意思,可以修饰类,成员变量,成员方法。
修饰类,类不能被继承
修饰变量,变量就变成了常量,只能被赋值一次
修饰方法,方法能被继承,方法不能被重写
final关键字面试题
final修饰局部变量
在方法内部,该变量不可以被改变
在方法声明上,分别演示基本类型和引用类型作为参数的情况
基本类型,是值不能被改变
引用类型,是地址值不能被改变
final修饰变量的初始化时机
在对象构造完毕前即可
需求:
当前定义的类为最终类,不能被其他子类进行继承...
在类名中使用 final修饰
*/
/*
需求:
在某些类中,某些成员方法,不能被子类所修改
*/
/*
总结:
对于final可以修饰 类、成员属性、成员方法
修饰类:
当前类不能被继承
形式:final class 类名 {}
修饰成员属性:
当前属性为一个常量
形式: final 数据类型 常量名
注意:对于修饰的是一个引用类型,那么地址值不能改变,但是地址所指向的位置空间数据可以改变
修饰成员方法:
当前方法可以被继承,但是不能被修改
形式: public final 返回值类型 方法名() {}
*/
构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
实例
package com.test.demo11;
public class Test4 {
public static void main(String[] args) {
System.out.println("------SubClass 类继承------");
SubClass sc1 = new SubClass();
SubClass sc2 = new SubClass(100);
System.out.println("------SubClass2 类继承------");
SubClass2 sc3 = new SubClass2();
SubClass2 sc4 = new SubClass2(200);
}
}
class SuperClass{
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n){
System.out.println("SuperClass(int n)");
this.n=n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){//自动调用父类的无参构造器
System.out.println("SubClass");
}
public SubClass(int n){
super(300);//调用父类中带有参数的构造器
System.out.println("SubClass(int n):"+n);
this.n=n;
}
}
class SubClass2 extends SuperClass{
private int n;
SubClass2(){
super(300);//调用父类中有参构造
System.out.println("SubClass2");
}
public SubClass2(int n){
System.out.println("SubClass2(int n):"+n);
this.n=n;
}
}
结果:
------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200
重写与重载
重写(Override)
重写的前提是继承,只有存在继承关系的类之间才存在重写。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
注意事项
重写方法的不能比被重写的方法有更严格的访问限制
相同的方法名,相同的参数列表,相同的返回值
重写方法不能抛出比被重写方法声明更广的异常,可以抛出范围更小的或不抛出异常
不能重写被final修饰的方法
如果一个方法不能被继承,则不能被重写。例如:构造器不能被继承,所以不能被重写。或者是父类声明为private的方法
子类不能重写父类的构造方法,不能重写static修饰的方法。
方法的重写规则
参数列表与被重写方法的参数列表必须完全相同。
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
父类的成员方法只能被它的子类重写。
声明为 final 的方法不能被重写。
声明为 static 的方法不能被重写,但是能够被再次声明。【父类静态方法,子类也必须通过静态方法进行重写。(其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中我会讲解)】
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
构造方法不能被重写。
如果不能继承一个方法,则不能重写这个方法。
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。和返回类型无关。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
如果方法有明确的返回值,一定要有return带回一个值
重写与重载之间的区别
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
多态
多态存在的三个必要条件
-
继承
-
重写
-
父类引用指向子类对象
在逻辑关系上,对象可以是由多种描述方式,但是前提是对象有对应的继承关系 多态: 将子类对象赋予给父类变量 -> 有父类引用指向子类对象 前提: 有继承关系 多态成员访问: 成员变量 编译看左边,运行看左边 成员方法 编译看左边,运行看右边 静态方法 编译看左边,运行看左边 多态中的转型: 向上转型 (多态的形式) 从子到父 父类引用指向子类对象 向下转型 从父到子 父类引用转为子类对象 注意事项: 当进行做强制类型转换时,在运行时,会校验当前地址的对象是否和变量的类型存在有继承关系 实例:Per per = new Stu(); // 将子类的对象赋予给父类变量 从子到父 Stu stu1 = (Stu) per; // 向下转换 将父类变量强制类型转换为子类变量 //上面两行同时有才能像下转型 Per tea = new Tea(); // 向上转型 /* 注意: tea中的内存地址实际上是指向一个教师对象, 现在通过强制转换将 教师对象地址赋予给 Stu stu2 学生变量 */ Stu stu2 = (Stu) tea; // 强制转换 有类型转换问题
Per per = new Stu(); // 将子类的对象赋予给父类变量 从子到父 //从父到子 Per per = new Stu(); Stu stu1 = (Stu) per;
向上转型和向下转型
对象的向上转型:父类 父类对象 = 子类实例
对象的向下转型:子类 子类对象 = (子类)父类实例
示例一
父类强制转子类
Father f = new Father();
Son s = (Son)f;//出错 ClassCastException
分析:
创建一个父类的实例,想要强制把父类对象转换成子类的,不行!
通俗的想,父亲永远不可能转换成儿子。因为有空白,假如转换了,那么上幼儿园、哺乳,这些儿子的特有属性父亲是没有的,强转就出现了ClassCastException
示例二
“假的”父类强制转子类
Father f = new Son();
Son s = (Son)f;//可以
分析:
父类对象引用着一个子类实例。
Son类特有的属性暂时不能通过 f 来操作,因为Father类没有Son类(子类)的特有属性。
接着创建子类对象 s,它引用的是父类对象 f 强制转换来的对象(其实就是个装爹的Son,把他强制转回了Son),这时就可以通过 s 来操作子类的特有属性了。
通俗的说就是儿子装爹,终究是儿子,本质没变,还是可以把他强制转回儿子的。
示例三
子类强制转父类
Son s = new Son();
Father f = (Father)s;//可以
分析:
子类转换成父类,只是子类对象的特有属性无法利用 f 操作,f 可以操作其非特有的属性(由父类继承而来的属性)。也就是说,儿子可以执行吃饭和睡觉的方法,父亲也有,因为这就是从父亲继承的,但是让父亲执行上小学的方法就不行,因为这是儿子的‘‘特有属性’’。
通俗的说,儿子和爹的共同点——“都是人”,儿子作为人的基本属性是从父亲继承过来的,儿子转换成了父亲,只是失去了原本自己比父亲多出来的那些特有属性。
抽象类
抽象类
抽象类的定义规则:
抽象类和抽象方法都必须用abstract关键字来修饰;
抽象类不能被实例化,也就是不能用new关键字去产生对象;
抽象方法只需声明,而不需实现;
含有抽象方法的类必须被声明为抽象类,抽象类的子类必须复写所有的抽象方法后才能被实例化,否则这个子类还是个抽象类
抽象类的定义格式:
abstract class 类名称 // 定义抽象类
{
声明数据成员;
访问权限 返回值的数据类型 方法名称(参数...)//定义一般方法
{
...
}
abstract 返回值的数据类型 方法名称(参数...);
//定义抽象方法,在抽象方法里,没有定义方法体
}
package com.shujia.day07;
import com.shujia.day07.animal.Animal;
import com.shujia.day07.animal.Cat;
import com.shujia.day07.animal.Dog;
import com.shujia.day07.father.GrandFather;
import com.shujia.day07.father.Son;
public class Demo02Abstract {
public static void main(String[] args) {
/*
什么是抽象:
① 从逻辑角度看 是表示一个概念性的东西
② 从代码角度来看 表示方法时 该方法只有具体的方法格式,而没有实现的代码块
public void eat() => 抽象的方法
抽象类:
① 从逻辑角度看 用于表示一个 物种的抽象种类 或者是对某个概念总和的抽象
比如:对于猫和狗都是动物,那么 猫类 狗类 动物类中 ,动物类是抽象类
② 从代码角度来看,包含有抽象方法的类称为抽象类
对于抽象方法,需要和其他方法进行区分,于是可以添加abstract进行标记
对于包含抽象方法的类,需要对其进行标记 可以在类定义时添加 abstract 进行标记
定义格式:
public abstract class 抽象类名{
public abstract 返回值类型 方法名(参数列表); // 没有方法体
}
单独的抽象类,并没有实际意义,因为抽象方法,没有具体的实现,所以可以构建类和类之间的关系——> 使用继承
于是可以使用 public class 类A extends 抽象类B => 由于 class 类A 是一个非抽象类
=> 根据继承关系 得到抽象类B中的抽象方法 => 但是类A不是抽象类,不能包含抽象的方法
=> 于是可以通过重写的方式,对其中的抽象方法进行重写,并定义其具体的代码块
注意:
1.抽象类不能创建其对象
2.抽象类的子类对象,可以指向抽象类的变量 (多态) 父类引用指向子类对象
从逻辑上看,猫和狗都是属于动物中的一种,所以可以使用动物来描述猫和狗
3.抽象类中可以有非抽象的方法:
(逻辑理解) 对于 爷爷类 父亲类, 对于爷爷类来说,会有一些未完成的理想,但是同时也有一些资产
对于父亲类来说,继承了爷爷类的资产和理想 => 对于爷爷类,当爷爷去世,那么该类就是一个抽象类
4.抽象类的子类可以是抽象的,也可以是非抽象的
对于抽象子类,不需要去实现其抽象方法
5.类型之间可以存在传递关系
爷 父 子 => 对于子的类的对象 可以使用爷类类型 变量 来接收
6.对于抽象类中的变量:
6.1 可以被子类继承
6.2 变量不能使用abstract进行修饰
6.3 对于static修饰的变量,可以直接使用类名进行调用
7.对于继承关系中,抽象类继承了另一个抽象父类,此时抽象类可以实现父类中的抽象方法也可以不实现其抽象方法
8.在抽象类的编译文件中,通过反编译可以看到其构造方法,为什么有?其作用是什么?
在继承关系中,子类如果要创建对象,那么需要调用自身的构造方法,但是创建之前,先去执行父类中的构造方法
之后再去,执行后续的代码,所以父类虽然是一个抽象类,但是必须要有其构造方法
9.在子类中,可以通过super()显示调用父抽象方法中的构造方法
注意:当编写代码时,出现有红色波浪 可以使用 alt + enter键 选择idea提供的错误解决方案
一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?
可以,该抽象类,可以用于类之间的逻辑划分,抽象类表示的是一个抽象的概念,并没有具体的实例
abstract 不能和哪些关键字共存
private 冲突: abstract修饰的方法是一个抽象方法,对于抽象方法来说要使其有意义,那么必须通过继承来进行实现
如果使用private修饰,那么子类无法通过继承来获取到
final 冲突: abstract修饰的方法是一个抽象方法,对于抽象方法来说要使其有意义,那么必须通过继承来进行实现
对于继承时,如果使用 final的方法 不能对方法进行重写操作
static 没有意义: static可以直接通过类进行调用,但是当前抽象类中抽象方法,没有具体的实现代码块
*/
// Animal animal = new Animal(); // 抽象类中编译时,虽然有构造方法,但是在该方式中不能创建单独的对象
Cat cat = new Cat();
cat.eat();
cat.sleep();
Animal animal1 = new Cat();
animal1.eat();
Dog dog = new Dog();
dog.eat();
Animal animal2 = new Dog();
animal2.eat();
// Father father = new Father();
// long money = father.getMoney();
// System.out.println("现在有"+money+"钱");
// father.haveDream();
GrandFather son = new Son();
long money = son.getMoney();
System.out.println("现在有"+money+"钱");
son.haveDream();
System.out.println(son.sign);
System.out.println(GrandFather.NAME);
// GrandFather.getMoney(); // static修饰的类可以被抽象类所调用
// Father father = new Father();
}
}
接口
接口的定义
接口(interface)是Java所提供的另一种重要技术,它的结构和抽象类非常相似,也具有数据成员与抽象方法,但它与抽象类又有以下两点不同:
接口里的数据成员必须初始化,且数据成员均为常量;
接口里的方法必须全部声明为abstract,也就是说,接口不能像抽象类一样保有一般的方法,而必须全部是“抽象方法”。
接口定义的语法如下:
interface 接口名称 // 定义抽象类
{
final 数据类型 成员名称 = 常量; //数据成员必须赋初值
abstract 返回值的数据类型 方法名称(参数...);
//抽象方法,注意在抽象方法里,没有定义方法主体
}
接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值将不能再更改,方法也必须是“抽象方法”。也正因为方法必须是抽象方法,而没有一般的方法,所以抽象方法声明的关键字abstract是可以省略的。
**相同的情况也发生在数据成员身上,因数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字final也可省略。**事实上只要记得:
接口里的“抽象方法”只要做声明即可,而不用定义其处理的方式;
数据成员必须赋初值。
接口的实现
在Java中接口是用于实现多继承的一种机制,也是Java设计中最重要的一个环节,每一个由接口实现的类必须在类内部复写接口中的抽象方法,且可自由地使用接口中的常量。
既然接口里只有抽象方法,它只要声明而不用定义处理方式,于是自然可以联想到接口也没有办法像一般类一样,再用它来创建对象。利用接口打造新的类的过程,称之为接口的实现(implementation)。
接口实现的语法:
class 类名称 implements 接口A,接口B //接口的实现
{
...
}
接口的扩展
接口是Java实现多继承的一种机制,一个类只能继承一个父类,但如果需要一个类继承多个抽象方法的话,就明显无法实现,所以就出现了接口的概念。一个类只可以继承一个父类,但却可以实现多个接口。
接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口,派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可加入新的成员以满足实际的需要。
同样的,接口的扩展(或继承)也是通过关键字extends来实现的。有趣的是,一个接口可以继承多个接口,这点与类的继承有所不同。
接口扩展的语法:
interface 子接口名称 extends 父接口1,父接口2...
{
...
}
package com.shujia.day07;
import com.shujia.day07.teacher.Sing;
import com.shujia.day07.teacher.TeacherInterface;
public class Demo05Interface {
public static void main(String[] args) {
/*
接口:
接口的意义实际上是对当前的类所属功能进行拓展
比如 电脑 通过 USB接口/ HDMI接口 外接其他设备,使其具有相对应的一些功能
接口在Java中的意义?
对于有些抽象类,其子类中并不是所有的方法都是抽象类所共有的,需要通过一些其他的方式再添加一些
功能到当前子类中
比如:教师抽象类中定义的方法 是所有教师子类所都有的,但是部分教师都有自身所独特的技能
教师A -> 唱歌
教师B -> 唱歌
包装唱歌为一个接口,每个教师唱歌的风格可能不一样,对应唱歌的方法有自己的实现形式
教师C -> 乐器
教师D -> 乐器
包装乐器为一个接口,每个教师掌握的乐器可能不一样,对应乐器有自己的实现形式
如何定义接口:
形式:
public interface 类名 {}
使用单独的关键字对其进行描述,为什么接口不能是一个 class 而必须是单独的一种类型?
注意:接口是功能性的拓展,是对当前的类给一些独特的 方法规范
注意:
① 对于接口中定义的方法,只是一个规范,不能存在有具体的方法体,具体的方法需要在子类中进行实现
② 接口不能构建其对象,所以需要借助类来构建,并对其进行调用
③ 对于接口要通过其子实现类来进行实现其抽象方法,使用关键字 implements
④ 对于接口中可以定义其属性,但是需要对其属性进行赋予初始值
接口中的属性,一般情况下,如果要定义,那么经常是以常量的形式存在,比如一些配置接口,将配置信息设置成一个常量形式
⑤ 接口和抽象类的区别
方法上的区别:
抽象类:既可以定义抽象方法也可以定义非抽象方法
接口: 只能定义抽象方法
存在的意义不同:
抽象类: 对于一个概念进行做抽象,将其相关的属性和方法形成一个类
接口: 对当前类的功能进行拓展,相当于是电脑的一个外接接口
⑥ 从代码设计角度为什么需要接口?
1. 因为如果使用抽象类来设计接口,那么根据Java的定义原则,只能存在有单继承或者多层继承的关系
不支持多继承,于是需要单独设计一个 interface
2. 在实际开发过程中,对于一个类,可能需要实现多个不同的接口,而抽象类无法提供
⑦ 一个类可以实现多个接口,其格式为:
class 类名 implements 接口1,接口2
⑧ 接口可以存在有继承关系,并且接口存在有多继承关系,为什么?
对于接口来说,继承以后,不能对其方法进行重写,那么就不会造成多继承中对于相同方法的不同实现
*/
// Sing sing = new Sing(); // 接口不能构建其对象
TeacherInterface teacherInterface = new TeacherInterface();
teacherInterface.sing();
// System.out.println(teacherInterface.singName);
System.out.println(teacherInterface.SING_NAME);
teacherInterface.skill();
}
}
实参和形参
形式参数
在方法的参数列表中定义的参数称为形式参数
基本类型:
一般情况下是传递具体的数值给形参,对于形参的计算,并不会改变原来的实参
引用类型:
一般情况下是传递内存地址给形参,如果对内存地址的变量进行修改,那么会改变原来实参的数值
实参:
除形式参数以外的其他参数,在一般情况下,在方法内进行定义的
返回值
返回值类型
基本类型
引用类型
封装
什么是封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装概述
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:
隐藏实现细节,提供公共的访问方式
提高了代码的复用性
提高安全性。
封装原则:
将不需要对外提供的内容都隐藏起来。
把属性隐藏,提供公共方法对其访问。
匿名对象
匿名对象:就是没有名字的对象。
是对象的一种简化表示形式
匿名对象的两种使用情况
对象调用方法仅仅一次的时候
作为实际参数传递
包
包:
在创建类时,会默认给当前类中第一行代码添加 package 路径 用于表示当前类所在的位置
该位置的作用是,在编译时,会将当前的.class文件放入指定路径中
路径是使用.进行分割
类路径: com.shujia.day08.teacher.Teacher 可以使用idea选中当前类 右键 选择copy Reference
包的作用
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
使用关键字 import 类路径
什么时候需要使用import导入?
当所需的类和当前类不在一个包路径下时,需要使用import导入
对于Java中的java.lang路径下的类,并不需要对其进行导入操作
在同一个包下,所导入的类有多个时,那么此时可以使用 * 来替代
比如:import com.shujia.day08.teacher.*;
注意:该方式,只能导入一层包下的所有类,不能进行迭代导入;
当导入的多个类出现重复时:(了解)
① 使用非*形式,类名称相同会报错
② 使用*形式,直接导入类名称的方式优先级会比*更高
注意事项:
package语句必须是程序的第一条可执行的代码
权限修饰符
在Java中,有四种主要的访问修饰符(权限修饰符)用于控制类、方法和变量的访问级别。这些访问修饰符分别是:public
、protected
、默认(package-private,默认即不写修饰符)和private
。下面是这些访问修饰符的作用范围:
- public:
- 在任何地方都可以访问,即对所有类可见。
- 示例:类、方法、变量被声明为
public
。
- protected:
- 同一类中可以访问。
- 同一包中的子类可以访问。
- 不同包中的子类可以访问(前提是子类引用的对象是子类的实例)。
- 示例:方法、变量被声明为
protected
。
- 默认(package-private,默认即不写修饰符):
- 同一类中可以访问。
- 同一包中的子类可以访问。
- 示例:类、方法、变量未使用任何修饰符,默认为包内可见。
- private:
- 同一类中可以访问。
- 示例:方法、变量被声明为
private
。
内部类
分类:成员内部类、局部内部类、静态内部类、匿名内部类
注意:
(1)、内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
(2)、内部类不能用普通的方式访问。
(3)、内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量 。
(4)、外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问
成员内部类
即在一个类中直接定义的内部类,成员内部类与普通类的成员没什么区别,可以与普通成员一样进行修饰和限制。成员内部类不能含有static的变量和方法。
package com.test.demo10;
public class Outer {
private static int number = 100;
private int j =20;
private String name ="java";
public static void outer_funOne(){
System.out.println("外部类的静态方法");
}
public void outer_funTwo(){
System.out.println("外部类的普通方法");
}
//内部类
class Demo{
//static int demos=22;//内部类不能定义静态变量
int j = 50;//内部类和外部类的实例变量可以共存
//成员内部类的方法定义
public void demo_funOne(){
//内部类中访问内部类自己的变量可以直接使用变量名也可以用this.j
System.out.println(j);
//内部类中访问外部类的成员变量语法:外部类类名.this.变量名
System.out.println("内部类访问外部类变量"+Outer.this.j);
//如果内部类中没有与外部类中相同的变量,也可以直接使用变量名
System.out.println(name);
//内部类调用外部类方法
outer_funOne();//静态方法
outer_funTwo();//非静态方法
}
}
public static void outer_funThree(){
//外部类静态方法访问成员内部类
//先建立外部类对象
Outer out = new Outer();
//根据外部类建立内部类对象
Demo demo =out.new Demo();
//访问内部类方法
demo.demo_funOne();
//访问内部类成员
System.out.println("内部类的成员:"+demo.j);
}
//外部类成员方法访问成员内部类
public void outer_funFirst(){
Demo outs =new Outer().new Demo();
outs.demo_funOne();
System.out.println(outs.j);
}
public static void main(String[] args) {
//调用内部类的方法
Outer.Demo demo1 = new Outer().new Demo();
demo1.demo_funOne();
System.out.println(demo1.j);
Outer out =new Outer();
System.out.println(out.j);
out.outer_funFirst();
Outer.outer_funThree();//静态方法用类名直接调用
}
}
局部内部类
在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,和此外围类所有的成员。
需要注意的是:
(1)、局部内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
(2)、局部内部类对象不能使用该内部类所在方法的非final局部变量。
package com.test.demo10;
public class Outers {
private static int number =100;
private int j =20;
private String name = "java";
//定义外部类方法
public void outer_funOne(int k){
final int number = 100;
int j =50;
//方法内部的类(局部内部类)
class Demo{
public Demo(int k){
demo_funOne(k);
}
final int number =300;//可以定义与外部类同名的变量
//static int j =10;//不可以定义静态变量
//内部类方法
public void demo_funOne(int k){
System.out.println("内部类方法:demo_funOne");
//访问外部类的变量,如果没有与内部类同名的变量,可以直接用变量名
System.out.println(name);
//访问外部类与内部类同名的变量(内部与外部都能访问)
System.out.println("我是内部的:"+this.number);
System.out.println("我是外部的"+Outers.number);
System.out.println("内部类方法传入的参数是:"+k);
}
}
Demo demo = new Demo(k);//外部类要创建内部类
}
public static void main(String[] args) {
//访问内部类必须要先有外部类对象
Outers out =new Outers();
out.outer_funOne(15);
}
}
静态内部类(嵌套类)
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:
1. 要创建嵌套类的对象,并不需要其外围类的对象。
2. 不能从嵌套类的对象中访问非静态的外围类对象。
package com.test.demo10;
public class Outer1 {
private static int number =100;
private int j =20;
private String name="java";
public static void outer1_funOne(){
System.out.println("外部类静态方法:outer_funOne");
}
public void outer1_funTwo(){
System.out.println("外部类普通方法");
}
//静态内部类可以用public,protected,private修饰
private static class Demo{
static int j =100;//静态内部类可以定义静态和非静态的变量
String name ="C";
//静态内部类的静态方法
static void demo_funOne(){
//静态内部类只能访问外部类的静态成员(静态变量、静态方法)
System.out.println("静态内部类访问外部类静态变量:"+number);
outer1_funOne();//访问外部类静态方法
}
//静态内部类非静态方法
void demo_funTwo(){
}
}
public void outer_funThree(){
//外部类访问内部类静态成员
System.out.println(Demo.j);
//访问静态方法
Demo.demo_funOne();
//访问静态内部类的非静态成员和方法,需要实例化内部类
Demo demo=new Demo();
System.out.println(demo.name);
demo.demo_funTwo();
}
public static void main(String[] args) {
new Outer1().outer_funThree();
}
}
匿名内部类★★★
简单地说:匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:
1、只用到类的一个实例。
2、类在定义后马上用到。
3、类非常小(推荐是在4行代码以下)
4、给类命名并不会导致你的代码更容易被理解。
在使用匿名内部类时,要记住以下几个原则:
1、 匿名内部类不能有构造方法。
2、 匿名内部类不能定义任何静态成员、方法和类。
3、 匿名内部类不能是public,protected,private,static。
4、 只能创建匿名内部类的一个实例。
5、 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
6、 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
package com.test.demo10;
public class HelloWorldAnonymousClasses {
//包含两个方法的HelloWorld接口
interface HelloWorld{
public void greet();
public void greetSomeone(String someone);
}
public void sayHello(){
//1.局部类EnglishGreeting实现了HelloWorld接口
class EnglishGreeting implements HelloWorld{
String name="world";
@Override
public void greet() {
greetSomeone("world");
}
@Override
public void greetSomeone(String someone) {
name =someone;
System.out.println("Hello "+name+" "+someone);
}
}
HelloWorld englishGreeting=new EnglishGreeting();
//2.匿名类实现接口
HelloWorld frenchGreeting =new HelloWorld() {
String name = "mundo";
@Override
public void greet() {
greetSomeone("tout is monde");
}
@Override
public void greetSomeone(String someone) {
name =someone;
System.out.println(" 1"+name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
frenchGreeting.greet();
}
public static void main(String[] args) {
HelloWorldAnonymousClasses myapp=new HelloWorldAnonymousClasses();
myapp.sayHello();
}
}
该例中用局部类来初始化变量englishGreeting,用匿类来初始化变量frenchGreeting和spanishGreeting,两种实现之间有明显的区别:
1)局部类EnglishGreetin继承HelloWorld接口,有自己的类名,定义完成之后需要再用new关键字实例化才可以使用;
2)frenchGreeting、spanishGreeting在定义的时候就实例化了,定义完了就可以直接使用;
3)匿名类是一个表达式,因此在定义的最后用分号";"结束。
匿名内部类的语法
如上文所述,匿名类是一个表达式,匿名类的语法就类似于调用一个类的构建函数(new HelloWorld()),除些之外,还包含了一个代码块,在代码块中完成类的定义,见以下两个实例:
案例一,实现接口的匿名类:
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
案例二,匿名子类(继承父类):
package com.test.demo10;
public class AnimalTest {
private final String ANIMAL = "动物";
public void accessTest() {
System.out.println("匿名内部类访问其外部类方法");
}
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void printAnimalName() {
System.out.println(bird.name);
}
}
// 鸟类,匿名子类,继承自Animal类,可以覆写父类方法
Animal bird = new Animal("布谷鸟") {
@Override
public void printAnimalName() {
accessTest(); // 访问外部类成员
System.out.println(ANIMAL); // 访问外部类final修饰的变量
super.printAnimalName();
}
};
public void print() {
bird.printAnimalName();
}
public static void main(String[] args) {
AnimalTest animalTest = new AnimalTest();
animalTest.print();
}
}
从以上两个实例中可知,匿名类表达式包含以下内部分:
-
操作符:new;
-
一个要实现的接口或要继承的类,案例一中的匿名类实现了HellowWorld接口,案例二中的匿名内部类继承了Animal父类;
-
一对括号,如果是匿名子类,与实例化普通类的语法类似,如果有构造参数,要带上构造参数;如果是实现一个接口,只需要一对空括号即可;
-
一段被"{}"括起来类声明主体;
-
末尾的";"号(因为匿名类的声明是一个表达式,是语句的一部分,因此要以分号结尾)
访问作用域内的局部变量、定义和访问匿名内部类成员
匿名内部类与局部类对作用域内的变量拥有相同的的访问权限。
(1)、匿名内部类可以访问外部内的所有成员;
(2)、匿名内部类不能访问外部类未加final修饰的变量(注意:JDK1.8即使没有用final修饰也可以访问);
(3)、属性屏蔽,与内嵌类相同,匿名内部类定义的类型(如变量)会屏蔽其作用域范围内的其他同名类型(变量):
(4)、匿名内部类中不能定义静态属性、方法;
(5)、匿名内部类可以有常量属性(final修饰的属性);
(6)、匿名内部内中可以定义属性,如上面代码中的代码:private int x = 1;
(7)、匿名内部内中可以可以有额外的方法(父接口、类中没有的方法);
(8)、匿名内部内中可以定义内部类;
(9)、匿名内部内中可以对其他类进行实例化。
案例一,内嵌类的属性屏蔽:
1 public class ShadowTest {
2
3 public int x = 0;
4
5 class FirstLevel {
6
7 public int x = 1;
8
9 void methodInFirstLevel(int x) {
10 System.out.println("x = " + x);
11 System.out.println("this.x = " + this.x);
12 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
13 }
14 }
15
16 public static void main(String... args) {
17 ShadowTest st = new ShadowTest();
18 ShadowTest.FirstLevel fl = st.new FirstLevel();
19 fl.methodInFirstLevel(23);
20 }
21 }
复制代码
输出结果为:
x = 23
this.x = 1
ShadowTest.this.x = 0
这个实例中有三个变量x:1、ShadowTest类的成员变量;2、内部类FirstLevel的成员变量;3、内部类方法methodInFirstLevel的参数。
methodInFirstLevel的参数x屏蔽了内部类FirstLevel的成员变量,因此,在该方法内部使用x时实际上是使用的是参数x,可以使用this关键字来指定引用是成员变量x:
1 System.out.println("this.x = " + this.x);
利用类名来引用其成员变量拥有最高的优先级,不会被其他同名变量屏蔽,如:
1 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
案例二,匿名内部类的属性屏蔽:
1 public class ShadowTest {
2 public int x = 0;
3
4 interface FirstLevel {
5 void methodInFirstLevel(int x);
6 }
7
8 FirstLevel firstLevel = new FirstLevel() {
9
10 public int x = 1;
11
12 @Override
13 public void methodInFirstLevel(int x) {
14 System.out.println("x = " + x);
15 System.out.println("this.x = " + this.x);
16 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
17 }
18 };
19
20 public static void main(String... args) {
21 ShadowTest st = new ShadowTest();
22 ShadowTest.FirstLevel fl = st.firstLevel;
23 fl.methodInFirstLevel(23);
24 }
25 }
复制代码
输出结果为:
x = 23
this.x = 1
ShadowTest.this.x = 0
(4)、匿名内部类中不能定义静态属性、方法;
1 public class ShadowTest {
2 public int x = 0;
3
4 interface FirstLevel {
5 void methodInFirstLevel(int x);
6 }
7
8 FirstLevel firstLevel = new FirstLevel() {
9
10 public int x = 1;
11
12 public static String str = "Hello World"; // 编译报错
13
14 public static void aa() { // 编译报错
15 }
16
17 public static final String finalStr = "Hello World"; // 正常
18
19 public void extraMethod() { // 正常
20 // do something
21 }
22 };
23 }
(5)、匿名内部类可以有常量属性(final修饰的属性);
(6)、匿名内部内中可以定义属性,如上面代码中的代码:private int x = 1;
(7)、匿名内部内中可以可以有额外的方法(父接口、类中没有的方法);
(8)、匿名内部内中可以定义内部类;
(9)、匿名内部内中可以对其他类进行实例化。
异常
异常概述
异常就是程序在运行过程中出现的一些错误,我们通过面向对象的思想,把这些错误也用类来描述,那么一旦产生一个错误,即就是创建了某一个错误的对象,这个对象就是我们所说的异常对象。
1)IndexOutOfBoundsException:
ArrayIndexOutOfBoundsException
数组角标越界异常 角标不在数组范围内
StringfIndexOutOfBoundsException
字符串角标越界异常 角标不在字符串范围内
(2)NullPointerException
空指针异常 对null调用其成员。
(3)ArithmeticException
数学运算异常 非法的数学运算。
(4)ClassCastException
类型转换异常 将类进行错误的强制转换。
(5)NumberFormatException
数字格式化异常 将数字字符串进行解析。
(6)InputMismatchException
输入不匹配异常 在输入时输入非法数据。
(7)ParseException
时间解析异常 非法的时间格式。
(8)StackOverFlowError
栈内存溢出异常 函数递归。
(9)OutOfMemoryError
堆内存异常 数组空间开辟过大 程序中对象太多。
Java异常体系
Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。
Throwable中的方法
getMessage()
获取异常信息,返回字符串。
toString()
获取异常类名和异常信息,返回字符串。
printStackTrace()
获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
printStackTrace(PrintStream s)
通常用该方法将异常内容保存在日志文件中,以便查阅。
异常分类
Error
Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
处理编译时异常的更多知识
声明异常
在 Java 中,当前执行的语句必属于某个方法。Java 解释器调用 main 方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常的类型。这称为声明异常( declaring exception)。只对编译时异常进行声明,、告知方法的调用者有异常。
为了在方法中声明一个异常,就要在方法头中使用关键字 throws, 如下所示:
public void myMethodO throws IOException
关键字 throws 表明 myMethod 方法可齙会抛出异常 IOException。如果方法可能会抛出多个异常,就可以在关键字 throws 后添加一个用逗号分隔的异常列表:
public void myMethodO throws Exceptionl, Exception2,… ,ExceptionN
注意:如果方法没有在父类中声明异常,那么就不能在子类中对其进行继承来声明异常。
抛出异常
检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这就称为抛出一个异常(throwing an exception)。这里有一个例子,假如程序发现传递给方法的参数与方法的合约不符(例如,方法中的参数必须是非负的,但是传入的是一个负参数),这个程序就可以创建 IllegalArgumentException 的一个实例并抛出它,如下所示:
IllegalArgumentException ex = new II1egalArgumentException("Wrong Argument");
throw ex;
或者,大多数使用下面的语句:
throw new IllegalArgumentException("Wrong Argument");
注意:IllegalArgumentException 是 Java API 中的一个异常类。通常,JavaAPI 中的每个异常类至少有两个构造方法:一个无参构造方法和一个带可描述这个异常的 String 参数的构造方法。该参数称为异常消息(exceptionmessage), 它可以用 getMessage()获取。
提示:声明异常的关楗字是 throws, 抛出异常的关键字是 throw。
捕获异常
现在我们知道了如何声明一个异常以及如何抛出一个异常。当抛出一个异常时,可以在try-catch 块中捕获和处理它;
try-catch-finally
try语句块中:放的是可能出现问题的代码,尽量不要把不相关的代码放入到里面,否则会出现截断的问题。
try{
codeA
throw ...
codeB
}
如果throw这个地方一旦抛出异常 codeB就不会执行了 建议把codeB放到后面。
catch语句块中:放的是出现问题并捕获后,处理问题的代码code,如果问题在try语句块中没有出现 则catch中不会运行。
catch(Exception e) {
code
}
finally语句块中:放的是不管问题异常是否产生 都要执行的代码code。
finally{
code//关闭资源(IO 数据库 网络),结尾处理的一些工作
}
在捕获异常中return的执行顺序
try、catch、finally、return执行顺序超详解析(针对面试题)
有关try、catch、finally和return执行顺序的题目在面试题中可谓是频频出现。总结一下此类问题几种情况。
不管try中是否出现异常,finally块中的代码都会执行;
当try和catch中有return时,finally依然会执行;
finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以如果finally中没有return,即使对数据有操作也不会影响返回值,即如果finally中没有return,函数返回值是在finally执行前就已经确定了;
finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。
注:
finally修改的基本类型是不影响 返回结果的。(传值)
修改list,map,自定义类等引用类型时,是影响返回结果的。(传址的)对象也是传址的
但是date类型经过测试是不影响的。
try{} catch(){}finally{} return;
按程序顺序运行,如果try中有异常,会执行catch中的代码块,有异常与否都会执行finally中的代码;最终返回。
try{ return; }catch(){} finally{} return;
1.先执行try块中return 语句(包括return语句中的表达式运算),但不返回;
2.执行finally语句中全部代码
3.最后执行try中return 返回
finally块之后的语句return不执行,因为程序在try中已经return。
示例:
package com.test.demo11;
public class Test {
public int add(int a,int b){
try {
return a+b;
}catch (Exception e){
System.out.println("catch语句块");
}finally {
System.out.println("finally语句块");
}
return 0;
}
public static void main(String[] args) {
Test test = new Test();
System.out.println("和是:"+test.add(9,34));
}
}
结果:
finally语句块
和是:43
至于为什么不是:和是 finally块43的原因:
System.out.println(“和是:”+test.add(9,34)); 这是进行字符串拼接是一个整体,所以首先是进入add方法,进去之后先不运算result,而是输出finally块。finally语句块,这句话先打印到控制台中。打印完后返回来执行try中的return得到43,此时再将结果与和是:拼接,输出和是:43.所以最终输出finally语句块 和是:43。
try{} catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,最终都会执行finally中的代码块;
有异常:
执行catch中的语句和return中的表达式运算,但不返回
执行finally语句中全部代码,
最后执行catch块中return返回。 finally块后的return语句不再执行。
无异常:执行完try再finally再return…
示例:
有异常
public class Test04 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
int i=1/0;
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
++temp;
System.out.println(temp);
}
return temp;
}
}
输出:1132
先执行try中的打印temp=1;有异常,执行catch中的打印,然后执行return中的表达式运算,此时temp=2,并将结果保存在临时栈中,但不返回;执行finally中的语句,temp++,此时temp更新为3,同时打印,但因为finally中的操作不是return语句,所以不会去临时栈中的值,此时临时栈中的值仍然是2。finally中的执行完后,执行catch中的返回,即2。
示例2:无异常
public class Test04 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
++temp;
System.out.println(temp);
}
return ++temp;
}
}
输出:123
try{ return; }catch(){} finally{return;}
执行try块中的代码,和return语句(包括return语句中的表达式运算),但不返回(try中return的表达式运算的结果放在临时栈);
再执行finally块,
执行finally块(和return中的表达式运算,并更新临时栈中的值),从这里返回。
此时finally块的return值,就是代码执行完后的值
try{} catch(){return;}finally{return;}
执行try中的语句块,
有无异常
有异常:程序执行catch块中return语句(包括return语句中的表达式运算,,并将结果保存到临时栈),但不返回;
无异常:直接执行下面的
再执行finally块,
执行finally块return中的表达式运算,并更新临时栈中的值,并从这里返回。
示例:
无异常
public class Test04 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
System.out.println(temp);
return ++temp;
}
}
}
输出:112
有异常
public class Test04 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
int i=1/0;
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
System.out.println(temp);
return ++temp;
}
}
}
输出:1123
try{ return;}catch(){return;} finally{return;}
程序执行try块中return语句(包括return语句中的表达式运算),但不返回;
有异常:
执行catch块中的语句和rreturn语句中的表达式运算,但不返回,结果保存在临时栈;
再执行finally块
执行finally块,有return,更新临时栈的值,并从这里返回。
无异常:
直接执行finally块
执行finally块,有return,更新临时栈的值,并从这里返回。
示例:
无异常
package com.jc;
public class Test04 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
// int i=1/0;
return ++temp;
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
System.out.println(temp);
return ++temp;
}
}
}
输出:123
有异常
package com.jc;
public class Test04 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
int i=1/0;
return ++temp;
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
System.out.println(temp);
return ++temp;
}
}
}
输出:1123
try{ return;}catch(){return;} finally{其他}
程序执行try块中return语句(包括return语句中的表达式运算),但不返回;
有异常:
执行catch块中return语句(包括return语句中的表达式运算),但不返回;
再执行finally块
无异常:
再执行finally块
执行finally块,有return,从这里返回。
示例:
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
return ++temp;
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
++temp;
System.out.println(temp);
}
}
}
输出结果为132
执行顺序为:
输出try里面的初始temp:1;
temp=2;
保存return里面temp的值:2;
执行finally的语句temp:3,输出temp:3;
返回try中的return语句,返回存在里面的temp的值:2;
输出temp:2。
finally代码块在return中间执行。return的值会被放入临时栈,然后执行finally代码块,如果finally中有return,会刷新临时栈的值,方法结束返回临时栈中的值。
小结
任何执行try 或者catch中的return语句之后,在返回之前,如果finally存在的话,都会先执行finally语句,
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
不管有没有异常,finally代码块(包括finally中return语句中的表达式运算)都会在return之前执行
多个return(中的表达式运算)是按顺序执行的,多个return执行了一个之后,后面的return就不会执行。不管return是在try、catch、finally还是之外。
泛型
什么是泛型
型,即“参数化类型”。我们最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
本质:其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
例如:
ArrayList<E> objectName =new ArrayList<>();
E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型。
作用
**第一是泛化。**可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。
**第二是类型安全。**泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。
**第三是消除强制类型转换。**泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
**第四是向后兼容。**支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。
优点
1、类型安全
2、消除强制类型转换
3、更高的运行效率
4、潜在的性能收益
泛型分类 :泛型类、泛型接口、泛型方法
首先我们得先了解泛型字母的含义
字母 | 意义 |
---|---|
E-Element | 在集合中使用,因为集合中存放的是元素 |
T-Type | Java类 |
K - Key | 键 |
v - Value | 值 |
N- Number | 数值类型 |
泛型类
定义格式:
修饰符 class类名<代表泛型的变量>{}
例子:
/**
* @Description: $定义一个含有泛型的类, 模拟Arraylist集合
* 泛型是一个未知的数据类型,当我们不确定什么什么数据类型的时候,可以使用泛型
* 泛型可以接牧任意的数据类型,可以使用Integer , String, student.. .
* 创建对象的时候确定泛型的数据类型
* @Author: dyq
* @Date: $
*/
public class GenericClass<E> {
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
public static void main(String[] args) {
//不写泛型默认为Object
GenericClass gc =new GenericClass();
gc.setName("我是Object");
Object name = gc.getName();
System.out.println(name);
//可以通过创建对象时确定数据类型
//泛型使用Integer类型
GenericClass<Integer> Igc = new GenericClass<>();
Igc.setName(1);
Integer i = Igc.getName();
System.out.println("我是Interger类型:"+i);
//泛型使用String类型
GenericClass<String> sgc = new GenericClass<>();
sgc.setName("花花");
String s = sgc.getName();
System.out.println("我是String类型:"+s);
}
}
结果:
我是Object
我是Interger类型:1
我是String类型:花花
泛型方法
语法:
修饰符<泛型>返回值类型方法名(参数列表(使用泛型)){
方法体;
}
例子:
/**
* @Description: 泛型方法$ 含有泛型的方法,在调用方法的时候确定泛型的数据类型,
* 传递什么类型的参数,泛型是什么类型
* @Author: dyq
* @Date: $2021年2月5日
*/
public class GenericMethod {
//定义一个含有泛型的方法
public <M> void method01(M m){
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <S> void method02(S s){
System.out.println(s);
}
//测试含有泛型的主方法
public static void main(String[] args) {
GenericMethod m = new GenericMethod();
m.method01(10);
m.method01("你好");
m.method01('男');
m.method01(19);
System.out.println("===============");
m.method02("我是方法02");
m.method02("ok!");
m.method02(1);
m.method02('女');
}
}
结果:
10
你好
男
19
===============
我是方法02
ok!
1
女
泛型接口
格式:
修饰符interface接口名<代表泛型的变量>{ }
例子:
/**
* @Description: 泛型接口$
* @Author: dyq
* @Date: 2021年2月5日$
*/
public interface GenericInterface<E> {
public abstract void add(E e);
public abstract E getE();
}
有两种实现方法先创建一个GenericInterfaceImpl01类
/**
* @Description: $含有泛型的接口,第一种方式:定义接口的实现,实现接口,指定接口的泛型
* @Author: dyq
* @Date: $
*/
public class GenericInterfaceImpl01 implements GenericInterface<String>{
@Override
public void add(String s) {
System.out.println(s);
}
@Override
public String getE() {
return null;
}
}
再创建一个GenericInterfaceImpl02实现类
/**
* @Description: $含有泛型的接口第二种使用方式:接口使用什么泛型,
* 实现类就使用什么泛型,类跟着接口走就相当于定义了一个含有泛型的类,
* 创建对象的时候确定泛型的类型
* @Author: dyq
* @Date: $
* public interface List<E>{
* boolean add(E e);
* E get(int index);
* }
* public class ArrayList<E> impLements List<E>{
* }
*/
public class GenericInterfaceImpl02<I> implements GenericInterface<I> {
@Override
public void add(I i) {
System.out.println(i);
}
@Override
public I getE() {
return null;
}
}
最后新建一个测试类
/**
* @Description: $测试含有泛型的接口
* @Author: dyq
* @Date: $
*/
public class GenericInterfaceTest {
public static void main(String[] args) {
//创建GenericInterfaceImpl
GenericInterfaceImpl01 gil =new GenericInterfaceImpl01();
gil.add("哈哈哈~");
//创建GenericInterfaceImpl01 定义输出的数据类型
GenericInterfaceImpl02<Integer> gil2 = new GenericInterfaceImpl02<Integer>();
gil2.add(10);
GenericInterfaceImpl02<Double> gil3 = new GenericInterfaceImpl02<Double>();
gil3.add(8.88);
}
}
结果:
哈哈哈~
10
8.88
泛型的通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符< ?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。此时只能接受数据,不能往该集合中存储数据。
例子:
import java.util.ArrayList;
import java.util.Iterator;
/**
* @Description: $类型通配符一般是使用?代替具体的类型参数
* @Author: dyq
* @Date: $
*/
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
printArray(list);
printArray(list1);
}
/*定义一个方法,能遍历所有类型的ArrayList集合
* 这时候我们知道ArrList集合使用说明数据,可以泛型的通配符?来接收数据类型*/
public static void printArray(ArrayList<?> list){
//使用迭代器遍历集合
Iterator<?> it =list.iterator();
while (it.hasNext()){
//it.next()方法。取出的元素是object,可以接收任意的数据类型
Object o = it.next();
System.out.println(o);
}
}
}
结果:
1
2
a
b
类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
类型通配符下限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如 Object 类型的实例。
import java.util.ArrayList;
import java.util.Collection;
/**
* @Description: $泛型的上线限定:? extends E 代表我使用的泛型只能是E类型/本身
* 泛型的下限定: ? super E 代表我使用的泛型只能是E类型/本身
* @Author: dyq
* @Date: $
*/
public class GenericTest01 {
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2); //报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
/*类与类之间的继承关系
Integer extends Number extents Object
STring extends Object
* */
}
//泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){
}
//泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> collection){}
}
结果:
自动装箱和自动拆箱
支持自动装箱的基本数据类型和其对应的包装类有:
byte <-> Byte
short <-> Short
int <-> Integer
long <-> Long
float <-> Float
double <-> Double
char <-> Character
boolean <-> Boolean
这些基本数据类型都有其对应的包装类,因此可以进行自动装箱和拆箱
对于其他类型,如自定义的类或其他基本数据类型,是不支持自动装箱和拆箱的。如果需要将其转换,需要手动进行装箱和拆箱操作。
对于基本数据类型,由于其并不是一个类,所以无法构建其对象,通过操作对象的方法完成相关操作
于是 Java提供对于基本数据类型的包装类
int => Integer
基本数据类型和包装类:
Byte,Short,Integer,Long,Float,Double,Character,Boolean
byte,short,int,long,float,double,char,boolean
对于包装类存在由 自动装箱和自动拆箱操作
自动拆箱:
对于包装类对象,可以直接将其赋予给对应的基本数据类型
比如:
Integer i2 = Integer.valueOf(str);
int i3 = i2;
int i4 = Integer.valueOf(str);
自动装箱:
当所需要的类型是包装类,但此时可以使用对应的基本数据类型来传递
注意:
对于包装类也可以使用 运算符 来完成相关操作
对于自定义的类或其他引用类型,Java 中并没有像基本数据类型和其对应的包装类那样提供自动装箱和自动拆箱的特性。自动装箱和拆箱仅适用于基本数据类型和其包装类之间的转换。
如果需要在自定义类或其他引用类型之间进行转换,需要手动进行装箱和拆箱的操作。通常,这包括通过构造方法或者提供特定的方法来进行转换。
举个例子,假设有一个自定义的类 MyClass:
public class MyClass {
private int value;
public MyClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
手动进行装箱和拆箱的示例:
// 手动装箱
int intValue = 42;
MyClass myObject = new MyClass(intValue);
// 手动拆箱
int retrievedValue = myObject.getValue();
在这个例子中,我们使用 new MyClass(intValue) 进行手动装箱,将基本数据类型的值封装到 MyClass 对象中。然后,通过调用 getValue() 方法进行手动拆箱,获取封装在对象中的值。
Collection list2 = new ArrayList();
Collection list3 = new ArrayList();
Collection list4 = new ArrayList();
getElement1(list1);
getElement1(list2); //报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
/*类与类之间的继承关系
Integer extends Number extents Object
STring extends Object
* */
}
//泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){
}
//泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> collection){}
}
结果:
## 自动装箱和自动拆箱
支持自动装箱的基本数据类型和其对应的包装类有:
byte <-> Byte
short <-> Short
int <-> Integer
long <-> Long
float <-> Float
double <-> Double
char <-> Character
boolean <-> Boolean
这些基本数据类型都有其对应的包装类,因此可以进行自动装箱和拆箱
对于其他类型,如自定义的类或其他基本数据类型,是不支持自动装箱和拆箱的。如果需要将其转换,需要手动进行装箱和拆箱操作。
对于基本数据类型,由于其并不是一个类,所以无法构建其对象,通过操作对象的方法完成相关操作
于是 Java提供对于基本数据类型的包装类
int => Integer
基本数据类型和包装类:
Byte,Short,Integer,Long,Float,Double,Character,Boolean
byte,short,int,long,float,double,char,boolean
对于包装类存在由 自动装箱和自动拆箱操作
自动拆箱:
对于包装类对象,可以直接将其赋予给对应的基本数据类型
比如:
Integer i2 = Integer.valueOf(str);
int i3 = i2;
int i4 = Integer.valueOf(str);
自动装箱:
当所需要的类型是包装类,但此时可以使用对应的基本数据类型来传递
注意:
对于包装类也可以使用 运算符 来完成相关操作
对于自定义的类或其他引用类型,Java 中并没有像基本数据类型和其对应的包装类那样提供自动装箱和自动拆箱的特性。自动装箱和拆箱仅适用于基本数据类型和其包装类之间的转换。
如果需要在自定义类或其他引用类型之间进行转换,需要手动进行装箱和拆箱的操作。通常,这包括通过构造方法或者提供特定的方法来进行转换。
举个例子,假设有一个自定义的类 MyClass:
public class MyClass {
private int value;
public MyClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
手动进行装箱和拆箱的示例:
// 手动装箱
int intValue = 42;
MyClass myObject = new MyClass(intValue);
// 手动拆箱
int retrievedValue = myObject.getValue();
在这个例子中,我们使用 new MyClass(intValue) 进行手动装箱,将基本数据类型的值封装到 MyClass 对象中。然后,通过调用 getValue() 方法进行手动拆箱,获取封装在对象中的值。