我的一些JAVA基础见解 一、Java数据类型 二、String家族 三、自动装箱与自动拆箱 四、内部类

这个学期学习JAVA基础课,虽说之前都自学过,但在学习时仍可以思考一些模糊不清的问题,可以更深一步的思考。在这里写下一些需要深入的知识点,对小白们也很友好~


1、基本数据类型

这个经常在微信公众号有关面试中见到,基础中的基础,于是拿出来看看。

共有八大基本数据类型,分别是byte, short, int, long, char, float, double, boolean

整型 byte、short、int、long
字符型 char
浮点型 float、double
布尔型 boolean

简单讲解一下:

byte:

byte 数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是 -128(-2^7);
最大值是 127(2^7-1);
默认值是 0;
byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
例子:byte a = 100,byte b = -50。
-----------------------------------------------------------------------------------
short:

short 数据类型是 16 位、有符号的以二进制补码表示的整数
最小值是 -32768(-2^15);
最大值是 32767(2^15 - 1);
Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
默认值是 0;
例子:short s = 1000,short r = -20000。
------------------------------------------------------------------------------------
int:

int 数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);
最大值是 2,147,483,647(2^31 - 1);
一般地整型变量默认为 int 类型;
默认值是 0 ;
例子:int a = 100000, int b = -200000。
-------------------------------------------------------------------------------------
long:

long 数据类型是 64 位、有符号的以二进制补码表示的整数;
最小值是 -9,223,372,036,854,775,808(-2^63);
最大值是 9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是 0L;
例子: long a = 100000L,Long b = -200000L。
"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
-------------------------------------------------------------------------------------
float:

float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
float 在储存大型浮点数组的时候可节省内存空间;
默认值是 0.0f;
浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f。
--------------------------------------------------------------------------------------
double:

double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
浮点数的默认类型为double类型;
double类型同样不能表示精确的值,如货币;
默认值是 0.0d;
例子:double d1 = 123.4。
---------------------------------------------------------------------------------------
boolean:

boolean数据类型表示一位的信息;
只有两个取值:true 和 false;
这种类型只作为一种标志来记录 true/false 情况;
默认值是 false;
例子:boolean one = true。
---------------------------------------------------------------------------------------
char:

char类型是一个单一的 16 位 Unicode 字符;
最小值是 u0000(即为0);
最大值是 uffff(即为65,535);
char 数据类型可以储存任何字符;
例子:char letter = 'A';。

2、引用数据类型

除了基本数据类型外的都属于引用数据类型,例如String,数组,Scanner,Random等都属于引用数据类型

所有引用类型的默认值都是null

3、自动类型转换

低-------------------------------------------------------------------------------->高

byte,short,char -> int -> long -> float ->double

转换从低级到高级,必须满足转换前的数据类型的位数要低于转换后的数据类型,还要满足以下规则:

(1)、不能对boolean类型进行类型转换。

(2)、不能把对象类型转换成不相关类的对象。

(3)、在把容量大的类型转换为容量小的类型时必须使用强制类型转换。

(4)、转换过程中可能导致溢出或损失精度,例如:

int i = 128;
byte b = (byte) i;

因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出 。

(5)、 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入,例如:

(int) 23.7 == 23;
(int)-45.98f == -45;

(6)、来个自动转换例子:

public class ZiDongLeiZhuan{
        public static void main(String[] args){
            char c1='a';//定义一个char类型
            int i1 = c1;//char自动类型转换为int
            System.out.println("char自动类型转换为int后的值等于"+i1);
            char c2 = 'A';//定义一个char类型
            int i2 = c2+1;//char 类型和 int 类型计算
            System.out.println("char类型和int计算后的值等于"+i2);
        }
}
/*
结果:char自动类型转换为int后的值等于97
	 char类型和int计算后的值等于66 
*/

4、强制类型转换

(1)、转换的数据类型必须是兼容的。

(2)、格式:(type)value type是要强制类型转换后的数据类型

例如:

public class QiangZhiZhuanHuan{
    public static void main(String[] args){
        int i = 123;
        byte b = (byte)i;	//强制类型转换为byte
        System.out.println("int强制类型转换为byte后的值等于"+b);
    }
}
//结果:int强制类型转换为byte后的值等于123

二、String家族

我们就围绕String、StringBuffer、StringBuilder来说吧!

1、执行速度比较

StringBuilder > StringBuffer > String

为什么是这样子的呢?

书中有云:String为字符串常量、而其余两者为字符串变量。对于字符串常量,也就是不可变的对象,看一段代码:

String s = "abc";
s= s+1;
System.out.print(s);
//结果: abc1

问题来了,他变了!他变了!他变了!

我明明就是改变了String型的变量s,为什么说是没有改变呢? 其实这是一种欺骗,JVM是这样解析这段代码的:首先创建对象s,赋予一个abcd,然后再创建一个新的对象s用来执行第二行代码,也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了。由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多低。

而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象.每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,当然速度就快了。

看个例子:

String str = “This is only a” + “ simple” + “ test”;
StringBuilder sb = new StringBuilder(“This is only a”).append(“ simple”).append(“test”);

StringBuilder.append()中间过程中产生的垃圾内存大多数都是小块的内存,锁产生的垃圾就是拼接的对象以及扩容原来的空间;而当发生String的"+"操作时,前一次String的"+"操作的结果就成了内存垃圾,垃圾会越来越多,最后扩容也会产生很多垃圾 ,影响了速率。

2、StringBuffer和StringBuilder比较

StringBuilder:线程非安全的

StringBuffer:线程安全的

当我们在字符串缓冲区被多线程使用时,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。

当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,这就是速度的原因。

3、三者的总结

(1)、如果要操作少量的数据用String

(2)、单线程操作字符串缓冲区 下操作大量数据用StringBuilder

(3)、多线程操作字符串缓冲区 下操作大量数据用StringBuffer

三、自动装箱与自动拆箱

1、什么是自动装箱与拆箱

书上给了简短的两句代码

//自动装箱
Integer value = 308;
 
//自动拆箱
int x = value;

解释一下, 装箱就是自动将基本数据类型转换为包装类;拆箱就是自动将包装类转换为基本数据类型 。

看一下基本数据类型的包装类

2、感受一下原理

自动装箱:

Integer value = 100;

Java虚拟机会自动调用Integer的valueOf方法,编译为:Integer value = Integer.valueOf(100);

自动拆箱:

Integer value = 100;
int x = value;

Java虚拟机会自动调用Integer的intValue方法, 编译为:int t=i.Integer.intValue();

3、网上看到的两个注意点

这段有幸看到大佬的博客,想深入了解可以点过来看看,感悟更深呢。

(1) 小心空指针异常

public static void main(String[] args) throws Exception
{
    Object obj = getObj(null);
    int i = (Integer)obj;
}
    
public static Object getObj(Object obj)
{
    return obj;
}

/* 
结果:Exception in thread "main" java.lang.NullPointerException
    at main.Test.main(Test.java:8)
*/

这种使用场景很常见(web),我们把一个int数值放在session或者request中,取出来的时候就是一个类似上面的场景了。所以,小心自动拆箱时候的空指针异常 。

(2)小心界限

第一段代码:

public class TestMain
{
    public static void main(String[] args)
    {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        
        System.out.println(i1 == i2);
        System.out.println(i3 == i4);
    }
}
/*
结果:true
     false
*/

第二段代码:

public class TestMain
{
    public static void main(String[] args)
    {
        Double d1 = 100.0;
        Double d2 = 100.0;
        Double d3 = 200.0;
        Double d4 = 200.0;
        
        System.out.println(d1 == d2);
        System.out.println(d3 == d4);
    }
}
/*
结果:false
	 false
*/

产生这样的结果的原因是:Byte、Short、Integer、Long、Char这几个装箱类的valueOf()方法是以128位分界线做了缓存的,假如是128以下且-128以上的值是会取缓存里面的引用的,以Integer为例,其valueOf(int i)的源代码为:

public static Integer valueOf(int i) {
    final int offset = 128;
    if (i >= -128 && i <= 127) { // must cache 
        return IntegerCache.cache[i + offset];
    }
        return new Integer(i);
    }

而Float、Double则不会,原因也很简单,因为byte、Short、integer、long、char在某个范围内的整数个数是有限的,但是float、double这两个浮点数却不是 。

四、内部类

学习内部类的时候老师也是一讲带过,自然也很蒙圈,想可以更加深入了解一下。

首先来说说为啥要有内部类呢?如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。

不过你可能要质疑,更改一下方法的不就行了吗?的确,以此作为设计内部类的理由,实在没有说服力。

真正的原因是这样的,JAVA中的内部类和接口加在一起,可以的解决常被C++程序员抱怨JAVA中存在的一个问题——没有多继承。实际上,C++的多继承设计起来很复杂,而JAVA通过内部类加上接口,可以很好的实现多继承的效果。

内部类分为四种:成员内部类、局部内部类、匿名内部类、静态内部类

1、成员内部类

public class Outer
{
    private int i;
    
    public Outer(int i)
    {
        this.i = i;
    }
    
    public void privateInnerGetI()
    {
        new PrivateInner().printI();
    }
    
    private class PrivateInner
    {
        public void printI()
        {
            System.out.println(i);
        }
    }
    
    public class PublicInner
    {
        private int i = 2;
        
        public void printI()
        {
            System.out.println(i);
        }
    }
}

//主函数
public static void main(String[] args)
{
    Outer outer = new Outer(0);
    outer.privateInnerGetI();
    Outer.PublicInner publicInner = outer.new PublicInner();
    publicInner.printI();
}

/*
结果:0
	 2
*/

结论:

(1)、成员内部类是依附其外部类而存在的,如果要产生一个成员内部类,必须有一个其外部类的实例

(2)、成员内部类中不可以定义静态方法

(3)、成员内部类可以声明为private的,声明为private的成员内部类对外不可见,外部不能调用私有成员内部类的public方法

(4)、成员内部类可以声明为public的,声明为public的成员内部类对外可见,外部也可以调用共有成员内部类的public方法

(5)、成员内部类可以访问其外部类的私有属性,如果成员内部类的属性和其外部类的属性重名,则以成员内部类的属性值为准

2、 局部内部类

简要定义: 局部内部类是定义在一个方法或者特定作用域里面的类

public static void main(String[] args)
{
    final int i = 0;
    class A
    {
        public void print()
            {
            System.out.println("i = " + i);
        }
    }
    
    A a = new A();
    a.print();
}

注意一下局部内部类没有访问修饰符,另外局部内部类要访问外部的变量或者对象,该变量或对象的引用必须是用final修饰的

3、匿名内部类

在多线程模块中的代码中大量使用了匿名内部类

public static void main(String[] args) throws InterruptedException
{
    final ThreadDomain td = new ThreadDomain();
    Runnable runnable = new Runnable()
    {
        public void run()
        {
            td.testMethod();
        }
    };
    Thread[] threads = new Thread[10];
    for (int i = 0; i < 10; i++)
        threads[i] = new Thread(runnable);
    for (int i = 0; i < 10; i++)
        threads[i].start();
    Thread.sleep(2000);
    System.out.println("有" + td.lock.getQueueLength()  "个线程正在等待!");
}

匿名内部类是唯一没有构造器的类,其使用范围很有限,一般都用于继承抽象类或实现接口(注意只能继承抽象类,不能继承普通类),匿名内部类Java自动为之起名为XXX$1.classs。另外,和局部内部类一样,td(见上代码)必须是用final修饰的。

4、静态内部类

简要定义: 用static修饰的内部类就是静态内部类

public class Outer
{
    private static final int i = 1;
    
    public static class staticInner{
        public void notStaticPrint()
        {
            System.out.println("Outer.staticInner.notStaticPrint(), i = " + i);
        }
        
        public static void staticPrint()
        {
            System.out.println("Outer.staticInner.staticPrint()");
        }
    }
}

public static void main(String[] args)
{
    Outer.staticInner os = new Outer.staticInner();
    os.notStaticPrint();
    Outer.staticInner.staticPrint();
}

/*
结果:
Outer.staticInner.notStaticPrint(), i = 1
Outer.staticInner.staticPrint()
*/

结论:

(1)、静态内部类中可以有静态方法,也可以有非静态方法

(2)、静态内部类只能访问其外部类的静态成员与静态方法

(3)、和普通的类一样,要访问静态内部类的静态方法,可以直接"."出来不需要一个类实例;要访问静态内部类的非静态方法,必须拿到一个静态内部类的实例对象

(4)、注意一下实例化成员内部类和实例化静态内部类这两种不同的内部类时写法上的差别

①成员内部类:外部类.内部类 XXX = 外部类.new 内部类();

②静态内部类:外部类.内部类 XXX = new 外部类.内部类();

5、运用滴好处

(1)、Java允许实现多个接口,但不允许继承多个类,使用成员内部类可以解决Java不允许继承多个类的问题。在一个类的内部写一个成员内部类,可以让这个成员内部类继承某个原有的类,这个成员内部类又可以直接访问其外部类中的所有属性与方法,就相当于多继承了。

(2)、成员内部类可以直接访问其外部类的private属性,而新起一个外部类则必须通过setter/getter访问类的private属性

(3)、有些类明明知道程序中除了某个固定地方都不会再有别的地方用这个类了,为这个只用一次的类定义一个外部类显然没必要,所以可以定义一个局部内部类或者成员内部类,写一段代码用用就好了

(4)、内部类某种程度上来说有效地对外隐藏了自己,比如我们常用的开发工具Eclipse、MyEclipse,看代码一般用的都是Packge这个导航器,Package下只有.java文件,我们是看不到定义的内部类的.java文件的


目前每周有一节课,也在不断的思考所学内容,有值得写的地方还会继续补写,为了热爱,加油吧!