方法(即函数)
what is 方法
1、理解:方法是程序中最小的执行单元,如main方法【其实就是函数】
2、什么时候用,有什么用:重复的代码、或具有普遍特性和功能的代码可以抽取到方法中,如发射功能
3、好处
- 将一些代码打包在一起,提高代码的复用性
- 提高代码的可维护性
4、写方法时注意思维,三连问:
我要干嘛?我需要用到什么?是否需要返回值?
方法的格式
1、分为两步:(1)方法定义(2)方法调用
2、形参和实参
- 形参:方法定义中的参数
- 实参:方法调用中的参数
3、注意事项
- 方法不调用就不执行
- 方法与方法平级,且不能互相嵌套(可以嵌套)
- return关键字
- 方法没有返回值:可以省略不写;如果一定要写,只写
return
,后面不能跟具体数据,表示结束方法- 方法有返回值:必须要写,且和返回值类型要一致
- return与方法有关,与循环无关,执行return后整个方法全部结束(包括方法里的循环)(与break有点像)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
//不带返回值的方法定义和调用 //缺点:大部分情况我们希望能够拿到返回值并用该值进行后续一系列操作,而不是把值打印在控制台上 public static void 方法名(参数1,参数2,...) {...} public static void main(String[] args){ getSum(10,20); //实参 } public static void getSum(int num1,int num2){ //形参 int result = num1 + num2; System.out.println(result); } //----------------------------------------------------------------- //带返回值的方法定义和调用 public static 返回值类型 方法名(参数){ 方法体; return 返回值; } public static void main(String[] args){ //直接调用,上述示例就是 int sum = getSum(10,20); //赋值调用 System.out.println(getSum(10,20)); //输出调用 } public static int getSum(int a,int b){ int result = a + b; return result; }
方法重载
这样定义方法和调用方法就不需要那么麻烦了,只需要一个名称就可以了
1 2 3 4 5 6 7 8 9 10
class Demo1{ public static void Sum(int num1,int num2){ return num1 + num2; } public static void Sum(int num1,int num2,int num3){ return num1 + num2 + num3; } }
方法内存图★
- 方法被调用之后就会进栈运行(因为可以嵌套,符合后进先出)
- 方法执行完毕之后,出栈,方法里定义的变量随之消失
基本和引用数据类型🤔
- 简单记忆:除了基本数据类型其他都是引用数据类型
- 理解记忆:(内存角度理解)只要是new出来的都是引用数据类型,用的不是真实的数据
基本数据类型变量中存储的是真实的值;而引用数据类型中变量存储的是地址值而不是真实的值,即引用了其他空间中的数据
- 基本数据类型:存储的真实数据
- 赋值给其他变量的,也是赋的真实值
- 引用数据类型:存储的是地址值,真实的数据存储在其他空间中
- 赋值给其他变量,赋的是地址值
面向对象
简单理解就是找现成工具使用。重点学的就是:学习获取已有对象并使用、学习如何设计对象并使用。
类和对象
(1)理解
在java中,必须先设计类,才能获得对象。两者之间的关系为:
- 类:是对象共同特征的描述(设计图)
- 对象:是真实存在的具体东西/实例(根据设计图创造出的工具)
- new一个东西就是创建了一个对象
1 2 3 4
类是一个模板,描述对象的状态和行为 对象是类的一个实例,有状态和行为 方法就是行为,一个类可以有很多行为 实例变量就是对象的实例,对象状态由zhe'x
(2)类的定义
类的组成是由属性和行为两部分组成,图如上
- 属性:在类中通过成员变量来体现
- 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class Phone { //成员变量 String brand; int price; //成员方法 public void call() { System.out.println("打电话"); } public void sendMessage() { System.out.println("发短信"); } }
(3)对象的使用
- 创建对象的格式:
类名 对象名 = new 类名();
- 调用成员的格式:
对象名.成员变量
、对象名.成员方法()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class PhoneDemo { public static void main(String[] args) { //创建对象 Phone p = new Phone(); //使用成员变量 System.out.println(p.brand); System.out.println(p.price); p.brand = "小米"; p.price = 2999; System.out.println(p.brand); System.out.println(p.price); //使用成员方法 p.call(); p.sendMessage(); } }
(4)类的注意事项
- 用来描述一类事物的类,专业叫做:
Javabean类
。在Javabean类
中,是不写main方法的;- 之前,编写main方法的类,叫做
测试类
。我们可以在测试类中创建javabean类的对象并进行赋值调用;- 建议一个java文件只写一个类,因为一个文件中只能有一个类是public修饰的,写多个类没意义
类名首字母大写,驼峰命名。类中成员变量的完整定义格式为:
修饰符 数据类型 变量名称 = 初始化值;
数据类型 变量名称;
(一般写法,比如上面定义的手机类)但是修饰符可以先不写(还没学到);因为类是抽象的,一般也不写初始化值(虽然没写,但会有一个默认值),而是在创建对象的时候赋值。
封装
介绍
(1)理解
封装是面向对象三大特征之一(封装,继承,多态)
封装就是把数据的属性和方法捆绑在一起,形成一个独立的单元。
封装代码实现将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。访问成员变量private,提供对应的get xxx()/set xxx()方法。
(2)封装好处
那可太多了,首先就是让编程变简单,
java API文档
已经有很多封装好的类以及对应的方法(比如String类,里面封装了很多方法),只需要查文档,找到相应的类,调方法就行。用什么找什么。
private 关键字
为了使数据更安全,防止非法数据,在编写成员变量和方法时看情况使用private关键字。private是一个权限修饰符,可以用来修饰成员(成员变量,成员方法)
被private修饰的成员,只能在本类进行访问,而public正好相反,能被所有类访问
针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作:
- 提供
get变量名()
方法,用于获取成员变量的值,方法用public修饰(无参有返回)
- 因为无返回值,类型是void
- 提供
set变量名(参数)
方法,用于设置成员变量的值,方法用public修饰(有参无返回)
- 有返回值,类型是定义的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
class Student { //成员变量 private String name; private int age; //get/set方法 public void setName(String n) { name = n; } public String getName() { return name; } public void setAge(int a) { //可以设置年龄范围,超出此范围非法 age = a; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); } } //----------------------- 学生测试类--------------- public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); //使用set方法给成员变量赋值 s.setName("林青霞"); s.setAge(30); s.show(); //使用get方法获取成员变量的值 System.out.println(s.getName() + "---" + s.getAge()); System.out.println(s.getName() + "," + s.getAge()); } }
上面代码存在一个问题,即变量名字要知其义,如果改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//--------------错误示例,输出为null------------ class Student { //成员变量 private String name; //默认值为null private int age; //get/set方法 public void setName(String name) { name = name; } public String getName() { return name; } }
这样的话,无论在测试类中传进来的参数是什么,输出得到的name永远是null(形参name自己赋给自己,成员变量name值没有变,仍为null)。这需要先了解两个概念:
- 成员变量和局部变量
根据就近原则,age输出的是局部变量值10。如果想要输出成员变量值null,就需要this关键字。
1 2 3 4 5 6 7
public class GirFriend{ private int age; public void method(){ int age = 10; System.out.printIn(this.age); } }
this 关键字
- this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
public class Student { private String name; private int age; public void setName(String name) { //等号右边:name为局部变量,即形参中传递的数据 //等号左边:成员变量 this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); } }
构造方法
作用:在创建对象的时候给成员变量赋值的,完成对象数据的初始化
分类:无参构造(通过set、get赋值和取值)和有参构造(创建对象时自动初始化,get取值)
格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class Student{ private String name; private int age; // ---空参构造方法--- public Student(){ ... } // ---带参构造方法--- public Student(String name,int age){ ... } }
特点:
- 方法名与类名相同,大小写也要一致
- 没有返回值类型,连void也没有
- 没有返回值,不能写return
执行时机:
- 创建对象的时候,虚拟机会自动调用构造方法,作用是给成员变量初始化的
- 每创建一次对象,就会调用一次构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
class Student { private String name; private int age; //-------------------分割线----------------- public Student() {} //空参构造 public Student(String name,int age) { //有参构造 this.name = name; this.age = age; } //-------------------分割线------------------ public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); } } //--------------------下面是测试类------------ public class StudentDemo { public static void main(String[] args) { //创建对象,调用空参构造,后续赋值的话通过set/get赋值 Student s1 = new Student(); s1.show(); //创建对象,调用有参构造 Student s2 = new Student("林青霞",30); s2.show(); } }
注意事项:
- 构造方法的创建
- 如果没有定义构造方法,系统将给出一个默认的无参数构造方法(上面写出的就是)
- 如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
- 带参构造和无参构造,两者方法名相同,但参数不同,这叫构造方法的重载
- 推荐的使用方式
- 无论是否使用,带参构造和无参构造都写
任何类定义出来,默认就自带了无参构造器,写不写都有;
但一旦定义了有参构造,默认的无参构造就消失了,此时需要自己写无参构造器了;
标准JavaBean类
以上就是面向对象的全部知识,下面就是整合上面所学知识。
- 类名驼峰命名、成员变量使用private修饰
- 提供两个构造方法:无参构造和带参构造
- get()和set()方法:提供每一个成员变量对应的set/get方法
- 其他行为方法
带参和无参构造方法、get/set方法生成快捷键:
alt + insert
;插件:PTG-1秒生成标准javabean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
class Student { //成员变量 private String name; private int age; //无参和带参构造方法 public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } //成员方法:get/set public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } //其他方法 public void show() { System.out.println(name + "," + age); } }
对象内存图
前面学过 java 的内存空间,先学习其中三个:
- 栈:方法以及方法内的变量运行时使用的内存
- 堆:new创建出来的存储在堆中,会为其分配一段地址空间,返回的值一个地址起始值
- 方法区:类的字节码文件加载时进入此内存
多个对象内存图
- 多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份
- 创建对象时,对象存储的是一个地址值(new出来的存在堆中,堆给出的是地址值)
- 对象中的成员变量是存储在堆中的
多个引用指向同一对象
基本&引用数据类型
- 基本数据类型:存的是真实值,存在自己空间中
- 赋值给其他变量,也是赋的真实的值
- 引用数据类型:自己空间存的是地址值,即引用的是别的地址空间存的值(数据存在其他空间)
- 赋值给其他变量,赋的是地址值
this的内存原理
- this的作用:区分局部变量和成员变量
- this的本质:所在方法调用者的地址值
成员变量和局部变量
了解即可
字符串
java API
API,应用程序接口。(学会使用帮助文档)
- API:就是JDK中提供的各种功能的Java类(这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习如何使用即可)
- 简单理解:别人已经写好的东西,不需要自己编写,直接使用即可
String类-1
(1)String类概述
String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!
(2)String类的特点
- 字符串不可变,它们的值在创建后不能被更改
- 虽然 String 的值是不可变的,但是它们可以被共享
- 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
1 2 3
String name = "嘿嘿" name = "111" // "嘿嘿"的内容并没有被“111”代替,而是创建了新的字符串“111”,name指向它而已
(3)String类的构造方法/创建String对象的方式
方法名 说明 public String() 创建一个空白字符串对象,不含有任何内容 public String(char[] chs) 根据字符数组的内容,来创建字符串对象 public String(byte[] bys) 根据字节数组的内容,来创建字符串对象 String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc,最常用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class StringDemo01 { public static void main(String[] args) { //public String():创建一个空白字符串对象,不含有任何内容 String s1 = new String(); System.out.println("s1:" + s1); //public String(char[] chs):根据字符数组的内容,来创建字符串对象 char[] chs = {'a', 'b', 'c'}; String s2 = new String(chs); System.out.println("s2:" + s2); //public String(byte[] bys):根据字节数组的内容,来创建字符串对象(查码表) byte[] bys = {97, 98, 99}; String s3 = new String(bys); System.out.println("s3:" + s3); //String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc(最常用) String s4 = "abc"; System.out.println("s4:" + s4); } }
(4)字符串创建方式区别
通过构造方法创建
通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同
直接赋值方式创建
以“”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护(有相同的字符串直接复用)
字符串操作-1
下面学习字符串的常见操作,也就是String类中方法的使用
字符串比较
(1)==原理
- 比较基本数据类型:比较的是具体的值
- 比较引用数据类型:比较的是对象地址值
(2)equals方法的使用
1 2
boolean equals(String s) 比较两个字符串内容是否相同、区分大小写 boolean equalslgnoreCase(String s) 忽略大小写比较
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
public class StringDemo02 { public static void main(String[] args) { //构造方法的方式得到对象 char[] chs = {'a', 'b', 'c'}; String s1 = new String(chs); String s2 = new String(chs); Scanner sc = new Scanner(System.in); String str1 = sc.next(); //直接赋值的方式得到对象 String s3 = "abc"; String s4 = "abc"; //比较字符串对象地址是否相同 System.out.println(s1 == s2); //False System.out.println(s1 == s3); //False System.out.println(str1 == s1); //False System.out.println(s3 == s4); //True System.out.println("--------"); //比较字符串内容是否相同 System.out.println(s1.equals(s2)); //True System.out.println(s1.equals(s3)); //True System.out.println(s3.equals(s4)); //True } }
- Scanner键盘录入的内容是new出来的
- 如果想比较字符串的内容,用String里的方法
遍历字符串
两个方法:
- charAt():会根据索引获取对应的字符
- length(): 会返回字符串的长度
1 2 3 4 5
public char charAt(int index): 根据索引返回字符 public int length(): 返回此字符串的长度 数组的长度:数组名.length 是属性,不加括号 字符串的长度:字符串对象.length() 是方法,加括号
键盘录入一个字符串,使用程序实现在控制台遍历该字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class Test2字符串直接遍历 { public static void main(String[] args) { //1.键盘录入一个字符串 Scanner sc = new Scanner(System.in); System.out.println("请输入字符串"); String str = sc.next(); System.out.println(str); //2.遍历 for (int i = 0; i < str.length(); i++) { //快捷键:str.length().fori //ctrl + alt + V 自动生成左边的接受变量 char c = str.charAt(i); System.out.println(c); } } }
统计字符串
键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
public class Test4统计个数 { public static void main(String[] args) { //1.键盘录入一个字符串 Scanner sc = new Scanner(System.in); System.out.println("请输入一个字符串"); String str = sc.next(); //2.统计 --- 计数器count int bigCount = 0; int smallCount = 0; int numberCount = 0; //得到这个字符串里面每一个字符 for (int i = 0; i < str.length(); i++) { //i 表示字符串中的索引 //c 表示字符串中的每一个字符 char c = str.charAt(i); //char类型的变量在计算时会自动转换为int,查询码表(数字也应该带引号比较) if (c >= 'a' && c <= 'z') { smallCount++; }else if(c >= 'A' && c <= 'Z'){ bigCount++; }else if(c >= '0' && c <= '9'){ numberCount++; } } //3.当循环结束之后,三个变量记录的就是对应的个数 System.out.println("大写字符有:" + bigCount + "个"); System.out.println("小写字符有:" + smallCount + "个"); System.out.println("数字字符有:" + numberCount + "个"); } }
字符串反转
定义一个方法,实现字符串反转。键盘录入一个字符串,调用该方法后,在控制台输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
public class Test6反转字符串 { public static void main(String[] args) { //1.定义一个字符串 Scanner sc = new Scanner(System.in); System.out.println("请输入一个字符串"); String str = sc.next(); //2.定义一个方法,反转字符串,可以把字符串倒着遍历,再拼接 String result = reverse(str); System.out.println(result); } //把传递进来的字符串进行反转 public static String reverse(String str){ //核心思想:倒着遍历并进行拼接就可以了 //fori :正着遍历 forr:倒着遍历 String s = ""; //拼接 for (int i = str.length() - 1; i >= 0; i--) { s = s + str.charAt(i); } return s; } }
字符串截取
字符串子串截取方法,下面是方法实现(方法重载)
- String substring(int beginindex, int endindex) 左闭右开,截取后原字符串不变,因为字符串不会发生改变
- String substring(int beginindex) 截取到末尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class Stringdemo { public static void main(String[] args) { String phoneNumber = "1313669913" //截取手机号码中的前三位 phoneNumber.substring(0, 3); String star = phoneNumber.substring(0, 3); //注意,字符串截取并不会对原字符串产生影响,如果想得到截取的子串,需要赋给变量接受 System.out.println(phoneNumber); //1313669913 System.out.println(star); //131 } }
- 因为字符串(截取等)不会对原串发生影响,因此如果想要对字符串修改,比如屏蔽中间几位,就需要分别截取前中后三段并对中间修改,在拼接后实现
字符串替换
- replace(target, replacement) 将字符串target替换为replacement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
public class Test10多个敏感词替换 { public static void main(String[] args) { //1.先键盘录入要说的话 Scanner sc = new Scanner(System.in); System.out.println("请输入要说的话"); String talk = sc.next(); //2.定义一个数组用来存多个敏感词 String[] arr = {"TMD","GDX","ctmd","ZZ","lj","FW","nt"}; //3.把说的话中所有的敏感词都替换为*** for (int i = 0; i < arr.length; i++) { talk = talk.replace(arr[i],"***"); } //4.打印结果 System.out.println(talk); //后裔你玩什么啊,***,***,***,*** } }
StringBuilder类-2
(1)概述
StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的。(与字符串最大区别)
作用:提高字符串的操作效率
应用场景:当我们在拼接字符串和反转字符串的时候会使用到
1 2 3 4 5
String result = s1 + s2 + ... + s99; //假设s1-s99都是字符串 /* 这样的话效率很低,因为字符串内容不可边,从左往右计算时,每两个相加都会产生一个新的字符串,会产生很多没用的中间结果,影响效率 所以可以使用StringBuilder来提高效率,它可看作是一个容器,其内容可变,在拼接时字符串都放进这个容器,只会有一个StringBuilder对象 */
(2)构造方法
- StringBuilder() 空参构造,创建一个空白可变字符串对象,不含任何内容
- StringBuilder(String str) 创建(包含所给字符串str的)可变字符串对象
(3)StringBuilder 常用方法
- public StringBuilder append(任意类型) 添加数据,并返回对象本身
- public StringBuilder reverse() 反转容器中的内容
- public int length() 返回长度,字符出现的个数
- public String toString() 把StringBuilder对象转换为String
因为其内容是可变的,因为对其的操作都是直接作用在其自身的,修改后不需要赋给新变量接受
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
public class StringBuilderDemo3 { public static void main(String[] args) { //1.创建对象 StringBuilder sb = new StringBuilder("abc"); //添加元素 sb.append(1); sb.append(2.3); sb.append(true); //反转 sb.reverse(); //获取长度 int len = sb.length(); System.out.println(len); //普及:因为StringBuilder是Java已经写好的类 //java在底层对他做了一些特殊处理。因此打印对象不是地址值而是属性值 System.out.println(sb); //1.创建对象 StringBuilder sb = new StringBuilder(); //2.添加字符串 sb.append("aaa").append("bbb").append("ccc").append("ddd"); System.out.println(sb); //aaabbbcccddd //3.再把StringBuilder变回字符串 String str = sb.toString(); System.out.println(str); //aaabbbcccddd } }
(4)链式编程
上面涉及到了一个概念,链式编程:当我们在调用一个方法时,不需要用变量接受其结果,可以继续调用其他方法
1 2
int len = s1.substring(1).replace("A","Q").length(); //s1是字符串 sb.append("aaa").append("bbb").append("ccc").append("ddd"); //sb是StringBuilder对象
字符串操作-2
对称字符串
键盘接受一个字符串,程序判断出该字符串是否是对称字符串,并在控制台打印是或不是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
public class StringBuilderDemo6 { public static void main(String[] args) { //1.键盘录入一个字符串 Scanner sc = new Scanner(System.in); System.out.println("请输入一个字符串"); String str = sc.next(); //2.反转键盘录入的字符串 String result = new StringBuilder().append(str).reverse().toString(); //3.比较 if(str.equals(result)){ System.out.println("当前字符串是对称字符串"); }else{ System.out.println("当前字符串不是对称字符串"); } } }
拼接字符串
需求:定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
//例如:数组为int[] arr = {1,2,3};执行方法后的输出结果为:[1, 2, 3] public class StringBuilderDemo7 { public static void main(String[] args) { int[] arr = {1,2,3}; //调用方法把数组变成字符串 String str = arrToString(arr); System.out.println(str); } public static String arrToString(int[] arr){ StringBuilder sb = new StringBuilder(); sb.append("["); for (int i = 0; i < arr.length; i++) { if(i == arr.length - 1){ sb.append(arr[i]); }else{ sb.append(arr[i]).append(", "); } } sb.append("]"); return sb.toString(); } }
StringJoiner类-3
- StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
- 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
- JDK8出现的
(1)构造方法
- public StringJoiner(间隔符号) 创建一个StringJoiner对象,指定拼接时的符号
- public StringJoiner(间隔符号,开始符号,结束符号) 创建一个StringJoiner对象,指定拼接、开始、结束符号
(2)成员方法
- public StringJoiner add(内容) 添加数据,并返回对象本身
- public int length() 返回长度(字符出现个数,包括添加的符号和空格)
- public String toString() 转换为字符串
1 2 3 4 5 6 7 8 9 10
//1.创建对象 StringJoiner sj = new StringJoiner(", ","[","]"); //2.添加元素 sj.add("aaa").add("bbb").add("ccc"); int len = sj.length(); System.out.println(len); //15 //3.打印 System.out.println(sj);//[aaa, bbb, ccc] String str = sj.toString(); System.out.println(str);//[aaa, bbb, ccc]
字符串原理
首先,字符串是引用类型的,存的是地址值
其次,字符串内容不可改,不可改,不可改!
如果我们看到要修改字符串的内容,有两个办法:
用subString进行截取,把左边的字符截取出来拼接到右侧去
可以把字符串先变成一个字符数组,然后调整字符数组里面数据,最后再把字符数组变成字符串。
(1)字符串存储的内存原理
- 直接赋值会复用字符串常量池中的
- 先检查字符串常量池中有没有字符串,如果有,不会创建新的,而是直接复用。如果没有,才会创建一个新的。
- new出来的不会复用,而是开辟一个新的空间
(2)==
- 基本数据类型比较的是数据值
- 引用数据类型比较的是地址值
(3)字符串拼接底层原理
…看视频了解即可…
如果很多字符串变量拼接,不要直接+。在底层会创建多个对象,浪费时间,浪费性能,可以使用StringBuilder或StringJoiner。
(4)StringBuilder提高效率原理图
会创建一个容器(一个地址),添加字符串时都是添加到这个容器内,不会产生额外对象
集合ArrayList
集合 vs 数组
- 长度
- 集合长度可变:添加数据的时候不需要考虑索引,默认将数据添加到末尾(自动变化)
- 而数组长度不可变
- 存储类型
- 集合只能存储引用数据类型,如果要存基本数据类型,需要先变成其对应的包装类
- 而数组既可以存基本数据类型,也可以存引用数据类型
ArrayList概述
- 什么是集合:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变
- ArrayList集合特点:长度可以变化,只能存储引用数据类型。
- 泛型的使用:用于约束集合中存储元素的数据类型(泛型写什么看元素类型)
因为 ArrayList 只能存储引用数据类型,创建对象时需要限定数据类型,例如创建数据对象时
int[] arr = {1,2,3}
,限定了此数组只能存储int类型数据。所以用泛型来限定集合的存储类型。
1 2 3 4 5 6 7 8 9 10 11 12
public class ArrayListDemo{ public static void main(String[] args){ //1.创建集合的对象 //创建的是ArrayList的对象,而ArrayList是java已经写好的一个类 //这个类在底层做了一些处理 //打印对象输出的不是地址值,而是集合中存储的数据内容 //在展示的时候会拿[]把所有的数据进行包裹 ArrayList<String> list = new ArrayList<>(); System.out.printIn(list); // 输出为[] } }
成员方法/常用方法
方法名 说明 public boolean add(E e) 将指定的元素追加到此集合的末尾 public boolean remove(E e) 删除指定元素,返回值表示是否删除成功 public E remove(int index) 删除指定索引处的元素,返回被删除的元素 public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 public E get(int index) 返回指定索引处的元素 public int size() 返回集合中的元素的个数
- 注意,集合的长度是可变的,一开始初始化长度为0,即长度为0
第三个方法可以根据索引删除,说明集合是有索引的(也就是说,集合里可以有重复元素)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
public class ArrayListDemo02 { public static void main(String[] args) { //创建集合 ArrayList<String> list = new ArrayList<>(); //添加元素,返回值是布尔类型 list.add("hello"); list.add("hello"); list.add("world"); list.add("java"); //删除元素 boolean result1 = list.remove("hello"); //只会删除第一个出现的hello System.out.println(result1); //True System.out.println(list); //[hello,world,java] //根据索引删除元素 String str = list.remove(2); System.out.println(str); //world System.out.println(list); //[hello,hello,java] //修改指定索引处的元素 String result = list.set(1,"Hello") System.out.println(result); // hello System.out.println(list); //[hello,Hello,world,java] //查询元素 String s = list.get(0); System.out.println(s); // hello //获取集合长度(元素的个数),遍历集合 //list.fori for (int i = 0; i < list.size(); i++){ String str = list.get(i); System.out.println(str); } } }
包装类
基本数据类型对应的包装类:大部分都是首字母变大写即可(两个特殊)
1 2 3
byte --> Byte double --> Double ... char --> Character(特殊1) int --> Integer(特殊2)
集合操作练习
(1)遍历集合元素
- list.fori 正向遍历; list.forr 倒着遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
public class ArrayListDemo3 { public static void main(String[] args) { /*创建一个存储字符串的集合,使用程序实现在控制台遍历该集合 格式为[元素1,元素2,元素3] */ //1.创建集合对象 ArrayList<String> list = new ArrayList<>(); //2.添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //3.遍历 System.out.print("["); for (int i = 0; i < list.size(); i++) { if(i == list.size() - 1){ System.out.print(list.get(i)); }else{ System.out.print(list.get(i) + ", "); } } System.out.print("]"); } }
(2)集合存储对象并遍历
- 泛型写什么看元素类型,题目要求是存储学生对象,所以应该写相应的学生类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
public class ArrayListDemo4 { public static void main(String[] args) { //需求:创建一个存储学生对象的集合,使用程序实现在控制台遍历该集合 //1.创建集合对象,用来存储数据(Student是一个学生类) ArrayList<Student> list = new ArrayList<>(); //2.创建学生对象 Student s1 = new Student("zhangsan",16); Student s2 = new Student("lisi",15); Student s3 = new Student("wangwu",18); //3.把学生对象添加到集合中 list.add(s1); list.add(s2); list.add(s3); //4.遍历 for (int i = 0; i < list.size(); i++) { //i 依次表示集合中的每一个索引 Student stu = list.get(i); System.out.println(stu.getName() + ", " + stu.getAge()); } } }
(3)集合存储对象并遍历(键盘录入)
- 集合长度是可变的,一开始长度为0,所以遍历创建对象时不能用
i < list.size()
,这样就没有进入循环- 创建对象必须写在循环里,创建对象会分配一个地址值,写在循环外只会分配一个对象,修改的永远是这一个对象的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
public class ArrayListDemo4 { public static void main(String[] args) { //需求:创建一个存储学生对象的集合,使用程序实现在控制台遍历该集合 //1.创建集合对象,用来存储数据(Student是一个学生类) ArrayList<Student> list = new ArrayList<>(); //2.创建学生对象 Scanner sc = new Scanner(System.in); for (int i = 0;i < 3;i++){ Student s = new Student(); //必须写在循环里 System.out.,printIn("请输入学生姓名:"); String name = sc.next(); System.out.,printIn("请输入学生年龄:"); int age = sc.next(); //把name和age赋给对象 s.setName(name); s.setAge(age); //3.把学生对象添加到集合中 list.add(s); } //4.遍历 for (int i = 0; i < list.size(); i++) { //i 依次表示集合中的每一个索引 Student stu = list.get(i); System.out.println(stu.getName() + ", " + stu.getAge()); } } }
(4)查找用户索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
public class ArrayListDemo6 { public static void main(String[] args) { /*需求: 1,main方法中定义一个集合,存入三个用户对象。 用户属性为:id,username,password 2,要求:定义一个方法,根据id查找对应的学生信息。 如果存在,返回索引 如果不存在,返回-1*/ //1.创建集合对象 ArrayList<User> list = new ArrayList<>(); //2.创建用户对象 User u1 = new User("heima001", "zhangsan", "123456"); User u2 = new User("heima002", "lisi", "1234"); User u3 = new User("heima003", "wangwu", "1234qwer"); //3.把用户对象添加到集合当中 list.add(u1); list.add(u2); list.add(u3); //4.调用方法,通过id获取对应的索引 int index = getIndex(list, "heima001"); System.out.println(index); } public static int getIndex(ArrayList<User> list, String id) { //遍历集合得到每一个元素 for (int i = 0; i < list.size(); i++) { User u = list.get(i); String uid = u.getId(); if(uid.equals(id)){ return i; } } //因为只有当集合里面所有的元素都比较完了,才能断定id是不存在的。 return -1; } }
(5)返回多个数据
- return只能返回一个数据,如果在方法中要返回多个数据,可以把这些数据先放到一个容器中,再把容器返回
- 容器可以是集合(首选,因为长度可变)、数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/* 需求: 定义Javabean类:Phone Phone属性:品牌,价格。 main方法中定义一个集合,存入三个手机对象。 分别为:小米,1000。苹果,8000。锤子 2999。 定义一个方法,将价格低于3000的手机信息返回。 */ public class Test8 { public static void main(String[] args) { //1.创建集合对象 ArrayList<Phone> list = new ArrayList<>(); //2.创建手机的对象 Phone p1 = new Phone("小米",1000); Phone p2 = new Phone("苹果",8000); Phone p3 = new Phone("锤子",2999); //3.添加数据 list.add(p1); list.add(p2); list.add(p3); //4.调用方法 & 遍历集合 ArrayList<Phone> phoneInfoList = getPhoneInfo(list); for (int i = 0; i < phoneInfoList.size(); i++) { Phone phone = phoneInfoList.get(i); System.out.println(phone.getBrand() + ", " + phone.getPrice()); } } //方法返回类型是一个集合,所以要写集合 public static ArrayList<Phone> getPhoneInfo(ArrayList<Phone> list){ //定义一个集合用于存储价格低于3000的手机对象 ArrayList<Phone> resultList = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { Phone p = list.get(i); int price = p.getPrice(); //如果当前手机的价格低于3000,那么就把手机对象添加到resultList中 if(price < 3000){ resultList.add(p); } } //返回resultList return resultList; } }
面向对象进阶
回顾复习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class Student { // 1.成员变量 private String name ; private char sex ; private int age; //2.成员方法(行为) public void study(){ System.out.printIn(name + "正在学习"); } //3.构造方法(初始化成员变量) 空参构造、有参构造、get()、set()方法 } // 创建对象 Student stu = new Student();
1、封装:
- 使用
private
关键字来修饰成员变量- 使用
public
修饰getter和setter方法2、构造方法:创建对象时给成员变量初始化
3、this关键字:代表所在类的当前对象的引用(地址值),即代表当前对象
static
概述
(1)概述
static是静态的意思。 static可以修饰成员变量或者修饰方法。
关于
static
关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。我们上面案例中的成员变量(name、age等)都是没有static修饰的,所以属于每个对象。在java中,变量和方法等是存在属性的,Java是通过static关键字来区分的。static关键字在Java开发非常的重要,对于理解面向对象非常关键。
(2)我的理解
先前所写的private所修饰的name、age等成员变量,在创建很多个对象后是每个对象都有的属性,通过赋不同值得到不同对象,可以认为是每个对象私有的。
而static修饰符所修饰的成员变量或方法就是公共的,相当于在类里开辟了一段公共空间,每个人都可以共享和修改此区域。所创建的任意一个对象修改static所修饰的变量的值后,其他对象使用这个值也会跟着改变。因为是公共的,所以推荐通过类名调用,也可以通过对象名调用。
格式和使用
特点:被该类所有对象共享
调用方式:
- 类名调用(推荐)
Student.schoolName = "黑马"
- 对象名调用
stu.schoolName = "黑马"
(1)static修饰静态变量
有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用 类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量。(没有被static修饰的就是成员变量或叫实例成员变量)
格式:
修饰符 static 数据类型 变量名 = 初始值;
1 2 3 4 5 6 7 8 9 10
public class Student { public static String schoolName = "传智播客"; // 属于类,只有一份。 // ..... } public static void main(String[] args){ System.out.println(Student.schoolName); // 传智播客 Student.schoolName = "黑马程序员"; //修改公共变量 System.out.println(Student.schoolName); // 黑马程序员 }
(2)static修饰静态方法
有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法。 直接用 类名访问即可。因为类只有一个,所以静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。(没有被static修饰的就是成员方法或实例成员方法)
特点:多用在测试类和工具类中;Javabean类中很少会用;静态方法没有this关键字
1 2 3 4 5 6 7 8 9 10 11
public class Student{ public static String schoolName = "传智播客"; // 属于类,只有一份。 // ..... public static void study(){ System.out.println("我们都在黑马程序员学习"); } } public static void main(String[] args){ Student.study(); }
扩展补充,现在学过的几种类:
- Javabean类:基本类,用来描述一类事物的类,如Student类
- 测试类:用来检查其他类是否正确,带有main方法的类,程序入口
- 工具类:帮助做一些事情,但是不描述任何事物的类
- 工具类一般私有化构造方法,不能直接被创建对象;方法定义为静态
1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class ArrayUtil{ //私有化构造方法,不让外界创建对象 private ArrayUtil(){} //其他方法(行为)定义为静态的(公共的),方便调用 //求浮点数组的平均值 public static double getAverage(double[] arr){ double sum = 0; for (int i = 0;i < arr.length;i++){ sum = sum + arr[i]; } return sum / arr.length; } }
小结😊
当
static
修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法。但是不推荐这种方式去访问。因为静态变量或者静态方法直接通过类名访问即可,完全没有必要用对象去访问。无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。
static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区)。无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。
总结:静态方法不能访问非静态;非静态可以访问所有;静态方法没有this关键字(因为是共享的,被所有对象共享,根本不知道指的是那个对象,所以没有this关键字)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public class Student{ String name; static String teacherName; //下面这个静态方法运行会报错, //静态区根本找不到name,只有teacherName,因此报错,所以静态方法不能访问非静态 /* public static void method(){ System.out.println(name + "---" + teacherName); } */ //测试类在调用的时候会指定具体的对象,比如s1.show()说明this-->s1 //这时候指的就是s1.name public void show(){ System.out.println(name + "---" + teacherName); } } public static void main(String[] args){ Student s1 = new Student("张三",23); s1.show(); }
继承🌙
概述
1、继承:就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为(私有的子类当然不能访问了呦)。继承的类称为子类(派生类);被继承的类称为父类(基类或超类)
2、好处:提高代码的复用性
3、格式:
public class 子类 extends 父类 {}
4、特点:Java是单继承的,一个类只能继承一个直接父类;不支持多继承,但支持多层继承
- 比如田园猫继承自猫类,猫继承自动物类,那猫可以使用动物类中公有的变量和方法(符合实际)
5、始祖:Java中所有的类都直接或者间接继承于Object类(如果一个类没有父类,默认继承Object类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
//--------------------父类---------------- public class Human { private String name ; private int age ; // get()、set()省略 } //---------------子类:教师类---------------- public class Teacher extends Human { private double salary ; // 工资 // 特有方法 public void teach(){ System.out.println("老师在认真教技术!"); } // get()、set()省略 } //---------------子类:学生类---------------- public class Student extends Human{ } //-----------------测试类---------------- public class Test { public static void main(String[] args) { //(间接和直接)父类的非私有化方法和属性都能继承 Teacher dlei = new Teacher(); dlei.setName("播仔"); dlei.setAge("31"); dlei.setSalary(1000.99); System.out.println(dlei.getName()); System.out.println(dlei.getAge()); System.out.println(dlei.getSalary()); dlei.teach(); Student xugan = new Student(); xugan.setName("播仔"); xugan.setAge("31"); System.out.println(xugan.getName()); System.out.println(xugan.getAge()); } }
子类继承😂
父类中有的就三种:构造方法、成员变量、成员方法。子类到底可以继承父类中的哪些内容?(重点)
- 误区一:父类私有的东西,子类就无法继承
- 误区二:父类中非私有的成员,就被子类继承下来了
子类不能继承父类的构造方法。值得注意的是子类可以继承父类的私有成员(成员变量,(虚)方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
1 2 3
理解:父类中私有的成员变量/方法就是父类自己的(私房钱),子类如果能继承一旦修改了(花掉了),那父类就没有了;因为实际是子类可以继承,但不能直接使用,相当于私有成员上了一把锁,如果要使用的话需要通知父类一声(即getter/setter方法) 子类使用成员变量的时候,会先在子类(即本类)里找,如果找不到,则从父类里找;但子类使用成员方法的时候不是这样,java对方法做了优化,会直接从虚方法表里找(从Object类一层一层继承和添加下来的),如果没有则是父类里找、间接父类里找...
私有成员变量可以被继承,只不过要通过getter/setter方法间接使用;而私有成员方法无法被继承和使用(没有getter/setter方法),只有虚方法表中的方法可以被继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
public class Demo03 { public static void main(String[] args) { Zi z = new Zi(); System.out.println(z.num1); // System.out.println(z.num2); // 私有的子类无法使用 // 通过getter/setter方法访问父类的private成员变量 System.out.println(z.getNum2()); z.show1(); // z.show2(); // 私有的子类无法使用 } } class Fu { public int num1 = 10; private int num2 = 20; public void show1() { System.out.println("show1"); } private void show2() { System.out.println("show2"); } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } } class Zi extends Fu { }
- 在创建对象的时候会把子类和其父类都加载进堆内存
继承内存图(公有成员变量可以直接使用)
继承内存图(私有成员变量不能直接继承)
继承内存图(只有虚方法表中的方法可以直接继承和使用)
继承后-成员变量
1、当类之间产生了继承关系后,成员变量的访问特点:就近原则,谁近用谁(先在局部位置找–>本类找–>父类找)
2、如果想直接访问父类的成员变量,使用
super
关键字。super代表的是父类对象的引用,this代表的是当前对象的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Fu { String name = "父"; String age = 18; } class Zi extends Fu { String name = "子"; public void ziShow(){ String name = "ziShow"; System.out.printIn(age); //18 就近原则,先从局部方法里找,没有的话一级一级往上 System.out.printIn(this.age); //18 直接从本类里找,没有,从父类里找 System.out.printIn(super.age); //18 直接从父类;里找 System.out.printIn(name); //ziShow System.out.printIn(this.name); //子 调用本类中的name System.out.printIn(super.name); //父 调用父类中的name } }
总结,如果出现重名成员变量:
System.out.printIn(name);
从局部位置开始往上找System.out.printIn(this.name);
从本类成员位置开始往上找System.out.printIn(super.name);
从父类成员位置开始往上找
继承后-成员方法
当类之间产生了关系,成员方法访问特点:也是就近原则。(先本类,在父类)
super调用,直接访问父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
class Fu { public void show() { System.out.println("Fu show"); } } class Zi extends Fu { //子类重写了父类的show方法 public void show() { super.show(); System.out.println("Zi show"); } } public class ExtendsDemo05{ public static void main(String[] args) { Zi z = new Zi(); z.show(); // 同时输出Fu show和Zi show 子类中有show方法,优先子类 } }
上面一段代码子类和父类中的方法名一样,这称为方法重写。(与重载不同)
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
使用场景:当父类的方法不满足子类现在的需求时,需要进行方法重写,覆盖继承的父类方法
格式:
@Override
1 2 3
@Override:重写注解,放在重写的方法上,校验子类重写时语法是否正确 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!
示例代码:
1 2 3 4 5 6 7 8
class Zi extends Fu { //子类重写了父类的show方法 @Override public void show() { super.show(); System.out.println("Zi show"); } }
注意事项:方法重写本质-覆盖虚方法表中的方法
- 只有添加到虚方法表中的方法才能被重写
- 方法重写是发生在子父类之间的关系。(必须要有继承)
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。(空着不写<protected<public)
- 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
继承后-构造方法
首先,子类无法继承父类的构造方法
构造方法的名字是与类名一致的。所以子类无法继承父类构造方法,但可以通过super调用。
构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子)继承后子类构造方法的特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法。如果想调用父类有参构造,必须手动写super进行调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
class Person { private String name; private int age; //无参构造 public Person() { System.out.println("父类无参构造"); } } class Student extends Person { private double score; //无参构造 public Student() { super(); // 调用父类无参构造,默认就存在,可以不写,必须在第一行 System.out.println("子类无参构造"); } //有参构造 public Student(double score) { super(); // 子类所有构造方法默认调用父类无参构造,可以不写,默认存在 this.score = score; System.out.println("子类有参构造"); } } public class Demo07 { public static void main(String[] args) { Student s1 = new Student(); System.out.println("----------"); Student s2 = new Student(99.9); } } 输出结果: 父类无参构造 子类无参构造 ---------- 父类无参构造 子类有参构造
如果调用父类有参构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
class Person { private String name; private int age; //无参构造 public Person() { System.out.println("父类无参构造"); } //有参构造 public Person(String name,int age) { this.name = name; this.age = age; } } class Student extends Person { private double score = 100; //无参构造 public Student() { super(); // 调用父类无参构造,默认就存在,可以不写,必须在第一行 System.out.println("子类无参构造"); } //有参构造 public Student(String name,int age,double score) { super(name,age); // 调用父类有参构造,必须手动书写super this.score = score; System.out.println("子类有参构造"); } } public class Demo07 { public static void main(String[] args) { Student s1 = new Student(); System.out.println("----------"); Student s2 = new Student("zhangsan",22,99.9); //直接调用有参构造,即使父类变量是私有的 System.out.println(s2.getScore()); // 99.9 System.out.println(s2.getName()); // 输出 zhangsan System.out.println(s2.getAge()); // 输出 22 } }
this和super
从上面的代码中总结
this
和super
用法
- 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()
- super(..)是根据参数去确定调用父类哪个构造方法的
补充,this构造方法使用
含义:表示使用本类(this)的其他构造方法,根据参数来确定具体调用哪一个构造方法。在创建对象时如果有值则用,没有给school的值则用默认的值”上海大学“
使用场景:通常用在要给变量赋默认的值的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
class Student { String name; int age; String school; //无参构造 public Student() { // 表示调用本类的其他构造方法(即有参构造) //此时,无参构造就不会在默认添加super()了。因为已经存在this() //只有在调用无参构造时才会使用,此时执行后会调用有参构造执行 this(null,0,"上海大学"); } //有参构造 public Student(String name,int age,String school) { this.name = name; this.age = age; this.school = school; } }
- super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
//综合案例 /* 写一个继承的标准Javabean类 猫:属性,姓名,年龄,颜色 狗:属性,姓名,年龄,颜色,吼叫 */ public class Animal { //姓名,年龄,颜色 private String name; private int age; private String color; public Animal() { } public Animal(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } 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 String getColor() { return color; } public void setColor(String color) { this.color = color; } } public class Cat extends Animal{ //空参 public Cat() { } //需要带子类和父类中所有的属性 public Cat(String name, int age, String color) { super(name,age,color); } } public class Dog extends Animal{ //Dog :吼叫 private String wang; //空参构造 public Dog() { } //带参构造:带子类加父类所有的属性 public Dog(String name, int age, String color,String wang) { //共性的属性交给父类赋值 super(name,age,color); //独有的属性自己赋值 this.wang = wang; } //只需要写子类独有的变量 public String getWang() { return wang; } public void setWang(String wang) { this.wang = wang; } } public class Demo { public static void main(String[] args) { //创建狗的对象-带参构造(也可以无参,然后通过d.set()方法一个一个赋值) Dog d = new Dog("旺财",2,"黑色","嗷呜~~"); System.out.println(d.getName()+", " + d.getAge() + ", " + d.getColor() + ", " + d.getWang()); //创建猫的对象-带参构造(也可以无参,然后通过c.set()方法一个一个赋值) Cat c = new Cat("中华田园猫",3,"黄色"); System.out.println(c.getName() + ", " + c.getAge() + ", " + c.getColor()); } }
多态
多态是继封装、继承之后,面向对象的第三大特性。
多态,就是指对象的多种形态。同一行为,具有多个不同的表现形式。
多态前提:有继承/实现关系;有方法的重写;有父类引用指向子类对象
好处:使用父类类型作为参数,可以接受所有子类对象
格式:
父类 变量名 = new 子类();
——————person p = new Student();
当在一个方法中参数是一个类的时候,就涉及到了多态。此方法可以接受这个类所有的子类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
例如,现在有一个需求:写一个学生、老师、管理员注册的方法。 如果没有多态,只能在父类Person中定义一个通用register方法,在这种情况下,需要在三个子类中分别重写三个不同的register方法分别接收学生,老师和管理员,这样很麻烦,而且如果在添加其他子类,还需要重写; 有了多态之后,方法的形参就可以定义为共同的父类Person。 public class Person { private String name; private int age; ...空参构造/带参构造/get和set方法... public void show(){ System.out.println(name + ", " + age); } } public class Administrator extends Person { @Override public void show() { System.out.println("管理员的信息为:" + getName() + ", " + getAge()); } } public class Student extends Person{ @Override public void show() { System.out.println("学生的信息为:" + getName() + ", " + getAge()); } } public class Teacher extends Person{ @Override public void show() { System.out.println("老师的信息为:" + getName() + ", " + getAge()); } } public class Test { public static void main(String[] args) { Student s = new Student(); s.setName("张三"); s.setAge(18); Teacher t = new Teacher(); t.setName("王建国"); t.setAge(30); Administrator admin = new Administrator(); admin.setName("管理员"); admin.setAge(35); register(s); register(t); register(admin); } //这个方法既能接收老师,又能接收学生,还能接收管理员 //只能把参数写成这三个类型的父类 public static void register(Person p){ p.show(); } }
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
- 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
- 而且多态还可以根据传递的不同对象来调用不同类中的方法。
多态使用
多态的运行特点:
- 调用成员变量时:编译看左边,运行看左边
- 调用成员方法时:编译看左边,运行看右边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
理解: 比如 Animal a = new Dog(); (1)调用成员变量 a.name 因为成员变量a所属的是Animal类,所以会看左边的父类中有没有这个变量 如果有,编译成功,如果没有,编译失败 实际运行时获取的也是 左边父类中成员变量的值 (2)调用成员方法 a.show() 方法和变量不一样,每个类会有一个虚方法表,且子类会重写一些方法。 所以java在编译时会看左边父类中是否有这个方法,有则成功,无则失败 但在实际运行时运行的是右边 子类中的方法,即Dog类中的show()方法 因为a是Animal类型的,所以默认都会从Animal这个类中去找,这也和前面学的一样 --成员变量:在子类的对象中,会把父类的成员变量也继承下来,但优先从父类中找(因为定义为Animal) --成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法覆盖的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
public class Test { public static void main(String[] args) { //创建对象(多态方式) Animal a = new Dog(); System.out.println(a.name);//动物 a.show();///Dog --- show方法 } } class Animal{ String name = "动物"; //我们一般不在类里赋初值 public void show(){ System.out.println("Animal --- show方法"); } } class Dog extends Animal{ String name = "狗"; @Override public void show() { System.out.println("Dog --- show方法"); } } class Cat extends Animal{ String name = "猫"; @Override public void show() { System.out.println("Cat --- show方法"); } }
优势和弊端
(1)多态优势
在多态形式下,右边对象可以实现解耦合,便于扩展和维护
1 2
Person p = new Student(); //当需要更换对象时,直接修改Student即可 p.work(); //业务逻辑发生改变时,后续代码无需修改
定义方法时,使用父类作为参数,可以接受所有子类对象,体现多态的扩展性和便利
1 2
StringBuilder sb = new StringBuilder(); sb.append(1); //在Idea中,append(Object obj)中是Object obj,表示所有类型都可以添加
(2)多态弊端
已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
class Animal{ public void eat(){ System.out.println("动物吃东西!") } } class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } public void watchHouse() { System.out.println("看家"); } } class Test{ public static void main(String[] args){ Animal a = new Cat(); a.eat(); //a.catchMouse();//编译报错,编译看左边,Animal没有这个方法 } }
解决方案:强制变回子类型就可以了
【回顾:数据类型转换分为两种:自动类型转换和强制类型转换】
【注意,不能瞎转,上面定义的是Cat类,在强转的时候,也只能强转回Cat类,转为Dog类会报错】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class Test{ public static void main(String[] args){ //自动类型转换 Animal a = new Cat(); a.eat(); //正确写法(强制类型转换) Cat c = (Cat)a; c.catchMouse(); // 调用的是 Cat 的 catchMouse //错误写法 //Dog d = (Dog)a; //d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】 } } 运行时,却报出了 ClassCastException ,类型转换异常! 这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
为了避免
ClassCastException
的发生,Java提供了instanceof
关键字,给引用变量做类型的校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
格式:变量名 instanceof 数据类型 如果变量属于该数据类型或者其子类类型,返回true。 如果变量不属于该数据类型或者其子类类型,返回false。 class Test{ public static void main(String[] args){ Animal a = new Cat(); a.eat(); // 写法1:实际中为了避免出错的写法 if (a instanceof Cat){ Cat c = (Cat)a; c.catchMouse(); // 调用的是 Cat 的 catchMouse } else if (a instanceof Dog){ Dog d = (Dog)a; d.watchHouse(); // 调用的是 Dog 的 watchHouse } // 写法2:简化代码,JDK14的时候提出了新特性,把判断和强转合并成了一行 //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d //如果不是,则不强转,结果直接是false if(a instanceof Dog d){ d.lookHome(); }else if(a instanceof Cat c){ c.catchMouse(); }else{ System.out.println("没有这个类型,无法转换"); } } }
包
包就是文件夹,用来管理各种不同功能的Java类,方便后期维护
包名一般是公司域名的倒写,必须用”.“连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
什么时候需要导包? 情况一:在使用Java中提供的非核心包中的类时,比如java.lang 情况二:使用自己写的其他包中的类时 (同时使用两个包中的同类名,需要用全类名) 什么时候不需要导包? 情况一:在使用Java核心包(java.lang)中的类时 情况二:在使用自己写的同一个包中的类时 为什么需要导包? 包的完整名称为:包名+类名,如com.itheima.homework.demo1.Student 在一个类中,每次使用类时都写完整包名太麻烦了, com.itheima.homework.demo1.Student s = new com.itheima.homework.demo1.Student(); 所以需要导包:import com.itheima.homework.demo1.Student 这样的话在类中使用很方便:Student s = new Student(); 使用不同包下的相同类怎么办? 那则需要使用全类名:包名 + 类名 因为如果两个包下都有Student类,导入两个包后,在使用Student类时我怎么知道是哪个包下的? //拷贝全类名的快捷键:选中类名crtl + shift + alt + c 或者用鼠标点copy,再点击copy Reference com.itheima.homework.demo1.Student s1 = new com.itheima.homework.demo1.Student(); com.itheima.homework.demo2.Student s2 = new com.itheima.homework.demo2.Student();
final关键字
子类可以在父类的基础上改写父类内容,比如,方法重写。如果有一个方法我不想别人去改写里面内容,该怎么办呢?
Java提供了**
final
关键字,表示修饰的内容不可变**。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,有且仅能被赋值一次。(相当于常量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
//--------------------final修饰方法------------------- //格式:修饰符 final 返回值类型 方法名(参数列表){} //使用场景:方法是一种规则,不希望被改变(使用不多) class Fu2 { public final void show1() { System.out.println("Fu2 show1"); } public void show2() { System.out.println("Fu2 show2"); } } class Zi2 extends Fu2 { //子类不能重写父类方法shou1() @Override public void show2() { System.out.println("Zi2 show2"); } } //--------------------final修饰类------------------- //格式:final class 类名 {} //使用场景:(使用不多) final class Fu {} // class Zi extends Fu {} // 报错,不能继承final的类 //--------------------final修饰局部变量------------------- //基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改 final int b = 10; // 声明变量,直接赋值,使用final修饰 b = 20; // 报错,不可重新赋值 //类型的局部变量,被final修饰后,只能赋值一次 final Student s = new Student("zhangsan",23); S.setName("lisi"); //引用数据类型内部数据值可以被改变 S = new Student(); //报错,不能创建第二个学生对象 //--------------------final修饰成员变量------------------- //成员变量涉及到初始化的问题,初始化方式有显示初始化和构造方法初始化,只能选择其中一个 //显示初始化(在定义成员变量的时候立马赋值)(常用) public class Student { final int num = 10; } //构造方法初始化(在构造方法中赋值一次)(不常用,了解即可) //注意:每个构造方法中都要赋值一次!! public class Student { final int num2; public Student() { this.num2 = 20; } public Student(String name) { this.num2 = 20; } }
被final修饰的常量名称,一般都有书写规范,单个单词全部大写,多个单词全部大写且用下划线隔开
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,对象内部的可以改变
权限修饰符
在Java中提供了四种权限修饰符,被修饰的内容会有不同的访问权限。
可以修饰成员变量、方法、构造方法、内部类
作用范围:
private < 默认(空着不写) < protected < public
public protected 默认 private 同一类中 √ √ √ √ 同一包中的类 √ √ √ 不同包的子类 √ √ 不同包中的无关类 √ 编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节。- 构造方法使用
public
,方便创建对象。- 成员方法使用
public
,方便调用方法。(如果是共性的方法,不想被改变,则需要私有)小贴士:不加权限修饰符,就是默认权限
不同包的子类指的是一个包中的一个类继承了另一个包的父类
代码块
(1)分类:局部代码块(淘汰)、构造代码块(慢慢淘汰)、静态代码块
(2)格式:
static{}
(3)特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
(4)使用场景:在类加载的时候,做一些数据初始化的时候使用
抽象类
1 2
抽象方法: 没有方法体的方法。 抽象类:包含抽象方法的类。
抽象方法:将**共性的行为(方法)**抽取到父类之后。由于每一个子类执行的内容是不一样的,所以在父类中不能确定具体的方法体。那么该方法就可以定义为抽象方法。
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类。
1 2 3 4 5 6 7 8 9
abstract是抽象的意思,用于修饰方法方法和类 修饰的方法是抽象方法,修饰的类是抽象类。 抽象方法格式: 修饰符 abstract 返回值类型 方法名 (参数列表); public abstract void run(); 抽象类格式: abstract class 类名字 {..}
(1)注意事项:
- 抽象类不能实例化(反过来想,如果允许,创建对象调用抽象方法执行什么?looking my eyes,why)
- 抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
- 可以有构造方法,即空参构造和有参构造
- 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
// 父类,抽象类 abstract class Employee { private String id; private String name; private double salary; public Employee() { } public Employee(String id, String name, double salary) { this.id = id; this.name = name; this.salary = salary; } // 抽象方法 // 抽象方法必须要放在抽象类中 abstract public void work(); } // 定义一个子类继承抽象类 class Manager extends Employee { public Manager() { } //通过父类的构造方法初始化id、name。所以抽象类写构造方法不是没有用 public Manager(String id, String name, double salary) { super(id, name, salary); } // 2.重写父类的抽象方法 @Override public void work() { System.out.println("管理其他人"); } } // 定义一个子类继承抽象类 class Cook extends Employee { public Cook() { } public Cook(String id, String name, double salary) { super(id, name, salary); } @Override public void work() { System.out.println("厨师炒菜多加点盐..."); } } // 测试类 public class Demo10 { public static void main(String[] args) { // 创建抽象类,抽象类不能创建对象 // 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象 //Employee e = new Employee(); // 3.创建子类 Manager m = new Manager(); m.work(); Cook c = new Cook("ap002", "库克", 1); c.work(); } }
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
(2)抽象类存在的意义
抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写。
我的理解:抽象类关键在于抽象方法,也就是方法写什么都不合适,把它抽象,强制要求只要继承了我这个抽象类,就必须重写里面的抽象方法。另外就是抽象类不能被创建对象,只能通过子类继承抽象类,通过子类创建对象(弊端)。
不需要背,只要当idea报错之后,知道如何修改即可。
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
抽象类存在的意义是为了被子类继承。
理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
接口
(1)初识
即侧重的不是继承体系和一类事物,而是谁有这个功能(行为/方法)且这个功能常用常见,哪怕是完全不相关的类
接口就是一种规则,是对**行为(方法)**的抽象
(2)定义和使用
- 接口用关键字
interface
来定义,格式为public interface 接口名 {}
- 接口不能实例化(即不能创建对象)
- 接口和类之间是实现关系,通过
implements
关键字表示,如public class 类名 implements 接口名 {}
- 如果一个类实现了接口(实现类),必须重写接口中的所有抽象方法;否则这个类必须定义成抽象类
1 2 3 4 5 6 7 8
类实现接口的意义: 接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。 注意点: (1)接口和类的实现关系,可以单实现,也可以多实现 public class 类名 implements 接口1,接口2 {} (2)实现类还可以在继承一个类的同时实现多个接口 public class 类名 extends 父类 implements 接口1,接口2 {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
//定义游泳接口 public interface Swim { public abstract void swim(); } //父类-因为有抽象方法,定义为抽象类 public abstract class Animal { private String name; private int age; public Animal() { } public Animal(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 abstract void eat(); } //子类,继承父类同时实现一个接口 public class Dog extends Animal implements Swim{ public Dog() { } public Dog(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("狗吃骨头"); } @Override public void swim() { System.out.println("狗刨"); } } //子类 public class Rabbit extends Animal{ public Rabbit() { } public Rabbit(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("兔子在吃胡萝卜"); } } //测试类 public class Test { public static void main(String[] args) { //创建青蛙的对象 Frog f = new Frog("小青",1); System.out.println(f.getName() + ", " + f.getAge()); f.eat(); f.swim(); } }
(3)接口中成员特点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
(1)成员变量 只能是常量 默认修饰符:public static final 在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。并且是静态化的变量可以直接用接口名访问 (2)构造方法:没有 (3)成员方法 JDK7以前,接口中只能定义抽象方法。默认修饰符:public abstract JDK8新特性:接口中可以定义有方法体的方法 JDK9新特性:接口中可以定义私有方法 public interface InterF { // public abstract void run(); void run(); //会自己默认加上public abstract // public abstract String getName(); String getName(); // public static final int AGE = 12 ; int AGE = 12; //常量,会自己默认加上public static final String SCHOOL_NAME = "黑马程序员"; }
(4)接口和类
- 类和类的关系
- 继承关系,只能单继承,不能多继承,但是可以多层继承
- 类和接口的关系
- 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 接口与接口的关系
- 继承关系,可以单继承,也可以多继承
- 细节:如果实现类实现了最下面的子接口,那么就需要重写所有的抽象方法
- 接口继承接口就是把其他接口的抽象方法与本接口进行了合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
//接口1 public interface Abc { void test(); } //接口2 public interface Law { void rule(); void test(); } //接口3,继承前两个接口,最底层的接口,下面没儿子了 public interface SportMan extends Law , Abc { void run(); } //实现类,因为继承的是最底层的接口,重写所有抽象方法(这里默认是JDK7) public class InterTemp implements SportMan{ @Override public void test(){} @Override public void rule(){} @Override public void run(){} }
我的理解:接口和抽象类有相似之处,即里面都有抽象方法,所以只要一个类继承了接口或者抽象类,就必须对所有抽象方法进行重写(本来就是抽象才定义的,子类继承时当然要写具体一点),只不过接口没有继承关系罢了。同样的不能被实例化,因为里面都是抽象方法,只能通过一个类实现接口(实现类),创建实现类的对象,然后通过对象名调用接口中的方法【弊端】
接口其他扩展
扩展-1:JDK8接口中新增的特性-默认方法
(1)允许在接口中定义默认方法,需要使用关键字
default
修饰
- 作用:解决接口升级的问题(当接口升级时,调用接口的类不用每次都重写接口里的方法)
(2)接口中默认方法的定义格式
- public default 返回值类型 方法名(…) {}
- 范例:public default void show() {}
(3)接口中默认方法的注意事项
- 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候需去掉default关键字
- public 可以省略,default 不能省略(否则会当成抽象方法)
- 如果一个类实现了多个接口,且多个接口中存在相同名字的默认方法,则该类就必须对该方法进行重写
扩展-1:JDK8接口中新增的特性-静态方法
(1)允许在接口中定义静态方法,需要用static修饰
(2)接口中静态方法定义格式
- public static 返回值类型 方法名 (..) {}
- 范例:public static void show() {}
(3)接口中静态方法的注意事项
- 静态方法只能通过接口名调用,不能通过实现类名或对象名调用,即
接口名.方法()
- public可以省略,static不能省略(否则会当成抽象方法)
(之前即JDK7接口中只能有抽象方法,不能通过接口名调用接口中的方法,因为没法实例化,是抽象的;只能和抽象类一样,通过一个类实现接口,创建实现类的对象,然后通过对象名调用接口中的方法)
扩展-1:JDK9接口中新增的特性-私有方法
(1)接口中私有方法格式-1
- private 返回值类型 方法名(..) {}
- private void show() {}
- 普通私有方法,默认方法修改为私有方法时使用
(2)接口中私有方法格式-1
- private static 返回值类型 方法名(..) {}
- private static void show() {}
- 静态私有方法,静态方法修改为私有方法时使用
扩展-2:接口应用
当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称为接口多态
不需要背,只要当idea报错之后,知道如何修改即可。
关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
- 当两个接口中存在相同抽象方法的时候,该怎么办?
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
- 实现类能不能继承A类的时候,同时实现其他接口呢?
继承的父类,就好比是亲爸爸一样 实现的接口,就好比是干爹一样 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
- 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
- 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。 处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
- 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?
可以在接口跟实现类中间,新建一个中间类(适配器类) 让这个适配器类去实现接口,对接口里面的所有的方法做空重写。 让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
内部类
(1)初识
类中五大成分之一(成员变量、方法、构造器、内部类、代码块【慢慢在淘汰】)
如果一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
场景:当一个类的内部,包含了一个完整的事物,且这个事物没必要单独设计,就设计成内部类,比如汽车内部的发动机
分类:成员内部类(了解)、静态内部类(了解)、局部内部类(了解)、匿名内部类(重点)
- 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
- 局部内部类,类定义在方法内
- 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。
(2)成员内部类
格式:
外部类.内部类 变量 = new 外部类().new 内部类();
访问内部类的类型都是用
外部类.内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
public class Test { public static void main(String[] args) { // 宿主:外部类对象。 // Outer out = new Outer(); // 创建内部类对象。 Outer.Inner oi = new Outer().new Inner(); oi.method(); } } class Outer { //外部类成员变量 private int age = 99; // 成员内部类,属于外部类对象的。 public class Inner{ // 这里面的东西与类是完全一样的。 private String name; private int age = 88; ..构造方法和getter/setter初始化.. public void method(){ int age = 66; System.out.println(age); //66 System.out.println(this.age); //88 System.out.println(Outer.this.age); //99 } } }
在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
(3)静态内部类
- 格式:Outer.Inter in = new Outer.Inter();
- 静态内部类是一种特殊的成员内部类。
- 有static修饰的内部类,属于外部类自己持有。
- 总结:静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。
拓展1:静态内部类可以直接访问外部类的静态成员。
拓展2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class Outer01{ private static String sc_name = "黑马程序"; // 静态内部类 public static class Inner01{ private String name; public Inner01(String name) { this.name = name; } public void showName(){ System.out.println(this.name); // 拓展:静态内部类可以直接访问外部类的静态成员。 System.out.println(sc_name); } } } public class InnerClassDemo01 { public static void main(String[] args) { // 创建静态内部类对象。 // 外部类.内部类 变量 = new 外部类.内部类构造器; Outer01.Inner01 in = new Outer01.Inner01("张三"); in.showName(); } }
(4)局部内部类(看一下就行,了解都没必要)
局部内部类 :定义在方法中的类
匿名内部类
是内部类的简化写法。是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。
1 2 3 4 5
new 类名或者接口名(..) { 类体(一般是方法重写); }; // 可以看到,包含了:继承或实现关系、方法重写、创建对象
特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象出来
场景:如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用
是为了简化代码。
要求:匿名内部类必须继承一个父类或者实现一个父接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
//--------------实现接口的一个完整流程--------- interface Swim { public abstract void swimming(); } // 1. 定义接口的实现类 class Student implements Swim { // 2. 重写抽象方法 @Override public void swimming() { System.out.println("狗刨式..."); } } public class Test { public static void main(String[] args) { // 3. 创建实现类对象 Student s = new Student(); // 4. 调用方法 s.swimming(); } } //---------------匿名内部类------------- interface Swim { public abstract void swimming(); } public class Demo07 { public static void main(String[] args) { // 匿名内部类使用场景,可以看作一个子类 //zhegn'c将创建的对象赋给s3接受 Swim s3 = new Swim() { @Override public void swimming() { System.out.println("蝶泳..."); } }; goSwimming(s3); // 传入匿名内部类 // 完美方案: 一步到位(实际中经常这样使用,将匿名内部类作为方法参数) goSwimming(new Swim() { public void swimming() { System.out.println("大学生, 蛙泳..."); } }); //相当于又创建了一个子类 goSwimming(new Swim() { public void swimming() { System.out.println("小学生, 自由泳..."); } }); } // 定义一个方法,模拟请一些人去游泳 public static void goSwimming(Swim s) { s.swimming(); } }
类型转换
(自己补充,上面也有–多态–day14.md)
- 子类转父类(向上转型):可以自动完成,不过通过父类引用只能访问父类中定义的方法。
- 父类转子类(向下转型):必须进行显式强制类型转换,并且要保证对象在运行时实际是子类的实例,否则会抛出
ClassCastException
异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
//子类对象转换为父类(向上转型) //这是可行的,而且无需进行显式强制类型转换。 //因为子类继承了父类的所有属性和方法,所以子类对象能够被视作父类对象 class Animal { public void eat() { System.out.println("动物进食"); } } class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); } public void bark() { System.out.println("汪汪叫"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); Animal animal = dog; // 子类转父类,自动转换 animal.eat(); // 输出:狗吃骨头 // animal.bark(); // 编译错误,父类引用无法调用子类特有的方法 } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
//父类对象转换为子类(向下转型) //这种转换不一定能成功,必须进行显式强制类型转换,并且在运行时要确保父类对象实际上是子类的一个实例 class Animal { public void eat() { System.out.println("动物进食"); } } class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); } public void bark() { System.out.println("汪汪叫"); } } public class Main { public static void main(String[] args) { // 情况1:父类引用指向子类对象 Animal animal1 = new Dog(); // 向上转型 Dog dog1 = (Dog) animal1; // 向下转型,可行 dog1.bark(); // 输出:汪汪叫 // 情况2:父类引用指向父类对象 Animal animal2 = new Animal(); // Dog dog2 = (Dog) animal2; // 运行时会抛出ClassCastException异常 // 安全的向下转型方式 if (animal2 instanceof Dog) { Dog dog2 = (Dog) animal2; dog2.bark(); } else { System.out.println("无法进行类型转换"); // 会执行此分支 } } }
总结:
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
- 向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。
- 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
1 2 3 4 5 6 7 8
//--------------向上转型(自动转换)---------- 父类类型 变量名 = new 子类类型(); 如:Animal a = new Cat(); //--------------向下转型(强制转换)---------- 子类类型 变量名 = (子类类型) 父类变量名; 如:Aniaml a = new Cat(); Cat c =(Cat) a;
常用API
即一些封装好的java类,不需要记忆,只需要了解类名和基本作用即可,随用随查
Math类
作用:执行数值操作的方法,如指数、幂数、对数、三角函数
public final class Math extends Object
,不能被继承方法等均被
static
修饰,直接通过类名调用,如Math.PI
向上取整和向下取整(有小数就进一或减一)不等于四舍五入
System类
作用:提供了一些和系统相关的方法
方法等均被
static
修饰,直接通过类名调用
System.currentTimeMillis()
:获取当前时间的毫秒值,经常通过结束时间-起始时间
计算程序所花费的时间
Runtime类
Runtime表示当前虚拟机的运行环境
Object类
Java中的顶级父类,所有的类都直接或间接的继承于Object类
1 2 3 4 5 6 7 8 9 10 11
-----------------扩展:System.out.printIn()原理-------------- System 类名 out 静态变量 printIn() 方法 当我们打印一个对象的时候,底层会调用对象的toString方法,把对象变成字符串,然后打印在控制台上 ----------toString方法结论:------------------- 默认情况下,因为Object类中的toString方法返回的是地址值,打印一个对象输出的就是地址值 如果我们打印一个对象,想要看到属性值的话,重写toString方法就可以了 在重写的方法中,把对象的属性值进行拼接 直接使用快捷键/右键即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//Object中的equal方法是比较两个对象是否相等,即比较的是两个对象的地址值 //实际中,我们想要比较的是对象的地址值,所以经常会重写equal方法,如果没有重写,就是调用Object中的equal方法,比较的 public class ObjectDemo3{ public static void main(String[] args){ String s = "abc"; StringBuilder sb = new StringBuilder("abc"); //调用的是字符串类中的equal方法 System.out.printIn(s.equals(sb)); //false,因为sb它不是一个对象 //调用的是StringBuilder的equal方法,由于其没有这个方法,所以用的是Object类中的equal方法 System.out.printIn(sb.equals(s)); //false,比较的是地址值 } }
1 2 3 4 5 6 7
java中的克隆分两种:浅克隆和深克隆 --浅克隆:不管对象内部的属性是基本数据类型还是引用数据类型(拷贝地址值),都完全拷贝过来 这样的话引用数据类型就共用一个空间,修改任意一个值都会改变 --深克隆:基本数据类型拷贝过来;字符串复用(共用);引用数据类型会重新创建新的 Object中的克隆属于浅克隆方式
(简单说了一下jar包导入和lib库)
Objects是一个工具类,提供了一些方法去完成一些功能
BigInteger、BigDecima类
任意大的整数和小数
通过静态方法
BigInteger.valueOf
获取对象,内部有一些优化和细节(在内部对-16~16进行了优化,提前创建好了BigInteger对象,如果多次获取不会重新创建新的)
- 如果要表示的数字不大,没有超出double的取值范围,建议用静态方法
- 如果要表示的数字比较大,超出了double的取值范围,建议用构造方法
正则表达式
API说明文档中的
Pattern
类
- 正则表达式中
[]
表示一个范围;{}
表示出现次数;()
就是分组,\数字
表示复用第几组的数据
- 正则内部使用:
\\组号
(java中\表示一个\)- 正则外部使用:
$组号
是一个一个字符匹配的,即一个
[]
判断一个字符是否符合要求。([xx[x]]
也是判断一个字符;[xx][xxx]才是判断两个字符
)在java中,
\
表示转义字符,具有特殊意义。如果想要输出原本意思,需要写\\
,前面的\
是一个转义字符,改变了后面\
原本的含义,把其变为普普通通的\
。
时间
Date类
- 对象比较的是地址值,如果想要比较时间大小,可以用get方法获取后(转为数值,即毫秒值)再进行比较
- 缺点:不能改变时间的格式,是固定的(输入是毫秒值,输出是固定的时间格式:
The Jan 01 08:00:00 CST 1970
)
SimpleDateFormat类
- 作用:(1)格式化时间,变为想要的格式;(2)解析,把字符串表示的时间变成Date对象
- 解析的目的是将字符串形式的时间变回对象,从而可以用get方法获取对应的(毫秒)数值,便于后续比较等操作
Calendar类
- 代表了系统当前时间的日历对象,可以单独修改、获取时间中的年月日
- 细节:Calendar是一个抽象类,不能直接创建对象,而是通过一个静态方法获取子类对象