目录
面向对象基础
思想:
- 面向对象编程:拿或者找东西过来编程解决问题
- 面向对象:把现实世界中的食物全部看成一个一个的对象来解决问题的。(万物皆对象)
- 开发一个一个的对象,把数据交给对象,再调用对象的方法来完成对数据的处理
- 好处:符合人类思维习惯,编程更简单,更直观
类、对象:
- 类(设计图):相同事物共同特征的描述
- 对象:对象是类的具体实例
- 在Java中必须先定义类,才能创建对应的具体对象
定义类的格式:
修饰符 class 类名{
1.成员变量(代表属性,一般是名词)
2.成员方法(代表行为,一般是名词)
3.构造器(用来给对象的成员变量初始化,并返回对象的地址)
4.代码块(待补充...)
5.内部类(待补充...)
}
创建对象:
- 格式:类名 对象名 = new 构造器(形参列表);
- 对象调用:
-
- 对象名.成员变量;
- 对象名.成员方法;
类与对象的一些注意事项:
- 类名建议用英文单词,首字母大写,满足驼峰模式,且要有意义。
- 类中定义的变量也称为成员变量(对象的属性),类中定义的方法也成为成员方法(对象的行为)
- 成员变量本身存在默认值,在定义成员变量时一般来说不需要赋初始值。
- 一个代码文件中,可以写多个class类,但只能一个用public修饰,且public修饰的类名必须成为代码文件名
- .如果某个对象没有一个变量引用它,则该对象无法被操作,该对象会成为所谓的垃圾对象。而Java存在自动垃圾回收机制,会自动清除掉垃圾对象
this关键字
- this就是一个变量,可以用在方法中,代表了当前对象的地址,可以访问当前对象的成员变量和成员方法
- this主要用来解决:变量名称冲突问题等等
构造器(构造方法)
- 作用:初始化一个类的对象,并返回这个对象的地址
- 格式:修饰符 类名(形参){ ... }
- 构造器的分类:
-
- 无参数构造器:初始化一个类的对象,并返回这个对象的地址,里面的数据都是默认值
- 有参数构造器:初始化一个类的对象,并返回这个对象的地址,并且可以同时为对象赋值进行初始化
- 构造器的调用:类名 对象名 = new 构造器();
- 注意:
-
- 任何类写出来JVM都默认配置一个无参数构造器
- 若自己在类中以及定义了一个有参数构造器了,那么JVM就不再默认配置无参数构造器,需要自行再配置一个。
封装(面向对象三大特征之一)
面向对象三大特征:封装、继承、多态(继承、多态内容待补)
- 封装的设计规范:合理隐藏,合理暴露
一般来说:
-
- 对象的成员变量(对象的属性)都private,只能本类访问,然后设计相应的setter、getter方法来赋值和取值
- 而对象的成员方法则根据实际情况进行隐藏(private)或暴露(public)。
实体类(JavaBean)
- 实体类(JavaBean)是一种特殊形式的类
- 作用:创建对象,仅仅用来封装保存数据,实现存储数据(JavaBean实体类)与数据处理业务(另一个类)的分离
- 标准bean的书写要求:
-
- 实体类中的成员变量都要私有化(private),并且要对外提供相应的getter,setter方法
- 类中必须要有一个公共的无参的构造器
局部变量和成员变量的区别
- 类中位置不同,成员变量(类中,方法外),局部变量(常见于方法中)
int a = 20; //main方法中 局部变量
- 初始化值不同,成员变量(有默认值,不需要初始化),局部变量(没有默认值,使用之前必须完成赋值)
- 内存位置不同成员变量(堆内存中),局部变量(栈内存中)
-
- 成员变量属于new出来的对象的,而对象在堆内存,故成员变量也在堆内存
- 局部变量在方法中,方法在栈内存中运行,故局部变量也在栈内存
- 作用域不同,成员变量(整个对象),局部变量(在所属的大括号中)
- 生命周期不同:成员变量(与对象同生共死),局部变量(方法调用而生,方法结束而亡)
面向对象高级(一)
static
- 叫静态,可以修饰成员变量,成员方法
- 成员变量按照有无static修饰,分为两种:
- 类变量:有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享
-
- 访问:类名.类变量(推荐) 对象.类变量(不推荐)
- 实例变量(对象的变量):无static修饰,属于每个对象
-
- 访问:对象.实例变量
类变量的应用场景:
- 在开发中,如果某个数据只需要一份,且希望能够被共享(访问,修改),则该数据可以定义成类变量来记住
成员变量的应用场景:
- 每个对象都要有一份,数据各不同
ps:
a.访问自己类中的类变量可以省略类名不写
b.一般类变量用public修饰,方便别人共享
2.成员方法的分类:
- 类方法:有static修饰的成员方法,属于类。
-
- 访问:类名.类方法(推荐) 对象名.类方法(不推荐)
- 实例方法:无static修饰的成员方法,属于对象。
-
- 访问:对象.实例方法
类方法的应用场景:
- 做工具类(用来完成一个功能,给开发人员共同使用的)
-
- 好处:提高了代码复用性,调用方便,提高了开发效率
- 为什么工具类中的方法要用类方法,而不用实例方法?
-
-
- 实例方法需要创建对象来调用,此时对象只是为了调用方法,对象占内存,这样会浪费内存
- 类方法,直接用类名调用即可,调用方便,也能节省内存。
-
-
- ps:工具类没有创建对象的需求,建议将工具类的构造器进行私有,以防止创造对象占用内存。
使用类方法、实例方法时的几点注意事项:
- 类方法中可以直接访问类成员,不可以直接访问实例成员。
- 实例方法中既可以直接访问类成员,也可以直接访问实例成员
- 实例方法中可以出现this关键字,类方法中不可以出现this关键字
代码块
代码块概述:
- 代码块时类的5大成分之一(成员变量,构造器,方法,代码块,内部类)
代码块种类:
- 静态代码块:
-
- 格式:static{ }
- 特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
- 作用:完成类的初始化,例如:对类变量的初始化赋值
- 实例代码块:
-
- 格式:{ }
- 特点:每次创建对象时,执行实例代码块,并在构造器前执行
- 作用:和构造器一样,都是用来完成对象的初始化,例如:对实例变量进行初始化赋值
设计模式 - 单例设计模式
设计模式概念:
- 具体问题的最优解法
单例设计模式
- 确保一个类只有一个对象
写法:
- 把类的构造器私有化
- 定义一个类变量记住类的这一个特定对象
- 定义一个类方法返回对象
类只会加载一次,类变量也只会随之加载一次,即只会创建一次对象,以确保一个类只有一个对象
单例设计模式的实现方式:
- 懒汉式单例:拿对象时,对象早就创建好了
/**
* 单例设计模式 - 饿汉式单例
*/
public class A {
//单例类
//2.定义一个类变量记住类的一个对象
private static A a = new A();
//1.把类的构造器私有,外部类访问不到
private A(){
}
//3.定义一个类方法,返回类变量所记住的该类的对象
public static A getObject(){
return a;
}
}
- 饿汉式单例:拿对象时,才开始创建对象
public class B {
//懒汉式单例
//2.创造一个类变量使它指向null
private static B b = null;
//1.将构造器private私有化
private B(){
}
//3.提供一个类方法,保证返回的是同一个对象
public static B getInstance(){
if(b == null){
System.out.println("第一次创建对象");
b = new B();
}
return b;
}
}
继承
继承定义:
- Java提供了一个extends关键字,可以让一个类与另一个类建立起父子关系
继承的特点:
- 子类能继承父类的非私有成员(成员变量,成员方法)
继承后对象的创建:
- 子类的对象是由父类,子类共同完成的
继承好处:
- 减少了重复代码的编写,提高了代码的复用性
继承的相关事项:
- 权限修饰符:
修饰符 | 在本类中 | 同一个包下的其他类 | 任意包下的子类里 | 任意包下的任意类 |
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
- 单继承、Object类:
-
- Java是单继承的,Java中的类不支持多继承,但是支持多层继承
- object类是Java所有类的祖宗类
- 方法重写(申明不变,重新实现):
-
- 当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写
- 重写小技巧:使用Override注解,它可以指定Java编译器,检查我们方法重写的格式是否正确
- 子类重写父类方法时,访问权限必须大于或者等于父类该方法的访问权限(public > protected > 缺省)
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
- 私有方法,静态方法不能被重写,会报错
- 子类中访问其他成员的特点:
-
- 就近原则:先子类局部范围找,再子类成员范围找,最后父类成员范围找
- 重名:调用子类成员(this.),调用父类成员(super.)
- 子类构造器的特点:
-
- 子类的全部构造器,都会先调用父类的构造器,再执行自己
-
-
- 默认情况下,子类全部构造器的第一行代码都是super()(写不写哦都有),它会调用父类的无参构造器
- 如果父类没有无参数构造器,则默认报错。此时我们必须在子类构造器的第一行手写super(...),指定去调用父类的有参数构造器
-
- 注意事项的小结:
-
- 补充知识:this(...)调用兄弟构造器
- this(...)和super(...)使用时的注意事项:
-
-
- this(...)、super(...)都只能放在构造器的第一行,因此,有了this(...)就不能写super(...)了,反之亦然
-
面向对象高级(二)
多态
多态定义:
- 多态是在继承/实现情况下的一种现象,表现为对象多态、行为多态
多态的具体代码实现:
public class Test {
public static void main(String[] args) {
//目标:认识多态,对象多态,行为多态
//1.对象多态
People p1 = new People();
p1.run();//行为多态。识别技巧:编译看左边,运行看右边
System.out.println(p1.name);//注意:对于变量,编译看左边,运行也看左边
People p2 = new Student();
p2.run();
System.out.println(p2.name);
People p3 = new Teacher();
p3.run();
System.out.println(p3.name);
}
}
运行结果:
人可以跑~
父类People
学生跑的贼快~
父类People
老师跑的气喘吁吁
父类People
多态的前提:
- 有继承/实现关系;存在父类引用子类对象(对象多态);存在方法重写(行为多态)
多态的注意事项:
- 多态是对象、行为(方法)的多态,Java中的属性(成员变量)不谈多态(编译和运行一律看变量的类型)
多态的好处和问题以及解决方法(类型转换):
- 好处1:可以实现解耦合(组件互相独立,可以随时切换),右边对象可以随时切换,后续业务随机改变
People p1 = new People();// new Student(); new Teacher();...
p1.run();
- 好处2:定义方法时,可以使用父类类型的变量作为形参,可以接收一切子类对象,扩展性更强
- 问题:无法直接调用子类的独有功能
-
- 解决方法:类型转换
-
-
- 自动类型转换:父类 变量名 = new 子类();
-
People p = new Student();
-
-
- 强制类型转换:子类 变量名 = (子类) 父类变量
-
Student s = (Student)p;
-
-
- 强制类型转换的一个注意事项:
-
-
-
-
- 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译不会报错
- 但运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常
-
-
People p = new Student();
Teacher t = (Teacher) p;//java.lang.ClassCastException
-
-
- 强转前,Java建议:使用instanceof关键字,判断当前对象的真实类型,再进行转换,进而调用子类的独有方法
-
p instanceof Student //true
综合代码:
public class Test {
public static void main(String[] args) {
//目标:理解多态的好处
//好处1:可以实现解耦合(组件互相独立,可以随时切换),右边对象可以随时切换,后续业务随机改变
People p1 = new People();// new Student(); new Teacher();...
p1.run();
//好处2:定义方法时,可以使用父类类型的变量作为形参,可以接收一切子类对象,扩展性更强
Student s1 = new Student();
go(s1);
Teacher t1 = new Teacher();
go(t1);
//问题:无法直接调用子类的独有功能
People p = new Student();
// p.study();//多态下存在问题,无法直接调用子类的独有功能
//强制类型转换
Student s = (Student) p;
s.study();
//强制类型转换可能存在问题,编译阶段有继承或者实现关系就可以进行强制转换,但是运行时可能出现类型转换异常
Teacher t = (Teacher) p;
t.teach();//运行时,发现对象的真实类型与强转后的类型不同,报错ClassCastException
//此时可以使用 instance 关键字来判断对象的真实类型,再进行强转,从而调用子类的独有功能
}
public static void go(People p){
p.run();
if(p instanceof Student){
// ((Student) p).study();
Student s = (Student) p;
s.study();
} else if (p instanceof Teacher){
// ((Teacher) p).teach();
Teacher t = (Teacher) p;
t.teach();
}
}
}
final
- final关键字是最终的意思,可以修饰(类、方法、变量)
-
- 修饰类:该类被称为最终类,特点是不能被继承了
- 修饰方法:该方法被称为最终方法,特点是不能被重写了
- 修饰变量:该变量仅能被赋值一次
final修饰变量的注意
- final修饰基本数据类型的变量,变量存储的数据不能改变
- final修饰引用数据类型的变量,变量存储的地址不能被改变,但地址所指向的对象的内容是可以被改变的
常量
- 使用了static final修饰的成员变量就被称为常量
- 作用:通常用于记录系统的配置信息
public class Constant{
public static final String SCHOOL_NAME = "黑马";
}
==注意:==常量名的命名规范:建议使用大写英文单词,多个单词使用下划线连接
使用常量记录系统配置信息的优势、执行原理
- 代码可读性更好,可维护性也更好
- 程序编译后,常量会被“宏替换”“出现常量的敌方全部被替换成其记住的字面量,这样可以保证使用常量和直接使用字面量的性能是一样的
抽象类
- abstract关键字,可以修饰(类、成员方法) --> 抽象类、抽象方法
-
- 抽象方法:必须abstract修饰,只有方法签名,不能有方法体
修饰符 abstract class 类名{
修饰符 abstract 返回值类型 方法名(形参列表);
}
抽象类的注意事项、特点
- 抽象类中不一定有抽象方法、但有抽象方法的类一定是抽象类
- 类该有的成员(成员变量、方法、构造器)抽象类都可以有
- ==抽象类最主要的特点:==抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
- 一个类继承抽象类,必须要重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
抽象类的应用场景和好处
- 父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现,我们抽出这样的抽象类,就是为了更好的支持多态
tips:
- abstract不可和static | private连用,因为后两者修饰的方法不支持重写,而abstract抽象方法在子类继承时需要将其重写
设计模式 - 模板方法设计模式
- 解决方法中存在重复代码的问题
写法
- 1.定义一个抽象类
- 2.在里面定义2个方法
-
- 一个是模板方法:把相同代码放里面去
- 一个是抽象方法:具体实现交给子类完成
- 最后将二者关系匹配联系起来
/**
* 设计 模板方法设计模式
* 1.定义一个模板方法出来
*/
public abstract class People {
//模板方法,所有子类直接套用即可,不能更改
public final void write(){
System.out.println("\t\t\t《作文题目》");
System.out.println("\t\t第一段大同小异");
//2.模板方法并不清楚正文部分应该具体怎么写,但是它知道子类肯定要写,故此处调用一个抽象方法
System.out.println(writeMain());
System.out.println("\t\t最后一段大同小异");
}
//3.设计一个抽象方法,具体实现交给子类子类,调用它时写正文
public abstract String writeMain();
}
tips:
- 建议使用final关键字修饰模板方法
-
- 模板方法是给对象直接使用的,不能被子类重写
- 一旦子类重写了模板方法,模板方法就失效了
接口
- interface关键字定义接口
- 结构:
public interface 接口名{
//成员变量(JVM默认成常量)
//成员方法(JVM默认成抽象方法)
//JDK8后出现的3种方法:
//默认方法:(public)default 返回值类型 方法名(){...}
// 注意:只能使用接口的实现类的对象调用
//私有方法:private 返回值类型 方法名(){...}
// 注意:只能接口内部调用使用
//静态方法:(public)static 返回值类型 方法名(){...}
// 注意:只能使用接口名.方法名来访问,
优点:增强了接口的能力,更便于项目的扩展和维护
}
tips:
- 接口中不能有构造器,代码块啥的
- 接口大多数成员都是默认public修饰的,因为接口要供别人(类,接口)使用
- 接口不能创建对象(因为接口是抽象的),接口是被类用来==实现(implements)==的,实现接口的类称为实现类
修饰符 class 实现类 implements 接口1,接口2...{
}
- 一个类可以实现多个接口(接口可以理解成干爹),实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类
接口的好处
- 弥补了类单继承的不足,一个类同时可以实现多个接口,从而去扩展自己的功能
- 让程序可以面向接口编程,更加灵活方便的切换各种业务实现
接口的多继承
interface B{}
interface C{}
interface A extends B,C{}//相当于把B、C接口合并了,便于实现类去实现
接口其他注意事项(了解)
public class Test2 {
public static void main(String[] args) {
new Zi().run();
}
}
//1.一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承
interface I{
void test();
}
interface J{
String test();
}
//interface K extends I,J{}
//2.一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现
//class E implements I,J{}
//3.一个类继承了父类,又同时实现了接口,父类和接口中有同名的默认方法,实现类会优先用父类的
class Fu{
public void run(){
System.out.println("父类的run方法~");
}
}
interface IT{
default void run(){
System.out.println("接口的run方法");
}
}
class Zi extends Fu implements IT{
}
//4.一个类实现多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写这个方法即可
interface A1{
default void test(){
System.out.println("test");
}
}
interface A2{
default void test(){
System.out.println("test");
}
}
class A3 implements A1,A2{
@Override
public void test() {
System.out.println("需要重写同名的默认方法");
}
}
面向对象高级(三)
内部类
- 内部类是类中的五大成分之一(成员变量,方法,构造器,内部类,代码块),如果一个类定义在另一个类的内部,这个类就是内部类
- 当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类
public class Car{
//内部类
public class Engine{
}
}
四大内部类
1.成员内部类
就是类中的一个普通成员,类似前面学过的普通的成员变量,成员方法,需要外部类的对象去调用
public class Outer{
//成员内部类
public class Inner{
}
}
创建内部类的对象的格式:
外部类名.内部类名 对象名 = new 外部类名().new内部类名();
Outer.Inner in = new Outer().new Inner();
成员内部类中访问其他成员的特点
和前面学过的实例方法一样,成员内部类的实例方法中,同样可以直接访问外部类的实例成员和静态成员
可以在成员内部类的重写方法中,拿到当前外部类对象,格式是:外部类名.this
2.静态内部类
有static修饰的内部类,属于外部类自己持有
直接由外部类名调用
public class Outer{
//静态内部类
public static class Inner{
}
}
创建对象的格式:
外部类名.内部类名 对象名 = new 外部类.内部类();
Outer.Inner in = new Outer.Inner();
静态内部类中访问外部类成员的特点
可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员
3.局部内部类
4.匿名内部类
一种特殊的局部内部类;所谓匿名:指的是程序员不需要为这个类声明名字
new 类或接口(){
类体(一般是方法重写);
};
new Animal(){
@Override
public void cry(){
...
}
};
特点:匿名内部类本质就是一个子类(实现类),由于调用了构造器会立即创建出一个子类(实现类)对象
作用:用于更方便的创建一个子类(实现类)对象
最终目的:简化代码
一般都是api需要我们使用匿名内部类去调用,自己写程序很少主动使用匿名内部类
枚举
- 枚举是一种特殊类
枚举的格式
修饰符 enum 枚举类名{
名称1,名称2...
其他成员...
}
public enum A{
X,Y,Z;
...
}
注意:
- 枚举类红的第一行,只能协议发合法的标识符(名称),多个名称用逗号隔开
- 这些名称,本质是常量,每个常量都会记住枚举类的一个对象
- 枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象
- 枚举都是最终类,不可以被继承
- 枚举类中,从第二行开始,可以定义类的其他各种成员
- 编译器为枚举类新增了几个方法,并且枚举类都是继承:java.lang.Enum类的,从enum类也会继承到一些方法
- 枚举类本身可以改造为一种单例模式
枚举的常见应用场景:
泛型
- 定义类、接口、方法时,同时声明了一个或者多个类型变量(如<E,T>),称为泛型类、泛型接口、泛型方法,它们统称为泛型
public class ArrayList<E>{
...
}
- 作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常
- 泛型的本质:把具体的数据类型作为参数传给类型变量
泛型接口是给实现类实现的,实现类实现泛型接口的时候可以申明一个数据类型,实现类重写的方法都是针对该类型的操作。
泛型方法:
修饰符<类型变量,类型变量...>返回值类型 方法名(形参列表){
}
public static <T> void test(T t){
}
通配符:
- 就是"?",可以在 使用泛型 的时候代表一切类型,E T K V 是在定义泛型的时候使用
泛型的上下限:
- 泛型上限:? extends Animal ?能接收的必须是Animal或其子类
- 泛型下限:? super Animal ?能接收的必须是Animal或其父类
理解泛型的注意事项:1.泛型是工作在编译阶段的,在编写代码时就纠错,一旦编译成class文件,class文件中就不存在泛型了,这就是泛型擦除2.泛型不支持基本数据类型(但支持基本数据类型对应的包装类),只支持对象类型(引用数据类型)