面向对象
1 面向对象思想概述
回想这几天的内容,我们完成一个功能的步骤:先确定要做什么,然后分析怎么做。最后通过代码实现。
我们在每一个具体步骤中都是一个参与者。这个就是面向过程的直接体现。
面向过程的代表语言是C语言。
如果需求变得复杂,每一步都要参与,就会变得比较麻烦。能不能把这些步骤和功能进行封装,封装时根据不同的功能,功能类似的封装在一起。这样的结构就变得清晰,我们需要使用时,直接找到对应封装好的类,直接使用。这就是面向对象思想。
面向对象思想:
- 面向对象是基于面向过程的编程思想。
- 面向过程:强调的是每一个功能的步骤
- 面向对象:强调的是对象,然后由对象去调用功能。
特点:
- 是一种更符合人类思考习惯的思想。
- 可以将复杂的事情变得简单化。
- 将程序员从执行者变成了指挥者
2 类与对象的使用
2.1 类与对象的关系
学习编程是为了模拟现实世界的事物。比如:超市计费系统,银行业务系统,各种管理系统。
我们如何来表示一个现实世界的事物?
- 属性 就是该事物的描述信息
- 行为 就是该事物能够干什么
我们学习的java语言最基本单位是类,所以我们就应该把事物用一个类来体现。
类就是属性和行为的集合。
对象:就是该类事物的具体体现。
2.2 类的定义
现实世界的事物
- 属性 事物的描述 信息
- 行为 事物能够做什么
java中用class描述事物:
- 成员变量(属性): 就是事物的属性
- 和以前定义变量是一样的,只不过位置发生了改变。是在类中,方法外。
- 成员方法:就是事物的行为
- 和以前定义方法一样,只不过把static去掉。
定义类其实就是定义类中的成员变量和成员方法。
定义一个Student类
public class Student {
// 成员变量
// 姓名
String name;
// 年龄
int age;
// 学号
int studentNo;
// 成员方法
// 学习
public void study(){
System.out.println("好好学习天天向上");
}
// 打游戏
public void playGame(){
System.out.println("原身启动~");
}
}
2.3 对象的创建及其使用
- 创建对象格式:
类名 对象名 = new 类名(参数);
- 使用对象访问类中的成员
- 对象名.成员变量
- 对象名.成员方法
public static void main(String[] args) {
// 通过类创建对象
Student student = new Student();
System.out.println(student);// cn.javasm.demo.Student@6a5fc7f7
// 访问对象的成员变量
student.name = "张三丰";
student.age = 120;
student.studentNo = 1024;
System.out.println(student.name);
System.out.println(student.age);
System.out.println(student.studentNo);
// 访问对象的成员方法
student.study();
student.playGame();
}
2.4 类的定义和对象创建的练习
定义一个手机类,创建对象并且画出内存示意图。
定义Phone类
package cn.javasm.demo;
/**
* @author : gfs
* @className: Phone
* @description:
* @date: 2024/3/5 16:22
* @version: 0.1
* @since: jdk11
*/
public class Phone {
// 成员变量(属性)
// 品牌
String brand;
// 价格
int price;
// 颜色
String color;
// 成员方法
// 打电话
public void call(String name){
System.out.println("给" + name + "打电话");
}
// 发短信
public String sendMessage(){
System.out.println("龙年大吉!");
return "发送短信成功!";
}
}
创建对象使用
public static void main(String[] args) {
// 通过类创建对象
Phone phone = new Phone();
// 给成员变量赋值
phone.brand = "apple";
phone.price = 4999;
phone.color = "土豪金";
System.out.println(phone.brand);
System.out.println(phone.price);
System.out.println(phone.color);
// 调用成员方法
phone.call("范冰冰");
String result = phone.sendMessage();
System.out.println(result);
}
引用同一个对象的内存图
3 成员变量和局部变量的区别
- 在类中的位置不同
- 成员变量
- 类中,方法外
- 局部变量
- 方法中或者方法的声明上
- 成员变量
- 内存中的位置不同
- 成员变量
- 堆
- 局部变量
- 栈
- 成员变量
- 生命周期不同
- 成员变量:随着对象的创建而出现,随着对象的消失而消失
- 局部变量:随着方法的调用而存在,出了它所在的大括号就消失了。
- 初始化值不同:
- 成员变量有默认值
- 局部变量,没有默认值。必须先定义赋值后才可以使用
4 封装
private关键字:
- 是一个权限修饰符
- 可以修饰成员(成员变量 成员方法)
- 被private修饰的成员只能在本类中访问。
package cn.javasm.demo;
/**
* @author : gfs
* @className: Student
* @description:
* @date: 2024/3/5 16:12
* @version: 0.1
* @since: jdk11
*/
public class Student {
// 成员变量
// 姓名
private String name;
// 年龄
private int age;
// 学号
private int studentNo;
// 提供方法给成员变量赋值
// 给name赋值
public void setName(String n){
name = n;
}
// 获取name
public String getName(){
return name;
}
public void setAge(int a){
age = a;
}
public int getAge(){
return age;
}
public void setStudentNo(int no){
studentNo = no;
}
public int getStudentNo(){
return studentNo;
}
// 成员方法
// 学习
public void study(){
System.out.println("好好学习天天向上");
}
// 打游戏
public void playGame(){
System.out.println("原身启动~");
}
}
private最常见的应用:
- 把成员变量用private修饰
- 提供对应的setXxx()/getXxx()方法
封装概述:
封装是面向对象三大特征之一。
面向对象三大特征:封装、继承、多态
是面向对象思想对客观世界的模拟。客观世界里成员变量都是隐藏在对象内部的。外部无法直接操作和修改。
封装的原则:
- 将不需要对外提供的内容都隐藏起来
- 对属性隐藏,提供公共方法对其访问。
封装的好处:
- 通过方法来控制成员变量的操作,提高了代码的安全性
- 把代码用方法进行封装,提高代码的复用性
5 this关键字
this关键字代表当前类的对象。
哪个对象调用,this就代表哪一个对象。
this的用法:
this.成员变量 能够获取到当前对象的成员变量
this.成员方法() 调用本类对象的成员方法 this. 一般都省略了
this(参数) 调用本类对应形式的构造方法 this()必须在构造方法体的第一行
package cn.javasm.demo;
/**
* @author : gfs
* @className: Student
* @description:
* @date: 2024/3/5 16:12
* @version: 0.1
* @since: jdk11
*/
public class Student {
// 成员变量
// 姓名
private String name;
// 年龄
private int age;
// 学号
private int studentNo;
// 提供方法给成员变量赋值
// 给name赋值
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getStudentNo() {
return studentNo;
}
public void setStudentNo(int studentNo) {
this.studentNo = studentNo;
}
// 成员方法
// 学习
public void study(){
System.out.println("好好学习天天向上");
}
// 打游戏
public void playGame(){
System.out.println("原身启动~");
}
}
6 构造方法
作用:给对象的数据进行初始化。
格式:
- 方法名和类名相同
- 没有返回值类型,没有void
- 没有具体的返回值
- 构造方法是可以重载的
// 定义构造方法
public Student(){
System.out.println("Student的构造方法被调用了");
}
public Student(String name){
this.name = name;
}
public Student(String name,int age,int studentNo){
this.name = name;
this.age = age;
this.studentNo = studentNo;
}
注意:
- 如果一个类没有提供任何构造方法,系统会默认提供一个无参的构造方法
- 如果提供了构造方法,那么就不会提供默认的无参构造方法了
7 构造代码块
构造代码块就是在类中方法外写一个{}
这个{}就会在每一次调用构造方法之前执行。
如果所有的构造方法都包含某一段代码,那么这段代码可以提取到代码块中。
8 局部代码块(了解)
在方法中用{}包起来的一段代码就是局部代码块
它的作用是限制变量的作用范围和生命周期,从而提高栈内存的利用率
9 课堂练习
- 定义一个类代表矩形(Rectangle),提供求周长(girth)和面积(area)的方法
package cn.javasm.demo;
/**
* @author : gfs
* @className: Rectangle
* @description:
* @date: 2024/3/6 10:27
* @version: 0.1
* @since: jdk11
*/
public class Rectangle {
// 成员变量
private double width;//宽
private double length;//长
public Rectangle(double width, double length) {
if (width > 0 && length > 0){
this.width = width;
this.length = length;
}
}
public double getWidth() {
return width;
}
public double getLength() {
return length;
}
// 成员方法
// 求周长
public double getGirth(){
return 2 * (width + length);
}
// 求面积
public double getArea(){
return width * length;
}
}
10 继承
如果多个类中的属性和方法是重复的,可以将这些重复的属性和方法的代码提取到一个新的类中。利用extends关键字让原来的类和新的类产生联系,这种联系称之为继承。
提取出来的新的类是父类(超类/基类),原来的类是子类(派生类)。子类通过继承父类可以使用父类的一部分属性和方法。
注意:子类通过继承可以继承父类的全部属性和方法,但是只有一部分属性和方法对子类可见。
优点:提高代码的复用性。
所有的类的顶级父类是Object类
Java中允许多层继承,就是A类可以继承B类,B类可以继承C类。这是A类就继承了B和C中可以继承的内容。
注意:Java中是单继承,不能使用多继承。A类可以继承B类,但是A类不能同时继承B类和C类。(一个类只能有一个父类)
多继承和单继承的比较:多继承在代码的复用性上要优于单继承,但是多继承在调用方法时会产生歧义。
11 权限修饰符
本类中 | 子类中 | 同包类中 | 其他类中 | |
---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
默认 | 可以 | 同包子类中 | 可以 | 不可以 |
private | 可以 | 不可以 | 不可以 | 不可以 |
在a包中创建A
public class A {
protected void m(){
System.out.println("m..........");
}
}
在b包中创建B,继承A 再创建C
public class B extends A {
}
public class C {
public static void main(String[] args) {
// m方法是被protected修饰
// 所以可以在本类,子类,同包类中可以使用
// m方法本质上是定义在A类中,所以范围的判断应该是基于A类
// m方法是在本类(A类)中使用吗? 不是
// m方法是在子类(B类)中使用吗? 不是
// m方法是在同包(a包)类中使用吗? 不是
// 所以编译不通过
// B b = new B();
// b.m();
}
}
在b包中创建D,继承A
public class D extends A {
public static void main(String[] args) {
// m方法是被protected修饰
// 所以可以在本类,子类,同包类中可以使用
// m方法本质上是定义在A类中,所以范围的判断应该是基于A类
// m方法是在本类(A类)中使用吗? 不是
// m方法是在子类(B类)中使用吗? 不是
// m方法是在同包(a包)类中使用吗? 不是
// 所以编译不通过
// 子类对象调用protected方法的时候必须在对应的子类中
// B b = new B();
// b.m();
}
}
12 super
super关键字代表父类的对象
用法:
- super(参数) 调用父类的构造方法 并且默认在子类构造方法中就有。
- super.成员变量 调用父类对象的成员变量
- super.成员方法() 调用父类对象的成员方法
在创建子类对象时,会先调用父类的构造方法,再调用子类的构造方法
13 重写
方法的重写就是在父子类中存在了方法签名相同的非静态方法,也称之为方法的覆盖
方法重写之后调用的就是子类中重写之后的方法。
public class demo2 {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.work();
}
}
// 代表职业的类
class Proffession{
public void work(){
System.out.println("在工作中....");
}
}
class Doctor extends Proffession{
@Override
public void work() {
System.out.println("医生治病救人");
}
}
class Teacher extends Proffession{
// 方法的重写/覆盖
public void work(){
System.out.println("老师在辅导学生");
}
}
方法重写需要遵循的原则:
- 方法签名一致(方法名和参数)
- 如果父类中的方法的返回值类型是基本数据类型,那么子类重写方法的返回值类型也要保持完全一致
- 子类重写的方法的权限修饰符的范围要大于等于父类对应方法的权限修饰符范围
class Proffession{
protected int work(){
System.out.println("在工作中....");
return 1;
}
}
class Doctor extends Proffession{
@Override
public int work() {
System.out.println("医生治病救人");
return 2;
}
}
- 如果父类方法的返回值类型是引用数据类型,那么子类重写的方法的返回值类型要么与父类方法返回值类型一致,要么子类方法的返回值类型是父类方法返回值类型的子类
class A{}
class B extends A{}
class C{
public A m(){
return null;
}
}
class D extends C{
public B m(){
return null;
}
}
14 多态
多态是继封装,继承之后,面向对象的第三大特征。
Java作为面向对象语言,同样可以描述一个事物的多种形态。比如说一个Student类继承了Person类,那么Student的对象既是Student,也是Person
多态的体现形式:
- 编译时多态
- 方法的重载
- 运行时多态
- 方法的重写
- 向上转型 父类 对象名 = new 子类();
// 用父类来声明对象 用子类来创建对象
// 在使用向上转型创建对象的时候,编译期间只会检查声明类和创建类之间是否有继承关系
// 编译期间只检查是否有继承关系,不会确定到底是哪一个子类
// 到了运行的时候才会确定具体的子类对象
Proffession proffession = new Teacher();
proffession.work();
// 编译期间不确定子类,所以只能按照父类的规定来执行
// 如果使用了向上转型创建对象,这个对象所能执行的方法由父类来规定
// 父类中不能使用子类的特有方法
// 总结:编译看左边,运行看右边
思考:
- 为什么子类重写的方法的权限修饰符的范围要大于等于父类对应方法的权限修饰符范围?如果没有这样规定,会发生什么?
class A{
public void m(){
}
}
class B extends A{
private void m(){
}
}
A a = new B();// a的对象的声明类是A类,所以a对象能干什么看的是A类,A类告诉a对象,有一个m方法可以使用,并且可以到处使用。
a.m();// a的对象本质上是B类创建的。所以m方法的执行要看B类,B类告诉a对象,m方法只能在B类中使用。 所以编译和运行的结果产生了冲突。声明的时候说可以在任意地方使用,运行的时候说只能在B类中使用
- 为什么父类方法的返回值类型是引用数据类型,那么子类重写的方法的返回值类型要么与父类方法返回值类型一致,要么子类方法的返回值类型是父类方法返回值类型的子类?
class A{}
class B extends A{
public void b(){}
}
class C {
public B m(){}
}
class D extends C{
public A m(){}
}
C c = new D();//c对象的声明类型是C类型,所以C类型告诉c对象,有一个m方法,返回值是B类型的对象
B b = c.m();// c对象的实际类型是D类型,D对象的m方法返回值是A类型的对象,所以b对象实际类型是A类型
b.b();// A类型对象调用b方法,但是A类型根本就没有b方法
15 static
static是java中的一个关键字,是一个修饰符,用来修饰数据、方法、代码块和内部类
15.1 静态变量
static修饰的数据(变量)称之为静态变量/类变量。静态变量是随着类的加载而加载到方法区,在方法区中被赋予了默认值。静态变量是先于对象而存在的,可以通过类名来调用。该类所产生的所有的对象共同的是同一个静态变量。每一个对象存储的都是静态变量的地址。
静态变量是属于类的,而不是属于对象的!
注意:
- 类是加载到方法区中的
- 类是在第一次被使用时才加载到方法区的
- 类只加载一次
静态变量可以用对象名调用,也可以用类名调用。建议使用 类名.静态变量名 来使用
思考:
- 静态变量能否定义到构造方法中? -- 不能
- 静态变量在类加载的时候加载并且初始化,构造方法是在创建对象的时候执行。静态变量是存储在方法区中,构造方法在栈内存中执行,创建的对象在堆内存中。
- 静态变量能够定义在成员方法中? --不能
- 静态变量在类加载的时候加载并且初始化,成员方法在对象创建成功之后才可以被调用。被调用的时候去定义静态变量,方法区中类的加载早就结束了。
15.2 静态方法
static修饰的方法称之为静态方法。静态方法在类加载的时候加载到方法区,并没有执行只是存储在方法区。在方法被调用的时候到栈内存中执行。静态方法本身也是先于对象存在的。所以习惯上也是通过类名来调用静态方法。
前面遇到过的静态方法
Arrays.toString(arr);
System.arraycopy();
Arrays.copyOf();
System.out.println(); 不是
// 静态方法/类方法
public static void sleep(){
System.out.println("人要睡觉");
}
思考:
-
静态方法中是否能使用this/super?
- 静态方法被调用时可能还没有对象,this和super代表当前类对象和父类对象。不能在静态方法中使用。
-
静态方法中能否直接使用本类中的非静态方法?
- 非静态方法需要使用this调用,this不能在静态方法中使用
-
静态方法能够被继承吗? 能
-
静态方法能否被重写? 不能
父子类中可以存在方法签名一致的静态方法,这种情况叫隐藏(hide) 隐藏也适用于重写的5个原则。 (了解)
A a = new B();
a.m();
class A{
public static void m(){
System.out.println("A....m....");
}
}
class B extends A{
public static void m(){
System.out.println("B.....m....");
}
}
如果父子类中存在方法签名一致的方法,要么都是非静态(重写),要么都是静态(隐藏)
练习:统计一个类所创建对象的个数
static int i = 0;
// 构造代码块
{
i++;
}
15.3 静态代码块
用static修饰的{},就是静态代码块。静态代码块只会在类加载的时候执行一次。
只有在类加载时会执行静态代码块一次!
一般用于工具类中。
15.4 执行顺序问题
- 情况一
public class StaticDemo2 {
public static void main(String[] args) {
new SA();
new SA();// A1 A2 A3 A2 A3
}
}
class SA{
// 静态代码块
static {
System.out.println("A1");
}
// 构造代码块
{
System.out.println("A2");
}
public SA(){
System.out.println("A3");
}
}
- 情况二
public class StaticDemo2 {
public static void main(String[] args) {
new SB();// A1 B1 A2 A3 B2 B3
}
}
class SA{
// 静态代码块
static {
System.out.println("A1");
}
// 构造代码块
{
System.out.println("A2");
}
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
先加载父类,然后加载子类。然后调用父类的构造方法,然后是子类的构造方法
- 情况三
public class StaticDemo2 {
public static void main(String[] args) {
new SB();// A1 C B1 A2 A3 B2 B3
}
}
class SA{
// 静态代码块
static {
System.out.println("A1");
}
// 构造代码块
{
System.out.println("A2");
}
// 构造方法
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println("C");
}
}
- 情况四
public class StaticDemo2 {
public static void main(String[] args) {
new SB();// A1 C B1 A2 A3 B2 B3
}
}
class SA{
SD d;
// 静态代码块
static {
System.out.println("A1");
}
// 构造代码块
{
System.out.println("A2");
}
// 构造方法
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println("C");
}
}
class SD extends SC{
public SD(){
System.out.println("D");
}
}
因为SD d只是声明,创建对应对象SA后,默认是null,不执行
- 情况五
public class StaticDemo2 {
public static void main(String[] args) {
new SB();// A1 C B1 A2 C D A3 B2 B3
}
}
class SA{
SD d;
// 静态代码块
static {
System.out.println("A1");
}
// 构造代码块
{
System.out.println("A2");
d = new SD();
}
// 构造方法
public SA(){
System.out.println("A3");
}
}
class SB extends SA{
static SC c = new SC();
static {
System.out.println("B1");
}
{
System.out.println("B2");
}
public SB(){
System.out.println("B3");
}
}
class SC{
public SC(){
System.out.println("C");
}
}
class SD extends SC{
public SD(){
System.out.println("D");
}
}
父类静态-->子类静态-->父类非静态-->子类非静态
15.5 static补充:
Java中类加载的过程。
Java文件经过编译成为class文件,class文件加载到内存中。
class文件加载到内存中的过程分为5步:
- 加载
- 将class文件转换成二进制的字节码
- 校验
- 检查字节码的安全性
- 准备
- 将静态变量放到方法区并且分配空间,然后标记一个默认值,标记的值可能会被舍弃
- 解析
- 常量符号引用的替换
- 初始化
- 执行静态变量的赋值和静态代码块,没有特定的先后顺序,谁在前先执行谁
情况一
/**
* 先将静态变量放入方法区,并且标记默认值0。检查变量是否具有具体的初始化值。
* 有初始化值3,舍弃标记值然后将3作为初始化值设置进去。然后再执行静态代码块,将i的值改为4.
* 所以最终结果是4
*/
static int i = 3;
static {
i = 4;
}
情况二
/**
* 准备阶段:先将静态变量i放入方法区,并且标记默认值0。
* 到了初始化阶段:先执行静态代码块,将i的标记值由0改为4,然后执行i的赋值,发现有初始化值3,舍弃标记值4,
* 将3作为最终值设置进去。
* 所以最终结果是3
*/
static {
i = 4;
}
static int i = 3;
情况三
/**
* 准备阶段:先将静态变量i放入方法区,并且标记默认值0。
* 到了初始化阶段:先执行静态代码块,将i的标记值由0改为4,执行静态变量的赋值。发现没有赋值,把i的标记值4作为最终值。
* 所以最终结果是4
*/
static {
i = 4;
}
static int i;
注意:静态变量再没有执行赋值语句前(标记值状态),不允许直接操作
情况四
// 结果是7.由于i舍弃标记值0,赋值3.再加4变成7
static int i = 3;
static {
i += 4;
}
情况五
// i在静态代码块中是标记值状态,不能直接操作。所以编译失败
static {
i += 4;
}
static int i = 3;
情况六
class SDemo{
/*
* 准备阶段:加载sd标记值是null,i 标记值是0,j标记值是0
* 初始化阶段:先执行sd = new SDemo();执行构造方法,已经跳出了类加载阶段.会将i的标记值变为1,j的标记值变为1
* 执行i = 5,舍弃标记值1,将5赋值给i。j没有初始化值,所以将标记值1作为初始化值赋值给j。
* */
static SDemo sd = new SDemo();
static int i = 5;
static int j;
public SDemo(){
i++;
j++;
}
}
情况七
class SE{
// 静态代码块执行完之后i和j的标记值都是1
// 注意:这里是引用操作,所以可以++
static {
SE.i++;
SE.j++;
}
// i舍弃标记值1,将5赋值给i
static int i = 5;
// 将标记值1赋值给j
static int j;
}
情况八
class SE{
// 准备阶段i和j的标记值都是0
// 初始化阶段:i赋值为5.j是将标记值0赋值给j。
// i++ 变成6
// j++ 变成1
static int i = 5;
// 将标记值1赋值给j
static int j;
static {
SE.i++;
SE.j++;
}
}
static关键字 修饰 变量 方法 代码块 类
静态变量/类变量 使用 类名.静态变量 属于类 而不属于对象 多个对象共享的是静态变量的同一个地址
静态方法:类名.静态方法
静态代码块:类加载时执行
执行顺序:父类静态 子类静态 父类非静态 子类非静态
16 final
final是一个关键字,也是一个修饰符。可以修饰变量、方法、类
16.1 常量
final修饰的变量称之为常量--定义之后不能修改。
- final修饰局部变量
public static void main(String[] args) {
// final int i = 5;
// i = 6;// 编译不通过
final int MAX_AGE;
MAX_AGE = 5;// 可以
System.out.println(MAX_AGE);
final int i = 10;
add(i);// 可以,因为main方法中的i的值没有发生改变
System.out.println(i);
// final修饰的引用数据类型,元素可以变,地址不可以发生改变
final int[] arr = {1,2,3,4,5};
System.out.println(arr);
arr[0] = 10;// 可以
System.out.println(Arrays.toString(arr));
changeRef(arr);//行 如果方法的形参被final修饰 就不行了
System.out.println(arr);
// arr = new int[3];// 不行 new int[3];开辟新的空间 地址已经发生改变
// arr = Arrays.copyOf(arr,arr.length * 2);// 不行 地址已经发生改变
}
public static void changeRef(final int[] arr1){
arr1 = new int[3]; //编译不通过
System.out.println(arr1);
}
- final修饰成员变量
要求成员变量在对象创建成功之前必须赋值。
直接赋值
// final修饰成员变量,必须在对象创建完成之前赋值
final int i = 10;
构造代码块中赋值
final int j;
{
j = 20;
}
构造方法中赋值
final int j;
public Person(){
j = 10; // 可以
}
不能在静态代码块中赋值,静态代码块中不能使用成员变量。
注意:不能同时在构造代码块和构造方法中赋值,这样常量的值会发生改变。
- final修饰静态变量
final修饰的静态变量称之为静态常量,可以直接赋值,也可以在静态代码块中赋值
// 静态常量
// 静态常量要求在类加载完成之前赋值
final static int x = 10;
static final int y;
static {
// 静态代码块中不能使用成员变量
// j = 10;
y = 20;
}
16.2 final修饰方法
final修饰的方法称之为最终方法--不能被重写/隐藏
- 不能被重写
class A{
public final void m(){
System.out.println("m.......");
}
}
class B extends A{
public void m(){
// 编译失败,最终方法不能被重写
}
}
- 不能被隐藏
class A{
public final static void m(){
System.out.println("m.......");
}
}
class B extends A{
public static void m(){
// 编译失败,最终方法不能被重写
}
}
- 可以被继承
class A{
public final void m(){
System.out.println("m.......");
}
}
class B extends A{
}
- 可以被重载
class A{
public final void m(){
System.out.println("m.......");
}
public final void m(int a){
}
}
16.3 final修饰类
final修饰的类称之为最终类,不能被继承。
final class A{
public final void m(){
System.out.println("m.......");
}
public final void m(int a){
}
}
class B extends A{
}
17 abstract
abstract是一个关键字,可以修饰方法和类
- abstract修饰的方法称之为抽象方法,不需要方法体
- 抽象方法必须在抽象类中
- 被abstract修饰的类称之为抽象类
- 抽象类不能被实例化(不能直接new对象)
- 抽象类可以有构造方法
- 抽象类可以有普通方法
- 抽象类中可以有成员变量
- 抽象方法也可以被继承
- 子类在继承抽象类之后要么重写其中的所有的抽象方法,要么子类也成为抽象类
abstract class Employee{
// abstract修饰的方法称之为抽象方法,不需要方法体
public abstract void work();
public abstract void eat();
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 抽象类中可以有构造方法,其存在的意义在于对自身进行初始化供子类使用
public Employee(){}
public void sleep(){
System.out.println("员工要睡觉");
}
}
abstract class Developer extends Employee{
@Override
public void work() {
System.out.println("编代码");
}
}
class SubDev extends Developer{
@Override
public void eat() {
}
}
- 抽象方法可以被重载
- 抽象类不能被final修饰,因为抽象类一定要被继承
- 抽象方法不能被static/final/private修饰
- 因为抽象方法一定要被继承重写的
- private修饰抽象方法,只能在本类中使用,在子类不可见,没法重写
- final修饰的抽象方法不能被重写
- static修饰抽象方法,由于抽象方法没有方法体,所以通过类名调用没有任何意义。
- Employee.work(); 没有方法体,没有实际意义
抽象类由于受到java单继承的限制,所以在实际中用的不多。
18 接口
18.1 接口基本功能
接口是功能的集合,同样是一种引用数据类型。
在jdk1.8之前,接口只描述所应该具备的方法,并没有具体的实现。具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义和实现分离,优化了程序设计。
-
在JDK1.8之前,接口中所有的方法都是抽象方法。
-
一个接口可以有多个实现类
-
接口不能被实例化,可以使用向上转型
定义接口和实现类
public interface Proffession {
// 工作
public abstract void work();
// 薪水
public abstract double salary();
}
// 类和接口之间用implements关键字来产生关系
//类称之为接口的实现类。要实现接口中的抽象方法
public class Teacher implements Proffession{
@Override
public void work() {
System.out.println("老师要教书育人");
}
@Override
public double salary() {
return 5000.0;
}
}
使用向上转型
public static void main(String[] args) {
// 接口不能实例化
// 接口 变量名 = new 接口的实现类();
Proffession proffession = new Teacher();
proffession.work();
double salary = proffession.salary();
System.out.println(salary);
}
接口特点:
- 接口中的变量都会被 public static final 修饰
- 接口中定义的抽象方法可以省略public abstract,系统在编译时会自动添加这两个关键字
- 接口不能直接实例化,需要使用向上转型
- 接口没有构造方法,但是接口编译之后也会产生class文件,但是接口不是类
- 一个接口可以有多个实现类
- 一个实现类也可以同时实现多个接口
- 接口和接口之间是多继承
补充:
javap 反编译指令
javap MyInter.class
18.2 类和接口的强转问题
public class TestDemo {
public static void main(String[] args) {
// 向上转型
// 在Java中,类和类之间支持单继承
// 所以可以形成一颗继承结构树
// 所以比较容易确定两个类之间是否有继承关系
A a = new B1();
// 把父类赋值给子类
// 在编译期间会检查对象的声明类型和要强转的类型
// 在编译期间a对象的声明类型是A类
// a对象要强转的类型是B1,发现B1和A之间有继承关系,所以编译通过
// 到了运行的时候,发现a的实际类型是B1,要强转的也是B1.类型匹配,可以转换
// B1 b1 = (B1)a;
// a对象的声明类型是A类
// 要强转的类型是B2,发现B2继承A,所以编译通过
// 到了运行阶段,发现a对象的实际类型是B1,要强转的类型是B2.类型不匹配
// 抛出ClassCastException:类型不匹配异常
// B2 b2 = (B2)a;
// a对象的声明类型A类,要强转的是C类,发现C和A之间没有继承关系,所以编译失败
// C c = (C)a;
// 在编译期间,任何一个接口都可以强转
// 在Java中,接口之间是多继承以及类和接口是多实现,所以形成一个网状结构
// 在网状结构中,不容易确定两个节点之间是否有继承或者实现关系
// 所以为了提高编译效率,Java在编译期间放弃接口类型的检查。
D d = (D)a;
}
}
class A{}
class B1 extends A{}
class B2 extends A{}
class C{}
interface D{}
18.3 JDK1.8后的接口特性
18.3.1 接口中的实体方法
在jdk1.8之前,接口中全部都是抽象方法。jdk1.8之后可以有静态方法,默认方法,私有方法,私有静态方法。
package cn.javasm.demo2;
/**
* @author : gfs
* @className: MyInter
* @description:
* @date: 2024/3/8 15:56
* @version: 0.1
* @since: jdk11
*/
@FunctionalInterface// 表示这个接口是一个函数式接口。接口中只有一个抽象方法。
public interface MyInter {
void add();
// 默认方法 --jdk8特性 --可以调用私有的实体方法
default boolean defaultMethod1(){
System.out.println("默认方法");
this.privateMethod();
return true;
}
// 静态方法 --jdk8特性 --可以调用私有静态方法
static void method2(){
System.out.println("static方法");
MyInter.privateMethod2();
}
// 私有方法 --jdk9特性
private void privateMethod(){
System.out.println("私有的实体方法");
}
// 私有静态方法
private static void privateMethod2(){
System.out.println("私有的静态方法");
}
}
使用:
public static void main(String[] args) {
MyInter myInter = new MyInterImpl();
// 调用接口的默认方法
myInter.defaultMethod1();
// 调用接口的静态方法
MyInter.method2();
}
18.3.2 lambda表达式
lambda表达式是jdk1.8的特性,要使用这个表达式,要求接口中只能允许一个抽象方法。
可以使用@FunctionalInterface来标记这个接口是要给函数式接口(只有一个抽象方法)。
lambda表达式本质上是在替换匿名内部类的语法,让程序员编写代码更加简洁。
lambda表达式表示面向函数式编程,面向接口中的那一个唯一的抽象方法编程。
lambda表达式的基本语法
(参数) -> {方法体}
lambda表达式的缺点:可读性不高
18.3.2.1 无参无返回值的抽象方法
@FunctionalInterface //函数式接口
public interface MyInterface {
// 无参无返回值
void hello();
}
public class TestDemo {
public static void main(String[] args) {
// 使用匿名内部类
// MyInterface myInterface = new MyInterface() {
// @Override
// public void hello() {
// System.out.println("hello....");
// }
// };
//
// myInterface.hello();
// 使用lambda表达式替换匿名内部类 --面向函数式编程
// MyInterface myInterface = () -> {
// System.out.println("hello...");
// };
// myInterface.hello();
// 方法体中如果只有一行代码,可以省略{}
MyInterface myInterface = ()-> System.out.println("hello");
myInterface.hello();
MyInterface myInterface1 = new MyInterImpl();
// demo(myInterface1);
// demo(new MyInterface() {
// @Override
// public void hello() {
//
// }
// });
MyInterface myInterface2 = demo(()-> System.out.println("hello..."));
myInterface2.hello();
}
public static MyInterface demo(MyInterface myInterface){
// return new MyInterface() {
// @Override
// public void hello() {
// System.out.println("hello...");
// }
// };
return ()-> System.out.println("aaa");
}
}
18.3.2.2 有参无返回值的抽象方法
- 参数只有一个
@FunctionalInterface //函数式接口
public interface MyInterface {
// 无参无返回值
// void hello();
// 有参无返回值 只有一个参数
void compareStr(String str);
}
// 有参无返回值
// 匿名内部类
// MyInterface myInterface = new MyInterface() {
// @Override
// public void compareStr(String str) {
// System.out.println("hello".equals(str));
// }
// };
// myInterface.compareStr("hello");
// lambda表达式
// 格式: (参数)-> {方法体};
// MyInterface myInterface = (String str) -> {
// System.out.println("hello".equals(str));
// };
// myInterface.compareStr("h");
// 方法体中只有一行代码,可以省略{}
// MyInterface myInterface = (String str) -> System.out.println("hello".equals(str));
// 如果形参中只有一个参数,可以省略数据类型和()
MyInterface myInterface = a -> System.out.println("hello".equals(a));
myInterface.compareStr("hello");
- 参数有多个
@FunctionalInterface //函数式接口
public interface MyInterface {
// 无参无返回值
// void hello();
// 有参无返回值 只有一个参数
// void compareStr(String str);
// 有多个参数
void min(int num1,int num2);
}
public static void main(String[] args) {
// 有参无返回值
// 有多个参数
// MyInterface myInterface = new MyInterface() {
// @Override
// public void min(int num1, int num2) {
// System.out.println("最小值是" + (num1 > num2 ? num2 : num1));
// }
// };
// lambda表达式
MyInterface myInterface = (int num1,int num2) -> {
System.out.println("最小值是" + (num1 > num2 ? num2 : num1));
};
// 如果形参数量大于1个,可以省略数据类型
// 形参名称是可以随意起名
MyInterface myInterface1 = (a,b) -> System.out.println("最小值是" + (a > b ? b : a));
myInterface1.min(10,20);
}
18.3.2.3 有参有返回值的抽象方法
@FunctionalInterface //函数式接口
public interface MyInterface {
// 无参无返回值
// void hello();
// 有参无返回值 只有一个参数
// void compareStr(String str);
// 有多个参数
// void min(int num1,int num2);
// 有参有返回值
// int add(int num1,int num2);
// boolean compareStr(String str);
// 定义方法,获取对象中的名称属性的内容
Object getPropertyValue(Student student);
}
private static void demo6() {
// 匿名内部类
// MyInterface myInterface = new MyInterface() {
// @Override
// public Object getPropertyValue(Student student) {
// return student.getName();
// }
// };
Student student = new Student();
student.setAge(18);
student.setName("冲田");
// lambda表达式
// MyInterface myInterface = t -> t.getName();
MyInterface myInterface = Student::getName;
Object value = myInterface.getPropertyValue(student);
System.out.println(value);
}
private static void demo5() {
// 匿名内部类
// MyInterface myInterface = new MyInterface() {
// @Override
// public boolean compareStr(String str) {
// return "hello".equals(str);
// }
// };
// lambda表达式
// MyInterface myInterface = str -> "hello".equals(str);
// 对象::方法名
// MyInterface myInterface = "hello"::equals;
//
// System.out.println(myInterface.compareStr("hello"));
}
private static void demo4() {
// 有参有返回值
// 匿名内部类
// MyInterface myInterface = new MyInterface() {
// @Override
// public int add(int num1, int num2) {
// return Integer.sum(num1,num2);
// }
// };
// lambda表达式
// MyInterface myInterface = (int num1,int num2) -> {
// return num1 + num2;
// };
// 参数可以省略数据类型
// MyInterface myInterface = (a,b) -> {
// return a + b;
// };
// 如果方法体中有返回值类型,并且语句只有一行,可以省略 {} 和 return ;
// MyInterface myInterface = (a,b) -> a + b;
// System.out.println(myInterface.add(10, 20));
// 如果重写的方法的形参数量和类型以及返回值类型 与 要调用的方法的形参数量和类型和返回值类型一样,可以使用下面的写法
// 数据类型::方法名;
// sum是静态方法
// MyInterface myInterface = Integer::sum;
// System.out.println(myInterface.add(10, 20));
}
标签:void,System,面向对象,static,println,public,out
From: https://www.cnblogs.com/460759461-zeze/p/18208853