Groovy 配置环境变量

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/70313790 
本文出自【赵彦军的博客】

概念

Groovy是一种动态语言,它和Java类似(算是Java的升级版,但是又具备脚本语言的特点),都在Java虚拟机中运行。当运行Groovy脚本时它会先被编译成Java类字节码,然后通过JVM虚拟机执行这个Java字节码类。

Groovy 配置环境变量

  • 在 Path 环境变量中添加 Groovy 的bin 目录路径,比如:

D:\soft\apache-groovy-sdk-2.4.10\groovy-2.4.10\bin

如图所示:

  • 用 CMD 打开命令行,执行:groovy -version , 如果看到如下提示,就代表配置成功了。

Groovy Version: 2.4.10 JVM: 1.8.0_112 Vendor: Oracle Corporation OS: Windows 10

如图所示:

开发工具 IntelliJ IDEA

groovy 的开发工具是 IntelliJ IDEA 
下载地址: https://www.jetbrains.com/idea/

安装完成后,新建项目 ,项目类型选择 Groovy ,然后填写 JDK 、Groovy 的安装目录

新建的项目 Groovy 如下图所示:

在 src 目录下,新建包名 groovy , 并且创建 groovy 源文件 Test.groovy,如下图所示:

现在我们在 Test.groovy 中输出一句 helloword ,代码如下:

package groovy

println( "hello world")

运行 Test.groovy 文件 ,如下图所示:

Groovy 语法

基本语法

1、Groovy注释标记和Java一样,支持 //或者/**/

2、Groovy语句可以不用分号结尾。Groovy为了尽量减少代码的输入,确实煞费苦心

3、单引号

单引号” 中的内容严格对应Java中的String,不对 $ 符号进行转义

def s1 = ‘i am 100 $ dolloar‘
println( s1 )

运行结果:

i am 100 $ dolloar

4、双引号

双引号”“的内容则和脚本语言的处理有点像,如果字符中有号的话,则它会表达式先求值。

def x = 100
def s1 = "i am $x dolloar"

println( s1 )

运行结果:

i am 100 dolloar

5、三引号

三个引号”’xxx”’中的字符串支持随意换行 比如

def s1 = ‘‘‘ x
y
z
f
‘‘‘

println(s1)

运行结果:

 x
y
z
f

定义变量

Groovy中支持动态类型,即定义变量的时候可以不指定其类型。Groovy中,变量定义可以使用关键字def。注意,虽然def不是必须的,但是为了代码清晰,建议还是使用def关键字

  • 定义一个变量
def a = 1 //定义一个整形

def b = "字符串" //定义一个字符串

def double c = 1.0  //定义一个 double 类型,也可以指定变量类型

定义函数

无返回类型的函数定义,必须使用def关键字 ,最后一行代码的执行结果就是本函数的返回值

//无参函数
def fun1(){

}

//有参函数 , 无需指定参数类型
def fun2( def1 , def2 ){

}

如果指定了函数返回类型,则可不必加def关键字来定义函数

String fun3(){
    return "返回值"
}

其实,所谓的无返回类型的函数,我估计内部都是按返回Object类型来处理的。毕竟,Groovy 是基于Java 的,而且最终会转成 Java Code 运行在 JVM 上 .

Groovy的函数里,可以不使用return xxx 来设置 xxx 为函数返回值。如果不使用 return 语句的话,则函数里最后一句代码的执行结果被设置成返回值。

def getSomething(){
     "getSomething return value" //如果这是最后一行代码,则返回类型为String
      1000  //如果这是最后一行代码,则返回类型为Integer
}  

除了每行代码不用加分号外,Groovy中函数调用的时候还可以不加括号

例子1:

def s1 = "123"

println(s1)

//或者

println s1 

例子2:

println(fun1())

println fun1()

def fun1(){
    "你好"
}

效果:

你好
你好

断言 assert

  • 断言变量为空
def s1 = null

assert s1

效果如下:

  • 断言变量的长度
def s2 = "abc"

assert s2.size()>3
  • 1
  • 2
  • 3

效果如下:

如果断言发生,断言后面的代码无法执行

循环 for

  • 方式一

for (i = 0; i < 5 ; i++) {
    println("测试")
}

1、运行结果: 输出5个测试 
2、groovy 语法中,注意 i 前面不用指定 int 类型。

  • 方式二
for (i in 0..5){
    println("hello world")
}

这种方式也是一种循环,只不过他输出的是 6 个 hello world , 如果想要输出5个,有3中方式。

第一种方法

for (i in 0..<5){
    println("hello world")
}

第二种方法

for (i in 0..4){
    println("hello world")
}

第三种方法


for (i in 1..5){
    println("hello world")
}

循环 time

times表示从0开始循环到4结束

4.times {
    println it
}

结果:

0
1
2
3

三目运算符

Java 语法

def name
def result = name != null ? name : "abc"
println(result)

groovy 语法

def name = ‘d‘
def result = name?: "abc"

捕获异常

捕获所有的 Exception ,有两种写法

//第一种写法,Java 写法
try {
    println 5 / 0
} catch (Exception e) {

}

//第二种写法,Groovy 写法
try {
    println 5 / 0
} catch (anything) {

}

这里的any并不包括Throwable,如果你真想捕获everything,你必须明确的标明你想捕获Throwable

switch

age = 36
def rate

switch (age) {
    case 10..26:
        rate = 0.05
        break
    case 27..36:
        rate = 0.06
        break
    case 37..46:
        rate = 0.07
        break
    default:
        throw new IllegalArgumentException()
}

println( rate)

判断是否为真

Person person

//Java 写法
if (person!= null){
    if (person.Data!=null){
        println person.Data.name
    }
}

//Groovy
println person?.Data?.name

asType

asType 就是数据类型转换

//String 转成 int
def s2 = s1 as int

//String 转成 int
def s3 = s1.asType(Integer)

Groovy 数据类型

Groovy中的数据类型主要分2种

  • 一个是Java中的基本数据类型。
  • 另外一个是Groovy中的容器类。
  • 最后一个非常重要的是闭包。

Java 基本类型

def boolean s1 = true

def int s2 = 100

def String s3 = "hello world"

if (s1) {
    println("hello world")
}

Groovy 容器

List:链表,其底层对应Java中的List接口,一般用ArrayList作为真正的实现类。 
Map:键-值表,其底层对应Java中的LinkedHashMap。 
Range:范围,它其实是List的一种拓展。

  • List
//变量定义:List变量由[]定义,其元素可以是任何对象
def aList = [5,‘string‘,false]

println(aList)

println aList[0]  //获取第1个数据

println aList[1]  //获取第2个数据

println aList[2]  //获取第3个数据

println aList[3]  //获取第4个数据

println( "集合长度:" + aList.size())

//赋值

aList[10] = 100  //给第10个值赋值

aList<<10 //在 aList 里面添加数据

println aList

println "集合长度:" + aList.size()

效果如下:

[5, string, false]
5
string
false
null
集合长度:3
[5, string, false, null, null, null, null, null, null, null, 100]
集合长度:11
  • map
def map = [key1: "value1", key2: "value2", key3: "value3"]

println map
//[key1:value1, key2:value2, key3:value3]

println("数据长度:" + map.size())
//数据长度:3

println(map.keySet())
//[key1, key2, key3]

println(map.values())
//[value1, value2, value3]

println("key1的值:" + map.key1)
//key1的值:value1

println("key1的值:" + map.get("key1"))
//key1的值:value1

//赋值
map.put("key4", "value4")

Iterator it = map.iterator()
while (it.hasNext()) {
    println "遍历map: " + it.next()
}

//遍历map: key1=value1
//遍历map: key2=value2
//遍历map: key3=value3
//遍历map: key4=value4

map.containsKey("key1") //判断map是否包含某个key

map.containsValue("values1")  //判断map是否包含某个values

map.clear()  //清除map里面的内容

Set set = map.keySet(); //把 map 的key值转换为 set
  • range

Range 是 Groovy 对 List 的一种拓展

def range = 1..5

println(range)
//[1, 2, 3, 4, 5]

range.size()  //长度

range.iterator() //迭代器

def s1 = range.get(1) //获取标号为1的元素

range.contains( 5) //是否包含元素5

range.last() //最后一个元素

range.remove(1) //移除标号为1的元素

range.clear() //清空列表

例子2:

def range = 1..5

println(range)
//[1, 2, 3, 4, 5]

println("第一个数据: "+range.from) //第一个数据
//第一个数据: 1

println("最后一个数据: "+range.to)   //最后一个数据
//最后一个数据: 5

闭包

闭包,英文叫Closure,是Groovy中非常重要的一个数据类型或者说一种概念了。闭包,是一种数据类型,它代表了一段可执行的代码。

def aClosure = {//闭包是一段代码,所以需要用花括号括起来..
    String param1, int param2 ->  //这个箭头很关键。箭头前面是参数定义,箭头后面是代码
    println"this is code" //这是代码,最后一句是返回值,
   //也可以使用return,和Groovy中普通函数一样
}  

简而言之,Closure的定义格式是:

def xxx = {paramters -> code}  

//或者
def xxx = {无参数,纯code}

说实话,从C/C++语言的角度看,闭包和函数指针很像。闭包定义好后,要调用它的方法就是: 
闭包对象.call(参数) 
或者更像函数指针调用的方法: 
闭包对象(参数) 
比如:

aClosure.call("this is string",100)  

//或者
aClosure("this is string", 100)

实例演练,源码如下

def fun1 = {
    p1 ->
        def s = "我是一个闭包," + p1
}

println(fun1.call())  //闭包 调用方式1

println(fun1.call("我是一个参数")) //闭包 调用方式2

println(fun1("我是一个参数2"))

运行结果如下:

我是一个闭包,null
我是一个闭包,我是一个参数
我是一个闭包,我是一个参数2

闭包没定义参数的话,则隐含有一个参数,这个参数名字叫it,和this的作用类似。it代表闭包的参数。

def fun2 = {
    it-> "dsdsd"
}

println( fun2.call())

如果在闭包定义时,采用下面这种写法,则表示闭包没有参数!

def fun3 = {
    -> "dsdsd"
}

println( fun3.call())

如果调用 fun3 的时候传递参数就会报错,比如

fun3.call("d")  //执行这个方法的时候就会报错

省略圆括号

def list = [1,2,3]  //定义一个List

//调用它的each,这段代码的格式看不懂了吧?each是个函数,圆括号去哪了?

list.each {
    println(it)
}

//结果

/**
 * 1
 * 2
 * 3
 */

each函数调用的圆括号不见了!原来,Groovy中,当函数的最后一个参数是闭包的话,可以省略圆括号。比如

def fun(int a1,String b1, Closure closure){
      //dosomething
     closure() //调用闭包
}

那么调用的时候,就可以免括号!

fun (4, "test", {
   println"i am in closure"
}) 

注意,这个特点非常关键,因为以后在Gradle中经常会出现这样的代码:

task hello{
    doLast{
        println("hello world")
    }
}

省略圆括号虽然使得代码简洁,看起来更像脚本语言

Java 属性

Groovy中可以像Java那样写package,然后写类。比如我们在 Person.groovy 文件中编写Java 代码,如下所示:

然后我们新建 Test.groovy 类,写测试工程,如下所:

运行结果如下所示:

Person{name=‘zhaoyanjun‘, age=20}
  • 1

当然,如果不声明public/private等访问权限的话,Groovy中类及其变量默认都是public的.

再识 Groovy

Java中,我们最熟悉的是类。但是我们在Java的一个源码文件中,不能不写class(interface或者其他….),而Groovy可以像写脚本一样,把要做的事情都写在xxx.groovy中,而且可以通过groovy xxx.groovy直接执行这个脚本。这到底是怎么搞的?

既然是基于Java的,Groovy会先把xxx.groovy中的内容转换成一个Java类。

在运行完 Test.groovy 后,发现会产生一个 out 目录,在这个目录里面可以看到 Person.groovy 、Test.groovy 被转换成了 .class 文件,如下图所示:

  • 编译完成后,.groovy 文件都被转换成了 .class 文件,每个 .class 文件都默认有静态 main 方法。每一个脚本都会生成一个static main函数。这样,当我们groovytest.groovy的时候,其实就是用java去执行这个main 函数。
  • 脚本中的所有代码都会放到run函数中。比如,println ‘Groovy world’,这句代码实际上是包含在run函数里的。
  • Test 继承 Script 类。

Script 类

在 groovy 的库文件中,可以看到 Script 类是一个抽象类,继承 GroovyObjectSupport 类,如下所示

脚本变量的作用域

在 Test.groovy 里面定义变量 s1 , 方法 fun1 , 同时在 fun1 方法中输出 s1 , 代码如下:

一运行就报错,错误如下

通过 out 目录,查看 Test.class 类如下:

可以看到在 s1 变量定义在 run 方法中,相当于局部变量,fun1 方法自然无法访问 s1 .

解决方法也很简单 ,就是把 s1 的 def 去掉,代码如下:

通过 out 目录,查看 Test.class 类如下:

上图中 s1 也没有被定义成 Test 的成员函数,而是在 run 的执行过程中,将 s1 作为一个属性添加到 Test 实例对象中了。然后在print s1 中,先获取这个属性。但是从反编译的实际上看,s1 并没有成为 Test.class 的成员变量,其他脚本却无法访问 s1 变量 。

怎样使 s1 彻彻底底变成 Test 的成员变量?

答案也很简单在 s1 前面加上 @Field 字段

@Field s1 = "123" //s1 彻彻底底变成 Test 的成员变量
  • 1

反编译效果如下:

JSON 操作

JsonOutput 类把对象转换成 json字符串。

JsonSlurper 类把 json 字符串转换成对象

定义 Person 实体类

public class Person {

    String name;
    int age;
}

对象转json 、 json 转对象

Person person = new Person();
person.name = "zhaoyanjun"
person.age = 27

//把对象转换为 json 字符串
def json =JsonOutput.toJson(person)

println(json)

JsonSlurper jsonSlurper = new JsonSlurper()

//把字符串转换为对象
Person person1 = jsonSlurper.parseText(json)

println( person1.name )

运行效果如下图:

{"age":27,"name":"zhaoyanjun"}
zhaoyanjun

集合对象转json 、json 转集合对象

Person person = new Person();
person.name = "zhaoyanjun"
person.age = 27

Person person1 = new Person();
person1.name = "zhaoyanjun2"
person1.age = 28

def list = [person,person1]

//把集合对象转换为 json 字符串
def jsonArray =JsonOutput.toJson(list)

println(jsonArray)

JsonSlurper jsonSlurper = new JsonSlurper()

//把字符串转换为集合对象
List<Person> list2 = jsonSlurper.parseText(jsonArray)

println( list2.get(1).name )

运行结果为:

[{"age":27,"name":"zhaoyanjun"},{"age":28,"name":"zhaoyanjun2"}]
zhaoyanjun2

I/O 操作

Groovy的 I/O 操作是在原有Java I/O操作上进行了更为简单方便的封装,并且使用Closure来简化代码编写。虽然比Java看起来简单,但要理解起来其实比较难。

文本文件读

在电脑上新建一个文本文件 test.txt , 内容如下:

今天是星期五
天气很好
明天就要放假了

下面用 groovy 读取里面的文本

def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath) ;

file.eachLine {
    println it
}

你没有看错,就是这么简单,groovy 的文件读取操作简单到令人发指。但是这不够,还有跟令人发指的操作,比如:

def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath) ;

println file.text //输出文本

看到这里,发现 Groovy 操作文件比 Java 简单了 100 倍,苍天啊!

更多用法

  • 指定编码格式
def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath);

//指定编码格式为 utf-8
file.eachLine("utf-8") {
    println it  //读取文本
}
  • 把小写转成大写
def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath);

file.eachLine {
    println( it.toUpperCase() )
}

文本文件写

  • 方式1
def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath);

file.withPrintWriter {
    it.println("测试")
    it.println("hello world")
}
  • 方式2
def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath);

def out = file.newPrintWriter();
out.println("测试")
out.println("hello world")
out.flush()
out.close()

效果如下:

文件夹操作

  • 遍历文件夹中的文件、文件
def filePath = "C:/Users/T/Desktop/123"
def file = new File(filePath);

//遍历 123 文件夹中文件夹
file.eachDir {
    println "文件夹:"+it.name
}

//遍历 123 文件夹中的文件
file.eachFile {
    println "文件:"+ it.name
}

效果如下:

文件夹:1
文件夹:2
文件夹:3
文件:1
文件:2
文件:3
文件:4.txt

深度遍历文件

def filePath = "e:/"
def file = new File(filePath);

//深度遍历目录,也就是遍历目录中的目录
file.eachDirRecurse {
    println it.name
}

//深度遍历文件,包括目录和文件
file.eachFileRecurse {
    println it.path
}

InputStream

def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath) ;

def ism =  file.newInputStream()
//操作ism,最后记得关掉

ism.close

使用闭包操作 inputStream,以后在Gradle里会常看到这种搞法

def filePath = "C:/Users/T/Desktop/test.txt"
def file = new File(filePath) ;

file.withInputStream {ism->
    // 操作ism. 不用close。Groovy会自动替你close
    ism.eachLine {
        println it  //读取文本
    }
}

确实够简单,令人发指。我当年死活也没找到withInputStream是个啥意思。所以,请各位开发者牢记Groovy I/O操作相关类的SDK地址:

xml 解析

在java中解析xml是非常繁琐的,通常需要用10行代码去解析5行的xml文件,非常不经济。在groovy 中解析xml 就很方便了。

实例1 解析简单xml

比如下面一段xml

<?xml version="1.0"?>

<langs type="current">
<language>Java</language>
<language>Groovy</language>
<language>JavaScript</language>
</langs>

groovy 解析如下:

//获取 xml 文件的 langs 节点
def langs = new XmlParser().parse("C:/Users/T/Desktop/test.xml")

//获取type 字段的值
def type = langs.attribute("type")

println type

langs.language.each{
    println it.text()
}

结果如下:

current
Java
Groovy
JavaScript

实例2 解析复杂 xml

xml 如下图所示:

<?xml version="1.0" encoding="UTF-8"?>
<metadata>

<groupId>com.yiba.sdk</groupId>
<artifactId>weshareWiFiSDk</artifactId>
<version>2.3.3</version>

<versioning>
<latest>2.3.3</latest>

    <versions>
    <version>2.2.7</version>
    <version>2.3.0</version>
    <version>2.3.1</version>
    <version>2.3.2</version>
    <version>2.3.3</version>
    </versions>
</versioning>

</metadata>

解析代码如下:

//获取metadata节点
def metadata = new XmlParser().parse("C:/Users/T/Desktop/test1.xml")

//获取metadata下面的 groupId 属性值
def groupId = metadata.groupId.text()

//获取metadata下面的 artifactId 属性值
def artifactId = metadata.artifactId.text()

//获取metadata下面的 version 属性值
def version = metadata.version.text()

println groupId + " " + artifactId + " " + version

//获取metadata下面的 versioning 节点
def versioning = metadata.versioning

//获取versioning 下面的 latest 属性值
println versioning.latest.text()

//获取versioning 下面的 versions 节点
def versions = versioning.versions
versions.version.each{
    //遍历 versions 下面的version 值
    println "version" + it.text()
}

结果如下:

com.yiba.sdk weshareWiFiSDk 2.3.3
2.3.3
version2.2.7
version2.3.0
version2.3.1
version2.3.2
version2.3.3

原文地址:https://www.cnblogs.com/hkgov/p/8494684.html

时间: 2024-11-09 01:59:31

Groovy 配置环境变量的相关文章

windows上安装Gradle并配置环境变量

安装Gradle 下载Gradle,然后配置运行环境就可以了,有一点要注意的是gradle使用的是Groovy语言,而这个语言依赖于java,因此你必须安装配置java环境. 首先下载gradle,我们直接到官网下载一个gradle版本的压缩包 下载网址: http://services.gradle.org/distributions/ 下载完成之后,随意找个目录解压缩,我这里选择 D:\Program Files\gradle\gradle-4.1 然后就是配置环境变量了,点击我的电脑 -

androidSDK配置环境变量

android的开发人员来说,首先要做的就是环境变量的配置.java是需要配置环境变量的.当然,安卓的环境变量需要我们配置adb的使用,将开发平台的两个工具包配置到环境变量里. 工具/原料 android SDK 方法/步骤 在我的电脑右键[属性]打开,点击[高级系统设置]如图.转到第二步. 点击[环境变量].转第三步. 在系统变量里找到Path选中(图中蓝色那条),点击下面的[编辑].转第四步. 在变量值里加入androidSDK中platform-tools和tools的目录路径,这里我的是

java环境安装、配置环境变量、java工具介绍

1.java的安装 1.在官网下载JDK安装包,链接如下: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 2.安装JDK,按照默认的方式安装JDK,只需一直点击下一步即可,此步省略 3.JDK安装成功之后,配置系统环境变量,通过右键点击计算机以此点击属性.高级系统设置.配置环境变量如下图所示: 4.点击环境变量之后会弹出一下窗口,点击新建 5.点击新建窗口之后分别输入JAVA

Linux环境下安装JDK并配置环境变量

首先查看是否已经安装了JDK并配置环境变量. [[email protected]_plat opt]# java -version -bash: java: command not found [[email protected]_plat opt]# 说明没有安装JDK或者配置环境变量. 安装JDK: Linux环境JDK安装比较简单,直接解压到指定目录即可. 1.上传安装介质至服务器,我下载的是tar.gz版本的JDK: 2.在上传目录中执行tar命令安装: tar -zxvf jdk-7

centos配置环境变量

配置环境变量 进入环境变量配置目录,这里需要切换到root用户:cd /etc/profile.d 创建配置文件:touch hive-1.1.0.sh 打开配置文件:vi hive-1.1.0.sh 配置环境变量: #set hive environment HIVE_HOME=/home/hadoop/hive-1.1.0 PATH=$HIVE_HOME/bin:$PATH CLASSPATH=$CLASSPATH:$HIVE_HOME/lib export HIVE_HOME export

Java学习 (一)、下载,配置环境变量,第一个demo

一.在 http://www.oracle.com 下载java JDK 安装到自定义的地方. 二.配置环境变量:在我的电脑→高级系统设置→环境变量 ① 找到Path新增一个路径(该路径为JDK存放的位置的bin,比如我放在D:\J2EE\JDK1.8.0\bin 下),两个路径之间要加分号隔开 ② 为了防止路径改变,可以新增一个变量,变量名自定义,变量值为JDK存放路径. 然后在Path 下新增一个路径为 %JAVA_HOME%\bin  即可. 保存后 运行(win+R) cmd 键入jav

hadoop搭建杂记:Linux下JDK环境变量的设置(三种配置环境变量的方法)

Linux下JDK环境变量的设置(三种配置环境变量的方法) Linux下JDK环境变量的设置(三种配置环境变量的方法) ①修改/etc/profile文件 如果你的计算机仅仅作为开发使用时推荐使用这种方法,因为所有用户的shell都有权使用这些环境变量,可能会给系统带来安全性问题. vi /etc/profile 在profile文件末尾加入: JAVA_HOME=/usr/share/jdk1.5.0_05PATH=$JAVA_HOME/bin:$PATHCLASSPATH=.:$JAVA_H

macOS Sierra 安装JRE8,配置环境变量

我的macOS Sierra版本号10.12.2,安装的JRE包为:jre-8u112-macosx-x64.dmg 双击正常安装后,在终端下输入java -version并不会正常显示出版本,需要配置环境变量. 方法如下: 打开终端,输入下面的在当前用户目录下创建一个配置文件: vi ~/.bash_profile  点击 i 键进入编辑模式,粘贴以下内容: JAVA_HOME="/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Conten

安装jdk配置环境变量JAVA_HOME不起作用

今天重新安装系统,需要装jdk,配置环境变量,于是先配置JAVA_HOME  D:\Program Files\Java\jdk1.8.0_144, 然后在配置path路径,但是cmd到dos命令行输入javac和java命令不起作用,于是配置path的绝对路径,可以了,但是 还是想知道为什么,于是在csdn上面找到了答案,原来我把JAVA_HOME配置到了用户变量,而path是在系统变量,所以 引用不到JAVA_HOME,于是将JAVA_HOME配置到系统变量,问题解决了. http://bb