1. 类和对象
面向对象编程
面向对象编程指先以面向对象的思想进行分析,然后使用面向对象的编程语言 进行表达的过程
精髓 : 封装、继承、多态
类和对象
- 概念
- 对象主要指现实生活中客观存在的实体,在Java语言中对象体现为内存空 间中的一块存储区域。
- 类简单来就是“分类”,是对具有相同特征和行为的多个对象共性的抽象描 述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性 的成员变量以及描述行为的成员方法。
- 类是用于构建对象的模板,对象的数据结构由定义它的类来决定。
- 定义及传建
class 类名 {
// 成员变量
数据类型 成员变量名 [= 初始值];
// 成员方法
返回值 方法名(形参列表) {
方法体;
}
}
// 类的实例化
new 类名(); // 本质是在内存空间申请一块存储区域
引用
a.使用引用数据类型定义的变量叫做引用型变量,简称为"引用"。
b.引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问。
类名 引用变量名 [= new 类名()];
引用变量名.成员变量名/成员方法;
成员变量初始化
成员变量的类型 | 默认初始值 |
---|---|
数值类型 byte、short、int、long、float、double、char | 0 |
boolean型 | false |
引用类型 | null |
传参
- 参数分为形参和实参,定义方法时的参数叫形参,调用方法时传递的参 数叫实参。
- 调用方法时采用值传递把实参传递给形参,方法内部其实是在使用形参。
- 所谓值传递就是当参数是基本类型时,传递参数的值,比如传递i=10, 真实传参时,把10赋值给了形参。当参数是对象时,传递的是对象的值, 也就是把对象的地址赋值给形参。
void show(){} // 无参
String show(String s){ return s} // 引用类型
void show(String s,int i){} // 参数列表
int show(int i) {return i} // 基本数据类型
-
基本数据类型传参与引用类型传参数
- 基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常 不会影响到实参变量的数值,因为两个变量有各自独立的内存空间;
- 引用数据类型的变量作为方法的参数传递时,形参变量指向内容的改变 会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间
- 当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后 再改变指定的内容,则通常不会影响到实参变量指向内容的改变,因为 两个变量指向不同的内存空间。
-
可变参数列表
void showArgument(int num, String... args) {
System.out.println("num = " + num);
for(int i = 0; i < args.length; i++) {
System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
}
2. 方法
构造方法
构造方法名与类名完全相同并且没有返回值类型,连void都不许有
public class Boy {
String name;
Boy() {
System.out.println("无参数构造方法");
}
Boy(String s) {
name = s;
System.out.println("有参数构造方法");
}
}
重载
若方法名称相同,参数列表不同,这样的方法之间构成重载关系 (Overload)。
/**
* 个数不同
* 参数类型不同
* 参数顺序不同
* 于返回值与形参变量名无关
*/
void show()
int show(String s)
void show(String s,int i)
String show(int i,String s)
-
实际意义
方法重载的实际意义在于调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能。
this关键字
- 若在构造方法中出现了this关键字,则代表当前正在构造的对象。
- 若在成员方法中出现了this关键字,则代表当前正在调用的对象。
- this关键字本质上就是当前类类型的引用变量。
- 使用方式
- 当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就 近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前 缀,明确要求该变量是成员变量(重中之重)。
- this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以 作为方法的返回值(重点)。
- 在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法 (了解)。
public class Boy {
String name;
Boy() {
this("无名");
System.out.println("无参数构造方法");
}
Boy(String name) {
name = ""; // 调用参数
this.name = name; // this.name 使用成员变量
System.out.println("有参数构造方法");
}
Body get() {
return this;// 返回
}
}
3. 封装
- 通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无 论是编译阶段还是运行阶段都不会报错或者给出提示,此时与现实生活 不符。
- 为了避免上述错误的发生,就需要对成员变量进行密封包装处理,来隐 藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装。
-
实现流程
- 私有化成员变量,使用private关键字
- 提供公有的get和set方法,并在方法体中进行合理值的判断。
- 在构造方法中调用set方法进行合理值的判断
public class Student {
// 私有化
// 只能在类内部使用
private int id;
// 有参数构造
public Student(int id) {
this.setId(id);
}
// 公有的set get
public int getId() {
return id;
}
public void setId(int id) {
if (id > 0) {
this.id = id;
}else {
System.out.println("学号不合理");
}
}
}
-
JavaBean的概念
-
类是公共的
-
有一个无参的公共的构造器
-
有属性,且有对应的get、set方法
-
4. Static
在非静态成员方法中既能访问非静态的成员又能访问静态的成员。
(成员:成员变量 + 成员方法, 静态成员被所有对象共享)
在静态成员方法中只能访问静态成员不能访问非静态成员。 (成员:成员变量 + 成员方法, 因为此时可能还没有创建对象)
在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用 static关键字修饰。(不能滥用static关键字)
public class StaticTest {
private int cnt = 1; // 隶属于对象层级,也就是每个对象都拥有独立的一份
private static int snt = 2; // 隶属于类层级,也就是所有对象都共享同一份
// 自定义非静态的成员方法 需要使用引用.的方式访问
public void show() {
System.out.println("cnt = " + this.cnt); // 1
System.out.println("snt = " + this.snt); // 2 静态成员被所有对象共享,this关键字可以省略
}
// 自定义静态的成员方法 推荐使用类名.的方式访问
public static void test() {
// StaticTest st = new StaticTest();
//System.out.println("cnt = " + cnt); // 1 静态成员方法中没有this关键字,因为是可以通过类名.方式调用的
System.out.println("snt = " + snt); // 2
}
}
构造快和静态代码快
构造块:在类体中直接使用{}括起来的代码块。
每创建一个对象都会执行一次构造块。
静态代码块:使用static关键字修饰的构造块。
静态代码块随着类加载时执行一次。
public class BlockTest {
// 当需要在执行构造方法体之前做一些准备工作时,则将准备工作的相关代码写在构造块中即可,比如:对成员变量进行的统一初始化操作
{
System.out.println("构造块!"); // (2)
}
// 静态代码块会随着类的加载而准备就绪,会先于构造块执行
// 当需要在执行代码块之前随着类的加载做一些准备工作时,则编写代码到静态代码块中,比如:加载数据库的驱动包等
static {
System.out.println("#####################静态代码块!"); // (1)
}
// 自定义构造方法
public BlockTest() {
System.out.println("====构造方法体!"); // (3)
}
}
单例模式
在某些特殊场合中,一个类对外提供且只提供一个对象时,这样的类叫 做单例类,而设计单例的流程和思想叫做单例设计模式。
/**
1. 私有化构造方法,使用private关
2. 声明本类类型的引用指向本类类型的对象,并使用private static关键字共 同修饰。
3. 提供公有的get方法负责将对象返回出去,并使用public static关键字共同 修饰。
*/
// 饿汉式
public class Singleton {
private static Singleton s = new Singleton();
private Singleton() {
}
public static Singleton getSingleton() {
return Singleton.s;
}
}
// 懒汉式
public class Singleton {
private static Singleton s = null;
private Singleton() {
}
public static Singleton getSingleton() {
if (s == null) {
s = new Singleton();
}
return Singleton.s;
}
}
5. 继承
当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成 一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需 要编写自己独有特征和行为的机制,叫做继承。
// 超类、父类、基类
public class Person {}
// 派生类、子类、孩子类
public class Teacher extends Person {}
// 使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件
-
特点
- 子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承 只是不能直接访问。
- 无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法
- 来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代 码super()的效果。
- 使用继承必须满足逻辑关系:子类 is a 父类,也就是不能滥用继承。
- Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父 类,但一个父类可以有多个子类。
-
方法重写(Override)
- 要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许 返回子类类型。
- 要求方法的访问权限不能变小,可以相同或者变大。
- 要求方法不能抛出更大的异常(异常机制)
/**
* 先执行父类的静态代码块,再执行子类的静态代码块。
* 执行父类的构造块,执行父类的构造方法体。
* 执行子类的构造块,执行子类的构造方法体
*/
public class SuperTest {
{
System.out.println("SuperTest类中的构造块!");
}
static {
System.out.println("SuperTest类中的静态代码块!");
}
public SuperTest() {
System.out.println("SuperTest类中的构造方法体!");
}
}
public class SubSuperTest extends SuperTest {
{
System.out.println("==========SubSuperTest类中的构造块!");
}
static {
System.out.println("==========SubSuperTest类中的静态代码块!");
}
public SubSuperTest() {
System.out.println("==========SubSuperTest类中的构造方法体!");
}
public static void main(String[] args) {
SubSuperTest sst = new SubSuperTest();
}
}
/**
SuperTest类中的静态代码块!
==========SubSuperTest类中的静态代码块!
SuperTest类中的构造块!
SuperTest类中的构造方法体!
==========SubSuperTest类中的构造块!
==========SubSuperTest类中的构造方法体!
*/
6. 访问控制
修饰符 | 本类 | 同一个包 中的类 | 子类 | 其他类 |
---|---|---|---|---|
public | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
protected | 可以访问 | 可以访问 | 可以访问 | 不能访问 |
默认 | 可以访问 | 可以访问 | 不能访问 | 不能访问 |
private | 可以访问 | 不能访问 | 不能访问 | 不能访问 |
7. final
final本意为"最终的、不可改变的",可以修饰类、成员方法以及成员变量。
// 该类不能被继承。- 主要用于防止滥用继承
public final class FinalClass {
// final关键字修饰成员变量体现在该变量必须初始化且不能改变
// 常量
public static final double PI = 3.14;
// final关键字修饰成员方法体现在该方法不能被重写但可以被继承。
public final void show() {
System.out.println("FinalClass类中的show方法!");
}
}
8.多态
多态主要指同一种事物表现出来的多种形态。
-
意义
多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的效果。
public class Shape {
public void show() {
System.out.println("Shape类");
}
}
public class Rect extends Shape {
@Override
public void show() {
super.show();
System.out.println("Rect");
}
}
// 多态
public static void draw(Shape s) {
s.show();
}
public static void main(String[] args) {
ShapeTest.draw(new Shape());
ShapeTest.draw(new Circle());
}
-
特点
- 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调 用父类独有的方法。
- 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接 调用子类独有的方法。
- 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段 调用子类重写的版本(动态绑定)。
- 对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本。
-
转换
// 自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也 叫做向上转型。 // 强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也 叫做向下转型或显式类型转换。 // 必须是父子类之间 否则编译报错。 // 若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运 行阶段发生类型转换异常。 // 若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运 行阶段发生类型转换异常。 if(sr instanceof Circle) { } else { }
9. 抽象类
抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是 不能创建对象。
-
意义
抽象类的实际意义不在于创建对象而在于被继承。
• 当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也 就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。
// 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有 抽象方法并且使用abstract关键字修饰的类。 public abstract class AbstractTest { // 抽象类中可以有成员变量、构造方法、成员方法 private int cnt; public AbstractTest() { } public AbstractTest(int cnt) { setCnt(cnt); } public int getCnt() { return cnt; } public void setCnt(int cnt) { this.cnt = cnt; } // 抽象类中可以没有抽象方法,也可以有抽象方法; // 自定义抽象方法 public abstract void show(); }
9. 接口
接口就是一种比抽象类还抽象的类,体现在所有方法都为抽象方法。
定义类的关键字是class,而定义接口的关键字是interface
public interface Runner {
// 自定义抽象方法描述奔跑的行为
public abstract void run();
}
名称 | 关键字 | 关系 |
---|---|---|
类和类之间的关系 | 使用extends关键字表达继承关系 | 支持单继承 |
类和接口之间的关系 | 使用implements关键字表达实现关系 | 支持多实现 |
接口和接口之间的关系 | 使用extends关键字表达继承关系 | 支持多继承 |
- 抽象类和接口的区别(笔试题)
- 定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
- 继承抽象类的关键字是extends,而实现接口的关键字是implements。
- 继承抽象类支持单继承,而实现接口支持多实现。
- 抽象类中可以有构造方法,而接口中不可以有构造方法。
- 抽象类中可以有成员变量,而接口中只可以有常量。
- 抽象类中可以有成员方法,而接口中只可以有抽象方法。
- 抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需 要重写(Java8以前的版本)。
- 从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非 抽象方法需要使用default关键字修饰。
- 从Java9开始增加新特性,接口中允许出现私有方法。
// private 和 abstract 关键字不能共同修饰一个方法
private abstract double getLixi();
// final 和 abstract 关键字不能共同修饰一个方法
public final abstract double getLixi();
// static 和 abstract 关键字不能共同修饰一个方法
public static abstract double getLixi();
10. 特殊类
内部类
- 普通内部类
public class NormalOuter {
public int cnt = 1;
public class Inner {
private int ia = 2;
private int cnt = 2;
private Inner() {
System.out.println("普通内部类");
}
public void show() {
System.out.println("外部类中的成员变量="+cnt);
System.out.println("内部类中的成员变量="+ia);
}
private void show2(int cnt) {
System.out.println("形参="+cnt);
System.out.println("外部类中的成员变量="+NormalOuter.this.cnt);
System.out.println("内部类中的成员变量="+this.cnt);
}
}
public static void main(String[] args) {
NormalOuter no = new NormalOuter();
NormalOuter.Inner ni = no.new Inner();
}
}
- 静态内部类
public class StaticOuter {
private int cnt = 1; // 隶属于对象层级
private static int snt = 2; // 隶属于类层级
public void show() {
System.out.println("外部类的show方法就是这里!");
}
/**
* 定义静态内部类 有static关键字修饰隶属于类层级
*/
public static class StaticInner {
private int ia = 3;
private static int snt = 4;
public StaticInner() {
System.out.println("静态内部类的构造方法哦!");
}
public void show() {
System.out.println("ia = " + ia); // 3
System.out.println("外部类中的snt = " + snt); // 2
//System.out.println("外部类的cnt = " + cnt); // Error:静态上下文中不能访问非静态的成员,因此此时可能还没有创建对象
}
public void show2(int snt) { // 就近原则
System.out.println("snt = " + snt); // 5
System.out.println("内部类中的成员snt = " + StaticInner.snt); // 4
System.out.println("外部类中的成员snt = " + StaticOuter.snt); // 2
//StaticOuter.show();
new StaticOuter().show();
}
}
}
- 局部内部类
public class AreaOuter {
private int cnt = 1;
public void show() {
// 定义一个局部变量进行测试,从Java8开始默认理解为final关键字修饰的变量
// 虽然可以省略final关键字,但建议还是加上
final int ic = 4;
// 定义局部内部类,只在当前方法体的内部好使 拷贝一份
class AreaInner {
private int ia = 2;
public AreaInner() {
System.out.println("局部内部类的构造方法!");
}
public void test() {
int ib = 3;
System.out.println("ia = " + ia); // 2
System.out.println("cnt = " + cnt); // 1
//ic = 5; Error
System.out.println("ic = " + ic); // 4
}
}
// 声明局部内部类的引用指向局部内部类的对象
AreaInner ai = new AreaInner();
ai.test();
}
}
-
匿名类
public interface AnonymousInterface { // 自定义抽象方法 public abstract void show(); } public class AnonymousInterfaceTest { // 接口类型的引用指向实现类型的对象,形成了多态 public static void test(AnonymousInterface ai) { // 编译阶段调用父类版本,运行调用实现类重写的版本 ai.show(); } public static void main(String[] args) { //AnonymousInterfaceTest.test(new AnonymousInterface()); // Error:接口不能实例化 // 使用匿名内部类的语法格式来得到接口类型的引用,格式为:接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 }; AnonymousInterface ait = new AnonymousInterface() { @Override public void show() { System.out.println("匿名内部类就是这么玩的,虽然你很抽象!"); } }; // 从Java8开始提出新特性lamda表达式可以简化上述代码,格式为:(参数列表) -> {方法体} AnonymousInterface ait2 = () -> System.out.println("lamda表达式原来是如此简单!"); AnonymousInterfaceTest.test(ait2); } }
枚举
public enum DirectionEnum {
UP("向上"),
DOWN("向下"),
LEFT("向左"),
RIGHT("向右");
// public static final DirectionEnum UP = new DirectionEnum("向上");
// public static final DirectionEnum DOWN = new DirectionEnum("向下");
// public static final DirectionEnum LEFT = new DirectionEnum("向左");
// public static final DirectionEnum RIGHT = new DirectionEnum("向右");
private final String desc;
private DirectionEnum(String desc) {
this.desc = desc;
}
}
static T[] values() | 返回当前枚举类中的所有对象 |
---|---|
String toString() | 返回当前枚举类对象的名称 |
int ordinal() | 获取枚举对象在枚举类中的索引位置 |
static T valueOf(String str) | 将参数指定的字符串名转为当前枚举类的对象 |
int compareTo(E o) | 比较两个枚举对象在定义时的顺序 |
- 枚举实现接口
public interface DirectionInterface {
// 自定义抽象方法
public abstract void show();
}
public enum DirectionEnum implements DirectionInterface {
// 2.声明本类类型的引用指向本类类型的对象
// 匿名内部类的语法格式:接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
UP("向上") {
@Override
public void show() {
System.out.println("贪吃蛇向上移动了一下!");
}
}, DOWN("向下") {
@Override
public void show() {
System.out.println("贪吃蛇向下移动了一下!");
}
}, LEFT("向左") {
@Override
public void show() {
System.out.println("左移了一下!");
}
}, RIGHT("向右") {
@Override
public void show() {
System.out.println("右移了一下!");
}
};
private final String desc; // 用于描述方向字符串的成员变量
// 1.私有化构造方法,此时该构造方法只能在本类的内部使用
private DirectionEnum(String desc) { this.desc = desc; }
// 整个枚举类型只重写一次,所有对象调用同一个
/*@Override
public void show() {
System.out.println("现在可以实现接口中抽象方法的重写了!");
}*/
}
注解
自定义注解自动继承java.lang.annotation.Annotation接口
// 通过@注解名称的方式可以修饰包、类、 成员方法 法、参数、局部变量的声明等。
// 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方 法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该 成员变量的类型。
// 如果注解只有一个参数成员,建议使用参数名为value,而 种基本数据类型、String类型、Class类型、enum类型及Annotation类型
public @interface MyAnnotation {
// 声明一个String类型的成员变量,名字为value 类型有要求
public String value() default "123";
// 声明一个String类型的成员变量,名字为value
public String value2();
}
-
元注解
- @Retention
@Retention(RetentionPolicy.SOURCE) // 表示下面的注解在源代码中有效 @Retention(RetentionPolicy.CLASS) // 表示下面的注解在字节码文件中有效,默认方式 @Retention(RetentionPolicy.RUNTIME) // 表示下面的注解在运行时有效
- @Documented
@Documented // 表示下面的注解信息可以被javadoc工具提取到API文档中,很少使用
- @Target
// 表示下面的注解可以用于类型、构造方法、成员变量、成员方法、参数 的修饰 @Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
- @Inherited
@Inherited // 表示下面的注解所修饰的类中的注解使用可以被子类继承
- @Repeatable
自然可重复的含义
- 预注解
@author 标明开发该类模块的作者,多个作者之间使用,分割 @version 标明该类模块的版本 @see 参考转向,也就是相关主题 @since 从哪个版本开始增加的 @param 对方法中某参数的说明,如果没有参数就不能写 @return 对方法返回值的说明,如果方法的返回值类型是void就 不能写 @exception 对方法可能抛出的异常进行说明 @Override 限定重写父类方法, 该注解只能用于方法 @Deprecated 用于表示所修饰的元素(类, 方法等)已过时 @SuppressWarnings 抑制编译器警告
注意点
- 当一个对象被当作参数传递到一个方法后,此方法可改变 这个对象的属性,并可返回变化后的结果,那么这里到底是值传 递还是引用传递?
是值传递。 Java 语言的方法调用只支持参数的值传递。 当一个对象实例作为一个 参数被传递到方法中时, 参数的值就是对该对象的引用。 对象的属性可以在被调 用过程中被改变, 但对对象引用的改变是不会影响到调用者的。
- 重载(Overload)和重写(Override)的区别
重载发生在一个类中, 同名的方法如果有不同 的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。 重载对返回类型没有特殊的要求。;
重写发生在子类与父类之间, 重写要求子类被重写方法与父类被重写方法有相同的返 回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常。