目录
第一篇、Java编程基础
❀Java中的属性名词
❀Java数据类型
❀转义字符
❀&& 和 & 与 || 和 |
❀方法
第二篇、Java面向对象编程
❀类和对象
❀面向过程和面向对象的区别
❀面向对象程序设计的主要特性
❀类和对象概述
❀对象的内存结构
❀垃圾空间
❀成员属性封装
❀构造方法
❀this关键字
❀简单Java类
❀static关键字
❀代码块
❀数组定义
❀数组与方法
❀数组类库支持
❀方法可变参数
❀对象数组
❀数据表和Java对象之间的映射关系
❀String类对象的实例化
❀字符串比较
❀字符串常量池
❀字符串的修改
❀主方法的组成分析
❀面向对象的继承性
❀覆写
❀this与super的区别
❀final关键字
❀Annotation注释
❀面向对象的多态性
❀instanceof关键字
❀Object类
❀'=='和equals()方法的区分
❀抽象类
❀接口
❀泛型
第一篇、Java编程基础
❀Java中的属性名词
IDE | Integrated Development Environment |
JDK | Java Development Kit |
JRE | Java Runtime Environment |
JVM | Java Virtual Machine |
- IDE:全称是集成开发环境(Integrated Development Environment),用于提供程序开发环境的应用程序,一般包括代码编辑器,编译器,调试器和图形用户界面等工具。
- JDK:全称是Java语言的程序开发工具包(Java Development Kit),主要用于移动设备,嵌入设备上的Java应用程序开发。
- JRE:全称为Java运行环境(Java Runtime Environment),可以让计算机系统运行Java程序,在JRE的内部有一个Java虚拟机(JVM)以及一些标准的类别函数库(ClassLibrary)。
- JVM:全称为Java虚拟机(java virtual machine),是一个规范定义的抽象计算机。
JDK、JRE、JVM三者之间的关系:
JDK包含JRE,JRE包含JVM。
❀Java数据类型
❀转义字符
\" | " |
\' | ' |
\n | 换行 |
\t | 制表符 |
\\ | \ |
❀&& 和 &、|| 和 |
❀:普通与和普通或逻辑运算符:&、|
判断条件依次执行,直至没有判断条件可以执行
❀:短路与和短路或逻辑运算符:&&、||
在短路与中,如果前一个判断条件为false,则不再去执行接下来的同条件判断条件;咋短路或中,如果条件语句前面有一条语句为true,可知此时整个条件的真值为true,不再去执行同条件的剩余判断条件。
❀方法
❀方法的重载
方法的重载是方法名称重用的一种方式,主要特点为“方法名称相同,参数的类型或者个数不同,方法的返回值可以相同或者不同”。
方法重载需要满足:
- 方法的名称相同
- 方法的参数个数或者参数类型不同
- 返回值和访问权限可以改变
补充:考虑到程序开发的标准性,大多数程序的方法在重载时都会统一方法的返回类型
第二篇、Java面向对象编程
❀类和对象
❀面向过程和面向对象的区别
- 面向过程:面向过程的程序开发是以实现程序的基本功能为主,实现之后就完成了,并不考虑项目的可维护性,设计出来的程序完全只是“为这个程序所服务”,可维护性滴,,不具有标准化和通用性。
- 面向对象:面向对象是现在最为流行的一种程序设计方法,面向对象更多的是要进行模块化的设计,每一个模块都需要单独存在且可以被重复利用,设计出来的程序可维护性高,更具有通用性。
❀面向对象程序设计的主要特性
- 封装性
封装性具有两个含义:一是指把对象的成员属性和行为看成一个密不可分的整体,将这两者封装在一个不可分割的独立单位(对象)中。另一层含义是指信息隐蔽,把不需要让外界知道的信息隐藏起来。封装机制在程序设计中的表现为,把描述对象属性的变量和实现对象属性的方法组合在一起,定义为一个程序结构,并保证外界不能随意更改其内部属性值,也不能任意调动其内部的属性方法,即给封装在一个整体内的变量及方法规定了不同级别的可见性或访问权限。 - 继承性
继承性是提高软件开发效率的主要手段,继承首先拥有反应事物一般特性的类,其次在其基础上派生出反应事物特殊性质的类。在Java程序设计中,被继承的类称为父类或者超类,经继承产生的类称为子类或派生类,派生类继承了超类的所有内容,并可以增加新的成员,面向对象的程序设计机制,大大增强了程序代码的可复用性,提高了软件开发效率,降低了程序产生错误的可能性,也为程序的修改和扩展提供了便利。
若一个子类只允许继承一个父类,称为单继承;若允许继承多个父类,则称为多继承。Java语言尚不支持多继承,而其可以通过接口(interface)的方式来弥补由于Java不支持多继承而带来的子类不能使用多个父类成员的缺憾。 - 多态性
多态性是指允许程序中出现重名现象,Java语言的多态性包括方法重载和对象多态。
方法重载:在一个类中,允许多个方法使用同一个名字,但方法的参数不同,完成的功能也不同。
对象多态:子类对象可以与父类对象进行相互转换,且使用的子类对象不同,完成的功能也不同。
多态的特性使程序的抽象程度和简洁程度更高,有助于程序设计人员对程序的分组协同开发。
❀类和对象概述
- 类
类实际上是客观世界中某类群体的基本特征的抽象,属于抽象的概念集合。例如在生活中,“人”就可以表示为一个类。类是对象所具有的的共同的特征的抽象集合。 - 对象
对象是类的实例化,表示从属于类的一个个可以操作的实例化个体,如果说“人”是一个类,那么小明,小白就是对象,是真正存在的可以白操作的个体。 - 类和对象
在面向对象中,有这样一句话可以很好理解类和对象的区别:“类是对象的模板,而对象是类的实例”,即对象所具备的行为都是由类来定义的,在开发中,应该先定义出类的结构,之后才能通过类的实例化产生对象来使用这个类。
❀对象的内存结构
Java中的类属于引用数据类型,所有引用类型使用时都要通过关键字new开辟内存空间,引用数据类型中最重要的内存款有两块:堆和栈。
- 【 heap】堆内存:保存的是对象的具体信息(成员属性)。
- 【stack】栈内存:保存的是一块堆内存的地址,或者可以更贴切的说保存的是堆内存对应的引用的名称。
关于方法信息的保存:类中所有成员的属性是每个对象私有的,而类中的方法是所有对象公开的,方法的信息会保存在"全局方法区"这样的公共内存当中。
关于栈内存:所有的堆内存都会有相应的内存地址(对象引用),同时栈内存会保存堆内存的地址(对象的名称)。
❀垃圾空间
垃圾空间产生原因:引用传递的的本质在于一块内存空间可以被不同的栈内存引用,而每一块栈内存都会保存堆内存的地址信息,并且只允许保存一块堆内存的地址信息,因此如果一块栈内存已经存在一块堆内存的引用,当需要改变引用指向时就需要丢弃已有的引用实体。
public class GarbagePose {
public static void main(String[] args) {
Person s1 = new Person();
Person s2 = new Person();
s1.age = 18;
s1.name = "bai";
s2.age = 20;
s2.name ="handsome";
s2 = s1; //s2的引用被赋值为s1,s2指向的堆空间在没有引用的情况下会被GC回收
s2.tell();
}
}
class Person {
int age;
String name;
public void tell() {
System.out.println("姓名:"+name +"," +"年龄:" + age);
}
}
❀成员属性封装
封装是程序访问权限的处理操作,封装性是面向对象的第一大特性,最重要的特点就是内部结构对外部不可见。如果类的属性或者说成员变量可以通过实例化对象在类的外部调用,称这样的调用是不安全的,稳妥的做法是使用private关键字实现类的成员变量的封装,使用setter和getter方法公外部操作成员变量使用。
public class PrivateTests {//创建软件包时包的名称不能和关键字的名称一样
/**
* @author xiaobai
* 使用private关键字对类的成员属性进行封装,在类的外部不能直接通过类的实例访问实例类的属性
*/
private String name;
private int age;
public void setName(String tempName) {
name = tempName;
}
public void setAge(int tempAge) {
age = tempAge;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class PrivateTestDrive {
public static void main(String[] args) {
PrivateTests s = new PrivateTests();
s.setAge(18);
s.setName("handsome");
System.out.println(s.getName() + " " + s.getAge());
}
}
补充:在类的封装中定义到的getter()和setter()方法的目的是为了设置和取的属性的内容,也许其中一个操作暂时不需要用到getter()操作,但并不表示以后不会用到,从开发角度来讲,getter()和setter()方法必须同时提供。
❀构造方法
概念:构造方法食杂类中定义的特殊方法,在类使用关键字new实例化对象时默认调用,其主要功能时完成对象属性的初始化操作,依次减少setter方法的调用,但是还是要继续提供setter方法,因为不确定在接下来的情况下不回去修改成员变量。
构造方法的定义要求:
- 构造方法的名称和类的名称相同
- 构造方法不允许头返回值类型声明
默认构造方法和自定义构造方法:如果没有自定义构造函数,java程序在编译时会自动生成一个无参且无返回值的构造方法。有自定义构造函数时,java程序只会执行自定义的构造函数。
构造方法和普通方法类似,可以进行重载。但在编写重载方法时,应按照规范将重载方法按照参数个数升序或者降序排列。
//构造方法无返回值 public Person() {} public Person(String name) {} public Person(String name,int age) {}
public class ConstructMethodTestDrive {
public static void main(String[] args) {
Person per = new Person(new Message("加油,拼搏"),18);
per.tell();
Message msg = per.getMessage(); //获取Message对象
System.out.println(msg.getInfo());
}
}
class Message {
private String info;
//自定义构造函数
public Message(String tempInfo) {
info = tempInfo;
}
//setter方法
public void setInfo(String tempInfo) {
this.info = tempInfo;
}
//getter方法
public String getInfo() {
return info;
}
}
class Person {
private String name;
private int age;
//自定义构造函数
public Person(Message msg, int tempAge) {
name = msg.getInfo();
age = tempAge;
}
public Message getMessage() {
return new Message("姓名:" + name + "、年龄:" + age );
}
//setter方法
public void setName(String tempName) {
name = tempName;
}
public void setAge(int tempAge) {
age = tempAge;
}
//getter方法
public String getName() {
return this.name;
}
public int getAge() {
return age;
}
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age);
}
}
❀this关键字
this是当前类调用的关键字,在Java中this关键字可以描述三种结构的调用:
- 当前类中的属性:this.属性
- 当前类中的方法(普通方法、构造方法):this.方法名称、this( )
- 描述当前对象
🍎使用this调用当前的类属性:
当通过setter方法或者构造方法为类中的成员设置内容时,为了可以清晰的描述出具体参数与成员属性之间的关系,往往会使用同样的参数名称,此时就需要通过this来调用当前类的成员属性。为了比秒不必要的麻烦,在开发过程中,只要有调用类成员属性的情况出现,都要使用this属性来进行表示。
public class ThisMemberProperitiesTests { public static void main(String[] args) { Person per = new Person("success",22); per.tell(); } } class Person { private String name; private int age; /** * 自定义构造方法,初始化成员属性,参数名称和成员属性名称相同 * @param name 设置name成员的属性内容 * @param age 设置age成员的属性内容 */ public Person(String name,int age) { this.name = name; //使用this标记当前的类的name属性 this.age = age; //使用this标记当前类的age属性 } //getter、setter方法略 public void tell() { System.out.println("姓名:" + name +"、 年龄:" +age); } }
🍎this调用当前类方法
在现在学的Java内容中,类有两种方法:构造方法和普通方法,在一个类中要实现本类方法的调用,可以通过this关键字来实现:
- 调用当前类的普通方法:this.方法( ),可以在构造方法和普通方法中使用
- 调用当前类的构造方法:this( ),此语句只允许放在构造方法首行,实现当前类的构造方法的相互调用
public class ThisClassMethodTests { public static void main(String[] args) { Person per = new Person("success",18); per.tell(); } } class Person { private String name; private int age; public Person() { System.out.println("****** 无参构造方法:一个新的Person类对象实例化了 ******"); } //构造方法的重载 public Person(String name) { this(); //调用当前类的无参构造方法,在构造函数中使用this()必须放在第一行 System.out.println("单参构造方法"); this.setName(name); } public Person(String name,int age) { this(name); //调用当前类的单参构造方法,在构造函数中使用this()必须放在第一行 System.out.println("双参构造方法"); this.age = age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void tell() { System.out.println("姓名:" + this.name + "、年龄:" + this.age); } }
运行结果:
🍎this描述当前对象:
一个类可以实例化多个对象,对于当前正在访问类中方法的对象就可以称为当前对象。this就可以描述出这种当前对象的概念。
通过一个实例(实现消息发送的处理逻辑)来进一步理解this表示当前对象这一概念
public class ChannelTestDrive { public static void main(String[] args) { //实例化一个Channel对象,并且传入要传入的消息标题和消息内容 Channel channel = new Channel("ZUA运动会","迎接5km挑战"); } } class Message { private final String title; //消息标题 private final String content; //消息内容 private final Channel channel; //保存消息发生通道 public Message(final Channel channel,final String title,final String content) { this.title = title; //设置title属性 this.content = content; //设置content属性 this.channel = channel; //保存消息通道 } public void send() { //判断当前通道是否可用 if(this.channel.isConnect()) { //连接成功 System.out.println("【消息发送】title:" + title + "、 content:" + content); } else { System.out.println("【error】没有可用的连接通道,无法进行消息发送"); } } } class Channel { private final Message message; //消息发送由message对象负责 //实例化channel对象,调用构造方法,接收要发送的信息标题和信息内容 public Channel(final String title,final String content) { //实例化message类,需要将但是需要将主类中的ch传递到message类中 this.message = new Message(this,title,content); this.message.send(); //消息发送 } public boolean isConnect() { return true; } }
❀简单Java类
🍎简单Java类是指可以描述某一类信息的程序类,只作为一种信息的保存媒介存在。核心开发结构如下:
- 类名称一定要有意义,可以明确的描述某一事物
- 类中提供的所有属性必须用private关键字进行封装,封装后的属性必须提供getter()和setter()方法
- 类中可以提供无数多个构造方法,但是必须保留无参构造方法
- 类中不允许出现输出语句,所有的内容必须返回
- [可选]可以提供一个获取对象详细信息的方法,暂时将该方法定义为getInfo( )
范例:定义一个描述部门的简单Java类
public class DeptTestDrive {
public static void main(String[] args) {
Dept dept = new Dept(10,"字节跳动","北京市海淀区");
System.out.println(dept.getInfo());
}
}
class Dept { //类的名称一定要有意义
//所有的成员属性都用private关键字进行封装
private long deptno; //部门编号
private String dName; //部门名称
private String loc; //部门位置
//必须保留无参的构造方法
public Dept() {}
public Dept(long deptno,String dName,String loc) {
//通过this.属性访问当前对象的属性值
this.deptno = deptno;
this.loc = loc;
this.dName = dName;
}
//返回this对象的详细信息
public String getInfo() {
return "部门编号:" + this.deptno + "、部门名称:" + this.dName + "、部门地址:" + this.loc;
}
//必须提供getter和setter方法
public void setDeptno (long deptno) {
this.deptno = deptno;
}
public long getDeptno() {
return this.deptno;
}
//dName和loc的getter和setter方法省略
}
❀static关键字
🍎static是一个用于声明程序结构的关键字,可以用于全局属性和全局方法的声明,通过static声明的变量或方法解除了对象实例化的限制,在没有实例化对象时可以直接通过类名称访问。
🍎static关键字描述的属性
- static关键字描述的属性称为全局属性即公共属性,该属性会保存在全局数据区,而不是在堆内存中。
- 通过static声明的类的属性会为所有共同类的对象所共有
- 该属性可以直接通过类名称调用,也可以通过实例变量调用
🍎static关键字定义的方法
- 和static关键字描述的对象类似,可以在没有实例化对象的时候通过类的名称调用,也可以通过实例化对象调用
- 方法中不允许使用this关键字
🐝static关键字定义的方法中this关键字不可以使用的原因:
使用static关键字定义的属性和方法,可以在没有实例化对象的时候使用(如果没有实例化对象,也就表示没有了表示当前对象的this,所以在static关键字定义的方法中无法使用this关键字).
🍎关于static方法的调用问题:
- static关键字定义的方法不能调用非static关键字定义的方法或属性
- 非static关键字定义的方法可以调用static关键字定义的方法或属性
- 非static关键字定义的属性和方法,必须在实例化对象之后才可以进行调用
使用static关键字定义属性和描述方法:
public class ChineseTestDrive {
public static void main(String[] args) {
//调用静态方法,修改静态成员属性值
Chinese.setCountry("中华人名民共和国");
Chinese per = new Chinese("成功",22);
System.out.println(per.getInfo());
// System.out.println(per.getClass());
}
}
class Chinese {
private String name;
private int age;
//通过static关键字定义的变量为所有共同类的对象共有
private static String country = "中国";
//通过构造函数设置普通属性内容
public Chinese(String name,int age) {
this.name = name; //设置name属性
this.age = age; //设置age属性
}
/**
* 通过static关键字定义方法,可以在没有实例化对象的情况下通过类名直接调用
* 该方法中不允许使用this关键字
* @param c 静态属性country要修改的属性的新内容
*/
public static void setCountry(String c) {
country = c; //修改静态属性的内容
}
/**
* 获取对象的完整信息,可以通过this关键字调用或者实例化对象调用
* static关键字修饰的属性,在类内最好还是通过类名称调用
* @return 对象的完整信息
*/
public String getInfo() {
return "姓名:" + this.name + "、年龄:" + this.age + "、国家:" + Chinese.country;
}
}
❀代码块
🍎代码块是指在程序中用"{ }"定义起来的一段程序,根据代码块声明位置以及声明关键字的不同,代码段可以分为:普通代码块、构造代码块、静态代码块和同步代码块。
🍎普通代码块
定义在方法中,起到将方法中的代码进行分割的作用
🍎构造代码块
定义在类中的代码块称为构造代码块。构造代码块的特点时在使用关键字new实例化新对象时进行调用。每一次实例化新的对象都会调用构造代码块,并且构造代码块的执行优先于构造方法的执行。
public class PersonTestDrive { public static void main(String[] args) { Person per = new Person(); } } class Person { public Person() { System.out.println("Person类构造方法执行"); } { //构造代码块 System.out.println("Person类内的构造代码块执行"); } } /** *运行结果: * Person类内的构造代码块执行 * Person类内的构造方法执行 */
🍎静态代码块
🐝静态代码块也是定义在类中的,定义格式为:static{ }
🐝静态代码块要考虑两种情况:
- 在非主类中定义的静态代码块
- 在主类中定义的静态代码块
🐝在非主类中定义的静态代码块的执行优先于构造代码块,在类加载时静态代码块中的内容就被执行。在主类中定义的静态代码块总是最先执行,并且不管实例化多少个对象,静态代码块中的代码只执行一次。
public class PersonTestDrive { public static void main(String[] args) { Person per1 = new Person(); Person per2 = new Person(); } } class Person { public Person() { //构造方法 System.out.println("Person类的构造方法执行"); } static { //静态代码块 System.out.println("Person类内的static静态代码块执行"); } { //构造代码块 System.out.println("Person类内的构造代码块执行"); } } /** * 执行结果: * Person类内的static静态代码块执行 * Person类内的构造代码块执行 * Person类的构造方法执行 * Person类内的构造代码块执行 * Person类的构造方法执行 */
❀数组定义
数组是一类相关变量的值的集合。
🍎一维数组的定义
- 静态初始化:int[ ] arr = new int[ ] {1,2,3,4,5};
- 动态初始化:int[ ] arr = new int[5]; 初始化后数组元素默认值为0。使用时要先对数组中的内容进行赋值
🐝一维数组的遍历
-
for(int i=0;i<arr.length;i++) { System.out.print(arr[i]+"、"); }
-
for(int temp : arr) { System.out.print(temp + "、"); }
🍎二维数组的定义
二维数组描述出了多行多列的结构,需要行下标和列下标才可以定位到一个元素。其结类似于一张表,本质上就是数组的嵌套。
- 动态初始化:int[][] arr = new int[][] {{1,2,3},{3,4,5,6,7}};
- 静态初始化:int[][] arr = new int[row][col];
🐝二维数组的遍历
-
int[][] arr = new int[][] {{1,2,5},{5,4,3,8},{1,2},{2,5,8,3,8}}; for(int i = 0; i < arr.length; i++) { for(int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + "、"); } System.out.println(); }
-
int[][] arr = new int[][] {{1,2,5},{5,4,3,8},{1,2},{2,5,8,3,8}}; for(int date[] : arr) { for (int temp : date) { System.out.print(temp + "、"); } System.out.println(); }
❀数组与方法
🐝在数组进行引用传递的处理中,最为常见的方式是基于方法进行引用数组的处理或返回。
结合面向对象设计,实现数组内容统计
public class ArrayUtilTestDrive {
public static void main(String[] args) {
int[] date = new int[] {1,2,3,4,5};
ArrayUtil util = new ArrayUtil(date);
System.out.println("数组内容总和:" + util.getSum());
System.out.println("数组总和均值:" + util.getAver());
System.out.println("数组元素最大值:" + util.getMax());
System.out.println("数组元素最小值:" + util.getMin());
}
}
class ArrayUtil {
private int sum; //保存数据统计总和
private double aver; //保存平均值
private int max; //保存最大值
private int min; //保存最小值
/**
* 接收要进行统计的数组数据,并且在构造方法中实现数据的统计处理
* @param date 要统计的处理数据
* @author whiteInJava
*/
public ArrayUtil(int[] date) {
this.max = date[0];
this.min = date[0];
for(int temp : date) {
if(temp>max) {
max = temp;
} else if(temp<min) {
min = temp;
}
this.sum += temp;
}
this.aver = (double)this.sum/date.length; //统计平均值,忽略小数位
}
public int getSum() {
return this.sum;
}
public double getAver() {
return this.aver;
}
public int getMax() {
return this.max;
}
public int getMin() {
return this.min;
}
}
🐝在实际的项目开发中,主类通常是作为客户端调用程序出现的,执行的代码应该越简单越好,客户端只是传入数据获取结果,而对于这个结果时怎么实现的并不关心。
❀数组类库支持
🍎数组排序
可以按照从小到大的顺序对基本数组进行排序,主要针对于一维数组,对数组的类型并没有限制,操作语法:java.util.Arrays.sort(数组名).
public class ArraySortTestDrive {
public static void main(String[] args) {
int[] date = new int[] {10,3,6,211,985,45,20,333};
java.util.Arrays.sort(date);
ArraySort.printArray(date);
}
}
class ArraySort {
public static void printArray(int[] temp) {
for(int i = 0 ; i < temp.length; i++) {
System.out.print(temp[i] + "、");
}
System.out.println();
}
}
//输出:3、6、10、20、45、211、333、985、
🍎数组复制
从一个数组中复制部分内容到另一个数组中,方法为:System.arraycopy(源数组名称,源数组开始点,目标数组名称,目标数组开始点,复制长度)。
🐝对于此类方法只允许某个索引范围为内的数据进行复制,一旦索引控制不当,依然有可能出现数组越界访问。(java.lang.ArrayIndexOfBoundsException).
❀方法可变参数
为了使开发者更加灵活的定义方法,Java提供了方法可变参数的支持(实质上是数组的一种变相应用),使用这种方法可以在方法调用时采用动态形式传递若干个参数,方法定义如下:
- public [static] [final] 返回值类型 方法名称(参数类型...变量名)
🐝如果包含混合参数的方法中需要接收普通参数和可变参数:
- 可变参数一定要定义在最后
- 该方法中只能有一个可变参数
- 可变参数可以根据需求决定是否传递参数
public class ChangeMethodTestDrive {
public static void main(String[] args) {
int[] date = new int[] {1,2,5,6,3};
System.out.println(ChangeMethod.sum(1,3,6));
System.out.println(ChangeMethod.sum(date));
}
}
class ChangeMethod {
/**
*计算给定参数数据的累加结果
*@param date 要进行累加的数据,采用可变参数,本质上属于数组
*@return 返回累加结果
*/
public static int sum(int... date) {
int sum = 0;
for(int temp : date) {
sum += temp;
}
return sum;
}
}
//输出:10"\n" 17
❀对象数组
在Java中所有的数据类型均可以定义为数组,包括基本数据类型和引用数据类型。引用数据类型定义为的数组就称为对象数组,对象数组的定义有以下两种方法:
- 对象数组动态初始化:类 对象数组名称 [ ] = new 类 [长度];
- 对象数组的动态初始化:类 对象数组名称 [ ] = new 类 [ ] {实例化对象,实例化对象...};
public class ObjectArrayTestDrive {
public static void main(String[] args) {
ObjectArray[] obj = new ObjectArray[3]; //对象数组的动态初始化
ObjectArray o1 = new ObjectArray("张三",19);
obj[0] = o1; //对象数组初始化内容可以利用引用传入
obj[1] = new ObjectArray("李四",20);
obj[2] = new ObjectArray("王五",21);
for(ObjectArray temp : obj) {
System.out.println(temp.getInfo());
}
}
}
class ObjectArray {
private String name;
private int age;
/**
* 通过构造函数初始化对象的成员属性
* @param name 对象的名称
* @param age 对象的年龄
*/
public ObjectArray(String name,int age) {
this.name = name;
this.age = age;
}
public void setName (String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return age;
}
/**
*返回当前对象的详细信息
* @return 返回详细信息
*/
public String getInfo() {
return "姓名:" + this.name + "、年龄:" + this.age;
}
}
/*
姓名:张三、年龄:19
姓名:李四、年龄:20
姓名:王五、年龄:21
*/
❀数据表和Java对象之间的映射关系
- 数据表和Java类之间的映射关系实例https://blog.csdn/weixin_64450588/article/details/127461540
❀String类对象的实例化
在实际项目开发中,String类是一个必须使用的类,可以说是项目的核心组成类。在Java中并没有String这一数据类型的存在,考虑到程序开发的需要,Java通过设计的方式提供了String类。JDK1.8以前的String类保存的是字符数组,1.9以后保存的是字节数组,可以得出字符串就是对数组的一种特殊包装的应用的结论。
🍎String对象的实例化有两种方式:
- 直接赋值实例化:String str = "success";
- 利用构造方法实例化:String str = new String("success");
🍎直接赋值实例化和利用构造方法实例化的最终结果相同,但是两者之间是有本质区别的。
🍎两种实例化方式的比较:
🐝直接赋值实例化String对象的内存分析:String str = "shuai";
此时会在内存中开辟一块堆内存空间,保存"shuai"字符串数据,并且位于占内存上的引用将直接引用此堆内存空间。并且通过直接赋值方式实例化的String对象那个会进入字符串常量池,对相同字符串的多次赋值还可以实现对堆内存的重用(即多个引用指向同一块堆内存空间)。
public class ValueJudgeTestDrive { public static void main(String[] args) { String strA = "shuai"; String strB = "shuai"; System.out.println(strA == strB);//strA和strB引用了同一块堆内存地址 } } //输出:true
🐝通过构造方法实例化的内存分析:String str = new String("shuai");
因为每一个字符串都是一个String的匿名对象,所以上述程序段会首先在堆内存中开辟一块空间保存字符串”shuai“,而后又使用关键字new开辟另一块堆内存空间,str引用真正引用的也是new关键字开辟的那一块堆内存空间,而通过匿名对象开辟的堆内存空间不会被任何栈内存指向,会成为垃圾空间,等待被垃圾护回收机制回收。所以用构造方法开辟内存空间实际上会在堆上开辟两块空间,其中有一块会成为垃圾空间,造成内存的浪费。此外,通过构造方法实例化String类对象,由于关键字new永远表示开辟信息堆内存空间,所以其内容不会保存在字符串常量池中。
public class ValueJudgeTestDrive { public static void main(String[] args) { String strA = new String("shuai"); String strB = new String("shuai"); System.out.println(strA == strB);//new关键字开辟了两块不同的堆内存空间,由两个不同的栈内存空间指向 } } //输出:false
🐕补充:如果希望通过构造方法实例化的String对象也可以进入字符串常量池保存,可以采用String类定义的intern( )方法实现:
public class ValueJudgeTestDrive { public static void main(String[] args) { String strB = new String("shuai").intern();//通过构造方法实例化的String类入池 String strC = "shuai"; System.out.println(strB == strC); } } //输出:true
❀字符串比较
🍎字符串比较有两种不同的方法:
- 运算符:"==":Java中的基本类型判断相等可以通过"==" 运算符直接进行数值的比较来实现,但对String类使用"=="比较的将不是内容而是字符串的堆内存地址
- 使用equals( )方法:负责字符串的内容比较。在项目开发中,对字符比较基本都是对内容是否相同的比较,主要使用String类的equals()方法
public class ValueJudgeTestDrive {
public static void main(String[] args) {
String strA = "shuai";
String strB = new String("shuai");
String strC = strA;
System.out.println(strA == strC);
System.out.println(strA == strB);
System.out.println(strA.equals(strB));
}
}
//输出:true,false,true
🍎实际开发中字符串比较规避NullPointerException问题:
public class ValueJudgeTestDrive {
public static void main(String[] args) {
String input = null; //假设此内容由用户输入
String result = "start";
if(input.equals(result)) {
System.out.println("right");
}
}
}
//输出:NullPointerException
/*
错误出现原因:程序在编译时还没有输入数据,input的内容为null,而空引用调用方法将导致错误信息提示
解决方法:
在if语句中使用result.equals(input)
*/
❀字符串常量池
🍎在程序中,常量是指不可被改变的内容的统称,Java中定义的字符串,严格意义上来讲是String类的匿名对象。对String类对象直接赋值的操作,实际上就是相当于为一个匿名对象设置了一个索引名称而已。String类的匿名对象是由系统自动生成的,不用用户自己创建。
🍎Java中实例化String对象时,如果处理不当,可以会存在内容相同的实例化对象重复开辟堆内存空间,造成内存的大量浪费,为了解决这个问题,JVM提供了一个字符串常量池(或称为字符串对象池,本质属于一个动态对象数组),所有通过直接赋值实例化的String对象和通过构造方法实例化的并且手动入池的String对象都会保存在此常量池中,以供内容相同的String对象的引用。
🐝Java中的字符串常量池分为两种:
- 静态常量池:是指(*.class)在加载的时候会自动将此程序中保存的字符串、普通的常量、类和方法等信息全部进行分配。
- 运行时常量池:当一个(*.class)文件加载之后,有一些字符串的内容是通过String对象的形式保存后再实现字符串连接处理。String对象的内容可以改变,所以称此时为运行时常量池。
public class ValueJudgeTestDrive { public static void main(String[] args) { String strA = "baixiaoshuai";//开辟对象并入池 //使用+进行字符串连接,由于所有内容都是常量,所以其本质上表示一个字符串 //strA和strB共同指向同一块堆内存空间 String strB = "bai" + "xiao" + "shuai"; System.out.println(strA == strB); } } //输出:true
public class ValueJudgeTestDrive { public static void main(String[] args) { String strA = "baixiaoshuai";//开辟对象并入池 String StrTemp = "shuai"; //使用+进行字符串连接,由于所有内容都是常量,所以其本质上表示一个字符串 //logo为变量,动态拼凑 String strB = "bai" + StrTemp + "shuai"; System.out.println(strA == strB); } } //输出:false //该程序运用了一个logo对象定义了要连接的字符串的内容,使得程序编译时无法知道logo对象的具体内容,strB无法从字符串常量池中获得相同字符串引用
❀字符串的修改
String类对于数据的存储是基于数组实现的,数组本身属于定长的数据类型,这表明String对象再定义之后其值不可以被修改,而所有字符串对象内容的修改都是通过引用的变化来实现的。
public class ValueJudgeTestDrive {
public static void main(String[] args) {
String strA = "www.";
strA += "shuai";
strA = strA + "";
System.out.println(strA);
}
}
//输出:www.shuai
❀主方法的组成分析
- public :描述的是一种访问权限,主方法是一切的开始点,开始点一定是公共的
- static :程序的执行是通过类名称完成的,表示此方法是由类直接调用。
- void :主方法是一切程序的起点,程序一旦开始执行是不需要返回任何结果的
- main :系统定义好的方法名称,通过Java方法解释一个类时,会自动找到此方法的名称
- String[] args :字符串数组,实现程序启动参数的接收
❀面向对象的继承性
在面向对象的设计过程中,类是基本的逻辑单位。对这样单位需要考虑到代码重用的问题,所以在面向对象的程序设计中提出了类的继承性这一特性,利用这一特性可以实现类的重用性定义。
🍎类继承的定义:
🐕严格来讲,继承性是指扩充一个类已有的功能,语法:class A extends B { };在继承结构中,多数情况下将子类称为派生类,把父类称为超类(SuperClass)。子类继承父类后,可以重用父类中的方法和属性(包括值),子类也可以根据功能的需要在父类的基础上进行结构扩充,所以子类往往比父类描述的更具体。
🍎子类对象实例化的过程:
🐕在子类对象实例化之前往往都会默认调用父类中的无参构造方法,为父类对象实例化,然后再进行子类构造方法的调用,为子类对象实例化。子类对象实例化之前一定会实例化父类对象,实际上就相当于子类的构造方法中隐含了一个super()的形式。
🐸super():表示在子类中明确调用父类中的构造方法,如果不写也会默认调用,super()构造调用语句只能在子类的构造方法中执行,且必须放在子类构造方法的首行,如果父类没有提供无参构造方法的话,可以通过"super(参数,...)"的形式调用父类中指定的构造方法。
class Person {
public Person() {
System.out.println("调用了父类构造方法");
}
}
class Student extends Person {
public Student() {
// super();
System.out.println("调用了子类构造方法");
}
}
public class ExtendsTestDrive {
public static void main(String[] args) {
Student s = new Student();
}
}
🍎继承限制
- 一个子类只能继承一个父类,存在单继承局限
- 子类汇集成父类所有的属性和方法,但是对于非私有(no private)的操作属于显示继承(可以直接利用对象进行操作),而对于私有(private)的属于隐式继承,通过getter()方法间接访问获得值。
class Person { private String name; public void setName(String name) { this.name = name; } public String getName() { return this.name; } } class Student extends Person { public Student() {} public Student(String name) { this.setName("adshjfh"); } public String getInfo() { return "姓名:" + this.getName(); } } public class ExtendsTestDrive { public static void main(String[] args) { Student s = new Student("字节跳动"); System.out.println(s.getInfo()); } } //输出: 字节跳动
其他语言中的多重继承和Java语言中的多层继承:
在Java中,一个子类只允许继承一个父类。为了实现其他语言中一个子类继承多个父类(多重继承)的效果,可以使用多层继承来实现,例如:
class C extends A,B {} //错误,Java语法中不支持 //为了实现同等效果,可以: class B extends A {} class C extends B {}
❀覆写
🍎方法覆写的定义和意义:
在继承结构中,子类可以继承父类的所有方法,当父类中的某些方法无法满足子类的需要时,子类可以针对已有的方法进行扩充。在子类中定义与父类中方法名称,返回值类型,参数类型以及个数完全相同的方法,称为方法覆写。
方法覆写主要是定义子类个性化的方法体,同时为了保持父类的结构形式,才保留了父类的方法名称。当通过子类实例化对象调用方法时调用的是被覆写过的方法,如果需要在子类中调用父类中的方法,可以使用"super.方法()"的形式调用。
子类调用父类已被覆写过的方法:
public class ChannelTestDrive {
public static void main(String[] args) {
DateBaseChannel channel = new DateBaseChannel();
channel.connect();
}
}
class Channel {
public void connect() {
System.out.println("【channel父类】进行资源的连接...");
}
}
class DateBaseChannel extends Channel {
public void connect() {
//子类调用父类被覆写过的方法,如果额米有使用super.方法()的定义,
//那么相当于使用this.方法,递归调用本类方法,最终会出现栈溢出错误
super.connect();
System.out.println("【channel】子类进行数据库资源的连接");
}
}
🍎方法覆写的限制:
被子类覆写的方法不能拥有比父类更严格的访问控制权限。
观察错误的方法覆写:
class Channel {
public void connect() {
System.out.println("channel父类");
}
}
class DateBaseChannel extends Channel {
void connect() { //错误,子类使用了比父类更严格的访问权限
System.out.println("channel子类");
}
}
🐕父类方法的权限定义为private时子类无法覆写该方法。
按照方法覆写的限制要求,子类方法的设置权限需要大于等于父类的权限。但如果父类中使用的是方法是private权限,即使子类的定义方法满足覆写要求,对于子类而言也只是定义了一个新方法而已。
public class DateBaseChannelTestDrive {
public static void main(String[] args) {
DateBaseChannel d = new DateBaseChannel();
d.handle();
}
}
class Channel {
private void connect() {
System.out.println("channel父类");
}
public void handle() {
this.connect();
}
}
class DateBaseChannel extends Channel {
public void connect() {
System.out.println("channel子类");
}
}
//输出:channel父类
🍎方法覆写和方法重载的区别:
NO. | 区别 | 重载 | 覆写 |
1 | 英文单词 | Overloading | Overriding |
2 | 定义 | 方法名称相同,参数的名称 及个数不同 | 方法的名称,参数类型 和个数,返回值类型完全相同 |
3 | 权限 | 没有权限要求 | 被子类覆写的方法不能拥有 比父类更严格的访问控制权限 |
4 | 范围 | 发生在一个类中 | 发生在继承关系中 |
🍎属性覆盖
子类中除了可以对父类中的方法进行覆写外,也可以对非private定义的父类属性进行覆盖,只需要在子类中定义与父类中成员属性相一致的名称即可。
❀this与super的区别
NO. | 区别 | this | super |
---|---|---|---|
1 | 定义 | 表示本类对象 | 表示父类对象 |
2 | 使用 | this.属性 this.方法() this() | super.属性 super.方法() super() |
3 | 调用构造 | 调用本类构造方法,要 放在首行 | 子类调用父类构造方法, 要放在首行 |
4 | 查找范围 | 先从本类中查找,查找 不到查找父类 | 直接由子类查找父类 |
5 | 特殊 | 表示当前对象 | -- |
❀final关键字
- final用于定义类:该类不可以被继承
- final用于定义方法:该方法不可以被覆盖
- final关键字用于定义变量: 该变量成为常量
🐕大部分的程序设计中,常量往往会作为一些全局的标记使用,在进行常量定义时,往往会使用public static final的组合来定义全局变量。
❀Annotation注释
Annotation注解是通过配置注解简化程序配置代码的一种技术手段。
🐕@Override
准确覆写,可以在不清楚父类的情况下立刻分辨出哪些是覆写方法,哪些是子类扩充方法,同时利用@Override注解也可以在编译时检测出由于子类拼写问题所造成的方法覆写的错误。
class ChannelTemp {
public void print() {
System.out.println(2);
}
}
class ChannelSon extends ChannelTemp {
@Override //此方法为超类方法的覆写
public void print() {
System.out.println(1);
}
}
🐕@Deprecated
过期声明,现在的软件项目开发几乎所有的项目都会出现迭代更新的过程,每一次更新都会涉及代码结构、性能与稳定性的提升,经常会出现某些程序结构不再适合新版本的情况。在这样的背景下,如果新版本直接取消某些类或者某些方法可能造成部分稳定程序的出错,为了解决这样的问题,在版本更新时对那些不再推荐使用的操作使用@Deprecated注解声明,这样在程序编译时会对使用了此类结构的代码提出警告信息。
@SuppressWarnings
压制警告。编译器为了代码的严格性,在编译时往往会给出一些错误的提示信息(非致命错误),但是有些错误信息不是必要的。为了防止这些提示信息的出现,Java提供@SuppressWarnings注解来进行警告信息的压制,在此注解中可以通过value属性来设置要压制的警告类型。
🍎value可设置的警告信息
- deprecation:使用了不赞成使用的类或者方法时的警告
- unchecked:执行了未检查的转换时警告。例如:泛型操作中没有指定泛型类型
- fallthrough:switch语句直接执行下一种情况没有break时的警告
- path:在类路径、源文件路径等有不存在路径时的警告
- serial:在可序列化的类上缺少serialVersionUID定义时的警告
- finally:任何finally子句不能正常完成时的警告
- all:关于以上任何情况时的警告
压制警告信息:
❀面向对象的多态性
在面向对象设计中多态性描述的是同一结构在执行时会根据不同的形式展现出不同的效果,在Java中,多态性可以分为两种不同的表现形式:
🍎展现形式1:方法的多态性
- 方法的重载:同一方法根据传入的参数类型或个数的不同实现不同的功能
- 方法的覆写同一方法根据实现子类的不同有不同的实现
🍎展现形式2:对象的多态性
- 对象向上转型:父类实例 = 子类实例,自动完成转换
- 对象向下转型:子类实例 = (子类)父类实例,强制完成转换
🐕对象的多态性和方法覆写是紧密联系在一起的。
🍎对象向上转型
在子类对象实例化之前一定会实例化父类对象,子类对象通过父类进行接收,即可实现对象的自动向上转型。但此时本质还是子类对象,一旦子类中覆写了父类的方法,并且调用了该方法,所调用的一定是子类中被覆写过的方法;否则就调用父类中定义的方法。
public class MessageTestDrive {
public static void main(String[] args) {
DateBaseMessage date = new DateBaseMessage();
date.print();
NetMessage net = new NetMessage();
net.print();
}
}
class Message {
public void print() {
System.out.println("Message中的print方法");
}
}
class DateBaseMessage {
public void print() {
System.out.println("DateBaseMessage中的print方法");
}
}
class NetMessage {
public void print() {
System.out.println("NetMessage中的print方法");
}
}
/*
输出:
DateBaseMessage中的print方法
NetMessage中的print方法
*/
🍎对象向下转型
子类继承父类后可以对父类的功能进行扩充,即除了方法覆写,子类也可以定义属于自己的新方法。而对于子类扩充的方法只有在具体的子类实例才可以调用。在这样的情况下如果子类已经发生了向上转型就需要强制向下转型来实现子类扩种方法的调用。
public class PersonTestDrive {
public static void main(String[] args) {
Person p = new SuperMan(); //对象向上转型
p.run();//run方法没有被覆盖,执行Person类中的run方法
SuperMan s = (SuperMan) p;//对象向下转换,调用Superman类中扩充出的新方法
s.fire();
s.fly();
}
}
class Person {
public void run() {
System.out.println("向前奔跑...");
}
}
class SuperMan extends Person {
public void fly() {
System.out.println("向前飞行...");
}
public void fire() {
System.out.println("勇敢战斗...");
}
}
🍎注意:对象必须先发生向上转型,之后才可以向下转型。
在对象向下转型中,父类实例是不可能转化为任意子类实例的,必须先通过子类实例化,利用向上转型让父类对象与其具体子类实例之间发生联系后才可以向下转型,否则将出现ClassCastException(类转换异常)的错误。
❀instanceof关键字
对象的向下转型存在安全隐患,在转换之前可以通过instanceof关键字对所属类型进行判断。一个父类对象在没有通过子类对象实例化之前使用Instanceof关键字判断的结果为false,实例化之后使用instanceof关键字判断的结果为true。所以在父类对象向下转型之前可以使用instance关键字判断该父类对象是否对相应的子类对象发生了向上转型。
//语法:
对象 instanceof 类
public class PersonTestDrive {
public static void main(String[] args) {
Person p = new SuperMan(); //对象向上转型
p.run();
if(p instanceof SuperMan) {
SuperMan s = (SuperMan) p;
s.fly();
s.fire();
}
}
}
🍎null的实例使用instanceof关键字进行判断会返回false
null没有对应的堆内存空间,无法确定出具体类型,所以使用instanceof关键字判断的结果为false.
❀Object类
Object是一个公共类,没有父类,但却是所有类的父类,使用class关键字定义的类都默认继承自Object类,所有类的对象都可以利用向上转型的特点实例化Object类对象。
🍎Object可以接收所有引用数据类型,包括数组
public class ObjectTestDrive {
public static void main(String[] args) {
Object obj = new int[] {1,7,8,5,6}; //子类对象int[]向上转型为Object类
if(obj instanceof int[]) { //判断obj是否为数组对象
int[] arr = (int[])obj; //将obj向下转型为int[]
for(int temp : arr) { //输出
System.out.println(temp);
}
}
}
}
🍎Object可以接收接口实例
interface IMessage { public static final String INFO = "success then";//全局常量 public abstract String getInfo();//抽象方法 } interface IChannel { public abstract boolean connect();//抽象方法 } class MessageImp implements IMessage,IChannel { //实现接口 @Override public String getInfo() { //方法覆盖 if (this.connect()) { return "加油:success then"; } return "[默认]" + IMessage.INFO; //访问接口中的全局常量 } @Override public boolean connect() { //方法覆盖 return true; } public void systemOut() { System.out.println("good"); } } public class InterfaceTestDrive { public static void main(String[] args) { Object obj = new MessageImp(); if(obj instanceof MessageImp) { ((MessageImp) obj).systemOut();; } } }
🍎获取对象信息的实现
在Object类中提供有一个toStrnig()方法,可以实现对象信息的获取,而该方法是在进行对象输出时默认调用的。
public class ToStringTestDrive {
public static void main(String[] args) {
Person p1 = new Person("success",21);
System.out.println(p1); //输出对象时默认调用teString方法
}
}
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "姓名:" + this.name + "、年龄:" + this.age;
}
}
//输出:姓名:success、年龄:21
❀'=='和equals()方法的区分
==运算符和equals方法的比较https://blog.csdn/weixin_64450588/article/details/127526756
❀抽象类
抽象类的作用是设计出一个特定的类对子类做出任何强制性的覆写约定,抽象类和普通类相比唯一增加的就是抽象方法的定义。抽象类在使用时要求必须被子类继承,并且子类需要覆写抽象类中的所有抽象方法。
🍎抽象类的特点:
- 抽象类必须有子类
- 抽象类无法进行对象实例化操作
🍎抽象类的定义原则
- 抽象类必须提供子类,子类使用extends继承一个抽象类
- 抽象类的子类一定要覆写抽象类中的全部抽象方法
- 抽象类的对象实例化可以利用对象的多态性通过子类向上转型的方式完成
public class Demo3TestDrive {
public static void main(String[] args) {
Message msg = new DateBaseMessage();//子类对向上转型实例化抽象类对象
msg.setType("success");
System.out.println(msg.getConnectInfo());
}
}
abstract class Message {
private String type;
public abstract String getConnectInfo();//在定义抽象方法时只需要定义方法名称
public void setType(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
}
class DateBaseMessage extends Message {
//对抽象类中的方法进行覆写
@Override
public String getConnectInfo() {
return super.getType();
}
}
🍎抽象类的相关说明
- 抽象类必须被子类继承,所以在使用时不允许使用final关键字定义抽象类或者抽象方法
abstract final class AbstractRequire { //错误,抽象类使用final关键字修饰后不能被子类继承 } abstract class AbstractRequire { public abstract final void print();//错误,抽象方法使用final关键字修饰后不能被覆写 }
-
在抽象类中可以定义成员属性和普通方法。为了对抽象类中的成员属性进行初始化,可以在抽象类中提供构造方法。子类在继承抽象类时,会默认调用抽象类的无参构造方法。如果抽象类提供的不是无参构造方法,那么子类必须通过super()的形式调用相应参数的构造方法.
public class Demo3TestDrive { public static void main(String[] args) { Message msg = new DateBaseMessage("msg");//子类对向上转型实例化抽象类对象 System.out.println(msg.getConnectInfo()); //输出msg } } abstract class Message { private String type; public Message(String type) { this.type = type; } public abstract String getConnectInfo();//在定义抽象方法时只需要定义方法名称 public void setType(String type) { this.type = type; } public String getType() { return this.type; } } class DateBaseMessage extends Message { public DateBaseMessage(String type) { super(type);//通过子类构造方法的super调用相应参数类型的构造方法 } //对抽象类中的方法进行覆写 @Override public String getConnectInfo() { return super.getType(); } }
-
抽象类中允许没有抽象方法,即使没有抽象方法,也无法通过new关键字进行抽象类对象的实例化。
-
在抽象类中可以使用static关键字定义方法,且该方法的调用不受抽象类实例化对象的限制。
abstract class Message { public abstract String getInfo(); public static Message getInstance() { return new DateBaseMessage(); } } class DateBaseMessage extends Message { //对超类中的抽象方法进行覆写 @Override public String getInfo() { return "success"; } } public class MsgTestDrive { public static void main(String[] args) { Message msg = Message.getInstance();//子类对象向上转型实例化父类对象 System.out.println(msg.getInfo());//调用的是子类中覆写的抽象类的方法 } }
❀接口
在Java中,接口属于一种特殊的类,需要通过interface关键字进行定义,在接口中可以定义全局常量,抽象方法(必须是public访问权限),default方法,以及用static关键字定义的方法。接口的出现,核心作用是实现方法名称的暴露和子类信息的隐藏,接口的使用原则如下:
- 接口需要子类实现,并且子类利用implements关键字可以实现多个接口
- 子类如果不是抽象类,那么必须覆写接口中的全部抽象方法
- 接口对象可以利用子类对象向上转型实例化
interface IMessage {
public static final String INFO = "success then";//全局常量
public abstract String getInfo();//抽象方法
}
interface IChannel {
public abstract boolean connect();//抽象方法
}
class MessageImp implements IMessage,IChannel { //实现接口
@Override
public String getInfo() { //方法覆盖
if(this.connect()) {
return "加油:success then";
}
return "[默认]" + IMessage.INFO; //访问接口中的全局常量
}
@Override
public boolean connect() { //方法覆盖
return true;
}
}
public class InterfaceTestDrive {
public static void main(String[] args) {
IMessage msg = new MessageImp(); //子类对象向上转型实例化接口对象
System.out.println(msg.getInfo()); //调用子类中覆写实现的方法
if(msg instanceof MessageImp) { //判断msg是否为对应的子类对象
System.out.println(((IChannel) msg).connect());///对象向下转型为接口类型调用子类中覆写过的connect方法
}
}
}
/*
输出:加油:success then
true
*/
🍎接口类型的简写(为了使接口定义更加清楚,通常保留public声明)
interface IChannelTemp {
public String INFO = "www.baidu"; //默认为final,static
public String getInfo(); //默认为abstract方法
}
🍎extends关键字应用在接口上,可以实现接口的继承关系,并且可以同时继承多个父接口:
interface IMessage {
public static final String INFO = "success";
public abstract String getInfo();
}
interface IChannel {
public abstract boolean connect();
}
interface IService extends IMessage,IChannel { //接口使用extends继承多个父接口
public String service(); //接口中默认为抽象方法
}
class MessageService implements IService { //类实现接口中的抽象方法
@Override
public String getInfo() { //实现父接口中的方法
return IMessage.INFO;
}
@Override
public boolean connect() { //实现接口中的方法
return true;
}
@Override
public String service() { //实现接口中的方法
return "www.baidu";
}
}
🍎接口定义的加强
使用default或static关键字在接口中定义方法,在接口中定义完整的方法,解决了后期维护升级过程中可能出现的在接口下的多个子类中共同增加一个相同方法的代码重复编写问题。使用default关键字定义的方法只能在接口中使用,并且只有通过实例化对象才可以调用,为了比卖你实例化对象的依赖,在接口中也可以使用static关键字定义方法,通过接口名称直接调用。通过static定义的接口方法只能在接口自身使用。通过default定义的接口内方法可以被子类覆写实现功能的更加抽象化。
interface Person {
public static String ageStage = "18-23";
public abstract String getInfo();
public default void print() { //通过default关键字定义的方法,在这里dafault和public关键字可以共同使用
System.out.println("success-man");
}
public static void memory() { //通过static关键字定义的方法,通过接口名称调用
System.out.println("in times of trials and tribulation");
}
}
class Student implements Person {
@Override
public String getInfo() {
return "success-then";
}
@Override
public void print() {
System.out.println("success-man1.2");
}
}
public class DefaultStaticInInterfaceTestDrive {
public static void main(String[] args) {
Person stu = new Student(); //子类对象向上转型实现接口的实例化
System.out.println(stu.getInfo());
stu.print(); //接口中default定义的方法可以被具体的子类覆写
Person.memory(); //接口中static定义的方法只能在接口内使用
}
}
/*
输出:
success-then
success-man1.2
in times of trials and tribulation
*/
🍎关于接口和抽象类
通过一系列的分析可以发现,接口中static,default关键字定义的方法与抽象类的作用有些重叠,但是不能认为开发中就不再使用抽象类。对于JDK1.8以后的接口功能扩充,通常更偏向的人为这只是Java接口的修复和升级,是对于哪些结构有缺陷的代码的补救措施。在实际开发中,有了自定义接口后,中间最好设计一个过渡的抽象类。
❀泛型
泛型的出现是为了解决项目中可能出现的ClassCastException异常,最为核心的方案就是避免强制性的进行对象向下转型的操作。泛型设计的核心在于:类中的属性类型或方法的参数类型与返回值类型采用动态标记,在对象实例化时动态配置所需要的数据类型。
注意:泛型只允许设置引用数据类型
🍎使用泛型定义类:
class Point<T> {
private T x;//声明x成员变量,类型由实例化对象决定
private T y;
public void setX(T x) { //设置X,类型由实例化对象决定
this.x = x;
}
public void setY(T y) {
this.y = y;
}
public T getX() {
return this.x;
}
public T getY() {
return this.y;
}
}
public class ParadigmTestDrive {
public static void main(String[] args) {
Point<Integer> point = new Point<Integer>();
point.setX(10); //自动装箱,必须是整数
point.setY(8); //自动装箱,必须是整数
//point.getX();避免强制转换,获取X的值
System.out.println("X: " + point.getX() + "、Y: " + point.getY());
}
}
在使用泛型的过程中,JDK考虑到了最初开发者的使用习惯,允许在实例化对象时不设置泛型类型,但是在编译时会出现警告信息,为了保证程序不出错,未设置的泛型类型将使用Object类作为默认类型。
public class ParadigmTestDrive {
public static void main(String[] args) {
Point point = new Point();
point.setX(10); //自动装箱,必须是整数
point.setY(8); //自动装箱,必须是整数
//point.getX();避免强制转换,获取X的值
int x = (Integer) point.getX(); //Object类强转为Integer类后自动拆箱
int y = (Integer) point.getY(); //Object类强转为Integer类后自动拆箱
System.out.println("X: " + x + "、Y: " + y);
}
}
/*
警告信息:
Raw use of parameterized class 'Point'
Unchecked call to 'setX(T)' as a member of raw type 'com.shuai.www.Point'
*/
🍎泛型通配符 ‘?’
利用泛型在实例化对象时进行动态类型匹配,可以有效解决对象向下转型的安全隐患,但是在程序中实例化泛型对象时,不同泛型类型的对象之间是无法进行引用传递的。在进行泛型对象类型引用时,为了适应所有当前类的实例化对象。可以在接收参数时使用 '?' 作为泛型通配符使用。利用 '?' 表示的泛型类型,只允许从对象中获取数据,而不允许修改数据。
class MessageCl<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return this.content;
}
}
public class MessageTestDrive {
public static void main(String[] args) {
Message<String> msg = new Message<>(); //实例化泛型对象
msg.setContent("success");
fun(msg);
}
/**
* 只允许读取msg对象中的内容,不允许修改
* @param temp 传入的要读取的泛型对象
*/
public static void fun(Message<?> temp) { //使用泛型通配符接收泛型对象
System.out.println(temp.getContent());
}
}
//输出:success
在泛型类型中,Object类型的泛型类型只能接收Object类型的泛型对象。
🍎泛型的上限和下限
泛型通配符'?'除了可以匹配任意的泛型类型外,也可以配置泛型的上限和下限定义更加严格的类的范围,
- 设置泛型上限:<? extends 类>,只能使用当前类或者当前类的子类设置泛型类型。
class MessageCls<T extends Number> { //只能传入Number的子类 private T content; //泛型属性 public void setContent(T content) { this.content = content; } public T getContent() { return this.content; } } public class ParadigmupperTestDrive { public static void main(String[] args) { MessageCls<Integer> s = new MessageCls<>(); //传入Number类的子类初始化MessageCls对象 s.setContent(888); fun(s); } /** * 接收传入的泛型对象,读取内容,不能修改 * @param temp */ public static void fun(MessageCls<? extends Number> temp) { System.out.println(temp.getContent()); //读取泛型对象中的内容,不能修改 } }
-
设置泛型下限:<? super 类>,只能使用设置类或者设置类的父类色湖之泛型类型
class MessageCls<T> { //只能传入Number的子类 private T content; //泛型属性 public void setContent(T content) { this.content = content; } public T getContent() { return this.content; } } public class ParadigmupperTestDrive { public static void main(String[] args) { MessageCls<String> s = new MessageCls<>(); //传入Number类的子类初始化MessageCls对象 s.setContent("success"); fun(s); } /** * 接收传入的泛型对象,读取内容,不能修改 * @param temp 传入的待读取的泛型类型 */ public static void fun(MessageCls<? super String> temp) { //只能传入String类或者String类的父类 System.out.println(temp.getContent()); //读取泛型对象中的内容,不能修改 } }
🍎泛型接口
定义泛型接口
interface IMessage<T> {
String echo(T msg); //默认为public的抽象方法
}
泛型接口子类定义的两种方式:
- 在子类中继续声明泛型定义泛型接口的子类
interface IMessage<T> { //泛型接口 String echo(T msg); } class MessageImp<S> implements IMessage<S> { //泛型子类实现泛型接口 @Override public String echo(S msg) { return "success"; } } public class ParadigmupperTestDrive { public static void main(String[] args) { IMessage<String> msg = new MessageImp<>(); //接口类型向上转型实例化接口对象 System.out.println(msg.echo("success")); } }
-
在子类中设置接口的泛型类型
class MessageImp implements IMessage<String> { //在子类中设置接口的泛型类型 @Override public String echo(String msg) { return "success"; } } public class ParadigmupperTestDrive { public static void main(String[] args) { IMessage<String> msg = new MessageImp(); //接口类型向上转型实例化接口对象 System.out.println(msg.echo("success")); } }
🍎泛型方法
在方法中定义泛型时,该方法不一定非要在泛型类中定义。
public class ParadigmMethodTestDrive {
public static void main(String[] args) {
String[] arr = fun("1","2","3");
for(String temp : arr) {
System.out.println(temp);
}
}
public static <T> T[] fun(T... args) {
return args;
}
}
更多推荐
Java编程基础
发布评论