Java基础知识二次学习--第三章 面向对象

 

第三章 面向对象
 
时间:2017年4月24日17:51:37~2017年4月25日13:52:34
章节:03章_01节 03章_02节
视频长度:30:11 + 21:44
内容:面向对象设计思想 
心得:
与以往的各种语言的根本不同的是,它的设计出发点就是为了更能直接的描述问题域中客观存在的事务
 
一个面向过程的设计思想和面向对象的设计思想的不同的例子
例如:我要去新疆
面向过程:我开车,挂挡,踩油门,到河北.......
面向对象:
我命令去新疆,车怎么去不关我事(
信息封装在车的类的内部
我不用去了解车开动的过程
关于这2个思想的的不同,有时间要仔细看,这里做个标记
是在复用的层次上有区别
面向过程是在方法层次上的区别
面向对象是隐藏了方法的过程与细节,直接在对象层次上的重复使用
 
设计思维:合适的方法应该出现在合适的类里面
 
思考问题 面向对象的思维
如果碰到一个问题
首先将对象抽象出来 思考 一个问题里有哪些类 哪些对象
再考虑 类和对象具有哪些属性与方法
最后再考虑这些类和类之间具备了什么关系
--这是面向对象的思考方式
而第一步做什么 第二部做什么 ... --这是面向过程的思考方式
面向对象更强调运用人类在日常的思维逻辑中经常采用的思想方法与原则,如抽象,分类,聚合,多态等
 
关于类和对象
类实用于描述同一类的对象的一个抽象的概念
类可以看成一类对象的模板,对象可以看成该类的一个具体实例,对象通过属性和方法来分别对应事物所具有的静态属性和动态属性
 
对象之间的关系
关联关系,是一种最弱的关系
继承关系, XX是一种XX,所以说 继承可以简单的说 “是什么”
现实情况中,一个类完全可能存在继承多个类,所以C++中存在多重继承
但是实际处理起来十分麻烦(例如2个父类有相同名的成员变量)
聚合关系,说简单点就是XX是XX的一部分(整体和部分 ),细分的话还可以分成聚集和组合的关系
例:球队--队长,队员 --聚集 ,一个队长可以同时是2个不同球队的队长
    人  -- 头,身体,肩膀 ,但是一个头不可能同时是是2个不同人的头(一般情况下,连体人除外)
实现关系,子类实现父类
多态
 
课堂练习

 

谈谈我的思考过程:
我一般习惯会从客户开始(也就是使用者的角度开始)去理解问题
但是仔细想想这似乎是一种面向过程的思考方式?....
旅客预订机票--旅客付钱预订成功--旅行社将机票给旅客--旅客 拿机票go
所以一定有一个 旅客类,机票类,旅行社类
对于旅行社
旅行社 收到预订机票的订单--查询航班目录--生成记账文件 记账,帮客户订机票--票给客户
除去上面的几个类,在旅行社这里还多了 航班目录类,记账文件类
 
所以一共是 旅客类,机票类,旅行社类,航班目录类,记账文件类
但是我觉得 好像又是 旅客自己并没有订票这种功能 他只是调用了旅行社的一个订票的方法,给钱,返回票,其他的都在内部实现
所以订票应该是旅行社的一个方法比较好
 
订票的伪方法
//伪方法代码
public 票 我要订票( 旅客 旅客的实例化L,钱 钱的实例化m){
....//旅行社与航空公司的种种操作,中间会用到旅客L的相关属性,以及查询航班目录
以上实现里面有个
最终获得到一个 机票的实例化对象 t
return t;
}
 
旅客预订机票--旅客付钱预订成功--旅行社将机票给旅客--旅客 拿机票go
所以 第一步和第二步通过直接调用这个 订票(旅客,钱)的方法
传入旅客和钱就好了
 
但是又仔细想了一下 这个钱应该是旅客的属性吧? 毕竟每个旅客都有自己的钱
 
所以应该变成 订票(旅客) 返回值为票
里面调用 旅客.钱这个属性来来付钱 ,再来一些判断,如果钱不够就怎么样怎么样处理...
 
所以如果是main方法
这里就问题简单化一点 指明了某家旅行社 否则的话 应该旅行社是一个抽象类
然后张三旅行社 李四旅行社继承这个抽象类?
 
 
public static void main(String args[]){
旅行社 l = new 旅行社;
旅客 ll=new 旅客(通过构造函数或者其他方法生成一个独特的实例化旅客);
票 p= l.订票(旅客);
//其实写到这里 我觉得无论如何都要有个航空公司的类啊,里面有个给票就上飞机的方法,虽然题目中没写
航空公司 H=new 航空公司(和旅行社问题一样,抽象类的问题);
H.给票我就让你上飞机(旅客,票);
}
 
旅客这部分结束了,我觉得这样作为一个旅客的操作应该还算比较简单
实际的操作只有 票 p= l.订票(旅客); H.给票我就让你上飞机(旅客,票);
就可以上飞机了
 
那么订票方法里面的细节就是旅行社要考虑的了 
如果说涉及到航空公司 那么之前的航班目录这个类 应该是航空公司里的一个属性,因为每个航空公司都有自己的航班目录
 
然后刚才思考到了订票钱的问题,本来想放在方法里面的,后来又觉得要放在方法外面
因此
 
 
public static void main(String args[]){
旅行社 l = new 旅行社;
旅客 ll=new 旅客(通过构造函数或者其他方法生成一个独特的实例化旅客);
航空公司 H=new 航空公司(和旅行社问题一样,抽象类的问题);
if(ll.钱 够){
票 p= l.订票(旅客);
H.给票我就让你上飞机(旅客,票);
}else{
System.out.println("钱不够怎么订票");
}
 
}
 
//伪方法代码
public 票 订票( 旅客 旅客的实例化L){
....//旅行社与航空公司的种种操作,中间会用到旅客L的相关属性,以及查询航班目录
以上实现里面有个
最终获得到一个 机票的实例化对象 t
return t;
}

 

 
想的似乎太复杂了
简单的抽象出来的话
2个类 2个实体,3个方法
旅行社类 旅客类 航班目录和记账文件是实体 
黄色的 预订机票 准备机票 和记账 是旅行社里的3个方法
 
总结:对于不同的问题,有不同的思考方法
比如说像设计一个系统,得要从宏观的角度 去看
视频里说的一句话很对 做鸡窝有做鸡窝的方法 造高楼大厦有造高楼大厦的方法
不可能说拿到瓦匠刀 就说我要去造人民大会堂 
我感觉我刚才的思考就不够抽象,还是太细节化问题了。
 
java与面向对象
对象是java程序的核心,在java程序中“万事万物皆对象”
对象可以看成是静态属性(成员变量)和动态属性(方法)的封装体
类实用来创建同一类型的对象的“模板”,在一个类中定义了该类对象所应具有的成员变量和方法
JDK提供了很多类共变成人员使用,变成人员也可以定义自己的类
在java中 成员变量=属性 方法=函数
 
有一个问题 为什么用对象?
 
面向对象更加容易使我们达到这些年苦苦追求的境界
1.Reusable(可重用性),如果是面向过程,属性与方法是分开的,并不是聚合的关系,复用的层次比较低,复用层次只是在方法层次的复用。
面向对象则是将属性和方法综合到一起,复用的时候整个对象一起复用,相比于面向过程,更容易让开发者完成复用。
2.Extensibility(可扩展性)
3.维护
4.替换
 
面向组件--比对象更高层次上的抽象(二进制级别)
(二进制级别的抽象有个好处,可以跨语言访问)
面向对象,一个汽车里引擎的每个零件都是对象
面向组件,里面的每个零件组合成一个叫引擎的组件,更高一级别的抽象
 
 
时间:2017年4月25日13:52:55 ~2017年4月25日14:37:58
章节:03章_03节 03章_04节
视频长度:22:11 17:59
内容:java中的面向对象与内存解析
心得 :
成员变量可以使用java里的任何一种数据类型(包括基本数据类型和引用类型)
 
定义成员变量时可以对其初始化,也可以不对其初始化,这时候java会默认给他一个初始化的值,而局部变量则不行(变量的基本 先声明 然后赋值 再使用)

 

大致都是0,false,null,'\u0000' 
 
成员变量的作用范围为整个类体
 
关于引用
java语言中除了8种基本数据类型之外的变量类型都叫做 引用类型
基本数据类型只占一块内存,在栈里
比如说int i=5 开辟一块内存 名称叫做i 值为5
引用数据类型 占2块内存
 
例如 Dog d = new Dog();
d栈中保存的是引用    ------->堆内存中的那个new出来的dog对象
虽然说java中没有指针,但是可以将这种引用类型的模式理解为"指针"
要访问到这个new出来的dog,要通过访问栈里的d 然后d指向堆里的dog对象
 
  根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。  
                                                                                                                       
对象是new出来的,位于堆内存,类的每个成员变量在不同的对象中有不同的值(除了静态变量),而 方法只有一份,并且在执行的时候才占用内存
 
堆内存是用来动态分配内存的,只有在运行期间才能分配(不是编译期间)
如果不用了,GC会进行回收
 
一点总结 
同一个类的每个对象有不同的成员变量存储空间 -- 对象在堆内存里
同一个类的每个对象共享该类的方法 --所以说方法是在方法区里的
因为 Method Area(方法区) 和堆(Heap)是线程共享的,这里引用一下我之前的一段整理:
 

大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区) ,Heap(堆) , Program Counter Register(程序计数器) ,   VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack  ( 本地方法栈 ),其中Method Area 和 Heap 是线程共享的  ,VM Stack,Native Method Stack  和Program Counter Register  是非线程共享的。为什么分为 线程共享和非线程共享的呢?请继续往下看。

首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?

概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) ,   VM Stack(虚拟机栈)和Native Method Stack  (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因, 非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因
 
从内存角度来理解构造方法
     比如说,有这么一个person类
 
 public class Person(){
     int id;
     int age=20;
   Person(int _id, int _age){
     id=_id;
     age=_age;
}
}
 
Person tom = new Person(1,25);

 

 
2个参数会在栈里临时开辟一块空间用于存储 id 1 和age 25 
然后将值传递到堆里对象的id 和age的属性里
 
当一个方法调用完成之后,栈里面为其分配的空间全部消失
 
当没有写构造方法的时候,会默认给一个无参的构造方法
如果自己定义了构造方法,往往要自己加上无参的构造
 
时间:2017年4月25日14:38:10~2017年4月25日14:39:53
章节:03章_05节 
视频长度:1:56
内容:约定俗成的命名规则
心得:一般运用驼峰命名即可,类型首字母大写,包名采取小写
 
时间:2017年4月25日14:40:16~2017年4月25日15:06:22
章节:03章_06节  03章_07节  03章_08节 
视频长度:3:24 +9:49+13:49
内容:内存解析
心得:

 

 
基本数据类型参数传递属于值传递
test.change1(date)方法里
 
test.change1(date);
public void change1(int i){
i = 1234;
}
 
由于参数调用属于值传递
因此 首先栈里创建了一个i ,9通过值传递直接把值给i
然后i的值变成了1234 接着方法结束之后,栈里面为其分配的空间全部消失
i也就不存在了,date依旧是9
 
test.change2(d1)方法里
 
test.change2(d1);
public void change2(BirthDate b){
b= new BirthDate(22,2,2004);
}
 

 

d1是一个对象

 


 

所以首先是栈里面存在一个b ,然后d1将自己指向的地址复制给了b,所以访问
b的地址就可以找到对象 所以当方式开始调用时 b和d1指向了同一个对象
然后方法里是
 
b = new B........
 
b new了一个新对象 指向了一个新的对象 如图

 

 
接着change2结束之后 ,b就消失了 
方法执行完之后 栈里面的b是立刻消失,而堆里new出来的这个

 

也会消失,不过不是立刻消失,是等GC将其回收,不过在开发者看来可以认为他已经消失了。
 
 
test.change3(d2);
change3(BirthDate b){
b.setDay(22);
}
 

 

同样的,先创建一个b 然后将d2的引用给b
然后放里面 b通过类里面的setday方法 直接改变了地址里的属性
接着方法结束 b消失 但是地址里面的值却被永久改变了
这是一次成功的改变。
 
总结:从内存的角度去理解这些相似的方法,十分的清晰,以前学习的时候只是属于懵懂状态
 
时间:2017年4月25日15:07:02~2017年4月25日15:21:18
章节:03章_09节 03章_10节 03章_11节 
视频长度: 1:43 + 9:45 + 8:38
内容:练习与答案 与 内存解析
心得:

 

 
定义属性x,y,z 然后生成get,set方法以及构造方法
提供一个计算距离方法 要用到java类库的 Math类
public int getInstance(Point p){
x=p.getX();
y=p.getY();
z=p.getZ();
sum = x*x+y*y+z*z;
return Math.pow(sum,2);//开二次方
}
 
一些基本数学计算的方法Math类里都有,记不得了可以看一看api或者查询
 
内存图

 

 
关于返回值的内存,也会临时存在的栈区,方法调用完成之后,所有开辟的局部变量(包括返回值)内存,消失
 
 
时间:2017年4月25日15:21:40~2017年4月25日15:27:23
章节:03章_12节 
视频长度: 18:40
内容:方法的重载 与 内存解析
心得:
重载的是java多态的一种体现
参数类型不同,数量不同,顺序不同
方法的重写不允许修改返回值 重载可以
内存解析上面已经整理了,这里就不加多了。
 
时间:2017年4月25日15:28:20~2017年4月25日15:37:26
章节:03章_13节  03章_14节 03章_15节 03章_16节
视频长度: 3:50 + 09:34 +11:06+7:59
内容:对象的创建与使用 以及 TestCircle程序的分析与复习
心得:
是以上内容的复习,再次不重复整理
贴一个小问题的解决
java常量池 在jvm的哪里?
 
- <span style="font-size: large;">import java.util.ArrayList;
-
- public class Test {
-
-     public static void main(String[] args) {
-         String str = "abc";
-         char[] array = {'a', 'b', 'c'};
-         String str2 = new String(array);
-         //使用intern()将str2字符串内容放入常量池
-         str2 = str2.intern();
-         //这个比较用来说明字符串字面常量和我们使用intern处理后的字符串是在同一个地方
-         System.out.println(str == str2);
-         //那好,下面我们就拼命的intern吧
-         ArrayList<String> list = new ArrayList<String>();
-         for (int i = 0; i < 10000000; i++) {
-             String temp = String.valueOf(i).intern();
-             list.add(temp);
-         }
-     }
- }</span>
 
就是将STR2字符串内容一直往常量池里放
看看会出哪种异常
true 
Exception in thread "main" java.lang .OutOfMemoryError: PermGen space 
        at java.lang.String.intern(Native Method) 
        at Test.main(Test.java:16) 
Java Result: 1  
 
PermGen space  就是方法区
 
所以说常量池是属于方法区里的,是各线程共享的内存区域。
 
 
时间:2017年4月25日15:37:47~2017年4月25日15:46:35
章节:03章_17
视频长度: 10:37
内容:this关键字
心得:
在类的方法定义中使用的this关键字代表使用该方法的对象的引用
当必须指出当前使用方法的对象是谁的时候要使用this
你对哪个对象调用的这个方法,this指的就是谁
this是指向自身对象的一个引用
 
一般使用this可以处理方法中变量重名的问题
this看做是一个变量,它的值是当前对象的引用
堆内存图

 

指向自己
 

 

 

 
return this; 返回的是对象的引用

 

多次调用this,返回自己 i++操作两次
 
时间:2017年4月25日15:46:44
章节: 03章_18节 03章_19节
视频长度: 10:33 +09:53
内容:static关键字
心得:
static属性为类所有,在类加载的时候创建,为类以及它的实例化对象共享
内存中在方法区
 
用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员
 

 

 
从图中看的话,静态的属性是在常量池。 当然这个图中的Name是String类型
数据存放在常量池,堆中的Name指向这个常量池
 
时间:2017年4月25日15:56:38~2017年4月25日16:00:39
章节: 03章_20 03章_21 03章_22 03章_23
视频长度: 16:25 +11:42 +7:22 +2:41
内容:package和import语句
心得:
因为之前做项目经常会用到,这部分基础知识略过
关于classpath的配置了解
实际开发中(idea eclipse myEclipse都自动配置了)
一些常用包 java.lang(这个不需要导入) java.io java.util java.net ....
 
 
时间:2017年4月25日16:00:50~2017年4月25日16:07:22
章节:  03章_24 03章_25
视频长度: 6:17 + 21:04
内容:继承和权限控制
心得:

 

一图流
 
java中的只支持单继承,不支持多继承
通过继承,子类自动拥有了基类(superclass)的所有成员(成员变量和方法)
 
从内存角度来讲,可以这么说,子类对象里包含了一个父类对象
内存图

 

 
堆里面的对象student实例化对象里包含了一个person对象 拥有者父类里的所有成员
同时子类也有属于自己的一些成员
 
时间:2017年4月25日16:07:34~2017年4月25日16:10:21
章节:  03章_26
视频长度: 11:00
内容:重写
心得:
子类中可以根据需要从基类中继承来的方法进行重写
重写方法必须和被重写方法具有相同方法名称,参数列表和返回类型
重写方法不能使用比被重写方法更严格的访问权限(比如父类实protect,你子类不能改成Public)
 
要注意 相同方法名称,参数列表和返回类型
要不然容易变成重载或者直接报错
 
时间:2017年4月25日16:10:32~2017年4月25日16:12:52
章节:  03章_27
视频长度: 08:21
内容:super关键字
心得:
和this十分类似,还是用内存图解释的更清楚
注意this和super都是在子类对象里,this指向的是自身,super指向的是
子类里面包含的父类对象

 

 
时间:2017年4月25日16:12:59~2017年4月25日16:17:14
章节:  03章_28
视频长度: 11:25
内容:继承中的构造方法
心得:
子类的构造的过程中 必须调用其基类的构造方法
从内存上来说 子类里面包含的那个父类的对象肯定要被构造出来
所以要构造一个子类,必须先构造其父类对象,从内存图中也很容易理解
使用super(参数列表)调用基类的构造方法
如果调用super,必须写在子类构造方法的第一行
如果没有显式的调用的话,系统默认调用父类的无参构造方法,如果父类没有,
编译出错
 
时间:2017年4月25日16:17:42~2017年4月25日16:25:19
章节:  03章_29 03章_30 03章_31 03章_32
视频长度: 1:15 +4:12 +14:00 + 8:14
内容:关于以上的一些小练习
 
这里牵扯一个java类执行顺序的问题
- 父类的静态成员赋值和静态块
 
- 子类的静态成员和静态块
 
- 父类的构造方法
 
- 父类的成员赋值和初始化块
 
- 父类的构造方法中的其它语句
 
- 子类的成员赋值和初始化块
 
- 子类的构造方法中的其它语句
 

 

所以说首先是父类构造方法 调用print方法 输出A()
然后是子类的构造方法,调用了父亲的print方法 输出B()
然后b.f是一个方法的重写 输出B:f()
所以最后输出结果是
A()
B()
B:f()
 
 
时间:2017年4月25日16:26:10~2017年4月25日16:28:35
章节:  03章_33
视频长度: 07:50
内容:JDK_API 文档查询
心得:
因为之前经常查,所以这里略过
之前都是直接看的chm类型的文档 
有个朋友推荐Dash来看各类API好像挺不错的,下次可以试试
 
时间:2017年4月25日16:28:50~2017年4月25日16:47:16
章节:  03章_34
视频长度: 18:15
内容:Object类之toString方法
心得:
Object类实所有java类的根基类
如果在类的声明未使用extends关键字指明其类,默认基类是Object类
public class Person{} 等价于public class Person extends Object{}
哈希吗 可以根据哈希码很快的找到对象在内存中的位置

 

返回代表这个对象的一个字符串
 
在进行String与其他数据类型的连接操作时(如:System.out.println("info"+person)),将自动调用该对象类的ToString()方法
 
这是api toString()的源码
 
    /**
     * Returns a string representation of the object. In general, the
     * {@code toString} method returns a string that
     * "textually represents" this object. The result should
     * be a concise but informative representation that is easy for a
     * person to read.
     * It is recommended that all subclasses override this method.
     * <p>
     * The {@code toString} method for class {@code Object}
     * returns a string consisting of the name of the class of which the
     * object is an instance, the at-sign character `{@code @}', and
     * the unsigned hexadecimal representation of the hash code of the
     * object. In other words, this method returns a string equal to the
     * value of:
     * <blockquote>
     * <pre>
     * getClass().getName() + '@' + Integer.toHexString(hashCode())
     * </pre></blockquote>
     *
     * @return  a string representation of the object.
     */
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
It is recommended that all subclasses override the method
推荐所有类都重写这个方法

 

默认的实现是这样的 返回类型+@+哈希编码(16进制的)
 
时间:2017年4月25日16:47:26~2017年4月25日16:51:59
章节:  03章_35
视频长度: 2:27
内容:hascode的简单解释
心得:

 

简单来说:
JVM根据hashcodes table上的对象的唯一哈希编码去寻找到这个对象以及它的位置
但是在java中的
哈希编码似乎实现的有点问题? 
就是2个对象不同 但是哈希编码是相同的 并且概率并不低?
这个有时间去查阅相关资料
 
时间:2017年4月25日16:52:21~2017年4月25日17:04:24
章节:  03章_36
视频长度: 29:01
内容:Object类之equals方法
心得:
object里的equals()方法源码
 
    /**
     * Indicates whether some other object is "equal to" this one.
     * <p>
     * The {@code equals} method implements an equivalence relation
     * on non-null object references:
     * <ul>
     * <li>It is <i>reflexive</i>: for any non-null reference value
     *     {@code x}, {@code x.equals(x)} should return
     *     {@code true}.
     * <li>It is <i>symmetric</i>: for any non-null reference values
     *     {@code x} and {@code y}, {@code x.equals(y)}
     *     should return {@code true} if and only if
     *     {@code y.equals(x)} returns {@code true}.
     * <li>It is <i>transitive</i>: for any non-null reference values
     *     {@code x}, {@code y}, and {@code z}, if
     *     {@code x.equals(y)} returns {@code true} and
     *     {@code y.equals(z)} returns {@code true}, then
     *     {@code x.equals(z)} should return {@code true}.
     * <li>It is <i>consistent</i>: for any non-null reference values
     *     {@code x} and {@code y}, multiple invocations of
     *     {@code x.equals(y)} consistently return {@code true}
     *     or consistently return {@code false}, provided no
     *     information used in {@code equals} comparisons on the
     *     objects is modified.
     * <li>For any non-null reference value {@code x},
     *     {@code x.equals(null)} should return {@code false}.
     * </ul>
     * <p>
     * The {@code equals} method for class {@code Object} implements
     * the most discriminating possible equivalence relation on objects;
     * that is, for any non-null reference values {@code x} and
     * {@code y}, this method returns {@code true} if and only
     * if {@code x} and {@code y} refer to the same object
     * ({@code x == y} has the value {@code true}).
     * <p>
     * Note that it is generally necessary to override the {@code hashCode}
     * method whenever this method is overridden, so as to maintain the
     * general contract for the {@code hashCode} method, which states
     * that equal objects must have equal hash codes.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is the same as the obj
     *          argument; {@code false} otherwise.
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
   
public boolean equals(Object obj) {
        return (this == obj);
    }
 
是通过== 进行比较的 
而== 比较的是最终指向的地址
 
关于这些注释的解释
<li>It is <i>reflexive</i>: for any non-null reference value
     *     {@code x}, {@code x.equals(x)} should return
     *     {@code true}.
     * <li>It is <i>symmetric</i>: for any non-null reference values
     *     {@code x} and {@code y}, {@code x.equals(y)}
     *     should return {@code true} if and only if
     *     {@code y.equals(x)} returns {@code true}.
     * <li>It is <i>transitive</i>: for any non-null reference values
     *     {@code x}, {@code y}, and {@code z}, if
     *     {@code x.equals(y)} returns {@code true} and
     *     {@code y.equals(z)} returns {@code true}, then
     *     {@code x.equals(z)} should return {@code true}.
     * <li>It is <i>consistent</i>: for any non-null reference values
     *     {@code x} and {@code y}, multiple invocations of
     *     {@code x.equals(y)} consistently return {@code true}
     *     or consistently return {@code false}, provided no
     *     information used in {@code equals} comparisons on the
     *     objects is modified.
 
1.自反 X要equalsX 2 2. 对称 X equals Y  ,Y也equalsX
3.传递 X equals Y Y equals Z ,那么X equals Z
4.持续性 X今天equals Y 明天也要equalsY 
非空 如果和空进行equals 返回的一定是false
并且不可以空和空进行equals 
 

 

 
 
String里的equals()
 
    /**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
 
- /**
- * instanceof运算符用法
- * 运算符是双目运算符,左面的操作元是一个对象,右面是一个类.当
- * 左面的对象是右面的类创建的对象时,该运算符运算的结果是true,否则是false
- *
- * 说明:(1)一个类的实例包括本身的实例,以及所有直接或间接子类的实例
- * (2)instanceof左边操作元显式声明的类型与右边操作元必须是同种类或右边是左边父类的继承关系,
- * (3)不同的继承关系下,编译出错
 
String里重写,作了几个判断
第一个如果 ==了 那么返回true
第二个 如果 这个对象是属于String类的 并且里面的值相同 那么也返回true
 
 
时间:2017年4月25日17:04:46~2017年4月25日17:23:55
章节:  03章_37节 03章_38节
视频长度: 13:25 + 04:51
内容:对象转型
心得:

 

 
比如父类是动物,子类是狗
如果你将狗转型成了动物,就不能将他当成狗了,得当成动物了
也就是狗里面独有的东西不能访问了
 
下面的是将动物转型成子类狗

 

 

将A一只动物 指向了一只狗
还是用内存图表示更为清晰

 

 

 

使用instanceof 时
在内存里 比较的是整个对象 而不是程序认为的
所以比较的是整个 是一只狗
 
这时候访问furColor就会报错
如果要强制访问的话,就要强转
Dog d1 = (Dog)a;
d1和a 指向的位置相同
内存图

 

 
 
转型有什么用处 
实例

 

本来要写很多方法的 这样只要写一种就可以了 
通过instanceof 进行判断 ,所有Animal都可以当成参数传进去
 
如果将来想添加输出鸟  通常还要加个方法 需要动的东西比较多 
但是这样的话 直接在里面做改动就可以了 
 
这样增加了这个程序的可扩展性
如果写很多方法的话 可扩展性就不好,结构也不好
可扩展性好:程序已经完成,要添加一些功能的时候,尽量的不去修改主要结构
就可以完成
 
一个比较不错的可扩展性的设计,方法的参数传的是父类的参数
然后实际传的是子类的对象
接着再在方法里判断是属于哪种子类,接着再执行相应操作
 
(下面的比较重要,因此加粗加大了)
时间:2017年4月25日20:30:36~2017年4月25日21:16:44
章节:  03章_39节
视频长度: 20:52 + 6:17
内容:动态绑定 与 多态
心得:
动态绑定是指“在执行期间(而非编译期)”判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

 

 
内存图

 

 
 
方法不是通过引用类型来找的,而是通过实际类型来寻找方法
动态绑定的机制是 实际上调用的实际的类型的方法
对象内部有一个enjoy方法的指针 指向方法 
在new对象的对象时候 指针指向的方法随之改变 new什么对象 就指向这个对象重写的方法
 
只有在运行期间 new出对象来 才能确定方法究竟调用的是哪个方法 实际的地址才会绑定到相应的方法之中的地址上面  所以这就是“动态绑定”
 
优势在于:可扩展性  达到了很高的水平
 
多态的存在 有三个必要条件
1.要有继承
2.要有重写
3.父类引用指向子类对象
 
写例子的时候出现了一个问题
 
出现这个错误的时候,我一直不太理解。
在借鉴别人的解释之后才恍然大悟。
在代码中,我的Dog类是定义在Main中的内部类。Dog内部类是动态的内部类,而我的main方法是static静态的。
就好比静态的方法不能调用动态的方法一样。
有两种解决办法:
第一种:
将内部类Dog定义成静态static的类。
第二种:
将内部类Dog在Main类外边定义。
 
疑问:关于这个例子一些内部类的问题?
 

 

一个简单的例子,飞机游戏,飞机,子弹以及各类物品都需要画出来
 
一般的设计思路
画的程序里作一个判断 
如果是飞机类就画一个飞机 如果是子弹类就画一个子弹
 
面相对象动态绑定的设计思路
首先设计一个总的游戏物品类 里面有一个draw画的方法
 
然后子弹 飞机 都作为子类重写这个draw方法 实现了自己的实现 
 
接着实际画的时候 直接传父类GameObject 作为参数  再进行一系列处理
而不用定义多个方法 或者作多种判断
 
这样如果要添加一个新的游戏物品进去 十分方便 程序的可扩展性就比较好
 
时间:2017年4月26日09:37:18~2017年4月26日09:45:00
章节:  03章_40节
视频长度: 08:49
内容:抽象类
心得:
用abstract关键字来修饰一个类时,这个类叫抽象类;用abstract来修饰一个方法时,该方法叫抽象方法
含有抽象方法的类必须被声明为抽象类,抽象类必须被继承,抽象方法必须被重写
抽象类不能被实例化
抽象方法只需声明,而不需实现
 
上面是视频里的一些总结点,其实也并没有什么关键是要理解
之前也有整理过关于抽象类和接口的一些详细区别,这里直接重新整理贴过来
 

从语法方面

   1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法
  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
在分别说一下抽象类与接口
下面要注意一个问题:在《Java编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法。个人觉得这个属于钻牛角尖的问题吧,因为如果一个抽象类不包含任何抽象方法,为何还要设计为抽象类?所以暂且记住这个概念吧,不必去深究为什么。
抽象类
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
接口
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

从设计方面

   1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 “是不是”的关系,而 接口 实现则是 “有没有”的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
  2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

举个例子

但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:
  1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
  2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。   从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。
 
时间:2017年4月26日09:45:15~2017年4月26日09:52:10
章节:  03章_42节
视频长度: 08:05
内容:final关键字
心得:
final的关键就是在于不可变 
对于修饰变量就是 变量不可改变
对于修饰方法就是 方法不可重写
对于修饰类就是     类不可被继承
 
关于之前研究过一个关于String的问题 也牵扯到了FINAL 这里也顺带整理过来吧
 
 
在java中String类为什么要设计成final?
 

String很多实用的特性,比如说“不可变性”,是工程师精心设计的艺术品!艺术品易碎!用final就是拒绝继承,防止世界被熊孩子破坏,维护世界和平!

1. 什么是不可变?
String不可变很简单,如下图,给一个已有字符串"abcd"第二次赋值成"abcedl",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。

 



2. String为什么不可变?
翻开JDK源码,java.lang.String类起手前三行,是这样写的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** String本质是个char数组. 而且用final关键字修饰.*/
    private final char value[];
        ...
        ...
}

首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[ ]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。

有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。Array的数据结构看下图,

 


也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,
final int[] value={1,2,3}
int[] another={4,5,6};
value=another;    //编译器报错,final不可变
value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。
final int[] value={1,2,3};
value[2]=100;  //这时候数组里已经是{1,2,100}
或者用更粗暴的反射直接改,也是可以的。
final int[] array={1,2,3};
Array.set(array,2,100); //数组也被改成{1,2,100}

所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

3. 不可变有什么好处?
这个最简单地原因,就是为了安全。看下面这个场景(有评论反应例子不够清楚,现在完整地写出来),一个函数appendStr( )在不可变的String参数后面加上一段“bbb”后返回。appendSb( )负责在可变的StringBuilder后面加“bbb”。
class Test{
    //不可变的String
    public static String appendStr(String s){
        s+="bbb";
        return s;
    }

    //可变的StringBuilder
    public static StringBuilder appendSb(StringBuilder sb){
        return sb.append("bbb");
    }

    public static void main(String[] args){
        //String做参数
        String s=new String("aaa");
        String ns=Test.appendStr(s);
        System.out.println("String aaa >>> "+s.toString());

        //StringBuilder做参数
        StringBuilder sb=new StringBuilder("aaa");
        StringBuilder nsb=Test.appendSb(sb);
        System.out.println("StringBuilder aaa >>> "+sb.toString());
    }
}

//Output: 
//String aaa >>> aaa
//StringBuilder aaa >>> aaabbb 

如果程序员不小心像上面例子里,直接在传进来的参数上加"bbb",因为Java对象参数传的是引用,所以可变的的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了"aaabbb"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。

再看下面这个HashSet用StringBuilder做元素的场景,问题就更严重了,而且更隐蔽。
class Test{
    public static void main(String[] args){
        HashSet<StringBuilder> hs=new HashSet<StringBuilder>();
        StringBuilder sb1=new StringBuilder("aaa");
        StringBuilder sb2=new StringBuilder("aaabbb");
        hs.add(sb1);
        hs.add(sb2);    //这时候HashSet里是{"aaa","aaabbb"}

        StringBuilder sb3=sb1;
        sb3.append("bbb");  //这时候HashSet里是{"aaabbb","aaabbb"}
        System.out.println(hs);
    }
}
//Output:
//[aaabbb, aaabbb]

StringBuilder型变量sb1和sb2分别指向了堆内的字面量"aaa"和"aaabbb"。把他们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先"aaa"的地址上改。导致sb1的值也变了。这时候,HashSet上就出现了两个相等的键值"aaabbb"。破坏了HashSet键值的唯一性。所以千万不要用可变类型做HashMap和HashSet键值。

还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竟态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以线程安全。

最后别忘了String另外一个字符串常量池的属性。像下面这样字符串one和two都用字面量"something"赋值。它们其实都指向同一个内存地址。
String one = "someString";
String two = "someString";

 


这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。
 
 
时间:2017年4月26日09:52:27~2017年4月26日10:51:04
章节:  03章_43节 03章_44节
视频长度: 25:21+10:21
内容:interface接口
心得:
C++中的多继承容易出现问题 java使用的接口来解决这一问题 
关于接口的一些整理 在上面的抽象类中包含了很多,这里重复的就不复制了
这里只整理上面没重复的
 
接口可以继承其他的接口,并添加新的属性和方法
 
多个无关的类可以实现同一个接口
一个类可以实现多个无关的接口
与继承关系类似,接口与实现类之间存在多态性
 

 

 
 
内存图

 

 
Singer:s 只能看得见sing方法
具体方法输出的时候 因为指向了实际对象 所以存在多态 因此输出的时候 可以清楚的输出
student is singing
 
后面的内存图 这里分开放 不然容易看的乱

 

 
从内存图中可以看出 
接口对于实际当中的对象来说,每一个接口暴露了对象的一部分方法
 

 

接口对象
定义一个接口对象 只要实现了这个接口的任何一个对象都可以往里面传 
 里面隐含了多态的实现(因为这个sing方法必然被重写了)
 
类和类之间相互继承 接口和接口之间可以相互继承
但是类只能实现接口
 
关于一个小问题
实现多个接口的时候 多个接口里有相同的方法名 参数类型 但返回类型不一样 
怎么办?视频中的老师没有给出解决办法
这种情况比较少见 如果是返回类型也一样的话 只需要实现一次就可以了
但是如果返回类型不一样的话 直接写2个方法就直接报错 
查阅了相关资料  遇到这种情况使用内部类处理
        首先说明java多实现的背景:
        c++中的指针和多重继承易产生安全问题。子类有多个父类,父类又有其父类,继承树很复杂,方法重写和构造易混乱,代码也不易维护。
        java吸取教训,采用了单继承多实现的方式。
 
        但有个无论多继承还是多实现都存在的隐含问题:多个接口中有重名方法怎么办?
        显然这种bug,接口编写者不会发现,只有程序员在写实现类时,ide首先就会报错。
        解决方法: 实现类里写个内部类,分别实现两个接口
 
补:
java规范里, 方法名+参数(不含返回值类型)唯一确定一个方法。
 
 
 
interface I1
{
    void get();
}
interface I2
{
    void get();
}
public class MultiInter implements I1{


    public void get(){
       System.out.println("I am from I1");
    }


    private class I2Impl implements I2{
         public void get(){
           System.out.println("I am from I2");
        }
    }
    public void get2(){
        I2 i2=new I2Impl();
        i2.get();
    }
    public static void main(String rsg[]){
        MultiInter mi=new MultiInter();
        mi.get();
        mi.get2();
    }
}
 
 
时间:2017年4月26日11:07:43
章节:  03章_45
视频长度:17:22 
内容:第三章知识点总结
心得:
这里直接截图了

 

转载于:https://www.cnblogs.com/invoker-/p/6772771.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值