Java 学习笔记(未完成)

这是一个先学了python再学java 的憨憨笔记,基本基于java核心编程第一部分写的。

数据类型

基本数据类型

一个字节是8位

int 4字节, short 2, long 8, byte 1
float 4, double 8
char 单字符 ,2字节,能够转换成数字

java区分引号,单引号是字符,双引号是字符串。

上述类型,小字节可以向大字节隐式转换,类型无所谓。同等大小则低精度向高精度转换。例如,byte可以转为doubleint会转为float

此外还能强制转换,不过会丢失精度,因为是直接截取低位。同时可能导致正数变负数

int a=989;		//0000 0011 1101 1101
long b = a;		//b=989
byte c=(byte)a;	//c=-35	二进制为1101 1101,最高位为1,所以是负数

下述类型,不可转换

boolean: true, false
enmu 枚举类型
String 字符串(实际上是类),字符串是不可变类型
null 空,类似python的None

变量在使用之前需要赋值,否则报错。

极大数值

非常大的数需要使用到java.math的 Biglnteger 和 BigDecimal类
大数的运算需要使用类方法,而不是运算符。

Biglnteger a = Biglnteger.valueOf(100);     //将普通数值转化为大数值
Biglnteger c = a.add(b);    // c = a + b
Biglnteger d = c.multipiy(b.add(Biglnteger.valueOf(2)));    // d = c *( b + 2 )

java.math.Biglnteger

  • Biglnteger add( Biglnteger other )
  • Biglnteger subtract( Biglnteger other )
  • Biglnteger multipiy( Biginteger other )
  • Biglnteger divide( Biglnteger other )
  • Biglnteger mod( Biglnteger other )
    返冋这个大整数和另一个大整数 other的和、 差 、积 、 商以及余数。
  • int compareTo( Biglnteger other )
    如果这个大整数与另一个大整数 other 相等,返回 0; 如果这个大整数小于另一个大整数 other, 返回负数;否则,返回正数。
  • static Biglnteger valueOf( long x )
    返回值等于 x 的大整数。

java.math.BigDecimal

  • BigDecimal add( BigDecimal other )
  • BigDecimal subtract( BigDecimal other )
  • BigDecimal multipiy( BigDecimal other )
  • BigDecimal divide( BigDecimal other RoundingMode mode )
    返回这个大实数与另一个大实数 other 的和、差、积、商。 要想计算商,必须给出舍入方式(rounding mode)。RoundingMode.HALF_UP 是在学校中学习的四舍五入方式(即,数值0到4舍去,数值5到9进位)。它适用于常规的计算。有关其他的舍入方式请参看 Api 文档。
  • int compareTo( BigDecimal other )
    如果这个大实数与另一个大实数相等,返回 0; 如果这个大实数小于另一个大实数,返回负数;否则,返回正数。
  • static BigDecimal valueOf( 1 ong x )
  • static BigDecimal valueOf( 1 ong x, int scale )
    返回值为 X 或 x / 10 scale 的一个大实数。

String类

String 的创建

  • 直接赋值。String a="123",不过这样赋值是将一个指针指向字符串常量,若字符串常量相同,通常不会复制新的字符串,这代表多个指针指向同一个字符串。

    String a = "123";
    String b = "123";
    String c = "123";	//a,b,c指向同一个字符串常量,也就是a==b,a==c,b==c
    
  • 实例赋值。

    char[] ch= {'1','2','3','4','5'};
    String a = new String(ch);	
    String b = new String(ch);	//由于是实例化,所以每个new的都是新的对象指针,所以a不等于b
    String c = new String(ch,2,2);	// String构造器还提供参数,这个表明是从第二个字符开始往后数4个字符,所以c是"34"
    

String的特点

  • String可以和其他类型相加,相加的时候会调用toString 方法,将类型转为字符串,然后相加。不能转换的就报错。"12"+2+2.3+"a"+0.7="1222.3a0.7"

  • 关于格式化,System.out.printf是一个格式化输出函数。字符串变量要格式话则是String.format方法。它一个参数提供格式化字符,一个参数提供值。

    String a =String.format("今天买了%d个桃子花了%.2f",50/4,10/3);
    

    此外关于日期格式化自行百度,个人感觉不是很重要(面试也基本不问)

  • java中正则表达式也是纯字符串,需要套用专用的方法才会解析为正则表达式。

    注意在java中,字符串中的所有特殊符号都要自行转义,所以反斜杠为\\所以,正则表达式的\d在java中是\\d。类似的正则表达式掺入反斜杠则是\\\\

String字符串常用方法

所有方法详情请看文档
java.lang模块无需import

String 支持使用+来拼接字符串,但java自身不支持运算符的重载

java.lang.String

  • char charAt( int index )
    返回给定位置的代码单元。 除非对底层的代码单元感兴趣,否则不需要调用这个方法

  • int codePointAt( int Index )
    返回从给定位置开始的码点。

  • int offsetByCodePoints( int startIndex, int cpCount )
    返回从 startIndex 代码点开始, 位移 cpCount 后的码点索引。

  • int compareTo( String other )
    按照字典顺序,如果字符串位于 other 之前,返回一个负数;如果字符串位于other 之后,返回一个正数;如果两个字符串相等,返回0。

  • IntStream codePoints()
    将这个字符串的码点作为一个流返回。调用 toArray 将它们放在一个数组中。

  • new String( int[] codePoints, int offset, int count )
    用数组中从 offset 开始的 count 个码点构造一个字符串。

  • boolean equals( Object other )
    如果字符串与 other 相等,返回 true。

  • boolean equalsIgnoreCase( String other )
    如果字符串与 other 相等(忽略大小写),返回 true。

  • boolean startsWith( String prefix )
    boolean endsWith( String suffix )
    如果字符串以 suffix 开头或结尾,则返回 true。

  • int indexOf( String str )
    int indexOf( String str, int fromIndex )
    int indexOf( int cp )
    int indexOf( int cp, int fromIndex )
    返回与字符串 str 或代码点 cp 匹配的第一个子串的开始位置。这个位置从索引0 或fromIndex 开始计算。如果在原始串中不存在str,返回 -1

  • int lastIndexOf( String str )
    int lastIndexOf( String str, int fromIndex )
    int lastindexOf( int cp )
    int lastindexOf( int cp, int fromIndex )
    返回与字符串 str 或代码点 cp 匹配的最后一个子串的开始位置。 这个位置从原始串尾端或 fromIndex 开始计算。

  • int length()
    返回字符串的长度。

  • int codePointCount( int startIndex, int endIndex )
    返回 startIndex 和 endludex - l 之间的代码点数量。 没有配成对的代用字符将计入代码点。

  • String replace( CharSequence oldString, CharSequence newString )
    返回一个新字符串。 这个字符串用 newString代替原始字符串中所有的 oldString。 可以用String 或 StringBuilder 对象作为 CharSequence 参数。

  • String substring( int beginIndex )
    String substring( int beginIndex, int endIndex )
    返回一个新字符串。 这个字符串包含原始字符串中从beginIndex 到串尾或 endIndex - 1的所有代码单元。

  • String toLowerCase()
    String toUpperCase()
    返回一个新字符串。 这个字符串将原始字符串中的大写字母改为小写 ,或者将原始字符串中的所有小写字母改成了大写字母。

  • String trim()
    返回一个新字符串。 这个字符串将删除了原始字符串头部和尾部的空格。

  • String join( CharSequence delimiter, CharSequence... elements )
    返回一个新字符串,用给定的定界符连接所有元素。


  • Boolean matches(String regex)

    验证字符串是否符合正则表达式,符合返回true

  • String replaceAll(String regex, String replacement)
    String replaceFirst(String regex, String replacement)

    根据正则表达式替换字符串,All是全部替换,First是替换第一个匹配的结果

  • String[] split(String regex)
    String[] split(String regex, int limit)

    根据正则表达式分割字符,正则表达式匹配的结果不会加入到结果中。

Java String Builder字符串构造类

仅开辟一个空间储存字符,在需要时再转为字符串。效率的字符串构建方式,单线程工作
若要在多线程工作的话,其替代类为StringBuffer,但效率略低。

简单讲,普通字符串由于是固定的长度,若要添加需要用用加号添加,这样的操作时间实际上比较长的。

若要频繁动态修改字符串,那么,就可以用StringBuilder。

java.lang.StringBuilder

  • StringBuilder()
    构造一个空的字符串构建器。 注意它的第一个字母是大写
  • int length()
    返回构建器或缓冲器中的代码单元数量。
  • StringBuilder append( String str )
    追加一个字符串并返回 this。
  • StringBuilder append( char c )
    追加一个代码单元并返回 this。
  • StringBuilder appendCodePoint( int cp )
    追加一个代码点, 并将其转换为一个或两个代码单元并返回 this。
  • void setCharAt( int i, char c )
    将第 i 个代码单元设置为 c。
  • ``StringBuilder insert( int offset, String str )`
    在 offset 位置插入一个字符串并返回 this。
  • StringBuilder insert( int offset, Char c )
    在 offset 位置插入一个代码单元并返回 this。
  • StringBuilder delete( int startindex, int endIndex )
    删除偏移量从 startindex 到 - endIndex - 1 的代码单元并返回 this。
  • String toString()
    返回一个与构建器或缓冲器内容相同的字符串

数组

初始化

数据的定义方式有两种

int[] a;    //java风格,推荐
int b[];    //c风格

但如果要使用数组的话,还需要对它进行赋值,它的赋值就是指定长度

a = new int[100]    //长度必须指定

指定长度后,数字数组元素初始化0,布尔数组初始化为false。对象数组初始化为null
初始化具体数值有两种方法。

//方法1
int[] a = {1,2,3};

//方法2
int[] a;
a = new int[] {1,2,3};       

其中new int[] {1,2,3} 被称为匿名数组。

引用与拷贝

若有一个已存在数组变量,赋值给另一个数组变量,则两个数组变量引用的是同一个数组。
若要拷贝数组给新的数组变量,则需要用到java.util.Array的copyOf方法

a = new int[] {1,2,3};
int[] b = a;    //b和a引用同一个数组

import java.util.*
int[] c = Array.copyOf(a,a.length);     //c是一个与a相同的新数组

此外,基本数据类型的赋值,确实是赋值行为,而且基本数据类型的变量是实实在在获得了一个值,而不是引用(虽然效果和python的引用自动复制相同)。

命令行参数

main函数的String args[]会读取命令行的参数。
比如命令行输入

java xx.java -a hello -b cccc

那么xx.java程序的arg会有如下数据

args[0]: "-a"
args[1]: "hello"
args[2]: "-b"
args[3]: "cccc"
多维数组

创建数组时添加多个[]即可创建多维数组。

int[][][] a={{{1,2},{1,2}},{{1,2},{1,2}}};

实际上,java只有一维数组,多维数组实际上是构建一个一维的引用数组,数组内的元素引用某个数组,然后这个数组又引用某个数组这样。
因为这种特性,所以可以构建不规则的数组

int[][] a = {{1,2,3},{1,2},{1,2,3,4}};
数组的其他使用

数组的很多使用都在java.util.Arrays类中,它提供很多实用方法。

java.util.Arrays

  • static String toString( type[] a )
    返回包含 a 中数据元素的字符串,这些数据元素被放在括号内,并用逗号分隔。
    参数:
    • a: 类型为 int、long、short、char、byte、boolean、float或 double 的数组。
  • static String deepToString( type[] a )
    返回包含 a 中多维数据元素的字符串,这些数据元素被放在括号内,并用逗号分隔。
    参数:
    • a: 类型为 int、long、short、char、byte、boolean、float或 double 的多维数组。
  • static type copyOf( type[] a, int length )
  • static type copyOfRange( type[] a, int start, int end )
    返回与 a 类型相同的一个数组,其长度为 length 或者 end-start,数组元素为 a 的值。
    参数 :
    • a 类型为 int、long、short、char、byte、boolean、float或 double 的数组。
    • start 起始下标(包含这个值)
    • end 终止下标(不包含这个值)。这个值可能大于a.length。在这种情况下,结果为0或false。
    • length 拷贝的数据元素长度。如果 length 值大于 a.length ,结果为 0 或 false ;否则,数组中只有前面 length 个数据元素的拷贝值。
  • static void sort( type[] a )
    采用优化的快速排序算法对数组进行排序。
    参数:
    • a 类型为 int、long、short、char、byte、boolean、float或 double 的数组。
  • static int binarySearch( type[] a, type v )
  • static int binarySearch( type[] a, int start, int end, type v )
    采用二分搜索算法查找值 v。 如果查找成功 ,则返回相应的下标值 ; 否则,返回一个负数值 r。 - r - 1是为保持 a 有序 v 应插入的位置。
    参数:
    • a 类型为 int、long、short、char、byte、boolean、float 或 double 的有序数组。
    • start 起始下标(包含这个值)。
    • end 终止下标(不包含这个值)。
    • v 同 a 的数据元素类型相同的值。
  • static void fi11( type[] a, type v )
    将数组的所有数据元素值设置为 V。
    参数:
    • a 类型为 int、long、short、char、byte、boolean、float 或 double 的数组。
    • v 与 a 数据元素类型相同的一个值。
  • static boolean equals( type[] a, type[] b )
    如果两个数组大小相同,并且下标相同的元素都对应相等,返回 true。
    参数 :
    • a、b 类型为 int、long、short、char、byte、boolean、float 或 double 的两个数组。

输入输出

标准输入输出

java输出的函数有三种
System.out.println() 输出一行,会添加一个换行符结束
System.out.print() 输出,不添加换行
System.out.printf() 支持格式化的输出

java的键盘输入略微麻烦一点,其过程为

import java.util.*;     //导入Scanner

Scanner in = new Scanner(System.in);
String in1 = in.nextline();   //输入一行,回车结束
String in2 = in.next();     //输入一个单词,任何空白字符结束
int in3 = in.nextInt();     //输入一个整数

此外Scanner的输入时是可见的,若要不可见,可以使用Console类

import java.io.*;   //导如Console
Console cons = System.console();
String username = cons.readLine("Username:");
char[] passwd = cons.readPassword("Password:");	//此时输入的内容是不可见的

java.util.Scanner

  • Scanner(InputStream in)
    用给定的输人流创建一个 Scanner 对象。
  • String nextLine()
    读取输入的下一行内容。
  • String next()
    读取输入的下一个单词(以空格作为分隔符)。
  • int nextlnt()
  • double nextDouble()
    读取并转换下一个表示整数或浮点数的字符序列。
  • boolean hasNext()
    检测输人中是否还有其他单词。
  • boolean hasNextInt()
    boolean hasNextDouble()
    检测是否还有表示整数或浮点数的下一个字符序列。

java.io.Console

  • static char [] readPassword(String prompt, Object ... args)
    static String readLine(String prompt, Object ... args)
    显示字符串 prompt 并且读取用户输入, 直到输入行结束。 args参数可以用来提供输人格式。
文件输入输出

文件输入也是一样使用Scanner

import java.nio.file.*	// Paths.get 处于java.nio.file模块中

Scanner in = new Scanner(Paths.get("miyflle.txt"), "UTF-8");
//必须要使用get获取文件流,直接使用文件名会报错

读取的方法和控制台输入一致

文件输出则需要使用PrintWriter类

import java.nio.file.*

PrintWriter out = new PrintWriter("myfile.txt", "UTF-8");

输出的方法也是print, printf, println

控制流程

使用{}即可构造块,在块内的参数无法作用到块外,块外参数可作用到块内。
块内外的参数名不能相同,否则会报错。

{
    int out=1;
    {
        int in=2;   //in 仅作用在内部
        System.out.printf(out);     //out 可作用在内部
        
        int out=3;  //会报错
    }
    System.out.printf(in);      //会报错
}

块的这种特性在下面的所有语句中都适用。

条件语句
if(condition) statement 
else if statement2 
else statement3         //statement 通常为一个块
while 循环
while(condition)
statement       //statement 通常为一个块

类似的还有do while

do
statement   //statement 通常为一个块
while(conditon)
for 循环

for循环和C++一致。

for(var;condition;varChange) 
statement   //statement 通常为一个块

需要注意的是,for内部定义的var作用域为statement,在外部是无法使用的,若需要使用,则需要在for之前定义var

{
    for(int i=1;i<10;i++){}
    System.out.printf(i); //报错
}
{
    int i=0;
    for(i=1;i<10;i++){}
    System.out.printf(i); //没有问题
}
for迭代

java也有用于迭代数组的for循环。

for(var:iterator) //类似于python的 for var in iterator
statement 

上述代码中的iterator通常都为数组。实际上任何迭代器都行。关于这部分可能要去看java核心编程第二部分

switch 选择
switch(condition)
{           //块,下面的冒号后面可以不加花括号
    case x:
        ...
        break;  //不添加break则会顺序运行下面的case
    ...
    default:
        ...		//不匹配任何x则运行此块
        break;		
}
标签

定义标签可以利用break用来跳出循环,但要注意标签需要放在需要跳出的块的前一行

while(1){   //外层
    label:
    while(1){   //中层    
        while(1){   //内层
            break label;    //直接跳出中层循环(虽然没有意义),但继续外层循环
        }
    }
    statement;      //跳出后会执行该statement
}

同时标签也可以用goto操作,但不推荐
同样continue也可以搭配标签,和break不同的是,它会跳到标签标注的循环的第一行

while(1){   //外层
    label:
    while(1){   //中层    
        statement;      //跳出后会执行该statement
        while(1){   //内层
            continue label;    //直接跳回中层循环(虽然没有意义)开头
        }
    }
}

类概述

这里的面向对象概念就不说了,只提供基本知识。

main方法类

main函数处于一个类中,该类是程序的入口,且mian函数所在文件的文件名与mian函数所在类的类名一致。

public class MainClass      //文件名为MainClass.java
{
    public static void main(String[] args)
    {
        ...     //主函数代码在这
    }
}

实际上,文件名要和public 类的名字相匹配,一个文件只能有一个public类

创建类
class ClassName
{
    fields      //通常用于定义类变量以及一些操作
    ...
    constructors    //构造函数,可以有多个
    ...
    methods         //方法
}
构造器
class A
{
    A(){    //构造器
        ...
    }
}
  • 构造器与类同名
  • 每个类可以有一个以上的构造器(重载)
  • 构造器可以有 0 个、1 个或多个参数
  • 构造器没有返回值
  • 构造器总是伴随着 new 操作一起调用
  • 若不显式的编写构造器则系统自动生成一个构造器,该构造器会把所有未赋值的参数赋值为默认值
创建类对象

创建对象有两种方式

classType var = new classCreateFun();   //使用构造函数创建

classType var = class.factoryFun();     //使用工厂函数创建

需要注意的是,构造函数以及工厂函数会生成类实例。但是classType var创建的是指向对象的引用变量,不赋值给该变量前它不包含任何类实例。
你可以直接使用类实例如Fun(new classCreateFun()),但不可以在赋值前使用类引用变量。

只访问类但不修改类的方法称为访问器方法,修改的方法叫做更改器方法。
访问器不应该返回可变对象

析构器

实际上java会自动进行垃圾回收,所以java不支持显式的创建析构器。但可以在任何类中添加finalize方法,他会在垃圾回收之前运行。但是已经不推荐使用了

java的垃圾回收比较深,而且牵扯到java的内存堆与栈,细节还是自行百度吧。

类细节

this指针

在方法中定义的变量为局部变量,在类中fields中定义的变量为内部变量,为了区别两种变量。java提供了this引用,它指向内部变量。(this就是python的self)

class A{
    private int a=10;
    public A(){ this.a++; }
    public int fun(int b){
        int c = 5;
        return c+b+this.a;
    }
}
pubilc,private域

pubilc和private的基本概念就不多讲了 。一句话就是一个是公共的,一个私有的。

注意类的方法可以访问属于同一个类的另一个实例的private 数据(私有域)

calss A{
    private int a=10; 
    public void fun(A b){
        System.out.print(this.a > b.a);
    }
}
final域

还有一种是final域,即最终常量域。它需要在创建时就初始化,在类中的话就是必须要在构造器中初始化。并且该域在初始化之后无法再被修改。
但是若是可变类的话,类内部的数据还是可变的,只是引用该类的引用名不能指向其他对象。实际上不推荐final指向可变对象

class A{
    private final string name="123";
    private int data=1;
    public A(){};
    public void funa(string a){this.name=a;}     //报错,不能赋值
    public void funb(int a){this.data=a;}
}

final A test = new A();
A testb = new A();
test.funb(123);     //可用,改变了final对象内部数据
test=testb;         //报错,不能指向新对象
static静态域与静态方法

定义static域之后,域中的变量所有类共享。可以有多个实例,但是只会有一个静态域。简单讲,多个实例对象指向同一个静态域。

class A{
    static int a = 0;
    public A(){a++;}
    public void show(){System.out.print(this.a);}
    public void add(){this.a++;}
    
}

A a1 = new A();
A a2 = new A();
a1.show();          //输出5
a2.show();          //输出5
a1.add();
a2.show();	//输出6

上述是静态变量,还有种常用的是静态常量,常用于定义某种常量,和C++的DEFINE很类似。(通常为public static final)

对应静态域,还有静态方法。所谓静态方法就是存在于类中,但是无法操作类的实例域,没有this指针,所有类、实例共享的方法。也可以说是存在于类中的普通函数。但是静态方法可以访问类中的静态域。 实际上,由于java是完全面向对象,所以没有正常函数一说,所以提出了静态方法。

class A{
    static int a =0;
    public A(){}
    public static int fun(){return a;}
}

System.out.print(A.fun());      //输出0
A a = new A();
System.out.print(a.fun())       //输出1

注意main函数也是静态方法,而每一个public类都可以有一个mian方法。和python的if __name__ == '__main__'一样,只有在运行该文件时才会自动启动main。

方法参数

java中所有的方法参数都是进行复制操作,也就是全部都是形参。

calss A
    public A(){}
    public fun(int a){ a= a*3;}
}

int test=10;
A b = new A();
b.fun(test);       
System.out.print(test);    //输出10

上述代码 test 的值复制给 a ,此时 test 和 a 指向不同的具体对象(基本数据类型的特点)。于是a的操作不会影响test。

但是若对于可变对象,数组等引用效果就不一样了。虽然也是复制,但是它所进行的复制是赋值引用对象的引用变量(也就是指针)。

class A{
    private int a=0;
    public A(){}
    public void fun(){this.a+=10;}
    public void geta(){return this.a;}
}
class B{
    public B(){}
    public void fun(A a){a.fun();}
}

A testa=new A();
B testb=new B();
B.fun(testa);
System.out.print(testa.geta());     //输出10

虽然是复制,但是类B.fun中的a和变量test都引用同一个对象,所以最后a对于对象的操作会反映到test中。

多参数

python 的多参数有 *args 和 **kws 。java也有类似的表现形式,它使用三个省略号...来表示可以接受多个参数。
不过需要注意的是,接受的参数类型都是要指定的。
实际上这样做的话就是讲多个参数转化一个数组而已。

class A{
    public A(){}
    public int fun(int... a){for (int q:a){System.out.print(q);}}
}
this特殊用法

this是指实例自己,类似于python的self。但this单独调用的时候会调用this所属类的构造器。

class A{
    private int a;
    public A(int a){ this.a = a;}
    public A(){this(10);}       //无参的构造器使用this调用有参数的构造器
}

A test = new A()        //调用A()构造器后会自动调用 A(10)构造器
初始化块

java提供一个初始化块,该块在任何构造器构造之前都会运行,可以存放公用初始化的代码。

class A{
    private int a;
    private static int id=0;
    {
        id++;       //调用任何构造器都会运行
    }
    public A(){this.a=10;}
    public A(int a){this.a=a;}
    public static int getId(){return id;}
}

A test1=new A();
A test2=new A(2);
System.out.print(A.getId());        //输出2 

java 和 python 一样,提供很多包来提供很多不同的功能。

导入包

使用一个包中的某一个类,最直白的方法是将类所属的包的完整名称写出来。

java.time.LocalDate today = java.time.LocalDate.now();

但是这样很繁琐,所以需要简单方式,也就是导入包。
和python一样,导入一个包需要使用import关键字。
导入一个包中的所有类的方式是使用通配符 * ,例如import java.time.*。这样就能导入time包中的所有类了。
但需要注意的是import一次只能导入一个包,且该包不能包含子包,仅能包含类。否则会报错

import java.time.*;
import java.*;          //包含多个包,错误
impoer java.*.*;        //包含多个包,错误

LocalDate to = LocalDate.now();

同时与python不同的是,import导入某特定类后,可以直接使用类名。

import java.time.LocalDate;

LocalDate to = LocalDate.now();     //没有问题

而java的内置包都存在java包以及javax包中。

命名空间的冲突

若几个包中都有名称相同的类时,用通配符导入时不会有问题,但是使用类时则会报错

import java.util.*;
import java.sql.*;         

Date test = new Date();         //报错,两个包都有Date类

解决这个方法有两种,第一种是使用类的全名,例如java.util.Date。第二种是若只是用某一个包的类时,可以import导入特定的类,例如import java.util.Date

静态导入

由于java是完全基于类的语言,他没有所谓的函数一说,所有的都是类方法。但是那种通用的函数,例如输出函数print,也是存在于java.lang.System类中的。
这种通用函数在java中也就是类中的静态方法。

为了简化静态方法的导入(即不用每次都写所在包与所在类)java提供了静态导入import static。静态导入之后就可以直接使用静态方法名了。

import static java.lang.System.*;

out.print("hello");     //直接调用静态域out的print方法
exit(0);            //即静态方法System.exit

当然也可以静态导入特定静态方法或者静态域。

构建自己的包

要构建自己的包,只需要在文件开头加入package关键字与包名即可。

package com.spring.java     

public class A{        //A类现在存在于com.spring.java包中
    ... 
}

实际上,在没有加入package时,文件的类也会放入到一个默认的包中。这个包没有名字,也就没法导入。

此外,包所在的源文件应该放在对应包名的文件夹目录中。例如上述的代码的源文件名称为A.classA.java。它所在的完整路径应该是com/spring/java/A.classcom/spring/java/A.java。文件树如下

.(根目录)
└─  com/
    └─  spring/
        └─  java/
            ├─ A.java
            └─ A.class

在根目录放入java文件并导入包后,java会自动找寻包中的类
此外还有就是jar的包,这个是目录包文件的封装,它的细节将会在后面讨论。

包作用域

public 的 变量和类能够被任何其他类使用;
private 的 变量 只能被定义该变量的类使用,且private不修饰类;
未标记public或private的变量或类能够被同一个包中的其他类使用,未定义包则是默认包。

包路径

和python类似,java搜寻包的路径默认个有java根目录,当前目录。当然用户是可以添加包的搜索路径的。方法有两种

  • 使用java -classpath path指令设置,注意path中可以有多个路径,unix使用:分开,windows使用;分开。
  • 添加环境变量CLASSPATH
包路径所在的问题

在源文件中导入的包名会和文件目录关联,而且包的搜索路径仅在默认的包路径以及工作路径中搜索的。
举个例子,System包存在于默认包路径中。而自己构建的包则需要放到工作路径当中,否则会找不到包。
例如下列文件树

.(根目录)
└─  com/
    ├─  spring/
    │   └─  java/
    │       ├─ A.java
    │       └─ A.class
    └─  hello/
        ├─ Hello.java
        └─ Hello.class

这种情况下,仍然要从根目录运行 java com/hello/Hello.java。否则com.spring.java包将会找不到。
编译器编译文件,解释器加载类
实际上若不使用包中的类的话即使导入不存在的包也不会报错,但是一旦使用不存在的包中的类就会报错

java注释工具javadoc

自行百度,不重要

继承

类的继承的基本概念不再赘述。
java 的继承都是公有继承。
java 的继承使用 extend 关键字

class A {...}
class B extend A {...}     //B 继承与 A。 A是超类,B是子类
重写方法

重写方法就是在子类中使用同名方法,即可重写方法。java和python一样,自动处理多态。
若是要在子类中调用超类的方法的话,需要使用super关键字,它和this类似。
super能够指向超类的方法与构造函数。

class A{
    public A(){}
    public void fun(){}
}

class B extend A{
    public B(){
        super()         //调用超类的构造器
    }     
    
    public void fun(){
        super.fun()     //调用超类的方法
    }
}
类型指定与转换

超类的引用变量可以引用子类实例,但超类的变量调用子类方法且该方法不存在于超类时会报错
java方法调用的过程是列出所有候选方法然后搜索,具体细节请百度
当然因此,类型转换也可以使用在有继承的类中。超类可以直接引用子类,但是子类引用超类就需要进行强制类型转换。不过java不提倡这个强制类型转换。并且这种类的强制类型转化仅存在于继承类之间。

class A{}
class B extend A{}

A testA;
B testB;

testA = new B();        //超类引用子类没有任何问题
testB = (B) new A();    //将超类A转化为子类B

testB = (B) new String();   //String类不属于AB继承中,会报错

java 提供了检测是否属于某类的运算关键字instanceof,和python的isinstance函数类似

抽象方法与抽象类

java 提供了abstract 关键字来设置某个类或者某个方法为抽象形式。
在某类中定义抽象方法后,该类也必须定义成抽象类。
抽象方法不需要提供实现,只要定义即可。继承抽象类的子类需要实现抽象方法。
若子类仍然存在抽象方法,那么子类仍然是抽象类。直到所有抽象方法都实现了。
此外抽象类是无法实例化的。

abstract class A{
    public A(){}
    public abstract void fun();
}

class B extend A{
    public B(){}
    public void fun(){}
}

A a= new A();   //报错,抽象类不能实例
final 和 protect

在类添加前缀final会使得该类不能被继承。
或者在类方法添加final,会使得子类无法被重写。实际上,在类前添加final会使得类内的所有方法添加final前缀。

class A{
    public final fun(){}
}
final class B extend A{
    public fun(){}      //报错,无法重写
}
class C extend B{}      //报错,无法继承

在方法或者属性前添加protected关键字,会使得该方法或者属性能够被子类的方法访问。但是实例无法访问。

class A{
    protected int a=10;
    protected void fun(){}
}
class B extend A{
    protected void fun(){
        super.a=15;         //可以访问
        super.fun();        //可以访问
    }
}

A testA = new A();
B testB = new B();

testA.a;        //报错,不能访问
testB.a;        //报错,不能访问
testB.fun();
元祖类Object

所有没有指定extend的类都默认继承于元祖类Object。
Object有几个很重要的方法。

  • Object.equal():判定应用变量与本引用的类是否是同一引用。是的话返回false
  • Object.hashCode():返回类的散列值,要求相同的对象返回相同的散列值。
  • Object.toString():将对象的属性转化为字符串并返回。通常println函数会自动调用对象的该方法并输出。和python类似

类细节杂项

列表ArrayList

数组由于需要制定大小并不方便。所以java后面又提供了动态数组–列表。
和python类似,它是一个动态的有序数组。但不同的是,java列表需要指定列表类型。
而且java的列表是种泛型类,泛型类的具体介绍会在后面介绍。
列表指定类型需要使用<>来指定,且类型必须是对象。

class A{}

ArrayList<A> c = new ArrayList<A>           //自定类A的列表

ArrayList<A> d = new ArrayList<>()
//javase7提供简化的构造方式

当然列表也可以初始大小,但是初始大小内部的元素是空的,而不是有默认值。
简单讲就是ArrayList会开辟内存空间,而不进行默认赋值

ArrayList<A> a = new ArrayList<A>(100)  //指定列表大小为100

ArrayList支持迭代方法。

ArrayList常用方法有:

  • .add(item)添加元素,和python的append类似。此外add(int index,item)能够在特定的位置添加元素
  • .ensureCapacity(int length)固定大小,使用方法后会使列表会创建大小为为所指长度的内存空间。和构造列表时的方式类似。
  • .size()返回列表大小(元素的数量)
  • .trimToSize()调整列表的内存空间为列表的实际大小,回收多余内存。
  • .set(int index,item)由于列表是一个类,所以不能用[]方式设置元素。该方法等价于a[index]=item。
  • .get(int index)等价于a[index]
  • .toArray(0)将列表转化为数组
  • .remove(int index)删除特定位置的元素

当方法需要使用列表时,只能定义为ArrayList而不能再指定类型。这样会导致所有的列表传递后会变为原始列表(无类型)。这样再传递给有类型的列表时会产生警告(不是错误)。若认为这个警告没有问题的话可以加上修饰@SupperssWarnings("unchecked")来取消警告。实际上泛型类都会哟这种状况。

class A{
    public void funa(ArrayList list){...}
    public ArrayList funb(){...}
}


ArrayList<A> test = new ArrayList<>();
A a = new A();

a.funa(test);   //传入时不会有警告,只有在运算发现类型错误时才会报错
ArrayList<int> test2 = a.funb();
//会有警告,而不是报错

@SuppressWarnings("unchecked") ArrayList<int> test3 = a.funb();   //不再有警告
对象包装器与自动装箱

包装器指的是能够将对象,数值包装成某种特定状态,类型的一种类。通常包装器是不可变类,并且大多支持自动装箱和拆箱。
所谓自动装箱指的是在调用包装器,但不进行显示包装时,它能够自动的将对象打包成包装器的特定状态。
自动拆箱类似,表示在使用包装好的对象时,它会自动拆解对象变成能够使用的状态。
java中自带的,常用的包装器有IntegerLongFloatDoubleShortByteCharacterVoidBoolean。它们对应那些基本数据类型。

在使用列表时,由于类型指定必须是类,所以ArrayList是不成立的。这个时候内置包装器就十分有用了。

ArrayList<Integer> a = new ArrayList<Integer>()         //Integer能够自动将传入数据包装成int类型

a.add(3)        //实际上包装器自动运行了.valueOf()方法,即这句话等价于a.add(Integer.valueOf(3))
                //这就是自动装箱
                
int b = a.get(0);    //等价于 a.get(0).intValue() ,自动拆箱

当然,包装器在其他运算中作用也是一致的,自动装箱和拆箱在基本运算中也会自动运行。并且包装器会自动类型转换,并且是低精度向高精度转换。

Integer n = 3;
n++;        //使用起来和int没有区别

Double m = n;   //n的值自动拆箱,然后被Double包装器自动装箱,所以这样运算不会有问题

包装器的通用方法:
.toString转为字符串 , parse将字符串转化为对应类型 , valueOf将对象尽可能的转化为对应类型

枚举类

实际上emnu类型是一个类,而且它比较特别,枚举内部的一个对象就是一个枚举实例。而且因为枚举是个类,所以可以在枚举中添加一个方法变量。

public enum Test{
    A(1),B(2),C(3);         //构建了3个实例对象,而且每一个都是一个构造函数来构建的
                            //枚举实例括号内的值就是对应对象的值。
    private int num=3;
    public int getNum(){return this.int}
}

实际上枚举类和python的字典有点类似。

反射

反射说的是java提供的一个库(java.lang.reflect),叫做反射库,它里面提供了很多实用的工具类与方法。之所以称为反射,是因为该库提供的类能够显示(反射)出一个类的各种详细信息。

Class类

需要将关键字 class 和类 Class 区分开
java中的每一个类都包含了很多信息与属性,而同时java提供了一个Class类用于获取一个类,实例的相信信息。
Class类相当于记录类信息的类,从Class里可以获取到变量的数量,类的名称,构造函数信息,等等等等。
而Object类也提供了一个.getClass()方法来返回类的Class类,同时还提供.class属性返回Class类。

class A{}
A a = new A();
Class test = a.getClass();
test.getName();             //Class提供很多有用的方法,getName就能获取实例所属类的完整名称(包含包路径)。

而且Class类可以获取类型的信息,例如Class a = int.class会得到一个int类型的Class类。
实际上Class类时个泛型类,所以任何类型都可以返回。

其他类

除了Class类,反射还提供 Filed,Method 和 Constructor 类,分别能够获取一个类的域信息,方法信息以及构造函数信息
反射还提供Array类,用来专门获取数组信息以及操作数组的。
反射中的类通常都有一个setAccessible(boolean choice)方法,设置它可以让反射类查看类中的private变量,方法等。
类的所有具体方法请查看文档。

接口

所谓接口,就是一种只定义方法与常量。但不做实现的抽象类,但是不使用class关键字而是使用interface关键字,这是为了区别类和接口

接口是不能使用与实例化的,它必须由其他类来实现(关键字 implements )接口内部定义的方法。并且实现中方法名,参数数量类型都要完全和接口中定义的一致。

interface A{        //接口A
    int M=10;
    void fun(int a);
}

class B implements A{
    public void fun(int a){return a;}
}               //类B实现了接口A
接口特性

首先接口不是类,它是为了将定义和实现完全区分开而出现的。
一个类只能继承一个类,但是能够实现多个接口。 并且继承和实现能够同时出现。
硬要按类的角度来说的话,接口就是比抽象类更加抽象的类(实际上接口出现后抽象类用的就少了)

接口的变量和方法可以省去修饰前缀,因为所有的接口变量都是public static final的,而方法都是public的。
接口变量的修饰不能改成其他,否则会报错。方法可以添加static和final。

最新的javaSE让接口的方法可以提供一个默认行为,只要添加关键字default,这样在类实现的时候可以不用实现所有方法。这个在GUI回调事件时很有用。

public interface A{
    public void atestfun(int a , String b);
    default void btestfun(int a){return a;}
}

public interface B{}

class C{}

class D extend C implements A,B{
    public void atestfun(int a, String b){return a;}
}
接口冲突

当然多个接口的方法名称有相同时,且两个方法都有默认行为,并且实现的类不实现接口(使用默认方法),那么会产生冲突。若一个有默认一个没有,那么有默认的接口会覆盖没默认的。而冲突需要程序员自己决定调用哪个默认接口,或者实现该接口。此外,当超类和接口有相同的方法时,超类的方法会覆盖接口(类优先)。

interface A{
    default int a(){return 12;}
}

interface B{
    default int a(){return 15;}
}

class testA implements A,B{
    int a(){A.a()}        //A,B都有a方法,这里指定使用哪个默认方法,不产生冲突

class test implements A,B{
    int a(){return 12;}        //重新实现接口不产生冲突
}
接口实例

接口的一个实例就是克隆。由于java是全引用语言,所以指向可变类时,多个变量是指向同一个对象。那么这个时候需要指向新对象就需要clone方法了。

java的Object类提供clone方法,但是它的赋值是浅拷贝,若类中还有对象指向新对象,这一块的复制是无效的。

因此,对象还需要进行深拷贝。而java提供了Cloneable接口,它里面有方法clone()。用户可以自己设计clone方法来进行深拷贝。此外,clone是泛型方法。
同时,

class A{
    public A clone() throws CloneNotSupportException        //必须有这个异常处理
    {
        ...     //拷贝内容,由于类的克隆要返回类本身的类型,所以clone接口是泛型
                //但是在实现上需要指定类型
    }
}

lambda 表达式

java的lambda和py的差别很大。
lambda 表达式的格式如下:

(参数类型 参数名,...) -> {函数内容}         //完整形式,函数内容可以有多行,单行内容可以省去花括号

(参数名,...) -> {...}      //多个参数的类型可以推断得到时可以省略参数类型

参数名 -> {...}    //一个参数时,参数类型可以推断那么可以省略括号

() -> {...}     //不需要参数时小括号不能省略

此外,如果lambda的内容是调用某个类的某个方法时,有以下简化书写形式

System.out::print       //相当于 x -> System.out.print(x)
Math::pow               //相当于 (x,y) -> Math.pow(x,y)

//该书写形式称作方法引用,不过它是一种语法糖,简化操作而已。同时该书写形式支持三种类型
// object::instanceMethod
// Class::staticMethod
// Class::instanceMethod

方法引用中可以引用new,表示引用类的构造函数。
三种类型中前两种方法需要多少参数则lambda就要传入多少参数。而对于第三种情况,第一个参数会认作类的实例。 举个例子:

class A{
    public A(int a){}
    public method(int a,int b){}
}

A::method;      //等价于 (a,b,c) -> a.method(b,c)
A::new;         //等价于 (a) -> return new A(a)
lambda 用途

lambda是一个匿名函数,java中它大多是用在函数回调之中。
调用回调的方法中,总是需要传入一个接口,然后在之后调用接口中的方法。
那么lambda就可以不需要实现一个类来直接实现一个接口。

interface A{
    void fun(int a,int b);
}

A test = (a,b) -> System.out.print(a+b);    //定义lambda函数,实现了接口A的方法
C.fun(test);        //设置lambda作为回调(假定C.fun中定义了接口A.fun的回调)

通常来说,lambda实现的接口有且只有一个抽象方法,同时java8中称这种接口为函数式接口。
同时,java8提供了简化的调用形式

interface A{
    void fun(int a,int b);
}

C.fun((a,b) -> System.out.print(a+b));      //直接在函数中定义

通常来说,大多时候lambda都是实现函数式接口的,在其他场合中用的比较少。(大概)

lambda的变量作用域

在一个回调的实现中,可能会有额外的参数,例如下面的方法

interface A{}
public static void testFun(int a,int b){
    A action = () -> System.out.println(a+b);
    C.fun(action); 
}

那么在调用action的时候 , 函数中的a和b可能已经消失了。不过java的lambda会储存它的参数,即使消失了参数也会存在,直到下一次回调储存新参数。这个在java中叫做闭包。

但是闭包不是什么情况都可以的。闭包有如下2个限制,

  • 闭包的参数不能在lambda中修改
  • 闭包的参数不能再lambda外改变
    具体来说是,lambda的闭包参数总是final的,虽然不一定添加final修饰,但是传入的一刻起就不能再进行修改了。
interface A{}
public static void testFun(int a,int b){
    a++;        //报错,不能在外部修改
    A action = () -> {
        a++;        //报错,不能在内部修改
        System.out.println(a+b);
    }
    C.fun(action); 
}

此外,lambda可以调用this指针,lambda的this指针指向的是lambda所在的类实例

interface B{int a=22;}
class A{
    private int a=15;
    void fun(){
        B action = () -> System.out.println(this.a);    //this 指向A而不是B
    }
}
回调编写

编写回调的过程具体来说就是:

  1. 编写函数式接口
  2. 编写调用回调的方法
    不过,java为了减少冗余的接口编写,它提供了通用的常用函数式接口。包含了有参数的接口,无参的接口,有返回的,没返回的等等。常用的接口有
接口名参数类型返回类型抽象方法名描述其他方法
Runnablevoidrun作为无参无返回的动作运行
SupplierTget提供一个T类型的值
ConsumerTvoidaccept处理一个T类型的值andThen
BiConsumer<T,U>T,Uvoidaccept处理T和U类型的值andThen
Function<T,R>TRapply有一个T类型参数的函数compose , andThen , identity
BiFunction<T,U,R>T,URapply有T和U类型参数的函数andThen
UnaryOperatorTTapply类型T上的一元操作符compose , andThen , identity
BinaryOperatorT,TTapply类型T上的二元操作符andThen , max By , minBy
PredicateTbooleantest布尔值函数and , or , negate , isEqual
BiPredicate<T,U>T,Ubooleantest两个参数的布尔值函数and , or , negate

当然,当上述接口不能满足时,那就需要自己编写接口了。

同时,java有一条标准的回调接口命名规范,在编写回调接口时推荐按照规范编写。

接口名参数类型返回类型抽象方法名
BooleanSupplierbooleangetAsBoolean
PSupplierpgetAsP
PConsumerpvoidaccept
ObjPConsumerT,pvoidaccept
PFunctionpTapply
PToQFunctionpqapplyAsQ
ToPFunctionTpapplyAsP
ToPBiFunction<T,U>T,UpapplyAsP
PUnaryOperatorppapplyAsP
PBinaryOperatorp,ppapplyAsP
PPredicatepbooleantest

其中p,q为 int ,long, double;PQ为Int,Long,Double

内部类

内部类指的就是在类内再定义类

class A{
    private int a=10;
    
    class innerA(){
        public void fun(print(a));
    }
}

内部类可以获取外部类的所有域,它内部操作是在构造函数中添加外部类的引用。
在使用this时,需要指定是指哪个类的this。即A.thisB.this
类似的,在外部类new内部类时,完整的写法是OuterObj.new InnerClass(),例如在A中B b = this.new B()

局部内部类

有了内部类之后,在方法中也可以定义类,这个称为局部内部类。
局部类不能用public和private说明,它只存在于方法中,外部无法看到它的存在
同时局部类的参数应该都为final(不必加修饰)

class A{
    public void fun(){
        class B{}       //B为局部内部类
    }
}
匿名内部类

当然为了方便,java又提供了匿名内部类。这个类在java8之前常被使用,因为它是回调的常用设置方式。但在java8之后被lambda以及函数式接口代替了。
匿名内部类的定义方式如下

class A {
    B test = new B(){           //B就是一个匿名内部类
        public void fun(){}
    }
}
静态内部类

若内部类仅仅是为了隐藏在外部类之内,内部类并不引用外部类对象,那么可以将内部类设为静态。
在内部类前加static修饰的话就变成了静态内部类。静态内部类不产生外部类的引用,可以减少内存消耗,在某些时候很有用。

class A{
    static class B{
        public void fun(){print(123);}
    }
}

代理

java的高级话题,对于应用开发程序员来说使用的很少。
等学完java基础再过来补充

异常

java的所有异常都继承于超类 Trowable ,该超类有两个子类 Error 和 Exception。
Error 表示程序运行发生错误,这种错误是程序内部引起,与编程无关,所以这类错误也很少见。
Exception 表示程序发生的异常,是编程导致,也是程序员要处理的东西。

基本的格式如下

class A throws XXXException,XXX{        //表明类可能会出现的异常
    
    ...
    throw new XXXException(...)      //异常是类,所以需要构造
    
    try{...}                        //try不多说
    catch(XXXExcepton | XXX | XXX e){....}      //捕获异常并处理,多个异常相同处理可以用|分开
    finally{}           //finally无论发不发生都运行
    
    try(FileClass res){...}     //和py的with类似,对于那种资源对象会自动处理关闭
}

断言

pass

日志

pass

泛型

也就是能够设定类型的类,其基本表达为

class A <T>
{
    private T a;
    private int b;
    
    public T fun(){return a;}
}

而且单独的方法也支持泛型,泛型方法能够存在于普通类中

class A{
    public <T> T fun(){}        //在普通类中的泛型方法需要加入<T>来表明该方法是泛型方法
}

此外接口也支持泛型,而接口在实现时就可以指定类型,或者不指定。

interface A<T>{
    T fun();
}

class B implements A<Integer>{       //实现整型接口
    public Integer fun(){...}
}

class C<T> implements A<T>{         //实现接口的类仍然是泛型
    public T fun(){...}
}
泛型限定

因为类型的不一定,在某些情况下,需要限定泛型支持的类型。这个时候就需要进行泛型的限定,其方式是继承能够支持的类或接口。
同时继承的类还能够进行进一步限定

class A{
    public void fun(){}
}

interface B{
    void fun2();
}

class C<T>{}

class D <T extends A & B>{      //泛型限定为类A和接口B,它需要支持类A的方法与设定
    public T fun3(){...}        //同时需要支持接口B的方法。
}
通配符

有时候设定了一个泛型,对于其的子类或者超类都无法使用

class A{void put}
class B extends A{}
class C<T>{}

C<A> a = new C<B>();        //报错,不属于同一泛型

这样导致每一个继承都需要写一个泛型,为了简化,java提供了通配符<? extends xx>, <? super xx>以及<?>
其中extend表示支持该类以及其子类,super表示该类以及其超类,?表示支持Object以及其所有子类。

而<?>和的区别在于T是指定一个特定的类型,并且在代码中可以使用,而?是不能用于申明类以及方法的。它只能定义好的泛型。

class A<T>{}
class B<?>{}    //报错,不能使用

A<?> a = new A<String>()        //通配符的一种常用方式。

实际上?用的很少,更多使用的是extends和super

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值