Home Tags Posts tagged with "Java"

Java

0 16

今天和猪仔讨论项目的时候遇到一个需求

目前有一个实体类,里面有一些String,int的属性,还有一个List<>类型的属性

但是这个List<>括号里面内容不同而形成不同实体

比如记录日志,该日志分为工作日志和系统日志,那么这两种不同类型的日志的日志时间,日志数量这两个属性对应String、int,是一样的,不同之处在于List<>传入的参数类型不同

一个传入List<Work> 一个传入List<Sys>

我们不想写两个不同的实体类,想用一个实体类来表示这两种不同的日志对象

一开始我的思路是写成 List<Object> 传入,再进行转型

可惜这样无法得到想要的结果,因为List<Object>应该看成一个整体,他不是List<Work>,也不是List<Sys>的父类

因此无法实现向上转型

后来改用 ? 泛型 即可 详情见下文

https://blog.csdn.net/eeeeasy/article/details/80999650?utm_source=app&app_version=4.14.1?utm_source=app

0 17

https://zhuanlan.zhihu.com/p/73628158

之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐盘端走清理的,是 C++ 程序员,吃完直接就走的,是 Java 程序员。

确实,在 Java 的世界里,似乎我们不用对垃圾回收那么的专注,很多初学者不懂 GC,也依然能写出一个能用甚至还不错的程序或系统。但其实这并不代表 Java 的 GC 就不重要。相反,它是那么的重要和复杂,以至于出了问题,那些初学者除了打开 GC 日志,看着一堆0101的天文,啥也做不了。

今天我们就从头到尾完整地聊一聊 Java 的垃圾回收。

什么是垃圾回收

  • 垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
  • Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?
  • 1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC 的历史远远大于 Java 的历史!

怎么定义垃圾

既然我们要做垃圾回收,首先我们得搞清楚垃圾的定义是什么,哪些内存是需要回收的。

引用计数算法
引用计数算法(Reachability Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。

String m = new String(“jack”);

先创建一个字符串,这时候”jack”有一个引用,就是 m。

然后将 m 设置为 null,这时候”jack”的引用次数就等于0了,在引用计数算法中,意味着这块内容就需要被回收了。

m = null;

引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的”Stop-The-World”的垃圾收集机制。

看似很美好,但我们知道JVM的垃圾回收就是”Stop-The-World”的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。

public class ReferenceCountingGC {

public Object instance;

public ReferenceCountingGC(String name){}
}

public static void testGC(){

ReferenceCountingGC a = new ReferenceCountingGC("objA");
ReferenceCountingGC b = new ReferenceCountingGC("objB");

a.instance = b;
b.instance = a;

a = null;
b = null;
}

1. 定义2个对象
2. 相互引用
3. 置空各自的声明引用

我们可以看到,最后这2个对象已经不可能再被访问了,但由于他们相互引用着对方,导致它们的引用计数永远都不会为0,通过引用计数算法,也就永远无法通知GC收集器回收它们。

可达性分析算法

可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。

通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于 GC Root。

Java 内存区域

在 Java 语言中,可作为 GC Root 的对象包括以下4种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

1、虚拟机栈(栈帧中的本地变量表)中引用的对象
此时的 s,即为 GC Root,当s置空时,localParameter 对象也断掉了与 GC Root 的引用链,将被回收。

public class StackLocalParameter {
public StackLocalParameter(String name){}
}

public static void testGC(){
StackLocalParameter s = new StackLocalParameter("localParameter");
s = null;
}

2、方法区中类静态属性引用的对象
s 为 GC Root,s 置为 null,经过 GC 后,s 所指向的 properties 对象由于无法与 GC Root 建立关系被回收。

而 m 作为类的静态属性,也属于 GC Root,parameter 对象依然与 GC root 建立着连接,所以此时 parameter 对象并不会被回收。

public class MethodAreaStaicProperties {
public static MethodAreaStaicProperties m;
public MethodAreaStaicProperties(String name){}
}

public static void testGC(){
MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties");
s.m = new MethodAreaStaicProperties("parameter");
s = null;
}

3、方法区中常量引用的对象
m 即为方法区中的常量引用,也为 GC Root,s 置为 null 后,final 对象也不会因没有与 GC Root 建立联系而被回收。

public class MethodAreaStaicProperties {
public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final");
public MethodAreaStaicProperties(String name){}
}

public static void testGC(){
MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties");
s = null;
}

4、本地方法栈中引用的对象
任何 native 接口都会使用某种本地方法栈,实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。

怎么回收垃圾

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,这里我们讨论几种常见的垃圾收集算法的核心思想。

标记 — 清除算法

标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。就像上图一样,清理掉的垃圾就变成未使用的内存区域,等待被再次使用。

这逻辑再清晰不过了,并且也很好操作,但它存在一个很大的问题,那就是内存碎片。

上图中等方块的假设是 2M,小一些的是 1M,大一些的是 4M。等我们回收完,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个 2M的内存区域,其中有2个 1M 是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。

复制算法

复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。

上面的图很清楚,也很明显的暴露了另一个问题,合着我这140平的大三房,只能当70平米的小两房来使?代价实在太高。

标记整理算法

标记整理算法(Mark-Compact)标记过程仍然与标记 — 清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。

标记整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。

分代收集算法分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。对象存活周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或者标记 — 整理算法来进行回收。so,另一个问题来了,那内存区域到底被分为哪几块,每一块又有什么特别适合什么算法呢?

内存模型与回收策略

Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下 Java 堆的结构。

Java 堆主要分为2个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2个区。可能这时候大家会有疑问,为什么需要 Survivor 区,为什么Survivor 还要分2个区。不着急,我们从头到尾,看看对象到底是怎么来的,而它又是怎么没的。

Eden 区

IBM 公司的专业研究表明,有将近98%的对象是朝生夕死,所以针对这一现状,大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。

通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。

Survivor 区

Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。

1、为啥需要?

不就是新生代到老年代么,直接 Eden 到 Old 不好了吗,为啥要这么复杂。想想如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。

所以,Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

2、为啥需要俩?

设置两个 Survivor 区最大的好处就是解决内存碎片化。

我们先假设一下,Survivor 如果只有一个区域会怎样。Minor GC 执行后,Eden 区被清空了,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为 Survivor 有2个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。

这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivor space 是无碎片的。那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果 Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是经过权衡之后的最佳方案。

Old 区

老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记 — 整理算法。

除了上述所说,在内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代。

1、大对象

大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时,得注意了。

2、长期存活对象

虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中没经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。

3、动态对象年龄

虚拟机并不重视要求对象年龄必须到15岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的综合大于 Survivor 空间的一般,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。

这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。

本文部分内容参考自书籍:《深入理解Java虚拟机》。

一、什么是JSON?

JSON:JavaScript Object Notation[JavaScript 对象表示法]

JSON 是存储和交换文本信息的语法。类似 XML。(什么是XML?XML 简介 – 什么是 XML? | 菜鸟教程 (runoob.com)

JSON采用完全独立于任何程序语言的文本格式,使JSON成为理想的数据交换语言


二、为什么需要JSON

https://zhuanlan.zhihu.com/p/33792109


三、JSON、JSON格式、JSON对象、JSON字符串是什么关系?(重点)

1.JSON

其实,JSON并不复杂,之所以新手会对JSON犯迷糊,原因是简称。(至少我是这个原因)无论是在工作中,还是在论坛上,绝大多数时候,我们把JSON对象和JSON字符串都简称为JSON,以至于你常常会听到这样的回答:

“这个响应你用JSON接收就行”

“你测试接口的时候要记得用JSON格式”

“把这个JSON解析一下”

久而久之,新手就把JSON格式、JSON字符串、JSON对象(又可分普通对象、JSON数组)三者搞混了,现在,我们就将它们的关系梳理清楚。

2.JSON格式

JSON格式是一种数据交互的格式

当客户端和服务端进行数据交互的时候(不仅是C/S模型,其他模型也可以使用JSON格式交互),需要定义数据格式,JSON格式就是其中一种。

JSON格式是从JavaScript演变而来的

JSON是从 JavaScript 脚本语言中演变而来,使用Javascript语法来描述数据对象,文件名扩展是 .json,但是JSON格式仅仅是一个文本,仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。 目前非常多的动态(PHP,JSP,.NET)编程语言都支持JSON

JavaScript对象的定义语法如下:

var person = {firstName:"Bill",
              lastName:"Gates", 
              age:62, 
              eyeColor:"blue"};
而JSON格式则长这样:
{
    "book": [
        {
            "id":"01",
            "language": "Java",
            "edition": "third",
            "author": "Herbert Schildt"
        },
        {
            "id":"07",
            "language": "C++",
            "edition": "second"
            "author": "E.Balagurusamy"
    }]
}

 JSON格式和JavaScript的区别

 二者之间的详细总结可以参考下述文章:JavaScript JSON的总结 – 知乎 (zhihu.com)

3.JSON字符串

首先,请清晰一点,JSON字符串本质上就是字符串。

String url = “https://restapi.amap.com/v3/config/district?output=JSON&key=75bf29a927a4ce42358665b507493393&extensions=base” + “&” + “keywords=” + keywords;
String result = HttpUtil.get(url);
上述代码是高德查询省市区的接口,返回值如下:诺,我们一眼就能发现,这不就是JSON格式的数据吗,我们直接用字符串类型的result去接收,一点问题都没有。
其实用什么类型去接收返回值,是由方法决定的
如果方法返回的是一个JSONObject类型,那你直接使用JSONObject去接收
如果方法返回的是一个JSON格式的字符串,那你直接使用String去接收,然后再把这个String类型转换成JSONObject类型或者JSONArray即可
那么,我们为什么要把String类型转成JSONObject或者JSONArray呢?

4.JSON对象(JSONObject、JSONArray)

为什么要把String类型转成转成JSONObject或者JSONArray,其实是业务决定的。你想想,如果我们需要通过高德接口查询某一个省下面的市信息,那么如果返回值是String类型的话,我们单单想获取市名称列表,即List<name>,需要对字符串进行截断,拼接等等一系列的麻烦操作,这个过程还极其容易出错。所以,我们直接使用JSONObject类型,其本质是k-v结构


四、JSON字符串和JSONObject对象的相互转换

我们可以使用FastJson 的JSONObject.parseObject(jsonStr)将JSON字符串转换为JSONObject对象,之后可以直接通过get(key)获取对应的属性值

JSONObject parse = JSONObject.parseObject(result);
//通过key取JSONArray
JSONArray puisneList = parse.getJSONArray("districts");
//通过key取JSONObject
JSONObject name = parse.get("jsonObject");
//通过key取属性值
String name = parse.get("name");

四、JSON字符串和Java对象的相互转换

JSON字符串转Java对象:JSON.parseObject(jsonStr, Target.class)

Java对象转JSON字符串:JSON.toJSONString(TargetObject)


五、JSONObject和Java对象的相互转换

Java对象—>JSON对象

Student stu = new Student(“公众号BiggerBoy”, “m”, 2);
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(stu);

JSON对象—>Java对象

Student stu = new Student(“公众号BiggerBoy”, “m”, 2);
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(stu);
Student student = JSONObject.toJavaObject(jsonObject, Student.class);

JSON字符串—>Java对象

ReportExceptionDto frontReportExceptionDto = JSONObject.parseObject(jsonObject.toJSONString(), ReportExceptionDto.class);

注意区别:toJavaObject和parseObject两个方法都可以将jsonObject转换为Java对象,
但是toJavaObjec没有处理json转换对象$ref关键词,而parseObject方法处理了!

toJavaObject和parseObject,区别是:

parseObject的第一个参数是json字符串;

toJavaObject的第一个参数是json对象;

参照:JSONObject.toJavaObject的使用以及其他方法的使用JSON与Java对象的转换_sucsmanw的博客-CSDN博客_jsonobject to object
      Fastjson-JSON.toJavaObject(String text, Class<T>)与JSONObject.toJavaObject(JSON json, Class<T> clazz)_hanjq_code的博客-CSDN博客_fastjson tojavaobject


五、JSON字符串和JavaScript对象的相互转换

JSON和JS对象的相互转换 (biancheng.net)

 JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,所以 JSON 本质是一个字符串。通过 JSON 调用相应的方法,JSON 和 JS 对象可以相互转换。

将JSON字符串转换为JS对象

通过 JSON字符串的 parse() 方法,可以将一个 JSON字符串 解析为一个JS对象,解析格式如下:JSON.parse(json)【JSON —> JS对象】

例如:

var json = '{"name":"张三", "age":36}';//定义一个JSON
var obj = JSON.parse(json);//调用parse()将json解析为一个JS对象
console.log(obj);//输出:{name: "张三", age: 36}

将JS对象转换为JSON字符串

通过 JSON 的 stringify() 方法,可以将一个 JS 对象转换为 JSON,转换格式如下:JSON.stringify(obj)【JS对象—>JSON字符串】

例如:

var obj = {name:"张三", age:36};//定义一个JS对象
var json = JSON.stringify(obj);//调用stringify()将一个JS对象转换为JSON
console.log(json);//输出:{"name":"张三","age":36}

0 20

为什么Java中只有值传递?

按值调用:表示方法接收的是调用者提供的值;

按引用调用:表示方法接收的是调用者提供的变量地址;

一个方法可以修改传递引用所对应的变量值,不能修改按值传递调用所对应的变量值,它用来描述各种程序设计语言中方法参数传递方式;

Java程序设计语言总是采用按值传递;也就是说,方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容;

举例说明:

在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。

通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.

	public static void main(String[] args) {
		int[] arr = { 1, 2, 3, 4, 5 };
		System.out.println(arr[0]);
		change(arr);
		System.out.println(arr[0]);
	}

	public static void change(int[] array) {
		// 将数组的第一个元素变为0
		array[0] = 0;
	}

array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。

通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

 

很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。

example 3

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Student s1 = new Student("小张");
		Student s2 = new Student("小李");
		Test.swap(s1, s2);
		System.out.println("s1:" + s1.getName());
		System.out.println("s2:" + s2.getName());
	}

	public static void swap(Student x, Student y) {
		Student temp = x;
		x = y;
		y = temp;
		System.out.println("x:" + x.getName());
		System.out.println("y:" + y.getName());
	}
}

结果:

x:小李
y:小张
s1:小张
s2:小李

解析:

交换之前:

交换之后:

通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

总结

Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 值传递的。

下面再总结一下 Java 中方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
  • 一个方法可以改变一个对象参数的状态。
  • 一个方法不能让对象参数引用一个新的对象。

大纲

1.扎实的基本功:编程语言(熟练掌握1-2门语言),数据结构(栈,队列,链表,哈希,树),算法;

2.高质量的代码:边界条件,特值输入等易忽略问题,必须注重代码的鲁棒性和稳定性;在写代码之前,想好测试用例,把各种可能的用例都先写出来,再进行相应处理;

3.清晰的思路:复杂问题简单化,举例子模拟操作过程,图像化分析;

4.优化效率的能力:多种方法时应当选择最优解,优化时间和空间效率;

5.优秀的综合能力:学习能力,沟通能力,性格,思维发散能力,抽象建模能力;

基础知识

编程语言知识

设计模式

数据结构

数组:C/C++中,数组作为参数传递时会退化为同类型指针;

            面试题3.数组中的重复数组  https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/

        考点:对一维数组的编程和理解能力,一维数组在内存中占据连续的空间,可以根据下标定位相应的元素;

考察分析问题的能力,问题相对复杂时,通过具体的实例来找到其中的规律是本题解决的关键所在

考察沟通能力,询问数组是否可以修改?

        扩展:若不能修改数组,则使用什么方法?(二分查找或者新建数组)

           面试题4.二维数组中的查找  https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/

考点:对二维数组的编程和理解能力,二维数组在内存中占据连续的空间,同一行从左到右存储,整体从上到下存储

考察分析问题的能力,问题相对复杂时,通过具体的实例(右上角,左下角,右下角,左上角等关键位置)来找到其中的规律是本题解决的关键所在

扩展:while(searchScope)和while(row<rows&&col>=0)的区别在哪呢?searchScope=row<rows&&col>=0

字符串:若干字符组成的序列,考察概论非常高,C/C++中以’\0‘作为结尾;”012456789″转换为字符数组则需要11个空间;char[]  ch=new  char[11]; 为了节省内存,C/C++把字符常量放到一个单独的字符区域,当几个指针赋值给相同的字符常量时,实际上指向相同的内存地址;

    面试题5.替换空格  https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/

考点:对字符串的编程和理解能力;

考察分析时间效率的能力;

考察是否对内存覆盖有警惕性,即字符串能否变长;

考察思维能力,从前往后的思路被否定后,应该想到从后往前;

扩展:合并两个数组或者字符串时,如果从前往后合并需要大量移动,我们可以考虑从后往前合并,减少数字移动次数来提高效率

链表:由指针把若干个结点链接起来构成线性表;是一种动态数据结构,因为在创建链表时无需知道链表的长度,内存分配不是一次性完成,而是新建结点则分配一次;

     面试题6.从尾到头打印链表  https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

考点:是否允许修改链表结构?必须询问;

如果使用递归,是否会因为过深而导致栈溢出;

考察对循环,递归, 栈三者关系的掌握;

树:除根以外的结点都只有一个父节点,根节点无父结点,除叶结点以外的结点都有一个或多个子结点,叶结点无子节点,父子之间使用指针链接;

二叉树:树中每个结点最多只能有两个子节点的树;

遍历方式:前序,中序,后序,层序遍历;

    面试题7.重建二叉树  https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-di-gui-fa-qin/

考点:对二叉树的前序遍历和中序遍历的理解程度,前序根在前,中序根前为左子树分支,根后为右子树分支;

考察分析复杂问题的能力,将构建二叉树的大问题分解为左,右子树的小问题,本质上一致,则递归调用;

栈:栈在计算机领域被广泛应用,例如操作系统会给每个线程创建一个栈来存储函数调用时各个函数的参数,返回地址和临时变量等,栈的特点是先进后厨,最后被push入栈的元素会被第一个pop出栈;栈通常不考虑排序,需要O(N)的时间找到最大或者最小的元素;

    面试题9.用两个栈实现队列  https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/

考点:对栈和队列的理解;

考察写模板相关的代码的能力

考察分析问题的能力,通过具体例子分析,结合画图将抽象问题形象化,从而解决该问题;

扩展:用两个队列实现栈;知识点:java里的Stack不推荐使用,因此我们可以使用LinkedList来替代Stack;原因的话是Stack继承了Vector接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。 可以使用LinkedList来做Stack的容器,因为LinkedList实现了Deque接口,所以Stack能做的事LinkedList都能做,其本身结构是个双向链表,扩容消耗少。

算法和数据操作:基于递归的实现方法代码比较简洁,但性能不如基于循环的实现方法,面试时应当根据题目的特点和要求,选择合适的方法;排序和查找是考察算法的重点,着重掌握二分查找,归并排序和快速排序;

考点:二维数组(迷宫或棋盘)上搜索路径,可以尝试采用回溯法,通常回溯用递归实现,或者用栈来实现;

若求某个问题的最优解,且该问题可以分为多个子问题,尝试使用动态规划(最优子问题,重复子结构),自上而下的递归思路去分析动态规划问题随时,会发现子问题之间存在重叠的更小子问题,为避免重复计算,采用自下而上的循环代码实现(如斐波那契数列),将子问题最优解算出并保存,再基于子问题的解来计算大问题的解。

DP的思路之后,如果分解子问题的时候是否存在某个特殊选择,采用此特殊选择一定可得到最优解,那么考虑贪婪算法。

位运算是特殊的算法,一共有,与,或,异或,左移,右移五种;

递归和循环:需要重复地多次 计算相同的问题,则可以选择递归或者循环;递归:在一个函数的内部调用此函数本身,循环:设置计算的初始值及终止条件,在一个范围内重复运算;递归代码简洁但不如循环效率高,原因:每次函数调用需要在内存栈中分配空间以保存参数,返回地址及临时变量,往栈压入数据和弹出数据都需要时间;

递归中很多计算可能重复,对性能将带来很大影响;

递归也可能发生栈溢出;

   面试题10.斐波那契数列  https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/

   青蛙跳台阶问题  https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/

考点:不同方法求斐波那契数列的时间效率大不相同,递归只管但效率低,循环实现可以提高时间效率(建立备忘录来减少重复计算,更巧妙的是维护两个变量交替更新);

考察对递归和循环的理解及编程能力

考察对时间复杂度的分析能力

考察数学建模能力;

查找和排序

面试题11.旋转数组的最小数字https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/

考点:考察对二分查找的理解;

考察沟通能力和学习能力。需要在很短的时间内学习和理解新概念,在面试过程中,如果面试官提出新概念,应当

主动和面试官沟通,多问问题,弄清概念;

考察应聘者思维的全面性。应当很好地处理这些特例;

回溯法:蛮力法的升级版,从解决问题的每一步的所有肯能选项里系统地选择出一个可行的解决方案,非常适合由多个步骤组成的问题,并且每个步骤都有多个选项;当我们在某一步选择了其中一个选项时,进入下一步,又面临新的选项,如此重复选择,直至到达最终状态;

回溯法解决的问题的所有选项可以形象地用树状结构表示;在某一步有n个可能的选项,则该步骤可以看成树状结构中的一个节点,每个选项看成树中节点连接线,经过这些连接线到达该节点的n个子节点。树的叶节点对应着终结状态,如果在叶节点的状态满足题目的约束要求,则找到了可行的方案;

如果叶节点的状态不满足约束要求,则回溯到它的上一个节点再尝试其他的选项,如果上一个节点的所有可能都被尝试过,且不能到达满足约束条件的终结状态,则再次回溯到上一个节点,如果所有节点的所有选项都尝试过且无法到达满足约束条件的终结状态,则该问题无解;

通常回溯法适合用递归实现,到我们到达某一个节点时,尝试所有可能的选项并且在满足条件的前提下递归地抵达下一个节点;

面试题12.矩阵中的路径https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/

考点:考察对回溯法的理解和掌握;通常在二维矩阵上找路径这类题目都可以使用回溯解决;

考察对数组的编程能力,例如越界处理,标记,回溯还原等;

面试题13.机器人的运动范围https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/

考点:考察对回溯法的理解;通常物体或人在二维方格运动这类问题都可以用回溯法解决;

考察应聘者对数组编程的能力;将矩阵一般看成一个二维数组,只有对数组的特性充分了解,才有可能快速正确地实现回溯法的代码编写;

动态规划与贪婪算法:如果面试题为求一个问题的最优解(通常是求最大值或者最小值),而且该问题能分解成若干个子问题,且子问题之间有重叠的更小的子问题(最优子结构,重复子问题),就可以考虑用动态规划来做;

在应用动态规划之前,分析能否把大问题分解成小问题,分解后的每个小问题也存在最优解;如果把小问题的最优解组合秋来能得到整个问题的最优解,那么使用动态规划解决此问题;从上往下分析题目,从下往上求解题目,此为应用动态规划求解问题的第四个特点;

在应用动态规划的时候,我们每一步都可能面临若干个选择,只好把所有可能都尝试一边,比较得出最优的剪法(面试题14.剪绳子);即f(n)=max(f(i)×f(n-i));

贪婪算法:当应用贪婪算法解决问题时,每一步都可以做出一个贪婪的选择,基于这个选择,我们确定能得到最优解;为什么这样的贪婪选择能得到最优解?这是我们应用贪婪算法时都需要问的问题,需要用数学方式来证明贪婪选择的正确性;

面试题14.剪绳子https://leetcode-cn.com/problems/jian-sheng-zi-lcof/

考点:考察应聘者的抽象建模能力;应聘者需要把一个具体的场景抽象成一个能用动态规划或者贪婪算法解决的模型;

考察应聘者对动态规划和贪心算法的理解;能够灵活运用动态规划解决问题的关键是:从上到下分析问题,从下到上解决问题的能力,而灵活运用贪婪算法则需要扎实的数学基本功;

位运算:将数字用二进制表示之后,对每一位上0或者1的运算;底层的技术离不开位运算;

理解位运算的第一步是理解二进制;

位运算一共有五种:与,或,异或,左移,右移;

面试题15.二进制中1的个数https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/

考点:考察对二进制及位运算的理解;

考察分析,调试代码的能力;如果在面试过程中采用的是第一种思路,则当面试官提示输入负数将出现问题时,面试官期待我们可以找出死循环的原因并加以改进,这要求我们有一定的调试功底;

知识点:把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于把整数的二进制表示中最右边的1变成0;很多二进制的题都可以这么解决;

高质量的代码

代码的规范性:

清晰的书写;

清晰的布局;

合理的命名;(详解可搜索“命名”关键词博客)

代码的完整性:

考虑问题是否周全;

是否完成了基本功能;

输入边界值是否能得到正确的输出;

是否对各种不合规范的非法输入做出了合理的错误处理;

1.从三个方面确保代码完整性;

a.功能测试:尽量突破常规思维的限制,比如打印从1道最大的n位数;最大的三位数是999,最大的四位数是9999,那么超出int范围的应该使用long,超出long表示的范围,即任意大的数字应该使用字符串或数组来保存大数,确保不会溢出;

b.边界测试;如果基于循环,则测试循环条件是否正确?如果基于递归,那么递归终止的边界值是否正确?

c.负面测试;考虑各种可能的错误输入;所写函数除了顺利地完成要求的功能,当输入不符合要求的时候还要能做出合理的错误处理;

在写代码时,能将未来需求可能的变化考虑进去,在需求发生变化时能尽量减少代码改动的风险,那么就展现了对程序的可扩展性合可维护性的理解;

2.三种错误处理方式;

a.函数使用返回值来告知调用者是否出错;(使用不便,函数不能直接将计算结果返回给其他变量,也不能把这个函数计算的结果直接作为参数传递地给其他函数)

b.当错误发生时,设置一个全局变量;调用者可以直接把返回值赋值给其他变量或者作为参数传递给其他函数;(安全隐患:调用者容易忘记检查全局变量,在调用出错时忘记进行相应的错误处理,从而留下安全隐患)

c.异常;当函数运行出错时,抛出一个异常,还可以根据不同的出错原因定义不同的异常类型;调用者根据异常的类型就能知道出错的原因,从而做出相应的处理;(抛出异常会打乱正常的顺序,对程序的性能有较大影响)

面试题16.数值的整数次方https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/

考点:考察应聘者思维的全面性;如底数为0而指数为负数时的错误处理;

对效率要求比较高的面试官还会考察应聘者快速做乘方的能力;

面试题17.打印从1道最大的n位数https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/

考点:考察解决大数问题的能力,选择合适的数据表示方式来解决大数问题;

考虑是否会考虑用户的使用习惯;(使用PrintNumber去掉前面的0字符)

面试小提示:如果是关于n位的整数并且没有限定n的取值范围,或者输入任意大小的整数,那么这道题很有可能需要考虑大数问题;

面试题18.删除链表的节点https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/

考点:考察对链表的编程能力;

考察创新思维能力;即当我们想删除一个节点时,不一定要删除这个节点本身,可以把下一个节点的内容复制覆盖到此节点,再删除下一个节点即可;

考察思维的全面性,即删除的节点位于头部,尾部,及输入的链表只有一个节点时的特殊情况;

面试题21.调整数组顺序,使得奇数位于数组前半部分,偶数位于数组后半部分https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/

考点:考察快速思维能力;

考察对扩展性的理解,要求写出的代码具有可重用性;

代码的鲁棒性:

又称健壮性;指程序能够判断输入是否合乎规范要求,对不符合要求的输入予以合理的处理;

容错性是鲁棒性的重要体现;

提高代码的鲁棒性的有效途径是:进行防御式编程,指遇见什么地方可能会出现什么问题,并为这些可能出现的问题指定处理方式;如:打开文件发现文件不存在时,提示用户检查文件名和路径,服务器连接不上时,提示用户更换连接线路等;

最简单最实用的防御式编程:再函数入口添加代码以验证用户输入是否符合要求;如指针为空,字符串为空,数组为空等,提前考虑道并进行相应处理;

多问:如果???那么???(比如22.链表中倒数第K个节点,如果K大于链表节点数怎么办)?提前思考能帮助发现潜在问题并解决,从而写出鲁棒性高的软件或代码;

面试题22.链表中倒数第K个节点https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/

考点:考察对链表的理解;

考察所写代码的鲁棒性,鲁棒性是解决此题的关键;例如K过大,head为空,K=0;

知识点:当用一个指针遍历链表不能解决问题时,可以尝试两个指针遍历链表,比如快慢指针或者首位指针;

面试题23.链表中环的入口

考点:对链表的理解;

代码的鲁棒性;

分析问题的能力;把一个问题分解成几个简单的步骤,是一种常用的解决复杂问题的方法;环入口此题可分解为三个步骤:判断有无环并且找出环中任意一个节点;得到环中节点的个数;找到环的入口;

面试题24.反转链表https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof

考点:与链表相关的问题总有大量的指针操作,而指针操作的代码容易出催哦;为避免出错,变成前先进性全面分析,写出鲁棒的代码;

考察对链表,指针的编程能力;

考察思维的全面性及代码的鲁棒性;

面试题25.合并两个排序的链表https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/

考点:分析问题的能力,解决此问题需要大量指针操作;

考察鲁棒性,应当考虑指针的安全,如处理空指针;

面试题26.树的子结构https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/

考点:先序遍历递归搜索;分两部分:1.搜索判断起点    2.开始判断;

考察对空指针的检查,即边界检查;

知识点:在计算机内表示小数(float和double)时有误差,只能判断它们之差的绝对值是不是在一个很小的范围内,如果两个数相差很小,则可以认为它们相等;

考察对递归的编程能力;

考察代码的鲁棒性,含有大量指针操作,应采用防御式编程的方式,每次访问指针地址之前都要考虑此指针是否为nullptr;

解决面试题的思路

画图让抽象问题形象化

面试题27.二叉树的镜像https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/comments/

考点:考察对二叉树的理解,实质上用树的遍历算法来解决;

考察思维能力;树的镜像是抽象概念,我们需要在短时间内想清楚求镜像的步骤并转化成代码;使用画图的方法将抽象问题形象化,有助于快速找到解题思路;

面试题28.对称的二叉树https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/

考点:对二叉树的理解,实质上用树的遍历算法来解决;

考察思维能力,树的对称是抽象概念,需要在短时间内想清楚对称的步骤并转换为代码;可以画图把抽象问题形象化,有助快速找i到解题思路;

面试题29.顺时针打印矩阵

面试题30.包含min函数的栈https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/

考点:考察分析复杂问题的思维能力,辅助栈;

考察对栈的理解和编程能力;

面试题31.从上到下打印二叉树https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/

考点:对层序遍历的编程和思考能力;

考察对队列Queue继承于LinkedList,ArrayList的声明规则;

考察思维能力,二叉树从上到下可借助队列;

考察对二叉树及队列的理解;

知识点:从上到下按层遍历二叉树,从本质上来说就是广度优先遍历二叉树;树是图的一种特殊退化形式;

面试题32.分行从上到下打印二叉树https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/

考点:对层序遍历的编程和思考能力;

考察对队列Queue继承于LinkedList,ArrayList的声明规则;

考察思维能力,二叉树从上到下可借助队列;

考察对二叉树及队列的理解;

面试题32.之字形打印二叉树https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/solution/

考点:双端队列LinkedList的使用,addLast,addfirst,以及使用每层队列的size()%2==1来确定奇偶层数;

考点:对层序遍历的编程和思考能力;

考察对队列Queue继承于LinkedList,ArrayList的声明规则;

考察思维能力,二叉树从上到下可借助队列;

考察对二叉树及队列的理解;

面试题33.二叉搜索树的后序遍历https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/

Java中的append( )方法其实是创建了一个新的数组,扩大了bai长度,将需要添加的字符串给复制到这个新的数组中。

JAVA中Stringbuffer有append( )方法:

而Stringbuffer是动态字符串数组,append( )是往动态字符串数组添加,跟“xxxx”+“yyyy”相当‘+’号。

跟String不同的是Stringbuffer是放一起的,String1+String2和Stringbuffer1.append(“yyyy”)虽然打印效果一样,但在内存中表示却不一样、

String1+String2 存在于不同的两个地址内存,Stringbuffer1.append(Stringbuffer2)放再一起。

StringBuffer是线程安全的,多用于多线程。

使用异或运算实现两数交换

~无关风月~ 2016-01-01 23:51:24 13830 收藏 20
文章标签: 算法
通常我们实现两数交换不得不引入一个临时变量temp作为媒介,而使用异或运算也能实现同样的功能,甚至无需使用临时变量。

这是一个通常的做法:

int main(){
int a=1,b=2,temp;
temp=a;
a=b;
b=temp;
printf(“%d,%d\n”,a,b);
return 0;
}

关于异或(Exclusive OR)
Wikipedia解释

在数位逻辑中,逻辑算符互斥或闸(exclusive or)是对两个运算元的一种逻辑分析类型,符号为XOR或EOR或⊕。与一般的或闸OR不同,当两两数值相同为真..而有一数值不同时为否..

两个运算元(命题):A与B的异或一般写成A异或B,或者写成A \quad \mathrm{XOR} \quad B、A \oplus B、A \neq B等等。在C语言中,写作A^B。

两个相同数的异或结果为0
异或真值表
A B A^B
0 0 0
0 1 1
1 0 1
1 1 0
异或的小例子

假设a为二进制数01,b为二进制数10,a^b的结果为11并将其存储在变量c中,经过反复的测试,于是发现以下的规律:

11^01=10
11^10=01
c^a=b;
c^b=a;
可以很惊奇的发现,将两数异或的结果与其中一数再进行异或,可以得到另一个数。
原理很简单,两数异或的结果保存了两个数上每一个二进制位不同或相同的信息,如果相应的二进制位不同,就标志为1,如果相同,则标志为0。
由于任意一个二进制位与1异或有这样一个特性:

0^1=1
1^1=0
即与1异或后,都将自己转换成相反的位

这样,我们就使用异或运算交换了两数

12(001100)

^ 34(100010)

——————-

101110

101110 ^ 001100=100010

101100 ^ 100010=001100

int main(){
int a=12,b=34,temp;
printf(“Original result: a=%d,b=%d\n”,a,b);
temp=a^b;
a=temp^a;
b=temp^b;
printf(“Transformed result: a=%d,b=%d\n”,a,b);
return 0;
}
result //
Original result: a=12,b=34
Transformed result: a=34,b=12

但是使用这种方法似乎与使用临时变量没有什么区别?

其实不然,通过简单分析可以发现临时变量的值在整个过程中并没有发生变化,因此也可以无需设置临时变量。

a=a^b^a;
b=a^b^b;
于是可以使用第三种方法,将a设置为临时变量

a=a^b;
b=b^a;
a=b^a;
还可以写得更简洁一点:
a^=b^=a^=b;

还可以通过加减实现两数互换:

a=a+b

b=a-b;

a=a-b;

前提是a+b的值不能溢出。

测试程序如下:

int main()
{
int a = 4, b = 5;
printf(“a=%d b=%d\n”, a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf(“a=%d b=%d\n”, a, b);
}

运行结果:
[root@test cs]# ./a.out
a=4 b=5
a=5 b=4
[ 注意:当a和b相等时,该方法不适用]

其他方法:

public void change1(int a, int b){
System.out.println(“change1交换之前\ta:”+a+”\tb:”+b);
a = a + b – (b = a);
System.out.println(“change1交换之后\ta:”+a+”\tb:”+b);
}
public void change2(int a, int b){//C/C++ 不适用
System.out.println(“change2交换之前\ta:”+a+”\tb:”+b);
b = a + (a = b)*0;
System.out.println(“change2交换之后\ta:”+a+”\tb:”+b);
}
public void change4(int a, int b){
System.out.println(“change4交换之前\ta:”+a+”\tb:”+b);
a = a * b;
b = a / b;
a = a / b;
System.out.println(“change4交换之后\ta:”+a+”\tb:”+b);
}

0 21

java基本数据类型和引用数据类型的区别

基本数据类型
boolean,byte,short,char,int,float,long,double。
引用数据类型
类类型,接口类型,数组类型

注意:
基本数据类型的数据不能包含其他类型的数据,也不能调用任何方法。
引用数据类型可以调用方法,可以包含其他类型的数据。
引用类型变量接收的只能是对象,基本类型变量接受的只能是一些比较简单的数据。

面向对象的思维方式

面向对象是基于万物皆对象这个哲学观点. 把一个对象抽象成类,具体上就是把一个对象的静态特征和动态特征抽象成属性和方法,也就是把一类事物的算法和数据结构封装在一个类之中,程序就是多个对象和互相之间的通信组成的.

面向对象具有封装性,继承性,多态性.封装隐蔽了对象内部不需要暴露的细节,使得内部细节的变动跟外界脱离,只依靠接口进行通信.封装性降低了编程的复杂性. 通过继承,使得新建一个类变得容易,一个类从派生类那里获得其非私有的方法和公用属性的繁琐工作交给了编译器. 而 继承和实现接口和运行时的类型绑定机制 所产生的多态,使得不同的类所产生的对象能够对相同的消息作出不同的反应,极大地提高了代码的通用性.

总之,面向对象的特性提高了大型程序的重用性和可维护性.

创建类的方法

对象和对象的引用
定义类的方法

class类名
{
属性;
方法;
}

属性也叫成员变量,主要用于描述类的状态
方法也叫成员方法,主要用于描述类的行为

生成对象的方法

格式:类名 对象名=new 类名() 例如:Dog dog = new Dog(); 创建一个Dog的引用 创建一个Dog的对象
类和对象的关系 类是抽象,而对象是具体的。

Dog().jump();

函数的重载和构造函数的作用

重载的表达

class A{
 void funA(){
  System.out.println("没有参数的funA函数");
 }
 void funA(int i){
  System.out.println("有参数的funA函数");
 }
 void funA(int i,double d){
  System.out.println("拥有两个参数的funA函数");
 }
}

class Test{
 public static void main(String args[]){
  A a = new A();
  a.funA();
  a.funA(1,2.0);
  }
}

什么叫函数的重载呢?

class Person{
 String name;
 int age;
 Person(){
  System.out.prinltn("Person的无参数构造函数");
 }
 Person(String name,int age){
  this.name=name;
  this.age=age;
  System.out.println("Person有参数的构造函数");
 }
 
 void eat(){
  System.out.println("定义吃饭的方法");
 }
}

class Student extends Person{
 //子类继承父类
 Student(){
  //父类
  super();
  System.out.println("Student的无参数构造函数");
 }
 Student(String name,int age,int id){
  super(name,age);
  this.id=id;
 }
}

class Test{
 public static void main(String args[]){
  Student student = new Student();
 }
}

虽然子类不能继承父类的构造函数,但我能用super()来调用父类的构造函数。
调用子类的构造函数,一定会调用父类的构造的函数。

多态和对象的转型

抽象类和接口的作用以及区别

IO流

IO流序列化的意义及API

Io流之节点流与处理流

线程的简单控制方法

中断线程
Thread.sleep();
Thread.yield();//让出自己正在使用的CPU

设置线程的优先级
getPriority();
setPriority();

JAVA类集框架

什么是对象的内容相等

对象的内容相等需要符合两个条件:
1.对象的类型相同(可以使用instanceof操作符进行比较);
2.两个对象的成员变量的值完全相同;

0 16

面向对象(上)

(1)Student类中包含姓名、成绩两个属性。

(2)分别给这两个属性定义两个方法,一个用于获取值,一个用于设置值。

(3)Stduent类中定义一个无参的构造方法和一个用于接收两个参数的构造方法,两个参数分别为姓名和成绩属性赋值。
(4)在测试类中创建两个 Student 对象,一个使用无参的构造方法,然后调用方法给姓名和成绩赋值,另一个使用有参的构造方法,在构造方法中给姓名和成绩赋值。

class Student{
    String name;
    int mark;
    public void Setname(String name){
        this.name=name;
    }
    public void Setmark(int mark){
        this.mark=mark;
    }
    public String Getname(){
        return name;
    }
    public int Getmark(){
        return mark;
    }
    public Student(){
        System.out.println("无参的构造方法被调用");
    }
    public Student(String name,int mark){
        System.out.println("有参的构造方法被调用");
        this.name=name;
        this.mark=mark;
    }
}
class Scratch{
    public static void main(String[]args){
        Student S1=new Student();
        S1.Setmark(90);
        S1.Setname("张三");
        Student S2=new Student("李华",95);
        System.out.println(S1.name);
        System.out.println(S1.mark);
        System.out.println(S2.Getname());
        System.out.println(S2.Getmark());
    }

}
//斐波那契数列递归形式;
class Scratch{
    static int Fbnq(int n){
        if(n==1||n==2)return 1;
        if(n==0)    return 0;
        return Fbnq(n-1)+Fbnq(n-2);
    }
    public static void main(String[]args){
        //斐波那契数列;
            System.out.println(Scratch.Fbnq(6));
        }
    }