文章目录
一、static关键字
-
static是静态的意思,可以修饰成员变量和成员方法;
-
static修饰的成员变量表示该变量只在内存中只存储一份,可以共享访问与修改;
1.静态成员变量
- static修饰的成员变量 —> 静态成员变量 (没有static的成员变量,为实例成员变量)
- 访问方式一般是 类名.静态变量
2.静态成员方法
成员方法的分类
- 静态成员方法(有static修饰,归属于类),建议使用类访问,也可以使用对象访问,执行公用功能
- 实例成员方法(无static修饰,归属于对象),只能通过对象触发访问,表示对象自己的行为
3.static工具类
工具类
- 内部都是一些静态方法,每个方法完成一个功能
- 一次编写,处处可用,提高代码的重用性
工具类的要求
- 建议工具类的构造器私有化
- 工具类不需要创建对象
4.代码块
/**
1.顶一个静态集合,这样的集合只加载一次。
因为房间内只需要一副牌
*/
public static ArrayList<String> cards = new ArrayList<>();
/**
2.程序真正运行main方法前,将54张牌放进去,个数确定好!
*/
static {
//3.正式做牌
//a.四种花色的制作
String[] number = new String[]{"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
String[] colors = {"♥","♠","♦","♣"};
for (int i = 0; i < number.length; i++) {
for (int j = 0; j < colors.length; j++) {
String card = number[i]+colors[j]; // 形如:2♥
cards.add(card);
}
}
//b.大小王
cards.add("大🃏");
cards.add("小🃏");
}
public static void main(String[] args) {
//输出牌
System.out.println("新牌:"+cards);
}
5.设计模式之创建型
单例模式(对象地址值唯一)
单例模式有以下特点:
- 1单例类只能有一个实例;
- 2单例类必须自己创建自己的唯一实例;
- 3单例类必须给所有其他对象提供这一实例。
5.1懒汉单例
真正需要该对象的时候,才去创建一个对象
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
//1.构造器私有化
private Singleton() {}
/**
2.定义存储一个静态成员变量 的 对象
只加载一次,只有一份
注意:!!!最好,进行私有化,可以避免给别人挖坑
*/
private static Singleton single;
// 3.仅提供一个方法,对外返回同一个对象
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
//获取对象
SingleInstance instance2 = SingleInstance.getInstance();
SingleInstance instance21 = SingleInstance.getInstance();
//比较地址值
System.out.println(instance2 == instance21); //true
5.2饿汉单例
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton {
//私有化构造器
private Singleton() {}
//加载类时,耗时构造完成
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
饿汉式和懒汉式区别:
(1)初始化时机与首次调用:
- 饿汉式是在类加载时,就将单例初始化完成,保证获取实例的时候,单例是已经存在的了。所以在第一次调用时速度也会更快,因为其资源已经初始化完成。
- 懒汉式会延迟加载,只有在首次调用时才会实例化单例,如果初始化所需要的工作比较多,那么首次访问性能上会有些延迟,不过之后就和饿汉式一样了。
(2)线程安全方面:饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的,需要通过额外的机制保证线程安全
5.3登记式单例
登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。
/类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton3 {
private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
static{
Singleton3 single = new Singleton3();
map.put(single.getClass().getName(), single);
}
//保护的默认构造子
protected Singleton3(){}
//静态工厂方法,返还此类惟一的实例
public static Singleton3 getInstance(String name) {
if(name == null) {
name = Singleton3.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
//反射构造实例对象
map.put(name, (Singleton3) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
//定义一个成员方法
public String getSingleInfo() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
Singleton3 single3 = Singleton3.getInstance(null);
System.out.println(single3.getSingleInfo());
}
}
二、继承
继承是什么?好处?
- 继承就是允许使用extends关键字,建立两个类之间的父子关系
- 提高代码复用性,减少代码冗余,增强类的扩展功能
格式?
子类extends父类
1.子类内存原理
2.继承的特点?
- 子类继承父类,子类可以得到父类的属性与行为,不能继承父类的构造器
- 单继承模式,一个类只能继承一个父类,不支持多继承,但是允许多层继承
- Java中所有的类都是Object类的子类
继承后的特点?
- 在子类方法中,访问成员(成员变量,成员方法)满足就近原则,子类中没有找到,再从父类中找,找不到就报错
- 如果子父类中出现了重名的成员,此时如何访问父类成员? super.父类成员(变量/方法)
3.方法重写
重写:子类与父类中一摸一样的方法声明,重新实现方法,用于父类不满足子类需求时;
@Override重写注解 : 建议重写方法都加上@Override注解,可以在编译阶段会出现错误提示,优雅!!!
方法重写的要求?
- 重写方法的名称,形参列表必须与父类完全一致;
- 私有方法不能被重写;
- 子类不能重写父类的静态方法;
- 子类重写父类方法时,访问权限必须大于或等于父类(暂时了解 : 缺省<protected<public)
4.继承构造器的特点
子类继承父类后构造器的特点:
- 子类的所有构造器默认先访问父类中的无参构造器,再执行自己;
- 子类初始化如果需要使用父类的数据,需要使用完成父类的初始化!
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化;
- 如何调用父类构造器?子类默认super(),不写也存在;
注意:
- super调用父类有参构造器的作用:初始化父类的数据
- 如果父类没有无参构造器,只有有参构造器,会报错!因为子类默认调用父类的无参构造
- 子类构造器中通过书写super(…),手动调用父类的有参构造器;
5.this和super
this(…)和super(…)都只能放在构造器的第一行,所以不能共存在同一个构造器中;
三、基础认识
1.包
同一个包下,不能定义相同的类名的类
导包
- 相同包下的类可以直接访问,不同包下的类必须导包,格式 import 包名.类名
- 加入一个类中需要很多的不同类,而这两个类的名称是一致的,则默认导入一个类,另一个类需要带包名访问
2.权限修饰符
- 权限修饰符:是用于控制一个成员能够被访问的范围
- 可以修饰成员变量、方法、构造器、内部类,不同权限修饰符修饰的成员能够访问的范围将收到限制
作用范围由小到大(private > 缺省 > protected > public)
修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | ✓ | |||
缺省 | ✓ | ✓ | ||
protected | ✓ | ✓ | ✓ | |
public | ✓ | ✓ | ✓ | ✓ |
3.final关键字
final作用:
- final关键字是最终的意思,可以修饰(类、方法、变量)
- 修饰类:表明该类是最终类,不可被继承
- 修饰方法:表明该方法是最终方法,不可被重写
- 修改变量:表明该变量第一次赋值后,不能再次被赋值(有且仅能赋值一次)
final修饰变量的注意:
- 修饰基本类型变量,变量的数据值不可变;
- 修饰引用类型变量,变脸的地址值不可变,但是地址指向的对象内容可以发生变化;
final Teacher teacher = new Teacher("万老师");
System.out.println(teacher);//Teacher{name='万老师'}
teacher.setName("王老师");
System.out.println(teacher);//Teacher{name='王老师'}
4.常量
-
常量是使用public static final修饰的成员变量,必须有赋值,且不可修改
-
常量可以用于做系统的配置信息,方便程序的维护,同时提高可读性
命名规范:英文单词大写,单词之间使用下划线连接
常量的执行原理:
- 在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量"…";
- 好处是,让使用常量的程序的执行性能与直接使用字面量一样
5.枚举
enum Season{
//常量有 spring, summer, autumn,winter,系统会自动添加 public static final修饰
spring,summer,autumn,winter;
}
api | 描述 |
---|---|
Enum.values() | 返回枚举类中的值 |
Enum.ordinal() | 可以找到每个枚举常量的索引,就像数组索引一样 |
Enum.valueOf() | 返回指定字符串值的枚举常量 |
public static void main(String[] args) {
// 1迭代季节values
for (Season season : Season.values()) {
// 2ordinal可以找到每个枚举常量的索引,就像数组索引一样。
System.out.println(season+" 索引为 "+season.ordinal());
}
//3valueOf
System.out.println(Season.valueOf("summer"));
}
枚举的特征
- 枚举类是继承了枚举类型:java.long.Enum
- 枚举都是最终类,不可以被继承
- 枚举的构造器都是私有的,对外不能创建对象
- 枚举类的第一行默认都是罗列枚举对象的名称
- 枚举类相当于是多例模式
四、oop进阶认识
1.抽象类
不完全设计类
- 抽象类abstract,可以修饰类,成员方法;
- abstract修饰类,这个类就是抽象类;修改方法,这个方法就是抽象方法;
格式:
修饰符 abstract class 类名 { //抽象类
//抽象方法 , 不实现(没有大括号)
修饰符 abstract 返回值类型 方法名(形参列表);
}
public abstract class Animal {
//了解抽象类(父类),抽象方法并不实现(没有大括号)
/**
抽象方法
*/
public abstract void run();
}
注意:
- 抽象方法只有方法签名,不能声明方法体;
- 一个类中如果定义抽象方法,则该类必须声明为抽象类;
- 一个类如果继承了抽象类,那么这个类必须重写全部的抽象方法;
使用场景:
-
抽象类可以理解成不完整的设计图,一般作为父类,让子类 继承
-
当父类知道子类一定要完成的某些行为,但每个子类的实现方式又不同,于是父类可把该行为定义为抽象行为,具体实现交由不同子类分别实现,同时父类声明为抽象类;
特征和注意事项:
- 类有的成员(成员变量、方法、构造器)抽象类都具备
- 抽象类中不一定有抽象方法,但有抽象方法一定是抽象类
- 一个类继承了抽象类,必须重写全部的抽象类,否则该类应该定义为抽象类
- 不能用abstract修饰变量、代码块、构造器
- 最重要的特征:得到了抽象方法,失去了 new 对象的能力!!!(有得有失)
final和abstract的关系
- 互斥关系
- abstract定义为抽象类作为模板让子类继承,final定义的类是不可修改的,不能被继承
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写
1.2模板方法模式
(1)抽象类/抽象模板(Abstract Class)
抽象模板类,负责给出一个算法的轮廓和骨架。
它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
- 模板方法:定义了一套算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是算法骨架/流程的某些步骤进行具体实现,包含以下几种类型,
- 抽象方法:在抽象类中声明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
(2)具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
public abstract class AbstractCook {
//模板使用final不可重写
public final void doCook(){
openFire();
cooking();
closedFire();
}
//嵌入的抽象方法
protected abstract void cooking();
protected void openFire() {
System.out.println("点火,开始做菜了");
}
protected void closedFire() {
System.out.println("关火,菜出锅了");
}
}
2.接口
格式
public interface 接口名{
//常量
//抽象方法 省去修饰符和abstract
}
JDK8之前,接口中只能是抽象方法和常量,没有其他成分
规范性
/**
声明一个接口:体现一种规范,规范一定是公开的
*/
public interface InterfaceDemo {
//目标:接口中成分的特点: JDK 8.0之前接口只能由抽象方法和常量
//1.常量
//注意:规范! 默认是都是公开的 public,代码层面public static final可以省略不写
// public static final String USER_NAME = "笑哈哈";
String USER_NAME = "小白马";
//2.抽象方法
//注意:规范! 默认是公开的 public,代码层面public abstract 可以省略不写
//public abstract void run();
void run();
}
接口的用法:
-
接口是被类实现的implements的,实现接口的类称为实现类。实现类可以理解为所谓的子类。
-
格式 :
修饰符 class 实现类 implements 接口1 , 接口2 , 接口3, … {
}
实现的关键字 : implements , 多实现
-
-
从上面可以看出,类可以实现多个接口
-
一个类实现接口,必须重写全部的抽象方法,否则这个类需要定义为抽象类;
基本小结:
- 类和类之间的关系:单继承
- 类和接口之间的关系:多实现
- 接口和接口之间的关系:多继承,一个接口可以继承多个接口
JDK对接口的拓展(允许有实现方法的存在!!!)
- JDK8后的新增哪些方法?
- 默认方法:default修饰,实现类对象可调用;
- 静态方法:static修饰,必须用当前接口名调用;
- 私有方法:private修饰,jdk9开始才有的,只能在接口内部被调用;
3.多态
同一件事情,发生在不同对象身上,就会产生不同的结果。
在java中要实现多态,必须要满足如下几个条件,缺一不可:
- 1.必须在继承体系下
- 2.子类必须要对父类中方法进行重写
- 3.通过父类的引用调用重写的方法
同一类型(父类,接口),执行子类重写行为(同一个行为),不同表现
范围 小 --> 大 ;强制类型转变 大 --> 小
注意:地址的传递!!!!
多态的常见形式: 向上转型 自动类型转换
-
父类类型 对象名称 = new 子类构造器
-
接口 对象名称 = new 实现类构造器
编译先看父类是否该方法,运行则调用子类重写方法;
一般,父类对象只能使用与子类共有的功能,不能使用子类独有的;
向上转型的使用:
-
直接赋值,这是最常见的向上转型
-
将方法形参声明为父类类型,实际传入子类对象
-
方法返回值为父类类型,实际返回子类对象
-
构造器中调用父类方法
public Cat(String name, int age) { super(age); // 调用父类构造函数 this.name = name; }
解决不能访问子类独有方法的问题:强制类型转换,向下转型
子类类型 引用名 = (子类类型) 父类构造器
Animal a2 = new Tortoise();
//强制类型转换,编译阶段不会报错(注意:有继承或实现关系,编译阶段可以强制转换,没有毛病)
//运行时可以出错 , ClassCastException , 使用instanceof 进行匹配
Dog d1 = (Dog) a2;
解决
public static void sport(Animal a){
//instanceof 进行匹配
if(a instanceof Dog){
Dog d = (Dog) a;
d.lookDoor();
}else if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEgg();
}
}
4.内部类
4.1静态内部类
静态内部类的使用场景、特点与访问总结:
- 如果一个类中包含了一个完整的成分,如汽车类中的发动机类
- 特点、使用与普通类是一样的,类有的成分它都有,只是位置在别人类内部
- 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员(变量和方法)
- 开发中实际用的比较少
4.2成员内部类
先创建外部类对象,才能创建内部类对象
特点:
-
创建格式:Outer.Inter in = new Outer构造器 . new Inter构造器
-
可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员;
-
无static修饰,属于外部类的对象
-
JDK16之前,成员内部类不能定义静态成员,JDK16开始也可以定义静态成员
成员内部类访问外部类对象,格式: 外部类名.this.方法名
4.3局部内部类
语法格式
[修饰符] class 外部类{
[修饰符] 返回值类型 方法名([形参列表]){
[final/abstract] class 内部类{
}
}
}
局部内部类的特点:
-
定义在类的某个方法里,而不是直接定义在类里
-
局部内部类前面不能有权限修饰符
-
局部内部类里不能使用static声明变量
-
局部内部类访问外部类的静态成员
-
如果这个局部内部类所在的方法是静态的,它无法访问外部类的非静态成员
-
局部内部类 可以访问外部方法 的局部变量 ,但 这个局部变量必须要非final修饰。JDK8以后,final可以省略
实现
/**
* 局部内部类实现
*/
class Outer {
int age = 19;
static int m = 10;
public void test() {
// final String y = "good";
String y = "good"; // JDK8 以后,final可以省略
class Inner { // 定义在外部类的某个方法里
// static int a = 10; 不能定义静态变量!
private void demo() {
System.out.println(age);
// 不能修改外部类成员方法内的局部变量
// y = 'yes';
// 只能访问被 final 修饰的外部方法的局部变量
// JDK8 以后,如果外部函数的局部变量没有加 final,编译器会自动加 final
System.out.println(y);
}
}
Inner inner = new Inner();
inner.demo();
}
}
public class Test1{
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
4.4匿名内部类
用来创建一个 接口或抽象类对象
格式
new 父类名 / 接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
匿名内部类特点:
-
匿名内部类就是一种特殊的局部内部类,只不过没有名称而已,基本特点和局部内部类一致
-
匿名内部类不能有构造器,匿名内部类没有类名,肯定无法声明构造器
-
匿名内部类的前提是,这个内部类必须要继承自一个父类或者父接口
-
匿名内部类是接口的一种常见简化写法,也是我们开发中最常使用的一种内部类。它的本质是一个实现类父类或者父类接口具体方法的一个匿名对象。
//匿名内部类 作为一个方法的参数使用
public class AnonymousTest {
//成员方法
public int calculate(int a,int b,Calculator calculator){
return calculator.doCalculator(a, b);
}
//main
public static void main(String[] args) {
AnonymousTest test = new AnonymousTest();
//匿名内部类 接口对象
Calculator c = new Calculator() {
@Override
public int doCalculator(int a, int b) {
return a+b;
}
};
int x = test.calculate(1,9,c);
//等价
int y = test.calculate(1, 9, new Calculator() {
@Override
public int doCalculator(int a, int b) {
return a+b;
}
});
System.out.println(x);
}
}
interface Calculator{
int doCalculator(int a,int b);
}