前言
- 该文章为Java自学笔记案例:面向对象进阶
- 学习视频为https://www.bilibili.com/video/av250694651
目录
静态关键字:static
static的作用、修饰成员变量的用法
static关键字的用法:
- static是静态的意思,可以修饰成员变量和成员方法。
- static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
成员变量可以分为两类:
- 静态成员变量(有static修饰,属于类,内存中加载一次): 常表示如在线人数信息等需要被共享的信息,可以被共享访问。
public class User {
// 静态成员变量
public static String onlineNumber= 161;
}
类名.静态成员变量。(推荐)
对象.静态成员变量。(不推荐)
- 实例成员变量(无static修饰,存在于每个对象中):常表示姓名name、年龄age、等属于每个对象的信息。
public class User {
// 实例成员变量
private String name;
private int age;
}
对象.实例成员变量。
注意:两种成员变量各自在什么情况下定义:
静态成员变量:表示在线人数等需要被共享的信息。
实例成员对象:属于每个对象,且每个对象信息不同时(name,age,…等)
static修饰成员变量的内存原理
public class User {
//静态成员变量
public static int onLineNumber = 161;
//实例成员变量
private String name;
private int age;
public static void main(String[] args) {
//1、类名.静态成员变量
User.onLineNumber++;
//注意:同一个类中访问静态成员变量,类名可以省略不写
System.out.println(onLineNumber);
//2、对象.实例成员变量
User u1 = new User();
u1.age = 20;
u1.name = "孙悟空";
//对象.静态成员变量(不推荐)
u1.onLineNumber++;
User u2 = new User();
u2.name = "猪八戒";
u2.age = 29;
System.out.println(u2.name);
System.out.println(u2.age);
// 对象名称.静态成员变量( 不推荐 )
u2.onLineNumber++;
System.out.println(User.onLineNumber); // 164
}
}
static修饰成员方法的基本用法
成员方法的分类:
- 静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问。
- 实例成员方法(无static修饰,属于对象),只能用对象触发访问。
使用场景:
表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
public class method {
private String name;
private int age;
//实例方法,无static修饰,属于对象,通常表示对象自己的行为,可以访问对象的成员变量
public void study(){
System.out.println(name +"好好学习,天天向上");
}
//静态方法,有static修饰,属于类,可以被类和对象共享访问
public static void getMax(int a, int b){
System.out.println(a > b ? a : b);
}
public static void main(String[] args) {
//1、类名,静态方法
method.getMax(10,100);
//注意:同一个类中,访问静态成员,可以省略类名不写
getMax(100,200);
//2、对象.实例方法
method m = new method();
m.name = "OK";
m.study();
//3、对象.静态方法(不推荐)
m.getMax(100,3000);
}
}
static修饰成员方法的内存原理
public class Student {
private String name;
// 1. 实例方法: 无static修饰的,属于对象的
public void study(){
System.out.println(name + "在好好学习~~~");
}
// 2. 静态方法:有static修饰,属于类和对象共享的
public static int getMax(int a , int b){
return a > b ? a : b;
}
public static void main(String[] args) {
// 1. 类名.静态成员方法
System.out.println(Student.getMax(10 , 2));
// 注意:同一个类中访问静态成员类名可以不写
System.out.println(getMax(2 , 10));
// 2. 对象.实例成员方法
// study(); // 会报错
Student s = new Student();
s.name = "猪八戒";
s.study();
// 3. 对象.静态成员方法。(不推荐)
System.out.println(s.getMax(20 , 10));
}
}
static实际应用案例:使用静态方法定义工具类
工具类: 工具类中定义的都是一些静态方法,每个方法都是以完成一个共用的功能为目的。
工具类的好处: 一是调用方便,二是提高了代码复用(一次编写,处处可用)
为什么工具类中的方法不用实例方法做?
- 实例方法需要创建对象调用,此时用对象只是为了调用方法,这样只会浪费内存。
工具类的定义注意:
- 建议将工具类的构造器进行私有,工具类无需创建对象。
- 里面都是静态方法,直接用类名访问即可。
public class VerifyTool {
//私有构造器
private VerifyTool(){
}
//静态方法
public static String createCode(int n){
// 1、使用String开发一个验证码
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
// 2、定义一个变量用于存储5位随机的字符作为验证码
String code = "";
// 3、循环
Random r = new Random();
for (int i = 0; i < n; i++) {
int index = r.nextInt(chars.length());
// 4、对应索引提取字符
code += chars.charAt(index);
}
return code;
}
}
public class Register {
public static void main(String[] args) {
// 验证码:
System.out.println("验证码:" + VerifyTool.createCode(5));
}
}
案例:定义数组工具类
需求:在实际开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:ArraysUtils
- 我们知道数组对象直接输出的时候是输出对象的地址的,而项目中很多地方都需要返回数组的内容,请在ArraysUtils中提供一个工具类方法toString,用于返回整数数组的内容,返回的字符串格式如:[10, 20, 50, 34, 100](只考虑整数数组,且只考虑一维数组)
- 经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供这样一个工具方法getAerage,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组)
- 定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。
public class ArrayUtils {
//把它的构造器私有化
private ArrayUtils(){
}
//静态方法,工具方法
public static String toString(int[] arr){
if(arr != null ){
String result = "[";
for (int i = 0; i < arr.length; i++) {
result += (i == arr.length - 1 ? arr[i] : arr[i] + ", ");
}
result += "]";
return result;
}else {
return null;
}
}
//静态方法,工具方法
public static double getAverage(int[] arr){
// 总和 最大值 最小值
int max = arr[0];
int min = arr[0];
int sum = 0;
for (int i = 0; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
if(arr[i] < min){
min = arr[i];
}
sum += arr[i];
}
return (sum - max - min)*1.0 / (arr.length - 2);
}
}
public class Test2 {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
System.out.println(arr);
System.out.println(ArrayUtils.toString(arr));
System.out.println(ArrayUtils.getAverage(arr));
int[] arr1 = null;
System.out.println(ArrayUtils.toString(arr1));
int[] arr2 = {};
System.out.println(ArrayUtils.toString(arr2));
}
static的注意事项
static访问注意实现:
- 静态方法只能访问静态的成员,不可以直接访问实例成员。
- 实例方法可以访问静态的成员,也可以访问实例成员。
- 静态方法中是不可以出现this关键字的。
public class Test {
// 静态成员变量
public static int onLineNumber;
// 实例成员变量
private String name;
public static void getMax(){
// 1、静态方法可以直接访问静态成员,不能访问实例成员。
System.out.println(Test.onLineNumber);
System.out.println(onLineNumber);
inAddr();
// System.out.println(name);
// 3、静态方法中不能出现this关键字
// System.out.println(this);
}
public void run(){
// 2、实例方法可以直接访问静态成员,也可以访问实例成员
System.out.println(Test.onLineNumber);
System.out.println(onLineNumber);
Test.getMax();
getMax();
System.out.println(name);
sing();
System.out.println(this);
}
public void sing(){
System.out.println(this);
}
// 静态成员方法
public static void inAddr(){
System.out.println("我们在黑马程序员~~");
}
public static void main(String[] args) {
}
}
static应用知识:代码块
代码块的分类、作用
代码块概述:
- 代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。
- 在Java类下,使用 { } 括起来的代码被称为代码块 。
代码块分为:
- 静态代码块:
- 格式:static{}
- 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次。
- 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
public class TestDemo1 {
public static String schoolName;
public static void main(String[] args) {
// 目标:学习静态代码块的特点、基本作用
System.out.println("=========main方法被执行输出===========");
System.out.println(schoolName);
}
/**
特点:与类一起加载,自动触发一次,优先执行
作用:可以在程序加载时进行静态数据的初始化操作(准备内容)
*/
static{
System.out.println("==静态代码块被触发执行==");
schoolName = "菜园";
}
}
==静态代码块被触发执行==
=========main方法被执行输出===========
菜园
- 构造代码块(了解,用的少):
- 格式:{}
- 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
- 使用场景:初始化实例资源。
public class TestDemo2 {
private String name;
/**
属于对象的,与对象一起加载,自动触发执行。
*/
{
System.out.println("==构造代码块被触发执行一次==");
name = "张麻子";
}
public TestDemo2(){
System.out.println("==构造器被触发执行==");
}
public static void main(String[] args) {
// 目标:学习构造代码块的特点、基本作用
TestDemo2 t = new TestDemo2();
System.out.println(t.name);
TestDemo2 t1 = new TestDemo2();
System.out.println(t1.name);
}
}
==构造代码块被触发执行一次==
==构造器被触发执行==
张麻子
==构造代码块被触发执行一次==
==构造器被触发执行==
张麻子
静态代码块的应用案例
需求:
在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据。
分析:
- 该房间只需要一副牌。
- 定义一个静态的ArrayList集合存储54张牌对象,静态的集合只会加载一份。
- 在启动游戏房间前,应该将54张牌初始化好
- 当系统启动的同时需要准备好54张牌数据,此时可以用静态代码块完成。
public class StaticCodeTest3 {
/**
模拟初始化牌操作
点数: "3","4","5","6","7","8","9","10","J","Q","K","A","2"
花色: "♠", "♥", "♣", "♦"
1、准备一个容器,存储54张牌对象,这个容器建议使用静态的集合。静态的集合只加载一次。
*/
public static ArrayList<String> cards = new ArrayList<>();
/**
2、在游戏启动之前需要准备好54张牌放进去,使用静态代码块进行初始化
*/
static{
// 3、加载54张牌进去。
// 4、准备4种花色:类型确定,个数确定了
String[] colors = {"♠", "♥", "♣", "♦"};
// 5、定义点数
String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
// 6、先遍历点数、再组合花色
for (int i = 0; i < sizes.length; i++) {
// sizes[i]
for (int j = 0; j < colors.length; j++) {
cards.add(sizes[i] + colors[j]);
}
}
// 7、添加大小王
cards.add("小🃏");
cards.add("大🃏");
}
public static void main(String[] args) {
System.out.println("新牌:" + cards);
}
}
static应用知识:单例
设计模式、单例模式介绍、饿汉单例模式
什么是设计模式(Design pattern):
- 开发中经常遇到一些问题,一个问题通常有n种解法的,但其中肯定有一种解法是最优的,这个最
优的解法被人总结出来了,称之为设计模式。 - 设计模式有20多种,对应20多种软件开发中会遇到的问题,学设计模式主要是学2点:
- 第一:这种模式用来解决什么问题。
- 第二:遇到这种问题了,该模式是怎么写的,他是如何解决这个问题的。
单例模式:
- 可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象。
- 例如任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间。
单例的实现方式很多
- 饿汉单例模式。
- 懒汉单例模式。
- …
饿汉单例设计模式: 在用类获取对象的时候,对象已经提前为你创建好了。
设计步骤:
- 定义一个类,把构造器私有。
- 定义一个静态变量存储一个对象。
/** a、定义一个单例类 */
public class SingleInstance {
/** c.定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance = new SingleInstance ();
/** b.单例必须私有构造器*/
private SingleInstance (){
System.out.println("创建了一个对象");
}
}
//SingleInstance s1 = SingleInstance.instance;
懒汉单例模式
懒汉单例设计模式: 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
设计步骤:
- 定义一个类,把构造器私有。
- 定义一个静态变量存储一个对象。
- 提供一个返回单例对象的方法
public class SingleInstance2 {
/**
2、定义一个静态的成员变量用于存储一个对象,一开始不要初始化对象,因为人家是懒汉
*/
private static SingleInstance2 instance;
/**
1、私有构造器啊
*/
private SingleInstance2(){
}
/**
3、提供一个方法暴露,真正调用这个方法的时候才创建一个单例对象
*/
public static SingleInstance2 getInstance(){
if(instance == null){
// 第一次来拿对象,为他做一个对象
instance = new SingleInstance2();
}
return instance;
}
//SingleInstance2 s1 = SingleInstance2.getInstance();
面向对象三大特征之二:继承
继承概述、使用继承的好处
什么是继承?
- Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系。public class Student extends People {}
- Student称为子类(派生类),People称为父类(基类 或超类)。
使用继承的好处:
- 当子类继承父类后,就可以直接使用父类公共的属性和方法了。因此,用好这个技术可以很好的我们提高代码的复用性。
案例:
解决方案:把相同的属性和行为抽离出来,可以降低重复代码的书写,抽取出来放到何处呢?
- 继承(extends)关系, 好处:提高代码复用
继承的设计规范、内存运行原理
继承设计规范:
- 子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的的属性和行为应该定义在子类自己里面。
为什么?
- 如果子类的独有属性、行为定义在父类中,会导致其它子类也会得到这些属性和行为,这不符合面向对象逻辑。
public class Role {
private String name;
private int age;
public void queryCourse(){
System.out.println(name + "开始查看课程信息");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Student extends Role{
private String classname;
public void writeInfo(){
System.out.println(getName()+"说:今天学习很不错");
}
public String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setName("张松松");//父类的
s.setAge(25);//父类的
s.setClassname("菜园子第一期");//子类的
System.out.println(s.getName());
System.out.println(s.getAge());
System.out.println(s.getClassname());
s.queryCourse(); // 父类的
s.writeInfo(); // 子类的
}
}
内存原理:
注意:父类空间与子类空间链接在一起,共用一个地址。
继承的特点
继承的特点:
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承、但是支持多层继承。
- Java中所有的类都是Object类的子类。
1、子类是否可以继承父类的构造器?
- 不可以的,子类有自己的构造器,父类构造器用于初始化父类对象。
2、子类是否可以继承父类的私有成员?
- 可以的,只是不能直接访问。(有争议)
3、子类是否可以继承父类的静态成员?
- 有争议的知识点。
- 子类可以直接使用父类的静态成员(共享)
- 但个人认为:子类不能继承父类的静态成员。(共享并非继承)
Java只支持单继承,不支持多继承。
Java支持多层继承
- 子类 A 继承父类 B ,父类B 可以 继承父类 C
Object特点:
- Java中所有类,要么直接继承了Object , 要么默认继承了Object , 要么间接继承了Object, Object是祖宗类。
继承后:成员变量、成员方法的访问特点
在子类方法中访问成员(成员变量、成员方法)满足:就近原则
- 先子类局部范围找
- 然后子类成员范围找
- 然后父类成员范围找,如果父类范围还没有找到则报错。
如果子父类中,出现了重名的成员,会优先使用子类的,此时如果一定要在子类中使用父类的怎么办?
- 可以通过super关键字,指定访问父类的成员。格式:super.父类成员变量/父类成员方法
变量
public class ExtendsDemo {
public static void main(String[] args) {
Wolf w = new Wolf();
System.out.println(w.name); // 子类的
w.showName();
}
}
class Animal{
public String name = "父类动物";
}
class Wolf extends Animal{
public String name = "子类动物";
public void showName(){
String name = "局部名称";
System.out.println(name); // 局部的
System.out.println(this.name); // 子类name
System.out.println(super.name); // 父类name
}
}
方法
public class ExtendsDemo2 {
public static void main(String[] args) {
Student s = new Student();
s.run(); // 子类的
System.out.println("-----------");
s.go();
}
}
class People{
public void run(){
System.out.println("可以跑~~");
}
}
class Student extends People{
public void run(){
System.out.println("学生跑的贼快~~");
}
public void go(){
run(); // 子类的
super.run(); // 父类的
}
}
继承后:方法重写
什么是方法重写?
- 在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。
方法重写的应用场景
- 当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。
- 子类可以重写父类中的方法。
案例演示:
- 旧手机的功能只能是基本的打电话,发信息
- 新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片。
@Override重写注解
- @Override是放在重写后的方法上,作为重写是否正确的校验注解。
- 加上该注解后如果重写错误,编译阶段会出现错误提示。
- 建议重写方法都加@Override注解,代码安全,优雅!
public class Phone {
public void call(){
System.out.println("打电话开始~~~");
}
public void sendMessage(){
System.out.println("发送短信开始~~~");
}
}
public class NewPhone extends Phone{
@Override
public void call(){
super.call();
System.out.println("支持视频通话~~~");
}
@Override
public void sendMessage(){
super.sendMessage();
System.out.println("支持发送图片和视频~~~");
}
}
方法重写注意事项和要求
- 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类 (暂时了解 :缺省 < protected < public)
- 子类不能重写父类的静态方法,如果重写会报错的。
继承后:子类构造器的特点
子类继承父类后构造器的特点:
- 子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
为什么?
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
- 子类构造器的第一行语句默认都是:super(),不写也存在。
public class Animal {
public Animal(){
System.out.println("==父类Animal无参数构造器被执行===");
}
}
public class Cat extends Animal{
public Cat(){
super(); // 默认的,写不写都有,默认就是找父类无参数构造器
System.out.println("==子类Cat无参数构造器被执行===");
}
public Cat(String n){
super(); // 默认的,写不写都有,默认就是找父类无参数构造器
System.out.println("==子类Cat有参数构造器被执行===");
}
}
public class Test {
public static void main(String[] args) {
Cat c = new Cat();
System.out.println("------------");
Cat c1 = new Cat("叮当猫");
}
}
继承后:子类构造器访问父类有参构造器
super调用父类有参数构造器的作用:
- 初始化继承自父类的数据。
如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?
- 会报错。因为子类默认是调用父类无参构造器的。
如何解决?
- 子类构造器中可以通过书写 super(…),手动调用父类的有参数构造器
public class People {
private String name;
private int age;
public People() {
}
public People(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Student extends People{
private String className;
public Student(){
}
public Student(String name, int age, String className) {
super(name, age);
this.className = className;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student("张三", 21, "99期");
System.out.println(s.getName());
System.out.println(s.getAge());
System.out.println(s.getClassName());
}
}
this、super使用总结
- this:代表本类对象的引用;super:代表父类存储空间的标识。
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员变量 访问本类成员变量 | this.成员方法(…) 访问本类成员方法 | this(…)访问本类构器 |
super | super.成员变量 访问父类成员变量 | super.成员方法(…)访问父类成员方法 | super(…)访问父类构造器 |
在以上的总结中,唯独只有this调用本类其他构造器我们是没有接触过的。
this(…)访问本类构器实例:
public class Student {
private String schoolName;
private String name;
public Student(String name){
this(name , "黑马培训中心");
}
public Student(String name , String schoolName ){
this.name = name;
this.schoolName = schoolName;
}
}
包
什么是包?
- 包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护。
- 建包的语法格式:package 公司域名倒写.技术名称。报名建议全部英文小写,且具备意义
- package com.itheima.javabean;
public class Student {
} - 建包语句必须在第一行,一般IDEA工具会帮助创建
导包
- 相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!导包格式:import 包名.类名;
- 假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问。
权限修饰符
什么是权限修饰符?
- 权限修饰符:是用来控制一个成员能够被访问的范围的。
- 可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
权限修饰符的分类和具体作用范围:
权限修饰符:有四种作用范围由小到大(private->缺省->protected->public)
修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
学完权限修饰符需要具备如下能力
- 能够识别别人定义的成员的访问权限。
- 自己定义成员(方法,成员变量,构造器等)一般满足如下要求:
- 成员变量一般私有。
- 方法一般公开。
- 如果该成员只希望本类访问,使用private修饰。
- 如果该成员只希望本类,同一个包下的其他类和子类访问,使用protected修饰。
final
final的作用:
- final 关键字是最终的意思,可以修饰(方法,变量,类)
- 修饰方法:表明该方法是最终方法,不能被重写。
- 修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。
- 修饰类:表明该类是最终类,不能被继承。
public class Test {
// 属于类,只加载一次,可以共享 (常量)
public static final String schoolName = "黑马";
public static final String schoolName2;
static{
schoolName2 = "传智";
// schoolName2 = "传智"; // 第二次赋值,报错了!
}
// 属于对象的! (final基本上不会用来修饰实例成员变量,没有意义!)
private final String name = "王麻子";
public static void main(String[] args) {
// final修饰变量,变量有且仅能被赋值一次。
/* 变量有几种:
局部变量。
成员变量。
-- 1、静态成员变量。
-- 2、实例成员变量。
*/
final int age;
age = 12;
// age = 20; // 第二次赋值,报错了!
System.out.println(age);
final double rate = 3.14;
buy(0.8);
// schoolName = "传智"; // 第二次赋值,报错了!
Test t = new Test();
// t.name = "麻子"; // 第二次赋值,报错了!
System.out.println(t.name);
}
public static void buy(final double z){
// z = 0.1; // 第二次赋值,报错了!
}
}
/**
final修饰类 类不能被继承了
*/
//final class Animal{
//}
//class Cat extends Animal{
//}
/**
final修饰方法,方法不能被重写
*/
class Animal{
public final void run(){
System.out.println("动物可以跑~~");
}
}
class Tiger extends Animal{
// @Override
// public void run() {
// System.out.println("老虎跑的贼快~~~");
// }
}
final修饰变量的注意:
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
public class Test2 {
public static void main(String[] args) {
// final修饰变量的注意事项:
// 1、final修饰基本类型变量,其数据不能再改变
final double rate = 3.14;
// rate = 3.15; // 第二次赋值,报错
// 2、final修饰引用数据类型的变量,变量中存储的地址不能被改变,但是地址指向的对象内容可以改变。
final int[] arr = {10, 20, 30};
System.out.println(arr);
// arr = null; // 属于第二次赋值,arr中的地址不能被改变
arr[1] = 200;
System.out.println(arr);
System.out.println(arr[1]);
}
常量
常量概述和基本作用
常量
- 常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
- 常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。
常量命名规范:英文单词全部大写,多个单词下划线连接起来。
public class Constant {
public static final String SCHOOL_NAME = "传智教育";
public static final String LOGIN_NAME = "admin";
public static final String PASS_WORD = "123456";
}
常量的执行原理:
- 在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量。
- 这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。
常量做信息标志和分类
案例说明:
- 现在开发的超级玛丽游戏需要接收用户输入的四个方向的信号(上下左右),以便控制玛丽移动的方向。
选择常量做信息标志和分类:
- 代码可读性好,实现了软编码形式。
代码部分涉及未学内容,无需过多关注
public class ConstantDemo2 {
public static final int UP = 1; // 上
public static final int DOWN = 2; // 上
public static final int LEFT = 3; // 左
public static final int RIGHT = 4; // 右
public static void main(String[] args) {
// 1、创建一个窗口对象(桌子)
JFrame win = new JFrame();
// 2、创建一个面板对象(桌布)
JPanel panel = new JPanel();
// 3、把桌布垫在桌子上
win.add(panel);
// 4、创建四个按钮对象
JButton btn1 = new JButton("上");
JButton btn2 = new JButton("下");
JButton btn3 = new JButton("左");
JButton btn4 = new JButton("右");
// 5、把按钮对象添加到桌布上去
panel.add(btn1);
panel.add(btn2);
panel.add(btn3);
panel.add(btn4);
// 6、显示窗口
win.setLocationRelativeTo(null);
win.setSize(300,400);
win.setVisible(true);
btn1.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(UP) ; // 让玛丽往上跳
}
});
btn2.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(ConstantDemo2.DOWN) ; // 让玛丽往下跳
}
});
btn3.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(LEFT) ; // 让玛丽往左跑
}
});
btn4.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(RIGHT) ; // 让玛丽往右跑
}
});
}
public static void move(int flag){
// 控制玛丽移动
switch (flag) {
case UP:
System.out.println("玛丽往上飞了一下~~");
break;
case DOWN:
System.out.println("玛丽往下蹲一下~~");
break;
case LEFT:
System.out.println("玛丽往左跑~~");
break;
case RIGHT:
System.out.println("玛丽往→跑~~");
break;
}
}
}
枚举
枚举的概述
枚举的概述:
- 枚举是Java中的一种特殊类型。
- 枚举的作用:”是为了做信息的标志和信息的分类“。
定义枚举类的格式:
修饰符 enum 枚举名称{
第一行都是罗列枚举类实例的名称。
}
enum Season{
SPRING , SUMMER , AUTUMN , WINTER;
}
反编译后观察枚举的特征:
Compiled from "Season.java"
public final class Season extends java.lang.Enum<Season> {
public static final Season SPRING = new Season();
public static final Season SUMMER = new Season();
public static final Season AUTUMN = new Season();
public static final Season WINTER = new Season();
public static Season[] values();
public static Season valueOf(java.lang.String);
}
枚举的特征:
- 枚举类都是继承了枚举类型:java.lang.Enum
- 枚举都是最终类,不可以被继承。
- 构造器都是私有的,枚举对外不能创建对象。
- 枚举类的第一行默认都是罗列枚举对象的名称的。
- 枚举类相当于是多例模式。
枚举的使用场景演示
案例说明:
- 现在开发的超级玛丽游戏需要接收用户输入的四个方向的信号(上下左右),以便控制玛丽移动的方向。
选择常量做信息标志和分类:
- 虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨。
枚举做信息标志和分类:
- 代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术!建议使用!
public enum Orientation {
UP, DOWN, LEFT, RIGHT;
}
public class EnumDemo2 {
public static void main(String[] args) {
// 1、创建一个窗口对象(桌子)
JFrame win = new JFrame();
// 2、创建一个面板对象(桌布)
JPanel panel = new JPanel();
// 3、把桌布垫在桌子上
win.add(panel);
// 4、创建四个按钮对象
JButton btn1 = new JButton("上");
JButton btn2 = new JButton("下");
JButton btn3 = new JButton("左");
JButton btn4 = new JButton("右");
// 5、把按钮对象添加到桌布上去
panel.add(btn1);
panel.add(btn2);
panel.add(btn3);
panel.add(btn4);
// 6、显示窗口
win.setLocationRelativeTo(null);
win.setSize(300,400);
win.setVisible(true);
btn1.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(Orientation.UP) ; // 让玛丽往上跳
}
});
btn2.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(Orientation.DOWN) ; // 让玛丽往下跳
}
});
btn3.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(Orientation.LEFT) ; // 让玛丽往左跑
}
});
btn4.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
move(Orientation.RIGHT) ; // 让玛丽往右跑
}
});
}
public static void move(Orientation o){
// 控制玛丽移动
switch (o) {
case UP:
System.out.println("玛丽往上飞了一下~~");
break;
case DOWN:
System.out.println("玛丽往下蹲一下~~");
break;
case LEFT:
System.out.println("玛丽往左跑~~");
break;
case RIGHT:
System.out.println("玛丽往→跑~~");
break;
}
}
}
抽象类
抽象类概述
抽象类:在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。
public abstract class Animal{
public abstract void run();
}
抽象的使用总结与注意事项:
- 抽象类可以理解成类的不完整设计图,是用来被子类继承的。
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
public abstract class Animal {
private String name;
public abstract void run();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Dog extends Animal{
@Override
public void run() {
System.out.println("狗跑很快");
}
}
public class Tiger extends Animal{
@Override
public void run() {
System.out.println("老虎跑很快");
}
}
public class Test {
public static void main(String[] args) {
Tiger t = new Tiger();
t.run();
Dog t1 = new Dog();
t1.run();
}
}
抽象类的案例
系统需求
- 某加油站推出了2种支付卡,一种是预存10000的金卡,后续加油享受8折优惠,另一种是预存5000的银卡 ,后续加油享受8.5折优惠。
- 请分别实现2种卡片进入收银系统后的逻辑,卡片需要包含主人名称,余额,支付功能。
分析实现
- 创建一张卡片父类:定义属性包括主人名称、余额、支付功能(具体实现交给子类)
- 创建一张白金卡类:重写支付功能,按照原价的8折计算输出。
- 创建一张银卡类:重写支付功能,按照原价的8.5折计算输出。
public abstract class Card {
private String name; // 主人名称
private double money;
public abstract void pay(double money);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
public class GoldCard extends Card{
@Override
public void pay(double money) {
// 优惠后的金额算出来:
double rs = money * 0.8;
double lastMoney = getMoney() - rs;
System.out.println(getName() + "当前账户总金额:"
+ getMoney() +",当前消费了:" + rs +",消费后余额剩余:" +
lastMoney);
setMoney(lastMoney); // 更新账户对象余额
}
}
public class Test {
public static void main(String[] args) {
GoldCard c = new GoldCard();
c.setMoney(5000); // 父类的
c.setName("菜比");
c.pay(300);
System.out.println("余额:" + c.getMoney());
}
}
抽象类的特征、注意事项
特征和注意事项:
- 有得有失: 得到了抽象方法,失去了创建对象的能力。
- 抽象类为什么不能创建对象?创建对象调用抽象方法会无法运行。
- 类有的成员(成员变量、方法、构造器)抽象类都具备。(需要被继承,所以一定有构造器)
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
- 不能用abstract修饰变量、代码块、构造器。
final和abstract是什么关系?
- 互斥关系
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
抽象类的应用知识:模板方法模式
使用场景说明:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。
模板方法模式实现步骤
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
- 模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
案例:
需求:
- 活期是0.35%,定期是 1.75%,定期如果满10万额外给予3%的收益。
- 结算利息要先进行用户名、密码验证,验证失败直接提示,登录成功进行结算
分析:
- 创建一个抽象的账户类Account作为父类模板,提供属性(卡号,余额)
- 在父类Account中提供一个模板方法实现登录验证,利息结算、利息输出。
- 具体的利息结算定义成抽象方法,交给子类实现。
- 定义活期账户类,让子类重写实现具体的结算方法
- 定义定期账户类,让子类重写实现具体的结算方法
- 创建账户对象,完成相关功能。
public abstract class Account {
private String cardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
模板方法
*/
public final void handle(String loginName , String passWord ){
// a.判断登录是否成功
if("abcdef".equals(loginName) && "123456".equals(passWord)){
System.out.println("登录成功。。");
// b.正式结算利息
// 当前模板方法知道所有子类账户都要结算利息,但是具体怎么结算,模板不清楚,交给具体的子类来计算
double result = calc();
// c.输出利息详情
System.out.println("本账户利息是:"+ result);
}else{
System.out.println("用户名或者密码错误了");
}
}
public abstract double calc();
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
/**
活期账户
*/
public class CurrentAccount extends Account {
public CurrentAccount(String cardId, double money) {
super(cardId, money);
}
@Override
public double calc() {
// b.正式结算利息
double result = getMoney() * 0.0175; // 结算利息了
return result;
}
}
public class Test {
public static void main(String[] args) {
CurrentAccount acc = new CurrentAccount("ICBC-111", 100000);
acc.handle("abcdef", "123456");
}
}
模板方法我们是建议使用final修饰的,这样会更专业,那么为什么呢?
答:模板方法是给子类直接使用的,不是让子类重写的,一旦子类重写了模板方法就失效了。
模板方法模式解决了什么问题?
- 极大的提高了代码的复用性。
- 模板方法已经定义了通用结构,模板不能确定的定义成抽象方法。
- 使用者只需要关心自己需要实现的功能即可。
接口
接口概述、特点
什么是接口:接口也是一种规范。
接口的定义与特点:
- 接口的格式如下:
接口用关键字interface来定义
public interface 接口名 {
// 常量
// 抽象方法
}
- JDK8之前接口中只能是抽象方法和常量,没有其他成分了。
- 接口不能实例化。
- 接口中的成员都是public修饰的,写不写都是,因为规范的目的是为了公开化。
public interface SportManInterface {
// 接口中的成员:JDK 1.8之前只有常量 和 抽象方法
// public static final 可以省略不写,接口默认会为你加上!
// public static final String SCHOOL_NAME = "菜园";
String SCHOOL_NAME = "菜园";
// 2、抽象方法
// public abstract 可以省略不写,接口默认会为你加上!
// public abstract void run();
void run();
// public abstract void eat();
void eat();
}
public class Test {
public static void main(String[] args) {
// 接口不能创建对象!
// SportManInterface s = new SportManInterface();
}
}
接口的基本使用:被实现
接口的用法:
- 接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类。
修饰符 class 实现类 implements 接口1, 接口2, 接口3 , ... {
}
实现的关键字:implements
- 从上面可以看出,接口可以被类单实现,也可以被类多实现。
接口实现的注意事项:
- 一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。
public interface SportManInterface {
void run();
void competition();
}
public interface Law {
void rule();//遵章守法
}
public class PingPongMan implements SportManInterface, Law{
private String name;
public PingPongMan(String name) {
this.name = name;
}
@Override
public void rule() {
System.out.println(name + "要遵章守法,不能随意外出,酗酒,约会~~~");
}
@Override
public void run() {
System.out.println(name + "必须要跑步训练~~");
}
@Override
public void competition() {
System.out.println(name + "需要参加国际比赛~~");
}
}
接口与接口的关系:多继承
基本小结
- 类和类的关系:单继承。
- 类和接口的关系:多实现。
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口。
接口多继承的作用
- 规范合并,整合多个接口为同一个接口,便于子类实现。
public interface SportMan extends Law, People {
void run();
void competition();
}
public interface Law {
void rule(); // 遵章守法
void eat();
}
public interface People {
void eat();
}
/**
实现类
*/
// public class BasketballMan implements Law, SportMan, People {
public class BasketballMan implements SportMan{
@Override
public void rule() {
}
@Override
public void eat() {
}
@Override
public void run() {
}
@Override
public void competition() {
}
}
JDK8开始接口新增方法
JDK8版本开始后,Java只对接口的成员方法进行了新增:
项目Version2.0需要对Inter接口丰富,加入10个新的抽象方法,此时改了接口就要所有实现类实现这些方法。(非常复杂)
新增:允许接口中直接定义带有方法体的方法
第一种:默认方法
- 类似之前写的普通实例方法:必须用default修饰
- 默认会public修饰。需要用接口的实现类的对象来调用
default void run(){
System.out.println("--开始跑--");
}
第二种:静态方法
- 默认会public修饰,必须static修饰。
- 注意:接口的静态方法必须用本身的接口名来调用。
static void inAddr(){
System.out.println("我们都在学习Java新增方法的语法,它是Java源码自己会用到的~~~");
}
第三种:私有方法
- 就是私有的实例方法:,必须使用private修饰,从JDK 1.9才开始有的。
- 只能在本类中被其他的默认方法或者私有方法访问。
private void go(){
System.out.println("--准备--");
}
实例:
public interface SportManInter {
/**
1、JDK 8开始 :默认方法(实例方法)
-- 必须default修饰,默认用public修饰
-- 默认方法,接口不能创建对象,这个方法只能过继给了实现类,由实现类的对象调用。
*/
default void run(){
go();
System.out.println("跑的很快~~~");
}
/**
2、静态方法
必须使用static修饰,默认用public修饰
-- 接口的静态方法,必须接口名自己调用。
*/
static void inAddr(){
System.out.println("我们都在学习Java新增方法的语法,它是Java源码自己会用到的~~~");
}
/**
3、私有方法(实例方法)
-- JDK 1.9开始才支持的。
-- 必须在接口内部才能被访问
*/
private void go(){
System.out.println("开始跑~~~");
}
}
class PingPongMan implements SportManInter{
}
class Test{
public static void main(String[] args) {
PingPongMan p = new PingPongMan();
p.run();
SportManInter.inAddr();
// PingPongMan.inAddr();
}
}
总结:JDK8开始后新增了那些方法?
- 默认方法:default修饰,实现类对象调用。
- 静态方法:static修饰,必须用当前接口名调用
- 私有方法:private修饰,jdk9开始才有的,只能在接口内部被调用。
- 他们都会默认被public修饰。
注意:JDK8新增的3种方法我们自己在开发中很少使用,通常是Java源码涉及到的,我们需要理解、识别语法、明白调用关系即可。
使用接口的注意事项
- 接口不能创建对象(接口更加抽象,自然不能创建)
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突。(接口不允许子类调用父类的静态方法,接口的静态方法只能自己调)
- 一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。 (就近原则,class离Animal近,若是先写implements 会报错)class Cat extends Animal implements Food{};
- 一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
interface AA{
default void go(){
System.out.println("AA");
}
}
interface BB{
default void go(){
System.out.println("BB");
}
}
class CC implements AA, BB{
@Override
public void go() {
}
}
- 一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
interface AAA{
int run();
}
interface BBB{
void run();
}
interface CCC extends AAA, BBB{//报错
}
面向对象三大特征之三:多态
多态的概述,多态的形式
什么是多态?
- 同类型的对象,执行同一个行为,会表现出不同的行为特征。
多态的常见形式:
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
多态中成员访问特点:
- 方法调用:编译看左边,运行看右边。
- 变量调用:编译看左边,运行也看左边。(多态侧重行为多态)
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~");
}
}
public class Dog extends Animal{
public String name = "狗名称";
@Override
public void run() {
System.out.println("狗跑的贼溜~~~~~");
}
}
public class Tortoise extends Animal{
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("乌龟跑的非常慢~~~");
}
}
public class Test {
public static void main(String[] args) {
// 目标:先认识多态的形式
// 父类 对象名称 = new 子类构造器();
Animal a = new Dog();
a.run(); //狗 方法调用:编译看左,运行看右
System.out.println(a.name); // 变量调用:编译看左,运行也看左,动物名称
Animal a1 = new Tortoise();
a1.run();//乌龟
System.out.println(a1.name); // 动物名称
}
}
多态的前提
- 有继承/实现关系;有父类引用指向子类对象;有方法重写。
多态的优势
优势
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
Animal a = new Tortoise();
a.run(); // 后续业务行为随对象而变,后续代码无需修改
- 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
多态下会产生的一个问题: 多态下不能使用子类的独有功能。(下方解决)
多态下引用数据类型的类型转换
自动类型转换(从子到父):子类对象赋值给父类类型的变量指向。
强制类型转换(从父到子):
- 此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
- 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
- 注意: 如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
- Java建议强转转换前使用instanceof判断当前对象的真实类型,再进行强制转换
- 变量名 instanceof 真实类型
判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之false.
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~");
}
}
public class Dog extends Animal {
public String name = "狗名称";
@Override
public void run() {
System.out.println("🐕跑的贼溜~~~~~");
}
/**
独有功能
*/
public void lookDoor(){
System.out.println("🐕在看🚪!!!");
}
}
public class Tortoise extends Animal {
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("🐢跑的非常慢~~~");
}
/**
独有功能
*/
public void layEggs(){
System.out.println("🐢在下蛋~~~");
}
}
public class Test {
public static void main(String[] args) {
//自动类型转换
Animal a = new Dog();
a.run();
//a.lookDour; 自动类型转换无法调用子类独有功能
//强制类型转换可以调用子类独有功能
Dog d = (Dog)a;
d.lookDoor();
// 注意:多态下直接强制类型转换,可能出现类型转换异常
// 规定:有继承或者实现关系的2个类型就可以强制类型转换,运行时可能出现问题。
// Tortoise t1 = (Tortoise) a;
// 建议强制转换前,先判断变量指向对象的真实类型,再强制类型转换。
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
Animal a1 = new Tortoise();
go(a1);
}
public static void go(Animal a){
System.out.println("预备~~~");
a.run();
// 独有功能
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
System.out.println("结束~~~~");
}
}
多态的综合案例
需求:
- 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
- 鼠标:被安装时可以完成接入、调用点击功能、拔出功能。
- 键盘:被安装时可以完成接入、调用打字功能、拔出功能。
分析
- 定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。
- 提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。
- 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。
public interface USB {
void connected();
void unconnected();
}
public class Mouse implements USB{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Mouse() {
}
public Mouse(String name) {
this.name = name;
}
@Override
public void connected() {
System.out.println(name + "成功的接入了设备了~~~");
}
@Override
public void unconnected() {
System.out.println(name + "成功的从设备弹出了~~~");
}
/**
独有功能
*/
public void click(){
System.out.println(name + "双击点亮小红心~~~~");
}
}
public class KeyBoard implements USB{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public KeyBoard() {
}
public KeyBoard(String name) {
this.name = name;
}
@Override
public void connected() {
System.out.println(name + "成功的接入了设备了~~~");
}
@Override
public void unconnected() {
System.out.println(name + "成功的从设备弹出了~~~");
}
/**
独有功能
*/
public void keyDown(){
System.out.println(name + "写下了:老铁,6666,下次再来哦,老弟~~~~");
}
}
public class Computer {
/**
提供一个安装的入口:行为。
*/
public void installUSB(USB u){
u.connected();
if(u instanceof Mouse){
Mouse m = (Mouse) u;
m.click();
}else if(u instanceof KeyBoard){
KeyBoard k = (KeyBoard) u;
k.keyDown();
}
u.unconnected();
}
}
public class Test {
public static void main(String[] args) {
// a、创建电脑对象
Computer c = new Computer();
// b、创建USB设备对象
USB u = new Mouse("鼠标");
c.installUSB(u);
USB k = new KeyBoard("键盘");
c.installUSB(k);
}
}
内部类
内部类概述
内部类
- 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。
public class People{
// 内部类
public class Heart{
}
}
内部类的使用场景、作用
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。(汽车与发动机)
- 内部类通常可以方便访问外部类的成员,包括私有的成员。
- 内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制。
内部类的分类
- 静态内部类[了解]
- 成员内部类(非静态内部类) [了解]
- 局部内部类[了解]
- 匿名内部类(重点)
内部类之一:静态内部类[了解]
什么是静态内部类?
- 有static修饰,属于外部类本身。
- 它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer{
// 静态成员内部类
public static class Inner{
}
}
静态内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
范例:Outer.Inner in = new Outer.Inner();
静态内部类的访问拓展:
1、静态内部类中是否可以直接访问外部类的静态成员?
- 可以,外部类的静态成员只有一份可以被共享访问。
2、静态内部类中是否可以直接访问外部类的实例成员?
- 不可以的,外部类的实例成员必须用外部类对象访问。
内部类之二:成员内部类[了解]
什么是成员内部类?
- 无static修饰,属于外部类的对象。
- JDK16之前,成员内部类中不能定义静态成员,JDK 16开始也可以定义静态成员了。
public class Outer {
// 成员内部类
public class Inner {
}
}
成员内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();
范例:Outer.Inner in = new Outer().new Inner();
成员内部类的访问拓展:
1、成员内部类中是否可以直接访问外部类的静态成员?
- 可以,外部类的静态成员只有一份可以被共享访问。
2、成员内部类的实例方法中是否可以直接访问外部类的实例成员?
- 可以的,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员
内部类之三:局部内部类[了解]
局部内部类 (鸡肋语法,了解即可)
- 局部内部类放在方法、代码块、构造器等执行体中。
- 局部内部类的类文件名为: 外部类$N内部类.class。
内部类之四:匿名内部类概述[重点]
匿名内部类:
- 本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等。
- 作用:方便创建子类对象,最终目的为了简化代码编写。
格式:
new 类|抽象类名|或者接口名() {
重写方法;
};
Animal a = new Animal() {
public void run() {
}
};
a. run();
特点总结:
- 匿名内部类是一个没有名字的内部类。
- 匿名内部类写出来就会产生一个匿名内部类的对象。
- 匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。
public class Test {
public static void main(String[] args) {
Animal a = new Animal(){
@Override
public void run() {
System.out.println("老虎跑的块~~~");//相当于Animal的子类老虎
}
};
a.run();
}
}
匿名内部类常见使用形式
某个学校需要让老师,学生,运动员一起参加游泳比赛
public class Test {
public static void main(String[] args) {
Swimming s = new Swimming() {
@Override
public void swim() {
System.out.println("快乐的游泳");
}
};
go(new Swimming() {
@Override
public void swim() {
System.out.println("老师游的很快");
}
});
}
public static void go(Swimming s){
System.out.println("开始");
s.swim();
System.out.println("结束");
}
}
interface Swimming{
void swim();
}
总结:匿名内部类可以作为方法的实际参数进行传输。
匿名内部类真实使用场景演示
/**
目标:通过GUI编程 理解匿名内部类的真实使用场景
*/
public class Test3 {
public static void main(String[] args) {
// 1、创建窗口
JFrame win = new JFrame("登录界面");
JPanel panel = new JPanel();
win.add(panel);
// 2、创建一个按钮对象
JButton btn = new JButton("登录");
// 注意:讲解匿名内部类的使用
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(win, "点我一下,说明爱我!");
}
});
// btn.addActionListener( e -> JOptionPane.showMessageDialog(win, "别说话,吻我!!") );
// 3、把按钮对象添加到桌布上展示
panel.add(btn);
// 4、展示窗口
win.setSize(400, 300);
win.setLocationRelativeTo(null);
win.setVisible(true);
}
}
总结:开发中不是我们主动去定义匿名内部类的,而是别人需要我们写或者我们可以写的时候才会使用。
匿名内部类的代码可以实现代码进一步的简化(回扣主题)
常用API
Object
Object类的作用:
- Object类的方法是一切子类对象都可以直接使用的,所以我们要学习Object类的方法。
- 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类。
Object类的常用方法:
方法名 | 说明 |
---|---|
public String toString() | 默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址 |
public boolean equals(Object o) | 默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false |
问题引出
- 开发中直接输出对象,默认输出对象的地址其实是毫无意义的。
- 开发中输出对象变量,更多的时候是希望看到对象的内容数据而不是对象的地址信息。
toString存在的意义
- 父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息!!
equals存在的意义
- 父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
@Override
public boolean equals(Object o){
// 1、判断o是不是学生类型
if(o instanceof Student){
Student s2 = (Student) o;
// 2、判断2个对象的内容是否一样。
// if(this.name.equals(s2.name) &&
// this.age == s2.age && this.sex == s2.sex){
// return true;
// }else {
// return false;
// }
return this.name.equals(s2.name) && this.age == s2.age
&& this.sex == s2.sex ;
}else {
// 学生只能和学生比较,否则结果一定是false
return false;
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
'}';
}
Objects
Objects概述:Objects是一个工具类,提供了一些方法去完成一些功能。
官方在进行字符串比较时,没有用字符串对象的的equals方法,而是选择了Objects的equals方法来比较。
@Override
public boolean equals(Object o) {
// 1、判断是否是同一个对象比较,如果是返回true。
if (this == o) return true;
// 2、如果o是null返回false 如果o不是学生类型返回false ...Student != ..Pig
if (o == null || this.getClass() != o.getClass()) return false;
// 3、说明o一定是学生类型而且不为null
Student student = (Student) o;
return sex == student.sex && age == student.age && Objects.equals(name, student.name);
}
使用Objects的equals方法在进行对象的比较会更安全。
Objects的常见方法:
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较 |
public static boolean isNull(Object obj) | 判断变量是否为null ,为null返回true ,反之false |
源码分析
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
对象进行内容比较的时候建议使用什么?为什么?
- 建议使用Objects提供的equals方法。
- 比较的结果是一样的,但是更安全。
StringBuilder
StringBuilder概述
- StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。
- 作用:提高字符串的操作效率,如拼接、修改等。
StringBuilder构造器
名称 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象 |
StringBuilder常用方法
方法名称 | 说明 |
---|---|
public StringBuilder append(任意类型) | 添加数据并返回StringBuilder对象本身 |
public StringBuilder reverse() | 将对象的内容反转 |
public int length() | 返回对象内容长度 |
public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
String类拼接字符串原理图
生成String变量也是通过了StringBuilder,每次要产生两个对象。
StringBuilder提高效率原理图
只产生一个对象即可完成任务
总结:为什么拼接、反转字符串建议使用StringBuilder?
- String :内容是不可变的、拼接字符串性能差。
- StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
- 定义字符串使用String
- 拼接、修改等操作字符串使用StringBuilder
案例:打印整型数组内容
需求:
设计一个方法用于输出任意整型数组的内容,要求输出成如下格式:
“该数组内容为:[11, 22, 33, 44, 55]”
分析:
1、定义一个方法,要求该方法能够接收数组,并输出数组内容。 —> 需要参数吗?需要返回值类型申明吗?
2、定义一个静态初始化的数组,调用该方法,并传入该数组。
public class StringBuilderTest2 {
public static void main(String[] args) {
int[] arr1 = null;
System.out.println(toString(arr1));
int[] arr2 = {10, 88, 99};
System.out.println(toString(arr2));
int[] arr3 = {};
System.out.println(toString(arr3));
}
/**
1、定义方法接收任意整型数组,返回数组内容格式
*/
public static String toString(int[] arr){
if(arr != null){
// 2、开始拼接内容。
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i] ).append(i == arr.length - 1 ? "" : ", ");
}
sb.append("]");
return sb.toString();
}else {
return null;
}
}
}
Math
Math类
- 包含执行基本数字运算的方法,Math类没有提供公开的构造器。
- 如何使用类中的成员呢?看类的成员是否都是静态的,如果是,通过类名就可以直接调用。
Math 类的常用方法
方法名 | 说明 |
---|---|
public static int abs(int a) | 获取参数绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入 |
public static int max(int a,int b) | 获取两个int值中的较大值 |
public static double pow(double a,double b) | 返回a的b次幂的值 |
public static double random() | 返回值为double的随机值,范围[0.0,1.0) |
System
System类概述:System也是一个工具类,代表了当前系统,提供了一些与系统相关的方法。
System 类的常用方法
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的 Java 虚拟机,非零表示异常终止 |
public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式 |
public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组拷贝 |
时间毫秒值
- 计算机认为时间是有起点的,起始时间: 1970年1月1日 00:00:00
- 时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总的毫秒数,应该是很大的。 1s = 1000ms。
原因:
- 1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。
BigDecimal
BigDecimal作用:用于解决浮点型运算精度失真的问题。
使用步骤:创建对象BigDecimal封装浮点型数据(最好的方式是调用方法)
- public static BigDecimal valueOf(double val): 包装浮点数成为BigDecimal对象。BigDecimal b1 = BigDecimal.valueOf(0.1);
BigDecima常用API
方法名 | 说明 |
---|---|
public BigDecimal add(BigDecimal b) | 加法 |
public BigDecimal subtract(BigDecimal b) | 减法 |
public BigDecimal multiply(BigDecimal b) | 乘法 |
public BigDecimal divide(BigDecimal b) | 除法 |
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) | 除法 |
BigDecimal divide = bd1.divide(参与运算的对象, 小数点后精确到多少位, 舍入模式);
参数1 ,表示参与运算的BigDecimal 对象。
参数2 ,表示小数点后面精确到多少位
参数3 ,舍入模式
BigDecimal.ROUND_UP 进一法
BigDecimal.ROUND_FLOOR 去尾法
BigDecimal.ROUND_HALF_UP 四舍五入
面向对象进阶部分完结!