Java基础总结2

总结源于海子博客http://www.cnblogs.com/dolphin0520/

1、在class文件中有一部分来存储编译期间生成的字面常量以及符号引用,这部分叫做class文件常量池,在运行期间对应着方法区的运行时常量池。

 (1)String str1 = "hello world";和String str2= "hello world";都在编译期间生成了字面常量和符号引用,运行期间字面常量"hello world"被存储在运行时常量池(当然只保存了一份)。通过这种方式来将String对象跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量; 否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。

 (2)String str = new String("hello world")通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"hello world"对象,而在代码执行过程中确实只创建了一个String对象,即在堆上创建了"hello world"对象。

 (3)String str = new String("hello");  str+="java";这段代码涉及(非创建)5个对象,首先,“hello”在堆和常量池各一个;其次,“java”在常量池有一个;最后,“helloJava”在堆和常量池各一第一次出现字符串直接量时会在池中创建一个新的,之后出现就不会创建了,而是直接把引用指向第一次创建的对象。但对于new出来的对象,无论怎样赋值,new一次在运行期创建一次(在堆中),不会考虑之前是否已经已存在相同的。而且,字符串是不可追加的,所以每次使用连接符号其实相当于先产生一个常量,然后再赋给引用,原来指向的串就成垃圾被回收了。

 (4)String str1="java";//指向字符串常量池     String str2="blog";//指向字符串常量池    String   s = str1+str2;   +运算符会在堆中建立起两个String对象,这两个对象的值分别是“java”,"blog",也就是说从字符串常量池中复制这两个值,然后在堆中创建两个对象。然后再建立对象s,然后将“javablog”的堆地址赋给s.  这句话共创建了3个String对象。

 2、Integer a = 1;      Integer b = 2;   Integer c = 3;  Integer d = 3;

  Integer e = 321;  Integer f = 321;  Long g = 3L;  Long h = 2L;

  System.out.println(c==d);//true

  System.out.println(e==f);//false

  System.out.println(c==(a+b));//true 

  System.out.println(c.equals(a+b));//true

  System.out.println(g==(a+b));//true 

  System.out.println(g.equals(a+b));//false

  System.out.println(g.equals(a+h));//true

  当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte, Short, Integer, Long, Character, Boolean, 另外两种浮点数类型(Float、Double)的包装类则没有实现。另外Byte, Short, Integer, Long, Character这5种整型的包装类也只在大于等于-128并且小于等于127时才使用常量池,也即对象不负责创建和管理大于127的这些类的对象。
 
3、(1)字符流使用了缓冲区,而字节流没有使用缓冲区。字节流不关闭时仍能输出内容,而字符流则不行,如果其不关闭流,则无法输出,但可以通过flush()方法强制性清空缓冲区从而输出内容。https://blog.csdn.net/qq_36494506/article/details/78188236                     
   (2)在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造器。也就是说类中的成员变量会在任何方法(包括构造器)调用之前得到初始化。
   (3)当用final修饰一个类时,表明这个类不能被继承;对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改,如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
  String a = "hello2";     final String b = "hello";       String c = b + 2;                System.out.println((a == c));  输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2; 
   (4)Google 核心jar包guava-19.0.jar
   (5)cmd 进行Maven打包:  
mvn install:install-file         //mvn 命令
-Dfile=sojson-demo.jar          //要添加的包
-DgroupId=com.sojson       //pom文件对应的groupId
-DartifactId=com.sojson.demo    //pom文件对应得artifactId
-Dversion=1.0          //添加包的版本
-Dpackaging=jar 
  (6)运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。
  (7)length、length()、size()的区别
    length属性是针对于数组的长度  String [] strs = {"ai","xue","xi"};   strs.length=3;
    length()方法是针对与字符串的长度  String str = "xuexi";  str.length()=5;
    size()方法是针对泛型集合的长度   List<String> array=new ArrayList<String>();  array.add(a);   array.size()=1;
  (8)被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。
  (9)访问权限     类     包    子类    其他包

        public        ∨      ∨       ∨       ∨

        protect      ∨      ∨       ∨       ×

        default      ∨      ∨       ×        ×

           private      ∨      ×       ×        ×

  (10)update ft_airport set ft_airport.airport_name=REPLACE(airport_name,'机场','');

  (11)Java8  时间日期api   https://lw900925.github.io/java/java8-newtime-api.html

  (12)分布式与集群的区别  http://blog.csdn.net/javaloveiphone/article/details/52368291

  (13)kafka  http://blog.csdn.net/tangdong3415/article/details/53432166

  (14)Windows环境下的监控工具

     jvisualvm.exe在JDK安装目录下的bin目录下面,双击即可打开。

       MemoryAnalyzer.exe。

  (15)this代表当前对象。

        static是不允许用来修饰局部变量。

        static final用来修饰成员变量和成员方法,可简单理解为“全局常量”! 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。对于方法,表示不可覆盖,并且可以通过类名直接访问。

4、类初始化顺序  
public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }
     
    public Test() {
        System.out.println("test constructor");
    }
     
    public static void main(String[] args) {
        new MyClass();
    }
}
 
class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}
 
 
class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }
     
    public MyClass() {
        System.out.println("myclass constructor");
    }
}

结果为:

test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor

在执行开始,先要寻找到main方法,因为main方法是程序的入口,但是在执行main方法之前,必须先加载Test类,因此会执行Test类中的static块。(注意:如果main方法在MyClass类中,则先会加载其父类Test,执行Test类中的static块,再加载MyClass类,执行MyClass类中的static代码块)接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块。在加载完之后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化,然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person(),最后执行MyClass的构造器。

第一点,所有的类都会优先加载基类

第二点,静态成员的初始化优先

第三点,成员初始化后,才会执行构造方法

第四点,静态成员的初始化与静态块的执行,发生在类加载的时候。

第五点,类对象的创建以及静态块的访问,都会触发类的加载。  

5、对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的

public class Test {
    public static void main(String[] args)  {
        Shape shape = new Circle();
        System.out.println(shape.name);  //隐藏了子类
        shape.printType();  //被子类覆盖
        shape.printName();  //隐藏了子类
    }
}
 
class Shape {
    public String name = "shape";
     
    public Shape(){
        System.out.println("shape constructor");
    }
     
    public void printType() {
        System.out.println("this is shape");
    }
     
    public static void printName() {
        System.out.println("shape");
    }
}
 
class Circle extends Shape {
    public String name = "circle";
     
    public Circle() {
        System.out.println("circle constructor");
    }
     
    public void printType() {
        System.out.println("this is circle");
    }
     
    public static void printName() {
        System.out.println("circle");
    }
}

结果为:

shape constructor
circle constructor
shape
this is circle
shape

6、接口与抽象类

抽象类:[ public ] abstract class ClassName {

       abstract void fun();
    }
  1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。

  2)抽象类不能用来创建对象;

  3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

    在其他方面,抽象类和普通的类并没有区别。

接口:[ public ] interface InterfaceName {  

      [ public static final ] String MSG = "hello",//全局常量

      [ public abstract ] void print();//抽象方法

     }

  1)接口中的变量会被隐式地指定为public static final变量

  2)方法会被隐式地指定为public abstract方法

语法层面区别:

  1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

设计层面上的区别:

  1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

  2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

7、创建成员内部类对象的一般方式

  

public class Test {
    public static void main(String[] args)  {
        //第一种方式:
        Outter outter = new Outter();
        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
         
        //第二种方式:
        Outter.Inner inner1 = outter.getInnerInstance();
    }
}
 
class Outter {
    private Inner inner = null;
    public Outter() {
         
    }
     
    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
      
    class Inner {
        public Inner() {
             
        }
    }
}

8、线程创建到消亡的状态

 9、Lock和synchronized的选择

  总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

10、nio读写文件  

public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream("D:\\bbb.txt");
        FileOutputStream on = new FileOutputStream("D:\\aaa.txt");
        
        //为该文件输入流生成唯一的文件通道  FileChannel
        FileChannel channel = in.getChannel();
        FileChannel fileChannel = on.getChannel();
        
        //开辟一个长度为1024的字节缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        while(true){
            //clear方法重设缓冲区,可以读新内容到buffer里
            buffer.clear();
            if (channel.read(buffer) == -1) {
                break;
            }
                System.out.println(new String(buffer.array(),"GBK"));
                //flip方法让缓冲区的数据输出到新的通道里面
                buffer.flip();  
                fileChannel.write(buffer);
        }
    }

11、return、continue、break

 public static void main(String[] args) {
        for (int i = 0; i < 6; i++) {
            if (i==4) {
                return;//0 1 2 3 满足return条件后该循环体以及循环后的方法不往下执行
//                break;//0 1 2 3 111 满足break条件后该循环终止,但循环后的方法仍往下执行
//                continue;//0 1 2 3 5 111 仅满足continue条件的项不执行,循环体以及循环后的方法仍执行
            }
            System.out.println(i);
        }
        System.out.println(111);
    }

12、简单工厂模式

  

  工厂方法模式

  抽象工厂模式

  http://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html

  在什么情况下应当使用抽象工厂模式

  (1)一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。

  (2)这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

  (3)同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。(比如:Intel主板必须使用Intel CPU、Intel芯片组)

  (4)系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

 13、成员变量与局部变量的区别:

   一:在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因。在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。

    (1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中

         (2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。

      二:在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量

       (1)当声明的是基本类型的变量其变量名及其值放在堆内存中的

       (2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中

  (1)定义的位置不同

    成员变量定义在类中,在整个类中都可以被访问。

    局部变量只定义在局部范围内,如:函数内,语句内等。

  (2)在内存中的位置不同

    成员变量存在于对象所在的堆内存中。

    局部变量存在于栈内存中。

  (3)生命周期不同

    成员变量随着对象的建立而建立,随着对象的消失而消失。

    局部变量随着方法的运行而出现,随着方法的弹栈而消失。

  (4)初始化不同

    成员变量不管程序有没有显式的进行初始化,加载时Java虚拟机都会先自动给它初始化为默认值。

    局部变量声明之后,Java虚拟机就不会自动给它初始化为默认值,因此局部变量的使用必须先经过显式的初始化。

14、静态变量与成员变量的区别:

  (1)所属范围不同

    静态变量所属于类,成员变量所属于对象。

    静态变量也称为:类变量  成员变量也称为:实例变量。

  (2)调用不同

    静态变量可以被对象和类名调用(一般用类名调用)。

    成员变量只能被对象调用。

  (3)加载时期不同

    静态变量随着类的加载而加载。

    成员变量随着对象的加载而加载。

  (4)内存的存储区域不同

    静态变量存储在方法区。

    成员变量存储在堆内存。

15、摘录于http://www.cnblogs.com/xiaoxi/p/6428432.html

     http://www.cnblogs.com/xiaoxi/p/6473480.html

  (1)从Set集合元素不可重复性看hashCode()的作用

  Set里面的元素是不可以重复的,那么如何做到?Set是根据equals()方法来判断两个元素是否相等的。比方说Set里面已经有1000个元素了,那么第1001个元素进来的时候,最多可能调用1000次equals方法,如果equals方法写得复杂,对比的东西特别多,那么效率会大大降低。使用HashCode就不一样了,比方说HashSet,底层是基于HashMap实现的,先通过HashCode取一个模,这样一下子就固定到某个位置了,如果这个位置上没有元素,那么就可以肯定HashSet中必定没有和新添加的元素equals的元素,就可以直接存放了,都不需要比较;HashCode相等,再equals比较,没有相同的元素就存,有相同的元素就不存。如果原来的Set里面有相同的元素,只要HashCode的生成方式定义得好(不重复),不管Set里面原来有多少元素,只需要执行一次的equals就可以了。这样一来,实际调用equals方法的次数大大降低,提高了效率。

  hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。

  每个Java类都包含hashCode() 函数。但是,仅仅当创建某个“类的散列表”(Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet)时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的位置);其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。

      也就是说:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

  (2)equals()的作用

    equals() 的作用是 用来判断两个对象是否相等(即,是否是同一个对象)

public boolean equals(Object obj) {
    return (this == obj);
}

  既然Object.java中定义了equals()方法,这就意味着所有的Java类都实现了equals()方法,所有的类都可以通过equals()去比较两个对象是否相等。 但是,我们已经说过,使用默认的“equals()”方法,等价于“==”方法。因此,我们通常会重写equals()方法:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。

下面根据“类是否覆盖equals()方法”,将它分为2类。
(01) 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。
(02) 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。

  (3)hashCode()与equals()总结  

    1)hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

    2)如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

    3)如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

    4)两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”

    hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。

例如内存中有这样的位置  
0  1  2  3  4  5  6  7    
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。  但如果用hashcode那就会使效率提高很多。  我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID 除 8求余数直接找到存放的位置了。  但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。  也就是说,我们先通过 hashcode来判断两个类是否存放某个篮子里,但这个篮子里可能有很多类,那么我们就需要再通过 equals 来在这个篮子里找到我们要的类

为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。

posted @ 2018-02-26 10:46  祎丫丫  阅读(214)  评论(0编辑  收藏  举报