01_java基础知识

1. java基础

1.1 java语言的三大特性

  1. 封装

    1. 首先,属性可用来描述同一类事物的特征,方法可以描述一类事物可做的操作。封装就是把属于同一类事物的共性(包括属性和方法)归到一个类中,以便方便使用
    2. 概念:封装也称为信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他部分只有通过包裹在数据外面的被授权的操作来与这个抽象数据类型交流和交互。也就是说,用户无需知道对象内部方法和实现细节,但可以根据对象提供的外部接口(对象名和参数)访问该对象
    3. 好处:
      1. 实现了专业的分工。将能实现某一特定功能的代码封装成一个独立的实体后,各程序员可以在需要的时候调用,从而实现了专业的分工
      2. 隐藏信息,实现细节。通过控制访问权限可以将不行让客户端程序员看到的信息隐藏起来,如某客户的银行的密码需要保密,只能对该客户开放权限
  2. 继承

    1. 就是个性对共性的属性与方法的接受,并加入个性特有的属性和方法

    2. 概念:一个类继承另一个类,则称继承的类为子类,被继承的类为父类。

    3. 目的:实现代码的复用

    4. 理解:子类与父类的关系并不是日常生活中的父子关系,子类与父类而是一种特殊化和一般化的关系,是is-a的关系,子类是父类更加详细的分类。如 class dog extends animal,就可以理解为dog is a animal。注意设计继承的时候,若要让某一个类能被继承,父类需要适当开放访问权限,遵循里氏代换原则,即向修改关闭对扩展开放,也就是开闭原则

      另外子类可以写自己特有的属性和方法,目的是实现功能的扩展,子类也可以复写父类的方法即方法的重写

  3. 多态

    1. 多态的概念发展是以封装和继承为基础的
    2. 多态就是在抽象的层面上实施一个统一的行为,到个体(具体)的层面上时,这个统一的行为会因为个体(具体)的形态特征而实施自己的特征(针对一个抽象的事物,对于内部个体又能找到其自身的行为去执行)
    3. 概念:相同的事物,调用其相同的方法,参数也相同的时候,但表现得行为不同
    4. 理解:子类以父类的身份出现,但做事情时还是以自己的方法实现。子类以父类的身份出现需要向上转型,这是由jvm自动实现的,是安全的。但是向下转型是不安全的,需要强制转换。子类以父类的身份出现的时候自己特有的方法和属性将不能使用

1.2 java基本数据类型及其封装类

基本类型大小(字节)默认值封装类
byte1(byte)0Byte
short2(short)0Short
int40Integer
long80L(L可以省略)Long
float40.0f(f不能省略)Float
double80.0d(d可以省略)Double
boolean-falseBoolean
char2\u0000(null)Characher
  • boolean类型单独使用是占4个字节,在数组中占1个字节
  • 基本类型放在栈中,直接存储值
  • 数值类型都有正负号,没有无符号的数值类型
  1. 为什么需要封装类

    因为泛型类包括预定义的集合,使用的参数都是对象类型,无法直接使用基本数据类型,所以java又提供了这些基本类型的包装类

  2. 基本类型与包装类的区别

    基本类型会在栈中创建,而对象类型在堆中创建,对象的引用在栈中创建。基本类型由于在栈中,效率会比较高

1.3 如果main方法被声明成为private会怎样?

能正常编译,但运行的时候会提示main方法不是public的

1.4 ==与equals的区别?

  • ==比较的是两个引用是不是指向同一个对象。基本数据类型则比较的是值相等。
  • equals比较的是内容,是由Object对象提供的方法,可以由子类重写,默认和==是等价的。但是其子类如Stirng,BitSet,Date和File都对equals方法进行了重写。一般重写为了内容相等

1.5 Object有哪些公用方法

  • Object是所有类的父类,任何类都默认继承Object
  • clone方法:实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则会抛出CloneNotSupportedException异常
  • equals方法:在Object中与==是一样的,子类一般都需要重写该方法
  • hashCode方法:返回该对象的哈希值,重写equals方法一般都要重写hashCode方法,这个方法在一些具有哈希功能的Collection中用到
  • gatClass方法:final方法,获取该对象的运行时类型
  • wait方法:使当前线程进入阻塞,放弃该对象的锁。直到下一次获得了该对象的锁或被中断才能继续运行。只有拥有了该对象锁才能调用该方法(拥有了锁才能释放锁),所以一般在Synchronized代码块内部。
  • notify方法:唤醒等待该对象锁的阻塞队列上的某一个线程。只有拥有了该对象锁才能调用该方法。
  • notifyAll方法:唤醒在该对象锁的阻塞队列上的所有线程
  • toString方法:将对象转换为字符串,默认返回对象句柄,一般子类都有重写

1.6 为什么java里没有全局变量?

全局是全局可见的,java不支持全局可见的变量:因为全局变量破坏了引用透明性原则。全局变量导致了命名空间的冲突。

1.7 修饰符的作用域

作用域当前类同一包子类其他包
publicyesyesyesyes
protectedyesyesyesno
defaultyesyesnono
privateyesnonono

1.8 short s1=1;s1=s1+1;有错吗?

有错,将int型结果赋值给short型

1.9 short s1=1;s1+=1;有错吗?

没错,编译器会进行特殊处理,帮我们做了强制类型转换。相当于s1=(short)(s1+1)

1.10 IntegerCache

整数的包装类-128~127之间做了缓存。源码如下

private static class IntegerCache {
	static final int low = -128;
	static final int high;
	static final Integer cache[];
	static {
		// high value may be configured by property
		int h = 127;
		String integerCacheHighPropValue =
		sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
		if (integerCacheHighPropValue != null) {
			try {
				int i = parseInt(integerCacheHighPropValue);
				i = Math.max(i, 127);
				// Maximum array size is Integer.MAX_VALUE
				h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
			} catch( NumberFormatException nfe) {
			// If the property cannot be parsed into an int, ignoreit.
		}
	}
	high = h;
	cache = new Integer[(high - low) + 1];
	int j = low;
	for(int k = 0; k < cache.length; k++)
		cache[k] = new Integer(j++);
		// range [-128, 127] must be interned (JLS7 5.1.7)
		assert IntegerCache.high >= 127;
	}
	private IntegerCache() {}
}
public static Integer valueOf(int i) {
	assert IntegerCache.high >= 127;
	if (i >= IntegerCache.low && i <= IntegerCache.high)
	return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

1.11 java中final、finally、finalize的区别与用法

  1. final
    1. final是一个关键字也是一个修饰符
    2. 被修饰的类无法被继承
    3. 被修饰的基本数据类型的变量在初始化后则无法更改数值,被修饰的引用类型变量在初始化后不能再指向其他的对象,但是它指向的对象的内容是可变的
    4. 被修饰的方法无法被重写,但是允许重载(类的private方法会隐式地被指定为final方法)
  2. finally
    1. finally是一个关键字
    2. finally在异常处理时提供finally块来执行任何清楚操作。不管有没有异常都被抛出或被捕获,finally块都会执行,通常用于释放资源。
    3. finally块正常情况下一定会被执行,但是有至少两个极端情况:
      1. 如果对应的try块没有执行,则这个try块的finally块也不会被执行
      2. 如果在try块中jvm关机,如system.exit(n),则finally块也不会执行
    4. finally块中如果有return语句,则会覆盖try或catch中的return语句,导致二者无法return,所以强烈建议finally块中不要存在return关键字
  3. finalize
    1. finalize()是Object类的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前都会调用该方法,但是我们主动调用该方法不会导致该对象变成垃圾
    2. finalize方法是存在很多问题的:
      1. java语言规范并不保证finalize方法回及时地执行,更根本不会保证他们一定会被执行(即finalize方法不是析构函数,所以撤销的时候不一定保证要执行它)
      2. finalize方法可能带来性能问题,因为jvm通常在单独的低优先级线程中完成finalize的执行
      3. finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的
      4. finalize方法最多由GC执行一次(但可以手动调用对象的finalize方法)

1.12 hashCode()和equals()的区别?

  1. equals既然已经能实现对比的功能,为什么还要hashCode呢?
    1. 因为重写的equals()里的比较一般是较为复杂,而且效率也相对比较低,而利用hashCode进行对比,则只要生成一个hash值就可以进行对比了,效率很高,所以我们一般会在hashCode比较不同后就不再用equals比较了。
  2. hashCode既然效率那么高为什么还要用equals呢?
    1. 因为hashCode并不是那么可靠,会产生哈希冲突,所以当哈希值相同后我们需要进一步通过验证equals比较结果来判断相等
  3. 阿里巴巴开发规范明确规定
    1. 只要重写equals,就必须重写hashCode:因为默认hashCode的方法返回的是对象的哈希值,而不同的对象不管内容是不是一样返回的哈希值都是不同的。所以:
      1. 比如Set存储的是不重复的对象,如何定义这个不重复,不是对象的地址不同就是不重复,而是依据hashCode和equals来进行判断。,所以需要重写这两个方法
      2. 如果自定义对象作为Map的键,则也需要重写这两方法
      3. String默认就重写了这两个方法,所以我们可以把String对象作为key来使用
    2. 什么时候需要重写
      1. 一般情况不需要重载hashCode,只有当类需要放在HashTable、hashMap的key中和HashSet中才会需要重载
    3. 为什么要重载hashCode?
      1. 如果你重写了equals,说明你是想基于对象的内容来进行比较是否相等的。而保留hashCode的实现不变,则很可能某两个对象明明内容相等,而hashCode值却不一样
    4. equals相等,hashCode一定要相等,而hashCode相等,equals却不一定要相等?
      1. 因为我们先第一步通过hashCode粗略筛选是否相等,hashCode比较不相等则一定不相等,就可以直接返回false。
      2. hashCode可以很快地查到小内存块

1.13 深拷贝和浅拷贝的区别是什么?

  1. 浅拷贝
    1. 被复制对象的所有基本变量都含有原来对象相同的值,而引用类型变量都是指向跟原来对象相同的对象。
  2. 深拷贝
    1. 拷贝一个对象,会拷贝对象里的每一个属性,也就是说若是包含引用对象,则也要把这个对象所引用的对象也拷贝一份
  3. 注意
    1. 如果想要一个对象支持拷贝,也就是说能调用clone方法,则这个对象一定要实现Cloneable接口
    2. 深拷贝还需要重写clone方法
    3. 调用对象的clone方法返回的是Object类型的,需要强制转换

1.14 java中操作字符串都有哪些类?

  • String

    • String对象是不可变的对象
    • 对于字符串常量,如果内容相同,java会认为他们代表同一个String对象
    • 用new关键字调用构造器创建String对象,都是会是一个新的对象
    • 对于class字节码文件中的字符串常量(而不是new出来的对象),加载入内存后,只会创建唯一的拘留字符串对象,所有相同的字符串常量都会指向这同一对象
  • String s= new String(“abc”)这句话会创建几个对象

    • 首先因为有“abc”字符串常量,若首次出现系统会创建一个拘留字符串对象,如果不是首次出现,则不会创建。
    • new 则会重新创建一个字符串对象,并用拘留字符串对象的值初始化这个刚创建的对象
    • 所以会创建两个对象
  • 谈谈操纵字符串的符号“+”的原理

    //代码1  
    String sa = "ab";                                          
    String sb = "cd";                                       
    String sab=sa+sb;                                      
    String s="abcd";  
    System.out.println(sab==s); // false  
    //代码2  
    String sc="ab"+"cd";  
    String sd="abcd";  
    System.out.println(sc==sd); //true 
    
    • sa与sb都是变量,编译时期不能被确定具体的值
    • "ab"与"cd"都是常量,编译时期能够确定具体的值
    • sc=“ab”+“cd”;在编译时期就合成了字符串"abcd"
    • sab=sa+sb;在运行期进行+的操作
    • 对于String sab=sa+sb;
      1.首先会创建一个Stringbuilder类
      2.用sa指向的拘留字符串完成初始化操作
      3.再通过调用append方法,将sb指向的拘留字符串合并。
      4.再调用toString方法生成新字符串对象
      5.最后将生成的字符串对象的堆内存地址返回给变量sab
  • 谈谈StringBuffer与String的区别?

    //String   
    public final class String  {  
    	private final char value[];  
        public String(String original) {  
        // 把原字符串original切分成字符数组并赋给value[];  
        }  
    }  
      
    //StringBuffer   
    public final class StringBuffer extends AbstractStringBuilder  {  
    	char value[]; //继承了父类AbstractStringBuilder中的value[]  
        public StringBuffer(String str) {  
        	super(str.length() + 16); 
            //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组  
            append(str); 
            //将str切分成字符序列并加入到value[]中  
        } 
        public StringBuffer() {  
        	super(16); 
        }
    }  
    
    //StringBuffer的扩容算法
    	void expandCapacity(int minimumCapacity) {
            //value代表数组
            int newCapacity = value.length * 2 + 2;
            if (newCapacity - minimumCapacity < 0)
                newCapacity = minimumCapacity;
            if (newCapacity < 0) {
                if (minimumCapacity < 0) // overflow
                    throw new OutOfMemoryError();
                newCapacity = Integer.MAX_VALUE;
            }
            //进行扩容。对数组进行扩容。
            value = Arrays.copyOf(value, newCapacity);
        }
    
    • String类中维持了一个被final修饰的字符数组,所以是不可变的
    • StringBuffer如果调用无参的构造器,初始大小为16的数组,若是有参,则是参数长度+16
    • 扩容时,StringBuffer尝试将新容量扩为大小变成2倍+2,然后判断一下这个容量是否不够,若不够,直接扩充到需要的容量大小,最后对数组进行扩容。
    • StringBuffer说白了也是用数组来实现的,但是它的数组是可以修改的,可以改变的
  • StringBuffer和StringBuilder的区别?

    • StringBuffer是线程安全的。效率低
    • StringBuilder是线程不安全的,但效率高

1.15 抽象类能使用final修饰吗?

  • 不能,因为抽象类就是要让其他类来继承的,而final修饰的类不能够被继承

1.16 static关键字的一些问题?

  1. 抽象的方法是否可以同时是静态的?
    1. 不可以,抽象的是要被重写的,而静态方法是不能被重写的,所以这个是错误的。
  2. 是否可以从一个静态方法内部发出对非静态方法的调用?
    1. 不可以,静态方法只能访问静态成员,非静态方法的调用要先创建对象
  3. static可否用来修饰局部变量?
    1. 不可以
  4. 内部类与静态内部类的区别
    1. 静态内部类相对于外部类是独立存在的,在静态内部类中无法直接访问外部类中的变量、方法。如果要访问的话,必须要new一个外部类的对象,使用new出来的对象来访问。但是可以直接访问静态的变量,调用静态的方法;
    2. 普通的内部类作为外部类一个成员存在的,在普通内部类中可以直接访问外部类的属性,调用外部类的方法
    3. 如果外部类要访问内部类的属性或者调用内部类的方法必须要创建一个内部类的对象,使用该对象访问属性或调用方法
    4. 如果其他类要访问普通内部类的属性或者调用普通内部类的方法,必须要在外部类中创建一个普通内部类的对象作为一个属性,外部类可以通过该属性调用普通内部类的方法或者访问普通内部类的属性
    5. 如果其他的类要访问静态内部类的属性或者调用静态内部类的方法,直接创建一个静态内部类对象即可
  5. java中是否可以覆盖一个private或者static方法
    • static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是在编译时期静态绑定的。static方法跟类的任何实例都不相关联,所以概念上不适用
    • private也不行,因为子类无法访问这个方法
  6. 重载和重写的区别?
    1. 两者都是实现多态的方式,区别在于
      1. 重载是在编译时期的多态性
      2. 重写是运行时期的多态性
    2. 重载发生在一个类中,同一方法有不同的参数列表就视为重载,重载对返回值类型没有要求
    3. 重写发生在子类与父类之间,重写要求子类方法与父类方法有相同的参数列表,相同的返回类型
      1. 重写后的访问权限不能低于父类(因为里式替换原则可以用子类来替换父类运行不报错,所以可以调用父类的子类一定可以调用)
      2. 重写抛出的异常更少,更限制的异常,或者不抛出异常(同理里式替换原则,子类抛出的异常一定可以被适用于父类的接收)

1.17 java的四种引用

  1. 强引用

    1. 最普遍的引用方式,只要有强引用,GC就不会回收这个对象
  2. 软引用(SoftReference)

    1. 用于描述有用但不是必须的对象,如果内存空间足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,jvm就会把这个软引用加入到与之关联的引用队列中去。

      public class Test {
          public static void main(String[] args) throws InterruptedException {
              //512M的缓存数据
              byte[] cacheData = new byte[512 * 1024 * 1024];
              //创建一个引用队列
              ReferenceQueue queue = new  ReferenceQueue();
              //将缓存数据用软引用持有,同时关联一个引用队列
              SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData, queue);
              //将缓存数据的强引用去除
              cacheData = null;
              System.out.println("第一次GC前强引用手动清空" + cacheData);
              System.out.println("第一次GC前软引用" + cacheRef.get());
              SoftReference ref = null;
              test(queue);
              System.out.println();
              //进行一次GC后查看对象的回收情况
              System.gc();
              //等待GC
              Thread.sleep(500);
              System.out.println("第一次GC后内存空间足够时软引用" + cacheRef.get());
              test(queue);
              System.out.println();
              //在分配一个1024M的对象,看看缓存对象的回收情况
              byte[] newData = new byte[1024 * 1024 * 1024];
              System.out.println("分配后内存空间不够时软引用" + cacheRef.get());
              test(queue);
          }
          //可以从队列中拿出软引用对象进行清空。
          private static void test(ReferenceQueue queue) {
              SoftReference ref;
              if ((ref = (SoftReference) queue.poll()) != null) {
                  System.out.println("软引用被回收");
                  System.out.println(ref);
                  ref = null;
              } else {
                  System.out.println("软引用没有被回收");
              }
          }
      }
      
      第一次GC前强引用手动清空null
      第一次GC前软引用[B@4554617c
      软引用没有被回收
                
      第一次GC后内存空间足够时软引用[B@4554617c
      软引用没有被回收
      
      分配后内存空间不够时软引用null
      软引用被回收
      java.lang.ref.SoftReference@74a14482
      
      
  3. 弱引用(WeekReference)

    1. 弱引用和软引用大致相同,区别在于在发生GC后,不管内存足不足够,弱都会回收。
  4. 虚引用(PhantomReference)

    1. 就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象经持有虚引用,那么它就跟没有任何引用一样。在任何时候都可能被垃圾回收器回收。虚引用主要是用来跟踪对象被垃圾回收器回收的活动
    2. 虚引用必须和引用队列联合使用。当垃圾回收器准备回收一个对象时,如果发现他还有虚引用,就会在回收对象内存前,把这个虚引用加入到与之关联的引用队列中。

1.18 java中Comparator与Compareble有什么不同?

  1. Compareble是一个接口,一个类可以通过实现Compareble接口中的comparaTo方法来定义排序规则

    1. 与类之间耦合性比较强。
    public class Domain implements Comparable<Domain>
    {
        private String str;
        public Domain(String str)
        {
            this.str = str;
        }
        public int compareTo(Domain domain)
        {
            if (this.str.compareTo(domain.str) > 0)
                return 1;	//如果当前对象大于传入对象,返回1
            else if (this.str.compareTo(domain.str) == 0)
                return 0;
            else 
                return -1;
        }
        public String getStr()
        {
            return str;
        }
    }
    
  2. Comparator可以认为是一个外比较器接口,通过实现compara方法来实现排序策略,它使用了一种策略模式。

    public class DomainComparator implements Comparator<Domain>
    {
        public int compare(Domain domain1, Domain domain2)
        {
            if (domain1.getStr().compareTo(domain2.getStr()) > 0)
                return 1;
            else if (domain1.getStr().compareTo(domain2.getStr()) == 0)
                return 0;
            else 
                return -1;
        }
    }
    public static void main(String[] args)
    {
        Domain[] list=new Domain[]{
            new Domain("c"),
            new Domain("c"),
            new Domain("b"),
            new Domain("d")
        };    
        DomainComparator dc = new DomainComparator();
       	Arrays.sort(list,dc);
        System.out.println(list);
    }
    
  3. 区别
    一个是本身具有比较功能,一个是定义了一个比较器,通过比较器实现比较功能

1.18 java的序列化问题

  1. 什么是序列化和反序列化
    java序列化是将对象转换为字节序列的过程,反序列化是将字节序列转为目标对象的过程
  2. 什么情况需要序列化
    java对象需要在网络上传输,或者持久化存储到文件中时。
  3. 如何实现序列化
    若一个类想要能够实现序列化,则让该类实现Serializable接口,该接口没有方法,起标记作用
  4. 如果某些数据不想被序列化,如何做
    在该字段前面加上transient关键字,则不参与序列化

1.19 java泛型和类型擦除?

  1. 泛型即参数化类型,在创建集合时,指定集合元素的类型,然后此集合就只能传入该类型的数据。
  2. 类型擦除:其实java编译器后的字节码不包含泛型信息,泛型的作用就是起到编译时前检查的作用。所以在编译时擦除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值