java笔记
1、如果某个方法为静态的,那么它的行为不具有多态性
public class demo5 {
public static void main(String[] args) {
Father father = new Son();
father.staticMethod();
father.normalMethod();
}
}class Father {
public static void staticMethod() {
System.out.println("Father static method");
}
public void normalMethod() {
System.out.println("Father normal method");
}
}class Son extends Father{
public static void staticMethod() {
System.out.println("Son static method");
}
public void normalMethod() {
System.out.println("son normal method");
}
}
结果
Father static method
son normal method
2、在父类构造函数内部调用具有多态行为的方法将导致无法预测的结果,因为此时子类对象还没初始化,此时调用子类方法不会得到我们想要的结果
public class demo6 {
public static void main(String[] args) {
new Son1(5);
}
}class Father1 {
Father1(){
System.out.println("father before draw");
draw();//此处调用具有多态行为的方法
System.out.println("father after draw");
}
void draw(){
System.out.println("father.draw");
}
}class Son1 extends Father1{
private int a=8;
Son1(int i){
a=i;
System.out.println("son construstor="+a);
}
void draw() {
System.out.println("son.draw="+a);
}
}
father before draw
son.draw=0
father after draw
son construstor=5
执行顺序为:
(1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0;
(2)调用基类构造函数。从根开始递归下去,因为多态性此时调用子类覆盖后的draw()方法(要在调用Son构造函数之前调用),由于步骤1的缘故,我们此时会发现radius的值为0;
(3)按声明顺序调用成员的初始化方法;
(4)最后调用子类的构造函数。
3、泛型只在编译阶段有效
AyyayList<String> a = new ArrayList<String>();
ArrayList b = new ArrayList();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(a == b); //true
上面程序的输出结果为true。所有反射的操作都是在运行时的,既然为true,就证明了编译之后,程序会采取去泛型化的措施,也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
4、组合和继承
组合就是创建一个新类去调用已经创建并且调试好的类,那么这个新类就可以把它叫做是一个组合
public class People { private String name; private int age; public void setName(String name){ this.name = name; } public String getName(){ return this.name; } public int getAge(){ return this.age; } public void setAge(int age){ this.age = age; } }
这里有一个People类,另外,我有一个新类Student,我要在Student中用到People,那么我就在新类里创建People对象,这就叫组合
class Student { People people = new People(); }
5、浅复制和深复制
Object类有一个clone方法,但是这个方法是protected,所以不能在类外访问,我们可以对该方法进行覆盖,需要被复制的类要实现Cloneable接口并覆盖clone方法将修饰符改为public
public class User implements Cloneable{
private String name;
private Integer age;public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}public Integer getAge() {
return age;
}public void setAge(Integer age) {
this.age = age;
}@Override
public User clone() throws CloneNotSupportedException {
return (User)super.clone();
}
}
测试类
public static void main(String[] args) {
User u1 = new User();
u1.setAge(32);
try {
User u2 =u1.clone();
u1.setAge(12);
System.out.println("u1:"+u1.getAge());
System.out.println("u2:"+u2.getAge());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
打印结果为
u1:12
u2:32
可见复制出来的对象是一个全新的对象,原对象的改变不会对新对象产生影响
但是存在另一种情况,那就是User类中引用了一个类Address
public class User implements Cloneable{
private String name;
private Integer age;
private Address addr;public Address getAddr() {
return addr;
}public void setAddr(Address addr) {
this.addr = addr;
}public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}public Integer getAge() {
return age;
}public void setAge(Integer age) {
this.age = age;
}@Override
public User clone() throws CloneNotSupportedException {
return (User)super.clone();
}
}
public class Address {
private String address;public Address(String address) {
super();
this.address = address;
}public Address() {
super();
}public String getAddress() {
return address;
}public void setAddress(String address) {
this.address = address;
}
}
public static void main(String[] args) {
User u1 = new User();
u1.setAge(32);
Address addr = new Address("北京");
u1.setAddr(addr);
try {
User u2 =u1.clone();
u1.setAge(12);
u1.getAddr().setAddress("杭州");
System.out.println("u1:"+u1.getAge()+"----"+"address:"+u1.getAddr().getAddress());
System.out.println("u2:"+u2.getAge()+"----"+"address:"+u2.getAddr().getAddress());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
此时的打印结果为
u1:12----address:杭州
u2:32----address:杭州
我们看到虽然复制出来的新类的age字段不受原来的类的影响,但是Address对象的地址信息却随着原类的改变而改变了。此时前面的浅复制就达不到预期效果。我们需要用深复制来解决此问题
Address类实现 Cloneable接口并重载clone方法
public class Address implements Cloneable{
private String address;public Address(String address) {
super();
this.address = address;
}public Address() {
super();
}public String getAddress() {
return address;
}public void setAddress(String address) {
this.address = address;
}@Override
public Address clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return (Address)super.clone();
}
}
调整User类的clone方法
@Override
public User clone() throws CloneNotSupportedException {
User user = (User)super.clone();
user.addr=(Address) addr.clone();
return user;
}
测试类不变,打印结果为
u1:12----address:杭州
u2:32----address:北京
现在地址和年龄都独立出去了,不会随原类的变化而变化
总结:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,
没有对引用指向的对象进行拷贝。
而深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。
区别就在于是否对 对象中的引用变量所指向的对象进行拷贝。
6、mybatis的#()和$()的区别和使用场景
#{}是经过预编译的,是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入,如判断一下输入的参数的长度是否正常(注入语句一般很长),更精确的过滤则可以查询一下输入的参数是否在预期的参数集合中。
7、BigDecimal类型运用和几个坑
BigDecimal b1 = new BigDecimal(2.532);
BigDecimal b2 = new BigDecimal(1.400);
System.out.println("b1:"+b1);
System.out.println("b2:"+b2);
(1)将double或float的小数转为BigDecimal时,会出现如下精度丢失问题,上述结果打印出来如下:
b1:2.532000000000000028421709430404007434844970703125
b2:1.399999999999999911182158029987476766109466552734375
解决方式一:通过字符串来转BigDecimal
BigDecimal b1 = new BigDecimal("2.532");
BigDecimal b2 = new BigDecimal("1.400");
System.out.println("b1:"+b1);
System.out.println("b2:"+b2);
得到的结果为:
b1:2.532
b2:1.400
解决方式二:
BigDecimal b3 = BigDecimal.valueOf(2.532);
BigDecimal b4 = BigDecimal.valueOf(1.400);
System.out.println("b3:"+b3);
System.out.println("b4:"+b4);
打印结果如下
b3:2.532
b4:1.4
(2)再来说BigDecimal的加减乘除运算
加法
BigDecimal add = b1.add(b2);
System.out.println("加法:"+add);
减法
BigDecimal subtract = b1.subtract(b2);
System.out.println("减法:"+subtract);
乘法
BigDecimal multiply = b1.multiply(b2);
System.out.println("乘法:"+multiply);
除法,除法要考虑能否除尽的情况,无法除尽必需指定保留小数的位数
BigDecimal divide= b1.divide(b2, 2,BigDecimal.ROUND_HALF_UP); //保留两位小数,第三个参数指定四舍五入方式,不填则起不到保留小数位的作用
System.out.println("除法:"+divide);
加减乘除运算结果为
加法:3.932
减法:1.132
乘法:3.544800
除法:1.809
(3)BigDecimal比较大小用compareto方法
(4)BigDecimal保留小数位
方法一:setScale()方法,注意,此处必需指定四舍五入的类型,否则会报 Rounding necessary错误。
BigDecimal setScale = b1.setScale(2,BigDecimal.ROUND_HALF_UP);
System.out.println("scale:"+setScale);
方法二:用除法来保留小数位,注意,此处也需要指定四舍五入类型,否则起不到保留小数位的作用。
BigDecimal divide2 = b1.divide(new BigDecimal(1),2,BigDecimal.ROUND_HALF_UP);
System.out.println("divide2:"+divide2);
8、linux常用命令
查询日志后10行数据
tail -n 10 app.log
根据关键字查询日志
grep ‘关键字’ 文件名
解压tar.bz2格式文件
tar xjvf gcc-4.9.3.tar.bz2
查看防火墙状:
service iptables status
1) 重启后生效
开启: chkconfig iptables on
关闭: chkconfig iptables off
2) 即时生效,重启后失效
开启: service iptables start
关闭: service iptables stop
查看mysql启动状态
service mysqld status
启动tomcat并打开日志
./startup.sh & tail -f ../logs/catalina.out
9、fail-fast机制
它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制
产生原因:
有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。线程A继续遍历执行next方法时,通告checkForComodification方法发现expectedModCount = N ,而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 异常,从而产生fail-fast机制
解决方法:
方法一:
在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
方法二:使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案