面向对象编程
神速理解面向对象_表格和对象的关系
表格结构和类结构
例子:
1.表格的动作和类的方法
每个公司的雇员都要有相关的动作。比如:所有雇员每天的工作标准动作有
1.参加晨会,领取当天任务
2.午休
3.提交工作日志
我们可以在雇员表中将动作信息也包含进去:
新增的列“雇员动作说明”,显然是对所有的雇员都有用,每个雇员都有这个动作。 在类中就是定义成方法:
当然,我们也可以根据需求,为雇员定义多个动作。比如:午休、提交工作日志、领取
工资等等。
2.对象对应表中的行数据
前面两节,我们主要讲解的是“表结构和类的对应关系”。那么,表中的数据和什么对应呢?
表中的一行一行的数据,都在表结构的约束范围内,大家的结构都是相同的。如下表:
显然,每一行数据都有“姓名”、“基本工资”等“列”,也都有标准的“晨会动作”。在面向对象编程中,下面三句话大家记住:
1.表结构对应:类结构
2.一行数据对应:一个对象!
3.表中所有数据对应:这个类的所有对象
因此,上面的四行数据,我们使用四个对象就需要这样表示(假设有对应的构造方法如下代码是示意,非真实代码):
有的人可能注意到了,创建对象的时候没有传入“晨会动作”这个列,是因为“晨会动作”是一个所有数据都有的标准动作,没必要再重复为每个对象创建“标准晨会动作”
把事务列出一个表,就能完美面向对象来表示或编程!
把事务列出一个表,就能完美面向对象来表示或编程
把事务列出一个表,就能完美面向对象来表示或编程
面向过程和面向对象区别
面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。C语言是一种典型的面向过程语言,Java是一种典型的面向对象语言。
面向过程适合简单,需要协作的事务,重点关注如何执行。 面向过程时,我们首先思考“怎么按步骤实现?并将步骤对应成方法,一步一步,最终完成。 这个适合简单任务,不需要过多协作的情况下。比如,如何开车?我们很容易就列出实现步骤:
比如:把大象装进冰箱分几步?
但是当我们思考比较复杂的设计任务时,比如“如何造车?",就会发现列出 1234 这样的步骤,是不可能的。那是因为,造车太复杂,需要很多协作才能完成。此时面向对象思想就应运而生了。
面向对象(Oriented-Obiect)思想更契合人的思维模式。我们首先思考的是“怎么设计这个事物?”比如思考造车,我们就会先思考“车怎么设计?",而不是“怎么按步骤造车的问题”。这就是思维方式的转变。
比如,我们用面向对象思想思考“如何设计车”:
天然的,我们就会从“车由什么组成”开始思考。发现,车由如下对象组成:
为了协作,我们找轮胎厂完成制造轮胎的步骤发动机厂完成制造发动机的步骤; 这样发现大家可以同时进行车的制造,最终进行组装,大大提高了效率。但是,具体到轮胎厂的一个流水线操作,仍然是有步骤的,还是离不开执行者、离不开面向过程思维!
因此,面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。但是,具体到
实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理。
我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面
向过程!
面向对象和面向过程思想的总结
面向对象如何设计_写诗和写小说
1.面向对象是“设计者思维”
面向对象是一种”设计者思维”设计时,先从问题中找名词然后确立这些名词哪些可以作为类,再根据问题需求确定的类的属性和方法,确定类之间的关系。
设计一款企业管理软件,我们需要进行面向对象分析。写一首诗、一篇文章、一篇小说
也需要进行面向对象分析。
因此,面向对象这种思维是任何人都需要学习、任何人都需要掌握的。
2.面向对象分析:写诗
'面向对象思维”不止适合工科、理科,也适合文科。比如:我要写一首诗,就是一种设计的过程,怎么样用简单的、有意境的词汇来描述一个场景。
《登鹳雀楼》是一首名诗,我们使用面向对象的思维方式分析一下。
我们先从场景中找出名词。里面有:
外在场景:1.太阳 2.天空 3.黄河 4.河流交汇处6. 草5.鹳雀楼
诗人情怀:1.登高望远2.进取3.努力
描述过程:由远及近,再到自身情怀。
我们也可以使用表格来表示上面的信息:
分析完上面这些 Object后,我们需要进行组合。把这些 Object 融合到一个场景。王之涣
大诗人设计的场景就是:
实:在高楼上看着太阳和黄河,真好看
虚:寄托一下登高望远、人生积极进取的精神。
好了。程序员“王之涣”开始琢磨他的程序了。
0.1 版《登鹳雀楼》
王之涣诗人爬上了登鹳雀楼,心情大好。把看到的场景和旁边的小甜甜说(0.1 版诗歌):
啊,太阳快下山了
啊,黄河往海的方向流。
啊,我还想看的远一点
啊,咱们再向上爬一层楼吧。
0.2 版《登鹳雀楼》
这么说完,哄小甜甜都有点费劲,这和我们村的二牛说的差不多啊。需要加工一下,太阳太俗,换成“红日"。“远一点”太没志气“诗人得夸张点,远一点怎么也得干里吧,估计李白那家伙就要写万里了,我就干里吧”。于是,诞生了 0.2 版诗歌:
红日要下山,
黄河往海(的方向)流。
要想看干里,
向上爬一楼。
0.3 版《登鹳雀楼》
至少都是5个字了,但是用语太俗,哄小甜甜还是费劲,但比村里面的二牛强一点,不光
词汇要雅,句子也要再雅一点(0.3 版诗歌):
红日依山尽
黄河往海流
欲看干里远
向上爬一楼
0.4 版《登鹳雀楼》
红日大家都知道,还是俗,我得变变样。咦,红的有点泛白(也可能是看久了,眼睛疲劳,那就白日吧,别人都没用过,“微创新一下”。欲看千里远,里面的动词换成名词更有场景感,欲目千里远。不错不错,再倒装一下变成“欲远干里目”。向上爬一楼,这个爬字看着就恶心,而且和“上”是一个意思,去掉它。“向上爬一楼'“向上一层楼”。于是(0.4 版诗歌出来了):
白日依山尽
黄河往海流。
欲远千里目
向上一层楼。
1.0版《登鹳雀楼》
这个 0.4 版已经哄的小甜甜花枝乱颤了。大诗人觉得这诗有流传的潜力,晚上回去再好好“精加工一下”。于是,字斟句酌,让读起来更加朗朗上口,最终成就了流传千年的诗歌(1.0 版诗歌):
白日依山尽
黄河入海流。
欲穷干里目
更上一层楼。
兄弟,会写诗了吗?多用“面向对象的设计思维”模拟“原创者的思考过程”,你离“封
神”会越来越近,你的学习也会越来越有意思。
3.面向对象分析:写小说
写小说本质上和设计软件是一样的,根据想要表达的内容,设计不同的场景、设计不同的任务。设计完成后,作者再按照一章一章的完成写作(执行阶段)。
所以,只要是“设计工作”都是相通的。程序员副业写写小说也不是不可能,《哈利波
特》的作者」.K.罗琳(J.K.Rowling),也是利用业余时间写出了传世之作,并且获得了数亿
英镑的收入。2004年,罗琳荣登《福布斯》富人排行榜,她的身价达到 10 亿美元。
对象的转型_向上转型或向下转型
1.明确小说的主要内容和目标
《茶馆》是现代文学家老舍所著,《茶馆》这本文章主要展现戊戌变法,揭示了近半个世纪中国社会的黑暗,腐败的社会现象。
以大茶馆的兴衰变迁,展示了清末到北洋军阀再到抗战胜利后,北京的各个阶层和社会风貌不同人物的变迁,像是亲身在当时的环境中,感受近 50 年的北京的变化!
2.人物设计分析
茶馆就是一个小社会,各类人物开始出现,他们不同的命运也被体现,侧面反映出当时黑反腐朽的时代。
3.整体事件设计
类的定义_属性_方法
1.对象和类的详解
类可以看做是一个模版,或者图纸,系统根据类的定义来造出对象。我们要造一个汽车怎么样造?类就是这个图纸,规定了汽车的详细信息,然后根据图纸将汽车造出来。
类:我们叫做 class。 对象:我们叫做 Object,instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。
总结:
2.类的定义方式
示例:
//每一个源文件必须有且只有一个public class,并且类名和文件名保持一致!
public class Car{
}
class Tyre{//一个JAVA文件可以同时定义多个class
}
class Ebgine{
}
class Seat{
}
对于一个类来说,有三种成员:属性field、方法method、构造器constructor。
3.属性(field 成员变量)
属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体在定义成员变量时可以对其初始化,如果不对其初始化,Java使用默认的值对其初始化。
属性的定义格式:
[修饰符]属性类型属性名=[默认值];
4.方法
方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
[修饰符] 方法返回值类型 方法名(形参列表){
//n条语句
}
示例:编写简单的学生类
/**
* 一个学生类
* */
public class SxtStu {
//属性
int id;
int age;
String sname;
public void study(){
//方法
System.out.println("学习中!");
}
public void kickball(){
System.out.println("踢球中!");
}
}
1.对象的转型
1.父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
2.向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型
简单内存分析(帮助理解面向对象)
/**
* 一个学生类
* */
public class SxtStu {
//属性
int id;
int age;
String sname;
public void study(){
//方法
System.out.println("学习中!");
}
public void kickball(){
System.out.println("踢球中!");
}
public static void main(String[] args) {
SxtStu s1 = new SxtStu(); //地址给s1
System.out.println(s1.id);
System.out.println(s1.sname);
//此时为默认值
s1.id = 1001;
s1.sname = "华生";
System.out.println(s1.id);
System.out.println(s1.sname);
s1.study();
s1.kickball();
}
}
运行结果:
构造方法详解_深入内存分析
构造方法(构造器 constructor)
构造器用于对象的初始化,而不是创建对象!
声明格式:
[修饰符]类名(形参列表){
//n条语句
}
构造器4个要点:
构造器通过new关键字调用!
构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值。
如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加!
构造器的方法名必须和类名一致!
以上次代码为例子:
课堂练习:
定义一个“点”(Point)类用来表示二维空间中的点(有两个坐标)。要求如下:
可以生成具有特定坐标的点对象。
提供可以计算该“点”距另外一点距离的方法。
/*
* 定义一个“点”(Point)类用来表示二维空间中的点(有两个坐标)。要求如下:
可以生成具有特定坐标的点对象。
提供可以计算该“点”距另外一点距离的方法。
* */
public class Point {
double x,y; //声明
//构造器↓
//类名↓ 形参列表↓
Point(double _x,double _y){
//多条语句↓
x = _x;
y = _y; //装修
}
//方法↓
//修饰符↓ //方法名↓ //形参列表↓
public double getDistance(Point p){
return Math.sqrt((x- p.x)*(x-p.x)+(y- p.y)*(y- p.y));
}
public static void main(String[] args) {
Point p1 = new Point(3.0,4.0);//生成特定坐标点
Point origin = new Point(0.0,0.0);//初始点0.0
System.out.println(p1.getDistance(origin));
}
}
运行结果;
内存模型分析:
构造方法的重载
代码示例:
/**
* 测试方法的重载
*/
public class User {
int id;
String name;
String pwd;
public User(){
}
public User(int id){
this.id =id;//重载
}
public User(int id,String name){
this.id =id;
this.name =name;//重载
}
public User(int id,String name,String pwd){
this.id =id;
this.name = name;
this.pwd = pwd;//重载
}
public static void main(String[] args) {
User u = new User();//调用了第一个无参构造器
User u1 = new User(1001);//调用了第二个重载的可定义id的构造方法;
User u2 = new User(1002,"zhangsan");//调用了第二个重载的可定义id,名字,的构造方法;
User u3 = new User(1003,"zhangsan","123456");//调用了第三个重载的可定义id,名字,密码的构造方法;
System.out.println(u.id);
System.out.println(u1.id+u1.pwd);
}
}
运行结果:
小技巧,右键代码页面:
生成
构造函数
JAVA虚拟机内存模型概念
虚拟机栈(简称:栈)的特点如下:
1.栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)。
2.JVM 为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变
量等)。
3.栈属于线程私有,不能实现线程间的共享!
4.栈的存储特性是“先进后出,后进先出
5.栈是由系统自动分配,速度快!栈是一个连续的内存空间!
堆的特点如下:
1.堆用于存储创建好的对象和数组(数组也是对象)。
2.JVM 只有一个堆,被所有线程共享。
3.堆是一个不连续的内存空间,分配灵活,速度慢!
4.堆被所有线程所共享,在堆的区域,会被垃圾回收器做进一步划分,例如新生代、老年代的划分。
方法区(也是堆)特点如下:
1.方法区是 JAVA 虚拟机规范,可以有不同的实现。
JDK7 以前是“永久代”
JDK7 部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中
JDK8 是“元数据空间”和堆结合起来。
2.JVM 只有一个方法区,被所有线程共享!
3.方法区实际也是堆,只是用于存储类、常量相关的信息!
4.用来存放程序中永远是不变或唯一的内容。(类信息【Class 对象,反射机制中会重点讲授】、静态变量、字符串常量等)
5.常量池主要存放常量:如文本字符串、final常量值。
程序执行时内存分析详解
运行时内存分配图:
参数传值机制
Java 中,方法中所有参数都是“值传递”,也就是“传递的是值的副本”也就是说我们得到的是“原参数的复印件,而不是原件”。因此,复印件改变不会影响原件。
基本数据类型参数的传值
传递的是值的副本。副本改变不会影响原件。
引用类型参数的传值
传递的是值的副本。但是引用类型指的是“对象的地址”。因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变”。
示例:多个变量指向同一个对象
public class Person {
String name;
int age;
public void show(){
System.out.println(name);
}
public static void main(String[] args) {
//创建p1对象
Person p1 = new Person();
p1.age=24;
p1.name="张三";
p1.show();
//创建p2对象
Person p2 = new Person();
p2.age =35;
p2.name ="李四";
p2.show();
Person p3 = p1;
Person p4 = p1;
p4.age = 80;
System.out.println(p1.age);
}
}
运行结果;
垃圾回收机制(Garbage Collection)
垃圾回收原理和算法
内存管理:
Java 的内存管理很大程度就是:堆中对象的管理,其中包括对象空间的分配和释放。
对象空间的分配:使用 new 关键字创建对象即可。
对象空间的释放:将对象赋值 null 即可。
垃圾回收过程
任何一种垃圾回收算法一般要做两件基本事情:
1.发现无用的对象
2.回收无用对象占用的内存空间。
垃圾回收机制保证可以将“无用的对象”进行回收。
无用的对象指的就是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。
垃圾回收相关算法
1.引用计数法
堆中的每个对象都对应一个引用计数器,当有引用指向这个对象时,引用计数器加1,而当指向该对象的引用失效时(引用变为 null),引用计数器减 1,最后如果该对象的引用计算器的值为 0时,则 Java 垃圾回收器会认为该对象是无用对象并对其进行回收。优点是算法简单,缺点是“循环引用的无用对象”无法别识别。
示例循环引用演示
代码中,s1 和 s2 互相引用对方,导致他们引用计数不为0,但是实际已经无用,但无法被
识别。
/*
* 测试循环引用
* */
public class Student {
String name;
Student friend;
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.friend = s2;
s2.friend = s1;
s1 = null;
s2 = null;
}
}
2.引用可达法
程序把所有的引用关系看作一张图,从一个节点 GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
内存管理_堆模型_分代机制(年轻代、老年代、永久代)
通用的分代垃圾回收机制
分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的。因此不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代、永久代。同时,将处于不同状态的对象放到堆中不同的区域。
1.年轻代
所有新生成的对象首先都是放在 Eden 区。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是 Minor GC,每次 Minor Gc 会清理年轻代的内存算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代”区域存放满对象后,就将对象存放到年老代区域。
2.年老代
在年轻代中经历了 N(默认 15)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象,年老代对象越来越多,我们就需要启动 Major GC和 Fu GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。
3,永久代
用于存放静态文件,如Java 类、方法等。持久代对垃圾回收没有显著影响。JDK7以前就是“方法区”的一种实现。JDK8 以后已经没有“永久代”了,使用metaspace元数据空间和堆替代。
.Minor GC:
用于清理年轻代区域。Eden 区满了就会触发一次 Minor GC。清理无用对象,将有用
对象复制到“Survivor1"、’“Survivor2” 区中。
.Major GC:
用于清理老年代区域。
.Full GC:
用于清理年轻代、年老代区域。 成本较高,会对系统性能产生影响,
JVM 调优和 FuI GC
在对 JVM 调优的过程中,很大一部分工作就是对于 Ful GC 的调节。有如下原因可能
导致 Full GC:
1.年老代(Tenured)被写满
2.永久代(Perm)被写满
3.System.gc()被显式调用
4.上一次 GC 之后 Heap 的各域分配策略动态变化
内存泄漏_垃圾回收机制总结
内存泄漏
指堆内存由于某种原因程序未释放,造成内存浪费,导致运行速度减慢甚至系统崩溃等。
如下四种情况时最容易造成内存泄露的场景,请大家开发时一定注意:
1.创建大量无用对象
比如:大量拼接字符串时,使用了 String 而不是 StringBuilder。
String str = "";
for (inti= 0;i< 10000;i++){
str +=i;
//相当于产生了 10000 个 String 对象
}
2.静态集合类的使用
像 HashMap、Vector、List 等的使用最容易出现内存泄露,这些静态变量的生命周期
和应用程序一致,所有的对象也不能被释放。
3.各种连接对象(10 流对象、数据库连接对象、网络连接对象)未关闭
I0 流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网络连接,不使用的时候一定要关闭。
4.监听器的使用不当
释放对象时,没有删除相应的监听器
其他要点
1.程序员无权调用垃圾回收器。
2.程序员可以调用 System.gc(),该方法只是通知 JVM,并不是运行垃圾回收器。尽量
少用,会申请启动 Full GC,成本高,影响系统性能。
3.Object 对象的 finalize 方法,是 Java 提供给程序员用来释放对象或资源的方法,但
是尽量少用。
本节作业
1.垃圾回收过程一般分为两步,是哪两步?
2.垃圾回收常见的两种算法是什么?
3.堆内存划分成:年轻代、年老代、永久代。垃圾回收器划分成:Minor Gc、Major Gc
Full GC。这三种垃圾收回器都对应哪些区域?
4. 对 JM 调优的过程中,很大一部分工作就是对于 Full GC的调节。这句话对吗?
5.System.gc()的作用是什么?
This的本质_对象创建过程的4步_隐式参数
this关键字
this的用法:
代码示例:
/**
* 测试This用法
* */
public class TestThis {
int a,b,c;
TestThis(){
System.out.println("正要初始化的对象"+this);
}
TestThis(int a, int b){
//TestThis(); //这样是无法调用构造方法的!
//this();//调用无参构造方法,并且必须位于第一行!
a =a;//这里都是指的局部变量而不是成员变量
//这样就区分了成员变量和局部变量,这种情况占了this使用情况大多数!
this.a=a;
this.b=b;
}
TestThis(int a,int b,int c){
this(a,b);//调用带参的构造方法,并且必须位于第一行!
this.c = c;
}
void sing(){
}
void eat(){
System.out.println("当前对象"+this);
sing();
System.out.println("吃饭了");
}
public static void main(String[] args) {
TestThis hi = new TestThis(2,3);
hi.eat();
}
}
-static本质_静态方法和静态变量_内存分析
static关键字
静态变量(类变量)、静态方法(类方法):static 声明的属性或方法。
代码示例:
/*
* 测试Static
*
* */
public class TestStatic {
int id; //id
String name; //账户名
static String company ="北京尚学堂"; //公司名称
public TestStatic(int id,String name){
this.id =id;
this.name =name;
}
public void login(){
System.out.println(name);
}
public static void printCompany(){
//login();//调用非静态成员,编译就会报错
System.out.println(company);
}
public static void main(String[] args) {
TestStatic u = new TestStatic(101,"高小七");
TestStatic.printCompany();
TestStatic.company ="北京阿里狼";
TestStatic.printCompany();
}
}
运行结果:
内存分析:
static本质_静态初始化块_继承树的追溯
静态初始化块
构造方法用于对象的普通属性初始化!
静态初始化块,用于类的初始化操作!初始化静态属性。
在静态初始化块中不能直接访问非 static 成员。
代码示例:
public class TestStatic2 {
static String company; //公司名称
static {
System.out.println("执行类的初始化工作");
company ="百战程序员";
printCompany();
}
public static void printCompany() {
System.out.println(company);
}
public static void main(String[] args) {
}
}
运行结果:
变量分类_局部变量_成员变量_静态变量的总结
变量的分类和作用域
package_包机制_JDK常见的包
包机制(package、import)
包(package)相当于文件夹对于文件的作用。用于管理类、用于解决类的重名问题。
package
package的使用有两个要点:
1.通常是类的第一句非注释性语句。
2.包名:域名倒着写即可,在加上模块名,便于内部管理类。
示例:package的命名演示
在IDEA项目中新建包
JDK中主要的包
import导入_静态导入
导入类import
如果要使用其他包的类,需使用 import,从而在本类中直接通过类名来调用,否则就
需要书写类的完整包名和类名。
代码示例:在另一个包下使用其他包中的类
1.在新建的com.zhishang.test包中新建TestImport类:
package com.itzhishang.test;
/*
* 测试导入import
* */
public class TestImport {
public static void main(String[] args) {
}
}
2.在新建的com.zhishang.test包中新建Car类:
package com.itzhisahng.oop;
public class Car {
}
现在需要在TestImport类中使用Car类
笨办法:
package com.itzhishang.test;
/*
* 测试导入import
* */
public class TestImport {
public static void main(String[] args) {
com.itzhisahng.oop.Car car = new com.itzhisahng.oop.Car();
//带上包名即可找到对应的类
String str;
java.lang.String str2;
}
}
可以看到实在是太长了
使用import导入:
package com.itzhishang.test;
/*
* 测试导入import
* */
import com.itzhisahng.oop.Car
public class TestImport {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
// com.itzhisahng.oop.Car car = new com.itzhisahng.oop.Car();
//
// //带上包名即可找到对应的类
// String str;
// java.lang.String str2;
}
}
省略很多。
静态导入
静态导入(static import):其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
静态导入示例:
package com.itzhishang.test;
//以下两种静态导入的方式二选一即可
import static java.lang.Math.*;
import static java.lang.Math.PI;
public class Test2 {
public static void main(String[] args) {
System.out.println(PI);
System.out.println(random());
}
}
运行结果:
面向对象的_继承_instanceof的使用
面向对象的三大特征
本章重点针对面向对象编程的三大特征:继承、封装、多态进行详细的讲解。不要期望,通过本章学习就“搞透面向对象编程”。本章只是面向对象编程的起点,后面所有的章节说白了都是对面向对象这一章的应用。
继承
继承是面向对象编程的三大特征之一。继承让我们更加容易实现类的扩展。实现代码的重用,不用再重新发明轮子(don't reinvent wheels)。
继承有两个主要作用:
1.代码复用,更加容易实现类的扩展
2.方便建模
继承的实现
从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展。现实世界中的继承无处不在。比如:
代码示例:使用extends实现继承
1.先声明Person student两个对象并初始化他们的属性和动作(方法):
package com.itzhisahng.oop;
public class TestExtend {
class Person{
String name;
int height;
public void rest(){
System.out.println("休息");
}
}
class Student{
String name;
int height;
String major; //专业
public void rest(){
System.out.println("休息");
}
public void study(){
System.out.println("学习");
}
}
}
这其中可以明显发现Student中的部分属性方法在Person中也存在,为了方便简略代码,我们便可以让Student继承Person,这样就不需要二次声明一些对象之间共有的方法。
2.重新修改对象Student
package com.itzhisahng.oop;
public class TestExtend {
public static void main(String[] args) {
Student s1 = new Student("zs", 165, "java");
}
}
class Person{
String name;
int height;
public void rest(){
System.out.println("休息");
}
}
class Student extends Person{
// String name;
// int height;
String major; //专业
// public void rest(){
// System.out.println("休息");
// }
public void study(){
System.out.println("学习");
rest();//直接继承了Person中的方法
System.out.println(this.name);//直接继承了Person中的方法
}
public Student(String name,int height,String major){
this.name =name;
this.height =height;
this.major = major;
}
}
此处可以看到在Student继承Person后可以流畅的对name,height等属性进行构造器以及其他操作,在继承Person的行为基础之上Student又扩展了学科专业以及'study'的方法,同理我们也可以利用继承在声明别的类,例如boy,(他也拥有姓名身高等属性),在继承的基础上再做出相关对象合理行为的扩展,此处不做展开。
小细节:
在代码中我们明确的写出了Student extend Person ,那么Person又继承了谁呢?
答案是当一个对象没有指定extend对象时系统会默认添加该对象继承了Object;
那么可以得出,该示例代码继承关系如下:
Instanceof 运算符
instanceof是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true;否则,返回false。比如:
代码示例:使用instanceof运算符进行类型判断
在主函数中添加这两段代码:
System.out.println(s1 instanceof Student);
System.out.println(s1 instanceof Person);
结果:
继承使用要点
1.父类也称作超类、基类。 子类:派生类等。
2. Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
3. Java中类没有多继承,接口有多继承。
4.子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
5. 如果定义一个类时,没有调用extends,则它的父类是:iava.lana.Obiect.
方法的重写override
子类重写父类的方法,可以用自身行为替换父类行为。重写是实现多态的必要条件。方法重写需要符合下面的
三个要点:
1.“= =” :方法名、形参列表相同。
2.“≤”:返回值类型和声明异常类型,子类小于等于父类。
3.“2”:访问权限,子类大于等于父类。
代码示例:
声明了一个父类:交通工具类Vehicle他有两个方法:run(跑)和getVehicle(获得交通工具)
在声明两个子类:horse(马)Plane(飞机):
package com.itzhisahng.oop;
public class TestOverriide {
public static void main(String[] args) {
}
}
class Vehicle {
public void run(){
System.out.println("run");
}
public Vehicle getVehicle(){
return null;
}
}
class horse extends Vehicle{ //马也是交通工具
}
class Plane extends Vehicle{
}
在主函数中声明一个‘马’的对象,并执行父类动作:
Horse s1 = new Horse();
s1.run();
s1.getVehicle();
}
结果:
可以看到天然的执行了父类方法,但是现在需要马跑起来要有马的样子,不向让他只是平淡的执行父类动作,要跑出自己的风格,这就设计方法的重写;
直接在子类horse中重写run方法:
class Horse extends Vehicle{ //马也是交通工具
@Override
public void run() {
super.run();
}
}
注意此处的@Override是系统自动添加的注解,在代码中可有可无并不影响程序运行,只是说明此处的原方法被覆盖;
@Override
public void run() {
System.out.println("嘶吼——————");
System.out.println("郭德纲郭德纲郭德纲于————————谦!");
}
结果:
可以看到这是马跑的样子。子类的新方法将父类的覆盖这就是重写的含义。
代码示例:
在完整代码:
package com.itzhisahng.oop;
public class TestOverriide {
public static void main(String[] args) {
Horse s1 = new Horse();
s1.run();
s1.getVehicle();
}
}
class Vehicle {
public void run(){
System.out.println("run");
}
public Vehicle getVehicle(){
System.out.println("获得一个交通工具");
return null;
}
}
class Horse extends Vehicle{ //马也是交通工具
@Override
public void run() {
System.out.println("嘶吼——————");
System.out.println("郭德纲郭德纲郭德纲于————————谦!");
}
}
class Plane extends Vehicle{
}
中,在子类horse中重写getVehice方法;
@Override
public Horse getVehicle() {
return new Horse();
}
由上可知返回值类型由Vehicle变为了horse
horse是Vehicle的子类所以代码没有报错可以正常运行,
但是反过来就不行
可以看到报错。
final关键字
final关键字的作用:
修饰变量:被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
final int MAX_SPEED = 120;
修饰方法:该方法不可被子类重写。但是可以被重载!final void study()0
修饰类:修饰的类不能被继承。比如: Math、String等。
final class A
在上面的代码中如若是在父类Vehicle的run方法中用Final修饰则子类无法重写run方法:
若将整个类用final修饰,则不能被继承:
组合_继承相关知识点复习
除了继承,“组合”也能实现代码的复用!“组合”核心是“将父类对象作为子类的属性”。
代码示例:
用继承章节的代码并且取消Student对Person的继承
package com.itzhisahng.oop;
public class TestComponet {
public static void main(String[] args) {
}
}
class Person2{
String name;
int height;
public void rest(){
System.out.println("休息");
}
}
class Student2/* extends Person2*/ {
// String name;
// int height;
String major; //专业
// public void rest(){
// System.out.println("休息");
// }
public void study() {
System.out.println("学习");
rest();//直接继承了Person中的方法
System.out.println(this.name);//直接继承了Person中的方法
}
public Student2(String name, int height, String major) {
this.name = name;
this.height = height;
this.major = major;
}
}
用组合的方法在Student中调用Person中的方法在Student类中写下关键代码:
Person2 person2 = new Person2();
Student声明父类对象将整个Person当做自己的属性:
此时原利用继承调用的方法全部报错:
既然原父类对象变为自己的属性只需利用student类中新声明的Person对象成员调用即可。
程序正常,这就是组合复用的方式。
组合比较灵活。继承只能有一个父类,但是组合可以有多个属性。所以,有人声称“组合优于继承,开发中可以不用继承”,但是,不建议大家走极端。
注意:灵活运用组合与继承来复用代码
本节练习:
Object类_toString方法_IDEA常用快捷键
Object类基本特性
1.Object类是所有类的父类,所有的Java对象都拥有Object类的属性和方法。
2.如果在类的声明中未使用extends,则默认继承Object类。
代码示例:Object
1.按住Ctrl+鼠标左键即可查看Object代码中的内容
package com.itzhisahng.oop;
/*
* 测试Object类
* */
public class TestObject extends Object {
public static void main(String[] args) {
TestObject t = new TestObject();
System.out.println(t.toString());
TestObject t2 = new TestObject();
System.out.println(t2); //默认调用Object类中的toString方法
}
}
运行结果:
package com.itzhisahng.oop;
/*
* 测试Object类
* */
public class TestObject extends Object {
String name;
String pwd;
@Override
public String toString() {
return "账户名"+name+"密码"+pwd;
}
public static void main(String[] args) {
TestObject t = new TestObject();
System.out.println(t.toString());
TestObject t2 = new TestObject();
System.out.println(t2); //默认调用Object类中的toString方法
//重写toString方法
}
}
Alt+7左侧出现类结构
IDEA快捷键:
equals方法的重写
== 和equals方法
“==”代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类内容相等”的逻辑。比如,我们在型则表示地址相等即是同一个对象。
equals()提供定义“对象公安系统中认为id相同的人就是同一个人、学籍系统中认为学号相同的人就是同一个人。
equals()默认是比较两个对象的hashcode。但,可以根据自己的要求重写equals方法。
示例:自定义类重写equals()方法
1.在TestObject中
,Ctrl+鼠标左键单击Object类,再Alt+7找到equals方法:
package com.itzhisahng.oop;
import java.sql.SQLOutput;
/*
* 测试Object类
* */
public class TestObject extends Object {
int id;
String name;
String pwd;
public TestObject(int id,String name, String pwd){
this.name =name;
this.pwd = pwd;
this.id =id;
}
// @Override
// public String toString() {
// return "账户名"+name+"密码"+pwd;
// }
public static void main(String[] args) {
TestObject t3 = new TestObject(1001,"zhangsan","123456");
TestObject t4 = new TestObject(1001,"lis","654321");
System.out.println(t3.toString());
System.out.println(t4.toString());
System.out.println(t3.equals(t4));
}
}
运行结果:
运行结果说明equals比较的是两对象的地址,现在我需要让他判断id相同就是一样的,这就需要重写equals方法:
这里可以手写,也可以利用IDEA快速生成:
1.鼠标右键代码页面选择生成....
2.点击对应的想要重写的方法:
在这个页面中可以勾选对哪个值进行比较:这里我们选择用id比较即可:
系统自动为我们生成了代码:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestObject that = (TestObject) o;
return id == that.id;
}
此时再次点击运行:
可以看到返回值为ture。
完整代码如下:
package com.itzhisahng.oop;
import java.util.Objects;
/*
* 测试Object类
* 测试重写toString()
* 测试重写equals()
* */
public class TestObject extends Object {
int id;
String name;
String pwd;
public TestObject(int id,String name, String pwd){
this.name =name;
this.pwd = pwd;
this.id =id;
}
// @Override
// public String toString() {
// return "账户名"+name+"密码"+pwd;
// }
public static void main(String[] args) {
TestObject t3 = new TestObject(1001,"zhangsan","123456");
TestObject t4 = new TestObject(1001,"zhangsan","123456");
System.out.println(t3.toString());
System.out.println(t4.toString());
System.out.println(t3.equals(t4));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestObject that = (TestObject) o;
return id == that.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Super关键字_子类对象内存结构_继承树追溯
1. super“可以看做”是直接父类对象的引用。可通过super来访问父类中被子类覆盖的方法或属性。
2.使用super调用普通方法,语句没有位置限制,可以在子类中随便调用。
3.在一个类中,若是构造方法的第一行没有调用super...)或者this(...);那么Java默认都会调用super(),含义是调用父类的无参数构造方法。
代码示例:
package com.itzhisahng.oop;
//测试Super
public class TestSuper01 {
public static void main(String[] args) {
new ChildClass().f();
}
}
class FatherClass{
public int value;
public void f(){
value = 100;
System.out.println("FatherClass.value="+value);
}
}
class ChildClass extends FatherClass{
public int value;
public int age;
public void f(){
super.f(); //调用父类的普通方法
value = 200;
System.out.println("ChildClass.value="+value);
System.out.println(value);
System.out.println(super.value);//调用父类的成员变量
}
public void f2(){
System.out.println(age);
}
}
运行结果:
可以看到在子类中调用value时调用的是自己的值:200,在用了super之后,直接越过覆盖调用了父类的value值100.
继承树的追溯:
代码示例:
package com.itzhisahng.oop;
public class TestSper02 {
public static void main(String[] args) {
new ChildClass2();
}
}
class FatherClass2{
public FatherClass2(){
System.out.println("创建FatherClass2");
}
}
class ChildClass2 extends FatherClass2{
public ChildClass2(){
super();//在一个类中,若是构造方法的第一行没有调用super...)或者this(...);那么Java默认都会调用super(),含义是调用父类的无参数构造方法。
System.out.println("创建ChildClass2");
}
}
在主函数中我们先调用了ChildClass2创建对象;
运行结果:
结果说明我们要创建一个子类对象的话就需要先创建他的父亲;
封装详解_四个访问控制符_protected的两个细节
封装(encapsulation)
封装是面向对象的三大特征之一。
复杂的内容封装隐藏起来不让他人了解,但是调用的时候很简单。
封装的含义与作用:
我要看电视,只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口。
我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
编程中封装的具体优点:
封装的实现--使用访问控制符
Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。Java中4种“访问控制符”分别为private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。
代码示例:
1.在项目中创建一个encapasulation包,并且在这个包中创建a包和b包;在a包中写一个Person类和Student类,在b包中写一个boy类。
2.在Person类中书写如下代码:
package com.encapasulation.a;
public class Person {
private int testPrivate;
int testDefault;
protected int testProtected;
public int testpublic;
public void test(){
System.out.println(this.testProtected);
}
}
在代码中我们写了四种权限修饰符语句,可以看到在Person类中这些都可以调用;
3.在同一包内Student类中我们书写如下代码:
package com.encapasulation.a;
public class Student {
public void study(){
Person p = new Person();
//System.out.println(p.testPrivate);报错
System.out.println(p.testProtected);
System.out.println(p.testDefault);
System.out.println(p.testpublic);
}
}
在这里我们用到了组合的方法在Student中调用了Person的属性,可以看到关键词修饰符:Private(私有)无法访问,而Protected,Default,Pubic可以访问。
4.在b包中的Boy类书写如下语句:
package com.encapasulation.b;
import com.encapasulation.a.Person;
public class Boy extends Person{
public void play(){
System.out.println(super.testProtected);
System.out.println(super.testpublic);
Person p = new Person();
System.out.println(p.testProtected);
}
}
这其中可以看到Boy是Person的子类,天然的,Protected(包内友好,父子友好)和public可以访问但是当我们利用组合访问时缺不能访问Protected,这里需要注意:
在上述Student和Boy两个例子当中,都作为Person的子类,区别是前者在同一个保重,后者不再同一个包中,而我们都newle一个父类对象 p ,利用p访问父类的属性,在同一包中Student可以用p.父类对象的Protected成员,而不再同一包中的Boy则不能;
封装详解_javabean的概念_get和set方法
封装的使用细节
代码示例:
在b包中创建User类,将属性全部用私有修饰符声明,利用快捷生成get,set方法:
package com.encapasulation.b;
public class User {
private int id;
private String name;
private boolean man;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMan() {
return man;
}
public void setMan(boolean man) {
this.man = man;
}
}
方法全是公开的属性全是私有的,可以看到boolean型中是isMan;
这就是一个javabean(简单类);接下来我们在b包中创建test类来运行这个类:
package com.encapasulation.b;
public class Test {
public static void main(String[] args) {
User u = new User();
u.setId(100);
u.setMan(true);
u.setName("张三");
System.out.println(u.getName());
System.out.println(u.getId());
System.out.println(u.isMan());
}
}
运行结果:
这就是get,set方法的简单运用,当然我们也可以用之前学的构造器来运行这个程序。
修改代码:在User类中快捷生成构造器:
相等的Test类中便不能调用默认的无参构造器:
运行结果:
我们把一开始的简单类具有简单的属性,用简单的getset方法称为javabean;
多态详解:
多态(polymorphism):
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。比如:同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭。
多态的要点:
1.多态是方法的多态,不是属性的多态(多态与属性无关)。
2.多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。
3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
代码示例:
1.在项目中新建一个polymorphism包来测试多态,并在其中创建Animal类编写如下代码:
package com.polymorphism;
public class Animal {
public void shout(){
System.out.println("叫了一声");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("wolf!wolf!wolf!");
}
public void seeDoor(){
System.out.println("看门中...");
}
}
class Cat extends Animal{
public void shout(){
System.out.println("miao miao miao ?");
}
}
现在在多态的三个必要条件当中我们有了继承,方法重写,接下来还需要父类引子类对象。
在包中新建类TestPolym:测试多态
package com.polymorphism;
//测试多态
public class TestPolym {
public static void main(String[] args) {
animalCry(new Dog());
animalCry(new Cat());
}
static void animalCry(Animal a){ //此处为父类引用子类对象
System.out.println("TestPolym.animalCry");
a.shout(); //可以出现多态
}
}
此处(Animal a)是一个行参,第一次将Dog作为引用对象,第二次为Cat,由此就完成了我们的多态。
运行结果:
我们也可以用笨办法达成同样目的:
但是这样很麻烦,后续每新增一个子类我们就要再次声明一个静态方法animalCry耦合性高,我们封装讲究高内聚,低耦合,所以不采用,这只作为反面教材。
不要忽略,在Dog类中,还有一个seeDoor方法,我们在TestPolym中修改一些新内容:
可以看到作为父类声明的子类对象Dog居然无法调用seeDoor方法,原因是编译器不让调用。
这涉及到对象的转型问题,下节课将详细说明这个问题。
对象的转型_向上转型和向下转型(casting)
1.父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
2.向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法这时,我们就需要进行类型的强制转换,我们称之为向下转型。
代码示例:
package com.polymorphism;
//测试多态
public class TestPolym {
public static void main(String[] args) {
// animalCry(new Dog());
// animalCry(new Cat());
//编译类型Animal 运行时类型dog
Animal animal = new Dog(); //向上转型(自动)
animal.shout();
Dog d = (Dog)animal; //向下转型(强制)
d.seeDoor(); //此时看门方法可以运行,注意这里d并不是一个新对象,它还是原来animal带了个帽子而已
//编译不会报错,运行时会报异常:ClassCastException
Cat c =(Cat) animal;
if (animal instanceof Cat){
Cat c = (Cat) animal;
c.catchMouse();
}
}
static void animalCry(Animal a){ //此处为父类引用子类对象
System.out.println("TestPolym.animalCry");
a.shout(); //可以出现多态
}
}
运行结果:
抽象方法_抽象类
抽象方法和抽象类
抽象方法:
1.使用abstract修饰的方法,没有方法体,只有声明。
2.定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类:
包含抽象方法的类就是抽象类。
通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
代码示例:
1.建立新的abstractClass包在其中新建抽象类Animal,并声明抽象方法;
package com.itzhishang.abstractClass;
//抽象类
public abstract class Animal {
int age;
public abstract void rest(); //抽象方法
public abstract void run(); //抽象方法
//子类必须为抽象类方法提供具体实现。
public void shout(){
System.out.println("Animal.shout");
}
}
2.创建子类Dog可以发现在继承时编译报错:
在这里我们可以快捷生成实现方法:
再以同样的方法创建子类Cat:
一看到父类定义了一个规范,要求其他继承子类必须实现,否则不予编译通过,便于对子类做出通用定义,这就是抽象类的定义。
抽象类的使用要点:
1.有抽象方法的类只能定义成抽象类
2.抽象类不能实例化,即不能用new来实例化抽象类。
3.抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,
只能用来被子类调用。
4.抽象类只能用来被继承。
5.抽象方法必须被子类实现。
接口的定义和实现
接口interface
接口就是一组规范((就像我们人间的法律一样),所有实现类都要遵守。
面向对象的精髓,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如C++、Java、C#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。
接口的作用
为什么需要接口?接口和抽象类得到的区别?
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。
接口和实现类不是父子关系,是实现规则的关系。比如:我定义一个接口Runnable,Car实现它就能在地上跑,Train 实现它也能在地上跑,飞机实现它也能在地上跑。就是说,如果它是交通工具,就一定能跑,但是一定要实现 Runnable接口。
如何定义和使用接口
声明格式:
定义接口的详细说明:
要点:
代码示例:
1.新建testInterface包,定义接口Volant,声明飞行接口和善良接口
packagpackage com.testInterface;
//飞行接口
public interface Volant {
/*总是用public static final 修饰,不写也是*/ int Fly_HIGHT = 100; //常量
/*总是用 public abstract 修饰,不写也是*/ void fly(); //方法
}
//善良接口
interface Honest{
void helpOther();
}
//子类通过implements来实现接口中的规范。
class GoodMan implements Honest{
@Override
public void helpOther() {
System.out.println("背老太太过马路");
}
}
class BirdMan implements Volant{
@Override
public void fly() {
System.out.println("飞");
}
}
class Angel implements Volant,Honest{
@Override
public void fly() {
System.out.println("天使飞行");
}
@Override
public void helpOther() {
System.out.println("别怕孩子,我来了");
}
}
class plane implements Volant{
@Override
public void fly() {
System.out.println("飞机飞");
}
}
从中可以看到不同于以往的例子Dog,Cat继承Animal这种物种特征,接口类更清晰的把飞行和善良这两种行为都抽象出来,这样即使是例子中人与飞机以及其他这种传统继承方面逻辑上毫无关联的类,也都能通过共同实现接口完成飞行的动作。
书写测试类:
package com.testInterface;
public class test {
public static void main(String[] args) {
Angel a = new Angel();
a.fly();
a.helpOther();
System.out.println(Volant.Fly_HIGHT);
//此处又涉及到上一节的类型转换问题
Volant a2 = new Angel(); //这里相当于父类对子类的引用
a2.fly();
a2.helpOther();
}
}
这其中:
若要调用helpOther方法,就需要强制类型转换向下转型。
运行结果:
接口的新特性_默认方法
1.默认方法
Java8及以上新版本,允许给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做默认方法(也称为扩展方法)。
默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都可以得到默认方法。
代码示例:
1.在testInterface包中创建接口:TestDefault书写如下代码:
package com.testInterface;
public interface TestDefault {
void printIbfo();
default void moren (){
System.out.println("TestDefault.moren");
System.out.println("测试默认方法");
}
}
class TestDefaultImpl01 implements TestDefault{
@Override
public void printIbfo() {
//完成了父接口给定的方法
}
//而扩展方法可以自由定义,无需一定要实现。
}
2.在test中测试扩展方法:
System.out.println("=========测试默认方法==========");
TestDefault td =new TestDefaultImpl01();
td.printIbfo();
td.moren();//默认方法
运行结果:
可以看到default修饰的moren方法在类中是默认实现的可以直接调用,除此之外也可以根据自己需要对moren进行重写。
接口新特性_静态方法
JAVA8以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。
如果子类中定义了相同名字的静态方法,那就是完全不同的方法了(不是重写),直接从属于子类可以通过子类名直接调用。
代码示例:
在接口TestDefault中声明静态方法:
public static void testStatic01(){
System.out.println("TestDefault.testStatic01");
}
在test类中测试:
直接从接口名调用即可:
System.out.println("==========测试默认方法==========");
TestDefault.testStatic01();//直接从接口名处调用
TestDefaultImpl01.testStatic01();//从属于类的新方法调用
也可以在继承接口的类中定义相同的静态方法,但是这里的方法不是父类接口中静态方法的重写,而是一个新的方法。
运行结果:
小拓展:
最后在这两个新特性中,默认方法可以调用静态方法,而反之则不行:
接口的多继承
代码示例:
1.在testInterface包中创建公开类TestMultipleInheritance书写如下代码:
package com.testInterface;
//测试接口多继承
public class TestMultipleInheritance {
public static void main(String[] args) {
}
}
interface A{
void testA();
}
interface B{
void testB();
}
interface C extends A,B{
void testC();
}
可以看到声明了三个接口A、B、C其中C继承了A、B
2.我们在声明一个类让他引入接口C
可以看到若是想建立这个类则需要同时实现接口C以及它的父类接口A.B的方法
package com.testInterface;
//测试接口多继承
public class TestMultipleInheritance {
public static void main(String[] args) {
C c = new CImpl01();
c.testA();
c.testB();
c.testC();
}
}
interface A{
void testA();
}
interface B{
void testB();
}
interface C extends A,B{
void testC();
}
class CImpl01 implements C{
@Override
public void testA() {
System.out.println("CImpl01.testA");
}
@Override
public void testB() {
System.out.println("CImpl01.testB");
}
@Override
public void testC() {
System.out.println("CImpl01.testC");
}
}
运行结果:
这就是接口的多继承。类只能单继承
String类的本质_JDK源码解读
字符串 String类详解
String是最常用的类,要掌握String类常见的方法,它底层实现也需要掌握好,不然在工作开发中很容易犯错。
String类又称作不可变字符序列。
String 位于java.lang包中,Java程序默认导入java.lang包下的所有类。
Java字符串就是Unicode字符序列,例如字符串“Java”就是4个Unicode字符’J.' a' . ' v' .' a’组成的。
Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是String类的一个实例。
代码示例:
package com.itzhishang;
//测试字符串
public class TestString {
public static void main(String[] args) {
String s0 = null; //完全为空,什么都不是
String s1 = ""; //空字符串
String s2 = "java";
String s3 = new String("java");
System.out.println(s1.length());
System.out.println(s2.length());
}
}
运行结果:
按住ctrl+鼠标左键点击String进入源码查看:
字符串相等的判断_常量池原理
String类和常量池
Java内存分析中,我们会经常听到关于“常量池”的描述,实际上常量池也分了以下三种:全局字符串常量池、class文件常量池、运行时常量池(Runtime Constant Pool)。
我们只关注运行时常量池即可。
(看视频38期理解)
string类常用方法详解_api文档如何阅读
如何下载API文档
1.下载地址,点击进入:
https://www.oracle.com/java/technologies/javase-jdk8-doc-downloads.html
查看API文档
下载成功后,解压下载的压缩文件,点击进入docs/api下的index.html文件即可。
String类常用的方法
String类是我们最常使用的类。列出常用的方法,请大家熟悉。
代码示例01:
package com.itzhishang.test;
public class StringTest1 {
public static void main(String[] args) {
String s1 = "core Java";
String s2 = "Core Java";
System.out.println(s1.charAt(3));//提取下标为3的字符
System.out.println(s2.length());//字符串的长度
System.out.println(s1.equals(s2));//比较两个字符串是否相等
System.out.println(s1.equalsIgnoreCase(s2));//比较两个字符串(忽略大小写)
System.out.println(s1.indexOf("Java"));//字符串s1中是否包含Java
String s3 = "I love java java is best language!";
System.out.println(s3.indexOf("java"));
System.out.println(s3.lastIndexOf("java"));//从后往前找
System.out.println(s1.indexOf("apple"));//字符串s1中是否包含apple
String s = s1.replace(' ','&');//将s1中的空格替换成&
System.out.println("result is:" +s);
}
}
运行结果:
代码示例02:
package com.itzhishang.test;
public class StringTest02 {
public static void main(String[] args) {
String s = "";
String s1 = "How are you?";
System.out.println(s1.startsWith("How"));//是否以How开头
System.out.println(s1.endsWith("you"));//是否以you结尾
s = s1.substring(4);//提取字符串:从下标为4的开始到字符串结尾为止
System.out.println(s);
s = s1.substring(4,7);//提取字符串:下标(4,7)不包括7
System.out.println(s);
s = s1.toLowerCase();//转小写
System.out.println(s);
s = s1.toUpperCase();//转大写
System.out.println(s);
String s2 = " How old are you!! ";
s = s2.trim();//去除字符串首尾的空格。注意:中间的空格不能去除
System.out.println(s);
System.out.println(s2);//因为String是不可变字符串,所以s2不变
}
}
运行结果:
String字符串是不可变的任何看似变换的字符串实际上是返回了新的字符串
内部类的基本概念和用法
内部类(Innerclass):
内部类的两个要点:
内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。但外部类不能访问内部类的内部属性。
代码示例:
在主包下创建新包innerClass创建名为Outer的外部类
package com.itzhishang.innerClass;
public class Outer {
private int age=10;
public void show(){
System.out.println("Outer.show");
System.out.println(age);
}
public class Inner{
int age =20;
public void show(){
System.out.println("Inner.show");
System.out.println(age);
}
}
}
内部类的分类
非静态内部类:
非静态内部类(外部类里使用非静态内部类和平时使用其他类没什么不同)
1.非静态内部类对象必须寄存在一个外部类对象里。因此,如果有一个非静态内部类
对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
2.非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类
成员。
3.非静态内部类不能有静态方法、静态属性和静态初始化块。
4.成员变量访问要点:
1.内部类属性: this.变量名。
2.外部类属性:外部类名.this.变量名。
代码示例:
System.out.println(Outer.this.age);
Outer.this.show();
内部类的访问:
1.外部类中定义内部类:new Ilnner()。
2.外部类以外的地方使用非静态内部类:
Outer.Inner varname = new Outer().new Inner().
代码示例:
1.在包中创建新类TestInner:
package com.itzhishang.innerClass;
public class TestInner {
public static void main(String[] args) {
Outer.Inner inner = new Outer().new Inner();
inner.show();
}
}
运行结果:
非静态内部类_静态内部类_匿名内部类_局部内部类
静态内部类
定义方式:
使用要点:
1.静态内部类可以访问外部类的静态成员,不能访问外部类的普通成员。
2.静态内部类看做外部类的一个静态成员。
代码示例(静态内部类的访问):
创建TestStaticInnerClass类:
package com.itzhishang.innerClass;
//测试内部静态类
class Outer2{
private int a =10;
private static int b =20;
//相当于外部类的一个静态成员
static class Inner{
public void test(){
// System.out.println(a); //静态内部类不能访问外部类的普通属性
System.out.println(b); //静态内部类可以访问外部类的静态属性
}
}
}
public class TestStaticInnerClass {
public static void main(String[] args) {
//通过 new 外部类名.内部类名() 来创建内部类对象
Outer2.Inner inner = new Outer2.Inner();
inner.test();
}
}
运行结果:
匿名内部类
适合那种只需要使用一次的类。比如:键盘监听操作等等。在安卓开发、awt、swing开发中常见。
语法:
代码示例(匿名内部类的使用):
1.在包中创建TestAnonymousInnerClass类:
package com.itzhishang.innerClass;
//测试匿名内部类
public class TestAnonymousInnerClass {
public void test1(A a) {
a.run();
}
public static void main(String[] args) {
}
interface A {
void run();
}
}
此时若我们想要调用接口A的方法一般情况就需要写一个实现接口a方法的类,
但是我们也可以用内部匿名类来解决这个问题:
局部内部类
定义在方法内部的,作用域只限于本方法,称为局部内部类。
局部内部类在实际开发中应用很少。
代码示例(方法中的内部类):
1.在包中创建TestLocalInnerClass类
package com.itzhishang.innerClass;
public class TestLocalInnerClass {
public void show(){
//作用域仅限于该方法
class Inner3{
public void fun(){
System.out.println("helloworld");
}
}
new Inner3().fun();
}
public static void main(String[] args) {
}
}
面向对象只是大总结(绘制思维导图)