0%

final,static,this,super 关键字总结

final 关键字

final关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:

  1. final修饰的类不能被继承,final类中的所有成员方法都会被隐式的指定为final方法;
  2. final修饰的方法不能被重写;
  3. final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。

说明:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。

static 关键字

static 关键字主要有以下四种使用场景:

  1. 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()

  2. 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.

    静态代码块的格式是

    1
    2
    3
    static {    
    语句体;
    }

    一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

  1. 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。

    Example(静态内部类实现单例模式)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Singleton {

    //声明为 private 避免调用默认构造方法创建对象
    private Singleton() {
    }

    // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
    private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
    return SingletonHolder.INSTANCE;
    }
    }

    当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

    这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

  2. 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     //将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
    //如果只想导入单一某个静态方法,只需要将换成对应的方法名即可

    import static java.lang.Math.*;//换成import static java.lang.Math.max;具有一样的效果

    public class Demo {
    public static void main(String[] args) {

    int max = max(1,2);
    System.out.println(max);
    }
    }
  3. 补充内容:static{}静态代码块与{}非静态代码块(构造代码块)

    相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。

    不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。

    修正 issue #677:静态代码块可能在第一次new的时候执行,但不一定只在第一次new的时候执行。比如通过 Class.forName("ClassDemo")创建 Class 对象的时候也会执行。

    一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.

    Example:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Test {
    public Test() {
    System.out.print("默认构造方法!--");
    }

    //非静态代码块
    {
    System.out.print("非静态代码块!--");
    }

    //静态代码块
    static {
    System.out.print("静态代码块!--");
    }

    private static void test() {
    System.out.print("静态方法中的内容! --");
    {
    System.out.print("静态方法中的代码块!--");
    }

    }

    public static void main(String[] args) {
    Test test = new Test();
    Test.test();//静态代码块!--静态方法中的内容! --静态方法中的代码块!--
    }
    }

    上述代码输出:

    1
    静态代码块!--非静态代码块!--默认构造方法!--静态方法中的内容! --静态方法中的代码块!--

    当只执行 Test.test(); 时输出:

    1
    静态代码块!--静态方法中的内容! --静态方法中的代码块!--

    当只执行 Test test = new Test(); 时输出:

    1
    静态代码块!--非静态代码块!--默认构造方法!--

    非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。

this 关键字

this关键字用于引用类的当前实例。 例如:

1
2
3
4
5
6
7
8
9
10
11
class Manager {
Employees[] employees;

void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}

void report() { }
}

在上面的示例中,this关键字用于两个地方:

  • this.employees.length:访问类Manager的当前实例的变量。
  • this.report():调用类Manager的当前实例的方法。

此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。

super 关键字

super关键字用于从子类访问父类的变量和方法。 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Super {
protected int number;

protected showNumber() {
System.out.println("number = " + number);
}
}

public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}

在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 showNumber() 方法。

使用 this 和 super 要注意的问题:

  • 在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
  • this、super不能用在static方法中。

简单解释一下:

被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西

转载自:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/basic/final,static,this,super.md

java中Selenium配置环境

介绍

Selenium是一个用于Web应用程序测试的工具,在上软件测试的时候需要使用到这个工具,这个工具有python版和java版,python版的配置方法网上有许多,但是java版感觉写的比较少,所以记录一下。

讲解

一、下载Chrome谷歌浏览器驱动

我使用的是Chrome浏览器,所以需要下载Chrome谷歌浏览器驱动,驱动和浏览器有对应关系,如果驱动版本下载错误是使用不了的,所以需要先看一下浏览器版本。

找到设置当中的“关于Chrome”中可以看到浏览器的版本,我的版本是81.0.4044.129

java中Selenium配置环境01

驱动下载可以到http://chromedriver.storage.googleapis.com/index.html进行下载

java中Selenium配置环境02

java中Selenium配置环境03

因为我是windows系统所以下载chromedriver_win32.zip,下载后解压可以得到一个chromedriver.exe。如果下载太慢也可以到淘宝镜像https://npm.taobao.org/mirrors/chromedriver/进行下载

二、创建java的Maven项目

将chromedriver.exe放在resources文件夹下

java中Selenium配置环境04

在pom.xml下写下下面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.zhouning</groupId>
<artifactId>FunctionTest</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
</dependencies>
</project>

下载jar包的版本可以参考maven的仓库,我选择的是3.141.59版本,仓库地址:点这里

java中Selenium配置环境05

最后创建一个Test类,看看是否配置成功

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe");// chromedriver服务地址
WebDriver driver = new ChromeDriver(); // 新建一个WebDriver 的对象,但是new 的是谷歌的驱动
String url = "http://www.baidu.com";
driver.get(url); // 打开指定的网站
driver.navigate().to(url); // 打开指定的网站
}
}

结果展示:

java中Selenium配置环境06

总结

因为在学习软件测试,所以Selenium深入的使用,目前并不知道,后续有再写一些。

java中的代理

简介

在学习spring的时候学习到了代理相关的知识,所以记录一下。代理的作用是帮助业务更加简单专一,而一些公共的东西交给代理来完成。比如:我们写Service的时候,需要在里面编写增删改查四个方法,并且调用这四个方法的时候都需要给出日志,这个时候我们就可以使用代理来完成,让Service中只专注于增删改查,而代理关注于写然日志。java的代理有静态和动态两种,一般使用动态更好。

代理讲解

提出问题

假设我们有一个需求,需要编写一个UserService,里面要有增删改查四个函数,在调用UserService的时候我们需要完成日志的输入。

一般情况下我们是这样实现的,先编写一个UserService的接口:

1
2
3
4
5
6
7
8
9
/**
* @author zhouning
*/
public interface UserService {
public void add();
public void update();
public void delete();
public void search();
}

然后再实现这个接口,并且在实现里面写一下日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @author zhouning
*/
public class UserServiceImpl implements UserService {
@Override
public void add() {
log("add");
System.out.println("增加用户");
}

@Override
public void update() {
log("update");
System.out.println("更新用户");
}

@Override
public void delete() {
log("delete");
System.out.println("删除用户");
}

@Override
public void search() {
log("search");
System.out.println("查询用户");
}

/**
* 日志
* @param methodName
*/
public void log(String methodName){
System.out.println("调用了"+methodName+"方法");
}
}

但是这样似乎有一些不好,因为UserService应该专注于增删改查的业务,这种日志方法写在里面,就有一种不纯粹的感觉,所以这个时候我们引入了代理,使用代理来帮助我处理日志问题。

静态代理

更改UserServiceImpl中代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @author zhouning
*/
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加用户");
}

@Override
public void update() {
System.out.println("更新用户");
}

@Override
public void delete() {
System.out.println("删除用户");
}

@Override
public void search() {
System.out.println("查询用户");
}
}

创建对应的代理类UserServiceProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* @author zhouning
*/
public class UserServiceProxy implements UserService {

UserService userService;
@Override
public void add() {
log("add");
userService.add();
}

@Override
public void update() {
log("update");
userService.update();
}

@Override
public void delete() {
log("delete");
userService.delete();
}

@Override
public void search() {
log("search");
userService.search();
}

/**
* 日志
* @param methodName
*/
public void log(String methodName){
System.out.println("调用了"+methodName+"方法");
}
}

​ 这样我们在实现UserService时,我们只需要关注业务的部分。但是我们发现这样的静态代理,有一个缺点,那就是类变多了增加了工作量,并且如果又有另外一个service有类似的需求,又需要创建另外一个service相关的接口,再实现另外一个代理类,比较麻烦,所以也就有了我们的动态代理类。

动态代理

这里讲的动态代理是基于jdk动态代理实习,相关的内容为InvocationHandler接口和Proxy类。

  • InvocationHandler,官方文档说明上介绍:“InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

    Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.”(翻译:InvocationHandler是由代理实例的调用处理程序实现的接口。 每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,该方法调用将被编码并分派到其调用处理程序的invoke方法。)

    InvocationHandler 里面有一个方法:

    1
    2
    Object	invoke(Object proxy, Method method, Object[] args)
    //Processes a method invocation on a proxy instance and returns the result.处理代理实例上的方法调用并返回结果

    理解一下就是我们将需要代理的对象、方法、参数传入invoke,然后再invoke里面调用代理实例的处理程序实现代理功能,其实跟上面的静态代理思想类似,比如在UserServiceProxy中调用代理对象userService相对应的处理程序

  • Proxy,官方文档说明上介绍:“Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.”(翻译:Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。)

    Proxy中提供了一个静态方法

    1
    2
    static Object	newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    //Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. 返回指定接口的代理类的实例,该实例将方法调用分派到指定的调用处理程序。

代码实现上,先创建一个ProxyInvocationHandler实现InvocationHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.zhouning.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {
//目标对象,需要代理的对象
private Object target;

public void setTarget(Object target) {
this.target = target;
}

/***
*
* @return 代理对象
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

/***
*
* @param proxy 代理类
* @param method 代理类的调用处理程序方法对象
* @param args 参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return Object;
}

/**
* 日志
* @param methodName
*/
public void log(String methodName){
System.out.println("调用了"+methodName+"方法");
}
}

调用:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//设置代理对象
pih.setTarget(userService);
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}

这样我们就实现了动态的代理,似乎来说动态代理比静态代理要复杂一些,那么为什么不用更加简单一些的静态代理呢?首先是我们可以看到动态代理处理上更加简洁,其次是动态代理可以处理一类的业务逻辑,加入说下次有另外一个service需求,我们基本上不需要重写一个代理类,直接使用现在这个动态代理类就行,减少了工作量。

总结

在学习spring_aop之前先学习了一下代理的内容,做个小结养成好习惯。

java中try-catch-finally

1.try-catch-finally

  • try 块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch 块: 用于处理 try 捕获到的异常。
  • finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

在以下 4 种特殊情况下,finally 块不会被执行:

  1. 在 finally 语句块第一行发生了异常。 因为在其他行,finally 块还是会得到执行
  2. 在前面的代码中用了 System.exit(int)已退出程序。 exit 是带参函数 ;若该语句在异常语句之后,finally 会执行
  3. 程序所在的线程死亡。
  4. 关闭 CPU。

注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
}

如果调用 f(2),返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。

2.使用 try-with-resources 来代替try-catch-finally

面对必须要关闭的资源,我们总是应该优先使用try-with-resources而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。

Java 中类似于InputStreamOutputStreamScannerPrintWriter等的资源都需要我们调用close()方法来手动关闭,一般情况下我们都是通过try-catch-finally语句来实现这个需求,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//读取文本文件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}

使用Java 7之后的 try-with-resources 语句改造上面的代码:

1
2
3
4
5
6
7
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}

当然多个资源需要关闭的时候,使用 try-with-resources 实现起来也非常简单,如果你还是用try-catch-finally可能会带来很多问题。

通过使用分号分隔,可以在try-with-resources块中声明多个资源。

1
2
3
4
5
6
7
8
9
10
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}

转载自:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Java基础知识.md

介绍

Java 泛型(generics)是 JDK1.5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型,在此之前java都是使用object来进行完成这种功能,但是object的向下转型会带来一些类型转化的问题,所以才加入了泛型。

讲解

一、泛型的引入

在jdk1.5以前想要实现类似于C++模板的功能时,一般都是使用object实现,如:实现一个point类,里面有x、y两个数据成员,但是对于x、y类似是不确定的,有时候需要int型的x、y,有时候需要float型的x、y。其实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Point{
private Object x;
private Object y;

public void setX(Object x) {
this.x = x;
}


public void setY(Object y) {
this.y = y;
}


public Object getX() {
return x;
}


public Object getY() {
return y;
}
}

这种做法是基本满足使用要求,但是如果在某些情况下,很容易发生Object向下转型带来的问题,如以下代码:

1
2
3
4
5
6
7
8

public static void main(String[] args) {
Point point = new Point();
point.setX(1);
point.setY(2);
Float x = (Float) point.getX();
Float y = (Float) point.getY();
}

运行时就会发错误(如下图),即类型转化所带来的问题,并且这种错误,在编译时不会显示出来,只会在运行时显示,所以针对这种情况java引进了泛型。

所以如果使用泛型改写上述代码就应该是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Point<T>{
private T x;
private T y;


public void setX(T x) {
this.x = x;
}


public void setY(T y) {
this.y = y;
}

public T getX() {
return x;
}


public T getY() {
return y;
}
}

使用起来就变成了这样Point<Integer>point = new Point<>(),如果这时候调用如果类型不对,会在编译的时候就爆出错误(如下图),方便开发人员及时的处理错误。

泛型2

另外如果申明了泛型,但是使用时不加<类型>,java还是会把里面的T当作Object处理,如上面的Point<Integer>point = new Point<>(),我这样用Point point = new Point(),并不会报错,而且会把x、y直接当作Object类型,也算时一种向下兼容,防止以前写的代码出问题。

二、泛型的使用

1.泛型类

泛型类使用起来比较简单,如上面的Point,就是泛型类的一种使用,不过需要注意的是泛型的参数字母可以任意定义,并且参数数量也没有什么限制,在这里就不过多举例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{

private T key;

public Generic(T key) {
this.key = key;
}

public T getKey(){
return key;
}
}

如何实例化泛型类:

1
Generic<Integer> genericInteger = new Generic<Integer>(123456);

2.泛型接口

泛型接口定义和泛型类相似,可以参考如下代码

1
2
3
interface IMessage<T>{
public void print(T t);
}

但是值得注意的是,接口实现时,有两种方式:

  • 继续使用泛型,不指定类型:
1
2
3
4
5
6
7
class IMessageImp <T> implements IMessage<T>{

@Override
public void print(T t) {
// TODO Auto-generated method stub
}
}
  • 使用具体的类型
1
2
3
4
5
6
7
8
9
class IMessageImp implements IMessage<String>{

@Override
public void print(String t) {
// TODO Auto-generated method stub
System.out.println(t);
}

}

两种方式都有使用,需要都掌握

3.泛型方法

泛型方法,个人感觉使用应该不多,但是有必要了解一下,因为在jdk文档当中有一些这样的例子。

1
2
3
public <T> T[] fun(T...t){
return t;
}

4.通配符

通配符是为了配合泛型类等做参数而使用的,为了防止函数只能对一个具体的参数进行处理而产生的。

常用的 T,E,K,V,?

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element
无界通配符

类型通配符一般是使用?代替具体的类型参数。例如 List 在逻辑上是List,List 等所有List<具体类型实参>的父类。

1
2
3
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}

除了上述的?通配符,还衍生出了如下两个通配符:

  • ?extends:设置泛型上限,参数和方法申明上都可以使用

    如:?extends Number,表示只能设置Number或者其子类,例如:Integer、Double,就是说泛型的上限就是Number

1
2
3
4
5
class Point<T extends Number >{

private T x;
private T y;
}
1
2
3
public void fun(List<? extends Number> list){
//无实际意义,只是简单举例
}
  • ?super:设置泛型下限,只能设置在参数上

    如:?super String,表示只能设置String或者其父类,例如:Object,就是说泛型的下限就是String

1
2
3
public void fun(List<?super Number> list){
//无实际意义,只是简单举例
}

泛型3

参考:https://juejin.im/post/6844903917835419661

总结

java泛型以前有所了解但是使用不多,这次系统的复习一下,做个小结。

[toc]

介绍

java的io是一个比较复杂的内容,其中的各种各样的类也是错中复杂,但是最主要的类还是可以分为4个类别:Inputstream、Outstream、Reader、Writer,其中Inputstream、Outstream属于字节流,Reader、Writer属于字符流。

讲解

一、Inputstream

从java文档当中找到java.io.InputStream,可以发现InputStream相关的定义:

1
2
3
public abstract class InputStream
extends Object
implements Closeable

相关函数:

1
2
3
4
5
6
7
8
9
10
11
abstract int read()
//Reads the next byte of data from the input stream.

int read(byte[] b)
//Reads some number of bytes from the input stream and stores them into the buffer array b.

int read(byte[] b, int off, int len)
//Reads up to len bytes of data from the input stream into an array of bytes.

void close()
//Closes this input stream and releases any system resources associated with the stream.

可以发现该类是一个抽象类,并且实现了接口Closeable。进而结合文档描述可以明白此抽象类是表示字节输入流的所有类的超类(父类),想要在程序中使用该类,就需要定义InputStream的子类,并且子类需要实现抽象函数int read()

二、OutputStream

从java文档当中找到java.io.OutputStream 可以发现OutputStream相关的定义:

1
2
3
public abstract class OutputStream
extends Object
implements Closeable, Flushable

相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void	close()
//Closes this output stream and releases any system resources associated with this stream.

void flush()
//Flushes this output stream and forces any buffered output bytes to be written out.

void write(byte[] b)
//Writes b.length bytes from the specified byte array to this output stream.

void write(byte[] b, int off, int len)
//Writes len bytes from the specified byte array starting at offset off to this output stream.

abstract void write(int b)
//Writes the specified byte to this output stream.

Inputstream一样,该类也是一个抽象类,实现了接口CloseableFlushable。进而结合文档描述可以知道该抽象类是表示字节输出流的所有类的超类(父类)。输出流接受输出字节并将其发送到某个接收器。在程序中使用该类 需要定义OutputStream子类,并且该类需要实现抽象方法void write(int b)

三、Reader

从java文档当中找到java.io.Reader,可以发现Reader相关的定义:

1
2
3
public abstract class Reader
extends Object
implements Readable, Closeable

相关函数:

1
2
3
4
5
6
7
8
9
10
11
int	read()
//Reads a single character.

int read(char[] cbuf)
//Reads characters into an array.

abstract int read(char[] cbuf, int off, int len)
//Reads characters into a portion of an array.

abstract void close()
//Closes the stream and releases any system resources associated with it.

和上面两个类一比发现其实差不太多,也是一个抽象类,实现了接口ReadableCloseable。进而结合文档描述可以知道该类是读取字符流的抽象类。子类必须实现的抽象方法是read(char [],int,int)和close()。但是,大多数子类将覆盖此处定义的某些方法,以提供更高的效率和/或附加功能。

四、Writer

从java文档当中找到java.io.Writer,可以发现Writer相关的定义:

1
2
3
public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable

相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract void	close()
//Closes the stream, flushing it first.

abstract void flush()
//Flushes the stream.

void write(char[] cbuf)
//Writes an array of characters.

abstract void write(char[] cbuf, int off, int len)
//Writes a portion of an array of characters.

void write(int c)
//Writes a single character.

void write(String str)
//Writes a string.

void write(String str, int off, int len)
//Writes a portion of a string.

从上向下看来,也就明白了Writer肯定也是一个抽象类,实现了Appendable、Closeable、Flushable这三个接口,然后再结合文档可知该类是用于写入字符流的抽象类。子类必须实现的抽象方法是write(char [],int,int),flush()和close()。但是,大多数子类将覆盖此处定义的某些方法,以提供更高的效率或附加功能。

总结

java中的io类大多都是从这四个类派生而来,所以先介绍下这四个类其实有利于理清很多东西。其中Inputstream、Outstream是一类,操作的主要是字节数组byte[];而Reader、Writer是一类,操作的主要是字符数组char[]。

[toc]

介绍

在IO(一)当中介绍了四个父类,但是四个类都是抽象类,无法直接使用,所以本次讲解一下可以直接使用FileInputstream、FileOutstream、FileReader、FileWriter等,还会讲解一下InputStreamReader和OutputStreamWriter

讲解

一、FileInputstream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。有多种构造方法可用来创建对象。可以使用字符串类型的文件名来创建一个输入流对象来读取文件,也可以使用一个文件对象来创建一个输入流对象来读取文件。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
File f = new File("hello.txt");
try {
//通过字符串
//InputStream in = new FileInputStream("hello.txt");

//通过File文件对象
InputStream in = new FileInputStream(f);
byte [] b = new byte[20];
int temp = in.read(b);
//只展示此函数的功能,其他的相关函数请查找文档
System.out.println("读取的字节数:"+temp);
//字节数组需要转化为字符串再进行输出
System.out.println("读取的内容:"+new String(b));
in.close();
} catch (IOException e) {
//TODO: handle exception
System.out.println(e);
}
}

其中”hello.txt”中的内容为hello world,总共11个字节

io1

输出:

1
2
读取的字节数:11
读取的内容:hello world

可以体会到相对应的功能。在实际写代码当中int read(byte[] b) throws IOException{}这个方法使用的频率最高,也可以看出该函数的功能是从输入流读取b.length长度的字节,并且返回读取的字节数(可能小于b.length),如果是文件结尾则返回-1。

二、FileOutputStream

该类用来创建一个文件并向文件中写数据。如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。有两个构造方法可以用来创建 FileOutputStream 对象。使用字符串类型的文件名来创建一个输出流对象,也可以使用一个文件对象来创建一个输入流对象来读取文件。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
File f = new File("test.txt");
try {
OutputStream out = new FileOutputStream(f);
String s = "test FileOutputStream";
byte [] b = s.getBytes();
out.write(b);
out.close();

} catch (IOException e) {
//TODO: handle exception
System.out.println(e);
}
}

最终test.txt的上的结果为:

io2

上面只是简单的讲解了一下用法,如果想要深入了解其他方法可以自行查找文档。

三、InputStreamReader和OutputStreamWriter

为什么需要先讲InputStreamReader和OutputStreamWriter呢?

InputStreamReader是直接继承自Reader的一个类,而OutputStreamWriter是直接继承Writer,想要弄清楚FileReader、FileWriter就必须先了解InputStreamReader、OutputStreamWriter,因为其实FileReader、FileWriter分别是继承自InputStreamReader、OutputStreamWriter的。因为实际总线中流动的只有字节流,所以字节流的InputStream和OutputStream是一切的基础,Java中负责从字节流向字符流解码的桥梁是InputStreamReaderOutputStreamWriter

1.InputStreamReader

1
2
public class InputStreamReader
extends Reader

构造函数:

1
2
3
4
5
6
7
8
9
10
11
InputStreamReader(InputStream in)
//Creates an InputStreamReader that uses the default charset.

InputStreamReader(InputStream in, Charset cs)
//Creates an InputStreamReader that uses the given charset.

InputStreamReader(InputStream in, CharsetDecoder dec)
//Creates an InputStreamReader that uses the given charset decoder.

InputStreamReader(InputStream in, String charsetName)
//Creates an InputStreamReader that uses the named charset.

通过构造函数可以看到InputStreamReader通过接收一个InputStream,也证明了InputStreamReader是从字节流到字符流的桥梁:它读取字节,并使用指定的字符集将它们解码为字符。它使用的字符集可以按名称指定,也可以明确指定,也可以接受平台的默认字符集。 InputStreamReader的read()方法之一的每次调用都可能导致从基础字节输入流中读取一个或多个字节。为了实现字节到字符的有效转换,与满足当前读取操作所需的字节相比,可以从基础流中提前读取更多的字节。

2.OutputStreamWriter

1
2
public class OutputStreamWriter
extends Writer

构造函数:

1
2
3
4
5
6
7
8
9
10
11
OutputStreamWriter(OutputStream out)
//Creates an OutputStreamWriter that uses the default character encoding.

OutputStreamWriter(OutputStream out, Charset cs)
//Creates an OutputStreamWriter that uses the given charset.

OutputStreamWriter(OutputStream out, CharsetEncoder enc)
//Creates an OutputStreamWriter that uses the given charset encoder.

OutputStreamWriter(OutputStream out, String charsetName)
//Creates an OutputStreamWriter that uses the named charset.

通过构造函数可以看到OutputStreamWriter通过接收一个OutputStream。也证明了OutputStreamWriter是从字符流到字节流的桥梁:使用指定的字符集将写入其中的字符编码为字节。它使用的字符集可以按名称指定,也可以明确指定,也可以接受平台的默认字符集。 每次调用write()方法都会导致在给定字符上调用编码转换器。生成的字节在写入底层输出流之前先在缓冲区中累积。可以指定此缓冲区的大小,但是默认情况下,它对于大多数用途来说足够大。请注意,传递给write()方法的字符不会被缓冲。

四、FileReader

FileReader是直接继承自InputStreamReader的,FileReader的构造方法和FileInputstream差不多,在这里就不展开细讲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
File f = new File("hello.txt");
try {
Reader reader = new FileReader(f);
char[] cbuf = new char[20];
int temp = reader.read(cbuf);
//只展示此函数的功能,其他的相关函数请查找文档
System.out.println("读取的字符数:"+temp);
//字节数组需要转化为字符串再进行输出
System.out.println("读取的内容:"+new String(cbuf));
reader.close();

} catch (IOException e) {
//TODO: handle exception
System.out.println(e);
}
}

hello.txt中内容和上面相同

最终结果:

1
2
读取的字符数:11
读取的内容:hello world

五、FileWriter

FileWriter直接继承自OutputStreamWriter,FileWriter的构造方法和FileOutputStream差不多,在这里就不展开细讲。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
File f = new File("test.txt");
try {
Writer writer = new FileWriter(f);
String s = "test FileWriter";
writer.write(s);
writer.close();

} catch (IOException e) {
//TODO: handle exception
System.out.println(e);
}
}

最终test.txt的上的结果为:

io3

总结

总结了一下java对于文件io的简单操作,除了文件io还有内存流处理,输入类输出类等还需要进行总结。

本次实习因为写java界面,有一个自定义对话框的需求,在网上查了一些才发现了一种方法,所以记录一下。具体需求类似于这样:按下一个按钮弹出一个输入界面,输入行李的长宽高,然后进行显示。需求很简单,最重要的是信息在两个界面之间传递。

阅读全文 »

介绍

在io(二)当中介绍的都是关于文件进行简单的IO处理,除了文件了IO处理之外,其实还有一种内存的IO操作,对于内存的操作流我们称为内存操作类。内存操作流主要有:ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter这四个类

阅读全文 »

介绍

打印流主要解决的就是OutputStream的一些问题,属于OutputStream功能的加强版。

比如:我们只是通过程序向终端输出一些信息,如果使用OutputStream就会产生一些问题,所以数据必须转变为字节数组再输出,输出int、double等类型就不是很方便。这时候我们使用打印流就方便很多。打印流主要是两个类:PrintStreamPrintWriter

阅读全文 »