前言:
经历这么久的考研复习,回头发现其他基础知识忘得厉害,而且,感觉脑子里于JAVA与C/C++的语法都弄混了..Java与C也有很多不同的地方.准备用些时间把基础部分整理一次,着重整理与C不同之处.省的下次遗忘.
首先声明的是:这仅仅是一份针对我个人易混淆与易忘记的知识点,不具有普遍性.
参考书目<Java核心技术第10版>,因为我看到这个书中有不少于C++的比较,正好是我现在需要的.
不知为何,我总有种感觉,我还会写 一版参考< think in Java >的… ..立帖为证…
Java基本程序设计结构
数据类型
注意java没有unsigned形式的int,long,short,byte类型.
三个特殊浮点数值:(
- 正无穷
- 负无穷
- NaN(不是一个数)
正整数除以0的结果是正无穷.整数被0除会得到一个异常.浮点数被0除得到无穷大或NaN.
0/0或者负数的平方根结果是NaN.
final指示常量,常量名全大写.
如果一个类标记为strictfp,这个类中的所有方法都要使用严格的浮点计算.
1 2
public static strictfp void main(String args) //此时main方法里采用严格的浮点计算
所有的关键字都是小写的.只要有大写就不是关键字了.
short,byte,long在运算的时候会先转化成int
1 2 3 4
byte b1 = 3,b2 = 4,b; b = b1 + b2; b = 3 + 4; //不要误以为是第三句会出错,错的是第二句.
运算符
+=运算符是右结合的.所以
1
a += b += c等价于a += (b += c)
Java没有逗号运算符.
Java没有运算符重载功能.
字符串
String类没有提供修改字符串的方法.
1 2 3 4
String greeting = "Hello"; //如果想将greeting修改为"Help"是不能直接像C那样直接修改最后两个字符的. //首先需要提取字符,然后在拼接上替换的字符串 greeting = greeting.substring(0,3)+"p!";
原始字符串放置在堆中,Java将自动进行垃圾回收.
一定不要使用 == 检测两个字符串是否相等. == 运算符只能确定两个字符串是否放置在同一位置上,有可能将内容相同的字符串拷贝放置在不同位置上.
空串也是一个Java对象,有自己的长度和内容.
输入输出
要想通过控制台输入,先构造Scanner对象,并与”标准输入流”System.in关联.
1 2 3 4 5
Scanner in = new Scanner(System.in); //之后就可以各种输入操作了. String name = in.nextLine();//读取一行 int age = in.nextInt();//读取下一个整数 //其余类似...
文件输入输出
1 2 3 4 5
// 文件操作与控制台类似,感觉与C也类似,不过还是整理一下 Scanner in = new Scanner(Paths.get("c:\\mydocument\\myfile.txt"),"utf-8"); //记住路径要多加一个斜杠 //要想写入文件,用PrintWrite对象替换Scanner对象就好.
控制流程
在循环中,检测两个浮点数是否相等要格外小心,下面的for循环可能永远不会结束
1 2
for(double x =10;x!=10;x+=0.1); //因为0.1不能被精确的表示
与C++不同,Java提供了一种带标签的break语句,用于跳出多重嵌套的循环语句.需要注意的是,只能跳出语句块,不能跳出语句块
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//用法: read_data: while(...){ for(...){ if(...) break read_data; } } //可以将标签应用到任何语句中甚至可以应用到if语句或者块语句中. label:{ ... if(...) break label; }
JDK7以后,seitch语句可以用字符串类型了.需要注意一下.
优先考虑for循环.
数组
数组这儿,比较特殊的就算是多了那个for each循环了吧,也没啥好说的.针对for each说一点,关于二维数组的,因为for each是按照以为数组处理的,要想访问二维数组a的所有值,要使用嵌套才可以.
1 2 3 4
for(double[] row:a) for(double value:row){ ... }
小提示:使用Array类的toString()方法,很方便打印数组所有值.
1
Arrays.toString(a);
Java与C++数组在堆栈上有很大不同,但基本上与分配在堆上的数组指针一样.
1 2 3 4 5
int[] a = new int[100];//java //不同于 int a[100];//C++ //而等同于 int* a = new int[100];//C++
Java中的[ ]运算符被与定义为检车数组边界,而且没有指针运算,既不能通过a+1 得到数组的下一个元素.
更新于2018-12-29
对象与类
对象与对象变量
- 任何对象变量的值都是对存储在另一个地方的一个对象的引用.
- 可以显式的将一个对象变量设为NULL,但如果将一个方法应用于一个值为NULL的对象上,就会产生运行时错误.
自定义类
所有的Java对象都是在堆中构造的,构造器总是伴随着new操作符一起使用.必须有new,这一点与C++不同.
每一个方法中,this表示隐式参数.
不要编写返回引用可变对象的访问器方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Employee{ private Date hireDay; ... public Date getHireDay(){ return hrieDay;//这样是不好的 } } //出错的原因很巧妙,这样Date对象是可变的,破坏了封装性. //如果需要返回一个可变对象的引用,应该首先对他进行克隆.对象克隆是存放在另一个位置的对象副本 class Employee{ private Date hireDay; ... public Date getHireDay(){ return (Date)hrieDay.clone();//这样是可以的 } }
final实例域.构造对象时必须在初始化的域,后面的操作中不能修改.
静态域与静态方法
- 域定义为static,每个类中只有一个这样的域.
- 静态常量:
public static final doubel PI = 3.14159;
- 静态方法:不能向对象实施操作,换句话说,没有隐式参数,再换句话说,属于类且不属于类的对象的函数.
- 使用静态工厂方法来构造对象…
方法参数
- Java都是按值调用.也就是说,方法得到的是参数值的拷贝,也就是说,不能修改传递给他的任何参数的值.而对象引用作为参数就不同了,不过,不要误解为传对象就是类似C++的传引用,实际上,传对象还是传值操作.
- 一个方法不能让对象参数引用新对象
- 方法重载必须在一个类中.
对象构造
在很小的构造器中关于参数名的问题.
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public Employee(String n,double s){ name = n; salary = s; } //上面那样写可以,但是有一个缺陷,只有阅读了代码才知道参数n的含义. //可以在每个参数前加一个前缀'a' public Employee(String aName,double aSalary){ name = aName; salary = aSalary; } //也可以使用隐式参数this public Employee(String name,double salary){ this.name = name; this.salary = salary; } //其实在Java中this引用等价于C++中的this指针
包
- import不仅可以导入类,也可以导入静态方法和静态域的功能.
更新于2018-12-30
继承
类,超类,子类
Java中,所有的继承都是公有继承.
Java中调用超类是super关键字,在C++中是::
super不是一个变量的引用,所以不能将super赋给另一个对象变量,他只是一个指示编译器调用超类方法的特殊关键字.
this关键字两个用途:
- 一是调用隐式参数
- 二是调用该类其他的构造器
super关键字也有两个用途:
- 一是调用超类的方法
- 二是调用超类的构造器
在运行时能够自动选择调用哪个方法的现象叫做动态绑定.不需要向C++那样声明为virtual,动态绑定是默认的处理方式.如果不希望一个方法具有虚拟特征,可以标记为final.
Java不支持多继承.
“is-a”规则的另一种表述法是置换法则,它表明出现超类对象的任何地方都可以用子类对象置换.例如,可以将一个子类对象赋给超类变量.然而,不能将超类引用赋给子类变量.
所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法,这个过程称为重载解析.
每次调用方法都要进行搜索,时间开销相当大,因此,虚拟机预先为每个类创建了一个方法表.
在覆盖一个方法的时候,子类方法不能低于超类方法的可见性,超类是public,子类必须是public.
将一个子类的引用赋值给超类变量是允许的.但是 将一个超类引用赋值给子类变量,必须进行类型转换.
用abstract声明为抽象类后,就不能创建这个类的对象.
Object:所有类的超类
- equals方法:用于检测一个对象是否等于另一个对象.
- 为自定义的每一个类增加一个toString()方法 .
泛型数组列表
- Java允许在运行时确定数组大小.
- ArrayList类似于C++的vector模板.
对象包装器与自动装箱
- 由于包装器引用可以为NULL,所以不自动装箱有可能抛出一个NullPOinterException异常.
- 如果一个表达式混合使用Integer和Double类型,Integer值就会拆箱,提升为Double,在装箱为Double.
- 装箱时编译器认可的,并不是虚拟机.
参数数量可变的方法
我目前的水平感觉不会写这样的方法呀,用到了在补充吧,先知道有这么个东西.就是在形式参数的括号里加上省略号(…),就可以表明这个方法可以接收任意数量的对象.
-
1 2 3 4 5 6 7
//自动装箱功能会将他们转化为对象 public static double max(double... values){ double largest = Double.NEGATIVE_INFINITY; for(double v: values) if(v>largest) largest = v; return largest; }
枚举类
比较两个枚举类时,不需要调用equals,直接使用==即可.
贴个demo看看吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
package enums; import java.util.*; public class EnumTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) "); String input = in.next().toUpperCase(); Size size = Enum.valueOf(Size.class, input); System.out.println("size=" + size); System.out.println("abbreviation=" + size.getAbbreviation()); if (size == Size.EXTRA_LARGE) System.out.println("Good job--you paid attention to the _."); } } enum Size { SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); private Size(String abbreviation) { this.abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; } private String abbreviation; }
反射
能够分析类能力的程序称为反射.
什么是反射
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。
反射能做什么
反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
反射的具体实现
|
|
- 得到class的三种方式
|
|
需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true.
通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
查阅 API 可以看到 Class 有很多方法:
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。我们通过一个例子来综合演示上面的方法:
|
|
根据反射获取父类属性
父类 Parent.java
|
|
子类 Son.java
|
|
测试类
|
|
通过执行上述代码,我们获得了父类的私有属性值,这里要注意的是直接通过反射获取子类的对象是不能得到父类的属性值的,必须根据反射获得的子类 Class 对象在调用 getSuperclass() 方法获取父类对象,然后在通过父类对象去获取父类的属性值。
反射总结
灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的 AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!
注:反射部分参考:https://www.cnblogs.com/ysocean/p/6516248.html
更新于2019-1-1
接口,Lambda表达式于内部类
接口
接口中的所有方法自动属于public,因此,在接口中声明方法的时候,不提供关键字public.
接口决不能含有实例域.可以将接口看成没有实例域的抽象类.
Java中,一个类实现了某接口,则必须实现该接口中的所有方法么?”这句话其实是不准确的,因为我们还没有考虑到抽象类.抽象类实现某个接口,可以不实现所有接口的方法,可以由它的子类实现。而普通类即非抽象类则必须实现接口里的全部方法。 实现某个接口必须覆写其中的所有方法,当然也可以是一个空的实现(方法体为空没有任何作用),但是这个类必须是非抽象类.
如果一个类同时实现了两个接口,而这两个接口有相同的方法,类会怎么继承呢??只需实现一个即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
//两个接口 interface in1{ public void fun1(); public void fun2(); } interface in2{ public void fun1(); public void fun3(); } //重名实现一次就可以了 class fun implements in1,in2{ public void fun3() { // TODO Auto-generated method stub } public void fun1() { // TODO Auto-generated method stub } public void fun2() { // TODO Auto-generated method stub } }
默认的对象克隆属于浅拷贝,并没有克隆对象中引用的其他对象.
- 如果原对象和克隆的对象共享的子对象是不可变的,那么这个默认的克隆就是安全的.
- 不过,通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝.同时克隆所有子对象.
必须当心子对象的克隆.
Lambda表达式
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误.
如果lambda表达式只在某些分支里发挥一个值,而在另外一些分支里不返回值,这是不合法的.
1
(int x) ->{if(x>=0) return 1;}
lambda表达式大概率我不会用,先写这些吧,如果需要用在好好看看..
内部类
- 使用内部类的原因:
- 内部类可以访问该类定义所在的作用域中的数据,包括是有的数据.
- 内部类可以对同一个包中的其他类隐藏起来.
- 当想要定义一个回调函数且不想编写大量代码时,可以使用匿名内部类比较快捷.
- 内部类是一种编译器现象,与虚拟机无关.
- 局部类的方法只可以引用定义为final的局部变量.
- 关于内部类重新立贴整理,我感觉这块有点乱,学的不是很好.
更新于2019-1-2
异常,断言和日志
处理错误
如果超类方法没有抛出任何受查异常,那么子类也不能抛出任何受查异常.
在C++中,如果函数抛出的异常没有在throw列表中,函数可能抛出任何异常,但是在JAVA中,没有throws说明符的方法将不会抛出任何受查异常.
捕获多个异常的时候,异常变量隐藏为final类型.
建议解耦合 try/catch 和 try/finally 语句块.这样可提高代码清晰度
1 2 3 4 5 6 7 8 9 10 11 12 13
//例如 //内层的try语句块只有一个职责,就是确保关闭输入流.外层的try语句块也只有一个职责.就是确保报告出现的错误. InputStream in = ...; try{ try{ }finally{ in.close(); } } catch(IOException e){ }
finally子句包含return语句时,会出现意想不到的结果.假设利用return语句从try语句块中 退出,在方法返回前,finally子句的内容将被执行.如果finally子句中也有一个return语句,这个返回值会覆盖原始的返回值.
1 2 3 4 5 6 7 8 9 10 11
public static int f(int n){ try{ int r = n*n; return r; } finally{ if(n==2) return 0; } } //如果调用f(2),try语句块的运算结果是r = 4,并执行return语句.但是,在返回前需要执行finally语句,finally子句使得返回值为0,这个返回值覆盖了原来的返回值4.
断言
- 断言失败是致命的,不可恢复的错误.
- 断言检查只用于开发和测试阶段.
- 我觉得我用不上……
日志
- 日志级别就不写了,记得之前看安卓的时候用过日志打印,就是在打印测试的时候不用println函数选用日志,这是目前对日志的理解.
2019-1-3
泛型程序设计
- 泛型这个东西我不知道该说啥,缺少具体实践经验,没有写过泛型代码.只能在这先写上这个分类,以后再补充.
2019-1-4
并发
线程
- 一个程序执行多个任务,每个任务是一个线程.
- 多进程与多线程的区别是
- 每个进程都拥有自己的一整套变量,而线程则共享数据.
- 如果线程被阻塞,就无法检测阻塞状态.
- 感觉泛型,集合,并发这一块没有具体的例子好难整理啊,还是要案例驱动才好,就这样吧,用案例驱动在整理.