目录
1. 概念
- 对象主要指现实生活中客观存在的实体,在Java语言中对象体现为内存空间中的一块存储区域。
- 类简单来就是“分类” ,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性 的成员变量以及描述行为的成员方法。
- 类是用于构建对象的模板,对象的数据结构由定义它的类来决定。
2. 类的定义
-
class 类名 { 类体; } • 注意:通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写
-
成员变量
class 类名 { 数据类型 成员变量名 = 初始值; } 注意:当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首 字母大写 。
-
成员变量的初始值
-
成员方法
-
class 类名 { 返回值类型 成员方法名(形参列表) { 成员方法体; } } 当成员方法名由多个单词组成时,要求从第二个单词起每个单词的首字母大写。
-
返回值类型主要指返回值的数据类型,可以是基本数据类型,也可以是引用数据类型,当该方法不需要返回任何数据内容时,则返回值类型写void即可。
-
可变长参数
-
写法:返回值类型 方法名(参数的类型… 参数名)
-
一个方法的形参列表中最多只能声明一个可变长形参,并且需要放到参 数列表的末尾,参数的个数可以改变,但是数据类型无法改变,把可变长参数当成以为数组使用即可
void showArgument (Sttring...args) { for (int i = 0; i < args.length; i++) { System.out.println("第" + (i + 1) + "个参数"+ args[i]); } }
-
-
方法的调用,引用变量名.成员方法名(实参列表);
-
参数传递
- 基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常 不会影响到实参变量的数值,因为两个变量有各自独立的内存空间;
- 引用数据类型的变量作为方法的参数传递时,形参变量指向内容的改变 会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间;
- 当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后 再改变指定的内容,则通常不会影响到实参变量指向内容的改变,因为 两个变量指向不同的内存空间。
-
-
对象的创建
new 类名(); 注意: a.当一个类定义完毕后,可以使用new关键字来创建该类的对象,这个 过程叫做类的实例化。 b.创建对象的本质就是在内存空间的堆区申请一块存储区域, 用于存放 该对象独有特征信息。
-
引用
-
基本概念
- 使用引用数据类型定义的变量叫做引用型变量,简称为"引用" 。
- 引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问。
-
语法格式
- 类名 引用变量名;
- 引用变量名.成员变量名;
-
成员方法
1. 构造方法
class 类名 {
类名(形参列表) {
构造方法体;
}
}
构造方法名与类名完全相同并且没有返回值类型,连void都不许有
- 默认构造方法
- 当一个类中没有定义任何构造方法时,编译器会自动添加一个无参空构造构造方法,叫做默认/缺省构造方法,如:Person(){}
- 若类中出现了构造方法,则编译器不再提供任何形式的构造方法
- 构造方法的作用
- 使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作
2. 方法重载
- 若方法名称相同,参数列表不同,(返回值最好相同)这样的方法之间构成重载关系 (Overload)。
- 方法重载的主要形式体现在:**参数的个数不同、参数的类型不同、参数的顺序不同,与返回值类型和形参变量名无关,**但建议返回值类型最好相同。
- 判断方法能否构成重载的核心:调用方法时能否加以区分。
- 重载的意义:方法重载的实际意义在于调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能。
3. this关键字
- 基本概念
- 若在构造方法中出现了this关键字,则代表当前正在构造的对象。
- 若在成员方法中出现了this关键字,则代表当前正在调用的对象。
- this关键字本质上就是当前类类型的引用变量。
- 使用方法
- 当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前缀,明确要求该变量是成员变量
- this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以作为方法的返回值(return this;返回的是调用的对象)
- 在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法 (了解)
4. static 关键字
- 概念:使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关。
- 使用:
-
static关键字修饰的成员可以使用引用.的方式访问,但推荐类名.的方式
-
在非静态成员方法中既能访问非静态的成员又能访问静态的成员。 (成员:成员变量 + 成员方法, 静态成员被所有对象共享)
-
在静态成员方法中只能访问静态成员不能访问非静态成员。 (成员:成员变量 + 成员方法, 因为此时可能还没有创建对象) ,static方法中不能有this关键字。
-
在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用 static关键字修饰。(不能滥用static关键字)
-
5. 构造方法和静态代码块
-
当需要在执行构造方法之前做一些准备工作时,则将准备工作的相关代码写在构造快中即可,比如:对成员变量进行的统一初始化操作。
-
构造块:在类体中直接使用{}括起来的代码块。
- 每创建一个对象都会执行一次构造块。
-
静态代码块:使用static关键字修饰的构造块。
- 静态代码块随着类加载时执行一次。
-
面试题:
public class SuperTest { { System.out.println("SuperTest类中的构造块!"); // (2) c } static { System.out.println("SuperTest类中的静态代码块!"); // (1) a } public SuperTest() { System.out.println("SuperTest类中的构造方法体!"); // (3) d } public static void main(String[] args) { // 使用无参方式构造对象 SuperTest st = new SuperTest(); } }
public class SubSuperTest extends SuperTest { { System.out.println("==========SubSuperTest类中的构造块!"); // (2) e } static { System.out.println("==========SubSuperTest类中的静态代码块!"); // (1) b } public SubSuperTest() { //System.out.println("==========SubSuperTest类中的构造方法体!"); // (3) f out.println("==========SubSuperTest类中的构造方法体!"); } public static void main(String[] args) { // 使用无参方式构造子类的对象 SubSuperTest sst = new SubSuperTest(); }
- 先执行父类的静态代码块,再执行子类的静态代码块。
- 执行父类的构造块,执行父类的构造方法体。
- 执行子类的构造块,执行子类的构造方法体。
6. final关键字
-
概念:final本意为"最终的、不可改变的",可以修饰类、成员方法以及成员变量。
-
final关键字修饰类体现在该类不能被继承。 - 主要用于防止滥用继承,如:java.lang.String类等。
-
final关键字修饰成员方法体现在该方法不能被重写但可以被继承。 - 主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。
-
final关键字修饰成员变量体现在该变量必须初始化且不能改变。 - 主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等。初始化有三种方式,构造块初始化,构造方法初始化,显式初始化。
-
常量:在以后的开发中很少单独使用final关键字来修饰成员变量,通常使用 public static final关键字共同修饰成员变量来表达常量的含义,常量的命名规范要求是所有字母都要大写,不同的单词之间采用下划线连。
public static final double PI = 3.14;
-
7. 递推与递归
//实现n的阶层
//递推
int show1 (int n) {
int num = 1;
for (int i = 1; i <= n; i++) {
num *= i;
}
return num;
}
//递归
int show2 (int n) {
if (i == n) return 1;
return n * show (n - 1);
}
- 注意:使用递归必须有递归的规律以及退出条件。
面向对象的特性
1. 封装
- 概念:通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无论是编译阶段还是运行阶段都不会报错或者给出提示,为了避免上述错误的发生,就需要对成员变量进行密封包装处理,来隐藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装。
- 实现流程:
- 私有化成员变量,使用private关键字修饰。
- 提供公有的get和set方法,并在方法体中进行合理值的判断。
- 在构造方法中调用set方法进行合理值的判断。
- JavaBean
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
权限修饰符
- public修饰的成员可以在任意位置使用。
- private修饰的成员只能在本类内部使用。
- 通常情况下,成员方法都使用public关键字修饰,成员变量都使用private 关键字修饰
2. 继承
-
概念:当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成 一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需要编写自己独有特征和行为的机制,叫做继承。
-
写法:在Java语言中使用extends(扩展)关键字来表示继承关系。
- 如: public class Worker extends Person{} ,表示Worker类继承自Person类。 其中Person类叫做超类、父类、基类。 其中Worker类叫做派生类、子类、孩子类。
- 使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件
-
特点:
-
子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承,只是不能直接访问。
-
无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法, 来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代码super()的效果。
-
使用继承必须满足逻辑关系:子类 is a 父类,也就是不能滥用继承。
-
Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类。
/* 编程实现Person类的封装 */ public class Person { // 1.私有化成员变量,使用private关键字修饰 private String name; private int age; //private boolean gender; // 性别 // 3.在构造方法中调用set方法进行合理值的判断 public Person() { System.out.println("Person()"); } public Person(String name, int age) { System.out.println("Person(String, int)"); setName(name); setAge(age); } // 2.提供公有的get和set方法并在方法体中进行合理值的判断 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if(age > 0 && age < 150) { this.age = age; } else { System.out.println("年龄不合理哦!!!"); } } // 自定义成员方法实现特征的打印 public void show() { System.out.println("我是" + getName() + ",今年" + getAge() + "岁了!"); } // 自定义成员方法描述吃饭的行为 public void eat(String food) { System.out.println(food + "真好吃!"); } // 自定义成员方法描述娱乐的行为 public void play(String game) { System.out.println(game + "真好玩!"); } }
/* 自定义Worker类继承自Person类 */ public class Worker extends Person { private int salary; public Worker() { super(); // 表示调用父类的无参构造方法,若没有加则编译器自动添加 System.out.println("Worker()"); } public Worker(String name, int age, int salary) { super(name, age); // 表示调用父类的有参构造方法 System.out.println("Worker(String, int, int)"); //setName(name); //setAge(age); setSalary(salary); } public int getSalary() { return salary; } public void setSalary(int salary) { if(salary >= 2200) { this.salary = salary; } else { System.out.println("薪水不合理哦!!!"); } } // 自定义成员方法描述工作的行为 public void work() { System.out.println("今天的砖头有点烫手..."); } // 自定义show方法覆盖从父类中继承的版本 @Override // 标注/注解,用于说明下面的方法是对父类方法的重写,若没有构成重写则编译报错 public void show() { super.show(); // 表示调用父类的show方法 System.out.println("我的薪水是:" + getSalary()); } }
-
方法重写
- 概念:从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写(Override)。
- 规则:
- 如果父类不是抽象类,子类可以选择是否重写父类方法
- 要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许返回子类类型。
- 要求方法的访问权限不能变小,可以相同或者变大。
- 要求方法不能抛出更大的异常(异常机制)。
3. 多态
-
概念:多态主要指同一种事物表现出来的多种形态。
-
语法格式:
父类类型 引用变量名 = new 子类类型(); • 如: Shape sr = new Rect(); sr.show();
-
多态的使用场合
//1. 通过方法的参数传递形成多态; public static void draw(Shape s){ s.show(); } draw(new Rect(1, 2, 3, 4)); //2. 在方法体中直接使用多态的语法格式 Account acc = new FixedAccount(); //3.通过方法的返回值类型形成多态 Calender getInstance(){ return new GregorianCalendar(zone, aLocale); }
-
特点:
-
当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法。
-
当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法。父类对象可以强制转化为子类对象调用子类的独有方法,注意要用instanceof判断。
-
对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)。
-
对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本。
//Shape.java public class Shape { private int x; private int y; public Shape() { } public Shape(int x, int y) { setX(x); setY(y); } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public void show() { System.out.println("横坐标:" + getX() + ",纵坐标:" + getY()); } // 自定义静态方法 public static void test() { System.out.println("Shape类中的静态方法!"); } }
//Rect.java public class Rect extends Shape { private int len; private int wid; public Rect() { } public Rect(int x, int y, int len, int wid) { super(x, y); setLen(len); setWid(wid); } public int getLen() { return len; } public void setLen(int len) { if(len > 0) { this.len = len; } else { System.out.println("长度不合理哦!!!"); } } public int getWid() { return wid; } public void setWid(int wid) { if (wid > 0) { this.wid = wid; } else { System.out.println("宽度不合理哦!!!"); } } @Override public void show() { super.show(); System.out.println("长度是:" + getLen() + ",宽度是:" + getWid()); } // 自定义静态方法 //@Override Error: 历史原因、不是真正意义上的重写 public static void test() { System.out.println("---Rect类中的静态方法!"); } }
//Circle.java public class Circle extends Shape { private int ir; public Circle() { } public Circle(int x, int y, int ir) { super(x, y); setIr(ir); } public int getIr() { return ir; } public void setIr(int ir) { if (ir > 0) { this.ir = ir; } else { System.out.println("半径不合理哦!!!"); } } @Override public void show() { super.show(); System.out.println("半径是:" + getIr()); } }
//ShapeRectTest.java public class ShapeRectTest { public static void main(String[] args) { // 1.声明Shape类型的引用指向Shape类型的对象并打印特征 Shape s1 = new Shape(1, 2); // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法 // 当Rect类中重写show方法后,下面调用Shape类中的show方法 s1.show(); // 1 2 // 使用ctrl+d快捷键可以复制当前行 System.out.println("------------------------------------"); // 2.声明Rect类型的引用指向Rect类型的对象并打印特征 Rect r1 = new Rect(3, 4, 5, 6); // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法 // 当Rect类中重写show方法后,下面调用Rect类中的show方法 r1.show(); // 3 4 5 6 // 使用alt+shift+上下方向键 可以移动代码 System.out.println("------------------------------------"); // 3.声明Shape类型的引用指向Rect类型的对象并打印特征 // 相当于从Rect类型到Shape类型的转换 也就是子类到父类的转换 小到大的转换 自动类型转换 Shape sr = new Rect(7, 8, 9, 10); // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法 // 当Rect类中重写show方法后,下面的代码在编译阶段调用Shape类的方法,在运行阶段调用Rect类中的show方法 sr.show(); // 7 8 9 10 System.out.println("------------------------------------"); // 4.测试Shape类型的引用能否直接调用父类和子类独有的方法呢??? int ia = sr.getX(); System.out.println("获取到的横坐标是:" + ia); // 7 //sr.getLen(); error Shape类中找不到getLen方法,也就是还在Shape类中查找 // 调用静态方法 sr.test(); // 提示:不建议使用引用.的方式访问 Shape.test(); // 推荐使用类名.的方式访问 System.out.println("------------------------------------"); // 5.使用父类类型的引用调用子类独有方法的方式 // 相当于从Shape类型到Rect类型的转换,也就是父类到子类的转换 大到小的转换 强制类型转换 int ib = ((Rect) sr).getLen(); System.out.println("获取到的长度是:" + ib); // 9 // 希望将Shape类型转换为String类型 强制类型转换要求必须拥有父子类关系 //String str1 = (String)sr; Error // 希望将Shape类型强制转换为Circle类型,下面没有报错 //Circle c1 = (Circle)sr; // 编译ok,但运行阶段发生 ClassCastException类型转换异常 // 在强制类型转换之前应该使用instanceof进行类型的判断 // 判断sr指向堆区内存中的对象是否为Circle类型,若是则返回true,否则返回false if(sr instanceof Circle) { System.out.println("可以放心地转换了!"); Circle c1 = (Circle)sr; } else { System.out.println("强转有风险,操作需谨慎!"); } } }
-
-
引用数据类型转化
- 引用数据类型之间的转换方式有两种:自动类型转换 和 强制类型转换。
- 自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也叫做向上转型。
- 强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也叫做向下转型或显式类型转换。
- 引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
- 若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运行阶段发生类型转换异常。
- 为了避免上述错误的发生,应该在强转之前进行判断,格式如下: if(引用变量 instanceof 数据类型) 判断引用变量指向的对象是否为后面的数据类型。
-
多态的实际意义
- 多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的效果。
-
多态的两种使用情况:
- 多态的使用场合一:通过参数传递形成了多态。参数传父类,调用子类重写过的方法。
- 多态的使用场合二: 直接在方法体中使用抽象类(接口)的引用指向子类类型的对象
4. 抽象类和抽象方法
-
抽象方法:抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就 是没有方法体。
- 具体格式如下: 访问权限 abstract 返回值类型 方法名(形参列表); public abstract void cry();
- 抽象方法不能用private(不能继承),final(不能重写),static(提升到类层级,违背了不能创建对象,调用抽象方法)修饰
-
抽象类:抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是 不能创建对象。
-
抽象类和抽象方法的关系:
- 抽象类中可以有成员变量、构造方法、成员方法;
- 抽象类中可以没有抽象方法,也可以有抽象方法;
- 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有 抽象方法并且使用abstract关键字修饰的类。
-
抽象方法的意义:
-
抽象类的实际意义不在于创建对象而在于被继承。
-
当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。
-
-
开发经验:
- 在以后的开发中推荐使用多态的格式,此时父类类型引用直接调用的所有方法一定是父类中拥有的方法,若以后更换子类时,只需要将new关键字后面的子类类型修改而其它地方无需改变就可以立即生效,从而提高了代码的可维护性和可扩展型。
- 该方式的缺点就是:父类引用不能直接调用子类独有的方法,若调用则需要强制类型转换。
5. 接口
-
接口就是一种比抽象类还抽象的类,体现在所有方法都为抽象方法。 接口没有构造方法。
-
定义类的关键字是class,而定义接口的关键字是interface。
-
接口可以弥补多继承的不足。
6. 抽象类与接口的关系和区别
-
抽象类和接口之间的关系
-
抽象类和接口之间的区别
- 定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
- 继承抽象类的关键字是extends,而实现接口的关键字是implements。
- 继承抽象类支持单继承,而实现接口支持多实现。
- 抽象类中可以有构造方法,而接口中不可以有构造方法。
- 抽象类中可以有成员变量,而接口中只可以有常量。
- 抽象类中可以有成员方法,而接口中只可以有抽象方法。
- 抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需 要重写(Java8以前的版本)。
- 从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非抽象方法需要使用default关键字修饰。
- 从Java9开始增加新特性,接口中允许出现私有方法。
public interface InterfaceTest { /*public static final */int CNT = 1; // 里面只能有常量 //private void show(){} // 从Java9开始允许接口中出现私有方法 /*public abstract */void show(); // 里面只能有抽象方法(新特性除外),注释中的关键字可以省略,但建议写上 }