有关java语法的一些细节(与c++比较)

本文记录一些javase的细节,多与c++进行比较。

 

 

java8种基本类型

数据类型名称 占用字节 默认值 包装类
byte(字节型) 1 0 Byte
short(短整型) 2 0 Short
char(字符型) 2 \u0000 Character
int(整形) 4 0 Integer
long(长整形) 8 0L Long
float(单精度浮点型) 4 0.0f Float
double(双精度浮点数) 8 0.0d Double
boolean(布尔值) 1bit(JVM) false Boolean

 


 

与c++不同,java不支持方法参数的默认值设置,因此若有此需要,可以使用重载的方式去实现,如:

public class Test {
    public int func(int a, String b){
        //TODO
    }

    public int func(){
        return func(0,new String("defult"));
    }
...
}

java中类型转换都是动态进行的,类似于c++的std::dynamic_cast。在进行不安全的转换时会抛出异常。


java中char默认占两个字节(默认编码格式为UTF-16)。


 

java中的abstract关键字,若修饰方法则类似于c++中的virtual,修饰类则与c++中的纯虚类类似,被abstract修饰的类可以有普通方法,需注意,若一个类有abstract方法,则该类必须被abstract修饰。

java中的final关键字,在修饰函数和类时和c++中的final效果一致,防止子类重写和继承。但是java中final可修饰变量,作用类似于c++的const

java中的interface接口类似于c++的纯虚类,但是Java中有纯虚类abstract,但是interface支持多继承,即一个类实现多个接口,但java中无法一个类继承多个父类。接口可以有成员,但是必须修饰为                 public static final,接口的方法中只能是抽象方法(需注意,java8以后,接口可以有默认方法(被default修饰的方法),也可以有静态方法)


 

对于java中的父类继承和接口实现的区别:继承的关系是“是不是”,而实现的关系是“有没有”,比如‘大雁’类,其继承于父类‘鸟’类,实现了接口‘翅膀’

 


java不支持运算符重载,在进行一些比较的时候应使用方法而非运算符。例如比较两个String内容是否相同应使用equals方法而非直接==,否则比较的是两者栈中的地址值。


 java不允许在三元条件运算符中进行赋值操作,如 return (f[x]==x)?x:f[x]=find(f[x]); 该句无法通过编译,需要对f[x]=find(f[x])外层加括号,而c++是支持这种形式的。


 

java深拷贝方法

  • 重写对象即对象成员的clone方法
  • 利用序列化反序列化来深拷贝
protected Son deepClone() throws IOException, ClassNotFoundException {
   
      Son son=null;
      //在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中
      //默认创建一个大小为32的缓冲区
      ByteArrayOutputStream byOut=new ByteArrayOutputStream();
      //对象的序列化输出
      ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输
      outputStream.writeObject(this);  //将当前student对象写入字节数组中
 
      //在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区
      ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建
      ObjectInputStream inputStream=new ObjectInputStream(byIn);
      son=(Son) inputStream.readObject(); //从字节数组中读取
      return  son;
}
View Code
  • 通过org.apache.commons中的工具类BeanUtilsPropertyUtils进行对象复制。

java中所有对象都继承自顶级类Object,Object中存在一些通用方法可以被重写,如toString(),hashCode(),equals(),还有一些不可重写方法如wait,notify等和synchronized相关。

需注意,一般对POJO来说,需要重写toString(),hashCode(),equals(),重写toString是为了方便打印调试;重写hashCode是为了为了配合基于散列的集合一起正常运行,如HashSet,Map等;重写equals是为了方便根据需求判断对象是否相等,同时也避免了哈希冲突所带来的影响,即若两者撞了哈希,可以再通过equals去分辨。

Object常用方法如下:

.toString()  返回String对象,默认返回对象地址,可重写。

.equals()    默认比较两个引用变量是否指向同一个对象(内存地址),可重写。

.hashCode() 将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来,可重写。

.clone()    就如上一条内容提到的,java无法用=直接拷贝,因此可用使用clone去拷贝对象,clone默认是浅拷贝。需注意:

  • 实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。
  • 覆盖clone()方法,可见性提升为public。

.getClass()   返回此 Object 的运行时类,常用于java反射机制。

.wait()  .notify()详见java并发锁


java中的引用分为强、软、弱、虚

  • 强引用:即java普通的引用,存在于栈上,只要有强引用指向的内存空间就不会被gc回收
  • 软引用 :只有在内存空间不足时gc才会回收弱引用指向的内存空间。例子:SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
  • 弱引用:只要遇到gc就会被回收。例子:WeakReference<M> m = new WeakReference<>(new M());
  • 虚引用:一般不使用,在jvm底层中使用,管理堆外内存。

 java中的对象和c++对比起来就像是所有都是以引用类型存在,但是需要额外注意一点

c++中的引用对象参数在函数中使用=赋值之后,在外层的主函数的参数也会改变,这是因为c++支持运算符重载,=实际上是拷贝,而java的=是纯粹的赋值,相当于改变了引用自身的值。


 java支持内部类,即在类的内部再声明一个类:

public class OuterClass {
    class InnerClass {
    }
    static class StaticInnerClass {
    }
    public static void main(String[] args) {
        // 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例
        // 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例
        // InnerClass innerClass = new InnerClass();
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();

        outerClass.test();
    }
    
    public void nonStaticMethod() {
        InnerClass innerClass = new InnerClass();
        System.out.println("nonStaticMethod...");
    }
}
View Code

正如上例代码,内部类创建的时候需先有外部类对象,然后调用外部类.new方法创建,静态内部类可直接创建。


 java中Arrays.sort()使用的排序算法:对于输入数组类型为基础类型时使用双轴快速排序;对于输入数组类型为包装类型时使用Timsort排序(归并+二分插入)算法


 

java.nio提供了非阻塞IO机制,主要由Channel通道、Buffer缓冲区、Selector选择器组成。

NIO和传统IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。传统IO线程会阻塞在read和write上,直到数据被完全读取或写入,而NIO从缓冲区读写,无需等待,无数据时可以去干其他事情,因此在处理网络传输时效率较高。


 

 Java中的基本数据类型都有对应的封装 ,当基本数据类型和其对应的封装类型进行以下操作时,编译器会自动进行装箱(将基础类型转化为包装类型)拆箱(将包装类型转化为基础类型)

  • 赋值操作(装箱或拆箱)
  • 进行加减乘除混合运算 (拆箱)
  • 进行>,<,==比较运算(拆箱)
  • 调用equals进行比较(装箱)
  • ArrayList、HashMap等集合类添加基础类型数据时(装箱)

 补充:Byte、Short、Integer、Long包装类均存在一字节缓存[-128,127],而Character的缓存范围为[0,127]

Integer内部有缓存Integer cache,其下限是-128,上限默认127(即一字节) ,当需要new范围的Integer时,会从缓存中直接取出使用。详见下例

Integer test1 = 99;
Integer test2 = 99;
System.out.println(test1==test2);
test1 = 256;
test2 = 256;
System.out.println(test1==test2);

运行结果:

可见在缓存范围内创建Integer使用的是缓存区内的同一对象。

需注意,仅在装箱时会使用缓存,直接new对象是不会使用缓存的。


 

String相关:

String的底层是final char[](JDK9之后为final byte[]),因此String是不可变的,即无法修改String的内部对象也无法修改String的大小,只能改变其指向(重新给予一个String对象)。也正是因为其不可变,让其拥有了线程安全的性质。

需注意,在JDK9之后,String的底层由char[]变为byte[],这样做是为了节省空间提升效率,即当String中只有Latin-1字符时,其按照1字节的规格进行分配内存,否则按照2字节分配。

 String str = "123";  String str = new String("123");

以上两种创建方式的区别为,第一种会先从字符串常量池中寻找,若有可用对象则直接用无需再创建,若没有则创建并存入字符串常量池;第二种则会直接在堆中创建,并依然会向字符串常量池存入。可用理解为,只要程序中出现 "xxxx" 字面量,就会向字符串常量池创建String对象储存,因此第二种方式可能产生两个String对象,一个在堆中,一个在常量池中。

有关String的不可变性

String底层的char或byte是final修饰的,因此无法修改。String是线程安全的

实现可变String方法

StringBuilder是一个可变的字符串类,我们可以把它看成是一个容器。常用函数有.append()添加数据并返回自身引用、.reverse()反转容器内容。需注意StringBuilder线程不安全!

StringBuffer的内容和长度同样,都是可以改变的,功能和StringBuilder类似,但是StringBuffer 是线程安全的,内部使用 synchronized 进行同步,因此性能较低。


 

前面有提过,jdk8支持接口默认方法,即接口提供一个已实现的方法,不用实现接口的类去额外提供实现方法。

java中不存在多继承(一个子类继承多个父类),但是存在多实现(一个类实现多个接口),对于多个接口存在相同的默认方法,或接口的默认方法与本类或父类方法同名的情况遵循如下规则:

  1. 类优先。如果本类中(或父类)提供了一个具体方法符合签名,则同名且具有相同参数列表的接口中的默认方法会被忽略;
  2. 接口冲突。如果一个接口提供了一个默认方法,另一个接口提供了一个同名且参数列表相同的方法 (顺序和类型都相同) ,则必须覆盖这个方法来解决冲突 (不覆盖编译器不会编译)

 jdk8新特性

 

 

持续记录中...

 

posted @ 2023-07-27 16:02  _Explosion!  阅读(16)  评论(0编辑  收藏  举报